From 97675ae40445fb5a61c007ed9e3318c58682e359 Mon Sep 17 00:00:00 2001 From: Deborah Kaplan Date: Wed, 12 Mar 2025 12:44:14 -0400 Subject: [PATCH] chore: pre-fix linting (#36368) * chore: pre-fix linting Before making some bug fixes in this area, reformatting these files to modern standards. --- .../djangoapps/enrollments/serializers.py | 40 +- openedx/core/djangoapps/enrollments/urls.py | 32 +- openedx/core/djangoapps/enrollments/views.py | 830 +++++++++--------- 3 files changed, 446 insertions(+), 456 deletions(-) diff --git a/openedx/core/djangoapps/enrollments/serializers.py b/openedx/core/djangoapps/enrollments/serializers.py index 44a1a56194..9b64cc95ca 100644 --- a/openedx/core/djangoapps/enrollments/serializers.py +++ b/openedx/core/djangoapps/enrollments/serializers.py @@ -2,14 +2,12 @@ Serializers for all Course Enrollment related return objects. """ - import logging from rest_framework import serializers from common.djangoapps.course_modes.models import CourseMode -from common.djangoapps.student.models import (CourseEnrollment, - CourseEnrollmentAllowed) +from common.djangoapps.student.models import CourseEnrollment, CourseEnrollmentAllowed log = logging.getLogger(__name__) @@ -21,6 +19,7 @@ class StringListField(serializers.CharField): [1,2,3] """ + def field_to_native(self, obj, field_name): # pylint: disable=unused-argument """ Serialize the object's class name. @@ -28,7 +27,7 @@ class StringListField(serializers.CharField): if not obj.suggested_prices: return [] - items = obj.suggested_prices.split(',') + items = obj.suggested_prices.split(",") return [int(item) for item in items] @@ -49,7 +48,7 @@ class CourseSerializer(serializers.Serializer): # pylint: disable=abstract-meth class Meta: # For disambiguating within the drf-yasg swagger schema - ref_name = 'enrollment.Course' + ref_name = "enrollment.Course" def __init__(self, *args, **kwargs): self.include_expired = kwargs.pop("include_expired", False) @@ -59,15 +58,8 @@ class CourseSerializer(serializers.Serializer): # pylint: disable=abstract-meth """ Retrieve course modes associated with the course. """ - course_modes = CourseMode.modes_for_course( - obj.id, - include_expired=self.include_expired, - only_selectable=False - ) - return [ - ModeSerializer(mode).data - for mode in course_modes - ] + course_modes = CourseMode.modes_for_course(obj.id, include_expired=self.include_expired, only_selectable=False) + return [ModeSerializer(mode).data for mode in course_modes] def get_pacing_type(self, obj): """ @@ -83,8 +75,9 @@ class CourseEnrollmentSerializer(serializers.ModelSerializer): the Course block and course modes, to give a complete representation of course enrollment. """ + course_details = CourseSerializer(source="course_overview") - user = serializers.SerializerMethodField('get_username') + user = serializers.SerializerMethodField("get_username") def get_username(self, model): """Retrieves the username from the associated model.""" @@ -92,8 +85,8 @@ class CourseEnrollmentSerializer(serializers.ModelSerializer): class Meta: model = CourseEnrollment - fields = ('created', 'mode', 'is_active', 'course_details', 'user') - lookup_field = 'username' + fields = ("created", "mode", "is_active", "course_details", "user") + lookup_field = "username" class CourseEnrollmentsApiListSerializer(CourseEnrollmentSerializer): @@ -101,14 +94,15 @@ class CourseEnrollmentsApiListSerializer(CourseEnrollmentSerializer): Serializes CourseEnrollment model and returns a subset of fields returned by the CourseEnrollmentSerializer. """ - course_id = serializers.CharField(source='course_overview.id') + + course_id = serializers.CharField(source="course_overview.id") def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields.pop('course_details') + self.fields.pop("course_details") class Meta(CourseEnrollmentSerializer.Meta): - fields = CourseEnrollmentSerializer.Meta.fields + ('course_id', ) + fields = CourseEnrollmentSerializer.Meta.fields + ("course_id",) class ModeSerializer(serializers.Serializer): # pylint: disable=abstract-method @@ -119,6 +113,7 @@ class ModeSerializer(serializers.Serializer): # pylint: disable=abstract-method does not handle the model object itself, but the tuple. """ + slug = serializers.CharField(max_length=100) name = serializers.CharField(max_length=255) min_price = serializers.IntegerField() @@ -137,7 +132,8 @@ class CourseEnrollmentAllowedSerializer(serializers.ModelSerializer): Aggregates all data from the CourseEnrollmentAllowed table, and pulls in the serialization to give a complete representation of course enrollment allowed. """ + class Meta: model = CourseEnrollmentAllowed - exclude = ['id'] - lookup_field = 'user' + exclude = ["id"] + lookup_field = "user" diff --git a/openedx/core/djangoapps/enrollments/urls.py b/openedx/core/djangoapps/enrollments/urls.py index 39a11a9893..828d4b6179 100644 --- a/openedx/core/djangoapps/enrollments/urls.py +++ b/openedx/core/djangoapps/enrollments/urls.py @@ -3,7 +3,6 @@ URLs for the Enrollment API """ - from django.conf import settings from django.urls import path, re_path @@ -14,21 +13,24 @@ from .views import ( EnrollmentListView, EnrollmentUserRolesView, EnrollmentView, - UnenrollmentView + UnenrollmentView, ) urlpatterns = [ - re_path(r'^enrollment/{username},{course_key}$'.format( - username=settings.USERNAME_PATTERN, - course_key=settings.COURSE_ID_PATTERN), - EnrollmentView.as_view(), name='courseenrollment'), - re_path(fr'^enrollment/{settings.COURSE_ID_PATTERN}$', - EnrollmentView.as_view(), name='courseenrollment'), - path('enrollment', EnrollmentListView.as_view(), name='courseenrollments'), - re_path(r'^enrollments/?$', CourseEnrollmentsApiListView.as_view(), name='courseenrollmentsapilist'), - re_path(fr'^course/{settings.COURSE_ID_PATTERN}$', - EnrollmentCourseDetailView.as_view(), name='courseenrollmentdetails'), - path('unenroll/', UnenrollmentView.as_view(), name='unenrollment'), - path('roles/', EnrollmentUserRolesView.as_view(), name='roles'), - path('enrollment_allowed/', EnrollmentAllowedView.as_view(), name='courseenrollmentallowed'), + re_path( + r"^enrollment/{username},{course_key}$".format( + username=settings.USERNAME_PATTERN, course_key=settings.COURSE_ID_PATTERN + ), + EnrollmentView.as_view(), + name="courseenrollment", + ), + re_path(rf"^enrollment/{settings.COURSE_ID_PATTERN}$", EnrollmentView.as_view(), name="courseenrollment"), + path("enrollment", EnrollmentListView.as_view(), name="courseenrollments"), + re_path(r"^enrollments/?$", CourseEnrollmentsApiListView.as_view(), name="courseenrollmentsapilist"), + re_path( + rf"^course/{settings.COURSE_ID_PATTERN}$", EnrollmentCourseDetailView.as_view(), name="courseenrollmentdetails" + ), + path("unenroll/", UnenrollmentView.as_view(), name="unenrollment"), + path("roles/", EnrollmentUserRolesView.as_view(), name="roles"), + path("enrollment_allowed/", EnrollmentAllowedView.as_view(), name="courseenrollmentallowed"), ] diff --git a/openedx/core/djangoapps/enrollments/views.py b/openedx/core/djangoapps/enrollments/views.py index 52ec4e3b31..620575a506 100644 --- a/openedx/core/djangoapps/enrollments/views.py +++ b/openedx/core/djangoapps/enrollments/views.py @@ -4,19 +4,20 @@ consist primarily of authentication, request validation, and serialization. """ - import logging from django.core.exceptions import ( # lint-amnesty, pylint: disable=wrong-import-order ObjectDoesNotExist, - ValidationError + ValidationError, ) -from django.db import IntegrityError # lint-amnesty, pylint: disable=wrong-import-order +from django.db import IntegrityError # lint-amnesty, pylint: disable=wrong-import-order from django.utils.decorators import method_decorator # lint-amnesty, pylint: disable=wrong-import-order -from edx_rest_framework_extensions.auth.jwt.authentication import \ - JwtAuthentication # lint-amnesty, pylint: disable=wrong-import-order -from edx_rest_framework_extensions.auth.session.authentication import \ - SessionAuthenticationAllowInactiveUser # lint-amnesty, pylint: disable=wrong-import-order +from edx_rest_framework_extensions.auth.jwt.authentication import ( + JwtAuthentication, +) # lint-amnesty, pylint: disable=wrong-import-order +from edx_rest_framework_extensions.auth.session.authentication import ( + SessionAuthenticationAllowInactiveUser, +) # lint-amnesty, pylint: disable=wrong-import-order from opaque_keys import InvalidKeyError # lint-amnesty, pylint: disable=wrong-import-order from opaque_keys.edx.keys import CourseKey # lint-amnesty, pylint: disable=wrong-import-order from rest_framework import permissions, status # lint-amnesty, pylint: disable=wrong-import-order @@ -38,13 +39,13 @@ from openedx.core.djangoapps.enrollments import api from openedx.core.djangoapps.enrollments.errors import ( CourseEnrollmentError, CourseEnrollmentExistsError, - CourseModeNotFoundError + CourseModeNotFoundError, ) from openedx.core.djangoapps.enrollments.forms import CourseEnrollmentsApiListForm from openedx.core.djangoapps.enrollments.paginators import CourseEnrollmentsApiListPagination from openedx.core.djangoapps.enrollments.serializers import ( CourseEnrollmentAllowedSerializer, - CourseEnrollmentsApiListSerializer + CourseEnrollmentsApiListSerializer, ) from openedx.core.djangoapps.user_api.accounts.permissions import CanRetireUser from openedx.core.djangoapps.user_api.models import UserRetirementStatus @@ -58,7 +59,7 @@ from openedx.features.enterprise_support.api import ( ConsentApiServiceClient, EnterpriseApiException, EnterpriseApiServiceClient, - enterprise_enabled + enterprise_enabled, ) log = logging.getLogger(__name__) @@ -68,7 +69,8 @@ REQUIRED_ATTRIBUTES = { class EnrollmentCrossDomainSessionAuth(SessionAuthenticationAllowInactiveUser, SessionAuthenticationCrossDomainCsrf): - """Session authentication that allows inactive users and cross-domain requests. """ + """Session authentication that allows inactive users and cross-domain requests.""" + pass # lint-amnesty, pylint: disable=unnecessary-pass @@ -97,15 +99,15 @@ class EnrollmentUserThrottle(UserRateThrottle, ApiKeyPermissionMixIn): # To see how the staff rate limit was selected, see https://github.com/openedx/edx-platform/pull/18360 THROTTLE_RATES = { - 'user': '40/minute', - 'staff': '120/minute', + "user": "40/minute", + "staff": "120/minute", } def allow_request(self, request, view): # Use a special scope for staff to allow for a separate throttle rate user = request.user if user.is_authenticated and (user.is_staff or user.is_superuser): - self.scope = 'staff' + self.scope = "staff" self.rate = self.get_rate() self.num_requests, self.duration = self.parse_rate(self.rate) @@ -115,66 +117,66 @@ class EnrollmentUserThrottle(UserRateThrottle, ApiKeyPermissionMixIn): @can_disable_rate_limit class EnrollmentView(APIView, ApiKeyPermissionMixIn): """ - **Use Case** + **Use Case** - Get the user's enrollment status for a course. + Get the user's enrollment status for a course. - **Example Request** + **Example Request** - GET /api/enrollment/v1/enrollment/{username},{course_id} + GET /api/enrollment/v1/enrollment/{username},{course_id} - **Response Values** + **Response Values** - If the request for information about the user is successful, an HTTP 200 "OK" response - is returned. + If the request for information about the user is successful, an HTTP 200 "OK" response + is returned. - The HTTP 200 response has the following values. + The HTTP 200 response has the following values. - * course_details: A collection that includes the following + * course_details: A collection that includes the following + values. + + * course_end: The date and time when the course closes. If + null, the course never ends. + * course_id: The unique identifier for the course. + * course_name: The name of the course. + * course_modes: An array of data about the enrollment modes + supported for the course. If the request uses the parameter + include_expired=1, the array also includes expired + enrollment modes. + + Each enrollment mode collection includes the following values. - * course_end: The date and time when the course closes. If - null, the course never ends. - * course_id: The unique identifier for the course. - * course_name: The name of the course. - * course_modes: An array of data about the enrollment modes - supported for the course. If the request uses the parameter - include_expired=1, the array also includes expired - enrollment modes. + * currency: The currency of the listed prices. + * description: A description of this mode. + * expiration_datetime: The date and time after which + users cannot enroll in the course in this mode. + * min_price: The minimum price for which a user can + enroll in this mode. + * name: The full name of the enrollment mode. + * slug: The short name for the enrollment mode. + * suggested_prices: A list of suggested prices for + this enrollment mode. - Each enrollment mode collection includes the following - values. + * course_end: The date and time at which the course closes. If + null, the course never ends. + * course_start: The date and time when the course opens. If + null, the course opens immediately when it is created. + * enrollment_end: The date and time after which users cannot + enroll for the course. If null, the enrollment period never + ends. + * enrollment_start: The date and time when users can begin + enrolling in the course. If null, enrollment opens + immediately when the course is created. + * invite_only: A value indicating whether students must be + invited to enroll in the course. Possible values are true or + false. - * currency: The currency of the listed prices. - * description: A description of this mode. - * expiration_datetime: The date and time after which - users cannot enroll in the course in this mode. - * min_price: The minimum price for which a user can - enroll in this mode. - * name: The full name of the enrollment mode. - * slug: The short name for the enrollment mode. - * suggested_prices: A list of suggested prices for - this enrollment mode. - - * course_end: The date and time at which the course closes. If - null, the course never ends. - * course_start: The date and time when the course opens. If - null, the course opens immediately when it is created. - * enrollment_end: The date and time after which users cannot - enroll for the course. If null, the enrollment period never - ends. - * enrollment_start: The date and time when users can begin - enrolling in the course. If null, enrollment opens - immediately when the course is created. - * invite_only: A value indicating whether students must be - invited to enroll in the course. Possible values are true or - false. - - * created: The date the user account was created. - * is_active: Whether the enrollment is currently active. - * mode: The enrollment mode of the user in this course. - * user: The ID of the user. - """ + * created: The date the user account was created. + * is_active: Whether the enrollment is currently active. + * mode: The enrollment mode of the user in this course. + * user: The ID of the user. + """ authentication_classes = ( JwtAuthentication, @@ -207,8 +209,11 @@ class EnrollmentView(APIView, ApiKeyPermissionMixIn): username = username or request.user.username # TODO Implement proper permissions - if request.user.username != username and not self.has_api_key_permissions(request) \ - and not request.user.is_staff: + if ( + request.user.username != username + and not self.has_api_key_permissions(request) + and not request.user.is_staff + ): # Return a 404 instead of a 403 (Unauthorized). If one user is looking up # other users, do not let them deduce the existence of an enrollment. return Response(status=status.HTTP_404_NOT_FOUND) @@ -223,7 +228,7 @@ class EnrollmentView(APIView, ApiKeyPermissionMixIn): "An error occurred while retrieving enrollments for user " "'{username}' in course '{course_id}'" ).format(username=username, course_id=course_id) - } + }, ) @@ -251,6 +256,7 @@ class EnrollmentUserRolesView(APIView): logged-in user, filtered by course_id if given, along with whether or not the user is global staff """ + authentication_classes = ( JwtAuthentication, BearerAuthenticationAllowInactiveUser, @@ -265,7 +271,7 @@ class EnrollmentUserRolesView(APIView): Gets a list of all roles for the currently logged-in user, filtered by course_id if supplied """ try: - course_id = request.GET.get('course_id') + course_id = request.GET.get("course_id") roles_data = api.get_user_roles(request.user.username) if course_id: roles_data = [role for role in roles_data if str(role.course_id) == course_id] @@ -273,85 +279,83 @@ class EnrollmentUserRolesView(APIView): return Response( status=status.HTTP_400_BAD_REQUEST, data={ - "message": ( - "An error occurred while retrieving roles for user '{username}" - ).format(username=request.user.username) - } + "message": ("An error occurred while retrieving roles for user '{username}").format( + username=request.user.username + ) + }, ) - return Response({ - 'roles': [ - { - "org": role.org, - "course_id": str(role.course_id), - "role": role.role - } - for role in roles_data], - 'is_staff': request.user.is_staff, - }) + return Response( + { + "roles": [ + {"org": role.org, "course_id": str(role.course_id), "role": role.role} for role in roles_data + ], + "is_staff": request.user.is_staff, + } + ) @can_disable_rate_limit class EnrollmentCourseDetailView(APIView): """ - **Use Case** + **Use Case** - Get enrollment details for a course. + Get enrollment details for a course. - Response values include the course schedule and enrollment modes - supported by the course. Use the parameter include_expired=1 to - include expired enrollment modes in the response. + Response values include the course schedule and enrollment modes + supported by the course. Use the parameter include_expired=1 to + include expired enrollment modes in the response. - **Note:** Getting enrollment details for a course does not require - authentication. + **Note:** Getting enrollment details for a course does not require + authentication. - **Example Requests** + **Example Requests** - GET /api/enrollment/v1/course/{course_id} + GET /api/enrollment/v1/course/{course_id} - GET /api/enrollment/v1/course/{course_id}?include_expired=1 + GET /api/enrollment/v1/course/{course_id}?include_expired=1 - **Response Values** + **Response Values** - If the request is successful, an HTTP 200 "OK" response is - returned along with a collection of course enrollments for the - user or for the newly created enrollment. + If the request is successful, an HTTP 200 "OK" response is + returned along with a collection of course enrollments for the + user or for the newly created enrollment. - Each course enrollment contains the following values. + Each course enrollment contains the following values. - * course_end: The date and time when the course closes. If - null, the course never ends. - * course_id: The unique identifier for the course. - * course_name: The name of the course. - * course_modes: An array of data about the enrollment modes - supported for the course. If the request uses the parameter - include_expired=1, the array also includes expired - enrollment modes. + * course_end: The date and time when the course closes. If + null, the course never ends. + * course_id: The unique identifier for the course. + * course_name: The name of the course. + * course_modes: An array of data about the enrollment modes + supported for the course. If the request uses the parameter + include_expired=1, the array also includes expired + enrollment modes. - Each enrollment mode collection includes the following - values. + Each enrollment mode collection includes the following + values. - * currency: The currency of the listed prices. - * description: A description of this mode. - * expiration_datetime: The date and time after which - users cannot enroll in the course in this mode. - * min_price: The minimum price for which a user can - enroll in this mode. - * name: The full name of the enrollment mode. - * slug: The short name for the enrollment mode. - * suggested_prices: A list of suggested prices for - this enrollment mode. + * currency: The currency of the listed prices. + * description: A description of this mode. + * expiration_datetime: The date and time after which + users cannot enroll in the course in this mode. + * min_price: The minimum price for which a user can + enroll in this mode. + * name: The full name of the enrollment mode. + * slug: The short name for the enrollment mode. + * suggested_prices: A list of suggested prices for + this enrollment mode. - * course_start: The date and time when the course opens. If - null, the course opens immediately when it is created. - * enrollment_end: The date and time after which users cannot - enroll for the course. If null, the enrollment period never - ends. - * enrollment_start: The date and time when users can begin - enrolling in the course. If null, enrollment opens - immediately when the course is created. - * invite_only: A value indicating whether students must be - invited to enroll in the course. Possible values are true or - false. + * course_start: The date and time when the course opens. If + null, the course opens immediately when it is created. + * enrollment_end: The date and time after which users cannot + enroll for the course. If null, the enrollment period never + ends. + * enrollment_start: The date and time when users can begin + enrolling in the course. If null, enrollment opens + immediately when the course is created. + * invite_only: A value indicating whether students must be + invited to enroll in the course. Possible values are true or + false. """ authentication_classes = [] @@ -374,54 +378,54 @@ class EnrollmentCourseDetailView(APIView): """ try: - return Response(api.get_course_enrollment_details(course_id, bool(request.GET.get('include_expired', '')))) + return Response(api.get_course_enrollment_details(course_id, bool(request.GET.get("include_expired", "")))) except CourseNotFoundError: return Response( status=status.HTTP_400_BAD_REQUEST, - data={ - "message": ( - "No course found for course ID '{course_id}'" - ).format(course_id=course_id) - } + data={"message": ("No course found for course ID '{course_id}'").format(course_id=course_id)}, ) class UnenrollmentView(APIView): """ - **Use Cases** + **Use Cases** - * Unenroll a single user from all courses. + * Unenroll a single user from all courses. - This command can only be issued by a privileged service user. + This command can only be issued by a privileged service user. - **Example Requests** + **Example Requests** - POST /api/enrollment/v1/enrollment { - "username": "username12345" - } + POST /api/enrollment/v1/enrollment { + "username": "username12345" + } - **POST Parameters** + **POST Parameters** - A POST request must include the following parameter. + A POST request must include the following parameter. - * username: The username of the user being unenrolled. - This will never match the username from the request, - since the request is issued as a privileged service user. + * username: The username of the user being unenrolled. + This will never match the username from the request, + since the request is issued as a privileged service user. - **POST Response Values** + **POST Response Values** - If the user has not requested retirement and does not have a retirement - request status, the request returns an HTTP 404 "Does Not Exist" response. + If the user has not requested retirement and does not have a retirement + request status, the request returns an HTTP 404 "Does Not Exist" response. - If the user is already unenrolled from all courses, the request returns - an HTTP 204 "No Content" response. + If the user is already unenrolled from all courses, the request returns + an HTTP 204 "No Content" response. - If an unexpected error occurs, the request returns an HTTP 500 response. + If an unexpected error occurs, the request returns an HTTP 500 response. - If the request is successful, an HTTP 200 "OK" response is - returned along with a list of all courses from which the user was unenrolled. - """ - permission_classes = (permissions.IsAuthenticated, CanRetireUser,) + If the request is successful, an HTTP 200 "OK" response is + returned along with a list of all courses from which the user was unenrolled. + """ + + permission_classes = ( + permissions.IsAuthenticated, + CanRetireUser, + ) def post(self, request): """ @@ -429,18 +433,18 @@ class UnenrollmentView(APIView): """ try: # Get the username from the request. - username = request.data['username'] + username = request.data["username"] # Ensure that a retirement request status row exists for this username. UserRetirementStatus.get_retirement_for_retirement_action(username) enrollments = api.get_enrollments(username) - active_enrollments = [enrollment for enrollment in enrollments if enrollment['is_active']] + active_enrollments = [enrollment for enrollment in enrollments if enrollment["is_active"]] if len(active_enrollments) < 1: return Response(status=status.HTTP_204_NO_CONTENT) return Response(api.unenroll_user_from_all_courses(username)) except KeyError: - return Response('Username not specified.', status=status.HTTP_404_NOT_FOUND) + return Response("Username not specified.", status=status.HTTP_404_NOT_FOUND) except UserRetirementStatus.DoesNotExist: - return Response('No retirement request status for username.', status=status.HTTP_404_NOT_FOUND) + return Response("No retirement request status for username.", status=status.HTTP_404_NOT_FOUND) except Exception as exc: # pylint: disable=broad-except return Response(str(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR) @@ -448,177 +452,178 @@ class UnenrollmentView(APIView): @can_disable_rate_limit class EnrollmentListView(APIView, ApiKeyPermissionMixIn): """ - **Use Cases** + **Use Cases** - * Get a list of all course enrollments for the currently signed in user. + * Get a list of all course enrollments for the currently signed in user. - * Enroll the currently signed in user in a course. + * Enroll the currently signed in user in a course. - Currently a user can use this command only to enroll the - user in the default course mode. If this is not - supported for the course, the request fails and returns - the available modes. + Currently a user can use this command only to enroll the + user in the default course mode. If this is not + supported for the course, the request fails and returns + the available modes. - This command can use a server-to-server call to enroll a user in - other modes, such as "verified", "professional", or "credit". If - the mode is not supported for the course, the request will fail - and return the available modes. + This command can use a server-to-server call to enroll a user in + other modes, such as "verified", "professional", or "credit". If + the mode is not supported for the course, the request will fail + and return the available modes. - You can include other parameters as enrollment attributes for a - specific course mode. For example, for credit mode, you can - include the following parameters to specify the credit provider - attribute. + You can include other parameters as enrollment attributes for a + specific course mode. For example, for credit mode, you can + include the following parameters to specify the credit provider + attribute. - * namespace: credit - * name: provider_id - * value: institution_name + * namespace: credit + * name: provider_id + * value: institution_name - **Example Requests** + **Example Requests** - GET /api/enrollment/v1/enrollment + GET /api/enrollment/v1/enrollment - POST /api/enrollment/v1/enrollment { + POST /api/enrollment/v1/enrollment { - "mode": "credit", - "course_details":{"course_id": "edX/DemoX/Demo_Course"}, - "enrollment_attributes":[{"namespace": "credit","name": "provider_id","value": "hogwarts",},] + "mode": "credit", + "course_details":{"course_id": "edX/DemoX/Demo_Course"}, + "enrollment_attributes":[{"namespace": "credit","name": "provider_id","value": "hogwarts",},] - } + } - **POST Parameters** + **POST Parameters** - A POST request can include the following parameters. + A POST request can include the following parameters. - * user: Optional. The username of the currently logged in user. - You cannot use the command to enroll a different user. + * user: Optional. The username of the currently logged in user. + You cannot use the command to enroll a different user. - * mode: Optional. The course mode for the enrollment. Individual - users cannot upgrade their enrollment mode from the default. Only - server-to-server requests can enroll with other modes. + * mode: Optional. The course mode for the enrollment. Individual + users cannot upgrade their enrollment mode from the default. Only + server-to-server requests can enroll with other modes. - * is_active: Optional. A Boolean value indicating whether the - enrollment is active. Only server-to-server requests are - allowed to deactivate an enrollment. + * is_active: Optional. A Boolean value indicating whether the + enrollment is active. Only server-to-server requests are + allowed to deactivate an enrollment. - * course details: A collection that includes the following - information. + * course details: A collection that includes the following + information. - * course_id: The unique identifier for the course. + * course_id: The unique identifier for the course. - * email_opt_in: Optional. A Boolean value that indicates whether - the user wants to receive email from the organization that runs - this course. + * email_opt_in: Optional. A Boolean value that indicates whether + the user wants to receive email from the organization that runs + this course. - * enrollment_attributes: A dictionary that contains the following - values. + * enrollment_attributes: A dictionary that contains the following + values. - * namespace: Namespace of the attribute - * name: Name of the attribute - * value: Value of the attribute + * namespace: Namespace of the attribute + * name: Name of the attribute + * value: Value of the attribute - * is_active: Optional. A Boolean value that indicates whether the - enrollment is active. Only server-to-server requests can - deactivate an enrollment. + * is_active: Optional. A Boolean value that indicates whether the + enrollment is active. Only server-to-server requests can + deactivate an enrollment. - * mode: Optional. The course mode for the enrollment. Individual - users cannot upgrade their enrollment mode from the default. Only - server-to-server requests can enroll with other modes. + * mode: Optional. The course mode for the enrollment. Individual + users cannot upgrade their enrollment mode from the default. Only + server-to-server requests can enroll with other modes. - * user: Optional. The user ID of the currently logged in user. You - cannot use the command to enroll a different user. + * user: Optional. The user ID of the currently logged in user. You + cannot use the command to enroll a different user. - * enterprise_course_consent: Optional. A Boolean value that - indicates the consent status for an EnterpriseCourseEnrollment - to be posted to the Enterprise service. + * enterprise_course_consent: Optional. A Boolean value that + indicates the consent status for an EnterpriseCourseEnrollment + to be posted to the Enterprise service. - **GET Response Values** + **GET Response Values** - If an unspecified error occurs when the user tries to obtain a - learner's enrollments, the request returns an HTTP 400 "Bad - Request" response. + If an unspecified error occurs when the user tries to obtain a + learner's enrollments, the request returns an HTTP 400 "Bad + Request" response. - If the user does not have permission to view enrollment data for - the requested learner, the request returns an HTTP 404 "Not Found" - response. + If the user does not have permission to view enrollment data for + the requested learner, the request returns an HTTP 404 "Not Found" + response. - **POST Response Values** + **POST Response Values** - If the user does not specify a course ID, the specified course - does not exist, or the is_active status is invalid, the request - returns an HTTP 400 "Bad Request" response. + If the user does not specify a course ID, the specified course + does not exist, or the is_active status is invalid, the request + returns an HTTP 400 "Bad Request" response. - If a user who is not an admin tries to upgrade a learner's course - mode, the request returns an HTTP 403 "Forbidden" response. + If a user who is not an admin tries to upgrade a learner's course + mode, the request returns an HTTP 403 "Forbidden" response. - If the specified user does not exist, the request returns an HTTP - 406 "Not Acceptable" response. + If the specified user does not exist, the request returns an HTTP + 406 "Not Acceptable" response. - **GET and POST Response Values** + **GET and POST Response Values** - If the request is successful, an HTTP 200 "OK" response is - returned along with a collection of course enrollments for the - user or for the newly created enrollment. + If the request is successful, an HTTP 200 "OK" response is + returned along with a collection of course enrollments for the + user or for the newly created enrollment. - Each course enrollment contains the following values. + Each course enrollment contains the following values. - * course_details: A collection that includes the following + * course_details: A collection that includes the following + values. + + * course_end: The date and time when the course closes. If + null, the course never ends. + + * course_id: The unique identifier for the course. + + * course_name: The name of the course. + + * course_modes: An array of data about the enrollment modes + supported for the course. If the request uses the parameter + include_expired=1, the array also includes expired + enrollment modes. + + Each enrollment mode collection includes the following values. - * course_end: The date and time when the course closes. If - null, the course never ends. + * currency: The currency of the listed prices. - * course_id: The unique identifier for the course. + * description: A description of this mode. - * course_name: The name of the course. + * expiration_datetime: The date and time after which users + cannot enroll in the course in this mode. - * course_modes: An array of data about the enrollment modes - supported for the course. If the request uses the parameter - include_expired=1, the array also includes expired - enrollment modes. + * min_price: The minimum price for which a user can enroll in + this mode. - Each enrollment mode collection includes the following - values. + * name: The full name of the enrollment mode. - * currency: The currency of the listed prices. + * slug: The short name for the enrollment mode. - * description: A description of this mode. + * suggested_prices: A list of suggested prices for this + enrollment mode. - * expiration_datetime: The date and time after which users - cannot enroll in the course in this mode. + * course_start: The date and time when the course opens. If + null, the course opens immediately when it is created. - * min_price: The minimum price for which a user can enroll in - this mode. + * enrollment_end: The date and time after which users cannot + enroll for the course. If null, the enrollment period never + ends. - * name: The full name of the enrollment mode. + * enrollment_start: The date and time when users can begin + enrolling in the course. If null, enrollment opens + immediately when the course is created. - * slug: The short name for the enrollment mode. + * invite_only: A value indicating whether students must be + invited to enroll in the course. Possible values are true or + false. - * suggested_prices: A list of suggested prices for this - enrollment mode. + * created: The date the user account was created. - * course_start: The date and time when the course opens. If - null, the course opens immediately when it is created. + * is_active: Whether the enrollment is currently active. - * enrollment_end: The date and time after which users cannot - enroll for the course. If null, the enrollment period never - ends. + * mode: The enrollment mode of the user in this course. - * enrollment_start: The date and time when users can begin - enrolling in the course. If null, enrollment opens - immediately when the course is created. - - * invite_only: A value indicating whether students must be - invited to enroll in the course. Possible values are true or - false. - - * created: The date the user account was created. - - * is_active: Whether the enrollment is currently active. - - * mode: The enrollment mode of the user in this course. - - * user: The username of the user. + * user: The username of the user. """ + authentication_classes = ( JwtAuthentication, BearerAuthenticationAllowInactiveUser, @@ -648,20 +653,23 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): Users who have the global staff permission can access all enrollment data for all courses. """ - username = request.GET.get('user', request.user.username) + username = request.GET.get("user", request.user.username) try: enrollment_data = api.get_enrollments(username) except CourseEnrollmentError: return Response( status=status.HTTP_400_BAD_REQUEST, data={ - "message": ( - "An error occurred while retrieving enrollments for user '{username}'" - ).format(username=username) - } + "message": ("An error occurred while retrieving enrollments for user '{username}'").format( + username=username + ) + }, ) - if username == request.user.username or GlobalStaff().has_user(request.user) or \ - self.has_api_key_permissions(request): + if ( + username == request.user.username + or GlobalStaff().has_user(request.user) + or self.has_api_key_permissions(request) + ): return Response(enrollment_data) filtered_data = [] for enrollment in enrollment_data: @@ -679,32 +687,33 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): """ # Get the User, Course ID, and Mode from the request. - username = request.data.get('user') - course_id = request.data.get('course_details', {}).get('course_id') + username = request.data.get("user") + course_id = request.data.get("course_details", {}).get("course_id") if not course_id: return Response( status=status.HTTP_400_BAD_REQUEST, - data={"message": "Course ID must be specified to create a new enrollment."} + data={"message": "Course ID must be specified to create a new enrollment."}, ) try: course_id = CourseKey.from_string(course_id) except InvalidKeyError: return Response( - status=status.HTTP_400_BAD_REQUEST, - data={ - "message": f"No course '{course_id}' found for enrollment" - } + status=status.HTTP_400_BAD_REQUEST, data={"message": f"No course '{course_id}' found for enrollment"} ) - mode = request.data.get('mode') + mode = request.data.get("mode") has_api_key_permissions = self.has_api_key_permissions(request) # Check that the user specified is either the same user, or this is a server-to-server request. - if username and username != request.user.username and not has_api_key_permissions \ - and not GlobalStaff().has_user(request.user): + if ( + username + and username != request.user.username + and not has_api_key_permissions + and not GlobalStaff().has_user(request.user) + ): # Return a 404 instead of a 403 (Unauthorized). If one user is looking up # other users, do not let them deduce the existence of an enrollment. return Response(status=status.HTTP_404_NOT_FOUND) @@ -712,7 +721,7 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): # A provided user has priority over a provided email. # Fallback on request user if neither is provided. if not username: - email = request.data.get('email') + email = request.data.get("email") if email: # Only server-to-server or staff users can use the email for the request. if not has_api_key_permissions and not GlobalStaff().has_user(request.user): @@ -722,22 +731,23 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): except ObjectDoesNotExist: return Response( status=status.HTTP_406_NOT_ACCEPTABLE, - data={ - 'message': f'The user with the email address {email} does not exist.' - } + data={"message": f"The user with the email address {email} does not exist."}, ) else: username = request.user.username - if mode not in (CourseMode.AUDIT, CourseMode.HONOR, None) and not has_api_key_permissions \ - and not GlobalStaff().has_user(request.user): + if ( + mode not in (CourseMode.AUDIT, CourseMode.HONOR, None) + and not has_api_key_permissions + and not GlobalStaff().has_user(request.user) + ): return Response( status=status.HTTP_403_FORBIDDEN, data={ "message": "User does not have permission to create enrollment with mode [{mode}].".format( mode=mode ) - } + }, ) try: @@ -745,10 +755,7 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): user = User.objects.get(username=username) except ObjectDoesNotExist: return Response( - status=status.HTTP_406_NOT_ACCEPTABLE, - data={ - 'message': f'The user {username} does not exist.' - } + status=status.HTTP_406_NOT_ACCEPTABLE, data={"message": f"The user {username} does not exist."} ) embargo_response = embargo_api.get_embargo_response(request, course_id, user) @@ -757,54 +764,53 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): return embargo_response try: - is_active = request.data.get('is_active') + is_active = request.data.get("is_active") # Check if the requested activation status is None or a Boolean if is_active is not None and not isinstance(is_active, bool): return Response( status=status.HTTP_400_BAD_REQUEST, - data={ - 'message': ("'{value}' is an invalid enrollment activation status.").format(value=is_active) - } + data={"message": ("'{value}' is an invalid enrollment activation status.").format(value=is_active)}, ) - explicit_linked_enterprise = request.data.get('linked_enterprise_customer') + explicit_linked_enterprise = request.data.get("linked_enterprise_customer") if explicit_linked_enterprise and has_api_key_permissions and enterprise_enabled(): enterprise_api_client = EnterpriseApiServiceClient() consent_client = ConsentApiServiceClient() try: enterprise_api_client.post_enterprise_course_enrollment(username, str(course_id)) except EnterpriseApiException as error: - log.exception("An unexpected error occurred while creating the new EnterpriseCourseEnrollment " - "for user [%s] in course run [%s]", username, course_id) + log.exception( + "An unexpected error occurred while creating the new EnterpriseCourseEnrollment " + "for user [%s] in course run [%s]", + username, + course_id, + ) raise CourseEnrollmentError(str(error)) # lint-amnesty, pylint: disable=raise-missing-from kwargs = { - 'username': username, - 'course_id': str(course_id), - 'enterprise_customer_uuid': explicit_linked_enterprise, + "username": username, + "course_id": str(course_id), + "enterprise_customer_uuid": explicit_linked_enterprise, } consent_client.provide_consent(**kwargs) - enrollment_attributes = request.data.get('enrollment_attributes') - force_enrollment = request.data.get('force_enrollment') + enrollment_attributes = request.data.get("enrollment_attributes") + force_enrollment = request.data.get("force_enrollment") # Check if the force enrollment status is None or a Boolean if force_enrollment is not None and not isinstance(force_enrollment, bool): return Response( status=status.HTTP_400_BAD_REQUEST, data={ - 'message': ("'{value}' is an invalid force enrollment status.").format(value=force_enrollment) - } + "message": ("'{value}' is an invalid force enrollment status.").format(value=force_enrollment) + }, ) # Only a staff user role can enroll a user forcefully force_enrollment = force_enrollment and GlobalStaff().has_user(request.user) enrollment = api.get_enrollment(username, str(course_id)) - mode_changed = enrollment and mode is not None and enrollment['mode'] != mode - active_changed = enrollment and is_active is not None and enrollment['is_active'] != is_active + mode_changed = enrollment and mode is not None and enrollment["mode"] != mode + active_changed = enrollment and is_active is not None and enrollment["is_active"] != is_active missing_attrs = [] if enrollment_attributes: - actual_attrs = [ - "{namespace}:{name}".format(**attr) - for attr in enrollment_attributes - ] + actual_attrs = ["{namespace}:{name}".format(**attr) for attr in enrollment_attributes] missing_attrs = set(REQUIRED_ATTRIBUTES.get(mode, [])) - set(actual_attrs) if (GlobalStaff().has_user(request.user) or has_api_key_permissions) and (mode_changed or active_changed): if mode_changed and active_changed and not is_active: @@ -831,7 +837,7 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): is_active=is_active, enrollment_attributes=enrollment_attributes, # If we are updating enrollment by authorized api caller, we should allow expired modes - include_expired=has_api_key_permissions + include_expired=has_api_key_permissions, ) else: # Will reactivate inactive enrollments. @@ -841,26 +847,26 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): mode=mode, is_active=is_active, enrollment_attributes=enrollment_attributes, - enterprise_uuid=request.data.get('enterprise_uuid'), + enterprise_uuid=request.data.get("enterprise_uuid"), force_enrollment=force_enrollment, # If we are creating enrollment by staff user with force_enrollment, we should allow expired modes - include_expired=force_enrollment + include_expired=force_enrollment, ) - cohort_name = request.data.get('cohort') + cohort_name = request.data.get("cohort") if cohort_name is not None: cohort = get_cohort_by_name(course_id, cohort_name) try: add_user_to_cohort(cohort, user) except ValueError: # user already in cohort, probably because they were un-enrolled and re-enrolled - log.exception('Cohort re-addition') - email_opt_in = request.data.get('email_opt_in', None) + log.exception("Cohort re-addition") + email_opt_in = request.data.get("email_opt_in", None) if email_opt_in is not None: org = course_id.org update_email_opt_in(request.user, org, email_opt_in) - log.info('The user [%s] has already been enrolled in course run [%s].', username, course_id) + log.info("The user [%s] has already been enrolled in course run [%s].", username, course_id) return Response(response) except CourseModeNotFoundError as error: return Response( @@ -869,21 +875,22 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): "message": ( "The [{mode}] course mode is expired or otherwise unavailable for course run [{course_id}]." ).format(mode=mode, course_id=course_id), - "course_details": error.data - }) + "course_details": error.data, + }, + ) except CourseNotFoundError: return Response( - status=status.HTTP_400_BAD_REQUEST, - data={ - "message": f"No course '{course_id}' found for enrollment" - } + status=status.HTTP_400_BAD_REQUEST, data={"message": f"No course '{course_id}' found for enrollment"} ) except CourseEnrollmentExistsError as error: - log.warning('An enrollment already exists for user [%s] in course run [%s].', username, course_id) + log.warning("An enrollment already exists for user [%s] in course run [%s].", username, course_id) return Response(data=error.enrollment) except CourseEnrollmentError: - log.exception("An error occurred while creating the new course enrollment for user " - "[%s] in course run [%s]", username, course_id) + log.exception( + "An error occurred while creating the new course enrollment for user [%s] in course run [%s]", + username, + course_id, + ) return Response( status=status.HTTP_400_BAD_REQUEST, data={ @@ -891,100 +898,100 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): "An error occurred while creating the new course enrollment for user " "'{username}' in course '{course_id}'" ).format(username=username, course_id=course_id) - } + }, ) except CourseUserGroup.DoesNotExist: - log.exception('Missing cohort [%s] in course run [%s]', cohort_name, course_id) + log.exception("Missing cohort [%s] in course run [%s]", cohort_name, course_id) return Response( status=status.HTTP_400_BAD_REQUEST, - data={ - "message": "An error occured while adding to cohort [%s]" % cohort_name - }) + data={"message": "An error occured while adding to cohort [%s]" % cohort_name}, + ) finally: # Assumes that the ecommerce service uses an API key to authenticate. if has_api_key_permissions: current_enrollment = api.get_enrollment(username, str(course_id)) audit_log( - 'enrollment_change_requested', + "enrollment_change_requested", course_id=str(course_id), requested_mode=mode, - actual_mode=current_enrollment['mode'] if current_enrollment else None, + actual_mode=current_enrollment["mode"] if current_enrollment else None, requested_activation=is_active, - actual_activation=current_enrollment['is_active'] if current_enrollment else None, - user_id=user.id + actual_activation=current_enrollment["is_active"] if current_enrollment else None, + user_id=user.id, ) @can_disable_rate_limit class CourseEnrollmentsApiListView(DeveloperErrorViewMixin, ListAPIView): """ - **Use Cases** + **Use Cases** - Get a list of all course enrollments, optionally filtered by a course ID or list of usernames. + Get a list of all course enrollments, optionally filtered by a course ID or list of usernames. - **Example Requests** + **Example Requests** - GET /api/enrollment/v1/enrollments + GET /api/enrollment/v1/enrollments - GET /api/enrollment/v1/enrollments?course_id={course_id} + GET /api/enrollment/v1/enrollments?course_id={course_id} - GET /api/enrollment/v1/enrollments?username={username},{username},{username} + GET /api/enrollment/v1/enrollments?username={username},{username},{username} - GET /api/enrollment/v1/enrollments?course_id={course_id}&username={username} + GET /api/enrollment/v1/enrollments?course_id={course_id}&username={username} - GET /api/enrollment/v1/enrollments?email={email},{email} + GET /api/enrollment/v1/enrollments?email={email},{email} - **Query Parameters for GET** + **Query Parameters for GET** - * course_id: Filters the result to course enrollments for the course corresponding to the - given course ID. The value must be URL encoded. Optional. + * course_id: Filters the result to course enrollments for the course corresponding to the + given course ID. The value must be URL encoded. Optional. - * username: List of comma-separated usernames. Filters the result to the course enrollments - of the given users. Optional. + * username: List of comma-separated usernames. Filters the result to the course enrollments + of the given users. Optional. - * email: List of comma-separated emails. Filters the result to the course enrollments - of the given users. Optional. + * email: List of comma-separated emails. Filters the result to the course enrollments + of the given users. Optional. - * page_size: Number of results to return per page. Optional. + * page_size: Number of results to return per page. Optional. - * page: Page number to retrieve. Optional. + * page: Page number to retrieve. Optional. - **Response Values** + **Response Values** - If the request for information about the course enrollments is successful, an HTTP 200 "OK" response - is returned. + If the request for information about the course enrollments is successful, an HTTP 200 "OK" response + is returned. - The HTTP 200 response has the following values. + The HTTP 200 response has the following values. - * results: A list of the course enrollments matching the request. + * results: A list of the course enrollments matching the request. - * created: Date and time when the course enrollment was created. + * created: Date and time when the course enrollment was created. - * mode: Mode for the course enrollment. + * mode: Mode for the course enrollment. - * is_active: Whether the course enrollment is active or not. + * is_active: Whether the course enrollment is active or not. - * user: Username of the user in the course enrollment. + * user: Username of the user in the course enrollment. - * course_id: Course ID of the course in the course enrollment. + * course_id: Course ID of the course in the course enrollment. - * next: The URL to the next page of results, or null if this is the - last page. + * next: The URL to the next page of results, or null if this is the + last page. - * previous: The URL to the next page of results, or null if this - is the first page. + * previous: The URL to the next page of results, or null if this + is the first page. - If the user is not logged in, a 401 error is returned. + If the user is not logged in, a 401 error is returned. - If the user is not global staff, a 403 error is returned. + If the user is not global staff, a 403 error is returned. - If the specified course_id is not valid or any of the specified usernames - are not valid, a 400 error is returned. + If the specified course_id is not valid or any of the specified usernames + are not valid, a 400 error is returned. - If the specified course_id does not correspond to a valid course or if all the specified - usernames do not correspond to valid users, an HTTP 200 "OK" response is returned with an - empty 'results' field. + If the specified course_id does not correspond to a valid course or if all the specified + usernames do not correspond to valid users, an HTTP 200 "OK" response is returned with an + empty 'results' field. """ + authentication_classes = ( JwtAuthentication, BearerAuthenticationAllowInactiveUser, @@ -1005,9 +1012,9 @@ class CourseEnrollmentsApiListView(DeveloperErrorViewMixin, ListAPIView): raise ValidationError(form.errors) queryset = CourseEnrollment.objects.all() - course_id = form.cleaned_data.get('course_id') - usernames = form.cleaned_data.get('username') - emails = form.cleaned_data.get('email') + course_id = form.cleaned_data.get("course_id") + usernames = form.cleaned_data.get("username") + emails = form.cleaned_data.get("email") if course_id: queryset = queryset.filter(course_id=course_id) @@ -1022,6 +1029,7 @@ class EnrollmentAllowedView(APIView): """ A view that allows the retrieval and creation of enrollment allowed for a given user email and course id. """ + permission_classes = (permissions.IsAdminUser,) throttle_classes = (EnrollmentUserThrottle,) serializer_class = CourseEnrollmentAllowedSerializer @@ -1042,7 +1050,7 @@ class EnrollmentAllowedView(APIView): - 200: Success. - 403: Forbidden, you need to be staff. """ - user_email = request.query_params.get('email') + user_email = request.query_params.get("email") if not user_email: user_email = request.user.email @@ -1051,10 +1059,7 @@ class EnrollmentAllowedView(APIView): CourseEnrollmentAllowedSerializer(enrollment).data for enrollment in enrollments_allowed ] - return Response( - status=status.HTTP_200_OK, - data=serialized_enrollments_allowed - ) + return Response(status=status.HTTP_200_OK, data=serialized_enrollments_allowed) def post(self, request): """ @@ -1089,29 +1094,22 @@ class EnrollmentAllowedView(APIView): - 409: Conflict, enrollment allowed already exists. """ is_bad_request_response, email, course_id = self.check_required_data(request) - auto_enroll = request.data.get('auto_enroll', False) + auto_enroll = request.data.get("auto_enroll", False) if is_bad_request_response: return is_bad_request_response try: enrollment_allowed = CourseEnrollmentAllowed.objects.create( - email=email, - course_id=course_id, - auto_enroll=auto_enroll + email=email, course_id=course_id, auto_enroll=auto_enroll ) except IntegrityError: return Response( status=status.HTTP_409_CONFLICT, - data={ - 'message': f'An enrollment allowed with email {email} and course {course_id} already exists.' - } + data={"message": f"An enrollment allowed with email {email} and course {course_id} already exists."}, ) serializer = CourseEnrollmentAllowedSerializer(enrollment_allowed) - return Response( - status=status.HTTP_201_CREATED, - data=serializer.data - ) + return Response(status=status.HTTP_201_CREATED, data=serializer.data) def delete(self, request): """ @@ -1148,33 +1146,27 @@ class EnrollmentAllowedView(APIView): return is_bad_request_response try: - CourseEnrollmentAllowed.objects.get( - email=email, - course_id=course_id - ).delete() + CourseEnrollmentAllowed.objects.get(email=email, course_id=course_id).delete() return Response( status=status.HTTP_204_NO_CONTENT, ) except ObjectDoesNotExist: return Response( status=status.HTTP_404_NOT_FOUND, - data={ - 'message': f"An enrollment allowed with email {email} and course {course_id} doesn't exists." - } + data={"message": f"An enrollment allowed with email {email} and course {course_id} doesn't exists."}, ) def check_required_data(self, request): """ Check if the request has email and course_id. """ - email = request.data.get('email') - course_id = request.data.get('course_id') + email = request.data.get("email") + course_id = request.data.get("course_id") if not email or not course_id: is_bad_request = Response( status=status.HTTP_400_BAD_REQUEST, - data={ - "message": "Please provide a value for 'email' and 'course_id' in the request data." - }) + data={"message": "Please provide a value for 'email' and 'course_id' in the request data."}, + ) else: is_bad_request = None return (is_bad_request, email, course_id)