feat: Course Apps API [BD-38] [TNL-8103] [BB-2716] (#27542)
* feat: Course Apps API This adds a new concept called course apps. These are exposed via a new "openedx.course_app" entrypoint, which helps the LMS and studio discover such apps and list them in a new rest api for the same. These course apps will drive the pages and resources view in the course authoring MFE. This system will track which apps are enabled and which are disabled. It also allows third-party apps to be listed here by using the plugin entrypoint. * Apply feedback from review
This commit is contained in:
61
openedx/core/djangoapps/discussions/plugins.py
Normal file
61
openedx/core/djangoapps/discussions/plugins.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""
|
||||
Course app configuration for discussions.
|
||||
"""
|
||||
from typing import Dict, Optional
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.utils.translation import ugettext_noop as _
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
from openedx.core.djangoapps.course_apps.plugins import CourseApp
|
||||
from .models import DiscussionsConfiguration
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class DiscussionCourseApp(CourseApp):
|
||||
"""
|
||||
Course App config for Discussions.
|
||||
"""
|
||||
|
||||
app_id = "discussion"
|
||||
name = _("Discussion")
|
||||
description = _("Encourage participation and engagement in your course with discussion forums.")
|
||||
|
||||
@classmethod
|
||||
def is_available(cls, course_key: CourseKey) -> bool:
|
||||
"""
|
||||
Discussions is always available.
|
||||
"""
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def is_enabled(cls, course_key: CourseKey) -> bool:
|
||||
"""
|
||||
Discussions enable/disable status is stored in a separate model.
|
||||
"""
|
||||
return DiscussionsConfiguration.is_enabled(course_key)
|
||||
|
||||
@classmethod
|
||||
def set_enabled(cls, course_key: CourseKey, enabled: bool, user: 'User') -> bool:
|
||||
"""
|
||||
Set discussion enabled status in DiscussionsConfiguration model.
|
||||
"""
|
||||
configuration = DiscussionsConfiguration.get(course_key)
|
||||
if configuration.pk is None:
|
||||
raise ValueError("Can't enable/disable discussions for course before they are configured.")
|
||||
configuration.enabled = enabled
|
||||
configuration.save()
|
||||
return configuration.enabled
|
||||
|
||||
@classmethod
|
||||
def get_allowed_operations(cls, course_key: CourseKey, user: Optional[User] = None) -> Dict[str, bool]:
|
||||
"""
|
||||
Return allowed operations for discussions app.
|
||||
"""
|
||||
# Can only enable discussions for a course if discussions are configured.
|
||||
can_enable = DiscussionsConfiguration.get(course_key).pk is not None
|
||||
return {
|
||||
"enable": can_enable,
|
||||
"configure": True,
|
||||
}
|
||||
@@ -3,16 +3,13 @@ Handle view-logic for the djangoapp
|
||||
"""
|
||||
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
|
||||
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from rest_framework import serializers
|
||||
from rest_framework.permissions import BasePermission
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from common.djangoapps.student.roles import CourseStaffRole
|
||||
from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser
|
||||
|
||||
from openedx.core.lib.api.view_utils import validate_course_key
|
||||
from .models import DiscussionsConfiguration
|
||||
from .serializers import DiscussionsConfigurationSerializer
|
||||
|
||||
@@ -33,7 +30,7 @@ class IsStaff(BasePermission):
|
||||
if user.is_staff:
|
||||
return True
|
||||
course_key_string = view.kwargs.get('course_key_string')
|
||||
course_key = _validate_course_key(course_key_string)
|
||||
course_key = validate_course_key(course_key_string)
|
||||
return CourseStaffRole(
|
||||
course_key,
|
||||
).has_user(request.user)
|
||||
@@ -55,7 +52,7 @@ class DiscussionsConfigurationView(APIView):
|
||||
"""
|
||||
Handle HTTP/GET requests
|
||||
"""
|
||||
course_key = _validate_course_key(course_key_string)
|
||||
course_key = validate_course_key(course_key_string)
|
||||
configuration = DiscussionsConfiguration.get(course_key)
|
||||
serializer = DiscussionsConfigurationSerializer(configuration)
|
||||
return Response(serializer.data)
|
||||
@@ -64,7 +61,7 @@ class DiscussionsConfigurationView(APIView):
|
||||
"""
|
||||
Handle HTTP/POST requests
|
||||
"""
|
||||
course_key = _validate_course_key(course_key_string)
|
||||
course_key = validate_course_key(course_key_string)
|
||||
configuration = DiscussionsConfiguration.get(course_key)
|
||||
serializer = DiscussionsConfigurationSerializer(
|
||||
configuration,
|
||||
@@ -77,20 +74,3 @@ class DiscussionsConfigurationView(APIView):
|
||||
if serializer.is_valid(raise_exception=True):
|
||||
serializer.save()
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
def _validate_course_key(course_key_string: str) -> CourseKey:
|
||||
"""
|
||||
Validate and parse a course_key string, if supported
|
||||
"""
|
||||
try:
|
||||
course_key = CourseKey.from_string(course_key_string)
|
||||
except InvalidKeyError as error:
|
||||
raise serializers.ValidationError(
|
||||
f"{course_key_string} is not a valid CourseKey"
|
||||
) from error
|
||||
if course_key.deprecated:
|
||||
raise serializers.ValidationError(
|
||||
'Deprecated CourseKeys (Org/Course/Run) are not supported.'
|
||||
)
|
||||
return course_key
|
||||
|
||||
Reference in New Issue
Block a user