diff --git a/backend/db_construct.sql b/backend/db_construct.sql index e3f6af41..347354fd 100644 --- a/backend/db_construct.sql +++ b/backend/db_construct.sql @@ -38,17 +38,19 @@ CREATE TABLE course_students ( PRIMARY KEY(course_id, uid) ); +CREATE TYPE deadline AS( + description TEXT, + deadline TIMESTAMP WITH TIME ZONE +); + CREATE TABLE projects ( project_id INT GENERATED ALWAYS AS IDENTITY, title VARCHAR(50) NOT NULL, description TEXT NOT NULL, - assignment_file VARCHAR(50), - deadline TIMESTAMP WITH TIME ZONE, + deadlines deadline[], course_id INT NOT NULL, visible_for_students BOOLEAN NOT NULL, archived BOOLEAN NOT NULL, - test_path VARCHAR(50), - script_name VARCHAR(50), regex_expressions VARCHAR(50)[], PRIMARY KEY(project_id), CONSTRAINT fk_course FOREIGN KEY(course_id) REFERENCES courses(course_id) ON DELETE CASCADE diff --git a/backend/project/endpoints/projects/endpoint_parser.py b/backend/project/endpoints/projects/endpoint_parser.py index d9737826..f4ab93ea 100644 --- a/backend/project/endpoints/projects/endpoint_parser.py +++ b/backend/project/endpoints/projects/endpoint_parser.py @@ -2,6 +2,7 @@ Parser for the argument when posting or patching a project """ +import json from flask_restful import reqparse from werkzeug.datastructures import FileStorage @@ -14,7 +15,7 @@ help='Projects assignment file', location="form" ) -parser.add_argument("deadline", type=str, help='Projects deadline', location="form") +parser.add_argument('deadlines', type=str, help='Projects deadlines', location="form") parser.add_argument("course_id", type=str, help='Projects course_id', location="form") parser.add_argument( "visible_for_students", @@ -23,8 +24,6 @@ location="form" ) parser.add_argument("archived", type=bool, help='Projects', location="form") -parser.add_argument("test_path", type=str, help='Projects test path', location="form") -parser.add_argument("script_name", type=str, help='Projects test script path', location="form") parser.add_argument( "regex_expressions", type=str, @@ -39,9 +38,20 @@ def parse_project_params(): """ args = parser.parse_args() result_dict = {} - for key, value in args.items(): if value is not None: - result_dict[key] = value + if "deadlines" == key: + deadlines_parsed = json.loads(value) + new_deadlines = [] + for deadline in deadlines_parsed: + new_deadlines.append( + ( + deadline["description"], + deadline["deadline"] + ) + ) + result_dict[key] = new_deadlines + else: + result_dict[key] = value return result_dict diff --git a/backend/project/endpoints/projects/project_detail.py b/backend/project/endpoints/projects/project_detail.py index 060587c7..d2affa57 100644 --- a/backend/project/endpoints/projects/project_detail.py +++ b/backend/project/endpoints/projects/project_detail.py @@ -89,7 +89,7 @@ def patch(self, project_id): 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 ({ diff --git a/backend/project/endpoints/projects/projects.py b/backend/project/endpoints/projects/projects.py index 835e692d..01873379 100644 --- a/backend/project/endpoints/projects/projects.py +++ b/backend/project/endpoints/projects/projects.py @@ -38,7 +38,7 @@ def get(self, teacher_id=None): return query_selected_from_model( Project, response_url, - select_values=["project_id", "title", "description", "deadline"], + select_values=["project_id", "title", "description", "deadlines"], url_mapper={"project_id": response_url}, filters=request.args ) @@ -55,7 +55,6 @@ def post(self, teacher_id=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: @@ -81,9 +80,9 @@ def post(self, teacher_id=None): os.makedirs(project_upload_directory, exist_ok=True) 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: + file_path = os.path.join(project_upload_directory, filename) + file.save(file_path) + with zipfile.ZipFile(file_path) as upload_zip: upload_zip.extractall(project_upload_directory) except zipfile.BadZipfile: os.remove(os.path.join(project_upload_directory, filename)) diff --git a/backend/project/models/project.py b/backend/project/models/project.py index 8ba901ff..624f9ed0 100644 --- a/backend/project/models/project.py +++ b/backend/project/models/project.py @@ -2,6 +2,7 @@ from dataclasses import dataclass from sqlalchemy import ARRAY, Boolean, Column, DateTime, ForeignKey, Integer, String, Text +from sqlalchemy_utils import CompositeType from project.db_in import db @dataclass @@ -23,11 +24,18 @@ class Project(db.Model): # pylint: disable=too-many-instance-attributes project_id: int = Column(Integer, primary_key=True) title: str = Column(String(50), nullable=False, unique=False) description: str = Column(Text, nullable=False) - assignment_file: str = Column(String(50)) - deadline: str = Column(DateTime(timezone=True)) + deadlines: list = Column(ARRAY( + CompositeType( + "deadline", + [ + Column("description", Text), + Column("deadline", DateTime(timezone=True)) + ] + ), + dimensions=1 + ) + ) course_id: int = Column(Integer, ForeignKey("courses.course_id"), nullable=False) visible_for_students: bool = Column(Boolean, nullable=False) archived: bool = Column(Boolean, nullable=False) - test_path: str = Column(String(50)) - script_name: str = Column(String(50)) regex_expressions: list[str] = Column(ARRAY(String(50))) diff --git a/backend/requirements.txt b/backend/requirements.txt index f47e98e6..0c8d687f 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -2,6 +2,7 @@ flask~=3.0.2 flask-cors flask-restful flask-sqlalchemy +sqlalchemy_utils python-dotenv~=1.0.1 psycopg2-binary pytest~=8.0.1 diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index a7cc092b..f7bacfa3 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -69,25 +69,19 @@ def projects(session): Project( title="B+ Trees", description="Implement B+ trees", - assignment_file="assignement.pdf", - deadline=datetime(2024,3,15,13,0,0), + deadlines=[("Deadline 1",datetime(2024,3,15,13,0,0))], course_id=course_id_ad3, visible_for_students=True, archived=False, - test_path="/tests", - script_name="script.sh", regex_expressions=["solution"] ), Project( title="Predicaten", description="Predicaten project", - assignment_file="assignment.pdf", - deadline=datetime(2023,3,15,13,0,0), + deadlines=[("Deadline 1", datetime(2023,3,15,13,0,0))], course_id=course_id_raf, visible_for_students=False, archived=True, - test_path="/tests", - script_name="script.sh", regex_expressions=[".*"] ) ] diff --git a/backend/tests/endpoints/conftest.py b/backend/tests/endpoints/conftest.py index fd46dd8a..401de3d0 100644 --- a/backend/tests/endpoints/conftest.py +++ b/backend/tests/endpoints/conftest.py @@ -190,13 +190,10 @@ def valid_project(valid_course_entry): data = { "title": "Project", "description": "Test project", - "assignment_file": "testfile", - "deadline": "2024-02-25T12:00:00", + "deadlines": [{"deadline": "2024-02-25T12:00:00", "description": "Deadline 1"}], "course_id": valid_course_entry.course_id, "visible_for_students": True, "archived": False, - "test_path": "tests", - "script_name": "script.sh", "regex_expressions": ["*.pdf", "*.txt"] } return data diff --git a/backend/tests/endpoints/project_test.py b/backend/tests/endpoints/project_test.py index 46f7bcbc..510e24ce 100644 --- a/backend/tests/endpoints/project_test.py +++ b/backend/tests/endpoints/project_test.py @@ -1,10 +1,13 @@ """Tests for project endpoints.""" +import json + def test_assignment_download(client, valid_project): """ Method for assignment download """ + valid_project["deadlines"] = json.dumps(valid_project["deadlines"]) with open("tests/resources/testzip.zip", "rb") as zip_file: valid_project["assignment_file"] = zip_file # post the project @@ -48,6 +51,7 @@ def test_getting_all_projects(client): def test_post_project(client, valid_project): """Test posting a project to the database and testing if it's present""" + valid_project["deadlines"] = json.dumps(valid_project["deadlines"]) with open("tests/resources/testzip.zip", "rb") as zip_file: valid_project["assignment_file"] = zip_file # post the project diff --git a/backend/tests/models/project_test.py b/backend/tests/models/project_test.py index b99a6134..cda7c562 100644 --- a/backend/tests/models/project_test.py +++ b/backend/tests/models/project_test.py @@ -16,13 +16,10 @@ def test_create_project(self, session: Session): project = Project( title="Pigeonhole", description="A new project", - assignment_file="assignment.pdf", - deadline=datetime(2024,12,31,23,59,59), + deadlines=[("Deadline 1", datetime(2024,12,31,23,59,59))], course_id=course.course_id, visible_for_students=True, archived=False, - test_path="/test", - script_name="script", regex_expressions=[r".*"] ) session.add(project)