From d6b675ab0f9fd72fa57e2ca17060d0555162a3d1 Mon Sep 17 00:00:00 2001 From: arora-r Date: Thu, 7 Aug 2025 17:20:56 -0400 Subject: [PATCH 1/2] feat: toggle cert enablement for courses --- edx_courses_api/urls.py | 3 ++- edx_courses_api/views.py | 48 +++++++++++++++++++++++++++++++++++----- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/edx_courses_api/urls.py b/edx_courses_api/urls.py index 2ee6809..8fe8850 100644 --- a/edx_courses_api/urls.py +++ b/edx_courses_api/urls.py @@ -4,7 +4,7 @@ from django.conf import settings from django.conf.urls import url -from .views import CourseView, hide, show, export, export_output, export_status, xblock_handler, xblock_item_handler +from .views import CourseView, hide, show, export, export_output, export_status, xblock_handler, xblock_item_handler, set_certificate_settings urlpatterns = [ url(r'^{}/$'.format(settings.COURSE_KEY_PATTERN), CourseView.as_view(), name='course'), @@ -19,6 +19,7 @@ url(r'^{}/export/$'.format(settings.COURSE_KEY_PATTERN), export, name='export'), url(r'^{}/export_status/$'.format(settings.COURSE_KEY_PATTERN), export_status, name='export_status'), url(r'^{}/export_output/$'.format(settings.COURSE_KEY_PATTERN), export_output, name='export_output'), + url(r'^{}/certificate/settings/$'.format(settings.COURSE_KEY_PATTERN), set_certificate_settings, name='set_certificate_settings'), ] # Since urls.py is executed once, create service user here for server to server auth diff --git a/edx_courses_api/views.py b/edx_courses_api/views.py index 9b0f48b..97a7f14 100644 --- a/edx_courses_api/views.py +++ b/edx_courses_api/views.py @@ -55,6 +55,9 @@ class CourseView(APIView): authentication_classes = [BasicAuthentication] permission_classes = [IsAuthenticated] + def str_to_bool(self, val): + return str(val).lower() == 'true' + def delete(self, request, course_key_string): course_key = CourseKey.from_string(course_key_string) log.info('DELETING {}'.format(course_key)) @@ -67,7 +70,11 @@ def post(self, request, course_key_string): try: user = User.objects.get(username=USERNAME) course_name = request.data.get("name", "Empty") - fields = { "display_name": course_name } + + # Correctly parse boolean value from request + certificate_enabled = self.str_to_bool(request.data.get("certificate_enabled", 'true')) + + fields = {"display_name": course_name} new_course = create_new_course_in_store( "split", user, @@ -78,15 +85,14 @@ def post(self, request, course_key_string): ) msg = u"Created {}".format(new_course.id) log.info(msg) - self.finalize_course(course_key) + self.finalize_course(course_key, certificate_enabled) return Response({'detail': msg}) except DuplicateCourseError: msg = u"Course already exists for {}, {}, {}".format(course_key.org, course_key.course, course_key.run) log.warning(msg) raise ParseError(msg) - - def finalize_course(self, course_key): + def finalize_course(self, course_key, certificate_enabled): log.info('Adding honor course mode') CourseMode.objects.get_or_create( course_id=course_key, @@ -96,7 +102,7 @@ def finalize_course(self, course_key): log.info('Enabling self generated certificates') CertificateGenerationCourseSetting.objects.get_or_create( course_key=course_key, - self_generation_enabled=True, + self_generation_enabled=certificate_enabled, ) log.info('Enabling LTI fields') CourseEditLTIFieldsEnabledFlag.objects.get_or_create( @@ -115,6 +121,38 @@ def set_visibility(course_key, visibility): course.catalog_visibility = visibility course.save() +@api_view(['POST']) +@authentication_classes([BasicAuthentication]) +@permission_classes([IsAuthenticated]) +def set_certificate_settings(request, course_key_string): + try: + course_key = CourseKey.from_string(course_key_string) + except Exception as e: + log.error(f"Invalid course key: {course_key_string}") + return Response({"detail": "Invalid course key."}, status=status.HTTP_400_BAD_REQUEST) + + try: + data = json.loads(request.body.decode('utf-8')) + enabled = data.get("enabled") + enabled = enabled.lower() == 'true' + except (json.JSONDecodeError, ValueError, KeyError) as e: + return Response({"detail": "Request must include a JSON body with a boolean field 'enabled'."}, + status=status.HTTP_400_BAD_REQUEST) + + certificate_generation_setting, created = CertificateGenerationCourseSetting.objects.get_or_create( + course_key=course_key + ) + + certificate_generation_setting.enabled = enabled + certificate_generation_setting.save() + + log.info(f"Course: {course_key} now has its certificate generation setting set to {enabled}") + return Response({ + 'course_key': str(course_key), + 'enabled': enabled, + 'status': 'created' if created else 'updated' + }) + @api_view(['POST']) @authentication_classes([BasicAuthentication]) @permission_classes([IsAuthenticated]) From 74e5e706924482228721fb6c8623e0b99f999fd9 Mon Sep 17 00:00:00 2001 From: arora-r Date: Fri, 8 Aug 2025 13:08:17 -0400 Subject: [PATCH 2/2] add explicit audit mode to be able to switch between audit and honor enrollments --- edx_courses_api/views.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/edx_courses_api/views.py b/edx_courses_api/views.py index 97a7f14..90349b0 100644 --- a/edx_courses_api/views.py +++ b/edx_courses_api/views.py @@ -18,7 +18,7 @@ from django.core.files.storage import FileSystemStorage # edx imports -from openedx.core.djangoapps.content.course_overviews.models import CourseOverview +from openedx.core.djangoapps.content.course_modes.models import CourseMode from cms.djangoapps.contentstore.views.course import create_new_course_in_store from cms.djangoapps.contentstore.utils import delete_course from xmodule.modulestore.exceptions import DuplicateCourseError @@ -93,6 +93,12 @@ def post(self, request, course_key_string): raise ParseError(msg) def finalize_course(self, course_key, certificate_enabled): + log.info('Adding audit course mode') + CourseMode.objects.get_or_create( + course_id=course_key, + mode_slug=CourseMode.AUDIT, + defaults={"mode_display_name": "Audit"}, + ) log.info('Adding honor course mode') CourseMode.objects.get_or_create( course_id=course_key,