Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: serializers for validation #68

Merged
merged 1 commit into from
Mar 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions backend/api/models/course.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from datetime import datetime
from typing import Self
from django.db import models

Expand Down Expand Up @@ -31,6 +32,10 @@ def __str__(self) -> str:
"""The string representation of the course."""
return str(self.name)

def is_past(self) -> bool:
"""Returns whether the course is from a past academic year"""
return datetime(self.academic_startyear + 1, 10, 1) < datetime.now()

def clone(self, clone_assistants=True) -> Self:
"""Clone the course to the next academic start year"""
course = Course(
Expand Down
6 changes: 6 additions & 0 deletions backend/api/serializers/assistant_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,9 @@ class Meta:
"create_time",
"courses",
]


class AssistantIDSerializer(serializers.Serializer):
assistant_id = serializers.PrimaryKeyRelatedField(
queryset=Assistant.objects.all()
)
49 changes: 48 additions & 1 deletion backend/api/serializers/course_serializer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from django.utils.translation import gettext
from rest_framework import serializers
from ..models.course import Course
from rest_framework.exceptions import ValidationError
from api.serializers.student_serializer import StudentIDSerializer
from api.models.course import Course


class CourseSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -40,3 +43,47 @@ class Meta:
"students",
"projects",
]


class CourseIDSerializer(serializers.Serializer):
student_id = serializers.PrimaryKeyRelatedField(
queryset=Course.objects.all()
)


class StudentJoinSerializer(StudentIDSerializer):
def validate(self, data):
# The validator needs the course context.
if "course" not in self.context:
raise ValidationError(gettext("courses.error.context"))

course: Course = self.context["course"]

# Check if the student isn't already enrolled.
if course.students.contains(data["student_id"]):
raise ValidationError(gettext("courses.error.students.already_present"))

# Check if the course is not from a past academic year.
if course.is_past():
raise ValidationError(gettext("courses.error.students.past_course"))

return data


class StudentLeaveSerializer(StudentIDSerializer):
def validate(self, data):
# The validator needs the course context.
if "course" not in self.context:
raise ValidationError(gettext("courses.error.context"))

course: Course = self.context["course"]

# Check if the student isn't already enrolled.
if not course.students.contains(data["student_id"]):
raise ValidationError(gettext("courses.error.students.not_present"))

# Check if the course is not from a past academic year.
if course.is_past():
raise ValidationError(gettext("courses.error.students.past_course"))

return data
6 changes: 6 additions & 0 deletions backend/api/serializers/student_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,9 @@ class StudentSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = '__all__'


class StudentIDSerializer(serializers.Serializer):
student_id = serializers.PrimaryKeyRelatedField(
queryset=Student.objects.all()
)
107 changes: 55 additions & 52 deletions backend/api/views/course_view.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
from django.utils.translation import gettext
from rest_framework import viewsets
from rest_framework.exceptions import NotFound
from rest_framework.permissions import IsAdminUser
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.request import Request
from api.models.course import Course
from api.models.assistant import Assistant
from api.models.student import Student
from api.permissions.course_permissions import CoursePermission, CourseAssistantPermission, CourseStudentPermission
from api.permissions.course_permissions import (
CoursePermission,
CourseAssistantPermission,
CourseStudentPermission
)
from api.permissions.role_permissions import IsTeacher
from api.serializers.course_serializer import CourseSerializer
from api.serializers.course_serializer import CourseSerializer, StudentJoinSerializer, StudentLeaveSerializer
from api.serializers.teacher_serializer import TeacherSerializer
from api.serializers.assistant_serializer import AssistantSerializer
from api.serializers.assistant_serializer import AssistantSerializer, AssistantIDSerializer
from api.serializers.student_serializer import StudentSerializer
from api.serializers.project_serializer import ProjectSerializer

Expand Down Expand Up @@ -42,40 +43,38 @@ def _add_assistant(self, request: Request, **_):
"""Add an assistant to the course"""
course = self.get_object()

try:
# Add assistant to course
assistant = Assistant.objects.get(
id=request.data.get("id")
)
# Add assistant to course
serializer = AssistantIDSerializer(
data=request.data
)

course.assistants.add(assistant)
if serializer.is_valid(raise_exception=True):
course.assistants.add(
serializer.validated_data["assistant_id"]
)

return Response({
"message": gettext("courses.success.assistants.add")
})
except Assistant.DoesNotExist:
# Not found
raise NotFound(gettext("assistants.error.404"))
return Response({
"message": gettext("courses.success.assistants.add")
})

@assistants.mapping.delete
def _remove_assistant(self, request: Request, **_):
"""Remove an assistant from the course"""
course = self.get_object()

try:
# Add assistant to course
assistant = Assistant.objects.get(
id=request.data.get("id")
)
# Remove assistant from course
serializer = AssistantIDSerializer(
data=request.data
)

course.assistants.remove(assistant)
if serializer.is_valid(raise_exception=True):
course.assistants.remove(
serializer.validated_data["assistant_id"]
)

return Response({
"message": gettext("courses.success.assistants.delete")
})
except Assistant.DoesNotExist:
# Not found
raise NotFound(gettext("assistants.error.404"))
return Response({
"message": gettext("courses.success.assistants.add")
})

@action(detail=True, methods=["get"], permission_classes=[IsAdminUser | CourseStudentPermission])
def students(self, request, **_):
Expand All @@ -94,40 +93,42 @@ def students(self, request, **_):
@students.mapping.put
def _add_student(self, request: Request, **_):
"""Add a student to the course"""
# Get the course
course = self.get_object()

try:
# Add student to course
student = Student.objects.get(
id=request.data.get("id")
)
# Add student to course
serializer = StudentJoinSerializer(data=request.data, context={
"course": course
})

course.students.add(student)
if serializer.is_valid(raise_exception=True):
course.students.add(
serializer.validated_data["student_id"]
)

return Response({
"message": gettext("courses.success.students.add")
})
except Student.DoesNotExist:
raise NotFound(gettext("students.error.404"))
return Response({
"message": gettext("courses.success.students.add")
})

@students.mapping.delete
def _remove_student(self, request: Request, **_):
"""Remove a student from the course"""
# Get the course
course = self.get_object()

try:
# Add student to course
student = Student.objects.get(
id=request.data.get("id")
)
# Add student to course
serializer = StudentLeaveSerializer(data=request.data, context={
"course": course
})

course.students.remove(student)
if serializer.is_valid(raise_exception=True):
course.students.remove(
serializer.validated_data["student_id"]
)

return Response({
"message": gettext("courses.success.students.remove")
})
except Student.DoesNotExist:
raise NotFound(gettext("students.error.404"))
return Response({
"message": gettext("courses.success.students.add")
})

@action(detail=True, methods=["get"])
def teachers(self, request, **_):
Expand Down Expand Up @@ -161,8 +162,10 @@ def clone(self, request: Request, **__):
course: Course = self.get_object()

try:
# We should return the already cloned course, if present
course = course.child_course
except Course.DoesNotExist:
# Else, we clone the course
course = course.clone(
clone_assistants=request.data.get("clone_assistants")
)
Expand Down
Loading