diff --git a/app/serializers/course_member.py b/app/serializers/course_member.py index 962b7f0..bb962a1 100644 --- a/app/serializers/course_member.py +++ b/app/serializers/course_member.py @@ -5,6 +5,7 @@ from app.serializers.attribute import AttributeResponseSerializer from app.serializers.relationship import RelationshipSerializer from app.serializers.section import SectionSerializer +from app.models.section import Section class CourseMemberSerializer(serializers.ModelSerializer): @@ -21,3 +22,25 @@ class CourseMemberSerializer(serializers.ModelSerializer): class Meta: model = CourseMember fields = "__all__" + + +class UpdateStudentSectionsRequest(serializers.Serializer): + sections = serializers.ListField( + child=serializers.IntegerField(), + required=True, + error_messages={ + "required": "Sections is required.", + }, + ) + + def validate_sections(self, value): + course = self.context.get("course") + sections_passed_in = set(value) + course_sections_ids = Section.objects.filter( + course=course, id__in=sections_passed_in + ) + if len(sections_passed_in) != course_sections_ids.count(): + raise serializers.ValidationError( + "One or more sections do not exist or are not part of the course." + ) + return value diff --git a/app/views/course_member.py b/app/views/course_member.py index b0d890f..8be2c34 100644 --- a/app/views/course_member.py +++ b/app/views/course_member.py @@ -1,11 +1,21 @@ from rest_framework import viewsets -from app.models.course_member import CourseMember +from app.models.course_member import CourseMember, UserRole from app.paginators.pagination import ExamplePagination +from app.models.section import Section from app.filters.course_member import FilterStudents -from app.serializers.course_member import CourseMemberSerializer +from app.serializers.course_member import ( + CourseMemberSerializer, + UpdateStudentSectionsRequest, +) from rest_framework.response import Response +from rest_framework.decorators import action +from rest_framework import status + +from django.db import transaction +from django.core.exceptions import ValidationError +from rest_framework.exceptions import APIException class CourseMemberViewSet(viewsets.ModelViewSet): @@ -30,3 +40,50 @@ def get_students_by_course(self, request, course=None, *args, **kwargs): else: serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) + + @action(detail=True, methods=["put"], url_path="update-sections") + @transaction.atomic + def update_sections(self, request, *args, **kwargs): + try: + course_member = self.get_object() + + if course_member.role != UserRole.STUDENT: + return Response( + { + "errors": { + "message": "Only students can be assigned to sections." + } + }, + status=status.HTTP_400_BAD_REQUEST, + ) + + request_body_serializer = UpdateStudentSectionsRequest( + instance=course_member, + data=request.data, + context={"course": course_member.course}, + ) + if request_body_serializer.is_valid(raise_exception=True): + section_ids = request_body_serializer._validated_data.get("sections") + proposed_sections_update = Section.objects.filter( + id__in=section_ids, course=course_member.course + ) + course_member.sections.set(proposed_sections_update) + course_member.save() + response_course_member_serializer = self.get_serializer(course_member) + return Response(response_course_member_serializer.data) + + except Exception as exc: + errors = [] + if isinstance(exc, APIException): + if isinstance(exc.detail, dict): + for field, error_list in exc.detail.items(): + for error in error_list: + errors.append({"message": str(error)}) + else: + errors.append({"message": exc.detail}) + if isinstance(exc, ValidationError): + for field, error_list in exc.detail.items(): + for error in error_list: + errors.append({"message": str(error)}) + response = Response({"errors": errors}, status=status.HTTP_400_BAD_REQUEST) + return response