Skip to content

Commit

Permalink
feat: [FC-0044] group configurations API DRF (#34389)
Browse files Browse the repository at this point in the history
  • Loading branch information
ruzniaievdm authored Mar 25, 2024
1 parent f663739 commit 6d13b77
Show file tree
Hide file tree
Showing 8 changed files with 373 additions and 76 deletions.
11 changes: 6 additions & 5 deletions cms/djangoapps/contentstore/rest_api/v1/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,25 @@
"""
from .certificates import CourseCertificatesSerializer
from .course_details import CourseDetailsSerializer
from .course_index import CourseIndexSerializer
from .course_rerun import CourseRerunSerializer
from .course_team import CourseTeamSerializer
from .course_index import CourseIndexSerializer
from .grading import CourseGradingModelSerializer, CourseGradingSerializer
from .group_configurations import CourseGroupConfigurationsSerializer
from .home import CourseHomeSerializer, CourseHomeTabSerializer, LibraryTabSerializer
from .proctoring import (
LimitedProctoredExamSettingsSerializer,
ProctoredExamConfigurationSerializer,
ProctoredExamSettingsSerializer,
ProctoringErrorsSerializer
ProctoringErrorsSerializer,
)
from .settings import CourseSettingsSerializer
from .textbooks import CourseTextbooksSerializer
from .vertical_block import ContainerHandlerSerializer, VerticalContainerSerializer
from .videos import (
CourseVideosSerializer,
VideoUploadSerializer,
VideoDownloadSerializer,
VideoImageSerializer,
VideoUploadSerializer,
VideoUsageSerializer,
VideoDownloadSerializer
)
from .vertical_block import ContainerHandlerSerializer, VerticalContainerSerializer
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""
API Serializers for course's settings group configurations.
"""

from rest_framework import serializers


class GroupConfigurationUsageSerializer(serializers.Serializer):
"""
Serializer for representing nested usage inside configuration.
"""

label = serializers.CharField()
url = serializers.CharField()
validation = serializers.DictField(required=False)


class GroupConfigurationGroupSerializer(serializers.Serializer):
"""
Serializer for representing nested group inside configuration.
"""

id = serializers.IntegerField()
name = serializers.CharField()
usage = GroupConfigurationUsageSerializer(required=False, allow_null=True, many=True)
version = serializers.IntegerField()


class GroupConfigurationItemSerializer(serializers.Serializer):
"""
Serializer for representing group configurations item.
"""

active = serializers.BooleanField()
description = serializers.CharField()
groups = GroupConfigurationGroupSerializer(allow_null=True, many=True)
id = serializers.IntegerField()
usage = GroupConfigurationUsageSerializer(required=False, allow_null=True, many=True)
name = serializers.CharField()
parameters = serializers.DictField()
read_only = serializers.BooleanField(required=False)
scheme = serializers.CharField()
version = serializers.IntegerField()


class CourseGroupConfigurationsSerializer(serializers.Serializer):
"""
Serializer for representing course's settings group configurations.
"""

all_group_configurations = GroupConfigurationItemSerializer(many=True)
experiment_group_configurations = GroupConfigurationItemSerializer(
allow_null=True, many=True
)
mfe_proctored_exam_settings_url = serializers.CharField(
required=False, allow_null=True, allow_blank=True
)
should_show_enrollment_track = serializers.BooleanField()
should_show_experiment_groups = serializers.BooleanField()
6 changes: 6 additions & 0 deletions cms/djangoapps/contentstore/rest_api/v1/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
CourseTextbooksView,
CourseIndexView,
CourseGradingView,
CourseGroupConfigurationsView,
CourseRerunView,
CourseSettingsView,
CourseVideosView,
Expand Down Expand Up @@ -115,6 +116,11 @@
CourseCertificatesView.as_view(),
name="certificates"
),
re_path(
fr'^group_configurations/{COURSE_ID_PATTERN}$',
CourseGroupConfigurationsView.as_view(),
name="group_configurations"
),
re_path(
fr'^container_handler/{settings.USAGE_KEY_PATTERN}$',
ContainerHandlerView.as_view(),
Expand Down
11 changes: 6 additions & 5 deletions cms/djangoapps/contentstore/rest_api/v1/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@
from .certificates import CourseCertificatesView
from .course_details import CourseDetailsView
from .course_index import CourseIndexView
from .course_team import CourseTeamView
from .course_rerun import CourseRerunView
from .course_team import CourseTeamView
from .grading import CourseGradingView
from .group_configurations import CourseGroupConfigurationsView
from .help_urls import HelpUrlsView
from .home import HomePageCoursesView, HomePageLibrariesView, HomePageView
from .proctoring import ProctoredExamSettingsView, ProctoringErrorsView
from .home import HomePageView, HomePageCoursesView, HomePageLibrariesView
from .settings import CourseSettingsView
from .textbooks import CourseTextbooksView
from .vertical_block import ContainerHandlerView, VerticalContainerView
from .videos import (
CourseVideosView,
VideoDownloadView,
VideoUsageView,
VideoDownloadView
)
from .help_urls import HelpUrlsView
from .vertical_block import ContainerHandlerView, VerticalContainerView
152 changes: 152 additions & 0 deletions cms/djangoapps/contentstore/rest_api/v1/views/group_configurations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
""" API Views for course's settings group configurations """

import edx_api_doc_tools as apidocs
from opaque_keys.edx.keys import CourseKey
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView

from cms.djangoapps.contentstore.utils import get_group_configurations_context
from cms.djangoapps.contentstore.rest_api.v1.serializers import (
CourseGroupConfigurationsSerializer,
)
from common.djangoapps.student.auth import has_studio_read_access
from openedx.core.lib.api.view_utils import (
DeveloperErrorViewMixin,
verify_course_exists,
view_auth_classes,
)
from xmodule.modulestore.django import modulestore


@view_auth_classes(is_authenticated=True)
class CourseGroupConfigurationsView(DeveloperErrorViewMixin, APIView):
"""
View for course's settings group configurations.
"""

@apidocs.schema(
parameters=[
apidocs.string_parameter(
"course_id", apidocs.ParameterLocation.PATH, description="Course ID"
),
],
responses={
200: CourseGroupConfigurationsSerializer,
401: "The requester is not authenticated.",
403: "The requester cannot access the specified course.",
404: "The requested course does not exist.",
},
)
@verify_course_exists()
def get(self, request: Request, course_id: str):
"""
Get an object containing course's settings group configurations.
**Example Request**
GET /api/contentstore/v1/group_configurations/{course_id}
**Response Values**
If the request is successful, an HTTP 200 "OK" response is returned.
The HTTP 200 response contains a single dict that contains keys that
are the course's settings group configurations.
**Example Response**
```json
{
"all_group_configurations": [
{
"active": true,
"description": "Partition for segmenting users by enrollment track",
"groups": [
{
"id": 2,
"name": "Enroll",
"usage": [
{
"label": "Subsection / Unit",
"url": "/container/block-v1:org+101+101+type@vertical+block@08772238547242848cef9"
}
],
"version": 1
}
],
"id": 50,
"usage": null,
"name": "Enrollment Track Groups",
"parameters": {
"course_id": "course-v1:org+101+101"
},
"read_only": true,
"scheme": "enrollment_track",
"version": 3
},
{
"active": true,
"description": "The groups in this configuration can be mapped to cohorts in the Instructor.",
"groups": [
{
"id": 593758473,
"name": "My Content Group",
"usage": [],
"version": 1
}
],
"id": 1791848226,
"name": "Content Groups",
"parameters": {},
"read_only": false,
"scheme": "cohort",
"version": 3
}
],
"experiment_group_configurations": [
{
"active": true,
"description": "desc",
"groups": [
{
"id": 276408623,
"name": "Group A",
"usage": null,
"version": 1
},
...
],
"id": 875961582,
"usage": [
{
"label": "Unit / Content Experiment",
"url": "/container/block-v1:org+101+101+type@split_test+block@90ccbbad0dac48b18c5c80",
"validation": null
},
...
],
"name": "Experiment Group Configurations 5",
"parameters": {},
"scheme": "random",
"version": 3
},
...
],
"mfe_proctored_exam_settings_url": "",
"should_show_enrollment_track": true,
"should_show_experiment_groups": true,
}
```
"""
course_key = CourseKey.from_string(course_id)
store = modulestore()

if not has_studio_read_access(request.user, course_key):
self.permission_denied(request)

with store.bulk_operations(course_key):
course = modulestore().get_course(course_key)
group_configurations_context = get_group_configurations_context(course, store)
serializer = CourseGroupConfigurationsSerializer(group_configurations_context)
return Response(serializer.data)
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""
Unit tests for the course's setting group configuration.
"""
from django.urls import reverse
from rest_framework import status

from cms.djangoapps.contentstore.course_group_config import (
CONTENT_GROUP_CONFIGURATION_NAME,
)
from cms.djangoapps.contentstore.tests.utils import CourseTestCase
from xmodule.partitions.partitions import (
Group,
UserPartition,
) # lint-amnesty, pylint: disable=wrong-import-order

from ...mixins import PermissionAccessMixin


class CourseGroupConfigurationsViewTest(CourseTestCase, PermissionAccessMixin):
"""
Tests for CourseGroupConfigurationsView.
"""

def setUp(self):
super().setUp()
self.url = reverse(
"cms.djangoapps.contentstore:v1:group_configurations",
kwargs={"course_id": self.course.id},
)

def test_success_response(self):
"""
Check that endpoint is valid and success response.
"""
self.course.user_partitions = [
UserPartition(
0,
"First name",
"First description",
[Group(0, "Group A"), Group(1, "Group B"), Group(2, "Group C")],
), # lint-amnesty, pylint: disable=line-too-long
]
self.save_course()

if "split_test" not in self.course.advanced_modules:
self.course.advanced_modules.append("split_test")
self.store.update_item(self.course, self.user.id)

response = self.client.get(self.url)
self.assertEqual(len(response.data["all_group_configurations"]), 1)
self.assertEqual(len(response.data["experiment_group_configurations"]), 1)
self.assertContains(response, "First name", count=1)
self.assertContains(response, "Group C")
self.assertContains(response, CONTENT_GROUP_CONFIGURATION_NAME)
self.assertEqual(response.status_code, status.HTTP_200_OK)
Loading

0 comments on commit 6d13b77

Please sign in to comment.