From 8f21d7a738a415d58acc8fb7d0712caad7e7ee43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Andr=C3=A9s=20Rocha?= Date: Thu, 10 Jan 2013 14:48:27 -0500 Subject: [PATCH] Add property to course module to check if a course is new The property can be set in the policy metadata. If it is not specified then it is set to true if the course has not started yet. Also adds a property to check how many days are left until the course starts. --- common/djangoapps/student/views.py | 2 +- common/lib/xmodule/xmodule/course_module.py | 41 +++++++-- .../xmodule/tests/test_course_module.py | 90 +++++++++++++++++++ lms/djangoapps/courseware/courses.py | 30 ------- lms/djangoapps/courseware/views.py | 4 +- lms/templates/course.html | 2 +- 6 files changed, 130 insertions(+), 39 deletions(-) create mode 100644 common/lib/xmodule/xmodule/tests/test_course_module.py diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 06c59d7937..39805fd85f 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -78,7 +78,7 @@ def index(request, extra_context={}, user=None): courses = get_courses(None, domain=domain) # Sort courses by how far are they from they start day - key = lambda course: course.metadata['days_to_start'] + key = lambda course: course.days_until_start courses = sorted(courses, key=key, reverse=True) # Get the 3 most recent news diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 5253d2976f..163e40b343 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -1,9 +1,9 @@ -from fs.errors import ResourceNotFoundError import logging from lxml import etree -from path import path # NOTE (THK): Only used for detecting presence of syllabus +from path import path # NOTE (THK): Only used for detecting presence of syllabus import requests import time +from datetime import datetime from xmodule.util.decorators import lazyproperty from xmodule.graders import load_grading_policy @@ -13,6 +13,7 @@ from xmodule.timeparse import parse_time, stringify_time log = logging.getLogger(__name__) + class CourseDescriptor(SequenceDescriptor): module_class = SequenceModule @@ -165,6 +166,38 @@ class CourseDescriptor(SequenceDescriptor): def show_calculator(self): return self.metadata.get("show_calculator", None) == "Yes" + @property + def is_new(self): + # The course is "new" if either if the metadata flag is_new is + # true or if the course has not started yet + flag = self.metadata.get('is_new', None) + if flag is None: + return self.days_until_start > 1 + elif isinstance(flag, basestring): + return flag.lower() in ['true', 'yes', 'y'] + else: + return bool(flag) + + @property + def days_until_start(self): + def convert_to_datetime(timestamp): + return datetime.fromtimestamp(time.mktime(timestamp)) + + start_date = convert_to_datetime(self.start) + + # Try to use course advertised date if we can parse it + advertised_start = self.metadata.get('advertised_start', None) + if advertised_start: + try: + start_date = datetime.strptime(advertised_start, + "%Y-%m-%dT%H:%M") + except ValueError: + pass # Invalid date, keep using 'start'' + + now = convert_to_datetime(time.gmtime()) + days_until_start = (start_date - now).days + return days_until_start + @lazyproperty def grading_context(self): """ @@ -244,7 +277,6 @@ class CourseDescriptor(SequenceDescriptor): raise ValueError("{0} is not a course location".format(loc)) return "/".join([loc.org, loc.course, loc.name]) - @property def id(self): """Return the course_id for this course""" @@ -258,7 +290,7 @@ class CourseDescriptor(SequenceDescriptor): # form text... if parsed_advertised_start is None and \ ('advertised_start' in self.metadata): - return self.metadata['advertised_start'] + return self.metadata['advertised_start'] displayed_start = parsed_advertised_start or self.start @@ -341,4 +373,3 @@ class CourseDescriptor(SequenceDescriptor): @property def org(self): return self.location.org - diff --git a/common/lib/xmodule/xmodule/tests/test_course_module.py b/common/lib/xmodule/xmodule/tests/test_course_module.py new file mode 100644 index 0000000000..63eaec1f61 --- /dev/null +++ b/common/lib/xmodule/xmodule/tests/test_course_module.py @@ -0,0 +1,90 @@ +import unittest +from time import strptime, gmtime +from fs.memoryfs import MemoryFS + +from mock import Mock, patch + +from xmodule.modulestore.xml import ImportSystem, XMLModuleStore + + +ORG = 'test_org' +COURSE = 'test_course' + +NOW = strptime('2013-01-01T01:00:00', '%Y-%m-%dT%H:%M:00') + + +class DummySystem(ImportSystem): + @patch('xmodule.modulestore.xml.OSFS', lambda dir: MemoryFS()) + def __init__(self, load_error_modules): + + xmlstore = XMLModuleStore("data_dir", course_dirs=[], + load_error_modules=load_error_modules) + course_id = "/".join([ORG, COURSE, 'test_run']) + course_dir = "test_dir" + policy = {} + error_tracker = Mock() + parent_tracker = Mock() + + super(DummySystem, self).__init__( + xmlstore, + course_id, + course_dir, + policy, + error_tracker, + parent_tracker, + load_error_modules=load_error_modules, + ) + + +class IsNewCourseTestCase(unittest.TestCase): + """Make sure the property is_new works on courses""" + @staticmethod + def get_dummy_course(start, is_new=None, load_error_modules=True): + """Get a dummy course""" + + system = DummySystem(load_error_modules) + is_new = '' if is_new is None else 'is_new="{0}"'.format(is_new).lower() + + start_xml = ''' + + + Two houses, ... + + + '''.format(org=ORG, course=COURSE, start=start, is_new=is_new) + + return system.process_xml(start_xml) + + @patch('xmodule.course_module.time.gmtime') + def test_non_started_yet(self, gmtime_mock): + descriptor = self.get_dummy_course(start='2013-01-05T12:00') + gmtime_mock.return_value = NOW + assert(descriptor.is_new == True) + assert(descriptor.days_until_start == 4) + + @patch('xmodule.course_module.time.gmtime') + def test_already_started(self, gmtime_mock): + gmtime_mock.return_value = NOW + + descriptor = self.get_dummy_course(start='2012-12-02T12:00') + assert(descriptor.is_new == False) + assert(descriptor.days_until_start < 0) + + @patch('xmodule.course_module.time.gmtime') + def test_is_new_set(self, gmtime_mock): + gmtime_mock.return_value = NOW + + descriptor = self.get_dummy_course(start='2012-12-02T12:00', is_new=True) + assert(descriptor.is_new == True) + assert(descriptor.days_until_start < 0) + + descriptor = self.get_dummy_course(start='2013-02-02T12:00', is_new=False) + assert(descriptor.is_new == False) + assert(descriptor.days_until_start > 0) + + descriptor = self.get_dummy_course(start='2013-02-02T12:00', is_new=True) + assert(descriptor.is_new == True) + assert(descriptor.days_until_start > 0) diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py index dc530bdebc..7c0d30ebd8 100644 --- a/lms/djangoapps/courseware/courses.py +++ b/lms/djangoapps/courseware/courses.py @@ -233,35 +233,5 @@ def get_courses(user, domain=None): courses = branding.get_visible_courses(domain) courses = [c for c in courses if has_access(user, c, 'see_exists')] - # Add metadata about the start day and if the course is new - for course in courses: - days_to_start = _get_course_days_to_start(course) - - metadata = course.metadata - metadata['days_to_start'] = days_to_start - metadata['is_new'] = course.metadata.get('is_new', days_to_start > 1) - courses = sorted(courses, key=lambda course:course.number) return courses - - -def _get_course_days_to_start(course): - from datetime import datetime as dt - from time import mktime, gmtime - - convert_to_datetime = lambda ts: dt.fromtimestamp(mktime(ts)) - - start_date = convert_to_datetime(course.start) - - # If the course has a valid advertised date, use that instead - advertised_start = course.metadata.get('advertised_start', None) - if advertised_start: - try: - start_date = dt.strptime(advertised_start, "%Y-%m-%dT%H:%M") - except ValueError: - pass # Invalid date, keep using course.start - - now = convert_to_datetime(gmtime()) - days_to_start = (start_date - now).days - - return days_to_start diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index f6e87dfe9f..9e52e2b281 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -70,7 +70,7 @@ def courses(request): courses = get_courses(request.user, domain=request.META.get('HTTP_HOST')) # Sort courses by how far are they from they start day - key = lambda course: course.metadata['days_to_start'] + key = lambda course: course.days_until_start courses = sorted(courses, key=key, reverse=True) return render_to_response("courseware/courses.html", {'courses': courses}) @@ -440,7 +440,7 @@ def university_profile(request, org_id): domain=request.META.get('HTTP_HOST'))[org_id] # Sort courses by how far are they from they start day - key = lambda course: course.metadata['days_to_start'] + key = lambda course: course.days_until_start courses = sorted(courses, key=key, reverse=True) context = dict(courses=courses, org_id=org_id) diff --git a/lms/templates/course.html b/lms/templates/course.html index a3217d2da5..a2eff572e1 100644 --- a/lms/templates/course.html +++ b/lms/templates/course.html @@ -5,7 +5,7 @@ %> <%page args="course" />
- %if course.metadata.get('is_new'): + %if course.is_new: New %endif