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.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
90
common/lib/xmodule/xmodule/tests/test_course_module.py
Normal file
90
common/lib/xmodule/xmodule/tests/test_course_module.py
Normal file
@@ -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 = '''
|
||||
<course org="{org}" course="{course}"
|
||||
graceperiod="1 day" url_name="test"
|
||||
start="{start}"
|
||||
{is_new}>
|
||||
<chapter url="hi" url_name="ch" display_name="CH">
|
||||
<html url_name="h" display_name="H">Two houses, ...</html>
|
||||
</chapter>
|
||||
</course>
|
||||
'''.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)
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%>
|
||||
<%page args="course" />
|
||||
<article id="${course.id}" class="course">
|
||||
%if course.metadata.get('is_new'):
|
||||
%if course.is_new:
|
||||
<span class="status">New</span>
|
||||
%endif
|
||||
<a href="${reverse('about_course', args=[course.id])}">
|
||||
|
||||
Reference in New Issue
Block a user