From 95fdbd981c4109828e0000abfff7bbcf12057e03 Mon Sep 17 00:00:00 2001 From: Nimisha Asthagiri Date: Wed, 12 Aug 2015 23:13:33 +0530 Subject: [PATCH] Refactor has_access for re-usability. --- lms/djangoapps/courseware/access.py | 60 ++------------ lms/djangoapps/courseware/access_utils.py | 78 +++++++++++++++++++ .../courseware/tests/test_access.py | 4 +- lms/djangoapps/courseware/views.py | 3 +- 4 files changed, 87 insertions(+), 58 deletions(-) create mode 100644 lms/djangoapps/courseware/access_utils.py diff --git a/lms/djangoapps/courseware/access.py b/lms/djangoapps/courseware/access.py index 348f5ba6ca..ccaa29827a 100644 --- a/lms/djangoapps/courseware/access.py +++ b/lms/djangoapps/courseware/access.py @@ -10,7 +10,7 @@ Note: The access control logic in this file does NOT check for enrollment in If enrollment is to be checked, use get_course_with_access in courseware.courses. It is a wrapper around has_access that additionally checks for enrollment. """ -from datetime import datetime, timedelta +from datetime import datetime import logging import pytz @@ -31,7 +31,6 @@ from xmodule.error_module import ErrorDescriptor from xmodule.x_module import XModule, DEPRECATION_VSCOMPAT_EVENT from xmodule.split_test_module import get_split_user_partitions from xmodule.partitions.partitions import NoSuchUserPartitionError, NoSuchUserPartitionGroupError -from xmodule.util.django import get_current_request_hostname from external_auth.models import ExternalAuthMap from courseware.masquerade import get_masquerade_role, is_masquerading_as_student @@ -57,26 +56,15 @@ from ccx_keys.locator import CCXLocator import dogstats_wrapper as dog_stats_api from courseware.access_response import ( - AccessResponse, MilestoneError, MobileAvailabilityError, - StartDateError, VisibilityError, ) - -DEBUG_ACCESS = False -ACCESS_GRANTED = AccessResponse(True) -ACCESS_DENIED = AccessResponse(False) +from courseware.access_utils import adjust_start_date, check_start_date, debug, ACCESS_GRANTED, ACCESS_DENIED log = logging.getLogger(__name__) -def debug(*args, **kwargs): - # to avoid overly verbose output, this is off by default - if DEBUG_ACCESS: - log.debug(*args, **kwargs) - - def has_access(user, action, obj, course_key=None): """ Check whether a user has the access to do action on obj. Handles any magic @@ -173,24 +161,7 @@ def _can_access_descriptor_with_start_date(user, descriptor, course_key): # pyl AccessResponse: The result of this access check. Possible results are ACCESS_GRANTED or a StartDateError. """ - start_dates_disabled = settings.FEATURES['DISABLE_START_DATES'] - if start_dates_disabled and not is_masquerading_as_student(user, course_key): - return ACCESS_GRANTED - else: - now = datetime.now(UTC()) - effective_start = _adjust_start_date_for_beta_testers( - user, - descriptor, - course_key=course_key - ) - if ( - descriptor.start is None - or now > effective_start - or in_preview_mode() - ): - return ACCESS_GRANTED - - return StartDateError(descriptor.start) + return check_start_date(user, descriptor.days_early_for_beta, descriptor.start, course_key) def _can_view_courseware_with_prerequisites(user, course): # pylint: disable=invalid-name @@ -698,7 +669,7 @@ def _dispatch(table, action, user, obj): type(obj), action)) -def _adjust_start_date_for_beta_testers(user, descriptor, course_key=None): # pylint: disable=invalid-name +def _adjust_start_date_for_beta_testers(user, descriptor, course_key): # pylint: disable=invalid-name,unused-argument """ If user is in a beta test group, adjust the start date by the appropriate number of days. @@ -717,21 +688,8 @@ def _adjust_start_date_for_beta_testers(user, descriptor, course_key=None): # p NOTE: For now, this function assumes that the descriptor's location is in the course the user is looking at. Once we have proper usages and definitions per the XBlock design, this should use the course the usage is in. - - NOTE: If testing manually, make sure FEATURES['DISABLE_START_DATES'] = False - in envs/dev.py! """ - if descriptor.days_early_for_beta is None: - # bail early if no beta testing is set up - return descriptor.start - - if CourseBetaTesterRole(course_key).has_user(user): - debug("Adjust start time: user in beta role for %s", descriptor) - delta = timedelta(descriptor.days_early_for_beta) - effective = descriptor.start - delta - return effective - - return descriptor.start + return adjust_start_date(user, descriptor.days_early_for_beta, descriptor.start, course_key) def _has_instructor_access_to_location(user, location, course_key=None): @@ -898,11 +856,3 @@ def get_user_role(user, course_key): return 'staff' else: return 'student' - - -def in_preview_mode(): - """ - Returns whether the user is in preview mode or not. - """ - hostname = get_current_request_hostname() - return bool(hostname and settings.PREVIEW_DOMAIN in hostname.split('.')) diff --git a/lms/djangoapps/courseware/access_utils.py b/lms/djangoapps/courseware/access_utils.py new file mode 100644 index 0000000000..44f8356abf --- /dev/null +++ b/lms/djangoapps/courseware/access_utils.py @@ -0,0 +1,78 @@ +""" +Simple utility functions for computing access. +It allows us to share code between access.py and block transformers. +""" + +from datetime import datetime, timedelta +from django.conf import settings +from django.utils.timezone import UTC +from logging import getLogger +from student.roles import CourseBetaTesterRole +from courseware.masquerade import is_masquerading_as_student +from courseware.access_response import AccessResponse, StartDateError +from xmodule.util.django import get_current_request_hostname + + +DEBUG_ACCESS = False +log = getLogger(__name__) + +ACCESS_GRANTED = AccessResponse(True) +ACCESS_DENIED = AccessResponse(False) + + +def debug(*args, **kwargs): + """ + Helper function for local debugging. + """ + # to avoid overly verbose output, this is off by default + if DEBUG_ACCESS: + log.debug(*args, **kwargs) + + +def adjust_start_date(user, days_early_for_beta, start, course_key): + """ + If user is in a beta test group, adjust the start date by the appropriate number of + days. + + Returns: + A datetime. Either the same as start, or earlier for beta testers. + """ + if days_early_for_beta is None: + # bail early if no beta testing is set up + return start + + if CourseBetaTesterRole(course_key).has_user(user): + debug("Adjust start time: user in beta role for %s", course_key) + delta = timedelta(days_early_for_beta) + effective = start - delta + return effective + + return start + + +def check_start_date(user, days_early_for_beta, start, course_key): + """ + Verifies whether the given user is allowed access given the + start date and the Beta offset for the given course. + + Returns: + AccessResponse: Either ACCESS_GRANTED or StartDateError. + """ + start_dates_disabled = settings.FEATURES['DISABLE_START_DATES'] + if start_dates_disabled and not is_masquerading_as_student(user, course_key): + return ACCESS_GRANTED + else: + now = datetime.now(UTC()) + effective_start = adjust_start_date(user, days_early_for_beta, start, course_key) + if start is None or now > effective_start or in_preview_mode(): + return ACCESS_GRANTED + + return StartDateError(start) + + +def in_preview_mode(): + """ + Returns whether the user is in preview mode or not. + """ + hostname = get_current_request_hostname() + return bool(hostname and settings.PREVIEW_DOMAIN in hostname.split('.')) diff --git a/lms/djangoapps/courseware/tests/test_access.py b/lms/djangoapps/courseware/tests/test_access.py index 109a77e32b..d899c734ae 100644 --- a/lms/djangoapps/courseware/tests/test_access.py +++ b/lms/djangoapps/courseware/tests/test_access.py @@ -207,7 +207,7 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): @ddt.data(None, YESTERDAY, TOMORROW) @patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False}) - @patch('courseware.access.get_current_request_hostname', Mock(return_value='preview.localhost')) + @patch('courseware.access_utils.get_current_request_hostname', Mock(return_value='preview.localhost')) def test__has_access_descriptor_in_preview_mode(self, start): """ Tests that descriptor has access in preview mode. @@ -225,7 +225,7 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): ) # ddt throws an error if I don't put the None argument there @ddt.unpack @patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False}) - @patch('courseware.access.get_current_request_hostname', Mock(return_value='localhost')) + @patch('courseware.access_utils.get_current_request_hostname', Mock(return_value='localhost')) def test__has_access_descriptor_when_not_in_preview_mode(self, start, expected_error_type): """ Tests that descriptor has no access when start date in future & without preview. diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index 024a12909f..dd2caa2dce 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -27,8 +27,9 @@ from django.db import transaction from markupsafe import escape from courseware import grades -from courseware.access import has_access, in_preview_mode, _adjust_start_date_for_beta_testers +from courseware.access import has_access, _adjust_start_date_for_beta_testers from courseware.access_response import StartDateError +from courseware.access_utils import in_preview_mode from courseware.courses import ( get_courses, get_course, get_course_by_id, get_studio_url, get_course_with_access,