diff --git a/openedx/core/djangoapps/discussions/views.py b/openedx/core/djangoapps/discussions/views.py index 9949661464..e47662f4d3 100644 --- a/openedx/core/djangoapps/discussions/views.py +++ b/openedx/core/djangoapps/discussions/views.py @@ -1,6 +1,7 @@ """ Handle view-logic for the djangoapp """ +from lti_consumer.models import LtiConfiguration from opaque_keys.edx.keys import CourseKey from opaque_keys import InvalidKeyError from rest_framework import serializers @@ -26,6 +27,20 @@ PROVIDER_FEATURE_MAP = { } +class LtiSerializer(serializers.ModelSerializer): + """ + Serialize LtiConfiguration responses + """ + class Meta: + model = LtiConfiguration + fields = [ + 'lti_1p1_client_key', + 'lti_1p1_client_secret', + 'lti_1p1_launch_url', + 'version', + ] + + @view_auth_classes() class DiscussionsConfigurationView(APIView): """ @@ -33,36 +48,45 @@ class DiscussionsConfigurationView(APIView): """ permission_classes = (IsStaff,) - class Serializer(serializers.BaseSerializer): + class Serializer(serializers.ModelSerializer): """ Serialize configuration responses """ + class Meta: + model = DiscussionsConfiguration + fields = [ + 'context_key', + 'enabled', + 'provider_type', + ] - def create(self, validated_data): - """ - Create and save a new instance - """ - raise NotImplementedError - - def to_internal_data(self, data): + def to_internal_value(self, data): """ Transform the *incoming* primitive data into a native value. """ - raise NotImplementedError + payload = { + 'context_key': data.get('course_key', ''), + 'enabled': data.get('enabled', False), + 'lti_configuration': data.get('lti_configuration', {}), + 'plugin_configuration': data.get('plugin_configuration', {}), + 'provider_type': data.get('provider_type', ''), + } + return payload def to_representation(self, instance) -> dict: """ Serialize data into a dictionary, to be used as a response """ - payload = { - 'context_key': str(instance.context_key), - 'enabled': instance.enabled, + payload = super().to_representation(instance) + lti_configuration = LtiSerializer(instance.lti_configuration) + payload.update({ 'features': { 'discussion-page', 'embedded-course-sections', 'lti', 'wcag-2.1', }, + 'lti_configuration': lti_configuration.data, 'plugin_configuration': instance.plugin_configuration, 'providers': { 'active': instance.provider_type or None, @@ -73,14 +97,36 @@ class DiscussionsConfigurationView(APIView): for provider in instance.available_providers }, }, - } + }) return payload def update(self, instance, validated_data): """ Update and save an existing instance """ - raise NotImplementedError + keys = [ + 'enabled', + 'plugin_configuration', + 'provider_type', + ] + for key in keys: + value = validated_data.get(key) + if value is not None: + setattr(instance, key, value) + lti_configuration_data = validated_data.get('lti_configuration') + if lti_configuration_data: + lti_key = lti_configuration_data.get('lti_1p1_client_key') + lti_secret = lti_configuration_data.get('lti_1p1_client_secret') + lti_url = lti_configuration_data.get('lti_1p1_launch_url') + lti_configuration = instance.lti_configuration or LtiConfiguration() + lti_configuration.config_store = LtiConfiguration.CONFIG_ON_DB + lti_configuration.lti_1p1_client_key = lti_key + lti_configuration.lti_1p1_client_secret = lti_secret + lti_configuration.lti_1p1_launch_url = lti_url + lti_configuration.save() + instance.lti_configuration = lti_configuration + instance.save() + return instance # pylint: disable=redefined-builtin def get(self, request, course_key_string, **_kwargs) -> Response: @@ -92,6 +138,19 @@ class DiscussionsConfigurationView(APIView): serializer = self.Serializer(configuration) return Response(serializer.data) + def post(self, request, course_key_string, **_kwargs) -> Response: + """ + Handle HTTP/POST requests + + TODO: Should we cleanup orphaned LTI config when swapping to cs_comments_service? + """ + course_key = self._validate_course_key(course_key_string) + configuration = DiscussionsConfiguration.get(course_key) + serializer = self.Serializer(configuration, data=request.data, partial=True) + if serializer.is_valid(raise_exception=True): + serializer.save() + return Response(serializer.data) + def _validate_course_key(self, course_key_string: str) -> CourseKey: """ Validate and parse a course_key string, if supported