Files
edx-platform/openedx/core/djangoapps/discussions/views.py
Kshitij Sobti c2326f0399 fix: change conditions for showing new and legacy discussion providers (#30818)
The current logic for showing discussion providers makes it hard to switch
from the legacy to the new provider. This commit changes the conditions in
which different providers are shown, and which provider is used as default.

Before this commit, the new provider would be hidden if the legacy provider
was in use and vice-versa. So both would only be shown if neither legacy
nor the new provider were in use (i.e. an LTI provider was in use).

Now, all providers are always displayed to global staff. If the waffle
flag for the new provider is set
(`discussions.enable_new_structure_discussions`), then new provider is
always displayed, and the legacy provider is hidden unless it's currently
in use.

If flag is not set, then the new provider is always hidden unless it is
used by a course.

Finally, the default provider now depends on the flag above. If it is
set globally, then the default provider is the new provider, otherwise
the legacy provider remains the default provider.
2022-08-25 04:00:18 -07:00

244 lines
8.9 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.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,
)
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=request.user.is_staff)
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)
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"],
},
}
)