Refactor Course Catalog fields in CourseDescriptor
This commit is contained in:
@@ -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
|
||||
classes, which both need these type of functions.
|
||||
"""
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
from base64 import b32encode
|
||||
from datetime import datetime, timedelta
|
||||
import dateutil.parser
|
||||
from math import exp
|
||||
|
||||
from django.utils.timezone import UTC
|
||||
|
||||
@@ -222,3 +223,43 @@ def may_certify_for_course(certificates_display_behavior, certificates_show_befo
|
||||
or certificates_show_before_end
|
||||
)
|
||||
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
|
||||
"""
|
||||
import logging
|
||||
from cStringIO import StringIO
|
||||
from math import exp
|
||||
from lxml import etree
|
||||
from path import Path as path
|
||||
import requests
|
||||
from datetime import datetime
|
||||
import dateutil.parser
|
||||
from lazy import lazy
|
||||
|
||||
from xmodule import course_metadata_utils
|
||||
@@ -1264,7 +1262,9 @@ class CourseDescriptor(CourseFields, SequenceDescriptor, LicenseMixin):
|
||||
flag = self.is_new
|
||||
if flag is None:
|
||||
# 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:
|
||||
# The course has been announced for less that month
|
||||
return True
|
||||
@@ -1284,41 +1284,11 @@ class CourseDescriptor(CourseFields, SequenceDescriptor, LicenseMixin):
|
||||
Returns a tuple that can be used to sort the courses according
|
||||
the how "new" they are. The "newness" score is computed using a
|
||||
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.
|
||||
"""
|
||||
# Make courses that have an announcement date shave a lower
|
||||
# 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
|
||||
return course_metadata_utils.sorting_score(self.start, self.advertised_start, self.announcement)
|
||||
|
||||
@lazy
|
||||
def grading_context(self):
|
||||
|
||||
@@ -150,14 +150,14 @@ class IsNewCourseTestCase(unittest.TestCase):
|
||||
|
||||
# Needed for test_is_newish
|
||||
datetime_patcher = patch.object(
|
||||
xmodule.course_module, 'datetime',
|
||||
xmodule.course_metadata_utils, 'datetime',
|
||||
Mock(wraps=datetime)
|
||||
)
|
||||
mocked_datetime = datetime_patcher.start()
|
||||
mocked_datetime.now.return_value = NOW
|
||||
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):
|
||||
gmtime_mock.return_value = NOW
|
||||
|
||||
@@ -208,7 +208,7 @@ class IsNewCourseTestCase(unittest.TestCase):
|
||||
(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):
|
||||
gmtime_mock.return_value = NOW
|
||||
for s in self.start_advertised_settings:
|
||||
@@ -216,7 +216,7 @@ class IsNewCourseTestCase(unittest.TestCase):
|
||||
print "Checking start=%s advertised=%s" % (s[0], s[1])
|
||||
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):
|
||||
gmtime_mock.return_value = NOW
|
||||
for setting in self.start_advertised_settings:
|
||||
|
||||
Reference in New Issue
Block a user