Merge pull request #6572 from edx/mobile/fix-decorator-api
Fix Mobile API decorators
This commit is contained in:
@@ -9,10 +9,10 @@ from courseware.courses import get_course_about_section, get_course_info_section
|
||||
from static_replace import make_static_urls_absolute, replace_static_urls
|
||||
from xmodule_modifiers import get_course_update_items
|
||||
|
||||
from ..utils import MobileView, mobile_course_access
|
||||
from ..utils import mobile_view, mobile_course_access
|
||||
|
||||
|
||||
@MobileView()
|
||||
@mobile_view()
|
||||
class CourseUpdatesList(generics.ListAPIView):
|
||||
"""
|
||||
**Use Case**
|
||||
@@ -57,7 +57,7 @@ class CourseUpdatesList(generics.ListAPIView):
|
||||
return Response(updates_to_show)
|
||||
|
||||
|
||||
@MobileView()
|
||||
@mobile_view()
|
||||
class CourseHandoutsList(generics.ListAPIView):
|
||||
"""
|
||||
**Use Case**
|
||||
@@ -89,7 +89,7 @@ class CourseHandoutsList(generics.ListAPIView):
|
||||
raise Http404(u"No handouts for {}".format(unicode(course.id)))
|
||||
|
||||
|
||||
@MobileView()
|
||||
@mobile_view()
|
||||
class CourseAboutDetail(generics.RetrieveAPIView):
|
||||
"""
|
||||
**Use Case**
|
||||
|
||||
@@ -4,17 +4,18 @@ Tests for mobile API utilities.
|
||||
|
||||
import ddt
|
||||
from mock import patch
|
||||
from django.test import TestCase
|
||||
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
from .utils import mobile_access_when_enrolled
|
||||
from .utils import mobile_access_when_enrolled, mobile_course_access, mobile_view
|
||||
from .testutils import MobileAPITestCase, ROLE_CASES
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestMobileApiUtils(MobileAPITestCase):
|
||||
class TestMobileAccessWhenEnrolled(MobileAPITestCase):
|
||||
"""
|
||||
Tests for mobile API utilities
|
||||
Tests for mobile_access_when_enrolled utility function.
|
||||
"""
|
||||
@ddt.data(*ROLE_CASES)
|
||||
@ddt.unpack
|
||||
@@ -45,3 +46,22 @@ class TestMobileApiUtils(MobileAPITestCase):
|
||||
Verifies that we handle the case where a course hasn't started
|
||||
"""
|
||||
self.assertFalse(mobile_access_when_enrolled(self.course, self.user))
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestMobileAPIDecorators(TestCase):
|
||||
"""
|
||||
Basic tests for mobile api decorators to ensure they retain the docstrings.
|
||||
"""
|
||||
@ddt.data(mobile_view, mobile_course_access)
|
||||
def test_function_decorator(self, decorator):
|
||||
@decorator()
|
||||
def decorated_func():
|
||||
"""
|
||||
Test docstring of decorated function.
|
||||
"""
|
||||
pass
|
||||
|
||||
self.assertIn("Test docstring of decorated function.", decorated_func.__doc__)
|
||||
self.assertEquals(decorated_func.__name__, "decorated_func")
|
||||
self.assertTrue(decorated_func.__module__.endswith("tests"))
|
||||
|
||||
@@ -5,8 +5,8 @@ Test utilities for mobile API tests:
|
||||
No tests are implemented in this base class.
|
||||
|
||||
Test Mixins to be included by concrete test classes and provide implementation of common test methods:
|
||||
MobileAuthTestMixin - tests for APIs with MobileView/mobile_view and is_user=False.
|
||||
MobileAuthUserTestMixin - tests for APIs with MobileView/mobile_view and is_user=True.
|
||||
MobileAuthTestMixin - tests for APIs with mobile_view and is_user=False.
|
||||
MobileAuthUserTestMixin - tests for APIs with mobile_view and is_user=True.
|
||||
MobileCourseAccessTestMixin - tests for APIs with mobile_course_access and verify_enrolled=False.
|
||||
MobileEnrolledCourseAccessTestMixin - tests for APIs with mobile_course_access and verify_enrolled=True.
|
||||
"""
|
||||
@@ -101,7 +101,7 @@ class MobileAPITestCase(ModuleStoreTestCase, APITestCase):
|
||||
|
||||
class MobileAuthTestMixin(object):
|
||||
"""
|
||||
Test Mixin for testing APIs decorated with MobileView or mobile_view.
|
||||
Test Mixin for testing APIs decorated with mobile_view.
|
||||
"""
|
||||
def test_no_auth(self):
|
||||
self.logout()
|
||||
@@ -110,7 +110,7 @@ class MobileAuthTestMixin(object):
|
||||
|
||||
class MobileAuthUserTestMixin(MobileAuthTestMixin):
|
||||
"""
|
||||
Test Mixin for testing APIs related to users: mobile_view or MobileView with is_user=True.
|
||||
Test Mixin for testing APIs related to users: mobile_view with is_user=True.
|
||||
"""
|
||||
def test_invalid_user(self):
|
||||
self.login_and_enroll()
|
||||
|
||||
@@ -26,10 +26,10 @@ from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
|
||||
from .serializers import CourseEnrollmentSerializer, UserSerializer
|
||||
from mobile_api import errors
|
||||
from mobile_api.utils import mobile_access_when_enrolled, mobile_view, MobileView, mobile_course_access
|
||||
from mobile_api.utils import mobile_access_when_enrolled, mobile_view, mobile_course_access
|
||||
|
||||
|
||||
@MobileView(is_user=True)
|
||||
@mobile_view(is_user=True)
|
||||
class UserDetail(generics.RetrieveAPIView):
|
||||
"""
|
||||
**Use Case**
|
||||
@@ -67,7 +67,7 @@ class UserDetail(generics.RetrieveAPIView):
|
||||
lookup_field = 'username'
|
||||
|
||||
|
||||
@MobileView(is_user=True)
|
||||
@mobile_view(is_user=True)
|
||||
class UserCourseStatus(views.APIView):
|
||||
"""
|
||||
Endpoints for getting and setting meta data
|
||||
@@ -202,7 +202,7 @@ class UserCourseStatus(views.APIView):
|
||||
return self._get_course_info(request, course)
|
||||
|
||||
|
||||
@MobileView(is_user=True)
|
||||
@mobile_view(is_user=True)
|
||||
class UserCourseEnrollmentsList(generics.ListAPIView):
|
||||
"""
|
||||
**Use Case**
|
||||
|
||||
@@ -55,7 +55,7 @@ def mobile_access_when_enrolled(course, user):
|
||||
|
||||
def mobile_view(is_user=False):
|
||||
"""
|
||||
Function decorator that abstracts the authentication and permission checks for mobile api views.
|
||||
Function and class decorator that abstracts the authentication and permission checks for mobile api views.
|
||||
"""
|
||||
class IsUser(permissions.BasePermission):
|
||||
"""
|
||||
@@ -64,28 +64,14 @@ def mobile_view(is_user=False):
|
||||
def has_permission(self, request, view):
|
||||
return request.user.username == request.parser_context.get('kwargs', {}).get('username', None)
|
||||
|
||||
def _decorator(func):
|
||||
def _decorator(func_or_class):
|
||||
"""
|
||||
Requires either OAuth2 or Session-based authentication.
|
||||
If is_user is True, also requires username in URL matches the request user.
|
||||
"""
|
||||
func.authentication_classes = (OAuth2Authentication, SessionAuthentication)
|
||||
func.permission_classes = (permissions.IsAuthenticated,)
|
||||
func_or_class.authentication_classes = (OAuth2Authentication, SessionAuthentication)
|
||||
func_or_class.permission_classes = (permissions.IsAuthenticated,)
|
||||
if is_user:
|
||||
func.permission_classes += (IsUser,)
|
||||
return func
|
||||
func_or_class.permission_classes += (IsUser,)
|
||||
return func_or_class
|
||||
return _decorator
|
||||
|
||||
|
||||
class MobileView(object):
|
||||
"""
|
||||
Class decorator that abstracts the authentication and permission checks for mobile api views.
|
||||
"""
|
||||
def __init__(self, is_user=False):
|
||||
self.is_user = is_user
|
||||
|
||||
def __call__(self, cls):
|
||||
class _Decorator(cls):
|
||||
"""Inner decorator class to wrap the given class."""
|
||||
mobile_view(self.is_user)(cls)
|
||||
return _Decorator
|
||||
|
||||
@@ -17,11 +17,11 @@ from opaque_keys.edx.locator import BlockUsageLocator
|
||||
from xmodule.exceptions import NotFoundError
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
from ..utils import MobileView, mobile_course_access
|
||||
from ..utils import mobile_view, mobile_course_access
|
||||
from .serializers import BlockOutline, video_summary
|
||||
|
||||
|
||||
@MobileView()
|
||||
@mobile_view()
|
||||
class VideoSummaryList(generics.ListAPIView):
|
||||
"""
|
||||
**Use Case**
|
||||
@@ -89,7 +89,7 @@ class VideoSummaryList(generics.ListAPIView):
|
||||
return Response(video_outline)
|
||||
|
||||
|
||||
@MobileView()
|
||||
@mobile_view()
|
||||
class VideoTranscripts(generics.RetrieveAPIView):
|
||||
"""
|
||||
**Use Case**
|
||||
|
||||
Reference in New Issue
Block a user