feat: Create DRF for course team (#32782)
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
Serializers for v1 contentstore API.
|
||||
"""
|
||||
from .course_details import CourseDetailsSerializer
|
||||
from .course_team import CourseTeamSerializer
|
||||
from .grading import CourseGradingModelSerializer, CourseGradingSerializer
|
||||
from .proctoring import (
|
||||
LimitedProctoredExamSettingsSerializer,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
"""
|
||||
API Serializers for course team
|
||||
"""
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
class UserCourseTeamSerializer(serializers.Serializer):
|
||||
"""Serializer for user in course team"""
|
||||
email = serializers.CharField()
|
||||
id = serializers.IntegerField()
|
||||
role = serializers.CharField()
|
||||
username = serializers.CharField()
|
||||
|
||||
|
||||
class CourseTeamSerializer(serializers.Serializer):
|
||||
"""Serializer for course team context data"""
|
||||
show_transfer_ownership_hint = serializers.BooleanField()
|
||||
users = UserCourseTeamSerializer(many=True)
|
||||
allow_actions = serializers.BooleanField()
|
||||
@@ -8,6 +8,7 @@ from openedx.core.constants import COURSE_ID_PATTERN
|
||||
|
||||
from .views import (
|
||||
CourseDetailsView,
|
||||
CourseTeamView,
|
||||
CourseGradingView,
|
||||
CourseSettingsView,
|
||||
ProctoredExamSettingsView,
|
||||
@@ -43,6 +44,11 @@ urlpatterns = [
|
||||
CourseDetailsView.as_view(),
|
||||
name="course_details"
|
||||
),
|
||||
re_path(
|
||||
fr'^course_team/{COURSE_ID_PATTERN}$',
|
||||
CourseTeamView.as_view(),
|
||||
name="course_team"
|
||||
),
|
||||
re_path(
|
||||
fr'^course_grading/{COURSE_ID_PATTERN}$',
|
||||
CourseGradingView.as_view(),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
Views for v1 contentstore API.
|
||||
"""
|
||||
from .course_details import CourseDetailsView
|
||||
from .course_team import CourseTeamView
|
||||
from .grading import CourseGradingView
|
||||
from .proctoring import ProctoredExamSettingsView, ProctoringErrorsView
|
||||
from .settings import CourseSettingsView
|
||||
|
||||
74
cms/djangoapps/contentstore/rest_api/v1/views/course_team.py
Normal file
74
cms/djangoapps/contentstore/rest_api/v1/views/course_team.py
Normal file
@@ -0,0 +1,74 @@
|
||||
""" API Views for course team """
|
||||
|
||||
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_course_team
|
||||
from common.djangoapps.student.auth import STUDIO_VIEW_USERS, get_user_permissions
|
||||
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, verify_course_exists, view_auth_classes
|
||||
|
||||
from ..serializers import CourseTeamSerializer
|
||||
|
||||
|
||||
@view_auth_classes(is_authenticated=True)
|
||||
class CourseTeamView(DeveloperErrorViewMixin, APIView):
|
||||
"""
|
||||
View for getting data for course team.
|
||||
"""
|
||||
@apidocs.schema(
|
||||
parameters=[
|
||||
apidocs.string_parameter("course_id", apidocs.ParameterLocation.PATH, description="Course ID"),
|
||||
],
|
||||
responses={
|
||||
200: CourseTeamSerializer,
|
||||
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 all CMS users who are editors for the specified course.
|
||||
|
||||
**Example Request**
|
||||
|
||||
GET /api/contentstore/v1/course_team/{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 team info.
|
||||
|
||||
**Example Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"show_transfer_ownership_hint": true,
|
||||
"users": [
|
||||
{
|
||||
"email": "edx@example.com",
|
||||
"id": "3",
|
||||
"role": "instructor",
|
||||
"username": "edx"
|
||||
},
|
||||
],
|
||||
"allow_actions": true
|
||||
}
|
||||
```
|
||||
"""
|
||||
user = request.user
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
|
||||
user_perms = get_user_permissions(user, course_key)
|
||||
if not user_perms & STUDIO_VIEW_USERS:
|
||||
self.permission_denied(request)
|
||||
|
||||
course_team_context = get_course_team(user, course_key, user_perms)
|
||||
serializer = CourseTeamSerializer(course_team_context)
|
||||
return Response(serializer.data)
|
||||
@@ -0,0 +1,78 @@
|
||||
"""
|
||||
Unit tests for course team.
|
||||
"""
|
||||
import ddt
|
||||
from django.urls import reverse
|
||||
from rest_framework import status
|
||||
|
||||
from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from cms.djangoapps.contentstore.tests.utils import CourseTestCase
|
||||
|
||||
from ...mixins import PermissionAccessMixin
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class CourseTeamViewTest(CourseTestCase, PermissionAccessMixin):
|
||||
"""
|
||||
Tests for CourseTeamView.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.url = reverse(
|
||||
"cms.djangoapps.contentstore:v1:course_team",
|
||||
kwargs={"course_id": self.course.id},
|
||||
)
|
||||
|
||||
def get_expected_course_data(self, instructor=None, staff=None):
|
||||
"""Utils is used to get expected data for course team"""
|
||||
users = []
|
||||
|
||||
if instructor:
|
||||
users.append({
|
||||
"email": instructor.email,
|
||||
"id": instructor.id,
|
||||
"role": "instructor",
|
||||
"username": instructor.username
|
||||
})
|
||||
|
||||
if staff:
|
||||
users.append({
|
||||
"email": staff.email,
|
||||
"id": staff.id,
|
||||
"role": "staff",
|
||||
"username": staff.username
|
||||
})
|
||||
|
||||
return {
|
||||
"show_transfer_ownership_hint": False,
|
||||
"users": users,
|
||||
"allow_actions": True,
|
||||
}
|
||||
|
||||
def create_course_user_roles(self, course_id):
|
||||
"""Get course staff and instructor roles user"""
|
||||
instructor = UserFactory()
|
||||
CourseInstructorRole(course_id).add_users(instructor)
|
||||
staff = UserFactory()
|
||||
CourseStaffRole(course_id).add_users(staff)
|
||||
|
||||
return instructor, staff
|
||||
|
||||
def test_course_team_response(self):
|
||||
"""Check successful response content"""
|
||||
response = self.client.get(self.url)
|
||||
expected_response = self.get_expected_course_data()
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertDictEqual(expected_response, response.data)
|
||||
|
||||
def test_users_response(self):
|
||||
"""Test the response for users in the course."""
|
||||
instructor, staff = self.create_course_user_roles(self.course.id)
|
||||
response = self.client.get(self.url)
|
||||
users_response = [dict(item) for item in response.data["users"]]
|
||||
expected_response = self.get_expected_course_data(instructor, staff)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertListEqual(expected_response["users"], users_response)
|
||||
@@ -26,7 +26,7 @@ from cms.djangoapps.contentstore.toggles import exam_setting_view_enabled
|
||||
from common.djangoapps.course_modes.models import CourseMode
|
||||
from common.djangoapps.edxmako.services import MakoService
|
||||
from common.djangoapps.student import auth
|
||||
from common.djangoapps.student.auth import has_studio_read_access, has_studio_write_access
|
||||
from common.djangoapps.student.auth import has_studio_read_access, has_studio_write_access, STUDIO_EDIT_ROLES
|
||||
from common.djangoapps.student.models import CourseEnrollment
|
||||
from common.djangoapps.student.roles import (
|
||||
CourseInstructorRole,
|
||||
@@ -1323,6 +1323,35 @@ def get_course_settings(request, course_key, course_block):
|
||||
return settings_context
|
||||
|
||||
|
||||
def get_course_team(auth_user, course_key, user_perms):
|
||||
"""
|
||||
Utils is used to get context of all CMS users who are editors for the specified course.
|
||||
It is used for both DRF and django views.
|
||||
"""
|
||||
|
||||
from cms.djangoapps.contentstore.views.user import user_with_role
|
||||
|
||||
course_block = modulestore().get_course(course_key)
|
||||
instructors = set(CourseInstructorRole(course_key).users_with_role())
|
||||
# the page only lists staff and assumes they're a superset of instructors. Do a union to ensure.
|
||||
staff = set(CourseStaffRole(course_key).users_with_role()).union(instructors)
|
||||
|
||||
formatted_users = []
|
||||
for user in instructors:
|
||||
formatted_users.append(user_with_role(user, 'instructor'))
|
||||
for user in staff - instructors:
|
||||
formatted_users.append(user_with_role(user, 'staff'))
|
||||
|
||||
course_team_context = {
|
||||
'context_course': course_block,
|
||||
'show_transfer_ownership_hint': auth_user in instructors and len(instructors) == 1,
|
||||
'users': formatted_users,
|
||||
'allow_actions': bool(user_perms & STUDIO_EDIT_ROLES),
|
||||
}
|
||||
|
||||
return course_team_context
|
||||
|
||||
|
||||
def get_course_grading(course_key):
|
||||
"""
|
||||
Utils is used to get context of course grading.
|
||||
|
||||
@@ -19,10 +19,9 @@ from common.djangoapps.student.auth import STUDIO_EDIT_ROLES, STUDIO_VIEW_USERS,
|
||||
from common.djangoapps.student.models import CourseEnrollment
|
||||
from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole, LibraryUserRole
|
||||
from common.djangoapps.util.json_request import JsonResponse, expect_json
|
||||
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
from ..toggles import use_new_course_team_page
|
||||
from ..utils import get_course_team_url
|
||||
from ..utils import get_course_team_url, get_course_team
|
||||
|
||||
__all__ = ['request_course_creator', 'course_team_handler']
|
||||
|
||||
@@ -85,23 +84,8 @@ def _manage_users(request, course_key):
|
||||
if not user_perms & STUDIO_VIEW_USERS:
|
||||
raise PermissionDenied()
|
||||
|
||||
course_block = modulestore().get_course(course_key)
|
||||
instructors = set(CourseInstructorRole(course_key).users_with_role())
|
||||
# the page only lists staff and assumes they're a superset of instructors. Do a union to ensure.
|
||||
staff = set(CourseStaffRole(course_key).users_with_role()).union(instructors)
|
||||
|
||||
formatted_users = []
|
||||
for user in instructors:
|
||||
formatted_users.append(user_with_role(user, 'instructor'))
|
||||
for user in staff - instructors:
|
||||
formatted_users.append(user_with_role(user, 'staff'))
|
||||
|
||||
return render_to_response('manage_users.html', {
|
||||
'context_course': course_block,
|
||||
'show_transfer_ownership_hint': request.user in instructors and len(instructors) == 1,
|
||||
'users': formatted_users,
|
||||
'allow_actions': bool(user_perms & STUDIO_EDIT_ROLES),
|
||||
})
|
||||
manage_users_context = get_course_team(request.user, course_key, user_perms)
|
||||
return render_to_response('manage_users.html', manage_users_context)
|
||||
|
||||
|
||||
@expect_json
|
||||
|
||||
Reference in New Issue
Block a user