Commit b5e987ba by christopher lee

MA-3091: Added mobile_available flag override admin setting

parent 6572b1df
......@@ -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()
......
"""
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)
# -*- 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'
......
"""
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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment