From 9566a46a4d7f8126698c2a61f99a462007a73fa6 Mon Sep 17 00:00:00 2001 From: Nimisha Asthagiri Date: Fri, 9 Jan 2015 22:30:58 -0500 Subject: [PATCH] Fix Mobile API decorators. --- .../mobile_api/course_info/views.py | 8 +++--- lms/djangoapps/mobile_api/tests.py | 26 ++++++++++++++++--- lms/djangoapps/mobile_api/testutils.py | 8 +++--- lms/djangoapps/mobile_api/users/views.py | 8 +++--- lms/djangoapps/mobile_api/utils.py | 26 +++++-------------- .../mobile_api/video_outlines/views.py | 6 ++--- 6 files changed, 44 insertions(+), 38 deletions(-) diff --git a/lms/djangoapps/mobile_api/course_info/views.py b/lms/djangoapps/mobile_api/course_info/views.py index 76defffe71..b0674b7be3 100644 --- a/lms/djangoapps/mobile_api/course_info/views.py +++ b/lms/djangoapps/mobile_api/course_info/views.py @@ -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** diff --git a/lms/djangoapps/mobile_api/tests.py b/lms/djangoapps/mobile_api/tests.py index 8065be07be..9c02de1b63 100644 --- a/lms/djangoapps/mobile_api/tests.py +++ b/lms/djangoapps/mobile_api/tests.py @@ -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")) diff --git a/lms/djangoapps/mobile_api/testutils.py b/lms/djangoapps/mobile_api/testutils.py index c4dbaa01ee..3c63308451 100644 --- a/lms/djangoapps/mobile_api/testutils.py +++ b/lms/djangoapps/mobile_api/testutils.py @@ -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() diff --git a/lms/djangoapps/mobile_api/users/views.py b/lms/djangoapps/mobile_api/users/views.py index 22f59d4220..9ac03b9b5e 100644 --- a/lms/djangoapps/mobile_api/users/views.py +++ b/lms/djangoapps/mobile_api/users/views.py @@ -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** diff --git a/lms/djangoapps/mobile_api/utils.py b/lms/djangoapps/mobile_api/utils.py index ccd81687e8..8e8aa56fb4 100644 --- a/lms/djangoapps/mobile_api/utils.py +++ b/lms/djangoapps/mobile_api/utils.py @@ -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 diff --git a/lms/djangoapps/mobile_api/video_outlines/views.py b/lms/djangoapps/mobile_api/video_outlines/views.py index 4876b8da4d..54f65e4a6e 100644 --- a/lms/djangoapps/mobile_api/video_outlines/views.py +++ b/lms/djangoapps/mobile_api/video_outlines/views.py @@ -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**