180 lines
7.3 KiB
Python
180 lines
7.3 KiB
Python
"""
|
|
Defines the "ReSTful" API for course modes.
|
|
"""
|
|
|
|
|
|
import logging
|
|
|
|
from django.shortcuts import get_object_or_404
|
|
from edx_rest_framework_extensions import permissions
|
|
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
|
|
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser
|
|
from opaque_keys.edx.keys import CourseKey
|
|
from rest_framework import status
|
|
from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView
|
|
from rest_framework.response import Response
|
|
|
|
from common.djangoapps.course_modes.rest_api.serializers import CourseModeSerializer
|
|
from common.djangoapps.course_modes.models import CourseMode
|
|
from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser
|
|
from openedx.core.lib.api.parsers import MergePatchParser
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class CourseModesMixin:
|
|
"""
|
|
A base class for course modes views that specifies authentication, permissions,
|
|
serialization, pagination, and the base queryset.
|
|
"""
|
|
authentication_classes = (
|
|
JwtAuthentication,
|
|
BearerAuthenticationAllowInactiveUser,
|
|
SessionAuthenticationAllowInactiveUser,
|
|
)
|
|
# When not considering JWT conditions, this permission class grants access
|
|
# to any authenticated client that is staff. When consider JWT, the client
|
|
# must be granted access to this resource via their JWT scopes.
|
|
permission_classes = (permissions.JWT_RESTRICTED_APPLICATION_OR_USER_ACCESS,)
|
|
required_scopes = ['course_modes:read']
|
|
serializer_class = CourseModeSerializer
|
|
pagination_class = None
|
|
lookup_field = 'course_id'
|
|
|
|
|
|
class CourseModesView(CourseModesMixin, ListCreateAPIView):
|
|
"""
|
|
View to list or create course modes for a course.
|
|
|
|
**Use Case**
|
|
|
|
List all course modes for a course, or create a new
|
|
course mode.
|
|
|
|
**Example Requests**
|
|
|
|
GET /api/course_modes/v1/courses/{course_id}/
|
|
|
|
Returns a list of all existing course modes for a course.
|
|
|
|
POST /api/course_modes/v1/courses/{course_id}/
|
|
|
|
Creates a new course mode in a course.
|
|
|
|
**Response Values**
|
|
|
|
For each HTTP verb below, an HTTP 404 "Not Found" response is returned if the
|
|
requested course id does not exist.
|
|
|
|
GET: If the request is successful, an HTTP 200 "OK" response is returned
|
|
along with a list of course mode dictionaries within a course.
|
|
The details are contained in a JSON dictionary as follows:
|
|
|
|
* course_id: The course identifier.
|
|
* mode_slug: The short name for the course mode.
|
|
* mode_display_name: The verbose name for the course mode.
|
|
* min_price: The minimum price for which a user can
|
|
enroll in this mode.
|
|
* currency: The currency of the listed prices.
|
|
* expiration_datetime: The date and time after which
|
|
users cannot enroll in the course in this mode (not required for POST).
|
|
* expiration_datetime_is_explicit: Whether the expiration_datetime field was
|
|
explicitly set (not required for POST).
|
|
* description: A description of this mode (not required for POST).
|
|
* sku: The SKU for this mode (for ecommerce purposes, not required for POST).
|
|
* bulk_sku: The bulk SKU for this mode (for ecommerce purposes, not required for POST).
|
|
|
|
POST: If the request is successful, an HTTP 201 "Created" response is returned.
|
|
"""
|
|
def get_queryset(self):
|
|
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
|
|
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
|
|
if 'course_id' in filter_kwargs:
|
|
filter_kwargs['course_id'] = CourseKey.from_string(filter_kwargs['course_id'])
|
|
return CourseMode.objects.filter(**filter_kwargs)
|
|
|
|
|
|
class CourseModesDetailView(CourseModesMixin, RetrieveUpdateDestroyAPIView):
|
|
"""
|
|
View to retrieve, update, or delete a specific course mode for a course.
|
|
|
|
**Use Case**
|
|
|
|
Get or update course mode details for a specific course mode on a course.
|
|
Or you may delete a specific course mode from a course.
|
|
|
|
**Example Requests**
|
|
|
|
GET /api/course_modes/v1/courses/{course_id}/{mode_slug}
|
|
|
|
Returns details on an existing course mode for a course.
|
|
|
|
PATCH /api/course_modes/v1/courses/{course_id}/{mode_slug}
|
|
|
|
Updates (via merge) details of an existing course mode for a course.
|
|
|
|
DELETE /api/course_modes/v1/courses/{course_id}/{mode_slug}
|
|
|
|
Deletes an existing course mode for a course.
|
|
|
|
**Response Values**
|
|
|
|
For each HTTP verb below, an HTTP 404 "Not Found" response is returned if the
|
|
requested course id does not exist, or the mode slug does not exist within the course.
|
|
|
|
GET: If the request is successful, an HTTP 200 "OK" response is returned
|
|
along with a details for a single course mode within a course. The details are contained
|
|
in a JSON dictionary as follows:
|
|
|
|
* course_id: The course identifier.
|
|
* mode_slug: The short name for the course mode.
|
|
* mode_display_name: The verbose name for the course mode.
|
|
* min_price: The minimum price for which a user can
|
|
enroll in this mode.
|
|
* currency: The currency of the listed prices.
|
|
* expiration_datetime: The date and time after which
|
|
users cannot enroll in the course in this mode (not required for PATCH).
|
|
* expiration_datetime_is_explicit: Whether the expiration_datetime field was
|
|
explicitly set (not required for PATCH).
|
|
* description: A description of this mode (not required for PATCH).
|
|
* sku: The SKU for this mode (for ecommerce purposes, not required for PATCH).
|
|
* bulk_sku: The bulk SKU for this mode (for ecommerce purposes, not required for PATCH).
|
|
|
|
PATCH: If the request is successful, an HTTP 204 "No Content" response is returned.
|
|
If "application/merge-patch+json" is not the specified content type,
|
|
a 415 "Unsupported Media Type" response is returned.
|
|
|
|
DELETE: If the request is successful, an HTTP 204 "No Content" response is returned.
|
|
"""
|
|
http_method_names = ['get', 'patch', 'delete', 'head', 'options']
|
|
parser_classes = (MergePatchParser,)
|
|
multiple_lookup_fields = ('course_id', 'mode_slug')
|
|
queryset = CourseMode.objects.all()
|
|
|
|
def get_object(self):
|
|
queryset = self.get_queryset()
|
|
query_filter = {}
|
|
for field in self.multiple_lookup_fields:
|
|
query_filter[field] = self.kwargs[field]
|
|
|
|
if 'course_id' in query_filter:
|
|
query_filter['course_id'] = CourseKey.from_string(query_filter['course_id'])
|
|
|
|
obj = get_object_or_404(queryset, **query_filter)
|
|
self.check_object_permissions(self.request, obj)
|
|
return obj
|
|
|
|
def patch(self, request, *args, **kwargs):
|
|
"""
|
|
Performs a partial update of a CourseMode instance.
|
|
"""
|
|
course_mode = self.get_object()
|
|
serializer = self.serializer_class(course_mode, data=request.data, partial=True)
|
|
|
|
if serializer.is_valid(raise_exception=True):
|
|
serializer.save() # can also raise ValidationError
|
|
return Response(
|
|
status=status.HTTP_204_NO_CONTENT,
|
|
content_type='application/json',
|
|
)
|