Skip to content

Commit

Permalink
Merge pull request #168 from SELab-2/develop
Browse files Browse the repository at this point in the history
Klaar voor release Milestone 2
  • Loading branch information
sPAICEcake authored Apr 18, 2024
2 parents 643d24d + 16a8309 commit c5b603b
Show file tree
Hide file tree
Showing 132 changed files with 15,207 additions and 6,564 deletions.
Binary file modified .coverage
Binary file not shown.
11 changes: 11 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[run]
omit=
*/tests/*
*/migrations/*
*/__init__.py
*/__pycache__/*
*/data/*
*/frontend/*
*/static/*
*/.github/*
*/usr/*
6 changes: 4 additions & 2 deletions .github/workflows/django.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ name: Django CI

on:
push:
branches: [ "develop", "tests", "main" ]

branches: [ "develop", "tests", "main" ]
pull_request:
branches: [ "develop", "tests", "main" ]
branches: [ "develop", "tests", "main" ]


jobs:
build:
Expand Down
10 changes: 8 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@ venv
.idea
/venv/
**/__pycache__/
migrations
migrations/
./api/.env
__pycache__/
frontend/frontend/src/authConfig/authSecrets.ts
api/.env
.env
htmlcov
data
uploads
uploads
/data/
/package-lock.json
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,28 @@

# Wiki

- Zie de wiki voor meer informatie zoals bijvoorbeeld de use-cases en documentatie
- Raadpleeg de wiki voor uitgebreidere informatie, waaronder Use-Cases en API-Documentatie en Website-documentatie.

# Frontend

## Hoe runnen?

### Benodigdheden:
- Je zal bepaalde node modules moeten installeren. Dit kan je gemakkelijk doen in één keer. Ga naar de UGent-4/frontend/frontend folder. Run dan het commando `npm i`.

### De frontend lokaal runnen:
Ga naar de UGent-4/frontend/frontend folder. Run het commando: ```npm run dev```. Normaal kan je nu naar de url `http://localhost:5173/` surfen in je browser en de frontend zien. Als je een wit scherm ziet dan kan je proberen om de frontend te stoppen, `npm audit fix` te runnen in dezelfde folder, en de frontend opnieuw te runnen.

### Linting:
De volgende commando's run je van de ***frontend/frontend*** directory:
`npm run lint`: geeft een lijst van alle linting errors weer.
`npm run lint -- --fix`: fixt een aantal linting errors automatisch indien mogelijk.
`npm run format`: format alle files in het project volgens de instellingen in ***.prettierrc***.

Er kan niet gepushed worden als er nog linting errors aanwezig zijn.

### Testen:

- WIP

# Backend
Expand Down
2 changes: 2 additions & 0 deletions api/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from api.models.project import Project
from api.models.indiening import Indiening, IndieningBestand
from api.models.score import Score
from api.models.restrictie import Restrictie

admin.site.register(Gebruiker)
admin.site.register(Vak)
Expand All @@ -13,3 +14,4 @@
admin.site.register(Indiening)
admin.site.register(Score)
admin.site.register(IndieningBestand)
admin.site.register(Restrictie)
16 changes: 16 additions & 0 deletions api/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM ubuntu:latest
LABEL maintainer="Your Name <[email protected]>"
RUN apt-get update -y && \
apt-get upgrade -y && \
apt-get dist-upgrade -y && \
apt-get -y autoremove && \
apt-get clean
RUN apt-get install -y zip \
unzip \
python3 \
&& rm -rf /var/lib/apt/lists/*
# Copy the script to the container
ADD ./api/docker/testing_entrypoint.sh /
RUN chmod +x /testing_entrypoint.sh
# Set the entrypoint to the script with CMD arguments
ENTRYPOINT ["/testing_entrypoint.sh"]
25 changes: 25 additions & 0 deletions api/docker/python_entrypoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import pexpect
from dotenv import dotenv_values


def run_tests_on(indiening_id, project_id):
"""
Voert tests uit op een specifieke indiening en project met behulp van Docker.
Args:
indiening_id (int): Het ID van de indiening.
project_id (int): Het ID van het project.
Returns:
tuple: Een tuple (bool, string) die aangeeft of de tests zijn mislukt en de uitvoer van de tests.
"""
command = f"sudo bash api/docker/rundocker.sh {indiening_id} {project_id}"
child = pexpect.spawn(command, timeout=None)
index = child.expect([r"\[sudo\] password", pexpect.EOF, pexpect.TIMEOUT])

dot_env_values = dotenv_values("api/.env")
if index == 0:
child.sendline(dot_env_values.get("SUDO_PASSWORD"))
output = child.read().decode("utf-8")
child.close()
return ": FAIL" in output, output
4 changes: 4 additions & 0 deletions api/docker/rundocker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
yes | docker system prune -a >&2
docker build -t script-demo -f api/docker/Dockerfile .
docker run --mount type=bind,source="$(pwd)"/data/restricties/project_$2,target=/data/restricties --mount type=bind,source="$(pwd)"/data/indieningen/indiening_$1,target=/data --name demo -d script-demo
docker logs demo -f
14 changes: 14 additions & 0 deletions api/docker/testing_entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash
cd data

for filename in ./restricties/*; do
echo -n "Testing ${filename}: "

if [[ "$filename" == *.sh ]]
then
bash $filename
elif [[ "$filename" == *.py ]]
then
python3 $filename
fi
done
69 changes: 54 additions & 15 deletions api/middleware.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,71 @@
from django.conf import settings
from django.shortcuts import redirect
from django.contrib.auth.models import User
from api.models.gebruiker import Gebruiker
from api.serializers.gebruiker import GebruikerSerializer
import requests

URL = "https://graph.microsoft.com/v1.0/me"

class RedirectAnonymousUserMiddleware:

class AuthenticationUserMiddleware:
"""
Middleware die anonieme gebruikers omleidt naar de inlogpagina.
Deze middleware controleert of de gebruiker anoniem is en of het huidige pad niet de inlogpagina is.
Als dit het geval is, wordt de gebruiker omgeleid naar de inlogpagina die is geconfigureerd in de instellingen.
Middleware voor authenticatie van gebruikers en het aanmaken van gebruikersindeling.
Args:
get_response (function): De functie die wordt aangeroepen om het verzoek te verwerken.
get_response (callable): De volgende middleware in de keten.
Returns:
HttpResponse: Een HTTP-omleiding naar de inlogpagina als de gebruiker anoniem is
en het huidige pad niet de inlogpagina is.
Anders wordt het verzoek verder verwerkt door de volgende middleware of de weergavefunctie.
HttpResponse: Een HTTP-response-object.
Raises:
Redirect: Redirect naar de inlog-URL als er geen autorisatiegegevens zijn.
"""

def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
# Check if the user is anonymous and the current path is not the login page
if request.user.is_anonymous and request.path not in [
"/oauth2/login",
"/oauth2/callback",
]:
# Redirect to the login page
return redirect(settings.LOGIN_URL)
if request.path in ["/oauth2/login", "/oauth2/callback"]:
return self.get_response(request)

if request.user.is_anonymous:
authorization = request.headers.get("Authorization")
if authorization:
headers = {
"Authorization": authorization,
"Content-Type": "application/json",
}

response = requests.get(url=URL, headers=headers)
json_data = response.json()
mail = json_data.get("mail")
first_name = json_data.get("givenName")
last_name = json_data.get("surname")
try:
user = User.objects.get(username=mail)
except User.DoesNotExist:
user = User.objects.create_user(
username=mail,
email=mail,
first_name=first_name,
last_name=last_name,
)

request.user = user
else:
return redirect(settings.LOGIN_URL)

try:
Gebruiker.objects.get(pk=request.user.id)
except Gebruiker.DoesNotExist:
gebruiker_post_data = {
"user": request.user.id,
"subjects": [],
"is_lesgever": False,
}
serializer = GebruikerSerializer(data=gebruiker_post_data)
if serializer.is_valid():
serializer.save()

return self.get_response(request)
4 changes: 4 additions & 0 deletions api/models/gebruiker.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.db import models
from django.contrib.auth.models import User
from api.models.vak import Vak


class Gebruiker(models.Model):
Expand All @@ -19,6 +20,9 @@ class Gebruiker(models.Model):

user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
is_lesgever = models.BooleanField(default=False)
gepinde_vakken = models.ManyToManyField(
Vak, related_name="gebruiker_gepinde_vakken", blank=True
)

def __str__(self):
return self.user.first_name + " " + self.user.last_name
63 changes: 62 additions & 1 deletion api/models/indiening.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from api.docker.python_entrypoint import run_tests_on
from threading import Thread
from django.db import transaction
import re


STATUS_CHOICES = (
(-1, "FAIL"),
(0, "PENDING"),
(1, "OK"),
)


def upload_to(instance, filename):
Expand Down Expand Up @@ -28,6 +41,8 @@ class Indiening(models.Model):
indieningen verwijderd.
tijdstip (DateTimeField): Een veld dat automatisch het tijdstip
registreert waarop de indiening is aangemaakt.
status (IntegerField): Een veld dat de status van de testen zal bijhouden.
result (TextField): Een veld dat het resultaat van de uitgevoerde testen zal bijhouden.
Methods:
__str__(): Geeft een representatie van het model als een string terug, die de ID van de indiening bevat.
Expand All @@ -36,6 +51,8 @@ class Indiening(models.Model):
indiening_id = models.AutoField(primary_key=True)
groep = models.ForeignKey("Groep", on_delete=models.CASCADE)
tijdstip = models.DateTimeField(auto_now_add=True)
status = models.IntegerField(default=0, choices=STATUS_CHOICES)
result = models.TextField(default="", blank=True)

def __str__(self):
return str(self.indiening_id)
Expand All @@ -59,8 +76,52 @@ class IndieningBestand(models.Model):
"""

indiening_bestand_id = models.AutoField(primary_key=True)
indiening = models.ForeignKey("Indiening", on_delete=models.CASCADE)
indiening = models.ForeignKey(
Indiening, related_name="indiening_bestanden", on_delete=models.CASCADE
)
bestand = models.FileField(upload_to=upload_to)

def __str__(self):
return str(self.bestand.name)


def run_tests_async(instance):
"""
Voert tests uit op een asynchrone manier en werkt de status en resultaat van de indiening bij.
Args:
instance: Het instantie-object van de indiening.
"""
indiening_id = instance.indiening_id
project_id = instance.groep.project.project_id
result = run_tests_on(indiening_id, project_id)
matches = re.findall(r"Testing \./.*", result[1])
try:
first_match_index = result[1].find(matches[0])
result = result[1][first_match_index:]
status = -1 if result[0] else 1
except Exception:
result = result[1]
status = -1

with transaction.atomic():
instance.status = status
instance.result = result
instance.save()


@receiver(post_save, sender=Indiening)
def indiening_post_init(sender, instance, created, **kwargs):
"""
Een signaalhandler die wordt geactiveerd na het maken van een nieuwe indiening.
Start een asynchrone thread om de tests uit te voeren.
Args:
sender: De verzender van het signaal.
instance: Het instantie-object van de indiening.
created: Geeft aan of de indiening is aangemaakt of bijgewerkt.
kwargs: Extra argumenten.
"""
if created:
thread = Thread(target=run_tests_async, args=(instance,))
thread.start()
23 changes: 18 additions & 5 deletions api/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class Project(models.Model):
"""
Model voor een project binnen een vak.
Fields:
Velden:
project_id (AutoField): Een automatisch gegenereerd veld dat fungeert als de primaire sleutel voor het project.
titel (CharField): Titel van het project.
beschrijving (TextField): Beschrijving van het project.
Expand All @@ -32,11 +32,20 @@ class Project(models.Model):
Als het bijbehorende vak wordt verwijderd, worden ook de bijbehorende projecten verwijderd.
deadline (DateTimeField): Een veld voor het instellen van de deadline voor het project.
Kan optioneel zijn (null=True).
extra_deadline (DateTimeField): Een extra veld voor het instellen van een extra deadline voor het project.
Kan optioneel zijn (null=True).
max_score (IntegerField): Een veld voor het instellen van de maximale score voor het project.
Standaard ingesteld op 20.
# indiening restricties (TODO): Restricties/tests bij indiening moeten nog toegevoegd worden.
max_groep_grootte (IntegerField): Een veld voor het instellen van de max grootte van de groep voor het project.
Standaard ingesteld op 1.
student_groep (BooleanField): Een veld om aan te geven of het een individueel project is of niet.
Standaard ingesteld of False.
zichtbaar (BooleanField): Een veld om aan te geven of het project zichtbaar is of niet.
Standaard ingesteld op True.
gearchiveerd (BooleanField): Een veld om aan te geven of het project gearchiveerd is of niet.
Standaard ingesteld op False.
Methods:
Methoden:
__str__(): Geeft een representatie van het model als een string terug,
die de titel van het project bevat.
"""
Expand All @@ -46,9 +55,13 @@ class Project(models.Model):
beschrijving = models.TextField()
opgave_bestand = models.FileField(upload_to=upload_to)
vak = models.ForeignKey(Vak, on_delete=models.CASCADE)
deadline = models.DateTimeField(null=True)
deadline = models.DateTimeField(null=True, blank=True)
extra_deadline = models.DateTimeField(null=True, blank=True)
max_score = models.IntegerField(default=20)
# indiening restricties
max_groep_grootte = models.IntegerField(default=1)
student_groep = models.BooleanField(default=False, blank=True)
zichtbaar = models.BooleanField(default=True, blank=True)
gearchiveerd = models.BooleanField(default=False, blank=True)

def __str__(self):
return self.titel
Loading

0 comments on commit c5b603b

Please sign in to comment.