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

Api routes implementation #27

Closed
wants to merge 73 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
2f88001
init and remove .env
gilles-arnout Mar 2, 2024
b5b24f2
basic login page
gilles-arnout Mar 2, 2024
59393c7
First implementation of a view. Much work to do.
AlexanderVanOyen Mar 2, 2024
66e1061
model manager and put function
AlexanderVanOyen Mar 2, 2024
43ad033
Merge remote-tracking branch 'origin/develop' into api_routes-impleme…
AlexanderVanOyen Mar 4, 2024
8165e90
add swagger ui on /swagger and remove .env from tracked files
robinpdev Mar 5, 2024
546f63b
course api
ReinhardDP Mar 7, 2024
9283344
Merge branch 'api_routes-implementation' of github.com:SELab-2/UGent-…
ReinhardDP Mar 7, 2024
2baf464
Project viewset and custom permission class
AlexanderVanOyen Mar 8, 2024
f224e8a
Added Material UI dependence and tested it out with some buttons.
Thibaud-Collyn Mar 8, 2024
d1aaba8
corrected instances
ReinhardDP Mar 9, 2024
d30a0aa
added permissions
ReinhardDP Mar 9, 2024
470806c
added courses to urls
ReinhardDP Mar 9, 2024
fb2692f
styled login page
gilles-arnout Mar 9, 2024
e0e4235
automatic submission number, student max in 1 group for every project
PJDeSmijter Mar 9, 2024
008aecc
add admin panel
robinpdev Mar 9, 2024
ada3209
fixed permission class, added url
AlexanderVanOyen Mar 9, 2024
31b22aa
Merge remote-tracking branch 'origin/api_routes-implementation' into …
AlexanderVanOyen Mar 9, 2024
fa9f422
new url for creating project, added field to project model
AlexanderVanOyen Mar 9, 2024
25e4146
course permissions fixed
ReinhardDP Mar 9, 2024
75b7b36
Merge branch 'api_routes-implementation' of github.com:SELab-2/UGent-…
ReinhardDP Mar 9, 2024
24db0ca
Merge branch 'develop' into login-screen
axellorreyne Mar 10, 2024
fd43ce8
auth backend (serializers + viewsets)
axellorreyne Mar 10, 2024
ea7ccd7
add auth route
axellorreyne Mar 10, 2024
fb9452f
fix route
axellorreyne Mar 10, 2024
54be022
fix pages structure
gilles-arnout Mar 10, 2024
0ddd30c
GET requests support for groups+submissions, but error for groups whe…
RunoBoy Mar 10, 2024
b14d75b
removed deadline temporary to find bug
RunoBoy Mar 10, 2024
ca408dd
without deadline in project seems to work
RunoBoy Mar 10, 2024
2f9a381
deadline for projects fixed
RunoBoy Mar 10, 2024
82c29e2
Everything fixed to create submissions
RunoBoy Mar 10, 2024
c5fcf3e
API works now, fixed URLs and the requests
AlexanderVanOyen Mar 10, 2024
8cbc51f
list fixed
AlexanderVanOyen Mar 10, 2024
8dcfb2d
Merge branch 'develop' into api-testen-backend
PJDeSmijter Mar 10, 2024
fecde1f
Merge branch 'develop' into api_routes-implementation
PJDeSmijter Mar 10, 2024
ec129d7
fix merge
PJDeSmijter Mar 10, 2024
42b7ab4
Merge branch 'api_routes-implementation' into api-testen-backend
PJDeSmijter Mar 10, 2024
cefeb98
course api tests, permission fixes, test fixes
PJDeSmijter Mar 10, 2024
a220d03
add basic user admin
axellorreyne Mar 11, 2024
c3dd5a4
partial projects tests, todo invalid project & unautherised
PJDeSmijter Mar 11, 2024
c3e2adc
fix views invalid objects
PJDeSmijter Mar 11, 2024
629ef66
cleanup projects view
PJDeSmijter Mar 11, 2024
853bdac
Update test_project.py
PJDeSmijter Mar 11, 2024
2d00aab
fix lint
axellorreyne Mar 11, 2024
2c8ee17
splitting client and server components
gilles-arnout Mar 11, 2024
8fcbed8
Merge branch 'login-screen' into authentication
axellorreyne Mar 11, 2024
6da1e6d
user uploads toevoegen
robinpdev Mar 11, 2024
4982495
add testing workflow
robinpdev Mar 11, 2024
6c6cc39
try fixing test workflow
robinpdev Mar 11, 2024
057c25e
same
robinpdev Mar 11, 2024
c297348
lol
robinpdev Mar 11, 2024
a0f5a92
users changed
ReinhardDP Mar 11, 2024
2bd063a
Merge branch 'api_routes-implementation' of github.com:SELab-2/UGent-…
ReinhardDP Mar 11, 2024
00e76fc
Merge branch 'api_routes-implementation' into api-testen-backend
PJDeSmijter Mar 11, 2024
68fc4c1
naming change, permission for project api
AlexanderVanOyen Mar 11, 2024
3aa7015
permissions updated, course api expanded
ReinhardDP Mar 11, 2024
8bd347b
course api error fixed
ReinhardDP Mar 11, 2024
17ecff7
Merge branch 'api_routes-implementation' into api-testen-backend
PJDeSmijter Mar 11, 2024
1f3ca8a
Merge branch 'develop' into api_routes-implementation
PJDeSmijter Mar 11, 2024
350ce8f
Merge branch '29-model-fixes' into api_routes-implementation
PJDeSmijter Mar 11, 2024
f8e13b6
more checks added to projects api, added more functionality to groups…
AlexanderVanOyen Mar 11, 2024
f70db26
refactor testen
PJDeSmijter Mar 12, 2024
aa2a7b2
todo's checks
PJDeSmijter Mar 12, 2024
3376aa2
clean testen
PJDeSmijter Mar 12, 2024
4e82db6
add basic fronted authentication
axellorreyne Mar 12, 2024
b32c57d
docker make tools
axellorreyne Mar 12, 2024
648d241
Merge branch 'api_routes-implementation' into authentication
axellorreyne Mar 12, 2024
7f1d4df
cleanup serializer
axellorreyne Mar 12, 2024
bab63ca
fix user manager
axellorreyne Mar 12, 2024
2de5362
cleanup
axellorreyne Mar 12, 2024
a36f155
reset migrations
axellorreyne Mar 12, 2024
871e91a
migrations axel
PJDeSmijter Mar 12, 2024
518d287
courses permissions fixed
ReinhardDP Mar 12, 2024
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
22 changes: 22 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Backend test CI

on:
- pull_request

jobs:
backend-test:
runs-on: self-hosted
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.11
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Install dependencies
working-directory: ./
run: |
python -m pip install --upgrade pip
pip install -r ./backend/requirements.txt
- name: Running Django tests
run: |
sh ./backend/runtests.sh
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
.env
django/db.sqlite3
backend/db.sqlite3
backend/uploads
venv/
.venv/

Expand Down
15 changes: 14 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,17 @@ stop:

lint:
docker exec pigeonhole-backend flake8 .
docker exec pigeonhole-frontend npm run lint
docker exec pigeonhole-frontend npm run lint

superuser:
docker exec -it pigeonhole-backend python manage.py createsuperuser

reset:
docker image prune -af
docker system prune

backendtest:
docker exec -it pigeonhole-backend sh /usr/src/app/backend/runtests.sh

backendshell:
docker exec -it pigeonhole-backend sh
3 changes: 2 additions & 1 deletion backend/pigeonhole/apps/courses/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Generated by Django 5.0.2 on 2024-03-02 21:03
# Generated by Django 5.0.3 on 2024-03-12 13:29

from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
Expand Down
23 changes: 23 additions & 0 deletions backend/pigeonhole/apps/courses/permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from rest_framework import permissions

from backend.pigeonhole.apps.users.models import User


class CourseUserPermissions(permissions.BasePermission):
def has_permission(self, request, view):
if request.user.is_admin or request.user.is_superuser:
return True

if request.user.is_teacher:
if view.action in ['create', 'list', 'retrieve']:
return True
elif view.action in ['update', 'partial_update', 'destroy'] and User.objects.filter(id=request.user.id,
course=view.kwargs[
'pk']).exists():
return True
return

if request.user.is_student or request.user.is_teacher:
return view.action in ['list', 'retrieve']

return False
74 changes: 74 additions & 0 deletions backend/pigeonhole/apps/courses/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

from backend.pigeonhole.apps.users.models import User
from .models import Course, CourseSerializer
from .permissions import CourseUserPermissions


class CourseViewSet(viewsets.ModelViewSet):
queryset = Course.objects.all()
serializer_class = CourseSerializer
permission_classes = [IsAuthenticated, CourseUserPermissions]

def create(self, request, *args, **kwargs):
serializer = CourseSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

def update(self, request, *args, **kwargs):
course_id = kwargs.get('pk')
course = Course.objects.get(pk=course_id)
serializer = CourseSerializer(course, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

def destroy(self, request, *args, **kwargs):
course_id = kwargs.get('pk')
course = Course.objects.get(pk=course_id)
course.delete()
return Response(status=status.HTTP_204_NO_CONTENT)

def list(self, request, *args, **kwargs):
serializer = CourseSerializer(self.queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)

def my_list(self, request, *args, **kwargs):
if request.user.is_admin or request.user.is_superuser:
serializer = CourseSerializer(self.queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
if request.user.is_teacher or request.user.is_student:
courses = User.objects.get(id=request.user.id).course.all()
serializer = CourseSerializer(courses, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
serializer = CourseSerializer(self.queryset, many=True)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

def retrieve(self, request, *args, **kwargs):
course_id = kwargs.get('pk')
course = Course.objects.get(pk=course_id)
serializer = CourseSerializer(course, many=False)
return Response(serializer.data, status=status.HTTP_200_OK)

def partial_update(self, request, *args, **kwargs):
course_id = kwargs.get('pk')
course = Course.objects.get(pk=course_id)
serializer = CourseSerializer(course, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@action(detail=True, methods=['post'])
def join_course(self, request, *args, **kwargs):
course_id = kwargs.get('pk')
course = Course.objects.get(pk=course_id)
user = User.objects.get(id=request.user.id)
user.course.add(course)
return Response(status=status.HTTP_200_OK)
7 changes: 4 additions & 3 deletions backend/pigeonhole/apps/groups/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Generated by Django 5.0.2 on 2024-03-02 21:03
# Generated by Django 5.0.3 on 2024-03-12 13:29

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
Expand All @@ -16,9 +17,9 @@ class Migration(migrations.Migration):
name='Group',
fields=[
('group_id', models.BigAutoField(primary_key=True, serialize=False)),
('group_nr', models.IntegerField()),
('group_nr', models.IntegerField(blank=True, null=True)),
('feedback', models.TextField(null=True)),
('final_score', models.IntegerField()),
('final_score', models.IntegerField(blank=True, null=True)),
('project_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='projects.project')),
],
),
Expand Down
10 changes: 6 additions & 4 deletions backend/pigeonhole/apps/groups/migrations/0002_initial.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
# Generated by Django 5.0.2 on 2024-03-02 21:03
# Generated by Django 5.0.3 on 2024-03-12 13:29

from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
('groups', '0001_initial'),
('users', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.AddField(
model_name='group',
name='student',
field=models.ManyToManyField(to='users.student'),
name='user',
field=models.ManyToManyField(to=settings.AUTH_USER_MODEL),
),
]

This file was deleted.

This file was deleted.

21 changes: 17 additions & 4 deletions backend/pigeonhole/apps/groups/models.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,44 @@
from django.db import models
from rest_framework import serializers
from django.core.exceptions import ValidationError

from backend.pigeonhole.apps.projects.models import Project
from backend.pigeonhole.apps.users.models import Student
from backend.pigeonhole.apps.users.models import User


class Group(models.Model):
group_id = models.BigAutoField(primary_key=True)
group_nr = models.IntegerField(blank=True, null=True)
project_id = models.ForeignKey(Project, on_delete=models.CASCADE)
student = models.ManyToManyField(Student)
user = models.ManyToManyField(User)
feedback = models.TextField(null=True)
final_score = models.IntegerField(null=True, blank=True)

objects = models.Manager()

# a student can only be in one group per project
def clean(self):
for student in self.student.all():
existing_groups = Group.objects.filter(
project_id=self.project_id, student=student).exclude(
group_id=self.group_id)
if existing_groups.exists():
raise ValidationError(f"Student {student} is already part of "
"another group in this project.")

# a student can only be in one group per project, group_nr is
# automatically assigned and unique per project
def save(self, *args, **kwargs):
if not self.group_id:
if self.group_nr is None:
max_group_nr = Group.objects.filter(
project_id=self.project_id).aggregate(
models.Max('group_nr'))['group_nr__max'] or 0
models.Max('group_nr'))['group_nr__max'] or 0
self.group_nr = max_group_nr + 1
super().save(*args, **kwargs)


class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = Group
fields = ["group_id", "group_nr", "final_score", "project_id", "student"]
fields = ["group_id", "group_nr", "final_score", "project_id", "user", "feedback"]
18 changes: 18 additions & 0 deletions backend/pigeonhole/apps/groups/permission.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from rest_framework import permissions


class CanAccessProject(permissions.BasePermission):
# Custom user class to check if the user can join a group.
def has_permission(self, request, view):
user = request.user
course_id = view.kwargs.get('course_id')

if user.is_admin or user.is_superuser:
return True
elif user.is_teacher:
if user.course.filter(course_id=course_id).exists():
return True
elif user.is_student:
if user.course.filter(course_id=course_id).exists():
return True
return False
53 changes: 53 additions & 0 deletions backend/pigeonhole/apps/groups/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from django.shortcuts import get_object_or_404
from rest_framework import viewsets, status
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

from backend.pigeonhole.apps.courses.models import Course
from backend.pigeonhole.apps.groups.models import Group, GroupSerializer
from backend.pigeonhole.apps.projects.models import Project

# TODO tests for score/max_score


class GroupViewSet(viewsets.ModelViewSet):
queryset = Group.objects.all()
serializer_class = GroupSerializer
permission_classes = [IsAuthenticated]

def create(self, request, *args, **kwargs):
# TODO zorg dat er geen 2 groepen met hetzelfde nummer kunnen zijn.
course_id = kwargs.get('course_id')

if request.user.is_teacher or request.user.is_admin or request.user.is_superuser:
# Check whether the course exists
get_object_or_404(Course, course_id=course_id)

serializer = GroupSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
return Response({"message": "You are not allowed to create a new group."},
status=status.HTTP_400_BAD_REQUEST)

def list(self, request, *args, **kwargs):
serializer = GroupSerializer(self.queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)

def retrieve(self, request, *args, **kwargs):
course_id = kwargs.get('course_id')
project_id = kwargs.get('project_id')
group_id = kwargs.get('pk')

# Check whether the course exists
get_object_or_404(Course, course_id=course_id)

# Check whether the project exists
get_object_or_404(Project, pk=project_id)
group = get_object_or_404(Group, group_id=group_id)

serializer = GroupSerializer(instance=group, many=False)

return Response(serializer.data, status=status.HTTP_200_OK)
Loading
Loading