From a5e5591262b18e0c796ed2df888d974b48078e7b Mon Sep 17 00:00:00 2001 From: "Dave St.Germain" Date: Tue, 23 Apr 2019 14:14:32 -0400 Subject: [PATCH] Automatically add master's to the group access of verified content --- common/djangoapps/course_modes/helpers.py | 39 +++++++++++++++++++ common/djangoapps/course_modes/signals.py | 18 +++++++++ .../course_modes/tests/test_signals.py | 29 +++++++++++++- 3 files changed, 85 insertions(+), 1 deletion(-) diff --git a/common/djangoapps/course_modes/helpers.py b/common/djangoapps/course_modes/helpers.py index a7a04e85e9..1444d1a9ff 100644 --- a/common/djangoapps/course_modes/helpers.py +++ b/common/djangoapps/course_modes/helpers.py @@ -1,14 +1,25 @@ """ Helper methods for CourseModes. """ +from __future__ import absolute_import, unicode_literals +import logging + +from django.conf import settings from django.utils.translation import ugettext_lazy as _ from course_modes.models import CourseMode from student.helpers import VERIFY_STATUS_APPROVED, VERIFY_STATUS_NEED_TO_VERIFY, VERIFY_STATUS_SUBMITTED +from xmodule.partitions.partitions import ENROLLMENT_TRACK_PARTITION_ID + DISPLAY_VERIFIED = "verified" DISPLAY_HONOR = "honor" DISPLAY_AUDIT = "audit" DISPLAY_PROFESSIONAL = "professional" +MASTERS_ID = settings.COURSE_ENROLLMENT_MODES.get('masters', {}).get('id', None) +VERIFIED_ID = settings.COURSE_ENROLLMENT_MODES['verified']['id'] + +log = logging.getLogger(__name__) + def enrollment_mode_display(mode, verification_status, course_id): """ Select appropriate display strings and CSS classes. @@ -80,3 +91,31 @@ def _enrollment_mode_display(enrollment_mode, verification_status, course_id): display_mode = enrollment_mode return display_mode + + +def update_masters_access(item): + """ + Update the XBlock's group access to allow the master's group, + in addition to the verified content group. + """ + group_access = item.group_access + enrollment_groups = group_access.get(ENROLLMENT_TRACK_PARTITION_ID, None) + if enrollment_groups is not None: + if VERIFIED_ID in enrollment_groups and MASTERS_ID not in enrollment_groups: + enrollment_groups.append(MASTERS_ID) + item.group_access = group_access + return True + + +def update_masters_access_course(store, course_id, user_id): + """ + Update all blocks in the verified content group to include the master's content group + """ + + with store.bulk_operations(course_id): + items = store.get_items(course_id, settings={'group_access': {'$exists': True}}, include_orphans=False) + for item in items: + if update_masters_access(item): + log.info("Publishing %s with Master's group access", item.location) + store.update_item(item, user_id) + store.publish(item.location, user_id) diff --git a/common/djangoapps/course_modes/signals.py b/common/djangoapps/course_modes/signals.py index ed8c550211..06f959990a 100644 --- a/common/djangoapps/course_modes/signals.py +++ b/common/djangoapps/course_modes/signals.py @@ -1,11 +1,17 @@ """ Signal handler for setting default course mode expiration dates """ +from __future__ import absolute_import, unicode_literals + +from crum import get_current_user + from django.core.exceptions import ObjectDoesNotExist +from django.db.models.signals import post_save from django.dispatch.dispatcher import receiver from xmodule.modulestore.django import SignalHandler, modulestore +from .helpers import update_masters_access_course from .models import CourseMode, CourseModeExpirationConfig @@ -35,3 +41,15 @@ def _listen_for_course_publish(sender, course_key, **kwargs): # pylint: disable 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) + + +@receiver(post_save, sender=CourseMode) +def update_access_for_masters_mode(sender, instance=None, **kwargs): # pylint: disable=unused-argument + """ + Adds master's access to verified content when the master's mode is created + """ + if instance.mode_slug != CourseMode.MASTERS: + return + user = get_current_user() + user_id = user.id if user else None + update_masters_access_course(modulestore(), instance.course_id, user_id) diff --git a/common/djangoapps/course_modes/tests/test_signals.py b/common/djangoapps/course_modes/tests/test_signals.py index 853622808b..4c7dc66809 100644 --- a/common/djangoapps/course_modes/tests/test_signals.py +++ b/common/djangoapps/course_modes/tests/test_signals.py @@ -1,6 +1,7 @@ """ Unit tests for the course_mode signals """ +from __future__ import absolute_import, unicode_literals from datetime import datetime, timedelta @@ -10,8 +11,9 @@ from pytz import UTC from course_modes.models import CourseMode from course_modes.signals import _listen_for_course_publish +from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase -from xmodule.modulestore.tests.factories import CourseFactory +from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory @ddt.ddt @@ -87,3 +89,28 @@ class CourseModeSignalTest(ModuleStoreTestCase): course_mode.refresh_from_db() self.assertEqual(course_mode.expiration_datetime, self.end - timedelta(days=verification_window)) + + def test_masters_mode(self): + # create an xblock with verified group access + verified_section = ItemFactory.create( + category="sequential", + metadata={'group_access': {50: [2]}} + ) + # and a section with no restriction + section2 = ItemFactory.create( + category="sequential", + ) + section3 = ItemFactory.create( + category='sequential', + metadata={'group_access': {50: [1]}} + ) + with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred): + # create the master's mode. signal will add masters to the verified section + self.create_mode('masters', 'masters') + verified_section_ret = self.store.get_item(verified_section.location) + section2_ret = self.store.get_item(section2.location) + section3_ret = self.store.get_item(section3.location) + # group 2 is verified. 7 is masters + assert verified_section_ret.group_access[50] == [2, 7] + assert section2_ret.group_access == {} + assert section2_ret.group_access == {50: [1]}