Files
Ahtisham Shahid c080f96da2 fix: Updates task in topics sync API (#36853)
fix: Updates task in topics sync API
2025-06-04 08:30:48 +00:00

279 lines
10 KiB
Python

"""
Handle view-logic for the discussions app.
"""
from typing import Dict
import edx_api_doc_tools as apidocs
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser
from rest_framework.exceptions import ValidationError
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser
from openedx.core.lib.api.view_utils import validate_course_key
from .config.waffle import ENABLE_NEW_STRUCTURE_DISCUSSIONS
from .models import AVAILABLE_PROVIDER_MAP, DiscussionsConfiguration, Features, Provider
from .permissions import IsStaffOrCourseTeam, check_course_permissions
from .serializers import DiscussionsConfigurationSerializer, DiscussionsProvidersSerializer
from .tasks import update_discussions_settings_from_course_task
class DiscussionsConfigurationSettingsView(APIView):
"""
View for configuring discussion settings.
"""
authentication_classes = (
JwtAuthentication,
BearerAuthenticationAllowInactiveUser,
SessionAuthenticationAllowInactiveUser
)
permission_classes = (IsStaffOrCourseTeam,)
@apidocs.schema(
parameters=[
apidocs.string_parameter(
'course_id',
apidocs.ParameterLocation.PATH,
description="The course for which to get provider list",
),
apidocs.string_parameter(
'provider_id',
apidocs.ParameterLocation.QUERY,
description="The provider_id to fetch data for"
)
],
responses={
200: DiscussionsConfigurationSerializer,
400: "Invalid provider ID",
401: "The requester is not authenticated.",
403: "The requester cannot access the specified course.",
404: "The requested course does not exist.",
},
)
def get(self, request: Request, course_key_string: str, **_kwargs) -> Response:
"""
Handle HTTP/GET requests
"""
data = self.get_configuration_data(request, course_key_string)
return Response(data)
@staticmethod
def get_configuration_data(request: Request, course_key_string: str) -> Dict:
"""
Get discussions configuration data for the course
Args:
request (Request): a DRF request
course_key_string (str): a course key string
Returns:
Dict: Discussion configuration data for the course
"""
course_key = validate_course_key(course_key_string)
configuration = DiscussionsConfiguration.get(course_key)
provider_type = request.query_params.get('provider_id', None)
if provider_type and provider_type not in AVAILABLE_PROVIDER_MAP:
raise ValidationError("Unsupported provider type")
serializer = DiscussionsConfigurationSerializer(
configuration,
context={
'user_id': request.user.id,
'provider_type': provider_type,
}
)
return serializer.data
def post(self, request, course_key_string: str, **_kwargs) -> Response:
"""
Handle HTTP/POST requests
"""
data = self.update_configuration_data(request, course_key_string)
return Response(data)
@staticmethod
def update_configuration_data(request, course_key_string):
"""
Update discussion configuration for the course based on data in the request.
Args:
request (Request): a DRF request
course_key_string (str): a course key string
Returns:
Dict: modified course configuration data
"""
course_key = validate_course_key(course_key_string)
configuration = DiscussionsConfiguration.get(course_key)
course = CourseOverview.get_from_id(course_key)
serializer = DiscussionsConfigurationSerializer(
configuration,
context={
'user_id': request.user.id,
},
data=request.data,
partial=True,
)
if serializer.is_valid(raise_exception=True):
new_provider_type = serializer.validated_data.get('provider_type', None)
if new_provider_type is not None and new_provider_type != configuration.provider_type:
check_course_permissions(course, request.user, 'change_provider')
serializer.save()
return serializer.data
class DiscussionsProvidersView(APIView):
"""
Read only view that lists details of discussion providers available for a course.
"""
authentication_classes = (
JwtAuthentication,
BearerAuthenticationAllowInactiveUser,
SessionAuthenticationAllowInactiveUser
)
permission_classes = (IsStaffOrCourseTeam,)
@apidocs.schema(
parameters=[
apidocs.string_parameter(
'course_id',
apidocs.ParameterLocation.PATH,
description="The course for which to get provider list",
)
],
responses={
200: DiscussionsProvidersSerializer,
401: "The requester is not authenticated.",
403: "The requester cannot access the specified course.",
404: "The requested course does not exist.",
},
)
def get(self, request, course_key_string: str, **_kwargs) -> Response:
"""
Handle HTTP/GET requests
"""
# Return all providers always, if the user is staff
data = self.get_provider_data(course_key_string, show_all=False)
return Response(data)
@staticmethod
def get_provider_data(course_key_string: str, show_all: bool = False) -> Dict:
"""
Get provider data for specified course
Args:
course_key_string (str): course key string
show_all (bool): don't hide any providers
Returns:
Dict: course discussion providers
"""
course_key = validate_course_key(course_key_string)
configuration = DiscussionsConfiguration.get(course_key)
hidden_providers = []
if not show_all:
# If the new style discussions are enabled, then hide the legacy provider unless it's already in use.
if ENABLE_NEW_STRUCTURE_DISCUSSIONS.is_enabled(course_key):
if configuration.provider_type != Provider.LEGACY:
hidden_providers.append(Provider.LEGACY)
# If new discussions is not enabled, hide the new provider
else:
if configuration.provider_type != Provider.OPEN_EDX:
hidden_providers.append(Provider.OPEN_EDX)
else:
# if new discussions is not enabled, hide the new provider in case it is not already in use
if not ENABLE_NEW_STRUCTURE_DISCUSSIONS.is_enabled(course_key):
if configuration.provider_type != Provider.OPEN_EDX:
hidden_providers.append(Provider.OPEN_EDX)
if configuration.provider_type != Provider.LEGACY:
hidden_providers.append(Provider.LEGACY)
else:
hidden_providers.append(Provider.OPEN_EDX)
serializer = DiscussionsProvidersSerializer(
{
'features': [
{'id': feature.value, 'feature_support_type': feature.feature_support_type}
for feature in Features
],
'active': configuration.provider_type,
'available': {
key: value
for key, value in AVAILABLE_PROVIDER_MAP.items()
if key not in hidden_providers
},
}
)
return serializer.data
class CombinedDiscussionsConfigurationView(DiscussionsConfigurationSettingsView):
"""
Combined view that includes both provider data and discussion configuration.
Note:
This is temporary code for backwards-compatibility and will be removed soon
after the frontend supports the new split APIs.
"""
def get(self, request: Request, course_key_string: str, **_kwargs) -> Response:
"""
Handle HTTP/GET requests
"""
config_data = self.get_configuration_data(request, course_key_string)
provider_data = DiscussionsProvidersView.get_provider_data(course_key_string, show_all=request.user.is_staff)
return Response({
**config_data,
"features": provider_data["features"],
"providers": {
"active": provider_data["active"],
"available": provider_data["available"],
},
})
def post(self, request, course_key_string: str, **_kwargs) -> Response:
"""
Handle HTTP/POST requests
"""
config_data = self.update_configuration_data(request, course_key_string)
provider_data = DiscussionsProvidersView.get_provider_data(course_key_string, request.user.is_staff)
return Response(
{
**config_data,
"features": provider_data["features"],
"providers": {
"active": provider_data["active"],
"available": provider_data["available"],
},
}
)
class SyncDiscussionTopicsView(APIView):
"""
View for syncing discussion topics for a course.
"""
authentication_classes = (BearerAuthenticationAllowInactiveUser, SessionAuthenticationAllowInactiveUser)
permission_classes = (IsAuthenticated, IsStaffOrCourseTeam)
def post(self, request, course_key_string):
"""
Sync discussion topics for the course based on data in the request.
Args:
request (Request): a DRF request
course_key_string (str): a course key string
Returns:
Response: modified course configuration data
"""
update_discussions_settings_from_course_task(course_key_string)
return Response({
"status": "success",
"message": "Discussion topics synced successfully."
})