Certificate activation handler to drf (#37037)

* feat: certificate_activation_handler to drf
This commit is contained in:
Hunzlah Malik
2025-07-24 19:08:43 +05:00
committed by GitHub
parent 5409baa5f8
commit 89190cc55a
4 changed files with 95 additions and 30 deletions

View File

@@ -32,13 +32,19 @@ from django.core.exceptions import PermissionDenied
from django.http import HttpResponse
from django.shortcuts import redirect
from django.utils.translation import gettext as _
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.decorators.http import require_http_methods
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import status
from common.djangoapps.course_modes.models import CourseMode
from eventtracking import tracker
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import AssetKey, CourseKey
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin
from common.djangoapps.edxmako.shortcuts import render_to_response
from common.djangoapps.student.auth import has_studio_write_access
from common.djangoapps.student.roles import GlobalStaff
@@ -47,6 +53,10 @@ from common.djangoapps.util.json_request import JsonResponse
from xmodule.modulestore import EdxJSONEncoder # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
from cms.djangoapps.contentstore.views.serializers import CertificateActivationSerializer
from cms.djangoapps.contentstore.views.permissions import HasStudioWriteAccess
from ..exceptions import AssetNotFoundException
from ..toggles import use_new_certificates_page
from ..utils import (
@@ -360,39 +370,56 @@ class Certificate:
return self._certificate_data
@login_required
@require_http_methods(("POST",))
@ensure_csrf_cookie
def certificate_activation_handler(request, course_key_string):
class ModulestoreMixin:
"""
A handler for Certificate Activation/Deactivation
POST
json: is_active. update the activation state of certificate
Mixin to provide a get_modulestore() method for views.
Makes it easier to override or patch in tests.
"""
course_key = CourseKey.from_string(course_key_string)
store = modulestore()
try:
course = _get_course_and_check_access(course_key, request.user)
except PermissionDenied:
msg = _('PermissionDenied: Failed in authenticating {user}').format(user=request.user)
return JsonResponse({"error": msg}, status=403)
def get_modulestore(self):
return modulestore()
data = json.loads(request.body.decode('utf8'))
is_active = data.get('is_active', False)
certificates = CertificateManager.get_certificates(course)
# for certificate activation/deactivation, we are assuming one certificate in certificates collection.
for certificate in certificates:
certificate['is_active'] = is_active
break
class CertificateActivationAPIView(
DeveloperErrorViewMixin,
ModulestoreMixin,
APIView
):
"""
View for activating or deactivating course certificates.
This view allows instructors to toggle the activation state of course certificates.
"""
permission_classes = [IsAuthenticated, HasStudioWriteAccess]
serializer_class = CertificateActivationSerializer
store.update_item(course, request.user.id)
cert_event_type = 'activated' if is_active else 'deactivated'
CertificateManager.track_event(cert_event_type, {
'course_id': str(course.id),
})
return HttpResponse(status=200)
@method_decorator(ensure_csrf_cookie)
def post(self, request, course_key_string):
"""
A handler for Certificate Activation/Deactivation
POST
json: is_active. update the activation state of certificate
"""
course_key = CourseKey.from_string(course_key_string)
course = self.get_modulestore().get_course(course_key, depth=0)
serializer = self.serializer_class(data=request.data)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
is_active = serializer.validated_data['is_active']
certificates = CertificateManager.get_certificates(course)
# for certificate activation/deactivation, we are assuming one certificate in certificates collection.
for certificate in certificates:
certificate['is_active'] = is_active
break
self.get_modulestore().update_item(course, request.user.id)
cert_event_type = 'activated' if is_active else 'deactivated'
CertificateManager.track_event(cert_event_type, {
'course_id': str(course.id),
})
return Response(status=status.HTTP_200_OK)
@login_required

View File

@@ -0,0 +1,22 @@
"""
Custom permissions for the content store views.
"""
from rest_framework.permissions import BasePermission
from common.djangoapps.student.auth import has_studio_write_access
from openedx.core.lib.api.view_utils import validate_course_key
class HasStudioWriteAccess(BasePermission):
"""
Check if the user has write access to studio.
"""
def has_permission(self, request, view):
"""
Check if the user has write access to studio.
"""
course_key_string = view.kwargs.get("course_key_string")
course_key = validate_course_key(course_key_string)
return has_studio_write_access(request.user, course_key)

View File

@@ -0,0 +1,16 @@
"""
Serializers for the contentstore.views module.
This module contains DRF serializers for various features such as certificates, blocks, and others.
Add new serializers here as needed for API endpoints in this module.
"""
from rest_framework import serializers
class CertificateActivationSerializer(serializers.Serializer):
"""
Serializer for activating or deactivating course certificates.
"""
# This field indicates whether the certificate should be activated or deactivated.
is_active = serializers.BooleanField(required=False, default=False)

View File

@@ -264,7 +264,7 @@ if core_toggles.ENTRANCE_EXAMS.is_enabled():
# Enable Web/HTML Certificates
if settings.FEATURES.get('CERTIFICATES_HTML_VIEW'):
from cms.djangoapps.contentstore.views.certificates import (
certificate_activation_handler,
CertificateActivationAPIView,
signatory_detail_handler,
certificates_detail_handler,
certificates_list_handler
@@ -272,7 +272,7 @@ if settings.FEATURES.get('CERTIFICATES_HTML_VIEW'):
urlpatterns += [
re_path(fr'^certificates/activation/{settings.COURSE_KEY_PATTERN}/',
certificate_activation_handler,
CertificateActivationAPIView.as_view(),
name='certificate_activation_handler'),
re_path(r'^certificates/{}/(?P<certificate_id>\d+)/signatories/(?P<signatory_id>\d+)?$'.format(
settings.COURSE_KEY_PATTERN), signatory_detail_handler, name='signatory_detail_handler'),