feat: add COURSES_INVITE_ONLY setting (#27252)
Setting COURSES_INVITE_ONLY to True overrides the INVITE_ONLY setting across all courses in a given deployment. Co-authored-by: tasawernawaz <tasawer.nawaz@arbisoft.com> Co-authored-by: asadiqbal08 <asad.iqbal@arbisoft.com>
This commit is contained in:
@@ -42,6 +42,7 @@ from lms.djangoapps.courseware.masquerade import get_masquerade_role, is_masquer
|
||||
from lms.djangoapps.ccx.custom_exception import CCXLocatorValidationException
|
||||
from lms.djangoapps.ccx.models import CustomCourseForEdX
|
||||
from lms.djangoapps.mobile_api.models import IgnoreMobileAvailableFlagConfig
|
||||
from lms.djangoapps.courseware.toggles import is_courses_default_invite_only_enabled
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.features.course_duration_limits.access import check_course_expired
|
||||
from common.djangoapps.student import auth
|
||||
@@ -271,7 +272,8 @@ def _can_enroll_courselike(user, courselike):
|
||||
if _has_staff_access_to_descriptor(user, courselike, course_key):
|
||||
return ACCESS_GRANTED
|
||||
|
||||
if courselike.invitation_only:
|
||||
# Access denied when default value of COURSES_INVITE_ONLY set to True
|
||||
if is_courses_default_invite_only_enabled() or courselike.invitation_only:
|
||||
debug("Deny: invitation only")
|
||||
return ACCESS_DENIED
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ from ccx_keys.locator import CCXLocator
|
||||
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import reverse
|
||||
from milestones.tests.utils import MilestonesTestCaseMixin
|
||||
from opaque_keys.edx.locator import CourseLocator
|
||||
@@ -500,6 +501,48 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
|
||||
)
|
||||
assert not access._has_access_course(user, 'enroll', course)
|
||||
|
||||
@override_settings(COURSES_INVITE_ONLY=False)
|
||||
def test__course_default_invite_only_flag_false(self):
|
||||
"""
|
||||
Ensure that COURSES_INVITE_ONLY does not take precedence,
|
||||
if it is not set over the course invitation_only settings.
|
||||
"""
|
||||
|
||||
user = UserFactory.create()
|
||||
|
||||
# User cannot enroll in the course if it is just invitation only.
|
||||
course = self._mock_course_with_invitation(invitation=True)
|
||||
self.assertFalse(access._has_access_course(user, 'enroll', course))
|
||||
|
||||
# User can enroll in the course if it is not just invitation only.
|
||||
course = self._mock_course_with_invitation(invitation=False)
|
||||
self.assertTrue(access._has_access_course(user, 'enroll', course))
|
||||
|
||||
@override_settings(COURSES_INVITE_ONLY=True)
|
||||
def test__course_default_invite_only_flag_true(self):
|
||||
"""
|
||||
Ensure that COURSES_INVITE_ONLY takes precedence over the course invitation_only settings.
|
||||
"""
|
||||
|
||||
user = UserFactory.create()
|
||||
|
||||
# User cannot enroll in the course if it is just invitation only and COURSES_INVITE_ONLY is also set.
|
||||
course = self._mock_course_with_invitation(invitation=True)
|
||||
self.assertFalse(access._has_access_course(user, 'enroll', course))
|
||||
|
||||
# User cannot enroll in the course if COURSES_INVITE_ONLY is set despite of the course invitation_only value.
|
||||
course = self._mock_course_with_invitation(invitation=False)
|
||||
self.assertFalse(access._has_access_course(user, 'enroll', course))
|
||||
|
||||
def _mock_course_with_invitation(self, invitation):
|
||||
yesterday = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1)
|
||||
tomorrow = datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1)
|
||||
return Mock(
|
||||
enrollment_start=yesterday, enrollment_end=tomorrow,
|
||||
id=CourseLocator('edX', 'test', '2012_Fall'), enrollment_domain='',
|
||||
invitation_only=invitation
|
||||
)
|
||||
|
||||
def test__user_passed_as_none(self):
|
||||
"""Ensure has_access handles a user being passed as null"""
|
||||
access.has_access(None, 'staff', 'global', None)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Toggles for courseware in-course experience.
|
||||
"""
|
||||
|
||||
from edx_toggles.toggles import LegacyWaffleFlagNamespace
|
||||
from edx_toggles.toggles import LegacyWaffleFlagNamespace, SettingToggle
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag
|
||||
@@ -190,3 +190,18 @@ def streak_celebration_is_active(course_key):
|
||||
courseware_mfe_progress_milestones_are_active(course_key) and
|
||||
COURSEWARE_MICROFRONTEND_PROGRESS_MILESTONES_STREAK_CELEBRATION.is_enabled(course_key)
|
||||
)
|
||||
|
||||
|
||||
# .. toggle_name: COURSES_INVITE_ONLY
|
||||
# .. toggle_implementation: SettingToggle
|
||||
# .. toggle_type: feature_flag
|
||||
# .. toggle_default: False
|
||||
# .. toggle_description: Setting this sets the default value of INVITE_ONLY across all courses in a given deployment
|
||||
# .. toggle_category: admin
|
||||
# .. toggle_use_cases: open_edx
|
||||
# .. toggle_creation_date: 2019-05-16
|
||||
# .. toggle_expiration_date: None
|
||||
# .. toggle_tickets: https://github.com/mitodl/edx-platform/issues/123
|
||||
# .. toggle_status: unsupported
|
||||
def is_courses_default_invite_only_enabled():
|
||||
return SettingToggle("COURSES_INVITE_ONLY", default=False).is_enabled()
|
||||
|
||||
@@ -86,6 +86,8 @@ from lms.djangoapps.courseware.permissions import ( # lint-amnesty, pylint: dis
|
||||
VIEW_COURSEWARE,
|
||||
VIEW_XQA_INTERFACE
|
||||
)
|
||||
|
||||
from lms.djangoapps.courseware.toggles import is_courses_default_invite_only_enabled
|
||||
from lms.djangoapps.courseware.user_state_client import DjangoXBlockUserStateClient
|
||||
from lms.djangoapps.experiments.utils import get_experiment_user_metadata_context
|
||||
from lms.djangoapps.grades.api import CourseGradeFactory
|
||||
@@ -947,7 +949,7 @@ def course_about(request, course_id):
|
||||
|
||||
# Used to provide context to message to student if enrollment not allowed
|
||||
can_enroll = bool(request.user.has_perm(ENROLL_IN_COURSE, course))
|
||||
invitation_only = course.invitation_only
|
||||
invitation_only = is_courses_default_invite_only_enabled() or course.invitation_only
|
||||
is_course_full = CourseEnrollment.objects.is_course_full(course)
|
||||
|
||||
# Register button should be disabled if one of the following is true:
|
||||
|
||||
@@ -5,6 +5,7 @@ from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import pgettext
|
||||
from django.urls import reverse
|
||||
from lms.djangoapps.courseware.courses import get_course_about_section
|
||||
from lms.djangoapps.courseware.toggles import is_courses_default_invite_only_enabled
|
||||
from django.conf import settings
|
||||
from six import text_type
|
||||
from common.djangoapps.edxmako.shortcuts import marketing_link
|
||||
@@ -90,7 +91,7 @@ from six import string_types
|
||||
<span class="register disabled">
|
||||
${_("Course is full")}
|
||||
</span>
|
||||
% elif invitation_only and not can_enroll:
|
||||
% elif (is_courses_default_invite_only_enabled() or invitation_only) and not can_enroll:
|
||||
<span class="register disabled">${_("Enrollment in this course is by invitation only")}</span>
|
||||
## Shib courses need the enrollment button to be displayed even when can_enroll is False,
|
||||
## because AnonymousUsers cause can_enroll for shib courses to be False, but we need them to be able to click
|
||||
|
||||
Reference in New Issue
Block a user