MA-3091: Added mobile_available flag override admin setting
This commit is contained in:
@@ -33,9 +33,25 @@ from xmodule.x_module import XModule
|
||||
from xmodule.split_test_module import get_split_user_partitions
|
||||
from xmodule.partitions.partitions import NoSuchUserPartitionError, NoSuchUserPartitionGroupError
|
||||
|
||||
from openedx.core.djangoapps.external_auth.models import ExternalAuthMap
|
||||
from courseware.access_response import (
|
||||
MilestoneError,
|
||||
MobileAvailabilityError,
|
||||
VisibilityError,
|
||||
)
|
||||
from courseware.access_utils import (
|
||||
ACCESS_DENIED,
|
||||
ACCESS_GRANTED,
|
||||
adjust_start_date,
|
||||
check_start_date,
|
||||
debug,
|
||||
in_preview_mode
|
||||
)
|
||||
from courseware.masquerade import get_masquerade_role, is_masquerading_as_student
|
||||
from lms.djangoapps.ccx.custom_exception import CCXLocatorValidationException
|
||||
from lms.djangoapps.ccx.models import CustomCourseForEdX
|
||||
from mobile_api.models import IgnoreMobileAvailableFlagConfig
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.djangoapps.external_auth.models import ExternalAuthMap
|
||||
from student import auth
|
||||
from student.models import CourseEnrollmentAllowed
|
||||
from student.roles import (
|
||||
@@ -55,19 +71,6 @@ from util.milestones_helpers import (
|
||||
)
|
||||
from ccx_keys.locator import CCXLocator
|
||||
|
||||
from courseware.access_response import (
|
||||
MilestoneError,
|
||||
MobileAvailabilityError,
|
||||
VisibilityError,
|
||||
)
|
||||
from courseware.access_utils import (
|
||||
adjust_start_date, check_start_date, debug, ACCESS_GRANTED, ACCESS_DENIED,
|
||||
in_preview_mode
|
||||
)
|
||||
|
||||
from lms.djangoapps.ccx.custom_exception import CCXLocatorValidationException
|
||||
from lms.djangoapps.ccx.models import CustomCourseForEdX
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -849,7 +852,10 @@ def _is_descriptor_mobile_available(descriptor):
|
||||
"""
|
||||
Returns if descriptor is available on mobile.
|
||||
"""
|
||||
return ACCESS_GRANTED if descriptor.mobile_available else MobileAvailabilityError()
|
||||
if IgnoreMobileAvailableFlagConfig.is_enabled() or descriptor.mobile_available:
|
||||
return ACCESS_GRANTED
|
||||
else:
|
||||
return MobileAvailabilityError()
|
||||
|
||||
|
||||
def is_mobile_available_for_user(user, descriptor):
|
||||
|
||||
@@ -4,9 +4,14 @@ Django admin dashboard configuration for LMS XBlock infrastructure.
|
||||
|
||||
from django.contrib import admin
|
||||
from config_models.admin import ConfigurationModelAdmin
|
||||
from mobile_api.models import MobileApiConfig, AppVersionConfig
|
||||
from .models import (
|
||||
AppVersionConfig,
|
||||
MobileApiConfig,
|
||||
IgnoreMobileAvailableFlagConfig
|
||||
)
|
||||
|
||||
admin.site.register(MobileApiConfig, ConfigurationModelAdmin)
|
||||
admin.site.register(IgnoreMobileAvailableFlagConfig, ConfigurationModelAdmin)
|
||||
|
||||
|
||||
class AppVersionConfigAdmin(admin.ModelAdmin):
|
||||
|
||||
@@ -9,7 +9,7 @@ from courseware.courses import get_course_info_section_module
|
||||
from static_replace import make_static_urls_absolute
|
||||
from openedx.core.lib.xblock_utils import get_course_update_items
|
||||
|
||||
from ..utils import mobile_view, mobile_course_access
|
||||
from ..decorators import mobile_course_access, mobile_view
|
||||
|
||||
|
||||
@mobile_view()
|
||||
|
||||
52
lms/djangoapps/mobile_api/decorators.py
Normal file
52
lms/djangoapps/mobile_api/decorators.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""
|
||||
Decorators for Mobile APIs.
|
||||
"""
|
||||
import functools
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
|
||||
from lms.djangoapps.courseware.courses import get_course_with_access
|
||||
from lms.djangoapps.courseware.courseware_access_exception import CoursewareAccessException
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
from openedx.core.lib.api.view_utils import view_auth_classes
|
||||
|
||||
|
||||
def mobile_course_access(depth=0):
|
||||
"""
|
||||
Method decorator for a mobile API endpoint that verifies the user has access to the course in a mobile context.
|
||||
"""
|
||||
def _decorator(func):
|
||||
"""Outer method decorator."""
|
||||
|
||||
@functools.wraps(func)
|
||||
def _wrapper(self, request, *args, **kwargs):
|
||||
"""
|
||||
Expects kwargs to contain 'course_id'.
|
||||
Passes the course descriptor to the given decorated function.
|
||||
Raises 404 if access to course is disallowed.
|
||||
"""
|
||||
course_id = CourseKey.from_string(kwargs.pop('course_id'))
|
||||
with modulestore().bulk_operations(course_id):
|
||||
try:
|
||||
course = get_course_with_access(
|
||||
request.user,
|
||||
'load_mobile',
|
||||
course_id,
|
||||
depth=depth,
|
||||
check_if_enrolled=True,
|
||||
)
|
||||
except CoursewareAccessException as error:
|
||||
return Response(data=error.to_json(), status=status.HTTP_404_NOT_FOUND)
|
||||
return func(self, request, course=course, *args, **kwargs)
|
||||
|
||||
return _wrapper
|
||||
return _decorator
|
||||
|
||||
|
||||
def mobile_view(is_user=False):
|
||||
"""
|
||||
Function and class decorator that abstracts the authentication and permission checks for mobile api views.
|
||||
"""
|
||||
return view_auth_classes(is_user)
|
||||
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('mobile_api', '0002_auto_20160406_0904'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='IgnoreMobileAvailableFlagConfig',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')),
|
||||
('enabled', models.BooleanField(default=False, verbose_name='Enabled')),
|
||||
('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, editable=False, to=settings.AUTH_USER_MODEL, null=True, verbose_name='Changed by')),
|
||||
],
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='mobileapiconfig',
|
||||
options={},
|
||||
),
|
||||
]
|
||||
@@ -2,9 +2,10 @@
|
||||
ConfigurationModel for the mobile_api djangoapp.
|
||||
"""
|
||||
from django.db import models
|
||||
from mobile_api import utils
|
||||
|
||||
from config_models.models import ConfigurationModel
|
||||
from mobile_api.mobile_platform import PLATFORM_CLASSES
|
||||
from .mobile_platform import PLATFORM_CLASSES
|
||||
from . import utils
|
||||
|
||||
|
||||
class MobileApiConfig(ConfigurationModel):
|
||||
@@ -19,6 +20,9 @@ class MobileApiConfig(ConfigurationModel):
|
||||
help_text="A comma-separated list of names of profiles to include for videos returned from the mobile API."
|
||||
)
|
||||
|
||||
class Meta(object):
|
||||
app_label = "mobile_api"
|
||||
|
||||
@classmethod
|
||||
def get_video_profiles(cls):
|
||||
"""
|
||||
@@ -50,6 +54,7 @@ class AppVersionConfig(models.Model):
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
app_label = "mobile_api"
|
||||
unique_together = ('platform', 'version',)
|
||||
ordering = ['-major_version', '-minor_version', '-patch_version']
|
||||
|
||||
@@ -76,3 +81,16 @@ class AppVersionConfig(models.Model):
|
||||
""" parses version into major, minor and patch versions before saving """
|
||||
self.major_version, self.minor_version, self.patch_version = utils.parsed_version(self.version)
|
||||
super(AppVersionConfig, self).save(*args, **kwargs)
|
||||
|
||||
|
||||
class IgnoreMobileAvailableFlagConfig(ConfigurationModel): # pylint: disable=W5101
|
||||
"""
|
||||
Configuration for the mobile_available flag. Default is false.
|
||||
|
||||
Enabling this configuration will cause the mobile_available flag check in
|
||||
access.py._is_descriptor_mobile_available to ignore the mobile_available
|
||||
flag.
|
||||
"""
|
||||
|
||||
class Meta(object):
|
||||
app_label = "mobile_api"
|
||||
|
||||
@@ -6,7 +6,7 @@ Tests for mobile API utilities.
|
||||
import ddt
|
||||
from django.test import TestCase
|
||||
|
||||
from mobile_api.utils import mobile_course_access, mobile_view
|
||||
from ..decorators import mobile_course_access, mobile_view
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
|
||||
@@ -30,6 +30,7 @@ from courseware.access_response import (
|
||||
from courseware.tests.factories import UserFactory
|
||||
from student import auth
|
||||
from student.models import CourseEnrollment
|
||||
from mobile_api.models import IgnoreMobileAvailableFlagConfig
|
||||
from mobile_api.tests.test_milestones import MobileAPIMilestonesMixin
|
||||
|
||||
|
||||
@@ -46,6 +47,7 @@ class MobileAPITestCase(ModuleStoreTestCase, APITestCase):
|
||||
self.user = UserFactory.create()
|
||||
self.password = 'test'
|
||||
self.username = self.user.username
|
||||
IgnoreMobileAvailableFlagConfig(enabled=False).save()
|
||||
|
||||
def tearDown(self):
|
||||
super(MobileAPITestCase, self).tearDown()
|
||||
@@ -188,12 +190,21 @@ class MobileCourseAccessTestMixin(MobileAPIMilestonesMixin):
|
||||
@ddt.unpack
|
||||
@patch.dict(settings.FEATURES, {'ENABLE_MKTG_SITE': True})
|
||||
def test_non_mobile_available(self, role, should_succeed):
|
||||
"""
|
||||
Tests that the MobileAvailabilityError() is raised for certain user
|
||||
roles when trying to access course content. Also verifies that if
|
||||
the IgnoreMobileAvailableFlagConfig is enabled,
|
||||
MobileAvailabilityError() will not be raised for all user roles.
|
||||
"""
|
||||
self.init_course_access()
|
||||
# set mobile_available to False for the test course
|
||||
self.course.mobile_available = False
|
||||
self.store.update_item(self.course, self.user.id)
|
||||
self._verify_response(should_succeed, MobileAvailabilityError(), role)
|
||||
|
||||
IgnoreMobileAvailableFlagConfig(enabled=True).save()
|
||||
self._verify_response(True, MobileAvailabilityError(), role)
|
||||
|
||||
def test_unenrolled_user(self):
|
||||
self.login()
|
||||
self.unenroll()
|
||||
|
||||
@@ -5,9 +5,9 @@ from opaque_keys.edx.keys import CourseKey
|
||||
from rest_framework import serializers
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from certificates.api import certificate_downloadable_status
|
||||
from courseware.access import has_access
|
||||
from student.models import CourseEnrollment, User
|
||||
from certificates.api import certificate_downloadable_status
|
||||
from util.course import get_lms_link_for_about_page
|
||||
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ from mobile_api.testutils import (
|
||||
MobileAuthUserTestMixin,
|
||||
MobileCourseAccessTestMixin,
|
||||
)
|
||||
|
||||
from .serializers import CourseEnrollmentSerializer
|
||||
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
|
||||
from .serializers import CourseEnrollmentSerializer, UserSerializer
|
||||
from .. import errors
|
||||
from ..utils import mobile_view, mobile_course_access
|
||||
from ..decorators import mobile_course_access, mobile_view
|
||||
|
||||
|
||||
@mobile_view(is_user=True)
|
||||
@@ -60,8 +60,7 @@ class UserDetail(generics.RetrieveAPIView):
|
||||
* username: The username of the currently signed in user.
|
||||
"""
|
||||
queryset = (
|
||||
User.objects.all()
|
||||
.select_related('profile')
|
||||
User.objects.all().select_related('profile')
|
||||
)
|
||||
serializer_class = UserSerializer
|
||||
lookup_field = 'username'
|
||||
|
||||
@@ -1,54 +1,6 @@
|
||||
"""
|
||||
Common utility methods and decorators for Mobile APIs.
|
||||
Common utility methods for Mobile APIs.
|
||||
"""
|
||||
import functools
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
|
||||
from lms.djangoapps.courseware.courses import get_course_with_access
|
||||
from lms.djangoapps.courseware.courseware_access_exception import CoursewareAccessException
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from openedx.core.lib.api.view_utils import view_auth_classes
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
|
||||
def mobile_course_access(depth=0):
|
||||
"""
|
||||
Method decorator for a mobile API endpoint that verifies the user has access to the course in a mobile context.
|
||||
"""
|
||||
def _decorator(func):
|
||||
"""Outer method decorator."""
|
||||
|
||||
@functools.wraps(func)
|
||||
def _wrapper(self, request, *args, **kwargs):
|
||||
"""
|
||||
Expects kwargs to contain 'course_id'.
|
||||
Passes the course descriptor to the given decorated function.
|
||||
Raises 404 if access to course is disallowed.
|
||||
"""
|
||||
course_id = CourseKey.from_string(kwargs.pop('course_id'))
|
||||
with modulestore().bulk_operations(course_id):
|
||||
try:
|
||||
course = get_course_with_access(
|
||||
request.user,
|
||||
'load_mobile',
|
||||
course_id,
|
||||
depth=depth,
|
||||
check_if_enrolled=True,
|
||||
)
|
||||
except CoursewareAccessException as error:
|
||||
return Response(data=error.to_json(), status=status.HTTP_404_NOT_FOUND)
|
||||
return func(self, request, course=course, *args, **kwargs)
|
||||
|
||||
return _wrapper
|
||||
return _decorator
|
||||
|
||||
|
||||
def mobile_view(is_user=False):
|
||||
"""
|
||||
Function and class decorator that abstracts the authentication and permission checks for mobile api views.
|
||||
"""
|
||||
return view_auth_classes(is_user)
|
||||
|
||||
|
||||
def parsed_version(version):
|
||||
|
||||
@@ -18,7 +18,7 @@ from opaque_keys.edx.locator import BlockUsageLocator
|
||||
from xmodule.exceptions import NotFoundError
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
from ..utils import mobile_view, mobile_course_access
|
||||
from ..decorators import mobile_course_access, mobile_view
|
||||
from .serializers import BlockOutline, video_summary
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user