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:
Kshitij Sobti
2021-06-23 22:21:12 +05:30
committed by GitHub
parent 29fcf8b868
commit d2c2fcdefe
32 changed files with 1157 additions and 118 deletions

View 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,
}

View File

@@ -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