Skip to content

Commit

Permalink
merged with dev
Browse files Browse the repository at this point in the history
  • Loading branch information
JibrilExe committed Mar 22, 2024
2 parents 9751348 + 2d0574c commit 5cf788a
Show file tree
Hide file tree
Showing 34 changed files with 584 additions and 205 deletions.
4 changes: 2 additions & 2 deletions backend/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Project pigeonhole backend
![tests](https://github.com/SELab-2/UGent-3/actions/workflows/ci-test-backend.yaml/badge.svg)
![linter](https://github.com/SELab-2/UGent-3/actions/workflows/ci-linter-backend.yaml/badge.svg)
![tests](https://github.com/SELab-2/UGent-3/actions/workflows/ci-test-backend.yaml/badge.svg?branch=development)
![linter](https://github.com/SELab-2/UGent-3/actions/workflows/ci-linter-backend.yaml/badge.svg?branch=development)
## Prerequisites
**1. Clone the repo**
```sh
Expand Down
5 changes: 3 additions & 2 deletions backend/db_construct.sql
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
CREATE TYPE role AS ENUM ('STUDENT', 'TEACHER', 'ADMIN');
CREATE TYPE submission_status AS ENUM ('SUCCESS', 'LATE', 'FAIL', 'RUNNING');

CREATE TABLE users (
uid VARCHAR(255),
Expand Down Expand Up @@ -56,10 +57,10 @@ CREATE TABLE submissions (
submission_id INT GENERATED ALWAYS AS IDENTITY,
uid VARCHAR(255) NOT NULL,
project_id INT NOT NULL,
grading INTEGER CHECK (grading >= 0 AND grading <= 20),
grading FLOAT CHECK (grading >= 0 AND grading <= 20),
submission_time TIMESTAMP WITH TIME ZONE NOT NULL,
submission_path VARCHAR(50) NOT NULL,
submission_status BOOLEAN NOT NULL,
submission_status submission_status NOT NULL,
PRIMARY KEY(submission_id),
CONSTRAINT fk_project FOREIGN KEY(project_id) REFERENCES projects(project_id) ON DELETE CASCADE,
CONSTRAINT fk_user FOREIGN KEY(uid) REFERENCES users(uid) ON DELETE CASCADE
Expand Down
12 changes: 10 additions & 2 deletions backend/project/__main__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
"""Main entry point for the application."""

from os import getenv
from dotenv import load_dotenv
from project import create_app_with_db
from project.db_in import url

load_dotenv()
DEBUG=getenv("DEBUG")

if __name__ == "__main__":
load_dotenv()
app = create_app_with_db(url)
app.run(debug=True, host='0.0.0.0')

if DEBUG and DEBUG.lower() == "true":
app.run(debug=True, host='0.0.0.0')
else:
from waitress import serve
serve(app, host='0.0.0.0', port=5000)
7 changes: 4 additions & 3 deletions backend/project/endpoints/courses/course_admin_relation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from urllib.parse import urljoin
from dotenv import load_dotenv

from flask import abort, request
from flask import request
from flask_restful import Resource

from project.models.course_relation import CourseAdmin
Expand All @@ -21,11 +21,12 @@
json_message
)
from project.utils.query_agent import query_selected_from_model, insert_into_model
from project.utils.authentication import login_required, authorize_teacher_of_course, authorize_teacher_or_course_admin
from project.utils.authentication import authorize_teacher_of_course, \
authorize_teacher_or_course_admin

load_dotenv()
API_URL = getenv("API_HOST")
RESPONSE_URL = urljoin(API_URL + "/", "courses")
RESPONSE_URL = urljoin(f"{API_URL}/", "courses")

class CourseForAdmins(Resource):
"""
Expand Down
2 changes: 1 addition & 1 deletion backend/project/endpoints/courses/course_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

load_dotenv()
API_URL = getenv("API_HOST")
RESPONSE_URL = urljoin(API_URL + "/", "courses")
RESPONSE_URL = urljoin(f"{API_URL}/", "courses")

class CourseByCourseId(Resource):
"""Api endpoint for the /courses/course_id link"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

load_dotenv()
API_URL = getenv("API_HOST")
RESPONSE_URL = urljoin(API_URL + "/", "courses")
RESPONSE_URL = urljoin(f"{API_URL}/", "courses")

class CourseToAddStudents(Resource):
"""
Expand Down
2 changes: 1 addition & 1 deletion backend/project/endpoints/courses/courses.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

load_dotenv()
API_URL = getenv("API_HOST")
RESPONSE_URL = urljoin(API_URL + "/", "courses")
RESPONSE_URL = urljoin(f"{API_URL}/", "courses")

class CourseForUser(Resource):
"""Api endpoint for the /courses link"""
Expand Down
2 changes: 1 addition & 1 deletion backend/project/endpoints/courses/courses_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

load_dotenv()
API_URL = getenv("API_HOST")
RESPONSE_URL = urljoin(API_URL + "/", "courses")
RESPONSE_URL = urljoin(f"{API_URL}/", "courses")

def execute_query_abort_if_db_error(query, url, query_all=False):
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from flask_restful import Resource
from project.utils.query_agent import query_by_id_from_model, delete_by_id_from_model
from project.models.course_share_code import CourseShareCode
from project.endpoints.courses.join_codes.join_codes_utils import check_course_exists
from project.utils.authentication import authorize_teacher_of_course

load_dotenv()
Expand All @@ -22,7 +21,7 @@ class CourseJoinCode(Resource):
the /courses/course_id/join_codes/<join_code> url, only an admin of a course can do this
"""

@check_course_exists
@authorize_teacher_of_course
def get(self, course_id, join_code):
"""
This function will return all the join codes of a course
Expand All @@ -35,7 +34,6 @@ def get(self, course_id, join_code):
urljoin(f"{RESPONSE_URL}/", f"{str(course_id)}/", "join_codes")
)

@check_course_exists
@authorize_teacher_of_course
def delete(self, course_id, join_code):
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from project.utils.query_agent import query_selected_from_model, insert_into_model
from project.models.course_share_code import CourseShareCode
from project.endpoints.courses.courses_utils import get_course_abort_if_not_found
from project.utils.authentication import login_required, authorize_teacher_of_course
from project.utils.authentication import authorize_teacher_of_course

load_dotenv()
API_URL = getenv("API_HOST")
Expand All @@ -23,7 +23,7 @@ class CourseJoinCodes(Resource):
the /courses/course_id/join_codes url, only an admin of a course can do this
"""

@login_required
@authorize_teacher_of_course
def get(self, course_id):
"""
This function will return all the join codes of a course
Expand Down
14 changes: 0 additions & 14 deletions backend/project/endpoints/courses/join_codes/join_codes_utils.py

This file was deleted.

58 changes: 53 additions & 5 deletions backend/project/endpoints/projects/project_detail.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,27 @@
for example /projects/1 if the project id of
the corresponding project is 1
"""
from os import getenv
import os
import zipfile
from urllib.parse import urljoin

from flask import request
from flask_restful import Resource

from project.db_in import db

from project.models.project import Project
from project.utils.query_agent import query_by_id_from_model, delete_by_id_from_model, \
patch_by_id_from_model
from project.utils.authentication import authorize_teacher_or_project_admin, authorize_teacher_of_project, authorize_project_visible
from project.utils.authentication import authorize_teacher_or_project_admin, \
authorize_teacher_of_project, authorize_project_visible

from project.endpoints.projects.endpoint_parser import parse_project_params

API_URL = getenv('API_HOST')
API_URL = os.getenv('API_HOST')
RESPONSE_URL = urljoin(API_URL, "projects")
UPLOAD_FOLDER = os.getenv('UPLOAD_URL')


class ProjectDetail(Resource):
"""
Expand Down Expand Up @@ -44,14 +52,54 @@ def patch(self, project_id):
Update method for updating a specific project
filtered by id of that specific project
"""
project_json = parse_project_params()

return patch_by_id_from_model(
output, status_code = patch_by_id_from_model(
Project,
"project_id",
project_id,
RESPONSE_URL,
request.json
project_json
)
if status_code != 200:
return output, status_code

if "assignment_file" in request.files:
file = request.files["assignment_file"]
filename = os.path.basename(file.filename)
project_upload_directory = os.path.join(f"{UPLOAD_FOLDER}", f"{project_id}")
os.makedirs(project_upload_directory, exist_ok=True)
try:
# remove the old file
try:
to_rem_files = os.listdir(project_upload_directory)
for to_rem_file in to_rem_files:
to_rem_file_path = os.path.join(project_upload_directory, to_rem_file)
if os.path.isfile(to_rem_file_path):
os.remove(to_rem_file_path)
except FileNotFoundError:
db.session.rollback()
return ({
"message": "Something went wrong deleting the old project files",
"url": f"{API_URL}/projects/{project_id}"
})

# removed all files now upload the new files
file.save(os.path.join(project_upload_directory, filename))
zip_location = os.path.join(project_upload_directory, filename)
with zipfile.ZipFile(zip_location) as upload_zip:
upload_zip.extractall(project_upload_directory)
project_json["assignment_file"] = filename
except zipfile.BadZipfile:
db.session.rollback()
return ({
"message":
"Please provide a valid .zip file for updating the instructions",
"url": f"{API_URL}/projects/{project_id}"
},
400)

return output, status_code

@authorize_teacher_of_project
def delete(self, project_id):
Expand Down
38 changes: 22 additions & 16 deletions backend/project/endpoints/projects/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from flask import request, jsonify
from flask_restful import Resource

from project.db_in import db

from project.models.project import Project
from project.utils.query_agent import query_selected_from_model, create_model_instance
from project.utils.authentication import authorize_teacher
Expand All @@ -18,6 +20,7 @@
API_URL = os.getenv('API_HOST')
UPLOAD_FOLDER = os.getenv('UPLOAD_URL')


class ProjectsEndpoint(Resource):
"""
Class for projects endpoints
Expand Down Expand Up @@ -47,10 +50,12 @@ def post(self, teacher_id=None):
using flask_restfull parse lib
"""

file = request.files["assignment_file"]
project_json = parse_project_params()
filename = os.path.basename(file.filename)
project_json["assignment_file"] = filename
filename = None
if "assignment_file" in request.files:
file = request.files["assignment_file"]
filename = os.path.basename(file.filename)
project_json["assignment_file"] = filename

# save the file that is given with the request
try:
Expand All @@ -73,20 +78,21 @@ def post(self, teacher_id=None):
return new_project, status_code

project_upload_directory = os.path.join(f"{UPLOAD_FOLDER}", f"{new_project.project_id}")

os.makedirs(project_upload_directory, exist_ok=True)

file.save(os.path.join(project_upload_directory, filename))
try:
with zipfile.ZipFile(os.path.join(project_upload_directory, filename)) as upload_zip:
upload_zip.extractall(project_upload_directory)
except zipfile.BadZipfile:
return ({
"message": "Please provide a .zip file for uploading the instructions",
"url": f"{API_URL}/projects"
},
400)

if filename is not None:
try:
file.save(os.path.join(project_upload_directory, filename))
zip_location = os.path.join(project_upload_directory, filename)
with zipfile.ZipFile(zip_location) as upload_zip:
upload_zip.extractall(project_upload_directory)
except zipfile.BadZipfile:
os.remove(os.path.join(project_upload_directory, filename))
db.session.rollback()
return ({
"message": "Please provide a .zip file for uploading the instructions",
"url": f"{API_URL}/projects"
},
400)
return {
"message": "Project created succesfully",
"data": new_project,
Expand Down
22 changes: 15 additions & 7 deletions backend/project/endpoints/submissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
from flask_restful import Resource
from sqlalchemy import exc
from project.db_in import db
from project.models.submission import Submission
from project.models.submission import Submission, SubmissionStatus
from project.models.project import Project
from project.models.user import User
from project.utils.files import filter_files, all_files_uploaded, zip_files
from project.utils.user import is_valid_user
from project.utils.project import is_valid_project
from project.utils.authentication import authorize_submission_request, authorize_submissions_request, authorize_grader, authorize_student_submission, authorize_submission_author
from project.utils.authentication import authorize_submission_request, \
authorize_submissions_request, authorize_grader, \
authorize_student_submission, authorize_submission_author

load_dotenv()
API_HOST = getenv("API_HOST")
Expand Down Expand Up @@ -119,7 +121,7 @@ def post(self) -> dict[str, any]:
zip_file.save(path.join(f"{UPLOAD_FOLDER}/", submission.submission_path))

# Submission status
submission.submission_status = False
submission.submission_status = SubmissionStatus.RUNNING

session.add(submission)
session.commit()
Expand Down Expand Up @@ -210,10 +212,16 @@ def patch(self, submission_id:int) -> dict[str, any]:
# Update the grading field
grading = request.form.get("grading")
if grading is not None:
if not (grading.isdigit() and 0 <= int(grading) <= 20):
data["message"] = "Invalid grading (grading=0-20)"
try:
grading_float = float(grading)
if 0 <= grading_float <= 20:
submission.grading = grading_float
else:
data["message"] = "Invalid grading (grading=0-20)"
return data, 400
except ValueError:
data["message"] = "Invalid grading (not a valid float)"
return data, 400
submission.grading = int(grading)

# Save the submission
session.commit()
Expand Down Expand Up @@ -276,4 +284,4 @@ def delete(self, submission_id: int) -> dict[str, any]:
submissions_bp.add_url_rule(
"/submissions/<int:submission_id>",
view_func=SubmissionEndpoint.as_view("submission")
)
)
Loading

0 comments on commit 5cf788a

Please sign in to comment.