Skip to content

Commit

Permalink
merge with dev and style fix
Browse files Browse the repository at this point in the history
  • Loading branch information
ReinhardDP committed May 22, 2024
2 parents 001ece7 + f47b11f commit c795c5f
Show file tree
Hide file tree
Showing 63 changed files with 2,792 additions and 1,263 deletions.
23 changes: 23 additions & 0 deletions .template.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
DATABASE=postgres
DEBUG=1
DJANGO_ALLOWED_HOSTS='localhost example.com 127.0.0.1 [::1] django'
DJANGO_SUPERUSER_EMAIL=abc@example.com
DJANGO_SUPERUSER_PASSWORD=abc
FRONTEND_URL=http://localhost:3000
NEXT_PUBLIC_BACKEND_URL=http://localhost:8000
NEXT_PUBLIC_REDIRECT_URL=/redirect
OAUTH_CLIENT_ID=1234
OAUTH_CLIENT_SECRET=1234
OAUTH_TENANT_ID=1234
REGISTRY_NAME=sel2-1.ugent.be:2002
REGISTRY_PASSWORD=testding
REGISTRY_URL=https://sel2-1.ugent.be:2002
REGISTRY_USER=test
SECRET_KEY=development_key
SQL_DATABASE=pigeonhole_dev
SQL_ENGINE=django.db.backends.postgresql
SQL_HOST=pigeonhole-database
SQL_PASSWORD=password
SQL_PORT=5432
SQL_USER=pigeonhole
SUBMISSIONS_PATH=./backend/uploads/submissions/
9 changes: 8 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,12 @@ frontshell:
componenttest:
docker exec -it pigeonhole-frontend npx jest

coveragecomponenttest:
docker exec -it pigeonhole-frontend npx jest --coverage --silent

silentcomponenttest:
docker exec -it pigeonhole-frontend npx jest --silent
docker exec -it pigeonhole-frontend npx jest --silent

resetdb:
docker exec pigeonhole-backend python manage.py flush --noinput
docker exec -it pigeonhole-backend python manage.py runscript mockdata
3 changes: 2 additions & 1 deletion backend/pigeonhole/apps/courses/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ def has_permission(self, request, view):
"get_teachers",
"get_students",
"get_archived_courses",
"get_open_courses"
]:
return True

if request.user.is_teacher:
if view.action in ["create", "list", "retrieve", "get_open_courses"]:
if view.action in ["create", "list", "retrieve"]:
return True
elif (
view.action in ["update", "partial_update", "destroy", "get_projects"]
Expand Down
33 changes: 32 additions & 1 deletion backend/pigeonhole/apps/courses/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from django_filters.rest_framework import DjangoFilterBackend

from backend.pigeonhole.apps.courses.models import CourseSerializer
from backend.pigeonhole.apps.groups.models import Group
from backend.pigeonhole.apps.groups.models import Group, GroupSerializer
from backend.pigeonhole.apps.projects.models import Project
from backend.pigeonhole.apps.projects.models import ProjectSerializer
from backend.pigeonhole.apps.users.models import User, UserSerializer
Expand Down Expand Up @@ -59,6 +59,21 @@ def join_course(self, request, *args, **kwargs):
if request.user.is_student:
if course.open_course:
user.course.add(course)

# Join all individual projects of the course
projects = Project.objects.filter(course_id=course, group_size=1)
for project in projects:
group_data = {
"project_id": project.project_id,
"user": [user.id],
"feedback": None,
"final_score": None,
"visible": False,
}
group_serializer = GroupSerializer(data=group_data)
group_serializer.is_valid(raise_exception=True)
group_serializer.save()

return Response(status=status.HTTP_200_OK)
else:
return Response(
Expand All @@ -81,6 +96,22 @@ def join_course_with_token(self, request, *args, **kwargs):

if invite_token == course.invite_token:
user.course.add(course)

# Join all individual projects of the course
if request.user.is_student:
projects = Project.objects.filter(course_id=course, group_size=1)
for project in projects:
group_data = {
"project_id": project.project_id,
"user": [user.id],
"feedback": None,
"final_score": None,
"visible": False,
}
group_serializer = GroupSerializer(data=group_data)
group_serializer.is_valid(raise_exception=True)
group_serializer.save()

return Response(
{"message": "Successfully joined the course with invite token."},
status=status.HTTP_200_OK,
Expand Down
7 changes: 7 additions & 0 deletions backend/pigeonhole/apps/submissions/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ class CanAccessSubmission(permissions.BasePermission):
# to the submission data.
def has_permission(self, request, view):
user = request.user

if not user.is_authenticated:
return False

if view.action == "get_project":
return True

if view.action in ["list"]:
return False
elif view.action in ["download_selection"]:
Expand Down
71 changes: 59 additions & 12 deletions backend/pigeonhole/apps/submissions/views.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import zipfile
import fnmatch
import os
import shutil
import zipfile
from datetime import datetime
from os.path import realpath, basename
from pathlib import Path

import pytz
from django.conf import settings
from django.http import FileResponse
from django.shortcuts import get_object_or_404
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.filters import OrderingFilter
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from django.shortcuts import get_object_or_404

from backend.pigeonhole.apps.groups.models import Group
from backend.pigeonhole.apps.projects.models import Project
Expand All @@ -22,12 +26,6 @@
from backend.pigeonhole.apps.submissions.permissions import CanAccessSubmission
from backend.pigeonhole.filters import CustomPageNumberPagination

from django.conf import settings
from pathlib import Path
import json as JSON

import os


class ZipUtilities:

Expand Down Expand Up @@ -69,9 +67,13 @@ class SubmissionsViewset(viewsets.ModelViewSet):
def create(self, request, *args, **kwargs):
group_id = request.data["group_id"]
group = get_object_or_404(Group, group_id=group_id)

data = request.data.copy() # Create a mutable copy
data["file_urls"] = JSON.dumps([key for key in request.FILES])
if len(request.FILES) != 0:
file_urls = []
for key in request.FILES:
file_urls.append(key)
else:
file_urls = request.data["file_urls"].split(",")

serializer = SubmissionsSerializer(data=data)
serializer.is_valid(raise_exception=True)
Expand Down Expand Up @@ -121,7 +123,20 @@ def create(self, request, *args, **kwargs):
"ERROR_FILE_UPLOAD"}, status=status.HTTP_400_BAD_REQUEST
)

return Response(serializer.data, status=status.HTTP_201_CREATED)
project = Project.objects.get(project_id=group.project_id.project_id)
# return Response(",".join(file_urls), status=status.HTTP_201_CREATED)
if project.file_structure is None or project.file_structure == "":
complete_message = {"message": "Submission successful"}
else:
violations = check_restrictions(file_urls, project.file_structure.split(","))

if not violations[0] and not violations[2]:
complete_message = {"success": 0}
else:
violations.update({'success': 1})
complete_message = violations

return Response(complete_message, status=status.HTTP_201_CREATED)

def update(self, request, *args, **kwargs):
return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
Expand Down Expand Up @@ -190,8 +205,8 @@ def download_selection(self, request, *args, **kwargs):
submission_folders.append(
submission_folder_path(
submission.group_id.group_id, submission.submission_id
)
)
)

utilities = ZipUtilities()
filename = path
Expand All @@ -204,3 +219,35 @@ def download_selection(self, request, *args, **kwargs):
response["Content-Disposition"] = f"inline; filename={basename(path)}"

return response

@action(detail=True, methods=["get"])
def get_project(self, request, *args, **kwargs):
return Response(
{"project": self.get_object().group_id.project_id.project_id},
status=status.HTTP_200_OK
)


def check_restrictions(filenames, restrictions):
# 0: Required file not found
# 1: Required file found
# 2: Forbidden file found
# 3: No forbidden file found
violations = {0: [], 1: [], 2: [], 3: []}
for restriction_ in restrictions:
restriction = restriction_.strip()
if restriction.startswith('+'):
pattern = restriction[1:]
matching_files = fnmatch.filter(filenames, pattern)
if not matching_files:
violations[0].append(pattern)
else:
violations[1].append(pattern)
elif restriction.startswith('-'):
pattern = restriction[1:]
matching_files = fnmatch.filter(filenames, pattern)
if matching_files:
violations[2].append(pattern)
else:
violations[3].append(pattern)
return violations
26 changes: 23 additions & 3 deletions backend/pigeonhole/tests/test_views/test_course/test_student.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from backend.pigeonhole.apps.courses.models import Course
from backend.pigeonhole.apps.projects.models import Project
from backend.pigeonhole.apps.users.models import User
from backend.pigeonhole.apps.groups.models import Group

API_ENDPOINT = '/courses/'

Expand All @@ -31,6 +32,15 @@ def setUp(self):

self.course = Course.objects.create(**self.course_data)

self.course_individual_project = Course.objects.create(name="Not of Student",
description="This is not of the student",
open_course=True)

self.individual_project = Project.objects.create(name="Individual Project",
deadline="2021-12-12 12:12:12",
course_id=self.course_individual_project,
group_size=1)

self.course_not_of_student = Course.objects.create(name="Not of Student",
description="This is not of the student",
open_course=True)
Expand Down Expand Up @@ -62,7 +72,7 @@ def setUp(self):
def test_create_course(self):
response = self.client.post(API_ENDPOINT, self.course_data, format='json')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(Course.objects.count(), 4)
self.assertEqual(Course.objects.count(), 5)

def test_update_course(self):
updated_data = {
Expand All @@ -88,7 +98,7 @@ def test_partial_update_course(self):
def test_delete_course(self):
response = self.client.delete(f'{API_ENDPOINT}{self.course.course_id}/')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(Course.objects.count(), 4)
self.assertEqual(Course.objects.count(), 5)

def test_retrieve_course(self):
response = self.client.get(f'{API_ENDPOINT}{self.course.course_id}/')
Expand All @@ -101,7 +111,7 @@ def test_list_courses(self):
response = self.client.get(API_ENDPOINT)
self.assertEqual(response.status_code, status.HTTP_200_OK)
content_json = json.loads(response.content.decode("utf-8"))
self.assertEqual(content_json["count"], 4)
self.assertEqual(content_json["count"], 5)

def test_retrieve_course_not_exist(self):
response = self.client.get(f'{API_ENDPOINT}100/')
Expand Down Expand Up @@ -159,6 +169,16 @@ def test_join_course(self):
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(self.student.course.count(), 2)

def test_join_course_individual_project(self):
self.assertEqual(self.student.course.count(), 1)
groups = Group.objects.filter(user=self.student)
self.assertEqual(groups.count(), 0)
response = self.client.post(f'{API_ENDPOINT}{self.course_individual_project.course_id}/join_course/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(self.student.course.count(), 2)
groups = Group.objects.filter(user=self.student)
self.assertEqual(groups.count(), 1)

def test_join_course_not_exist(self):
response = self.client.post(f'{API_ENDPOINT}56152/join_course/')
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
Expand Down
Loading

0 comments on commit c795c5f

Please sign in to comment.