Commit aef4da86 by Nimisha Asthagiri

Refactor Course Catalog fields in CourseDescriptor

parent 8289c321
...@@ -5,9 +5,10 @@ This is a place to put simple functions that operate on course metadata. It ...@@ -5,9 +5,10 @@ This is a place to put simple functions that operate on course metadata. It
allows us to share code between the CourseDescriptor and CourseOverview allows us to share code between the CourseDescriptor and CourseOverview
classes, which both need these type of functions. classes, which both need these type of functions.
""" """
from datetime import datetime
from datetime import timedelta
from base64 import b32encode from base64 import b32encode
from datetime import datetime, timedelta
import dateutil.parser
from math import exp
from django.utils.timezone import UTC from django.utils.timezone import UTC
...@@ -222,3 +223,43 @@ def may_certify_for_course(certificates_display_behavior, certificates_show_befo ...@@ -222,3 +223,43 @@ def may_certify_for_course(certificates_display_behavior, certificates_show_befo
or certificates_show_before_end or certificates_show_before_end
) )
return show_early or has_ended return show_early or has_ended
def sorting_score(start, advertised_start, announcement):
"""
Returns a tuple that can be used to sort the courses according
to how "new" they are. The "newness" score is computed using a
heuristic that takes into account the announcement and
(advertised) start dates of the course if available.
The lower the number the "newer" the course.
"""
# Make courses that have an announcement date have a lower
# score than courses than don't, older courses should have a
# higher score.
announcement, start, now = sorting_dates(start, advertised_start, announcement)
scale = 300.0 # about a year
if announcement:
days = (now - announcement).days
score = -exp(-days / scale)
else:
days = (now - start).days
score = exp(days / scale)
return score
def sorting_dates(start, advertised_start, announcement):
"""
Utility function to get datetime objects for dates used to
compute the is_new flag and the sorting_score.
"""
try:
start = dateutil.parser.parse(advertised_start)
if start.tzinfo is None:
start = start.replace(tzinfo=UTC())
except (ValueError, AttributeError):
start = start
now = datetime.now(UTC())
return announcement, start, now
...@@ -3,12 +3,10 @@ Django module container for classes and operations related to the "Course Module ...@@ -3,12 +3,10 @@ Django module container for classes and operations related to the "Course Module
""" """
import logging import logging
from cStringIO import StringIO from cStringIO import StringIO
from math import exp
from lxml import etree from lxml import etree
from path import Path as path from path import Path as path
import requests import requests
from datetime import datetime from datetime import datetime
import dateutil.parser
from lazy import lazy from lazy import lazy
from xmodule import course_metadata_utils from xmodule import course_metadata_utils
...@@ -1264,7 +1262,9 @@ class CourseDescriptor(CourseFields, SequenceDescriptor, LicenseMixin): ...@@ -1264,7 +1262,9 @@ class CourseDescriptor(CourseFields, SequenceDescriptor, LicenseMixin):
flag = self.is_new flag = self.is_new
if flag is None: if flag is None:
# Use a heuristic if the course has not been flagged # Use a heuristic if the course has not been flagged
announcement, start, now = self._sorting_dates() announcement, start, now = course_metadata_utils.sorting_dates(
self.start, self.advertised_start, self.announcement
)
if announcement and (now - announcement).days < 30: if announcement and (now - announcement).days < 30:
# The course has been announced for less that month # The course has been announced for less that month
return True return True
...@@ -1284,41 +1284,11 @@ class CourseDescriptor(CourseFields, SequenceDescriptor, LicenseMixin): ...@@ -1284,41 +1284,11 @@ class CourseDescriptor(CourseFields, SequenceDescriptor, LicenseMixin):
Returns a tuple that can be used to sort the courses according Returns a tuple that can be used to sort the courses according
the how "new" they are. The "newness" score is computed using a the how "new" they are. The "newness" score is computed using a
heuristic that takes into account the announcement and heuristic that takes into account the announcement and
(advertized) start dates of the course if available. (advertised) start dates of the course if available.
The lower the number the "newer" the course. The lower the number the "newer" the course.
""" """
# Make courses that have an announcement date shave a lower return course_metadata_utils.sorting_score(self.start, self.advertised_start, self.announcement)
# score than courses than don't, older courses should have a
# higher score.
announcement, start, now = self._sorting_dates()
scale = 300.0 # about a year
if announcement:
days = (now - announcement).days
score = -exp(-days / scale)
else:
days = (now - start).days
score = exp(days / scale)
return score
def _sorting_dates(self):
# utility function to get datetime objects for dates used to
# compute the is_new flag and the sorting_score
announcement = self.announcement
if announcement is not None:
announcement = announcement
try:
start = dateutil.parser.parse(self.advertised_start)
if start.tzinfo is None:
start = start.replace(tzinfo=UTC())
except (ValueError, AttributeError):
start = self.start
now = datetime.now(UTC())
return announcement, start, now
@lazy @lazy
def grading_context(self): def grading_context(self):
......
...@@ -150,14 +150,14 @@ class IsNewCourseTestCase(unittest.TestCase): ...@@ -150,14 +150,14 @@ class IsNewCourseTestCase(unittest.TestCase):
# Needed for test_is_newish # Needed for test_is_newish
datetime_patcher = patch.object( datetime_patcher = patch.object(
xmodule.course_module, 'datetime', xmodule.course_metadata_utils, 'datetime',
Mock(wraps=datetime) Mock(wraps=datetime)
) )
mocked_datetime = datetime_patcher.start() mocked_datetime = datetime_patcher.start()
mocked_datetime.now.return_value = NOW mocked_datetime.now.return_value = NOW
self.addCleanup(datetime_patcher.stop) self.addCleanup(datetime_patcher.stop)
@patch('xmodule.course_module.datetime.now') @patch('xmodule.course_metadata_utils.datetime.now')
def test_sorting_score(self, gmtime_mock): def test_sorting_score(self, gmtime_mock):
gmtime_mock.return_value = NOW gmtime_mock.return_value = NOW
...@@ -208,7 +208,7 @@ class IsNewCourseTestCase(unittest.TestCase): ...@@ -208,7 +208,7 @@ class IsNewCourseTestCase(unittest.TestCase):
(xmodule.course_module.CourseFields.start.default, 'January 2014', 'January 2014', False, 'January 2014'), (xmodule.course_module.CourseFields.start.default, 'January 2014', 'January 2014', False, 'January 2014'),
] ]
@patch('xmodule.course_module.datetime.now') @patch('xmodule.course_metadata_utils.datetime.now')
def test_start_date_text(self, gmtime_mock): def test_start_date_text(self, gmtime_mock):
gmtime_mock.return_value = NOW gmtime_mock.return_value = NOW
for s in self.start_advertised_settings: for s in self.start_advertised_settings:
...@@ -216,7 +216,7 @@ class IsNewCourseTestCase(unittest.TestCase): ...@@ -216,7 +216,7 @@ class IsNewCourseTestCase(unittest.TestCase):
print "Checking start=%s advertised=%s" % (s[0], s[1]) print "Checking start=%s advertised=%s" % (s[0], s[1])
self.assertEqual(d.start_datetime_text(), s[2]) self.assertEqual(d.start_datetime_text(), s[2])
@patch('xmodule.course_module.datetime.now') @patch('xmodule.course_metadata_utils.datetime.now')
def test_start_date_time_text(self, gmtime_mock): def test_start_date_time_text(self, gmtime_mock):
gmtime_mock.return_value = NOW gmtime_mock.return_value = NOW
for setting in self.start_advertised_settings: for setting in self.start_advertised_settings:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment