Files
edx-platform/common/djangoapps/course_modes/rest_api/v1/views.py
2021-03-18 11:19:46 +05:00

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',
)