Skip to content

Commit

Permalink
Merge branch 'development' of https://github.com/SELab-2/UGent-7 into…
Browse files Browse the repository at this point in the history
… file_structure_checks
  • Loading branch information
tyboro2002 committed Mar 9, 2024
2 parents df77e10 + 270a243 commit 27b9b82
Show file tree
Hide file tree
Showing 10 changed files with 354 additions and 75 deletions.
20 changes: 20 additions & 0 deletions backend/api/models/course.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from datetime import datetime
from typing import Self
from django.db import models


Expand Down Expand Up @@ -30,6 +32,24 @@ 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(
name=self.name,
description=self.description,
academic_startyear=self.academic_startyear + 1,
parent_course=self
)

if clone_assistants:
course.assistants.add(self.assistants)

return course

@property
def academic_year(self) -> str:
"""The academic year of the course."""
Expand Down
Empty file.
83 changes: 83 additions & 0 deletions backend/api/permissions/course_permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from rest_framework.permissions import BasePermission, SAFE_METHODS
from rest_framework.request import Request
from rest_framework.viewsets import ViewSet
from authentication.models import User
from api.permissions.role_permissions import is_student, is_assistant, is_teacher
from api.models.course import Course


class CoursePermission(BasePermission):
"""Permission class used as default policy for course endpoints."""

def has_permission(self, request: Request, view: ViewSet) -> bool:
"""Check if user has permission to view a general course endpoint."""
user: User = request.user

# Logged-in users can fetch course information.
if request.method in SAFE_METHODS:
return request.user.is_authenticated

# Only teachers can create courses.
return is_teacher(user)

def has_object_permission(self, request: Request, view: ViewSet, course: Course) -> bool:
"""Check if user has permission to view a detailed course endpoint"""
user: User = request.user

# Logged-in users can fetch course details.
if request.method in SAFE_METHODS:
return user.is_authenticated

# We only allow teachers and assistants to modify their own courses.
return is_teacher(user) and user.teacher.courses.contains(course) or \
is_assistant(user) and user.assistant.courses.contains(course)


class CourseAssistantPermission(CoursePermission):
"""Permission class for assistant related endpoints."""

def has_object_permission(self, request: Request, view: ViewSet, course: Course) -> bool:
user: User = request.user

# Logged-in users can fetch course assistants.
if request.method in SAFE_METHODS:
return user.is_authenticated

# Only teachers can modify assistants of their own courses.
return is_teacher(user) and user.teacher.courses.contains(course)


class CourseStudentPermission(CoursePermission):
"""Permission class for student related endpoints."""
def has_permission(self, request: Request, view: ViewSet) -> bool:
return request.user and request.user.is_authenticated

def has_object_permission(self, request: Request, view: ViewSet, course: Course):
user: User = request.user

# Logged-in users can fetch course students.
if request.method in SAFE_METHODS:
return user.is_authenticated

# Only students can add or remove themselves from a course.
if is_student(user) and request.data.get("student_id") == user.id:
return True

# Teachers and assistants can add and remove any student.
return super().has_object_permission(request, view, course)


class CourseProjectPermission(CoursePermission):
"""Permission class for project related endpoints."""
def has_permission(self, request: Request, view: ViewSet) -> bool:
return request.user and request.user.is_authenticated

def has_object_permission(self, request: Request, view: ViewSet, course: Course):
user: User = request.user

# Logged-in users can fetch course projects.
if request.method in SAFE_METHODS:
return user.is_authenticated

# Teachers and assistants can modify projects.
return super().has_object_permission(request, view, course)
39 changes: 39 additions & 0 deletions backend/api/permissions/role_permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from authentication.models import User
from api.models.student import Student
from api.models.assistant import Assistant
from api.models.teacher import Teacher


def is_student(user: User):
return Student.objects.filter(id=user.id).exists()


def is_assistant(user: User):
return Assistant.objects.filter(id=user.id).exists()


def is_teacher(user: User):
return Teacher.objects.filter(id=user.id).exists()


class IsStudent(IsAuthenticated):
def has_permission(self, request: Request, view):
"""Returns true if the request contains a user,
with said user being a student"""
return super().has_permission(request, view) and is_student(request.user)


class IsTeacher(IsAuthenticated):
def has_permission(self, request: Request, view):
"""Returns true if the request contains a user,
with said user being a student"""
return super().has_permission(request, view) and is_teacher(request.user)


class IsAssistant(IsAuthenticated):
def has_permission(self, request, view):
"""Returns true if the request contains a user,
with said user being a student"""
return super().has_permission(request, view) and is_assistant(request.user)
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()
)
6 changes: 3 additions & 3 deletions backend/api/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
from api.models.student import Student


def user_creation(user: User, attributes: dict, **kwargs):
def user_creation(user: User, attributes: dict, **_):
"""Upon user creation, auto-populate additional properties"""
student_id = attributes.get("ugentStudentID")
student_id: str = attributes.get("ugentStudentID")

if student_id:
if student_id is not None:
Student(user_ptr=user, student_id=student_id).save_base(raw=True)
Loading

0 comments on commit 27b9b82

Please sign in to comment.