Files
edx-platform/common/djangoapps/course_modes/tests/test_admin.py
2019-04-23 15:29:27 -04:00

225 lines
9.7 KiB
Python

"""
Tests for the course modes Django admin interface.
"""
from __future__ import absolute_import, unicode_literals
import unittest
from datetime import datetime, timedelta
import ddt
import six
from django.conf import settings
from django.urls import reverse
from pytz import UTC, timezone
from course_modes.admin import CourseModeForm
from course_modes.models import CourseMode
from course_modes.tests.factories import CourseModeFactory
# Technically, we shouldn't be importing verify_student, since it's
# defined in the LMS and course_modes is in common. However, the benefits
# of putting all this configuration in one place outweigh the downsides.
# Once the course admin tool is deployed, we can remove this dependency.
from lms.djangoapps.verify_student.models import VerificationDeadline
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from student.tests.factories import UserFactory
from util.date_utils import get_time_display
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
# We can only test this in the LMS because the course modes admin relies
# on verify student, which is not an installed app in Studio, so the verification
# deadline table will not be created.
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
class AdminCourseModePageTest(ModuleStoreTestCase):
"""
Test the course modes Django admin interface.
"""
def test_expiration_timezone(self):
# Test that expiration datetimes are saved and retrieved with the timezone set to UTC.
# This verifies the fix for a bug in which the date displayed to users was different
# than the date in Django admin.
user = UserFactory.create(is_staff=True, is_superuser=True)
user.save()
course = CourseFactory.create()
expiration = datetime(2015, 10, 20, 1, 10, 23, tzinfo=timezone(settings.TIME_ZONE))
CourseOverview.load_from_module_store(course.id)
data = {
'course': six.text_type(course.id),
'mode_slug': 'verified',
'mode_display_name': 'verified',
'min_price': 10,
'currency': 'usd',
'_expiration_datetime_0': expiration.date(), # due to django admin datetime widget passing as separate vals
'_expiration_datetime_1': expiration.time(),
}
self.client.login(username=user.username, password='test')
# Create a new course mode from django admin page
response = self.client.post(reverse('admin:course_modes_coursemode_add'), data=data)
self.assertRedirects(response, reverse('admin:course_modes_coursemode_changelist'))
# Verify that datetime is appears on list page
response = self.client.get(reverse('admin:course_modes_coursemode_changelist'))
self.assertContains(response, get_time_display(expiration, '%B %d, %Y, %H:%M %p'))
# Verify that on the edit page the datetime value appears as UTC.
resp = self.client.get(reverse('admin:course_modes_coursemode_change', args=(1,)))
self.assertContains(resp, expiration.date())
self.assertContains(resp, expiration.time())
# Verify that the expiration datetime is the same as what we set
# (hasn't changed because of a timezone translation).
course_mode = CourseMode.objects.get(pk=1)
self.assertEqual(course_mode.expiration_datetime.replace(tzinfo=None), expiration.replace(tzinfo=None))
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
@ddt.ddt
class AdminCourseModeFormTest(ModuleStoreTestCase):
"""
Test the course modes Django admin form validation and saving.
"""
UPGRADE_DEADLINE = datetime.now(UTC)
VERIFICATION_DEADLINE = UPGRADE_DEADLINE + timedelta(days=5)
def setUp(self):
"""
Create a test course.
"""
super(AdminCourseModeFormTest, self).setUp()
self.course = CourseFactory.create()
CourseOverview.load_from_module_store(self.course.id)
@ddt.data(
("honor", False),
("verified", True),
("professional", True),
("no-id-professional", False),
("credit", False),
)
@ddt.unpack
def test_load_verification_deadline(self, mode, expect_deadline):
# Configure a verification deadline for the course
VerificationDeadline.set_deadline(self.course.id, self.VERIFICATION_DEADLINE)
# Configure a course mode with both an upgrade and verification deadline
# and load the form to edit it.
deadline = self.UPGRADE_DEADLINE if mode == "verified" else None
form = self._admin_form(mode, upgrade_deadline=deadline)
# Check that the verification deadline is loaded,
# but ONLY for verified modes.
loaded_deadline = form.initial.get("verification_deadline")
if expect_deadline:
self.assertEqual(
loaded_deadline.replace(tzinfo=None),
self.VERIFICATION_DEADLINE.replace(tzinfo=None)
)
else:
self.assertIs(loaded_deadline, None)
@ddt.data("verified", "professional")
def test_set_verification_deadline(self, course_mode):
# Configure a verification deadline for the course
VerificationDeadline.set_deadline(self.course.id, self.VERIFICATION_DEADLINE)
# Create the course mode Django admin form
form = self._admin_form(course_mode)
# Update the verification deadline form data
# We need to set the date and time fields separately, since they're
# displayed as separate widgets in the form.
new_deadline = (self.VERIFICATION_DEADLINE + timedelta(days=1)).replace(microsecond=0)
self._set_form_verification_deadline(form, new_deadline)
form.save()
# Check that the deadline was updated
updated_deadline = VerificationDeadline.deadline_for_course(self.course.id)
self.assertEqual(updated_deadline, new_deadline)
def test_disable_verification_deadline(self):
# Configure a verification deadline for the course
VerificationDeadline.set_deadline(self.course.id, self.VERIFICATION_DEADLINE)
# Create the course mode Django admin form
form = self._admin_form("verified", upgrade_deadline=self.UPGRADE_DEADLINE)
# Use the form to disable the verification deadline
self._set_form_verification_deadline(form, None)
form.save()
# Check that the deadline was disabled
self.assertIs(VerificationDeadline.deadline_for_course(self.course.id), None)
@ddt.data("honor", "professional", "no-id-professional", "credit")
def test_validate_upgrade_deadline_only_for_verified(self, course_mode):
# Only the verified mode should have an upgrade deadline, so any other course
# mode that has an upgrade deadline set should cause a validation error
form = self._admin_form(course_mode, upgrade_deadline=self.UPGRADE_DEADLINE)
self._assert_form_has_error(form, (
'Only the "verified" mode can have an upgrade deadline. '
'For other modes, please set the enrollment end date in Studio.'
))
@ddt.data("honor", "no-id-professional", "credit")
def test_validate_verification_deadline_only_for_verified(self, course_mode):
# Only the verified mode should have a verification deadline set.
# Any other course mode should raise a validation error if a deadline is set.
form = self._admin_form(course_mode)
self._set_form_verification_deadline(form, self.VERIFICATION_DEADLINE)
self._assert_form_has_error(form, "Verification deadline can be set only for verified modes.")
def test_verification_deadline_after_upgrade_deadline(self):
form = self._admin_form("verified", upgrade_deadline=self.UPGRADE_DEADLINE)
before_upgrade = self.UPGRADE_DEADLINE - timedelta(days=1)
self._set_form_verification_deadline(form, before_upgrade)
self._assert_form_has_error(form, "Verification deadline must be after the upgrade deadline.")
def _configure(self, mode, upgrade_deadline=None, verification_deadline=None):
"""Configure course modes and deadlines. """
course_mode = CourseModeFactory.create(
mode_slug=mode,
mode_display_name=mode,
)
if upgrade_deadline is not None:
course_mode.upgrade_deadline = upgrade_deadline
course_mode.save()
VerificationDeadline.set_deadline(self.course.id, verification_deadline)
return CourseModeForm(instance=course_mode)
def _admin_form(self, mode, upgrade_deadline=None):
"""Load the course mode admin form. """
course_mode = CourseModeFactory.create(
course_id=self.course.id,
mode_slug=mode,
)
return CourseModeForm({
"course": six.text_type(self.course.id),
"mode_slug": mode,
"mode_display_name": mode,
"_expiration_datetime": upgrade_deadline,
"currency": "usd",
"min_price": 10,
}, instance=course_mode)
def _set_form_verification_deadline(self, form, deadline):
"""Set the verification deadline on the course mode admin form. """
date_str = deadline.strftime("%Y-%m-%d") if deadline else None
time_str = deadline.strftime("%H:%M:%S") if deadline else None
form.data["verification_deadline_0"] = date_str
form.data["verification_deadline_1"] = time_str
def _assert_form_has_error(self, form, error):
"""Check that a form has a validation error. """
validation_errors = form.errors.get("__all__", [])
self.assertIn(error, validation_errors)