-
Ned Batchelder authored
Also remove useless docstrings where they were added to keep pylint quiet.
322ca34b
admin.py 8.76 KiB
"""
Django admin page for course modes
"""
from django.conf import settings
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.contrib import admin
from pytz import timezone, UTC
from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from opaque_keys import InvalidKeyError
from util.date_utils import get_time_display
from xmodule.modulestore.django import modulestore
from course_modes.models import CourseMode
# Technically, we shouldn't be doing this, since verify_student is defined
# in LMS, and course_modes is defined in common.
#
# Once we move the responsibility for administering course modes into
# the Course Admin tool, we can remove this dependency and expose
# verification deadlines as a separate Django model admin.
#
# The admin page will work in both LMS and Studio,
# but the test suite for Studio will fail because
# the verification deadline table won't exist.
from verify_student import models as verification_models # pylint: disable=import-error
class CourseModeForm(forms.ModelForm):
class Meta(object):
model = CourseMode
COURSE_MODE_SLUG_CHOICES = (
[(CourseMode.DEFAULT_MODE_SLUG, CourseMode.DEFAULT_MODE_SLUG)] +
[(mode_slug, mode_slug) for mode_slug in CourseMode.VERIFIED_MODES] +
[(CourseMode.NO_ID_PROFESSIONAL_MODE, CourseMode.NO_ID_PROFESSIONAL_MODE)] +
[(mode_slug, mode_slug) for mode_slug in CourseMode.CREDIT_MODES]
)
mode_slug = forms.ChoiceField(choices=COURSE_MODE_SLUG_CHOICES, label=_("Mode"))
# The verification deadline is stored outside the course mode in the verify_student app.
# (we used to use the course mode expiration_datetime as both an upgrade and verification deadline).
# In order to make this transition easier, we include the verification deadline as a custom field
# in the course mode admin form. Longer term, we will deprecate the course mode Django admin
# form in favor of an external Course Administration Tool.
verification_deadline = forms.SplitDateTimeField(
label=_("Verification Deadline"),
required=False,
help_text=_(
"OPTIONAL: After this date/time, users will no longer be able to submit photos for verification. "
"This appies ONLY to modes that require verification."
),
widget=admin.widgets.AdminSplitDateTime,
)
def __init__(self, *args, **kwargs):
super(CourseModeForm, self).__init__(*args, **kwargs)
default_tz = timezone(settings.TIME_ZONE)
if self.instance.expiration_datetime:
# 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.
expiration_datetime = self.instance.expiration_datetime.replace(tzinfo=None)
self.initial["expiration_datetime"] = default_tz.localize(expiration_datetime)
# Load the verification deadline
# 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
# it to the user's timezone. We'll make sure we write it back to the database with the timezone
# set to UTC later.
if self.instance.course_id and self.instance.mode_slug in CourseMode.VERIFIED_MODES:
deadline = verification_models.VerificationDeadline.deadline_for_course(self.instance.course_id)
self.initial["verification_deadline"] = (
default_tz.localize(deadline.replace(tzinfo=None))
if deadline is not None else None
)
def clean_course_id(self):
course_id = self.cleaned_data['course_id']
try:
course_key = CourseKey.from_string(course_id)
except InvalidKeyError:
try:
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
except InvalidKeyError:
raise forms.ValidationError("Cannot make a valid CourseKey from id {}!".format(course_id))
if not modulestore().has_course(course_key):
raise forms.ValidationError("Cannot find course with id {} in the modulestore".format(course_id))
return course_key
def clean_expiration_datetime(self):
"""
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
# changes its tzinfo to UTC
if self.cleaned_data.get("expiration_datetime"):
return self.cleaned_data.get("expiration_datetime").replace(tzinfo=UTC)
def clean_verification_deadline(self):
"""
Ensure that the verification deadline we save uses the UTC timezone.
"""
if self.cleaned_data.get("verification_deadline"):
return self.cleaned_data.get("verification_deadline").replace(tzinfo=UTC)
def clean(self):
"""
Clean the form fields.
This is the place to perform checks that involve multiple form fields.
"""
cleaned_data = super(CourseModeForm, self).clean()
mode_slug = cleaned_data.get("mode_slug")
upgrade_deadline = cleaned_data.get("expiration_datetime")
verification_deadline = cleaned_data.get("verification_deadline")
# Allow upgrade deadlines ONLY for the "verified" mode
# This avoids a nasty error condition in which the upgrade deadline is set
# for a professional education course before the enrollment end date.
# When this happens, the course mode expires and students are able to enroll
# in the course for free. To avoid this, we explicitly prevent admins from
# setting an upgrade deadline for any mode except "verified" (which has an upgrade path).
if upgrade_deadline is not None and mode_slug != CourseMode.VERIFIED:
raise forms.ValidationError(
'Only the "verified" mode can have an upgrade deadline. '
'For other modes, please set the enrollment end date in Studio.'
)
# Verification deadlines are allowed only for verified modes
if verification_deadline is not None and mode_slug not in CourseMode.VERIFIED_MODES:
raise forms.ValidationError("Verification deadline can be set only for verified modes.")
# Verification deadline must be after the upgrade deadline,
# if an upgrade deadline is set.
# There are cases in which we might want to set a verification deadline,
# but not an upgrade deadline (for example, a professional education course that requires verification).
if verification_deadline is not None:
if upgrade_deadline is not None and verification_deadline < upgrade_deadline:
raise forms.ValidationError("Verification deadline must be after the upgrade deadline.")
return cleaned_data
def save(self, commit=True):
"""
Save the form data.
"""
# Trigger validation so we can access cleaned data
if self.is_valid():
course_key = self.cleaned_data.get("course_id")
verification_deadline = self.cleaned_data.get("verification_deadline")
mode_slug = self.cleaned_data.get("mode_slug")
# Since the verification deadline is stored in a separate model,
# we need to handle saving this ourselves.
# Note that verification deadline can be `None` here if
# the deadline is being disabled.
if course_key is not None and mode_slug in CourseMode.VERIFIED_MODES:
verification_models.VerificationDeadline.set_deadline(course_key, verification_deadline)
return super(CourseModeForm, self).save(commit=commit)
class CourseModeAdmin(admin.ModelAdmin):
"""Admin for course modes"""
form = CourseModeForm
fields = (
'course_id',
'mode_slug',
'mode_display_name',
'min_price',
'currency',
'expiration_datetime',
'verification_deadline',
'sku'
)
search_fields = ('course_id',)
list_display = (
'id',
'course_id',
'mode_slug',
'min_price',
'expiration_datetime_custom',
'sku'
)
def expiration_datetime_custom(self, obj):
"""adding custom column to show the expiry_datetime"""
if obj.expiration_datetime:
return get_time_display(obj.expiration_datetime, '%B %d, %Y, %H:%M %p')
# Display a more user-friendly name for the custom expiration datetime field
# in the Django admin list view.
expiration_datetime_custom.short_description = "Upgrade Deadline"
admin.site.register(CourseMode, CourseModeAdmin)