diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f5b7250683..6c71fb77fd 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,12 @@ These are notable changes in edx-platform. This is a rolling list of changes, in roughly chronological order, most recent first. Add your entries at or near the top. Include a label indicating the component affected. +Studio: New advanced setting "invitation_only" for courses. This setting overrides the enrollment start/end dates + if set. LMS-2670 + +LMS: Register button on About page was active even when greyed out. Now made inactive when appropriate and +displays appropriate context sensitive message to student. LMS-2717 + Blades: Redirect Chinese students to a Chinese CDN for video. BLD-1052. Studio: Show display names and help text in Advanced Settings. Also hide deprecated settings diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 7641a27627..24334bcc73 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -540,6 +540,11 @@ class CourseFields(object): default=False, scope=Scope.settings) + invitation_only = Boolean(display_name=_("Invitation Only"), + help="Whether to restrict enrollment to invitation by the course staff.", + default=False, + scope=Scope.settings) + class CourseDescriptor(CourseFields, SequenceDescriptor): module_class = SequenceModule diff --git a/lms/djangoapps/courseware/access.py b/lms/djangoapps/courseware/access.py index f081862576..57b7a3543a 100644 --- a/lms/djangoapps/courseware/access.py +++ b/lms/djangoapps/courseware/access.py @@ -3,6 +3,7 @@ Ideally, it will be the only place that needs to know about any special settings like DISABLE_START_DATES""" import logging from datetime import datetime, timedelta +import pytz from django.conf import settings from django.contrib.auth.models import AnonymousUser @@ -139,12 +140,14 @@ def _has_access_course_desc(user, action, course): If it is, then the user must pass the criterion set by the course, e.g. that ExternalAuthMap was set by 'shib:https://idp.stanford.edu/", in addition to requirements below. Rest of requirements: - Enrollment can only happen in the course enrollment period, if one exists. - or - (CourseEnrollmentAllowed always overrides) + or (staff can always enroll) + or + Enrollment can only happen in the course enrollment period, if one exists, and + course is not invitation only. """ + # if using registration method to restrict (say shibboleth) if settings.FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD') and course.enrollment_domain: if user is not None and user.is_authenticated() and \ @@ -154,16 +157,11 @@ def _has_access_course_desc(user, action, course): else: reg_method_ok = False else: - reg_method_ok = True #if not using this access check, it's always OK. + reg_method_ok = True # if not using this access check, it's always OK. now = datetime.now(UTC()) - start = course.enrollment_start - end = course.enrollment_end - - if reg_method_ok and (start is None or now > start) and (end is None or now < end): - # in enrollment period, so any user is allowed to enroll. - debug("Allow: in enrollment period") - return True + start = course.enrollment_start or datetime.min.replace(tzinfo=pytz.UTC) + end = course.enrollment_end or datetime.max.replace(tzinfo=pytz.UTC) # if user is in CourseEnrollmentAllowed with right course key then can also enroll # (note that course.id actually points to a CourseKey) @@ -173,8 +171,17 @@ def _has_access_course_desc(user, action, course): if CourseEnrollmentAllowed.objects.filter(email=user.email, course_id=course.id): return True - # otherwise, need staff access - return _has_staff_access_to_descriptor(user, course, course.id) + if _has_staff_access_to_descriptor(user, course, course.id): + return True + + # Invitation_only doesn't apply to CourseEnrollmentAllowed or has_staff_access_access + if course.invitation_only: + debug("Deny: invitation only") + return False + + if reg_method_ok and start < now < end: + debug("Allow: in enrollment period") + return True def see_exists(): """ diff --git a/lms/djangoapps/courseware/tests/test_about.py b/lms/djangoapps/courseware/tests/test_about.py index 6b616ac333..824890e348 100644 --- a/lms/djangoapps/courseware/tests/test_about.py +++ b/lms/djangoapps/courseware/tests/test_about.py @@ -2,6 +2,8 @@ Test the about xblock """ import mock +import pytz +import datetime from django.test.utils import override_settings from django.core.urlresolvers import reverse @@ -10,6 +12,10 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from opaque_keys.edx.locations import SlashSeparatedCourseKey +from student.tests.factories import UserFactory, CourseEnrollmentAllowedFactory + +# HTML for registration button +REG_STR = "