Commit 522095e1 by Bill DeRusha

Auto setting of verification deadlines with manual overrides

parent ef4f39fe
...@@ -14,7 +14,7 @@ from opaque_keys import InvalidKeyError ...@@ -14,7 +14,7 @@ from opaque_keys import InvalidKeyError
from util.date_utils import get_time_display from util.date_utils import get_time_display
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from course_modes.models import CourseMode from course_modes.models import CourseMode, CourseModeExpirationConfig
# Technically, we shouldn't be doing this, since verify_student is defined # Technically, we shouldn't be doing this, since verify_student is defined
# in LMS, and course_modes is defined in common. # in LMS, and course_modes is defined in common.
...@@ -66,12 +66,13 @@ class CourseModeForm(forms.ModelForm): ...@@ -66,12 +66,13 @@ class CourseModeForm(forms.ModelForm):
default_tz = timezone(settings.TIME_ZONE) default_tz = timezone(settings.TIME_ZONE)
if self.instance.expiration_datetime: if self.instance._expiration_datetime: # pylint: disable=protected-access
# django admin is using default timezone. To avoid time conversion from db to form # django admin is using default timezone. To avoid time conversion from db to form
# convert the UTC object to naive and then localize with default timezone. # convert the UTC object to naive and then localize with default timezone.
expiration_datetime = self.instance.expiration_datetime.replace(tzinfo=None) _expiration_datetime = self.instance._expiration_datetime.replace( # pylint: disable=protected-access
self.initial["expiration_datetime"] = default_tz.localize(expiration_datetime) tzinfo=None
)
self.initial["_expiration_datetime"] = default_tz.localize(_expiration_datetime)
# Load the verification deadline # Load the verification deadline
# Since this is stored on a model in verify student, we need to load it from there. # Since this is stored on a model in verify student, we need to load it from there.
# We need to munge the timezone a bit to get Django admin to display it without converting # We need to munge the timezone a bit to get Django admin to display it without converting
...@@ -99,14 +100,14 @@ class CourseModeForm(forms.ModelForm): ...@@ -99,14 +100,14 @@ class CourseModeForm(forms.ModelForm):
return course_key return course_key
def clean_expiration_datetime(self): def clean__expiration_datetime(self):
""" """
Ensure that the expiration datetime we save uses the UTC timezone. Ensure that the expiration datetime we save uses the UTC timezone.
""" """
# django admin saving the date with default timezone to avoid time conversion from form to db # django admin saving the date with default timezone to avoid time conversion from form to db
# changes its tzinfo to UTC # changes its tzinfo to UTC
if self.cleaned_data.get("expiration_datetime"): if self.cleaned_data.get("_expiration_datetime"):
return self.cleaned_data.get("expiration_datetime").replace(tzinfo=UTC) return self.cleaned_data.get("_expiration_datetime").replace(tzinfo=UTC)
def clean_verification_deadline(self): def clean_verification_deadline(self):
""" """
...@@ -122,7 +123,7 @@ class CourseModeForm(forms.ModelForm): ...@@ -122,7 +123,7 @@ class CourseModeForm(forms.ModelForm):
""" """
cleaned_data = super(CourseModeForm, self).clean() cleaned_data = super(CourseModeForm, self).clean()
mode_slug = cleaned_data.get("mode_slug") mode_slug = cleaned_data.get("mode_slug")
upgrade_deadline = cleaned_data.get("expiration_datetime") upgrade_deadline = cleaned_data.get("_expiration_datetime")
verification_deadline = cleaned_data.get("verification_deadline") verification_deadline = cleaned_data.get("verification_deadline")
# Allow upgrade deadlines ONLY for the "verified" mode # Allow upgrade deadlines ONLY for the "verified" mode
...@@ -181,7 +182,7 @@ class CourseModeAdmin(admin.ModelAdmin): ...@@ -181,7 +182,7 @@ class CourseModeAdmin(admin.ModelAdmin):
'mode_display_name', 'mode_display_name',
'min_price', 'min_price',
'currency', 'currency',
'expiration_datetime', '_expiration_datetime',
'verification_deadline', 'verification_deadline',
'sku' 'sku'
) )
...@@ -206,4 +207,12 @@ class CourseModeAdmin(admin.ModelAdmin): ...@@ -206,4 +207,12 @@ class CourseModeAdmin(admin.ModelAdmin):
# in the Django admin list view. # in the Django admin list view.
expiration_datetime_custom.short_description = "Upgrade Deadline" expiration_datetime_custom.short_description = "Upgrade Deadline"
class CourseModeExpirationConfigAdmin(admin.ModelAdmin):
"""Admin interface for the course mode auto expiration configuration. """
class Meta(object):
model = CourseModeExpirationConfig
admin.site.register(CourseMode, CourseModeAdmin) admin.site.register(CourseMode, CourseModeAdmin)
admin.site.register(CourseModeExpirationConfig, CourseModeExpirationConfigAdmin)
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course_modes', '0002_coursemode_expiration_datetime_is_explicit'),
]
operations = [
migrations.AlterField(
model_name='coursemode',
name='expiration_datetime_is_explicit',
field=models.BooleanField(default=False),
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from datetime import timedelta
from django.db import migrations, models
import django.db.models.deletion
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('course_modes', '0003_auto_20151113_1443'),
]
operations = [
migrations.CreateModel(
name='CourseModeExpirationConfig',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')),
('enabled', models.BooleanField(default=False, verbose_name='Enabled')),
('verification_window', models.DurationField(default=timedelta(10), help_text='The time period before a course ends in which a course mode will expire')),
('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, editable=False, to=settings.AUTH_USER_MODEL, null=True, verbose_name='Changed by')),
],
options={
'ordering': ('-change_date',),
'abstract': False,
},
),
]
""" """
Add and create new modes for running courses on this particular LMS Add and create new modes for running courses on this particular LMS
""" """
from datetime import datetime, timedelta
import pytz import pytz
from datetime import datetime
from collections import namedtuple, defaultdict
from config_models.models import ConfigurationModel
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from collections import namedtuple, defaultdict
from django.utils.translation import ugettext_lazy as _
from django.db.models import Q from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from xmodule_django.models import CourseKeyField from xmodule_django.models import CourseKeyField
Mode = namedtuple('Mode', Mode = namedtuple('Mode',
...@@ -54,19 +54,20 @@ class CourseMode(models.Model): ...@@ -54,19 +54,20 @@ class CourseMode(models.Model):
# For example, if there is a verified mode that expires on 1/1/2015, # For example, if there is a verified mode that expires on 1/1/2015,
# then users will be able to upgrade into the verified mode before that date. # then users will be able to upgrade into the verified mode before that date.
# Once the date passes, users will no longer be able to enroll as verified. # Once the date passes, users will no longer be able to enroll as verified.
expiration_datetime = models.DateTimeField( _expiration_datetime = models.DateTimeField(
default=None, null=True, blank=True, default=None, null=True, blank=True,
verbose_name=_(u"Upgrade Deadline"), verbose_name=_(u"Upgrade Deadline"),
help_text=_( help_text=_(
u"OPTIONAL: After this date/time, users will no longer be able to enroll in this mode. " u"OPTIONAL: After this date/time, users will no longer be able to enroll in this mode. "
u"Leave this blank if users can enroll in this mode until enrollment closes for the course." u"Leave this blank if users can enroll in this mode until enrollment closes for the course."
), ),
db_column='expiration_datetime',
) )
# The system prefers to set this automatically based on default settings. But # The system prefers to set this automatically based on default settings. But
# if the field is set manually we want a way to indicate that so we don't # if the field is set manually we want a way to indicate that so we don't
# overwrite the manual setting of the field. # overwrite the manual setting of the field.
expiration_datetime_is_explicit = models.BooleanField(default=True) expiration_datetime_is_explicit = models.BooleanField(default=False)
# DEPRECATED: the `expiration_date` field has been replaced by `expiration_datetime` # DEPRECATED: the `expiration_date` field has been replaced by `expiration_datetime`
expiration_date = models.DateField(default=None, null=True, blank=True) expiration_date = models.DateField(default=None, null=True, blank=True)
...@@ -150,6 +151,17 @@ class CourseMode(models.Model): ...@@ -150,6 +151,17 @@ class CourseMode(models.Model):
""" """
return self.mode_slug return self.mode_slug
@property
def expiration_datetime(self):
""" Return _expiration_datetime. """
return self._expiration_datetime
@expiration_datetime.setter
def expiration_datetime(self, new_datetime):
""" Saves datetime to _expiration_datetime and sets the explicit flag. """
self.expiration_datetime_is_explicit = True
self._expiration_datetime = new_datetime
@classmethod @classmethod
def all_modes_for_courses(cls, course_id_list): def all_modes_for_courses(cls, course_id_list):
"""Find all modes for a list of course IDs, including expired modes. """Find all modes for a list of course IDs, including expired modes.
...@@ -223,8 +235,8 @@ class CourseMode(models.Model): ...@@ -223,8 +235,8 @@ class CourseMode(models.Model):
Q(course_id=course_id) & Q(course_id=course_id) &
Q(min_price__gt=0) & Q(min_price__gt=0) &
( (
Q(expiration_datetime__isnull=True) | Q(_expiration_datetime__isnull=True) |
Q(expiration_datetime__gte=now) Q(_expiration_datetime__gte=now)
) )
) )
return [mode.to_tuple() for mode in found_course_modes] return [mode.to_tuple() for mode in found_course_modes]
...@@ -259,7 +271,7 @@ class CourseMode(models.Model): ...@@ -259,7 +271,7 @@ class CourseMode(models.Model):
# Filter out expired course modes if include_expired is not set # Filter out expired course modes if include_expired is not set
if not include_expired: if not include_expired:
found_course_modes = found_course_modes.filter( found_course_modes = found_course_modes.filter(
Q(expiration_datetime__isnull=True) | Q(expiration_datetime__gte=now) Q(_expiration_datetime__isnull=True) | Q(_expiration_datetime__gte=now)
) )
# Credit course modes are currently not shown on the track selection page; # Credit course modes are currently not shown on the track selection page;
...@@ -633,3 +645,19 @@ class CourseModesArchive(models.Model): ...@@ -633,3 +645,19 @@ class CourseModesArchive(models.Model):
expiration_date = models.DateField(default=None, null=True, blank=True) expiration_date = models.DateField(default=None, null=True, blank=True)
expiration_datetime = models.DateTimeField(default=None, null=True, blank=True) expiration_datetime = models.DateTimeField(default=None, null=True, blank=True)
class CourseModeExpirationConfig(ConfigurationModel):
"""
Configuration for time period from end of course to auto-expire a course mode.
"""
verification_window = models.DurationField(
default=timedelta(days=10),
help_text=_(
"The time period before a course ends in which a course mode will expire"
)
)
def __unicode__(self):
""" Returns the unicode date of the verification window. """
return unicode(self.verification_window)
"""
Signal handler for setting default course mode expiration dates
"""
from django.core.exceptions import ObjectDoesNotExist
from django.dispatch.dispatcher import receiver
from xmodule.modulestore.django import SignalHandler, modulestore
from .models import CourseMode, CourseModeExpirationConfig
@receiver(SignalHandler.course_published)
def _listen_for_course_publish(sender, course_key, **kwargs): # pylint: disable=unused-argument
"""
Catches the signal that a course has been published in Studio and
sets the verified mode dates to defaults.
"""
try:
verified_mode = CourseMode.objects.get(course_id=course_key, mode_slug=CourseMode.VERIFIED)
if _should_update_date(verified_mode):
course = modulestore().get_course(course_key)
if not course:
return None
verification_window = CourseModeExpirationConfig.current().verification_window
new_expiration_datetime = course.end - verification_window
if verified_mode.expiration_datetime != new_expiration_datetime:
# Set the expiration_datetime without triggering the explicit flag
verified_mode._expiration_datetime = new_expiration_datetime # pylint: disable=protected-access
verified_mode.save()
except ObjectDoesNotExist:
pass
def _should_update_date(verified_mode):
""" Returns whether or not the verified mode should be updated. """
return not(verified_mode is None or verified_mode.expiration_datetime_is_explicit)
"""
Setup the signals on startup.
"""
import course_modes.signals # pylint: disable=unused-import
...@@ -48,8 +48,8 @@ class AdminCourseModePageTest(ModuleStoreTestCase): ...@@ -48,8 +48,8 @@ class AdminCourseModePageTest(ModuleStoreTestCase):
'mode_display_name': 'verified', 'mode_display_name': 'verified',
'min_price': 10, 'min_price': 10,
'currency': 'usd', 'currency': 'usd',
'expiration_datetime_0': expiration.date(), # due to django admin datetime widget passing as seperate vals '_expiration_datetime_0': expiration.date(), # due to django admin datetime widget passing as separate vals
'expiration_datetime_1': expiration.time(), '_expiration_datetime_1': expiration.time(),
} }
...@@ -201,7 +201,7 @@ class AdminCourseModeFormTest(ModuleStoreTestCase): ...@@ -201,7 +201,7 @@ class AdminCourseModeFormTest(ModuleStoreTestCase):
"course_id": unicode(self.course.id), "course_id": unicode(self.course.id),
"mode_slug": mode, "mode_slug": mode,
"mode_display_name": mode, "mode_display_name": mode,
"expiration_datetime": upgrade_deadline, "_expiration_datetime": upgrade_deadline,
"currency": "usd", "currency": "usd",
"min_price": 10, "min_price": 10,
}, instance=course_mode) }, instance=course_mode)
......
...@@ -49,7 +49,7 @@ class CourseModeModelTest(TestCase): ...@@ -49,7 +49,7 @@ class CourseModeModelTest(TestCase):
min_price=min_price, min_price=min_price,
suggested_prices=suggested_prices, suggested_prices=suggested_prices,
currency=currency, currency=currency,
expiration_datetime=expiration_datetime, _expiration_datetime=expiration_datetime,
) )
def test_save(self): def test_save(self):
...@@ -403,3 +403,21 @@ class CourseModeModelTest(TestCase): ...@@ -403,3 +403,21 @@ class CourseModeModelTest(TestCase):
return dict(zip(dict_keys, display_values.get('verify_none'))) return dict(zip(dict_keys, display_values.get('verify_none')))
else: else:
return dict(zip(dict_keys, display_values.get(dict_type))) return dict(zip(dict_keys, display_values.get(dict_type)))
def test_expiration_datetime_explicitly_set(self):
""" Verify that setting the expiration_date property sets the explicit flag. """
verified_mode, __ = self.create_mode('verified', 'Verified Certificate')
now = datetime.now()
verified_mode.expiration_datetime = now
self.assertTrue(verified_mode.expiration_datetime_is_explicit)
self.assertEqual(verified_mode.expiration_datetime, now)
def test_expiration_datetime_not_explicitly_set(self):
""" Verify that setting the _expiration_date property does not set the explicit flag. """
verified_mode, __ = self.create_mode('verified', 'Verified Certificate')
now = datetime.now()
verified_mode._expiration_datetime = now # pylint: disable=protected-access
self.assertFalse(verified_mode.expiration_datetime_is_explicit)
self.assertEqual(verified_mode.expiration_datetime, now)
"""
Unit tests for the course_mode signals
"""
from datetime import datetime, timedelta
from mock import patch
import ddt
from pytz import UTC
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from course_modes.models import CourseMode
from course_modes.signals import _listen_for_course_publish
@ddt.ddt
class CourseModeSignalTest(ModuleStoreTestCase):
"""
Tests for the course_mode course_published signal.
"""
def setUp(self):
super(CourseModeSignalTest, self).setUp()
self.end = datetime.now(tz=UTC).replace(microsecond=0) + timedelta(days=7)
self.course = CourseFactory.create(end=self.end)
CourseMode.objects.all().delete()
def create_mode(
self,
mode_slug,
mode_name,
min_price=0,
suggested_prices='',
currency='usd',
expiration_datetime=None,
):
"""
Create a new course mode
"""
return CourseMode.objects.get_or_create(
course_id=self.course.id,
mode_display_name=mode_name,
mode_slug=mode_slug,
min_price=min_price,
suggested_prices=suggested_prices,
currency=currency,
_expiration_datetime=expiration_datetime,
)
def test_no_verified_mode(self):
""" Verify expiration not updated by signal for non-verified mode. """
course_mode, __ = self.create_mode('honor', 'honor')
_listen_for_course_publish('store', self.course.id)
course_mode.refresh_from_db()
self.assertIsNone(course_mode.expiration_datetime)
@ddt.data(1, 14, 30)
def test_verified_mode(self, verification_window):
""" Verify signal updates expiration to configured time period before course end for verified mode. """
course_mode, __ = self.create_mode('verified', 'verified')
self.assertIsNone(course_mode.expiration_datetime)
with patch('course_modes.models.CourseModeExpirationConfig.current') as config:
instance = config.return_value
instance.verification_window = timedelta(days=verification_window)
_listen_for_course_publish('store', self.course.id)
course_mode.refresh_from_db()
self.assertEqual(course_mode.expiration_datetime, self.end - timedelta(days=verification_window))
@ddt.data(1, 14, 30)
def test_verified_mode_explicitly_set(self, verification_window):
""" Verify signal does not update expiration for verified mode with explicitly set expiration. """
course_mode, __ = self.create_mode('verified', 'verified')
course_mode.expiration_datetime_is_explicit = True
self.assertIsNone(course_mode.expiration_datetime)
with patch('course_modes.models.CourseModeExpirationConfig.current') as config:
instance = config.return_value
instance.verification_window = timedelta(days=verification_window)
_listen_for_course_publish('store', self.course.id)
course_mode.refresh_from_db()
self.assertEqual(course_mode.expiration_datetime, self.end - timedelta(days=verification_window))
...@@ -58,8 +58,9 @@ class Course(object): ...@@ -58,8 +58,9 @@ class Course(object):
def save(self, *args, **kwargs): # pylint: disable=unused-argument def save(self, *args, **kwargs): # pylint: disable=unused-argument
""" Save the CourseMode objects to the database. """ """ Save the CourseMode objects to the database. """
# Update the verification deadline for the course (not the individual modes) # Override the verification deadline for the course (not the individual modes)
VerificationDeadline.set_deadline(self.id, self.verification_deadline) if self.verification_deadline is not None:
VerificationDeadline.set_deadline(self.id, self.verification_deadline, is_explicit=True)
for mode in self.modes: for mode in self.modes:
mode.course_id = self.id mode.course_id = self.id
...@@ -87,7 +88,8 @@ class Course(object): ...@@ -87,7 +88,8 @@ class Course(object):
merged_mode.min_price = posted_mode.min_price merged_mode.min_price = posted_mode.min_price
merged_mode.currency = posted_mode.currency merged_mode.currency = posted_mode.currency
merged_mode.sku = posted_mode.sku merged_mode.sku = posted_mode.sku
merged_mode.expiration_datetime = posted_mode.expiration_datetime if posted_mode.expiration_datetime is not None:
merged_mode.expiration_datetime = posted_mode.expiration_datetime
merged_mode.save() merged_mode.save()
merged_modes.add(merged_mode) merged_modes.add(merged_mode)
......
...@@ -1531,7 +1531,7 @@ def financial_assistance_form(request): ...@@ -1531,7 +1531,7 @@ def financial_assistance_form(request):
{'name': enrollment.course_overview.display_name, 'value': unicode(enrollment.course_id)} {'name': enrollment.course_overview.display_name, 'value': unicode(enrollment.course_id)}
for enrollment in CourseEnrollment.enrollments_for_user(user).order_by('-created') for enrollment in CourseEnrollment.enrollments_for_user(user).order_by('-created')
if CourseMode.objects.filter( if CourseMode.objects.filter(
Q(expiration_datetime__isnull=True) | Q(expiration_datetime__gt=datetime.now(UTC())), Q(_expiration_datetime__isnull=True) | Q(_expiration_datetime__gt=datetime.now(UTC())),
course_id=enrollment.course_id, course_id=enrollment.course_id,
mode_slug=CourseMode.VERIFIED mode_slug=CourseMode.VERIFIED
).exists() ).exists()
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('verify_student', '0002_auto_20151124_1024'),
]
operations = [
migrations.AlterField(
model_name='historicalverificationdeadline',
name='deadline_is_explicit',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='verificationdeadline',
name='deadline_is_explicit',
field=models.BooleanField(default=False),
),
]
...@@ -958,7 +958,7 @@ class VerificationDeadline(TimeStampedModel): ...@@ -958,7 +958,7 @@ class VerificationDeadline(TimeStampedModel):
# The system prefers to set this automatically based on default settings. But # The system prefers to set this automatically based on default settings. But
# if the field is set manually we want a way to indicate that so we don't # if the field is set manually we want a way to indicate that so we don't
# overwrite the manual setting of the field. # overwrite the manual setting of the field.
deadline_is_explicit = models.BooleanField(default=True) deadline_is_explicit = models.BooleanField(default=False)
# Maintain a history of changes to deadlines for auditing purposes # Maintain a history of changes to deadlines for auditing purposes
history = HistoricalRecords() history = HistoricalRecords()
...@@ -966,7 +966,7 @@ class VerificationDeadline(TimeStampedModel): ...@@ -966,7 +966,7 @@ class VerificationDeadline(TimeStampedModel):
ALL_DEADLINES_CACHE_KEY = "verify_student.all_verification_deadlines" ALL_DEADLINES_CACHE_KEY = "verify_student.all_verification_deadlines"
@classmethod @classmethod
def set_deadline(cls, course_key, deadline): def set_deadline(cls, course_key, deadline, is_explicit=False):
""" """
Configure the verification deadline for a course. Configure the verification deadline for a course.
...@@ -984,11 +984,12 @@ class VerificationDeadline(TimeStampedModel): ...@@ -984,11 +984,12 @@ class VerificationDeadline(TimeStampedModel):
else: else:
record, created = VerificationDeadline.objects.get_or_create( record, created = VerificationDeadline.objects.get_or_create(
course_key=course_key, course_key=course_key,
defaults={"deadline": deadline} defaults={"deadline": deadline, "deadline_is_explicit": is_explicit}
) )
if not created: if not created:
record.deadline = deadline record.deadline = deadline
record.deadline_is_explicit = is_explicit
record.save() record.save()
@classmethod @classmethod
......
"""
Signal handler for setting default course verification dates
"""
from django.core.exceptions import ObjectDoesNotExist
from django.dispatch.dispatcher import receiver
from xmodule.modulestore.django import SignalHandler, modulestore
from .models import VerificationDeadline
@receiver(SignalHandler.course_published)
def _listen_for_course_publish(sender, course_key, **kwargs): # pylint: disable=unused-argument
"""
Catches the signal that a course has been published in Studio and
sets the verification deadline date to a default.
"""
try:
deadline = VerificationDeadline.objects.get(course_key=course_key)
if deadline and not deadline.deadline_is_explicit:
course = modulestore().get_course(course_key)
if course and deadline.deadline != course.end:
VerificationDeadline.set_deadline(course_key, course.end)
except ObjectDoesNotExist:
pass
"""
Setup the signals on startup.
"""
import lms.djangoapps.verify_student.signals # pylint: disable=unused-import
"""
Unit tests for the VerificationDeadline signals
"""
from datetime import datetime, timedelta
from pytz import UTC
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from lms.djangoapps.verify_student.models import VerificationDeadline
from lms.djangoapps.verify_student.signals import _listen_for_course_publish
class VerificationDeadlineSignalTest(ModuleStoreTestCase):
"""
Tests for the VerificationDeadline signal
"""
def setUp(self):
super(VerificationDeadlineSignalTest, self).setUp()
self.end = datetime.now(tz=UTC).replace(microsecond=0) + timedelta(days=7)
self.course = CourseFactory.create(end=self.end)
VerificationDeadline.objects.all().delete()
def test_no_deadline(self):
""" Verify the signal does not raise error when no deadlines found. """
_listen_for_course_publish('store', self.course.id)
self.assertIsNone(_listen_for_course_publish('store', self.course.id))
def test_deadline(self):
""" Verify deadline is set to course end date by signal. """
deadline = datetime.now(tz=UTC) - timedelta(days=7)
VerificationDeadline.set_deadline(self.course.id, deadline)
_listen_for_course_publish('store', self.course.id)
self.assertEqual(VerificationDeadline.deadline_for_course(self.course.id), self.course.end)
def test_deadline_explicit(self):
""" Verify deadline is unchanged by signal when explicitly set. """
deadline = datetime.now(tz=UTC) - timedelta(days=7)
VerificationDeadline.set_deadline(self.course.id, deadline, is_explicit=True)
_listen_for_course_publish('store', self.course.id)
actual_deadline = VerificationDeadline.deadline_for_course(self.course.id)
self.assertNotEqual(actual_deadline, self.course.end)
self.assertEqual(actual_deadline, deadline)
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