Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
a7fabd59
Commit
a7fabd59
authored
May 14, 2015
by
Nimisha Asthagiri
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Move generic mobile API view decorators.
parent
c54952b5
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
93 additions
and
176 deletions
+93
-176
lms/djangoapps/courseware/access.py
+7
-19
lms/djangoapps/mobile_api/course_info/tests.py
+3
-32
lms/djangoapps/mobile_api/course_info/urls.py
+1
-6
lms/djangoapps/mobile_api/course_info/views.py
+0
-30
lms/djangoapps/mobile_api/testutils.py
+1
-8
lms/djangoapps/mobile_api/users/serializers.py
+0
-7
lms/djangoapps/mobile_api/users/tests.py
+4
-4
lms/djangoapps/mobile_api/users/views.py
+0
-1
lms/djangoapps/mobile_api/utils.py
+4
-65
lms/djangoapps/mobile_api/video_outlines/tests.py
+3
-3
openedx/core/lib/api/permissions.py
+0
-1
openedx/core/lib/api/view_utils.py
+70
-0
No files found.
lms/djangoapps/courseware/access.py
View file @
a7fabd59
...
@@ -124,7 +124,6 @@ def _has_access_course_desc(user, action, course):
...
@@ -124,7 +124,6 @@ def _has_access_course_desc(user, action, course):
'load' -- load the courseware, see inside the course
'load' -- load the courseware, see inside the course
'load_forum' -- can load and contribute to the forums (one access level for now)
'load_forum' -- can load and contribute to the forums (one access level for now)
'load_mobile' -- can load from a mobile context
'load_mobile' -- can load from a mobile context
'load_mobile_no_enrollment_check' -- can load from a mobile context without checking for enrollment
'enroll' -- enroll. Checks for enrollment window,
'enroll' -- enroll. Checks for enrollment window,
ACCESS_REQUIRE_STAFF_FOR_COURSE,
ACCESS_REQUIRE_STAFF_FOR_COURSE,
'see_exists' -- can see that the course exists.
'see_exists' -- can see that the course exists.
...
@@ -158,29 +157,19 @@ def _has_access_course_desc(user, action, course):
...
@@ -158,29 +157,19 @@ def _has_access_course_desc(user, action, course):
Can this user access this course from a mobile device?
Can this user access this course from a mobile device?
"""
"""
return
(
return
(
# check mobile requirements
can_load_mobile_no_enroll_check
()
and
# check enrollment
(
CourseEnrollment
.
is_enrolled
(
user
,
course
.
id
)
or
_has_staff_access_to_descriptor
(
user
,
course
,
course
.
id
)
)
)
def
can_load_mobile_no_enroll_check
():
"""
Can this enrolled user access this course from a mobile device?
Note: does not check for enrollment since it is assumed the caller has done so.
"""
return
(
# check start date
# check start date
can_load
()
and
can_load
()
and
# check mobile_available flag
# check mobile_available flag
is_mobile_available_for_user
(
user
,
course
)
and
is_mobile_available_for_user
(
user
,
course
)
and
# check staff access, if not then check for unfulfilled milestones
(
(
# either is a staff user or
_has_staff_access_to_descriptor
(
user
,
course
,
course
.
id
)
or
_has_staff_access_to_descriptor
(
user
,
course
,
course
.
id
)
or
not
any_unfulfilled_milestones
(
course
.
id
,
user
.
id
)
(
# check enrollment
CourseEnrollment
.
is_enrolled
(
user
,
course
.
id
)
and
# check for unfulfilled milestones
not
any_unfulfilled_milestones
(
course
.
id
,
user
.
id
)
)
)
)
)
)
...
@@ -307,7 +296,6 @@ def _has_access_course_desc(user, action, course):
...
@@ -307,7 +296,6 @@ def _has_access_course_desc(user, action, course):
'view_courseware_with_prerequisites'
:
can_view_courseware_with_prerequisites
,
'view_courseware_with_prerequisites'
:
can_view_courseware_with_prerequisites
,
'load_forum'
:
can_load_forum
,
'load_forum'
:
can_load_forum
,
'load_mobile'
:
can_load_mobile
,
'load_mobile'
:
can_load_mobile
,
'load_mobile_no_enrollment_check'
:
can_load_mobile_no_enroll_check
,
'enroll'
:
can_enroll
,
'enroll'
:
can_enroll
,
'see_exists'
:
see_exists
,
'see_exists'
:
see_exists
,
'staff'
:
lambda
:
_has_staff_access_to_descriptor
(
user
,
course
,
course
.
id
),
'staff'
:
lambda
:
_has_staff_access_to_descriptor
(
user
,
course
,
course
.
id
),
...
...
lms/djangoapps/mobile_api/course_info/tests.py
View file @
a7fabd59
...
@@ -11,41 +11,12 @@ from xmodule.modulestore.django import modulestore
...
@@ -11,41 +11,12 @@ from xmodule.modulestore.django import modulestore
from
xmodule.modulestore.xml_importer
import
import_course_from_xml
from
xmodule.modulestore.xml_importer
import
import_course_from_xml
from
..testutils
import
(
from
..testutils
import
(
MobileAPITestCase
,
MobileCourseAccessTestMixin
,
Mobile
EnrolledCourseAccessTestMixin
,
Mobile
AuthTestMixin
MobileAPITestCase
,
MobileCourseAccessTestMixin
,
MobileAuthTestMixin
)
)
class
TestAbout
(
MobileAPITestCase
,
MobileAuthTestMixin
,
MobileCourseAccessTestMixin
):
"""
Tests for /api/mobile/v0.5/course_info/{course_id}/about
"""
REVERSE_INFO
=
{
'name'
:
'course-about-detail'
,
'params'
:
[
'course_id'
]}
def
verify_success
(
self
,
response
):
super
(
TestAbout
,
self
)
.
verify_success
(
response
)
self
.
assertTrue
(
'overview'
in
response
.
data
)
def
init_course_access
(
self
,
course_id
=
None
):
# override this method since enrollment is not required for the About endpoint.
self
.
login
()
def
test_about_static_rewrite
(
self
):
self
.
login
()
about_usage_key
=
self
.
course
.
id
.
make_usage_key
(
'about'
,
'overview'
)
about_module
=
modulestore
()
.
get_item
(
about_usage_key
)
underlying_about_html
=
about_module
.
data
# check that we start with relative static assets
self
.
assertIn
(
'
\"
/static/'
,
underlying_about_html
)
# but shouldn't finish with any
response
=
self
.
api_response
()
self
.
assertNotIn
(
'
\"
/static/'
,
response
.
data
[
'overview'
])
@ddt.ddt
@ddt.ddt
class
TestUpdates
(
MobileAPITestCase
,
MobileAuthTestMixin
,
Mobile
Enrolled
CourseAccessTestMixin
):
class
TestUpdates
(
MobileAPITestCase
,
MobileAuthTestMixin
,
MobileCourseAccessTestMixin
):
"""
"""
Tests for /api/mobile/v0.5/course_info/{course_id}/updates
Tests for /api/mobile/v0.5/course_info/{course_id}/updates
"""
"""
...
@@ -111,7 +82,7 @@ class TestUpdates(MobileAPITestCase, MobileAuthTestMixin, MobileEnrolledCourseAc
...
@@ -111,7 +82,7 @@ class TestUpdates(MobileAPITestCase, MobileAuthTestMixin, MobileEnrolledCourseAc
self
.
assertIn
(
"Update"
+
str
(
num
),
update_data
[
'content'
])
self
.
assertIn
(
"Update"
+
str
(
num
),
update_data
[
'content'
])
class
TestHandouts
(
MobileAPITestCase
,
MobileAuthTestMixin
,
Mobile
Enrolled
CourseAccessTestMixin
):
class
TestHandouts
(
MobileAPITestCase
,
MobileAuthTestMixin
,
MobileCourseAccessTestMixin
):
"""
"""
Tests for /api/mobile/v0.5/course_info/{course_id}/handouts
Tests for /api/mobile/v0.5/course_info/{course_id}/handouts
"""
"""
...
...
lms/djangoapps/mobile_api/course_info/urls.py
View file @
a7fabd59
...
@@ -4,16 +4,11 @@ URLs for course_info API
...
@@ -4,16 +4,11 @@ URLs for course_info API
from
django.conf.urls
import
patterns
,
url
from
django.conf.urls
import
patterns
,
url
from
django.conf
import
settings
from
django.conf
import
settings
from
.views
import
Course
AboutDetail
,
Course
UpdatesList
,
CourseHandoutsList
from
.views
import
CourseUpdatesList
,
CourseHandoutsList
urlpatterns
=
patterns
(
urlpatterns
=
patterns
(
'mobile_api.course_info.views'
,
'mobile_api.course_info.views'
,
url
(
url
(
r'^{}/about$'
.
format
(
settings
.
COURSE_ID_PATTERN
),
CourseAboutDetail
.
as_view
(),
name
=
'course-about-detail'
),
url
(
r'^{}/handouts$'
.
format
(
settings
.
COURSE_ID_PATTERN
),
r'^{}/handouts$'
.
format
(
settings
.
COURSE_ID_PATTERN
),
CourseHandoutsList
.
as_view
(),
CourseHandoutsList
.
as_view
(),
name
=
'course-handouts-list'
name
=
'course-handouts-list'
...
...
lms/djangoapps/mobile_api/course_info/views.py
View file @
a7fabd59
...
@@ -87,33 +87,3 @@ class CourseHandoutsList(generics.ListAPIView):
...
@@ -87,33 +87,3 @@ class CourseHandoutsList(generics.ListAPIView):
else
:
else
:
# course_handouts_module could be None if there are no handouts
# course_handouts_module could be None if there are no handouts
raise
Http404
(
u"No handouts for {}"
.
format
(
unicode
(
course
.
id
)))
raise
Http404
(
u"No handouts for {}"
.
format
(
unicode
(
course
.
id
)))
@mobile_view
()
class
CourseAboutDetail
(
generics
.
RetrieveAPIView
):
"""
**Use Case**
Get the HTML for the course about page.
**Example request**:
GET /api/mobile/v0.5/course_info/{organization}/{course_number}/{course_run}/about
**Response Values**
* overview: The HTML for the course About page.
"""
@mobile_course_access
(
verify_enrolled
=
False
)
def
get
(
self
,
request
,
course
,
*
args
,
**
kwargs
):
# There are other fields, but they don't seem to be in use.
# see courses.py:get_course_about_section.
#
# This can also return None, so check for that before calling strip()
about_section_html
=
get_course_about_section
(
course
,
"overview"
)
about_section_html
=
make_static_urls_absolute
(
self
.
request
,
about_section_html
)
return
Response
(
{
"overview"
:
about_section_html
.
strip
()
if
about_section_html
else
""
}
)
lms/djangoapps/mobile_api/testutils.py
View file @
a7fabd59
...
@@ -7,8 +7,7 @@ Test utilities for mobile API tests:
...
@@ -7,8 +7,7 @@ Test utilities for mobile API tests:
Test Mixins to be included by concrete test classes and provide implementation of common test methods:
Test Mixins to be included by concrete test classes and provide implementation of common test methods:
MobileAuthTestMixin - tests for APIs with mobile_view and is_user=False.
MobileAuthTestMixin - tests for APIs with mobile_view and is_user=False.
MobileAuthUserTestMixin - tests for APIs with mobile_view and is_user=True.
MobileAuthUserTestMixin - tests for APIs with mobile_view and is_user=True.
MobileCourseAccessTestMixin - tests for APIs with mobile_course_access and verify_enrolled=False.
MobileCourseAccessTestMixin - tests for APIs with mobile_course_access.
MobileEnrolledCourseAccessTestMixin - tests for APIs with mobile_course_access and verify_enrolled=True.
"""
"""
# pylint: disable=no-member
# pylint: disable=no-member
import
ddt
import
ddt
...
@@ -130,7 +129,6 @@ class MobileAuthUserTestMixin(MobileAuthTestMixin):
...
@@ -130,7 +129,6 @@ class MobileAuthUserTestMixin(MobileAuthTestMixin):
class
MobileCourseAccessTestMixin
(
MobileAPIMilestonesMixin
):
class
MobileCourseAccessTestMixin
(
MobileAPIMilestonesMixin
):
"""
"""
Test Mixin for testing APIs marked with mobile_course_access.
Test Mixin for testing APIs marked with mobile_course_access.
(Use MobileEnrolledCourseAccessTestMixin when verify_enrolled is set to True.)
Subclasses are expected to inherit from MobileAPITestCase.
Subclasses are expected to inherit from MobileAPITestCase.
Subclasses can override verify_success, verify_failure, and init_course_access methods.
Subclasses can override verify_success, verify_failure, and init_course_access methods.
"""
"""
...
@@ -197,11 +195,6 @@ class MobileCourseAccessTestMixin(MobileAPIMilestonesMixin):
...
@@ -197,11 +195,6 @@ class MobileCourseAccessTestMixin(MobileAPIMilestonesMixin):
else
:
else
:
self
.
verify_failure
(
response
)
self
.
verify_failure
(
response
)
class
MobileEnrolledCourseAccessTestMixin
(
MobileCourseAccessTestMixin
):
"""
Test Mixin for testing APIs marked with mobile_course_access with verify_enrolled=True.
"""
def
test_unenrolled_user
(
self
):
def
test_unenrolled_user
(
self
):
self
.
login
()
self
.
login
()
self
.
unenroll
()
self
.
unenroll
()
...
...
lms/djangoapps/mobile_api/users/serializers.py
View file @
a7fabd59
...
@@ -31,16 +31,10 @@ class CourseField(serializers.RelatedField):
...
@@ -31,16 +31,10 @@ class CourseField(serializers.RelatedField):
kwargs
=
{
'course_id'
:
course_id
},
kwargs
=
{
'course_id'
:
course_id
},
request
=
request
request
=
request
)
)
course_about_url
=
reverse
(
'course-about-detail'
,
kwargs
=
{
'course_id'
:
course_id
},
request
=
request
)
else
:
else
:
video_outline_url
=
None
video_outline_url
=
None
course_updates_url
=
None
course_updates_url
=
None
course_handouts_url
=
None
course_handouts_url
=
None
course_about_url
=
None
return
{
return
{
"id"
:
course_id
,
"id"
:
course_id
,
...
@@ -59,7 +53,6 @@ class CourseField(serializers.RelatedField):
...
@@ -59,7 +53,6 @@ class CourseField(serializers.RelatedField):
"video_outline"
:
video_outline_url
,
"video_outline"
:
video_outline_url
,
"course_updates"
:
course_updates_url
,
"course_updates"
:
course_updates_url
,
"course_handouts"
:
course_handouts_url
,
"course_handouts"
:
course_handouts_url
,
"course_about"
:
course_about_url
,
"subscription_id"
:
course
.
clean_id
(
padding_char
=
'_'
),
"subscription_id"
:
course
.
clean_id
(
padding_char
=
'_'
),
}
}
...
...
lms/djangoapps/mobile_api/users/tests.py
View file @
a7fabd59
...
@@ -10,7 +10,7 @@ from certificates.models import CertificateStatuses
...
@@ -10,7 +10,7 @@ from certificates.models import CertificateStatuses
from
certificates.tests.factories
import
GeneratedCertificateFactory
from
certificates.tests.factories
import
GeneratedCertificateFactory
from
..
import
errors
from
..
import
errors
from
..testutils
import
MobileAPITestCase
,
MobileAuthTestMixin
,
MobileAuthUserTestMixin
,
Mobile
Enrolled
CourseAccessTestMixin
from
..testutils
import
MobileAPITestCase
,
MobileAuthTestMixin
,
MobileAuthUserTestMixin
,
MobileCourseAccessTestMixin
from
.serializers
import
CourseEnrollmentSerializer
from
.serializers
import
CourseEnrollmentSerializer
...
@@ -43,7 +43,7 @@ class TestUserInfoApi(MobileAPITestCase, MobileAuthTestMixin):
...
@@ -43,7 +43,7 @@ class TestUserInfoApi(MobileAPITestCase, MobileAuthTestMixin):
self
.
assertTrue
(
self
.
username
in
response
[
'location'
])
self
.
assertTrue
(
self
.
username
in
response
[
'location'
])
class
TestUserEnrollmentApi
(
MobileAPITestCase
,
MobileAuthUserTestMixin
,
Mobile
Enrolled
CourseAccessTestMixin
):
class
TestUserEnrollmentApi
(
MobileAPITestCase
,
MobileAuthUserTestMixin
,
MobileCourseAccessTestMixin
):
"""
"""
Tests for /api/mobile/v0.5/users/<user_name>/course_enrollments/
Tests for /api/mobile/v0.5/users/<user_name>/course_enrollments/
"""
"""
...
@@ -160,7 +160,7 @@ class CourseStatusAPITestCase(MobileAPITestCase):
...
@@ -160,7 +160,7 @@ class CourseStatusAPITestCase(MobileAPITestCase):
)
)
class
TestCourseStatusGET
(
CourseStatusAPITestCase
,
MobileAuthUserTestMixin
,
Mobile
Enrolled
CourseAccessTestMixin
):
class
TestCourseStatusGET
(
CourseStatusAPITestCase
,
MobileAuthUserTestMixin
,
MobileCourseAccessTestMixin
):
"""
"""
Tests for GET of /api/mobile/v0.5/users/<user_name>/course_status_info/{course_id}
Tests for GET of /api/mobile/v0.5/users/<user_name>/course_status_info/{course_id}
"""
"""
...
@@ -178,7 +178,7 @@ class TestCourseStatusGET(CourseStatusAPITestCase, MobileAuthUserTestMixin, Mobi
...
@@ -178,7 +178,7 @@ class TestCourseStatusGET(CourseStatusAPITestCase, MobileAuthUserTestMixin, Mobi
)
)
class
TestCourseStatusPATCH
(
CourseStatusAPITestCase
,
MobileAuthUserTestMixin
,
Mobile
Enrolled
CourseAccessTestMixin
):
class
TestCourseStatusPATCH
(
CourseStatusAPITestCase
,
MobileAuthUserTestMixin
,
MobileCourseAccessTestMixin
):
"""
"""
Tests for PATCH of /api/mobile/v0.5/users/<user_name>/course_status_info/{course_id}
Tests for PATCH of /api/mobile/v0.5/users/<user_name>/course_status_info/{course_id}
"""
"""
...
...
lms/djangoapps/mobile_api/users/views.py
View file @
a7fabd59
...
@@ -212,7 +212,6 @@ class UserCourseEnrollmentsList(generics.ListAPIView):
...
@@ -212,7 +212,6 @@ class UserCourseEnrollmentsList(generics.ListAPIView):
* url: URL to the downloadable version of the certificate, if exists.
* url: URL to the downloadable version of the certificate, if exists.
* course: A collection of data about the course:
* course: A collection of data about the course:
* course_about: The URI to get the data for the course About page.
* course_updates: The URI to get data for course updates.
* course_updates: The URI to get data for course updates.
* number: The course number.
* number: The course number.
* org: The organization that created the course.
* org: The organization that created the course.
...
...
lms/djangoapps/mobile_api/utils.py
View file @
a7fabd59
"""
"""
Common utility methods and decorators for Mobile APIs.
Common utility methods and decorators for Mobile APIs.
"""
"""
from
openedx.core.lib.api.view_utils
import
view_course_access
,
view_auth_classes
import
functools
from
django.http
import
Http404
def
mobile_course_access
(
depth
=
0
):
from
rest_framework
import
permissions
,
status
,
response
from
opaque_keys.edx.keys
import
CourseKey
from
courseware.courses
import
get_course_with_access
from
openedx.core.lib.api.permissions
import
IsUserInUrl
from
openedx.core.lib.api.authentication
import
(
SessionAuthenticationAllowInactiveUser
,
OAuth2AuthenticationAllowInactiveUser
,
)
from
util.milestones_helpers
import
any_unfulfilled_milestones
from
xmodule.modulestore.django
import
modulestore
def
mobile_course_access
(
depth
=
0
,
verify_enrolled
=
True
):
"""
"""
Method decorator for a mobile API endpoint that verifies the user has access to the course in a mobile context.
Method decorator for a mobile API endpoint that verifies the user has access to the course in a mobile context.
"""
"""
def
_decorator
(
func
):
return
view_course_access
(
depth
=
depth
,
access_action
=
'load_mobile'
,
check_for_milestones
=
True
)
"""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'
if
verify_enrolled
else
'load_mobile_no_enrollment_check'
,
course_id
,
depth
=
depth
)
except
Http404
:
# any_unfulfilled_milestones called a second time since get_course_with_access returns a bool
if
any_unfulfilled_milestones
(
course_id
,
request
.
user
.
id
):
message
=
{
"developer_message"
:
"Cannot access content with unfulfilled pre-requisites or unpassed entrance exam."
# pylint: disable=line-too-long
}
return
response
.
Response
(
data
=
message
,
status
=
status
.
HTTP_204_NO_CONTENT
)
else
:
raise
return
func
(
self
,
request
,
course
=
course
,
*
args
,
**
kwargs
)
return
_wrapper
return
_decorator
def
mobile_view
(
is_user
=
False
):
def
mobile_view
(
is_user
=
False
):
"""
"""
Function and class 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.
"""
"""
def
_decorator
(
func_or_class
):
return
view_auth_classes
(
is_user
)
"""
Requires either OAuth2 or Session-based authentication.
If is_user is True, also requires username in URL matches the request user.
"""
func_or_class
.
authentication_classes
=
(
OAuth2AuthenticationAllowInactiveUser
,
SessionAuthenticationAllowInactiveUser
)
func_or_class
.
permission_classes
=
(
permissions
.
IsAuthenticated
,)
if
is_user
:
func_or_class
.
permission_classes
+=
(
IsUserInUrl
,)
return
func_or_class
return
_decorator
lms/djangoapps/mobile_api/video_outlines/tests.py
View file @
a7fabd59
...
@@ -18,7 +18,7 @@ from xmodule.partitions.partitions import Group, UserPartition
...
@@ -18,7 +18,7 @@ from xmodule.partitions.partitions import Group, UserPartition
from
openedx.core.djangoapps.course_groups.tests.helpers
import
CohortFactory
from
openedx.core.djangoapps.course_groups.tests.helpers
import
CohortFactory
from
openedx.core.djangoapps.course_groups.models
import
CourseUserGroupPartitionGroup
from
openedx.core.djangoapps.course_groups.models
import
CourseUserGroupPartitionGroup
from
..testutils
import
MobileAPITestCase
,
MobileAuthTestMixin
,
Mobile
Enrolled
CourseAccessTestMixin
from
..testutils
import
MobileAPITestCase
,
MobileAuthTestMixin
,
MobileCourseAccessTestMixin
class
TestVideoAPITestCase
(
MobileAPITestCase
):
class
TestVideoAPITestCase
(
MobileAPITestCase
):
...
@@ -407,7 +407,7 @@ class TestNonStandardCourseStructure(MobileAPITestCase, TestVideoAPIMixin):
...
@@ -407,7 +407,7 @@ class TestNonStandardCourseStructure(MobileAPITestCase, TestVideoAPIMixin):
@ddt.ddt
@ddt.ddt
class
TestVideoSummaryList
(
class
TestVideoSummaryList
(
TestVideoAPITestCase
,
MobileAuthTestMixin
,
Mobile
Enrolled
CourseAccessTestMixin
,
TestVideoAPIMixin
# pylint: disable=bad-continuation
TestVideoAPITestCase
,
MobileAuthTestMixin
,
MobileCourseAccessTestMixin
,
TestVideoAPIMixin
# pylint: disable=bad-continuation
):
):
"""
"""
Tests for /api/mobile/v0.5/video_outlines/courses/{course_id}..
Tests for /api/mobile/v0.5/video_outlines/courses/{course_id}..
...
@@ -863,7 +863,7 @@ class TestVideoSummaryList(
...
@@ -863,7 +863,7 @@ class TestVideoSummaryList(
class
TestTranscriptsDetail
(
class
TestTranscriptsDetail
(
TestVideoAPITestCase
,
MobileAuthTestMixin
,
Mobile
Enrolled
CourseAccessTestMixin
,
TestVideoAPIMixin
# pylint: disable=bad-continuation
TestVideoAPITestCase
,
MobileAuthTestMixin
,
MobileCourseAccessTestMixin
,
TestVideoAPIMixin
# pylint: disable=bad-continuation
):
):
"""
"""
Tests for /api/mobile/v0.5/video_outlines/transcripts/{course_id}..
Tests for /api/mobile/v0.5/video_outlines/transcripts/{course_id}..
...
...
openedx/core/lib/api/permissions.py
View file @
a7fabd59
from
django.conf
import
settings
from
django.conf
import
settings
from
rest_framework
import
permissions
from
rest_framework
import
permissions
from
rest_framework.exceptions
import
PermissionDenied
from
django.http
import
Http404
from
django.http
import
Http404
...
...
openedx/core/lib/api/view_utils.py
View file @
a7fabd59
"""
"""
Utilities related to API views
Utilities related to API views
"""
"""
import
functools
from
django.core.exceptions
import
NON_FIELD_ERRORS
,
ValidationError
from
django.core.exceptions
import
NON_FIELD_ERRORS
,
ValidationError
from
django.http
import
Http404
from
django.http
import
Http404
from
rest_framework
import
status
,
response
from
rest_framework.exceptions
import
APIException
from
rest_framework.exceptions
import
APIException
from
rest_framework.response
import
Response
from
rest_framework.response
import
Response
from
lms.djangoapps.courseware.courses
import
get_course_with_access
from
opaque_keys.edx.keys
import
CourseKey
from
xmodule.modulestore.django
import
modulestore
from
openedx.core.lib.api.authentication
import
(
SessionAuthenticationAllowInactiveUser
,
OAuth2AuthenticationAllowInactiveUser
,
)
from
openedx.core.lib.api.permissions
import
IsUserInUrl
,
IsAuthenticatedOrDebug
from
util.milestones_helpers
import
any_unfulfilled_milestones
class
DeveloperErrorViewMixin
(
object
):
class
DeveloperErrorViewMixin
(
object
):
"""
"""
...
@@ -48,3 +61,60 @@ class DeveloperErrorViewMixin(object):
...
@@ -48,3 +61,60 @@ class DeveloperErrorViewMixin(object):
return
self
.
make_validation_error_response
(
exc
)
return
self
.
make_validation_error_response
(
exc
)
else
:
else
:
raise
raise
def
view_course_access
(
depth
=
0
,
access_action
=
'load'
,
check_for_milestones
=
False
):
"""
Method decorator for an API endpoint that verifies the user has access to the course.
"""
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
,
access_action
,
course_id
,
depth
=
depth
)
except
Http404
:
# any_unfulfilled_milestones called a second time since has_access returns a bool
if
check_for_milestones
and
any_unfulfilled_milestones
(
course_id
,
request
.
user
.
id
):
message
=
{
"developer_message"
:
"Cannot access content with unfulfilled "
"pre-requisites or unpassed entrance exam."
}
return
response
.
Response
(
data
=
message
,
status
=
status
.
HTTP_204_NO_CONTENT
)
else
:
raise
return
func
(
self
,
request
,
course
=
course
,
*
args
,
**
kwargs
)
return
_wrapper
return
_decorator
def
view_auth_classes
(
is_user
=
False
):
"""
Function and class decorator that abstracts the authentication and permission checks for api views.
"""
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_or_class
.
authentication_classes
=
(
OAuth2AuthenticationAllowInactiveUser
,
SessionAuthenticationAllowInactiveUser
)
func_or_class
.
permission_classes
=
(
IsAuthenticatedOrDebug
,)
if
is_user
:
func_or_class
.
permission_classes
+=
(
IsUserInUrl
,)
return
func_or_class
return
_decorator
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment