From 6b0928893bddfd9da0476bd65ef0bd59827f46ae Mon Sep 17 00:00:00 2001 From: Matthias Seghers Date: Tue, 27 Feb 2024 19:13:00 +0100 Subject: [PATCH 01/13] savepoint --- backend/app.py | 19 ++++++------------- backend/db/database.py | 15 +++++++++++++++ backend/db/extensions.py | 16 +++++++++++----- backend/requirements.txt | 3 +++ backend/routes/teachers.py | 17 +++++++++-------- 5 files changed, 44 insertions(+), 26 deletions(-) create mode 100644 backend/db/database.py diff --git a/backend/app.py b/backend/app.py index efaf32bc..bd0e627b 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1,24 +1,17 @@ import os -from flask import Flask +import uvicorn +from fastapi import FastAPI from db.extensions import db -from routes.teachers import teachers_blueprint +from routes.teachers import teachers_router -app = Flask(__name__) +app = FastAPI() # Koppel routes uit andere modules. -app.register_blueprint(teachers_blueprint) +app.include_router(teachers_router, prefix="/teachers") -db_host = os.getenv("DB_HOST", "localhost") -db_port = os.getenv("DB_PORT", "5432") -db_user = os.getenv("DB_USERNAME", "postgres") -db_password = os.getenv("DB_PASSWORD", "postgres") -db_database = os.getenv("DB_DATABASE", "delphi") - -# Koppel postgres uri en db aan app instantie -app.config["SQLALCHEMY_DATABASE_URI"] = f"postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_database}" db.init_app(app) if __name__ == "__main__": - app.run(debug=True) + uvicorn.run("fastapi_code:app") diff --git a/backend/db/database.py b/backend/db/database.py new file mode 100644 index 00000000..01dfa787 --- /dev/null +++ b/backend/db/database.py @@ -0,0 +1,15 @@ + +from db.extensions import DBSession + + +def get_session() -> DBSession: + """ + Maakt een nieuwe database sessie aan. + :return: De nieuwe sessie. + """ + session = DBSession() + # Use "yield" and "finally" to close the session when it's no longer needed + try: + yield session + finally: + session.close() diff --git a/backend/db/extensions.py b/backend/db/extensions.py index 86ff1dad..fe86fc85 100644 --- a/backend/db/extensions.py +++ b/backend/db/extensions.py @@ -1,9 +1,15 @@ -from flask_sqlalchemy import SQLAlchemy -from sqlalchemy.orm import DeclarativeBase +import os +from sqlalchemy.orm import sessionmaker +from sqlalchemy import create_engine -class Base(DeclarativeBase): - pass +db_host = os.getenv("DB_HOST", "localhost") +db_port = os.getenv("DB_PORT", "5432") +db_user = os.getenv("DB_USERNAME", "postgres") +db_password = os.getenv("DB_PASSWORD", "postgres") +db_database = os.getenv("DB_DATABASE", "delphi") +DB_URI = f"postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_database}" +engine = create_engine(DB_URI) -db = SQLAlchemy(model_class=Base) +DBSession = sessionmaker(autocommit=False, autoflush=False, bind=engine) diff --git a/backend/requirements.txt b/backend/requirements.txt index 0e9c38d8..e098c5f2 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,7 +1,10 @@ blinker==1.7.0 +fastapi==0.110.0 +uvicorn==0.27.1 click==8.1.7 Flask==3.0.2 Flask-SQLAlchemy==3.1.1 +FastAPI-SQLAlchemy-improved==0.5.4 greenlet==3.0.3 itsdangerous==2.1.2 Jinja2==3.1.3 diff --git a/backend/routes/teachers.py b/backend/routes/teachers.py index d35d4737..6581e46d 100644 --- a/backend/routes/teachers.py +++ b/backend/routes/teachers.py @@ -1,7 +1,7 @@ import json from http import HTTPStatus -from flask import Blueprint, Response, request +from fastapi import APIRouter, Response, Request from db.implementation.SqlLesgeverDAO import SqlTeacherDAO from db.interface.TeacherDAO import TeacherDAO @@ -9,10 +9,11 @@ from domain.validation.TeacherValidator import TeacherValidator from domain.validation.ValidationResult import ValidationResult -teachers_blueprint = Blueprint("teachers", __name__) +# teachers_blueprint = Blueprint("teachers", __name__) +teachers_router = APIRouter() -@teachers_blueprint.route("/teachers") +@teachers_router.get("/teachers") def get_teachers(): dao: TeacherDAO = SqlTeacherDAO() @@ -22,18 +23,18 @@ def get_teachers(): return Response(json.dumps(teachers_json, indent=4), content_type="application/json") -@teachers_blueprint.route("/teachers/") -def get_teacher(teacher_id): +@teachers_router.get("/teachers/{teacher_id}") +def get_teacher(teacher_id: int): dao: TeacherDAO = SqlTeacherDAO() teacher: TeacherDataclass = dao.get_teacher(teacher_id) teacher_json = teacher.to_dict() - return Response(json.dumps(teacher_json, indent=4), content_type="application/json") + return teacher_json -@teachers_blueprint.route("/teachers", methods=["POST"]) -def create_teacher(): +@teachers_router.post("/teachers") +def create_teacher(request: Request): teacher_data: dict = request.get_json() if not teacher_data: From 8f93477d925eaffe3d2ed1f1a70d21b9b33ecb89 Mon Sep 17 00:00:00 2001 From: Matthias Seghers Date: Tue, 27 Feb 2024 19:43:23 +0100 Subject: [PATCH 02/13] intial test only in teachers and related things --- backend/app.py | 5 --- backend/db/database.py | 1 - backend/domain/models/UserDataclass.py | 7 ++-- backend/domain/models/base_model.py | 1 + backend/domain/validation/TeacherValidator.py | 1 + backend/routes/teachers.py | 32 ++++++++----------- 6 files changed, 20 insertions(+), 27 deletions(-) diff --git a/backend/app.py b/backend/app.py index bd0e627b..e66c8560 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1,9 +1,6 @@ -import os - import uvicorn from fastapi import FastAPI -from db.extensions import db from routes.teachers import teachers_router app = FastAPI() @@ -11,7 +8,5 @@ # Koppel routes uit andere modules. app.include_router(teachers_router, prefix="/teachers") -db.init_app(app) - if __name__ == "__main__": uvicorn.run("fastapi_code:app") diff --git a/backend/db/database.py b/backend/db/database.py index 01dfa787..b6ae4fd9 100644 --- a/backend/db/database.py +++ b/backend/db/database.py @@ -1,4 +1,3 @@ - from db.extensions import DBSession diff --git a/backend/domain/models/UserDataclass.py b/backend/domain/models/UserDataclass.py index 1a0696a7..f92b3f75 100644 --- a/backend/domain/models/UserDataclass.py +++ b/backend/domain/models/UserDataclass.py @@ -1,10 +1,11 @@ from dataclasses import dataclass -from domain.models.base_model import JsonRepresentable +from pydantic import BaseModel @dataclass() -class UserDataclass(JsonRepresentable): - id: int +class UserDataclass(BaseModel): + id: int | None # needs to be optional because it is not known when creating a new user otherwise + # pydanitc will throw an error when creating a new user name: str email: str diff --git a/backend/domain/models/base_model.py b/backend/domain/models/base_model.py index 9ab89131..0356cadd 100644 --- a/backend/domain/models/base_model.py +++ b/backend/domain/models/base_model.py @@ -2,6 +2,7 @@ from dataclasses import dataclass +# what is the use of this class when one can use pydantic models? @dataclass() class JsonRepresentable: def to_dict(self) -> dict: diff --git a/backend/domain/validation/TeacherValidator.py b/backend/domain/validation/TeacherValidator.py index 02d39b88..0f57aa15 100644 --- a/backend/domain/validation/TeacherValidator.py +++ b/backend/domain/validation/TeacherValidator.py @@ -1,6 +1,7 @@ from domain.validation.ValidationResult import ValidationError, ValidationResult, ValidationSuccess +# deze klassen zijn niet meer nodig omdat we gebruik maken van pydantic en FastAPI class TeacherValidator: @staticmethod def validate(json_data: dict) -> ValidationResult: diff --git a/backend/routes/teachers.py b/backend/routes/teachers.py index 2bd1ae30..62e72b20 100644 --- a/backend/routes/teachers.py +++ b/backend/routes/teachers.py @@ -1,15 +1,10 @@ -import json -from http import HTTPStatus -from fastapi import APIRouter, Response, Request +from fastapi import APIRouter from db.implementation.SqlTeacherDAO import SqlTeacherDAO from db.interface.TeacherDAO import TeacherDAO from domain.models.TeacherDataclass import TeacherDataclass -from domain.validation.TeacherValidator import TeacherValidator -from domain.validation.ValidationResult import ValidationResult -# teachers_blueprint = Blueprint("teachers", __name__) teachers_router = APIRouter() @@ -20,7 +15,7 @@ def get_teachers(): teachers: list[TeacherDataclass] = dao.get_all_teachers() teachers_json = [teacher.to_dict() for teacher in teachers] - return Response(json.dumps(teachers_json, indent=4), content_type="application/json") + return teachers_json @teachers_router.get("/teachers/{teacher_id}") @@ -34,19 +29,20 @@ def get_teacher(teacher_id: int): @teachers_router.post("/teachers") -def create_teacher(request: Request): - teacher_data: dict = request.get_json() +def create_teacher(teacher_data: TeacherDataclass): - if not teacher_data: - return Response(json.dumps({"error": "Foute JSON of Content-Type"}), status=HTTPStatus.BAD_REQUEST) + # can be commented because of the validation that happens through pydantic and FastAPI + # if not teacher_data: + # return Response(json.dumps({"error": "Foute JSON of Content-Type"}), status=HTTPStatus.BAD_REQUEST) - validation_result: ValidationResult = TeacherValidator.validate(teacher_data) - - if not validation_result: - return Response(json.dumps({"error": validation_result.errors}), status=HTTPStatus.BAD_REQUEST) + # validation_result: ValidationResult = TeacherValidator.validate(teacher_data) + # + # if not validation_result: + # return Response(json.dumps({"error": validation_result.errors}), status=HTTPStatus.BAD_REQUEST) dao: TeacherDAO = SqlTeacherDAO() - lesgever = TeacherDataclass(**teacher_data) # Vul alle velden van het dataobject in met de json - dao.create_teacher(lesgever.name, lesgever.email) + # is niet meer nodig omdat teacher_data een instance is van TeacherDataclass + # lesgever = TeacherDataclass(**teacher_data) # Vul alle velden van het dataobject in met de json + dao.create_teacher(teacher_data.name, teacher_data.email) - return Response(json.dumps(lesgever.to_dict()), status=HTTPStatus.CREATED) + return teacher_data From 2a42fbf243ae460182140dd9c73f618f71598a9a Mon Sep 17 00:00:00 2001 From: Matthias Seghers Date: Tue, 27 Feb 2024 19:49:28 +0100 Subject: [PATCH 03/13] ruff is happy now --- backend/db/extensions.py | 2 +- backend/routes/teachers.py | 31 +++++++++++-------------------- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/backend/db/extensions.py b/backend/db/extensions.py index fe86fc85..2df44c0d 100644 --- a/backend/db/extensions.py +++ b/backend/db/extensions.py @@ -1,7 +1,7 @@ import os -from sqlalchemy.orm import sessionmaker from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker db_host = os.getenv("DB_HOST", "localhost") db_port = os.getenv("DB_PORT", "5432") diff --git a/backend/routes/teachers.py b/backend/routes/teachers.py index 62e72b20..699093d5 100644 --- a/backend/routes/teachers.py +++ b/backend/routes/teachers.py @@ -9,40 +9,31 @@ @teachers_router.get("/teachers") -def get_teachers(): +def get_teachers() -> list[TeacherDataclass]: dao: TeacherDAO = SqlTeacherDAO() - - teachers: list[TeacherDataclass] = dao.get_all_teachers() - teachers_json = [teacher.to_dict() for teacher in teachers] - - return teachers_json + return dao.get_all_teachers() @teachers_router.get("/teachers/{teacher_id}") -def get_teacher(teacher_id: int): +def get_teacher(teacher_id: int) -> TeacherDataclass: dao: TeacherDAO = SqlTeacherDAO() - - teacher: TeacherDataclass = dao.get_teacher(teacher_id) - teacher_json = teacher.to_dict() - - return teacher_json + return dao.get_teacher(teacher_id) @teachers_router.post("/teachers") -def create_teacher(teacher_data: TeacherDataclass): +def create_teacher(teacher_data: TeacherDataclass) -> TeacherDataclass: # can be commented because of the validation that happens through pydantic and FastAPI - # if not teacher_data: - # return Response(json.dumps({"error": "Foute JSON of Content-Type"}), status=HTTPStatus.BAD_REQUEST) + # woordjes if not teacher_data: + # woordjes return Response(json.dumps({"error": "Foute JSON of Content-Type"}), status=HTTPStatus.BAD_REQUEST) - # validation_result: ValidationResult = TeacherValidator.validate(teacher_data) + # woordjes validation_result: ValidationResult = TeacherValidator.validate(teacher_data) # - # if not validation_result: - # return Response(json.dumps({"error": validation_result.errors}), status=HTTPStatus.BAD_REQUEST) + # woordjes if not validation_result: + # woordjes return Response(json.dumps({"error": validation_result.errors}), status=HTTPStatus.BAD_REQUEST) dao: TeacherDAO = SqlTeacherDAO() # is niet meer nodig omdat teacher_data een instance is van TeacherDataclass - # lesgever = TeacherDataclass(**teacher_data) # Vul alle velden van het dataobject in met de json + # woordjes lesgever = TeacherDataclass(**teacher_data) # Vul alle velden van het dataobject in met de json dao.create_teacher(teacher_data.name, teacher_data.email) - return teacher_data From 7040b563b844e7bec0472304dee38b3cdcfd8aa4 Mon Sep 17 00:00:00 2001 From: Matthias Seghers Date: Tue, 27 Feb 2024 21:30:31 +0100 Subject: [PATCH 04/13] is pyright nu happy? --- backend/db/database.py | 14 ---- backend/db/extensions.py | 5 +- backend/db/implementation/SqlSubjectDAO.py | 2 +- backend/db/implementation/SqlTeacherDAO.py | 2 +- backend/domain/models/TeacherDataclass.py | 7 +- backend/domain/models/UserDataclass.py | 8 ++- backend/fill_database_mock.py | 80 +++++++++++----------- backend/routes/teachers.py | 7 +- 8 files changed, 60 insertions(+), 65 deletions(-) delete mode 100644 backend/db/database.py diff --git a/backend/db/database.py b/backend/db/database.py deleted file mode 100644 index b6ae4fd9..00000000 --- a/backend/db/database.py +++ /dev/null @@ -1,14 +0,0 @@ -from db.extensions import DBSession - - -def get_session() -> DBSession: - """ - Maakt een nieuwe database sessie aan. - :return: De nieuwe sessie. - """ - session = DBSession() - # Use "yield" and "finally" to close the session when it's no longer needed - try: - yield session - finally: - session.close() diff --git a/backend/db/extensions.py b/backend/db/extensions.py index 2df44c0d..ad57b51b 100644 --- a/backend/db/extensions.py +++ b/backend/db/extensions.py @@ -1,7 +1,8 @@ import os +from fastapi_sqlalchemy import SQLAlchemy from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker + db_host = os.getenv("DB_HOST", "localhost") db_port = os.getenv("DB_PORT", "5432") @@ -12,4 +13,4 @@ DB_URI = f"postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_database}" engine = create_engine(DB_URI) -DBSession = sessionmaker(autocommit=False, autoflush=False, bind=engine) +db = SQLAlchemy(custom_engine=engine) diff --git a/backend/db/implementation/SqlSubjectDAO.py b/backend/db/implementation/SqlSubjectDAO.py index 40e59f10..388e0704 100644 --- a/backend/db/implementation/SqlSubjectDAO.py +++ b/backend/db/implementation/SqlSubjectDAO.py @@ -1,8 +1,8 @@ from db.errors.database_errors import ItemNotFoundError, UniqueConstraintError -from db.extensions import db from db.interface.SubjectDAO import SubjectDAO from db.models.models import Student, Subject, Teacher from domain.models.SubjectDataclass import SubjectDataclass +from db.extensions import db class SqlSubjectDAO(SubjectDAO): diff --git a/backend/db/implementation/SqlTeacherDAO.py b/backend/db/implementation/SqlTeacherDAO.py index 9623cb3e..8f55ee5f 100644 --- a/backend/db/implementation/SqlTeacherDAO.py +++ b/backend/db/implementation/SqlTeacherDAO.py @@ -1,8 +1,8 @@ from sqlalchemy import select from db.errors.database_errors import ItemNotFoundError -from db.extensions import db from db.interface.TeacherDAO import TeacherDAO +from db.extensions import db from db.models.models import Teacher from domain.models.TeacherDataclass import TeacherDataclass diff --git a/backend/domain/models/TeacherDataclass.py b/backend/domain/models/TeacherDataclass.py index d36d1215..0b11f10b 100644 --- a/backend/domain/models/TeacherDataclass.py +++ b/backend/domain/models/TeacherDataclass.py @@ -1,8 +1,13 @@ from dataclasses import dataclass -from domain.models.UserDataclass import UserDataclass +from domain.models.UserDataclass import UserDataclass, UserDataClassRequest @dataclass() class TeacherDataclass(UserDataclass): pass + + +@dataclass() +class TeacherDataClassRequest(UserDataClassRequest): + pass diff --git a/backend/domain/models/UserDataclass.py b/backend/domain/models/UserDataclass.py index f92b3f75..2798c255 100644 --- a/backend/domain/models/UserDataclass.py +++ b/backend/domain/models/UserDataclass.py @@ -5,7 +5,13 @@ @dataclass() class UserDataclass(BaseModel): - id: int | None # needs to be optional because it is not known when creating a new user otherwise + id: int # pydanitc will throw an error when creating a new user name: str email: str + + +@dataclass() +class UserDataClassRequest(BaseModel): + name: str + email: str diff --git a/backend/fill_database_mock.py b/backend/fill_database_mock.py index 0981928a..a89f3574 100644 --- a/backend/fill_database_mock.py +++ b/backend/fill_database_mock.py @@ -2,7 +2,6 @@ from psycopg2 import tz -from app import app from db.extensions import db from db.implementation.SqlAdminDAO import SqlAdminDAO from db.implementation.SqlGroupDAO import SqlGroupDAO @@ -12,43 +11,42 @@ from db.implementation.SqlTeacherDAO import SqlTeacherDAO if __name__ == "__main__": - with app.app_context(): - db.create_all() - - admin_dao = SqlAdminDAO() - student_dao = SqlStudentDAO() - teacher_dao = SqlTeacherDAO() - subject_dao = SqlSubjectDAO() - group_dao = SqlGroupDAO() - project_dao = SqlProjectDAO() - - # maak een vak - objeprog = subject_dao.create_subject(name="OBJECTGERICHTPROGRAMMEREN") - - # maak een project voor dat vak - objeprog_project = project_dao.create_project(subject_id=objeprog.id, - name="PROJECT", - archived=False, - visible=True, - requirements="Maak iets in javafx", - max_students=2, - deadline=datetime(2000, 1, 1, 0, 0, 0, tzinfo=tz.LOCAL)) - - # maak een groepje voor het project van objeprog - groep1 = group_dao.create_group(project_id=objeprog_project.id) - - # maak studenten - student1 = student_dao.create_student("Student1", "Student1@gmail.com") - student2 = student_dao.create_student("Student2", "Student2@gmail.com") - student3 = student_dao.create_student("Student3", "Student3@gmail.com") - - # maak teacher - teacher1 = teacher_dao.create_teacher("Teacher1", "Teacher1@gmail.com") - - # voeg teacher toe aan objeprog - subject_dao.add_teacher_to_subject(teacher1.id, objeprog.id) - - # voeg studenten toe aan de groep - group_dao.add_student_to_group(student1.id, groep1.id) - group_dao.add_student_to_group(student2.id, groep1.id) - group_dao.add_student_to_group(student3.id, groep1.id) + db.create_all() + + admin_dao = SqlAdminDAO() + student_dao = SqlStudentDAO() + teacher_dao = SqlTeacherDAO() + subject_dao = SqlSubjectDAO() + group_dao = SqlGroupDAO() + project_dao = SqlProjectDAO() + + # maak een vak + objeprog = subject_dao.create_subject(name="OBJECTGERICHTPROGRAMMEREN") + + # maak een project voor dat vak + objeprog_project = project_dao.create_project(subject_id=objeprog.id, + name="PROJECT", + archived=False, + visible=True, + requirements="Maak iets in javafx", + max_students=2, + deadline=datetime(2000, 1, 1, 0, 0, 0, tzinfo=tz.LOCAL)) + + # maak een groepje voor het project van objeprog + groep1 = group_dao.create_group(project_id=objeprog_project.id) + + # maak studenten + student1 = student_dao.create_student("Student1", "Student1@gmail.com") + student2 = student_dao.create_student("Student2", "Student2@gmail.com") + student3 = student_dao.create_student("Student3", "Student3@gmail.com") + + # maak teacher + teacher1 = teacher_dao.create_teacher("Teacher1", "Teacher1@gmail.com") + + # voeg teacher toe aan objeprog + subject_dao.add_teacher_to_subject(teacher1.id, objeprog.id) + + # voeg studenten toe aan de groep + group_dao.add_student_to_group(student1.id, groep1.id) + group_dao.add_student_to_group(student2.id, groep1.id) + group_dao.add_student_to_group(student3.id, groep1.id) diff --git a/backend/routes/teachers.py b/backend/routes/teachers.py index 699093d5..e68b8206 100644 --- a/backend/routes/teachers.py +++ b/backend/routes/teachers.py @@ -3,7 +3,7 @@ from db.implementation.SqlTeacherDAO import SqlTeacherDAO from db.interface.TeacherDAO import TeacherDAO -from domain.models.TeacherDataclass import TeacherDataclass +from domain.models.TeacherDataclass import TeacherDataclass, TeacherDataClassRequest teachers_router = APIRouter() @@ -21,7 +21,7 @@ def get_teacher(teacher_id: int) -> TeacherDataclass: @teachers_router.post("/teachers") -def create_teacher(teacher_data: TeacherDataclass) -> TeacherDataclass: +def create_teacher(teacher_data: TeacherDataClassRequest) -> TeacherDataclass: # can be commented because of the validation that happens through pydantic and FastAPI # woordjes if not teacher_data: @@ -35,5 +35,4 @@ def create_teacher(teacher_data: TeacherDataclass) -> TeacherDataclass: dao: TeacherDAO = SqlTeacherDAO() # is niet meer nodig omdat teacher_data een instance is van TeacherDataclass # woordjes lesgever = TeacherDataclass(**teacher_data) # Vul alle velden van het dataobject in met de json - dao.create_teacher(teacher_data.name, teacher_data.email) - return teacher_data + return dao.create_teacher(teacher_data.name, teacher_data.email) From a15d1d31eaf48c9df8540c0db7aef1c47e2711aa Mon Sep 17 00:00:00 2001 From: Matthias Seghers Date: Tue, 27 Feb 2024 21:31:29 +0100 Subject: [PATCH 05/13] is pyright nu happy? en ruff? --- backend/db/extensions.py | 1 - backend/db/implementation/SqlSubjectDAO.py | 2 +- backend/db/implementation/SqlTeacherDAO.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/backend/db/extensions.py b/backend/db/extensions.py index ad57b51b..3b59d0cd 100644 --- a/backend/db/extensions.py +++ b/backend/db/extensions.py @@ -3,7 +3,6 @@ from fastapi_sqlalchemy import SQLAlchemy from sqlalchemy import create_engine - db_host = os.getenv("DB_HOST", "localhost") db_port = os.getenv("DB_PORT", "5432") db_user = os.getenv("DB_USERNAME", "postgres") diff --git a/backend/db/implementation/SqlSubjectDAO.py b/backend/db/implementation/SqlSubjectDAO.py index 388e0704..40e59f10 100644 --- a/backend/db/implementation/SqlSubjectDAO.py +++ b/backend/db/implementation/SqlSubjectDAO.py @@ -1,8 +1,8 @@ from db.errors.database_errors import ItemNotFoundError, UniqueConstraintError +from db.extensions import db from db.interface.SubjectDAO import SubjectDAO from db.models.models import Student, Subject, Teacher from domain.models.SubjectDataclass import SubjectDataclass -from db.extensions import db class SqlSubjectDAO(SubjectDAO): diff --git a/backend/db/implementation/SqlTeacherDAO.py b/backend/db/implementation/SqlTeacherDAO.py index 8f55ee5f..9623cb3e 100644 --- a/backend/db/implementation/SqlTeacherDAO.py +++ b/backend/db/implementation/SqlTeacherDAO.py @@ -1,8 +1,8 @@ from sqlalchemy import select from db.errors.database_errors import ItemNotFoundError -from db.interface.TeacherDAO import TeacherDAO from db.extensions import db +from db.interface.TeacherDAO import TeacherDAO from db.models.models import Teacher from domain.models.TeacherDataclass import TeacherDataclass From ede6c1f1f1501a8f15476761412369cc49cf455c Mon Sep 17 00:00:00 2001 From: Matthias Seghers Date: Wed, 28 Feb 2024 20:20:04 +0100 Subject: [PATCH 06/13] bijna heel fastAPI geintegreerd + DAOs zouden bijna af moeten zijn --- backend/db/extensions.py | 6 +- backend/db/implementation/SqlAbstractDAO.py | 35 +++++ backend/db/implementation/SqlAdminDAO.py | 28 ++-- backend/db/implementation/SqlGroupDAO.py | 111 +++++++-------- backend/db/implementation/SqlProjectDAO.py | 71 +++++----- backend/db/implementation/SqlStudentDAO.py | 36 ++--- backend/db/implementation/SqlSubjectDAO.py | 126 ++++++++++-------- backend/db/implementation/SqlSubmissionDAO.py | 78 +++++------ backend/db/implementation/SqlTeacherDAO.py | 38 +++--- backend/db/implementation/SqlUserDAO.py | 23 ++-- backend/db/interface/AbstractDAO.py | 20 +++ backend/db/interface/AdminDAO.py | 20 --- backend/db/interface/GroupDAO.py | 26 ++-- backend/db/interface/ProjectDAO.py | 22 ++- backend/db/interface/StudentDAO.py | 27 +--- backend/db/interface/SubjectDAO.py | 30 ++--- backend/db/interface/SubmissionDAO.py | 25 ++-- backend/db/interface/TeacherDAO.py | 27 +--- backend/db/interface/UserDAO.py | 26 +--- backend/db/models/models.py | 28 ++-- backend/fill_database_mock.py | 8 +- backend/pyproject.toml | 3 +- backend/routes/teachers.py | 4 +- backend/tests/teachers_test.py | 8 +- 24 files changed, 410 insertions(+), 416 deletions(-) create mode 100644 backend/db/implementation/SqlAbstractDAO.py create mode 100644 backend/db/interface/AbstractDAO.py diff --git a/backend/db/extensions.py b/backend/db/extensions.py index 3b59d0cd..137d3c8a 100644 --- a/backend/db/extensions.py +++ b/backend/db/extensions.py @@ -1,7 +1,7 @@ import os -from fastapi_sqlalchemy import SQLAlchemy from sqlalchemy import create_engine +from sqlalchemy.orm import DeclarativeBase db_host = os.getenv("DB_HOST", "localhost") db_port = os.getenv("DB_PORT", "5432") @@ -12,4 +12,6 @@ DB_URI = f"postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_database}" engine = create_engine(DB_URI) -db = SQLAlchemy(custom_engine=engine) + +class Base(DeclarativeBase): + pass diff --git a/backend/db/implementation/SqlAbstractDAO.py b/backend/db/implementation/SqlAbstractDAO.py new file mode 100644 index 00000000..3715e2b1 --- /dev/null +++ b/backend/db/implementation/SqlAbstractDAO.py @@ -0,0 +1,35 @@ +from sqlalchemy import select +from abc import ABC + +from sqlalchemy.orm import Session + +from db.errors.database_errors import ItemNotFoundError +from db.extensions import engine +from typing import Generic, TypeVar, Type + +from db.models.models import AbstractModel + +T = TypeVar("T", bound=AbstractModel) +D = TypeVar("D") + + +class SqlAbstractDAO(Generic[T, D], ABC): + + @staticmethod + def get_object(ident: int) -> D: + with Session(engine) as session: + generic_object: T | None = session.get(T, ident) + + if not generic_object: + msg = f"object with id {ident} not found" + raise ItemNotFoundError(msg) + + return generic_object.to_domain_model() + + @staticmethod + def get_all() -> list[D]: + with Session(engine) as session: + generic_objects: list[T] = list(session.scalars(select(T)).all()) + return [generic_object.to_domain_model() for generic_object in generic_objects] + + diff --git a/backend/db/implementation/SqlAdminDAO.py b/backend/db/implementation/SqlAdminDAO.py index 2aff92ec..19f1273d 100644 --- a/backend/db/implementation/SqlAdminDAO.py +++ b/backend/db/implementation/SqlAdminDAO.py @@ -1,27 +1,17 @@ -from sqlalchemy import select +from sqlalchemy.orm import Session -from db.errors.database_errors import ItemNotFoundError -from db.extensions import db +from db.extensions import engine +from db.implementation.SqlAbstractDAO import SqlAbstractDAO from db.interface.AdminDAO import AdminDAO from db.models.models import Admin from domain.models.AdminDataclass import AdminDataclass -class SqlAdminDAO(AdminDAO): - def get_admin(self, ident: int) -> AdminDataclass: - admin: Admin | None = db.session.get(Admin, ident=ident) - if not admin: - msg = f"Admin with id {ident} not found" - raise ItemNotFoundError(msg) - return admin.to_domain_model() - - def get_all_admins(self) -> list[AdminDataclass]: - admins: list[Admin] = list(db.session.scalars(select(Admin)).all()) - return [admin.to_domain_model() for admin in admins] +class SqlAdminDAO(AdminDAO, SqlAbstractDAO[Admin, AdminDataclass]): def create_admin(self, name: str, email: str) -> AdminDataclass: - new_admin: Admin = Admin(name=name, email=email) - db.session.add(new_admin) - db.session.commit() - return new_admin.to_domain_model() - + with Session(engine) as session: + new_admin: Admin = Admin(name=name, email=email) + session.add(new_admin) + session.commit() + return new_admin.to_domain_model() diff --git a/backend/db/implementation/SqlGroupDAO.py b/backend/db/implementation/SqlGroupDAO.py index cb426e24..47b8a3fd 100644 --- a/backend/db/implementation/SqlGroupDAO.py +++ b/backend/db/implementation/SqlGroupDAO.py @@ -1,65 +1,70 @@ from db.errors.database_errors import ItemNotFoundError, UniqueConstraintError -from db.extensions import db +from db.extensions import engine +from sqlalchemy.orm import Session + +from db.implementation.SqlAbstractDAO import SqlAbstractDAO from db.interface.GroupDAO import GroupDAO from db.models.models import Group, Project, Student from domain.models.GroupDataclass import GroupDataclass from domain.models.StudentDataclass import StudentDataclass -class SqlGroupDAO(GroupDAO): - def create_group(self, project_id: int) -> GroupDataclass: - project: Project | None = db.session.get(Project, ident=project_id) - if not project: - msg = f"Project with id {project} not found" - raise ItemNotFoundError(msg) - new_group: Group = Group(project_id=project_id) - db.session.add(new_group) - db.session.commit() - return new_group.to_domain_model() - - def get_group(self, group_id: int) -> GroupDataclass: - group: Group | None = db.session.get(Group, ident=group_id) - if not group: - msg = f"Group with id {group_id} not found" - raise ItemNotFoundError(msg) - return group.to_domain_model() +class SqlGroupDAO(GroupDAO, SqlAbstractDAO[Group, GroupDataclass]): + @staticmethod + def create_group(project_id: int) -> GroupDataclass: + with Session(engine) as session: + project: Project | None = session.get(Project, ident=project_id) + if not project: + msg = f"Project with id {project} not found" + raise ItemNotFoundError(msg) + new_group: Group = Group(project_id=project_id) + session.add(new_group) + return new_group.to_domain_model() - def get_groups_of_project(self, project_id: int) -> list[GroupDataclass]: - project: Project | None = db.session.get(Project, ident=project_id) - if not project: - msg = f"Project with id {project} not found" - raise ItemNotFoundError(msg) - groups: list[Group] = project.groups - return [group.to_domain_model() for group in groups] + @staticmethod + def get_groups_of_project(project_id: int) -> list[GroupDataclass]: + with Session(engine) as session: + project: Project | None = session.get(Project, ident=project_id) + if not project: + msg = f"Project with id {project} not found" + raise ItemNotFoundError(msg) + groups: list[Group] = project.groups + return [group.to_domain_model() for group in groups] - def get_groups_of_student(self, student_id: int) -> list[GroupDataclass]: - student: Student | None = db.session.get(Student, ident=student_id) - if not student: - msg = f"Student with id {student_id} not found" - raise ItemNotFoundError(msg) - groups: list[Group] = student.groups - return [group.to_domain_model() for group in groups] + @staticmethod + def get_groups_of_student(student_id: int) -> list[GroupDataclass]: + with Session(engine) as session: + student: Student | None = session.get(Student, ident=student_id) + if not student: + msg = f"Student with id {student_id} not found" + raise ItemNotFoundError(msg) + groups: list[Group] = student.groups + return [group.to_domain_model() for group in groups] - def add_student_to_group(self, student_id: int, group_id: int) -> None: - student: Student | None = db.session.get(Student, ident=student_id) - group: Group | None = db.session.get(Group, ident=group_id) - if not student: - msg = f"Student with id {student_id} not found" - raise ItemNotFoundError(msg) - if not group: - msg = f"Group with id {group_id} not found" - raise ItemNotFoundError(msg) - if student in group.students: - msg = f"Student with id {student_id} already in group with id {group_id}" - raise UniqueConstraintError(msg) + @staticmethod + def add_student_to_group(student_id: int, group_id: int) -> None: + with Session(engine) as session: + student: Student | None = session.get(Student, ident=student_id) + group: Group | None = session.get(Group, ident=group_id) + if not student: + msg = f"Student with id {student_id} not found" + raise ItemNotFoundError(msg) + if not group: + msg = f"Group with id {group_id} not found" + raise ItemNotFoundError(msg) + if student in group.students: + msg = f"Student with id {student_id} already in group with id {group_id}" + raise UniqueConstraintError(msg) - group.students.append(student) - db.session.commit() + group.students.append(student) + session.commit() - def get_students_of_group(self, group_id: int) -> list[StudentDataclass]: - group: Group | None = db.session.get(Group, ident=group_id) - if not group: - msg = f"Group with id {group_id} not found" - raise ItemNotFoundError(msg) - students: list[Student] = group.students - return [student.to_domain_model() for student in students] + @staticmethod + def get_students_of_group(group_id: int) -> list[StudentDataclass]: + with Session(engine) as session: + group: Group | None = session.get(Group, ident=group_id) + if not group: + msg = f"Group with id {group_id} not found" + raise ItemNotFoundError(msg) + students: list[Student] = group.students + return [student.to_domain_model() for student in students] diff --git a/backend/db/implementation/SqlProjectDAO.py b/backend/db/implementation/SqlProjectDAO.py index 6f2bd059..2d5175bb 100644 --- a/backend/db/implementation/SqlProjectDAO.py +++ b/backend/db/implementation/SqlProjectDAO.py @@ -1,40 +1,49 @@ from datetime import datetime from db.errors.database_errors import ItemNotFoundError -from db.extensions import db +from sqlalchemy.orm import Session + +from db.extensions import engine +from db.implementation.SqlAbstractDAO import SqlAbstractDAO +from db.interface.AbstractDAO import D from db.interface.ProjectDAO import ProjectDAO from db.models.models import Project, Subject from domain.models.ProjectDataclass import ProjectDataclass -class SqlProjectDAO(ProjectDAO): - def create_project(self, subject_id: int, name: str, deadline: datetime, archived: bool, requirements: str, +class SqlProjectDAO(ProjectDAO, SqlAbstractDAO[Project, ProjectDataclass]): + + @staticmethod + def get_all() -> list[ProjectDataclass]: + return SqlAbstractDAO.get_all() + + @staticmethod + def get_object(ident: int) -> ProjectDataclass: + return SqlAbstractDAO.get_object(ident) + + @staticmethod + def create_project(subject_id: int, name: str, deadline: datetime, archived: bool, requirements: str, visible: bool, max_students: int) -> ProjectDataclass: - subject: Subject | None = db.session.get(Subject, subject_id) - if not subject: - msg = f"Subject with id {subject_id} not found" - raise ItemNotFoundError(msg) - - new_project: Project = Project(subject_id=subject_id, name=name, deadline=deadline, - archived=archived, requirements=requirements, visible=visible, - max_students=max_students) - - db.session.add(new_project) - db.session.commit() - return new_project.to_domain_model() - - - def get_project(self, project_id: int) -> ProjectDataclass: - project: Project | None = db.session.get(Project, ident=project_id) - if not project: - msg = f"Project with id {project_id} not found" - raise ItemNotFoundError(msg) - return project.to_domain_model() - - def get_projects_of_subject(self, subject_id: int) -> list[ProjectDataclass]: - subject: Subject | None = db.session.get(Subject, ident=subject_id) - if not subject: - msg = f"Subject with id {subject_id} not found" - raise ItemNotFoundError(msg) - projects: list[Project] = subject.projects - return [project.to_domain_model() for project in projects] + with Session(engine) as session: + subject: Subject | None = session.get(Subject, subject_id) + if not subject: + msg = f"Subject with id {subject_id} not found" + raise ItemNotFoundError(msg) + + new_project: Project = Project(subject_id=subject_id, name=name, deadline=deadline, + archived=archived, requirements=requirements, visible=visible, + max_students=max_students) + + session.add(new_project) + session.commit() + return new_project.to_domain_model() + + @staticmethod + def get_projects_of_subject(subject_id: int) -> list[ProjectDataclass]: + with Session(engine) as session: + subject: Subject | None = session.get(Subject, ident=subject_id) + if not subject: + msg = f"Subject with id {subject_id} not found" + raise ItemNotFoundError(msg) + projects: list[Project] = subject.projects + return [project.to_domain_model() for project in projects] diff --git a/backend/db/implementation/SqlStudentDAO.py b/backend/db/implementation/SqlStudentDAO.py index 580a7670..f6a75c83 100644 --- a/backend/db/implementation/SqlStudentDAO.py +++ b/backend/db/implementation/SqlStudentDAO.py @@ -1,26 +1,30 @@ from sqlalchemy import select from db.errors.database_errors import ItemNotFoundError -from db.extensions import db +from db.extensions import engine +from sqlalchemy.orm import Session + +from db.implementation.SqlAbstractDAO import SqlAbstractDAO +from db.interface.AbstractDAO import D from db.interface.StudentDAO import StudentDAO from db.models.models import Student from domain.models.StudentDataclass import StudentDataclass -class SqlStudentDAO(StudentDAO): - def get_student(self, ident: int) -> StudentDataclass: - student: Student | None = db.session.get(Student, ident=ident) - if not student: - msg = f"Student with id {ident} not found" - raise ItemNotFoundError(msg) - return student.to_domain_model() +class SqlStudentDAO(StudentDAO, SqlAbstractDAO[Student, StudentDataclass]): + + @staticmethod + def get_all() -> list[StudentDataclass]: + return SqlAbstractDAO.get_all() - def get_all_students(self) -> list[StudentDataclass]: - students: list[Student] = list(db.session.scalars(select(Student)).all()) - return [student.to_domain_model() for student in students] + @staticmethod + def get_object(ident: int) -> StudentDataclass: + return SqlAbstractDAO.get_object(ident) - def create_student(self, name: str, email: str) -> StudentDataclass: - new_student: Student = Student(name=name, email=email) - db.session.add(new_student) - db.session.commit() - return new_student.to_domain_model() + @staticmethod + def create_student(name: str, email: str) -> StudentDataclass: + with Session(engine) as session: + new_student: Student = Student(name=name, email=email) + session.add(new_student) + session.commit() + return new_student.to_domain_model() diff --git a/backend/db/implementation/SqlSubjectDAO.py b/backend/db/implementation/SqlSubjectDAO.py index 40e59f10..bfa1d447 100644 --- a/backend/db/implementation/SqlSubjectDAO.py +++ b/backend/db/implementation/SqlSubjectDAO.py @@ -1,70 +1,86 @@ from db.errors.database_errors import ItemNotFoundError, UniqueConstraintError -from db.extensions import db +from sqlalchemy.orm import Session + +from db.extensions import engine +from db.implementation.SqlAbstractDAO import SqlAbstractDAO +from db.interface.AbstractDAO import D from db.interface.SubjectDAO import SubjectDAO from db.models.models import Student, Subject, Teacher from domain.models.SubjectDataclass import SubjectDataclass -class SqlSubjectDAO(SubjectDAO): - def create_subject(self, name: str) -> SubjectDataclass: - new_subject = Subject(name=name) - db.session.add(new_subject) - db.session.commit() - return new_subject.to_domain_model() +class SqlSubjectDAO(SubjectDAO, SqlAbstractDAO[Subject, SubjectDataclass]): + + @staticmethod + def get_all() -> list[SubjectDataclass]: + return SqlAbstractDAO.get_all() + + @staticmethod + def get_object(ident: int) -> SubjectDataclass: + return SqlAbstractDAO.get_object(ident) - def get_subject(self, subject_id: int) -> SubjectDataclass: - subject: Subject | None = db.session.get(Subject, ident=subject_id) - if not subject: - msg = f"Subject with id {subject_id} not found" - raise ItemNotFoundError(msg) - return subject.to_domain_model() + @staticmethod + def create_subject(name: str) -> SubjectDataclass: + with Session(engine) as session: + new_subject = Subject(name=name) + session.add(new_subject) + session.commit() + return new_subject.to_domain_model() - def get_subjects_of_teacher(self, teacher_id: int) -> list[SubjectDataclass]: - teacher: Teacher | None = db.session.get(Teacher, ident=teacher_id) - if not teacher: - msg = f"Teacher with id {teacher_id} not found" - raise ItemNotFoundError(msg) - subjects: list[Subject] = teacher.subjects - return [vak.to_domain_model() for vak in subjects] + @staticmethod + def get_subjects_of_teacher(teacher_id: int) -> list[SubjectDataclass]: + with Session(engine) as session: + teacher: Teacher | None = session.get(Teacher, ident=teacher_id) + if not teacher: + msg = f"Teacher with id {teacher_id} not found" + raise ItemNotFoundError(msg) + subjects: list[Subject] = teacher.subjects + return [vak.to_domain_model() for vak in subjects] - def get_subjects_of_student(self, student_id: int) -> list[SubjectDataclass]: - student: Student | None = db.session.get(Student, ident=student_id) - if not student: - msg = f"Student with id {student_id} not found" - raise ItemNotFoundError(msg) - subjects: list[Subject] = student.subjects - return [vak.to_domain_model() for vak in subjects] + @staticmethod + def add_student_to_subject(student_id: int, subject_id: int) -> None: + with Session(engine) as session: + student: Student | None = session.get(Student, ident=student_id) + subject: Subject | None = session.get(Subject, ident=subject_id) - def add_student_to_subject(self, student_id: int, subject_id: int) -> None: - student: Student | None = db.session.get(Student, ident=student_id) - subject: Subject | None = db.session.get(Subject, ident=subject_id) + if not student: + msg = f"Student with id {student_id} not found" + raise ItemNotFoundError(msg) + if not subject: + msg = f"Subject with id {subject_id} not found" + raise ItemNotFoundError(msg) + if subject in student.subjects: + msg = f"Student with id {student_id} already has subject with id {subject_id}" + raise UniqueConstraintError(msg) - if not student: - msg = f"Student with id {student_id} not found" - raise ItemNotFoundError(msg) - if not subject: - msg = f"Subject with id {subject_id} not found" - raise ItemNotFoundError(msg) - if subject in student.subjects: - msg = f"Student with id {student_id} already has subject with id {subject_id}" - raise UniqueConstraintError(msg) + student.subjects.append(subject) + session.commit() - student.subjects.append(subject) - db.session.commit() + @staticmethod + def add_teacher_to_subject(teacher_id: int, subject_id: int) -> None: + with Session(engine) as session: + teacher: Teacher | None = session.get(Teacher, ident=teacher_id) + subject: Subject | None = session.get(Subject, ident=subject_id) - def add_teacher_to_subject(self, teacher_id: int, subject_id: int) -> None: - teacher: Teacher | None = db.session.get(Teacher, ident=teacher_id) - subject: Subject | None = db.session.get(Subject, ident=subject_id) + if not teacher: + msg = f"Teacher with id {teacher_id} not found" + raise ItemNotFoundError(msg) + if not subject: + msg = f"Subject with id {subject_id} not found" + raise ItemNotFoundError(msg) + if subject in teacher.subjects: + msg = f"Teacher with id {teacher_id} already has subject with id {subject_id}" + raise UniqueConstraintError(msg) - if not teacher: - msg = f"Teacher with id {teacher_id} not found" - raise ItemNotFoundError(msg) - if not subject: - msg = f"Subject with id {subject_id} not found" - raise ItemNotFoundError(msg) - if subject in teacher.subjects: - msg = f"Teacher with id {teacher_id} already has subject with id {subject_id}" - raise UniqueConstraintError(msg) + teacher.subjects.append(subject) + session.commit() - teacher.subjects.append(subject) - db.session.commit() + @staticmethod + def get_subjects_of_student(student_id: int) -> list[SubjectDataclass]: + with Session(engine) as session: + student: Student | None = session.get(Student, ident=student_id) + if not student: + msg = f"Student with id {student_id} not found" + raise ItemNotFoundError(msg) + subjects: list[Subject] = student.subjects + return [vak.to_domain_model() for vak in subjects] diff --git a/backend/db/implementation/SqlSubmissionDAO.py b/backend/db/implementation/SqlSubmissionDAO.py index 51a101b5..69fecd1f 100644 --- a/backend/db/implementation/SqlSubmissionDAO.py +++ b/backend/db/implementation/SqlSubmissionDAO.py @@ -1,49 +1,51 @@ from datetime import datetime from db.errors.database_errors import ItemNotFoundError -from db.extensions import db +from sqlalchemy.orm import Session + +from db.extensions import engine +from db.implementation.SqlAbstractDAO import SqlAbstractDAO from db.interface.SubmissionDAO import SubmissionDAO from db.models.models import Group, Student, Submission from domain.models.SubmissionDataclass import SubmissionDataclass, SubmissionState -class SqlSubmissionDAO(SubmissionDAO): - def create_submission(self, student_id: int, group_id: int, message: str,state: SubmissionState, +class SqlSubmissionDAO(SubmissionDAO, SqlAbstractDAO[Submission, SubmissionDataclass]): + @staticmethod + def create_submission(student_id: int, group_id: int, message: str, state: SubmissionState, date_time: datetime) -> SubmissionDataclass: + with Session(engine) as session: + student: Student | None = session.get(Student, ident=student_id) + group: Group | None = session.get(Group, ident=group_id) + if not student: + msg = f"Student with id {student_id} not found" + raise ItemNotFoundError(msg) + if not group: + msg = f"Group with id {group_id} not found" + raise ItemNotFoundError(msg) + new_submission: Submission = Submission(student_id=student_id, + group_id=group_id, message=message, state=state, + date_time=date_time) + session.add(new_submission) + session.commit() + return new_submission.to_domain_model() - student: Student | None = db.session.get(Student, ident=student_id) - group: Group | None = db.session.get(Group, ident=group_id) - if not student: - msg = f"Student with id {student_id} not found" - raise ItemNotFoundError(msg) - if not group: - msg = f"Group with id {group_id} not found" - raise ItemNotFoundError(msg) - new_submission: Submission = Submission(student_id=student_id, - group_id=group_id, message=message, state=state, date_time=date_time) - db.session.add(new_submission) - db.session.commit() - return new_submission.to_domain_model() - - def get_submission(self, submission_id: int) -> SubmissionDataclass: - submission: Submission | None = db.session.get(Submission, ident=submission_id) - if not submission: - msg = f"Submission with id {submission_id} not found" - raise ItemNotFoundError(msg) - return submission.to_domain_model() - - def get_submissions_of_student(self, student_id: int) -> list[SubmissionDataclass]: - student: Student | None = db.session.get(Student, ident=student_id) - if not student: - msg = f"Student with id {student_id} not found" - raise ItemNotFoundError(msg) - submissions: list[Submission] = student.submissions - return [submission.to_domain_model() for submission in submissions] + @staticmethod + def get_submissions_of_student(student_id: int) -> list[SubmissionDataclass]: + with Session(engine) as session: + student: Student | None = session.get(Student, ident=student_id) + if not student: + msg = f"Student with id {student_id} not found" + raise ItemNotFoundError(msg) + submissions: list[Submission] = student.submissions + return [submission.to_domain_model() for submission in submissions] - def get_submissions_of_group(self, group_id: int) -> list[SubmissionDataclass]: - group: Group | None = db.session.get(Group, ident=group_id) - if not group: - msg = f"Group with id {group_id} not found" - raise ItemNotFoundError(msg) - submissions: list[Submission] = group.submissions - return [submission.to_domain_model() for submission in submissions] + @staticmethod + def get_submissions_of_group(group_id: int) -> list[SubmissionDataclass]: + with Session(engine) as session: + group: Group | None = session.get(Group, ident=group_id) + if not group: + msg = f"Group with id {group_id} not found" + raise ItemNotFoundError(msg) + submissions: list[Submission] = group.submissions + return [submission.to_domain_model() for submission in submissions] diff --git a/backend/db/implementation/SqlTeacherDAO.py b/backend/db/implementation/SqlTeacherDAO.py index 9623cb3e..5ed33a97 100644 --- a/backend/db/implementation/SqlTeacherDAO.py +++ b/backend/db/implementation/SqlTeacherDAO.py @@ -1,28 +1,26 @@ -from sqlalchemy import select - -from db.errors.database_errors import ItemNotFoundError -from db.extensions import db +from sqlalchemy.orm import Session +from db.extensions import engine +from db.implementation.SqlAbstractDAO import SqlAbstractDAO +from db.interface.AbstractDAO import D from db.interface.TeacherDAO import TeacherDAO from db.models.models import Teacher from domain.models.TeacherDataclass import TeacherDataclass -class SqlTeacherDAO(TeacherDAO): - def get_teacher(self, ident: int) -> TeacherDataclass: - teacher: Teacher | None = db.session.get(Teacher, ident) - - if not teacher: - msg = f"Teacher with id {ident} not found" - raise ItemNotFoundError(msg) +class SqlTeacherDAO(TeacherDAO, SqlAbstractDAO[Teacher, TeacherDataclass]): - return teacher.to_domain_model() + @staticmethod + def get_all() -> list[TeacherDataclass]: + return SqlAbstractDAO.get_all() - def get_all_teachers(self) -> list[TeacherDataclass]: - teachers: list[Teacher] = list(db.session.scalars(select(Teacher)).all()) - return [teacher.to_domain_model() for teacher in teachers] + @staticmethod + def get_object(ident: int) -> TeacherDataclass: + return SqlAbstractDAO.get_object(ident) - def create_teacher(self, name: str, email: str) -> TeacherDataclass: - new_teacher = Teacher(name=name, email=email) - db.session.add(new_teacher) - db.session.commit() - return new_teacher.to_domain_model() + @staticmethod + def create_teacher(name: str, email: str) -> TeacherDataclass: + with Session(engine) as session: + new_teacher = Teacher(name=name, email=email) + session.add(new_teacher) + session.commit() + return new_teacher.to_domain_model() diff --git a/backend/db/implementation/SqlUserDAO.py b/backend/db/implementation/SqlUserDAO.py index 7a1b25ac..18db371f 100644 --- a/backend/db/implementation/SqlUserDAO.py +++ b/backend/db/implementation/SqlUserDAO.py @@ -1,21 +1,14 @@ -from sqlalchemy import select - -from db.errors.database_errors import ItemNotFoundError -from db.extensions import db +from db.implementation.SqlAbstractDAO import SqlAbstractDAO from db.interface.UserDAO import UserDAO from db.models.models import User from domain.models.UserDataclass import UserDataclass -class SqlUserDAO(UserDAO): - def get_user(self, ident: int) -> UserDataclass: - user: User | None = db.session.get(User, ident=ident) - if not user: - msg = f"User with id {ident} not found" - raise ItemNotFoundError(msg) - return user.to_domain_model() - - def get_all_users(self) -> list[UserDataclass]: - users: list[User] = list(db.session.scalars(select(User)).all()) - return [user.to_domain_model() for user in users] +class SqlUserDAO(UserDAO, SqlAbstractDAO[User, UserDataclass]): + @staticmethod + def get_all() -> list[UserDataclass]: + return SqlAbstractDAO.get_all() + @staticmethod + def get_object(ident: int) -> UserDataclass: + return SqlAbstractDAO.get_object(ident) diff --git a/backend/db/interface/AbstractDAO.py b/backend/db/interface/AbstractDAO.py new file mode 100644 index 00000000..7162d2f0 --- /dev/null +++ b/backend/db/interface/AbstractDAO.py @@ -0,0 +1,20 @@ +from abc import ABC, abstractmethod +from typing import Generic, TypeVar + +from db.models.models import AbstractModel + +T = TypeVar("T") +D = TypeVar("D") + + +class AbstractDAO(Generic[T, D], ABC): + + @staticmethod + @abstractmethod + def get_object(ident: int) -> D: + raise NotImplementedError + + @staticmethod + @abstractmethod + def get_all() -> list[D]: + raise NotImplementedError diff --git a/backend/db/interface/AdminDAO.py b/backend/db/interface/AdminDAO.py index f776df7e..b9132e17 100644 --- a/backend/db/interface/AdminDAO.py +++ b/backend/db/interface/AdminDAO.py @@ -4,26 +4,6 @@ class AdminDAO(ABC): - @abstractmethod - def get_admin(self, ident: int) -> AdminDataclass: - """ - Haalt een admin op aan de hand van zijn identificatie. - - :param ident: Het id van de te zoeken admin. - :return: De admin die overeenkomt met de gegeven id. - :raises ItemNotFoundException: Als geen admin met het gegeven id gevonden werd. - """ - raise NotImplementedError - - @abstractmethod - def get_all_admins(self) -> list[AdminDataclass]: - """ - Haalt alle admins op. - - :return: Een lijst van alle admins. - """ - raise NotImplementedError - @abstractmethod def create_admin(self, name: str, email: str) -> AdminDataclass: """ diff --git a/backend/db/interface/GroupDAO.py b/backend/db/interface/GroupDAO.py index c76ccd73..6eb744b1 100644 --- a/backend/db/interface/GroupDAO.py +++ b/backend/db/interface/GroupDAO.py @@ -5,8 +5,9 @@ class GroupDAO(ABC): + @staticmethod @abstractmethod - def create_group(self, project_id: int) -> GroupDataclass: + def create_group(project_id: int) -> GroupDataclass: """ Creëert een nieuw GroupDataClass in de database en associeert het met een ProjectDataClass. @@ -16,19 +17,9 @@ def create_group(self, project_id: int) -> GroupDataclass: """ raise NotImplementedError + @staticmethod @abstractmethod - def get_group(self, group_id: int) -> GroupDataclass: - """ - Haalt een GroupDataClass op aan de hand van zijn identificatie. - - :param group_id: De identificatie van het op te halen GroupDataClass. - :raises ItemNotFoundException: Als er geen GroupDataClass met de opgegeven `group_id` in de database bestaat. - :returns: De domeinmodel-instantie van het opgehaalde GroupDataClass. - """ - raise NotImplementedError - - @abstractmethod - def get_groups_of_project(self, project_id: int) -> list[GroupDataclass]: + def get_groups_of_project(project_id: int) -> list[GroupDataclass]: """ Haalt alle groepen op die bij een bepaald project horen. @@ -37,8 +28,9 @@ def get_groups_of_project(self, project_id: int) -> list[GroupDataclass]: """ raise NotImplementedError + @staticmethod @abstractmethod - def get_groups_of_student(self, student_id: int) -> list[GroupDataclass]: + def get_groups_of_student(student_id: int) -> list[GroupDataclass]: """ Haalt alle groepen op die bij een bepaalde student horen. @@ -47,8 +39,9 @@ def get_groups_of_student(self, student_id: int) -> list[GroupDataclass]: """ raise NotImplementedError + @staticmethod @abstractmethod - def add_student_to_group(self, student_id: int, group_id: int) -> None: + def add_student_to_group(student_id: int, group_id: int) -> None: """ Gaat een student toevoegen aan een groep @@ -58,7 +51,8 @@ def add_student_to_group(self, student_id: int, group_id: int) -> None: """ raise NotImplementedError - def get_students_of_group(self, group_id: int) -> list[StudentDataclass]: + @staticmethod + def get_students_of_group(group_id: int) -> list[StudentDataclass]: """ Gaat alle studenten geven die in een bepaalde groep zitten diff --git a/backend/db/interface/ProjectDAO.py b/backend/db/interface/ProjectDAO.py index e1db39dd..cfab73f3 100644 --- a/backend/db/interface/ProjectDAO.py +++ b/backend/db/interface/ProjectDAO.py @@ -1,12 +1,16 @@ from abc import ABC, abstractmethod from datetime import datetime +from db.interface.AbstractDAO import AbstractDAO +from db.models.models import Project from domain.models.ProjectDataclass import ProjectDataclass -class ProjectDAO(ABC): +class ProjectDAO(AbstractDAO[Project, ProjectDataclass], ABC): + + @staticmethod @abstractmethod - def create_project(self, subject_id: int, name: str, deadline: datetime, archived: bool, requirements: str, + def create_project(subject_id: int, name: str, deadline: datetime, archived: bool, requirements: str, visible: bool, max_students: int) -> ProjectDataclass: """ Creëert een nieuw ProjectDataClass in de database en associeert het met een SubjectDataClass. @@ -23,19 +27,9 @@ def create_project(self, subject_id: int, name: str, deadline: datetime, archive """ raise NotImplementedError + @staticmethod @abstractmethod - def get_project(self, project_id: int) -> ProjectDataclass: - """ - Haalt een ProjectDataClass op aan de hand van zijn identificatie. - - :param project_id: De identificatie van het op te halen ProjectDataClass. - :raises ItemNotFoundException: Als er geen ProjectDataClass met de opgegeven `project_id` in de database bestaat - :returns: De domeinmodel-instantie van het opgehaalde ProjectDataClass. - """ - raise NotImplementedError - - @abstractmethod - def get_projects_of_subject(self, subject_id: int) -> list[ProjectDataclass]: + def get_projects_of_subject(subject_id: int) -> list[ProjectDataclass]: """ Haalt alle projecten op die bij een bepaald subject horen. diff --git a/backend/db/interface/StudentDAO.py b/backend/db/interface/StudentDAO.py index 19e7b6c6..95065dbb 100644 --- a/backend/db/interface/StudentDAO.py +++ b/backend/db/interface/StudentDAO.py @@ -1,31 +1,14 @@ from abc import ABC, abstractmethod +from db.interface.AbstractDAO import AbstractDAO +from db.models.models import Student from domain.models.StudentDataclass import StudentDataclass -class StudentDAO(ABC): +class StudentDAO(AbstractDAO[Student, StudentDataclass], ABC): + @staticmethod @abstractmethod - def get_student(self, ident: int) -> StudentDataclass: - """ - Haalt een student op aan de hand van zijn identificatie. - - :param ident: Het id van de te zoeken student. - :return: De student die overeenkomt met de gegeven id. - :raises ItemNotFoundException: Als geen student met het gegeven id gevonden werd. - """ - raise NotImplementedError - - @abstractmethod - def get_all_students(self) -> list[StudentDataclass]: - """ - Haalt alle studenten op. - - :return: Een lijst van alle studenten. - """ - raise NotImplementedError - - @abstractmethod - def create_student(self, name: str, email: str) -> StudentDataclass: + def create_student(name: str, email: str) -> StudentDataclass: """ Maakt een nieuwe student aan. diff --git a/backend/db/interface/SubjectDAO.py b/backend/db/interface/SubjectDAO.py index cf8a2d0a..a14b5520 100644 --- a/backend/db/interface/SubjectDAO.py +++ b/backend/db/interface/SubjectDAO.py @@ -1,11 +1,14 @@ from abc import ABC, abstractmethod +from db.interface.AbstractDAO import AbstractDAO +from db.models.models import Subject from domain.models.SubjectDataclass import SubjectDataclass -class SubjectDAO(ABC): +class SubjectDAO(AbstractDAO[Subject, SubjectDataclass], ABC): + @staticmethod @abstractmethod - def create_subject(self, name: str) -> SubjectDataclass: + def create_subject(name: str) -> SubjectDataclass: """ Creëert een nieuw SubjectDataclass in de database. @@ -14,19 +17,9 @@ def create_subject(self, name: str) -> SubjectDataclass: """ raise NotImplementedError + @staticmethod @abstractmethod - def get_subject(self, subject_id: int) -> SubjectDataclass: - """ - Haalt een SubjectDataclass op aan de hand van zijn identificatie. - - :param subject_id: De identificatie van het op te halen SubjectDataclass. - :raises ItemNotFoundException: Als er geen SubjectDataclass met de opgegeven `ident` in de database bestaat. - :returns: De domeinmodel-instantie van het opgehaalde SubjectDataclass. - """ - raise NotImplementedError - - @abstractmethod - def get_subjects_of_teacher(self, teacher_id: int) -> list[SubjectDataclass]: + def get_subjects_of_teacher(teacher_id: int) -> list[SubjectDataclass]: """ Haalt de subjects op die door een bepaalde teacher worden gegeven. @@ -35,8 +28,9 @@ def get_subjects_of_teacher(self, teacher_id: int) -> list[SubjectDataclass]: """ raise NotImplementedError + @staticmethod @abstractmethod - def get_subjects_of_student(self, student_id: int) -> list[SubjectDataclass]: + def get_subjects_of_student(student_id: int) -> list[SubjectDataclass]: """ Haalt de subjects op die door een bepaalde student worden gevolgd. @@ -45,8 +39,9 @@ def get_subjects_of_student(self, student_id: int) -> list[SubjectDataclass]: """ raise NotImplementedError + @staticmethod @abstractmethod - def add_student_to_subject(self, student_id: int, subject_id: int) -> None: + def add_student_to_subject(student_id: int, subject_id: int) -> None: """ Voegt een student toe aan een vak. @@ -56,8 +51,9 @@ def add_student_to_subject(self, student_id: int, subject_id: int) -> None: """ raise NotImplementedError + @staticmethod @abstractmethod - def add_teacher_to_subject(self, teacher_id: int, subject_id: int) -> None: + def add_teacher_to_subject(teacher_id: int, subject_id: int) -> None: """ Voegt een teacher toe aan een vak. diff --git a/backend/db/interface/SubmissionDAO.py b/backend/db/interface/SubmissionDAO.py index 09651dca..b7ed0bdf 100644 --- a/backend/db/interface/SubmissionDAO.py +++ b/backend/db/interface/SubmissionDAO.py @@ -1,12 +1,15 @@ from abc import ABC, abstractmethod from datetime import datetime +from db.interface.AbstractDAO import AbstractDAO +from db.models.models import Submission from domain.models.SubmissionDataclass import SubmissionDataclass, SubmissionState -class SubmissionDAO(ABC): +class SubmissionDAO(AbstractDAO[Submission, SubmissionDataclass], ABC): + @staticmethod @abstractmethod - def create_submission(self, student_id: int, group_id: int, message: str, + def create_submission(student_id: int, group_id: int, message: str, state: SubmissionState, date_time: datetime) -> SubmissionDataclass: """ Creëert een nieuw SubmissionDataClass in de database en associeert het met een StudentDataclass en een @@ -20,20 +23,9 @@ def create_submission(self, student_id: int, group_id: int, message: str, """ raise NotImplementedError + @staticmethod @abstractmethod - def get_submission(self, submission_id: int) -> SubmissionDataclass: - """ - Haalt een SubmissionDataClass op aan de hand van zijn identificatie. - - :param submission_id: De identificatie van het op te halen SubmissionDataClass. - :raises ItemNotFoundException: Als er geen SubmissionDataclass met de opgegeven `project_id` in de database - bestaat. - :returns: De domeinmodel-instantie van het opgehaalde SubmissionDataClass. - """ - raise NotImplementedError - - @abstractmethod - def get_submissions_of_student(self, student_id: int) -> list[SubmissionDataclass]: + def get_submissions_of_student(student_id: int) -> list[SubmissionDataclass]: """ Haalt alle projecten op die bij een bepaalde student horen. @@ -42,8 +34,9 @@ def get_submissions_of_student(self, student_id: int) -> list[SubmissionDataclas """ raise NotImplementedError + @staticmethod @abstractmethod - def get_submissions_of_group(self, group_id: int) -> list[SubmissionDataclass]: + def get_submissions_of_group(group_id: int) -> list[SubmissionDataclass]: """ Haalt alle projecten op die bij een bepaalde groep horen. diff --git a/backend/db/interface/TeacherDAO.py b/backend/db/interface/TeacherDAO.py index 5ea45861..10c6faee 100644 --- a/backend/db/interface/TeacherDAO.py +++ b/backend/db/interface/TeacherDAO.py @@ -1,31 +1,14 @@ from abc import ABC, abstractmethod +from db.interface.AbstractDAO import AbstractDAO +from db.models.models import Teacher from domain.models.TeacherDataclass import TeacherDataclass -class TeacherDAO(ABC): +class TeacherDAO(AbstractDAO[Teacher, TeacherDataclass], ABC): + @staticmethod @abstractmethod - def get_teacher(self, ident: int) -> TeacherDataclass: - """ - Haalt een teacher op aan de hand van zijn identificatie. - - :param ident: Het id van de te zoeken teacher. - :return: De teacher die overeenkomt met de gegeven id. - :raises ItemNotFoundException: Als geen teacher met het gegeven id gevonden werd. - """ - raise NotImplementedError - - @abstractmethod - def get_all_teachers(self) -> list[TeacherDataclass]: - """ - Haalt alle lesgevers op. - - :return: Een lijst van alle lesgevers. - """ - raise NotImplementedError - - @abstractmethod - def create_teacher(self, name: str, email: str) -> TeacherDataclass: + def create_teacher(name: str, email: str) -> TeacherDataclass: """ Maakt een nieuwe teacher aan. diff --git a/backend/db/interface/UserDAO.py b/backend/db/interface/UserDAO.py index e4d85b25..2e73beff 100644 --- a/backend/db/interface/UserDAO.py +++ b/backend/db/interface/UserDAO.py @@ -1,25 +1,9 @@ -from abc import ABC, abstractmethod +from abc import ABC +from db.interface.AbstractDAO import AbstractDAO +from db.models.models import User from domain.models.UserDataclass import UserDataclass -class UserDAO(ABC): - @abstractmethod - def get_user(self, ident: int) -> UserDataclass: - """ - Haalt een user op aan de hand van zijn identificatie. - - :param ident: Het id van de te zoeken user. - :return: De user die overeenkomt met de gegeven id. - :raises ItemNotFoundException: Als geen user met het gegeven id gevonden werd. - """ - raise NotImplementedError - - @abstractmethod - def get_all_users(self) -> list[UserDataclass]: - """ - Haalt alle users op. - - :return: Een lijst van alle users. - """ - raise NotImplementedError +class UserDAO(AbstractDAO[User, UserDataclass], ABC): + pass diff --git a/backend/db/models/models.py b/backend/db/models/models.py index 8aa1425c..a6c47695 100644 --- a/backend/db/models/models.py +++ b/backend/db/models/models.py @@ -1,10 +1,11 @@ +from abc import abstractmethod from dataclasses import dataclass from datetime import datetime +from typing import Any from sqlalchemy import Column, ForeignKey, Table -from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship -from db.extensions import db from domain.models.AdminDataclass import AdminDataclass from domain.models.GroupDataclass import GroupDataclass from domain.models.ProjectDataclass import ProjectDataclass @@ -16,7 +17,14 @@ @dataclass() -class User(db.Model): +class AbstractModel: + @abstractmethod + def to_domain_model(self) -> Any: + pass + + +@dataclass() +class User(DeclarativeBase, AbstractModel): name: Mapped[str] email: Mapped[str] id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) @@ -35,19 +43,19 @@ def to_domain_model(self) -> AdminDataclass: teachers_subjects = Table( "teachers_subjects", - db.Model.metadata, + DeclarativeBase.metadata, Column("teacher_id", ForeignKey("teacher.id"), primary_key=True), Column("subject_id", ForeignKey("subject.id"), primary_key=True), ) students_subjects = Table( "students_subjects", - db.Model.metadata, + DeclarativeBase.metadata, Column("student_id", ForeignKey("student.id"), primary_key=True), Column("subject_id", ForeignKey("subject.id"), primary_key=True), ) students_groups = Table( "students_groups", - db.Model.metadata, + DeclarativeBase.metadata, Column("student_id", ForeignKey("student.id"), primary_key=True), Column("group_id", ForeignKey("group.id"), primary_key=True), ) @@ -74,7 +82,7 @@ def to_domain_model(self) -> StudentDataclass: @dataclass() -class Subject(db.Model): +class Subject(DeclarativeBase, AbstractModel): name: Mapped[str] id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) teachers: Mapped[list[Teacher]] = relationship(secondary=teachers_subjects, back_populates="subjects") @@ -86,7 +94,7 @@ def to_domain_model(self) -> SubjectDataclass: @dataclass() -class Project(db.Model): +class Project(DeclarativeBase, AbstractModel): name: Mapped[str] deadline: Mapped[datetime] archived: Mapped[bool] @@ -112,7 +120,7 @@ def to_domain_model(self) -> ProjectDataclass: @dataclass() -class Group(db.Model): +class Group(DeclarativeBase, AbstractModel): id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) project_id: Mapped[int] = mapped_column(ForeignKey(Project.id)) project: Mapped[Project] = relationship(back_populates="groups") @@ -124,7 +132,7 @@ def to_domain_model(self) -> GroupDataclass: @dataclass() -class Submission(db.Model): +class Submission(DeclarativeBase, AbstractModel): date_time: Mapped[datetime] state: Mapped[SubmissionState] message: Mapped[str] diff --git a/backend/fill_database_mock.py b/backend/fill_database_mock.py index a89f3574..09334a60 100644 --- a/backend/fill_database_mock.py +++ b/backend/fill_database_mock.py @@ -2,7 +2,8 @@ from psycopg2 import tz -from db.extensions import db +from db.extensions import engine, Base +from sqlalchemy.orm import Session from db.implementation.SqlAdminDAO import SqlAdminDAO from db.implementation.SqlGroupDAO import SqlGroupDAO from db.implementation.SqlProjectDAO import SqlProjectDAO @@ -11,7 +12,8 @@ from db.implementation.SqlTeacherDAO import SqlTeacherDAO if __name__ == "__main__": - db.create_all() + session = Session(engine) + Base.metadata.create_all(engine) admin_dao = SqlAdminDAO() student_dao = SqlStudentDAO() @@ -33,7 +35,7 @@ deadline=datetime(2000, 1, 1, 0, 0, 0, tzinfo=tz.LOCAL)) # maak een groepje voor het project van objeprog - groep1 = group_dao.create_group(project_id=objeprog_project.id) + groep1 = group_dao.create_group(objeprog_project.id) # maak studenten student1 = student_dao.create_student("Student1", "Student1@gmail.com") diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 69bedea1..762500eb 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -16,7 +16,8 @@ python = ">=3.12" [tool.pyright] exclude = [ - ".venv" + ".venv", + "venv" ] [tool.ruff] diff --git a/backend/routes/teachers.py b/backend/routes/teachers.py index e68b8206..7d990480 100644 --- a/backend/routes/teachers.py +++ b/backend/routes/teachers.py @@ -11,13 +11,13 @@ @teachers_router.get("/teachers") def get_teachers() -> list[TeacherDataclass]: dao: TeacherDAO = SqlTeacherDAO() - return dao.get_all_teachers() + return dao.get_all() @teachers_router.get("/teachers/{teacher_id}") def get_teacher(teacher_id: int) -> TeacherDataclass: dao: TeacherDAO = SqlTeacherDAO() - return dao.get_teacher(teacher_id) + return dao.get_object(teacher_id) @teachers_router.post("/teachers") diff --git a/backend/tests/teachers_test.py b/backend/tests/teachers_test.py index 960a98ab..af30bf49 100644 --- a/backend/tests/teachers_test.py +++ b/backend/tests/teachers_test.py @@ -2,22 +2,24 @@ import unittest from http import HTTPStatus +from fastapi.testclient import TestClient + from app import app class LesgeverTestCase(unittest.TestCase): def setUp(self) -> None: - self.app = app.test_client() + self.app = TestClient(app) def test_create_teacher_bad_request(self) -> None: - response = self.app.post("/teachers", data=json.dumps({}), content_type="application/json") + response = self.app.post("/teachers", data={}) assert response.status_code == HTTPStatus.BAD_REQUEST assert "error" in json.loads(response.data) def test_create_teacher_success(self) -> None: teacher_data = {"name": "Bart De Bruyn"} - response = self.app.post("/teachers", data=json.dumps(teacher_data), content_type="application/json") + response = self.app.post("/teachers", data=teacher_data) assert response.status_code == HTTPStatus.CREATED From 94f798ba98ca28e435655fadf29c60c243f5444e Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Wed, 28 Feb 2024 22:04:42 +0100 Subject: [PATCH 07/13] Lint code and revert pydantic for now --- backend/db/implementation/SqlAbstractDAO.py | 5 ++--- backend/db/implementation/SqlGroupDAO.py | 4 ++-- backend/db/implementation/SqlProjectDAO.py | 3 +-- backend/db/implementation/SqlStudentDAO.py | 5 +---- backend/db/implementation/SqlSubjectDAO.py | 3 +-- backend/db/implementation/SqlSubmissionDAO.py | 2 +- backend/db/implementation/SqlTeacherDAO.py | 2 +- backend/db/interface/AbstractDAO.py | 2 -- backend/db/models/models.py | 4 ++-- backend/domain/models/TeacherDataclass.py | 7 +------ backend/domain/models/UserDataclass.py | 11 ++--------- backend/domain/models/base_model.py | 1 - backend/fill_database_mock.py | 4 ++-- 13 files changed, 16 insertions(+), 37 deletions(-) diff --git a/backend/db/implementation/SqlAbstractDAO.py b/backend/db/implementation/SqlAbstractDAO.py index 3715e2b1..d5f50610 100644 --- a/backend/db/implementation/SqlAbstractDAO.py +++ b/backend/db/implementation/SqlAbstractDAO.py @@ -1,12 +1,11 @@ -from sqlalchemy import select from abc import ABC +from typing import Generic, TypeVar +from sqlalchemy import select from sqlalchemy.orm import Session from db.errors.database_errors import ItemNotFoundError from db.extensions import engine -from typing import Generic, TypeVar, Type - from db.models.models import AbstractModel T = TypeVar("T", bound=AbstractModel) diff --git a/backend/db/implementation/SqlGroupDAO.py b/backend/db/implementation/SqlGroupDAO.py index 47b8a3fd..aa8a48ef 100644 --- a/backend/db/implementation/SqlGroupDAO.py +++ b/backend/db/implementation/SqlGroupDAO.py @@ -1,7 +1,7 @@ -from db.errors.database_errors import ItemNotFoundError, UniqueConstraintError -from db.extensions import engine from sqlalchemy.orm import Session +from db.errors.database_errors import ItemNotFoundError, UniqueConstraintError +from db.extensions import engine from db.implementation.SqlAbstractDAO import SqlAbstractDAO from db.interface.GroupDAO import GroupDAO from db.models.models import Group, Project, Student diff --git a/backend/db/implementation/SqlProjectDAO.py b/backend/db/implementation/SqlProjectDAO.py index 2d5175bb..39228b66 100644 --- a/backend/db/implementation/SqlProjectDAO.py +++ b/backend/db/implementation/SqlProjectDAO.py @@ -1,11 +1,10 @@ from datetime import datetime -from db.errors.database_errors import ItemNotFoundError from sqlalchemy.orm import Session +from db.errors.database_errors import ItemNotFoundError from db.extensions import engine from db.implementation.SqlAbstractDAO import SqlAbstractDAO -from db.interface.AbstractDAO import D from db.interface.ProjectDAO import ProjectDAO from db.models.models import Project, Subject from domain.models.ProjectDataclass import ProjectDataclass diff --git a/backend/db/implementation/SqlStudentDAO.py b/backend/db/implementation/SqlStudentDAO.py index f6a75c83..cab3dde6 100644 --- a/backend/db/implementation/SqlStudentDAO.py +++ b/backend/db/implementation/SqlStudentDAO.py @@ -1,11 +1,8 @@ -from sqlalchemy import select -from db.errors.database_errors import ItemNotFoundError -from db.extensions import engine from sqlalchemy.orm import Session +from db.extensions import engine from db.implementation.SqlAbstractDAO import SqlAbstractDAO -from db.interface.AbstractDAO import D from db.interface.StudentDAO import StudentDAO from db.models.models import Student from domain.models.StudentDataclass import StudentDataclass diff --git a/backend/db/implementation/SqlSubjectDAO.py b/backend/db/implementation/SqlSubjectDAO.py index bfa1d447..a3b0f061 100644 --- a/backend/db/implementation/SqlSubjectDAO.py +++ b/backend/db/implementation/SqlSubjectDAO.py @@ -1,9 +1,8 @@ -from db.errors.database_errors import ItemNotFoundError, UniqueConstraintError from sqlalchemy.orm import Session +from db.errors.database_errors import ItemNotFoundError, UniqueConstraintError from db.extensions import engine from db.implementation.SqlAbstractDAO import SqlAbstractDAO -from db.interface.AbstractDAO import D from db.interface.SubjectDAO import SubjectDAO from db.models.models import Student, Subject, Teacher from domain.models.SubjectDataclass import SubjectDataclass diff --git a/backend/db/implementation/SqlSubmissionDAO.py b/backend/db/implementation/SqlSubmissionDAO.py index 69fecd1f..f563ffca 100644 --- a/backend/db/implementation/SqlSubmissionDAO.py +++ b/backend/db/implementation/SqlSubmissionDAO.py @@ -1,8 +1,8 @@ from datetime import datetime -from db.errors.database_errors import ItemNotFoundError from sqlalchemy.orm import Session +from db.errors.database_errors import ItemNotFoundError from db.extensions import engine from db.implementation.SqlAbstractDAO import SqlAbstractDAO from db.interface.SubmissionDAO import SubmissionDAO diff --git a/backend/db/implementation/SqlTeacherDAO.py b/backend/db/implementation/SqlTeacherDAO.py index 5ed33a97..60e5cf84 100644 --- a/backend/db/implementation/SqlTeacherDAO.py +++ b/backend/db/implementation/SqlTeacherDAO.py @@ -1,7 +1,7 @@ from sqlalchemy.orm import Session + from db.extensions import engine from db.implementation.SqlAbstractDAO import SqlAbstractDAO -from db.interface.AbstractDAO import D from db.interface.TeacherDAO import TeacherDAO from db.models.models import Teacher from domain.models.TeacherDataclass import TeacherDataclass diff --git a/backend/db/interface/AbstractDAO.py b/backend/db/interface/AbstractDAO.py index 7162d2f0..d5d846bd 100644 --- a/backend/db/interface/AbstractDAO.py +++ b/backend/db/interface/AbstractDAO.py @@ -1,8 +1,6 @@ from abc import ABC, abstractmethod from typing import Generic, TypeVar -from db.models.models import AbstractModel - T = TypeVar("T") D = TypeVar("D") diff --git a/backend/db/models/models.py b/backend/db/models/models.py index a6c47695..97ab874a 100644 --- a/backend/db/models/models.py +++ b/backend/db/models/models.py @@ -1,12 +1,12 @@ from abc import abstractmethod from dataclasses import dataclass from datetime import datetime -from typing import Any from sqlalchemy import Column, ForeignKey, Table from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship from domain.models.AdminDataclass import AdminDataclass +from domain.models.base_model import JsonRepresentable from domain.models.GroupDataclass import GroupDataclass from domain.models.ProjectDataclass import ProjectDataclass from domain.models.StudentDataclass import StudentDataclass @@ -19,7 +19,7 @@ @dataclass() class AbstractModel: @abstractmethod - def to_domain_model(self) -> Any: + def to_domain_model(self) -> JsonRepresentable: pass diff --git a/backend/domain/models/TeacherDataclass.py b/backend/domain/models/TeacherDataclass.py index 0b11f10b..d36d1215 100644 --- a/backend/domain/models/TeacherDataclass.py +++ b/backend/domain/models/TeacherDataclass.py @@ -1,13 +1,8 @@ from dataclasses import dataclass -from domain.models.UserDataclass import UserDataclass, UserDataClassRequest +from domain.models.UserDataclass import UserDataclass @dataclass() class TeacherDataclass(UserDataclass): pass - - -@dataclass() -class TeacherDataClassRequest(UserDataClassRequest): - pass diff --git a/backend/domain/models/UserDataclass.py b/backend/domain/models/UserDataclass.py index 2798c255..1a0696a7 100644 --- a/backend/domain/models/UserDataclass.py +++ b/backend/domain/models/UserDataclass.py @@ -1,17 +1,10 @@ from dataclasses import dataclass -from pydantic import BaseModel +from domain.models.base_model import JsonRepresentable @dataclass() -class UserDataclass(BaseModel): +class UserDataclass(JsonRepresentable): id: int - # pydanitc will throw an error when creating a new user - name: str - email: str - - -@dataclass() -class UserDataClassRequest(BaseModel): name: str email: str diff --git a/backend/domain/models/base_model.py b/backend/domain/models/base_model.py index 0356cadd..9ab89131 100644 --- a/backend/domain/models/base_model.py +++ b/backend/domain/models/base_model.py @@ -2,7 +2,6 @@ from dataclasses import dataclass -# what is the use of this class when one can use pydantic models? @dataclass() class JsonRepresentable: def to_dict(self) -> dict: diff --git a/backend/fill_database_mock.py b/backend/fill_database_mock.py index 09334a60..6b923ce8 100644 --- a/backend/fill_database_mock.py +++ b/backend/fill_database_mock.py @@ -1,9 +1,9 @@ from datetime import datetime from psycopg2 import tz - -from db.extensions import engine, Base from sqlalchemy.orm import Session + +from db.extensions import Base, engine from db.implementation.SqlAdminDAO import SqlAdminDAO from db.implementation.SqlGroupDAO import SqlGroupDAO from db.implementation.SqlProjectDAO import SqlProjectDAO From 42651ae9e94b96854fbbd00c4a067eeabecf753d Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Wed, 28 Feb 2024 22:05:56 +0100 Subject: [PATCH 08/13] Regenerate requirements.txt --- backend/requirements.txt | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index e8b07a78..ec792dec 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,17 +1,16 @@ -blinker==1.7.0 +annotated-types==0.6.0 +anyio==4.3.0 fastapi==0.110.0 -uvicorn==0.27.1 -click==8.1.7 -Flask==3.0.2 -Flask-SQLAlchemy==3.1.1 -FastAPI-SQLAlchemy-improved==0.5.4 greenlet==3.0.3 -itsdangerous==2.1.2 -Jinja2==3.1.3 -MarkupSafe==2.1.5 -psycopg2==2.9.9 +idna==3.6 +nodeenv==1.8.0 +psycopg2-binary==2.9.9 +pydantic==2.6.3 +pydantic_core==2.16.3 +pyright==1.1.351 ruff==0.2.2 +setuptools==69.1.1 +sniffio==1.3.1 SQLAlchemy==2.0.27 -typing_extensions==4.9.0 -Werkzeug==3.0.1 -pyright==1.1.351 \ No newline at end of file +starlette==0.36.3 +typing_extensions==4.10.0 From 35374a7ccefcf7a9c9588b4f37e69f2b02456559 Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Wed, 28 Feb 2024 22:17:23 +0100 Subject: [PATCH 09/13] Fix models/fill_database_mock.py --- backend/db/implementation/SqlGroupDAO.py | 1 + backend/db/implementation/SqlStudentDAO.py | 2 -- backend/db/models/models.py | 39 +++++++++++++--------- backend/fill_database_mock.py | 20 +++++------ 4 files changed, 35 insertions(+), 27 deletions(-) diff --git a/backend/db/implementation/SqlGroupDAO.py b/backend/db/implementation/SqlGroupDAO.py index aa8a48ef..301e480b 100644 --- a/backend/db/implementation/SqlGroupDAO.py +++ b/backend/db/implementation/SqlGroupDAO.py @@ -19,6 +19,7 @@ def create_group(project_id: int) -> GroupDataclass: raise ItemNotFoundError(msg) new_group: Group = Group(project_id=project_id) session.add(new_group) + session.commit() return new_group.to_domain_model() @staticmethod diff --git a/backend/db/implementation/SqlStudentDAO.py b/backend/db/implementation/SqlStudentDAO.py index cab3dde6..5979f5ea 100644 --- a/backend/db/implementation/SqlStudentDAO.py +++ b/backend/db/implementation/SqlStudentDAO.py @@ -1,4 +1,3 @@ - from sqlalchemy.orm import Session from db.extensions import engine @@ -9,7 +8,6 @@ class SqlStudentDAO(StudentDAO, SqlAbstractDAO[Student, StudentDataclass]): - @staticmethod def get_all() -> list[StudentDataclass]: return SqlAbstractDAO.get_all() diff --git a/backend/db/models/models.py b/backend/db/models/models.py index 97ab874a..1e8cf809 100644 --- a/backend/db/models/models.py +++ b/backend/db/models/models.py @@ -3,8 +3,9 @@ from datetime import datetime from sqlalchemy import Column, ForeignKey, Table -from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship +from sqlalchemy.orm import Mapped, mapped_column, relationship +from db.extensions import Base from domain.models.AdminDataclass import AdminDataclass from domain.models.base_model import JsonRepresentable from domain.models.GroupDataclass import GroupDataclass @@ -24,7 +25,8 @@ def to_domain_model(self) -> JsonRepresentable: @dataclass() -class User(DeclarativeBase, AbstractModel): +class User(Base, AbstractModel): + __tablename__ = "users" name: Mapped[str] email: Mapped[str] id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) @@ -35,6 +37,7 @@ def to_domain_model(self) -> UserDataclass: @dataclass() class Admin(User): + __tablename__ = "admins" id: Mapped[int] = mapped_column(ForeignKey(User.id), primary_key=True) def to_domain_model(self) -> AdminDataclass: @@ -43,26 +46,27 @@ def to_domain_model(self) -> AdminDataclass: teachers_subjects = Table( "teachers_subjects", - DeclarativeBase.metadata, - Column("teacher_id", ForeignKey("teacher.id"), primary_key=True), - Column("subject_id", ForeignKey("subject.id"), primary_key=True), + Base.metadata, + Column("teacher_id", ForeignKey("teachers.id"), primary_key=True), + Column("subject_id", ForeignKey("subjects.id"), primary_key=True), ) students_subjects = Table( "students_subjects", - DeclarativeBase.metadata, - Column("student_id", ForeignKey("student.id"), primary_key=True), - Column("subject_id", ForeignKey("subject.id"), primary_key=True), + Base.metadata, + Column("student_id", ForeignKey("students.id"), primary_key=True), + Column("subject_id", ForeignKey("subjects.id"), primary_key=True), ) students_groups = Table( "students_groups", - DeclarativeBase.metadata, - Column("student_id", ForeignKey("student.id"), primary_key=True), - Column("group_id", ForeignKey("group.id"), primary_key=True), + Base.metadata, + Column("student_id", ForeignKey("students.id"), primary_key=True), + Column("group_id", ForeignKey("groups.id"), primary_key=True), ) @dataclass() class Teacher(User): + __tablename__ = "teachers" id: Mapped[int] = mapped_column(ForeignKey(User.id), primary_key=True) subjects: Mapped[list["Subject"]] = relationship(secondary=teachers_subjects, back_populates="teachers") @@ -72,6 +76,7 @@ def to_domain_model(self) -> TeacherDataclass: @dataclass() class Student(User): + __tablename__ = "students" id: Mapped[int] = mapped_column(ForeignKey(User.id), primary_key=True) subjects: Mapped[list["Subject"]] = relationship(secondary=students_subjects, back_populates="students") groups: Mapped[list["Group"]] = relationship(secondary=students_groups, back_populates="students") @@ -82,7 +87,8 @@ def to_domain_model(self) -> StudentDataclass: @dataclass() -class Subject(DeclarativeBase, AbstractModel): +class Subject(Base, AbstractModel): + __tablename__ = "subjects" name: Mapped[str] id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) teachers: Mapped[list[Teacher]] = relationship(secondary=teachers_subjects, back_populates="subjects") @@ -94,7 +100,8 @@ def to_domain_model(self) -> SubjectDataclass: @dataclass() -class Project(DeclarativeBase, AbstractModel): +class Project(Base, AbstractModel): + __tablename__ = "projects" name: Mapped[str] deadline: Mapped[datetime] archived: Mapped[bool] @@ -120,7 +127,8 @@ def to_domain_model(self) -> ProjectDataclass: @dataclass() -class Group(DeclarativeBase, AbstractModel): +class Group(Base, AbstractModel): + __tablename__ = "groups" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) project_id: Mapped[int] = mapped_column(ForeignKey(Project.id)) project: Mapped[Project] = relationship(back_populates="groups") @@ -132,7 +140,8 @@ def to_domain_model(self) -> GroupDataclass: @dataclass() -class Submission(DeclarativeBase, AbstractModel): +class Submission(Base, AbstractModel): + __tablename__ = "submissions" date_time: Mapped[datetime] state: Mapped[SubmissionState] message: Mapped[str] diff --git a/backend/fill_database_mock.py b/backend/fill_database_mock.py index 6b923ce8..f379f5d4 100644 --- a/backend/fill_database_mock.py +++ b/backend/fill_database_mock.py @@ -1,7 +1,6 @@ from datetime import datetime from psycopg2 import tz -from sqlalchemy.orm import Session from db.extensions import Base, engine from db.implementation.SqlAdminDAO import SqlAdminDAO @@ -12,7 +11,6 @@ from db.implementation.SqlTeacherDAO import SqlTeacherDAO if __name__ == "__main__": - session = Session(engine) Base.metadata.create_all(engine) admin_dao = SqlAdminDAO() @@ -26,16 +24,18 @@ objeprog = subject_dao.create_subject(name="OBJECTGERICHTPROGRAMMEREN") # maak een project voor dat vak - objeprog_project = project_dao.create_project(subject_id=objeprog.id, - name="PROJECT", - archived=False, - visible=True, - requirements="Maak iets in javafx", - max_students=2, - deadline=datetime(2000, 1, 1, 0, 0, 0, tzinfo=tz.LOCAL)) + objprog_project = project_dao.create_project( + subject_id=objeprog.id, + name="PROJECT", + archived=False, + visible=True, + requirements="Maak iets in javafx", + max_students=2, + deadline=datetime(2000, 1, 1, 0, 0, 0, tzinfo=tz.LOCAL), + ) # maak een groepje voor het project van objeprog - groep1 = group_dao.create_group(objeprog_project.id) + groep1 = group_dao.create_group(objprog_project.id) # maak studenten student1 = student_dao.create_student("Student1", "Student1@gmail.com") From fcd1b31ac107f7f0a61b2f7a19a2dc2eb23ccc90 Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Wed, 28 Feb 2024 22:57:46 +0100 Subject: [PATCH 10/13] Fix DAOs --- backend/app.py | 4 +-- backend/db/implementation/SqlAbstractDAO.py | 20 +++++++------- backend/db/implementation/SqlAdminDAO.py | 4 ++- backend/db/implementation/SqlGroupDAO.py | 20 +++++++------- backend/db/implementation/SqlProjectDAO.py | 18 ++++--------- backend/db/implementation/SqlStudentDAO.py | 14 +++------- backend/db/implementation/SqlSubjectDAO.py | 27 ++++++------------- backend/db/implementation/SqlSubmissionDAO.py | 14 +++++----- backend/db/implementation/SqlTeacherDAO.py | 15 +++-------- backend/db/implementation/SqlUserDAO.py | 11 +++----- backend/db/interface/AbstractDAO.py | 6 ++--- backend/db/interface/AdminDAO.py | 6 +++-- backend/db/interface/GroupDAO.py | 22 +++++++-------- backend/db/interface/ProjectDAO.py | 10 +++---- backend/db/interface/StudentDAO.py | 7 +++-- backend/db/interface/SubjectDAO.py | 19 +++++-------- backend/db/interface/SubmissionDAO.py | 13 ++++----- backend/db/interface/TeacherDAO.py | 7 +++-- backend/db/interface/UserDAO.py | 3 +-- backend/db/models/models.py | 4 +-- backend/requirements.txt | 3 +++ backend/routes/teachers.py | 6 ++--- 22 files changed, 101 insertions(+), 152 deletions(-) diff --git a/backend/app.py b/backend/app.py index e66c8560..0eb2e828 100644 --- a/backend/app.py +++ b/backend/app.py @@ -6,7 +6,7 @@ app = FastAPI() # Koppel routes uit andere modules. -app.include_router(teachers_router, prefix="/teachers") +app.include_router(teachers_router) if __name__ == "__main__": - uvicorn.run("fastapi_code:app") + uvicorn.run("app:app") diff --git a/backend/db/implementation/SqlAbstractDAO.py b/backend/db/implementation/SqlAbstractDAO.py index d5f50610..06a5cffa 100644 --- a/backend/db/implementation/SqlAbstractDAO.py +++ b/backend/db/implementation/SqlAbstractDAO.py @@ -1,4 +1,3 @@ -from abc import ABC from typing import Generic, TypeVar from sqlalchemy import select @@ -7,17 +6,19 @@ from db.errors.database_errors import ItemNotFoundError from db.extensions import engine from db.models.models import AbstractModel +from domain.models.base_model import JsonRepresentable T = TypeVar("T", bound=AbstractModel) -D = TypeVar("D") +D = TypeVar("D", bound=JsonRepresentable) -class SqlAbstractDAO(Generic[T, D], ABC): +class SqlAbstractDAO(Generic[T, D]): + def __init__(self) -> None: + self.model_class: type[T] - @staticmethod - def get_object(ident: int) -> D: + def get_object(self, ident: int) -> D: with Session(engine) as session: - generic_object: T | None = session.get(T, ident) + generic_object: T | None = session.get(self.model_class, ident) if not generic_object: msg = f"object with id {ident} not found" @@ -25,10 +26,7 @@ def get_object(ident: int) -> D: return generic_object.to_domain_model() - @staticmethod - def get_all() -> list[D]: + def get_all(self) -> list[D]: with Session(engine) as session: - generic_objects: list[T] = list(session.scalars(select(T)).all()) + generic_objects: list[T] = list(session.scalars(select(self.model_class)).all()) return [generic_object.to_domain_model() for generic_object in generic_objects] - - diff --git a/backend/db/implementation/SqlAdminDAO.py b/backend/db/implementation/SqlAdminDAO.py index 19f1273d..7f1349b7 100644 --- a/backend/db/implementation/SqlAdminDAO.py +++ b/backend/db/implementation/SqlAdminDAO.py @@ -7,7 +7,9 @@ from domain.models.AdminDataclass import AdminDataclass -class SqlAdminDAO(AdminDAO, SqlAbstractDAO[Admin, AdminDataclass]): +class SqlAdminDAO(SqlAbstractDAO[Admin, AdminDataclass], AdminDAO): + def __init__(self) -> None: + self.model_class = Admin def create_admin(self, name: str, email: str) -> AdminDataclass: with Session(engine) as session: diff --git a/backend/db/implementation/SqlGroupDAO.py b/backend/db/implementation/SqlGroupDAO.py index 301e480b..7f7fae72 100644 --- a/backend/db/implementation/SqlGroupDAO.py +++ b/backend/db/implementation/SqlGroupDAO.py @@ -9,9 +9,11 @@ from domain.models.StudentDataclass import StudentDataclass -class SqlGroupDAO(GroupDAO, SqlAbstractDAO[Group, GroupDataclass]): - @staticmethod - def create_group(project_id: int) -> GroupDataclass: +class SqlGroupDAO(SqlAbstractDAO[Group, GroupDataclass], GroupDAO): + def __init__(self) -> None: + self.model_class = Group + + def create_group(self, project_id: int) -> GroupDataclass: with Session(engine) as session: project: Project | None = session.get(Project, ident=project_id) if not project: @@ -22,8 +24,7 @@ def create_group(project_id: int) -> GroupDataclass: session.commit() return new_group.to_domain_model() - @staticmethod - def get_groups_of_project(project_id: int) -> list[GroupDataclass]: + def get_groups_of_project(self, project_id: int) -> list[GroupDataclass]: with Session(engine) as session: project: Project | None = session.get(Project, ident=project_id) if not project: @@ -32,8 +33,7 @@ def get_groups_of_project(project_id: int) -> list[GroupDataclass]: groups: list[Group] = project.groups return [group.to_domain_model() for group in groups] - @staticmethod - def get_groups_of_student(student_id: int) -> list[GroupDataclass]: + def get_groups_of_student(self, student_id: int) -> list[GroupDataclass]: with Session(engine) as session: student: Student | None = session.get(Student, ident=student_id) if not student: @@ -42,8 +42,7 @@ def get_groups_of_student(student_id: int) -> list[GroupDataclass]: groups: list[Group] = student.groups return [group.to_domain_model() for group in groups] - @staticmethod - def add_student_to_group(student_id: int, group_id: int) -> None: + def add_student_to_group(self, student_id: int, group_id: int) -> None: with Session(engine) as session: student: Student | None = session.get(Student, ident=student_id) group: Group | None = session.get(Group, ident=group_id) @@ -60,8 +59,7 @@ def add_student_to_group(student_id: int, group_id: int) -> None: group.students.append(student) session.commit() - @staticmethod - def get_students_of_group(group_id: int) -> list[StudentDataclass]: + def get_students_of_group(self, group_id: int) -> list[StudentDataclass]: with Session(engine) as session: group: Group | None = session.get(Group, ident=group_id) if not group: diff --git a/backend/db/implementation/SqlProjectDAO.py b/backend/db/implementation/SqlProjectDAO.py index 39228b66..c8632aa0 100644 --- a/backend/db/implementation/SqlProjectDAO.py +++ b/backend/db/implementation/SqlProjectDAO.py @@ -10,18 +10,11 @@ from domain.models.ProjectDataclass import ProjectDataclass -class SqlProjectDAO(ProjectDAO, SqlAbstractDAO[Project, ProjectDataclass]): +class SqlProjectDAO(SqlAbstractDAO[Project, ProjectDataclass], ProjectDAO): + def __init__(self) -> None: + self.model_class = Project - @staticmethod - def get_all() -> list[ProjectDataclass]: - return SqlAbstractDAO.get_all() - - @staticmethod - def get_object(ident: int) -> ProjectDataclass: - return SqlAbstractDAO.get_object(ident) - - @staticmethod - def create_project(subject_id: int, name: str, deadline: datetime, archived: bool, requirements: str, + def create_project(self, subject_id: int, name: str, deadline: datetime, archived: bool, requirements: str, visible: bool, max_students: int) -> ProjectDataclass: with Session(engine) as session: subject: Subject | None = session.get(Subject, subject_id) @@ -37,8 +30,7 @@ def create_project(subject_id: int, name: str, deadline: datetime, archived: boo session.commit() return new_project.to_domain_model() - @staticmethod - def get_projects_of_subject(subject_id: int) -> list[ProjectDataclass]: + def get_projects_of_subject(self, subject_id: int) -> list[ProjectDataclass]: with Session(engine) as session: subject: Subject | None = session.get(Subject, ident=subject_id) if not subject: diff --git a/backend/db/implementation/SqlStudentDAO.py b/backend/db/implementation/SqlStudentDAO.py index 5979f5ea..e8556495 100644 --- a/backend/db/implementation/SqlStudentDAO.py +++ b/backend/db/implementation/SqlStudentDAO.py @@ -7,17 +7,11 @@ from domain.models.StudentDataclass import StudentDataclass -class SqlStudentDAO(StudentDAO, SqlAbstractDAO[Student, StudentDataclass]): - @staticmethod - def get_all() -> list[StudentDataclass]: - return SqlAbstractDAO.get_all() +class SqlStudentDAO(SqlAbstractDAO[Student, StudentDataclass], StudentDAO): + def __init__(self) -> None: + self.model_class = Student - @staticmethod - def get_object(ident: int) -> StudentDataclass: - return SqlAbstractDAO.get_object(ident) - - @staticmethod - def create_student(name: str, email: str) -> StudentDataclass: + def create_student(self, name: str, email: str) -> StudentDataclass: with Session(engine) as session: new_student: Student = Student(name=name, email=email) session.add(new_student) diff --git a/backend/db/implementation/SqlSubjectDAO.py b/backend/db/implementation/SqlSubjectDAO.py index a3b0f061..5d368901 100644 --- a/backend/db/implementation/SqlSubjectDAO.py +++ b/backend/db/implementation/SqlSubjectDAO.py @@ -8,26 +8,18 @@ from domain.models.SubjectDataclass import SubjectDataclass -class SqlSubjectDAO(SubjectDAO, SqlAbstractDAO[Subject, SubjectDataclass]): +class SqlSubjectDAO(SqlAbstractDAO[Subject, SubjectDataclass], SubjectDAO): + def __init__(self) -> None: + self.model_class = Subject - @staticmethod - def get_all() -> list[SubjectDataclass]: - return SqlAbstractDAO.get_all() - - @staticmethod - def get_object(ident: int) -> SubjectDataclass: - return SqlAbstractDAO.get_object(ident) - - @staticmethod - def create_subject(name: str) -> SubjectDataclass: + def create_subject(self, name: str) -> SubjectDataclass: with Session(engine) as session: new_subject = Subject(name=name) session.add(new_subject) session.commit() return new_subject.to_domain_model() - @staticmethod - def get_subjects_of_teacher(teacher_id: int) -> list[SubjectDataclass]: + def get_subjects_of_teacher(self, teacher_id: int) -> list[SubjectDataclass]: with Session(engine) as session: teacher: Teacher | None = session.get(Teacher, ident=teacher_id) if not teacher: @@ -36,8 +28,7 @@ def get_subjects_of_teacher(teacher_id: int) -> list[SubjectDataclass]: subjects: list[Subject] = teacher.subjects return [vak.to_domain_model() for vak in subjects] - @staticmethod - def add_student_to_subject(student_id: int, subject_id: int) -> None: + def add_student_to_subject(self, student_id: int, subject_id: int) -> None: with Session(engine) as session: student: Student | None = session.get(Student, ident=student_id) subject: Subject | None = session.get(Subject, ident=subject_id) @@ -55,8 +46,7 @@ def add_student_to_subject(student_id: int, subject_id: int) -> None: student.subjects.append(subject) session.commit() - @staticmethod - def add_teacher_to_subject(teacher_id: int, subject_id: int) -> None: + def add_teacher_to_subject(self, teacher_id: int, subject_id: int) -> None: with Session(engine) as session: teacher: Teacher | None = session.get(Teacher, ident=teacher_id) subject: Subject | None = session.get(Subject, ident=subject_id) @@ -74,8 +64,7 @@ def add_teacher_to_subject(teacher_id: int, subject_id: int) -> None: teacher.subjects.append(subject) session.commit() - @staticmethod - def get_subjects_of_student(student_id: int) -> list[SubjectDataclass]: + def get_subjects_of_student(self, student_id: int) -> list[SubjectDataclass]: with Session(engine) as session: student: Student | None = session.get(Student, ident=student_id) if not student: diff --git a/backend/db/implementation/SqlSubmissionDAO.py b/backend/db/implementation/SqlSubmissionDAO.py index f563ffca..432296dd 100644 --- a/backend/db/implementation/SqlSubmissionDAO.py +++ b/backend/db/implementation/SqlSubmissionDAO.py @@ -10,9 +10,11 @@ from domain.models.SubmissionDataclass import SubmissionDataclass, SubmissionState -class SqlSubmissionDAO(SubmissionDAO, SqlAbstractDAO[Submission, SubmissionDataclass]): - @staticmethod - def create_submission(student_id: int, group_id: int, message: str, state: SubmissionState, +class SqlSubmissionDAO(SqlAbstractDAO[Submission, SubmissionDataclass], SubmissionDAO): + def __init__(self) -> None: + self.model_class = Submission + + def create_submission(self, student_id: int, group_id: int, message: str, state: SubmissionState, date_time: datetime) -> SubmissionDataclass: with Session(engine) as session: student: Student | None = session.get(Student, ident=student_id) @@ -30,8 +32,7 @@ def create_submission(student_id: int, group_id: int, message: str, state: Submi session.commit() return new_submission.to_domain_model() - @staticmethod - def get_submissions_of_student(student_id: int) -> list[SubmissionDataclass]: + def get_submissions_of_student(self, student_id: int) -> list[SubmissionDataclass]: with Session(engine) as session: student: Student | None = session.get(Student, ident=student_id) if not student: @@ -40,8 +41,7 @@ def get_submissions_of_student(student_id: int) -> list[SubmissionDataclass]: submissions: list[Submission] = student.submissions return [submission.to_domain_model() for submission in submissions] - @staticmethod - def get_submissions_of_group(group_id: int) -> list[SubmissionDataclass]: + def get_submissions_of_group(self, group_id: int) -> list[SubmissionDataclass]: with Session(engine) as session: group: Group | None = session.get(Group, ident=group_id) if not group: diff --git a/backend/db/implementation/SqlTeacherDAO.py b/backend/db/implementation/SqlTeacherDAO.py index 60e5cf84..6092e87d 100644 --- a/backend/db/implementation/SqlTeacherDAO.py +++ b/backend/db/implementation/SqlTeacherDAO.py @@ -7,18 +7,11 @@ from domain.models.TeacherDataclass import TeacherDataclass -class SqlTeacherDAO(TeacherDAO, SqlAbstractDAO[Teacher, TeacherDataclass]): +class SqlTeacherDAO(SqlAbstractDAO[Teacher, TeacherDataclass], TeacherDAO): + def __init__(self) -> None: + self.model_class = Teacher - @staticmethod - def get_all() -> list[TeacherDataclass]: - return SqlAbstractDAO.get_all() - - @staticmethod - def get_object(ident: int) -> TeacherDataclass: - return SqlAbstractDAO.get_object(ident) - - @staticmethod - def create_teacher(name: str, email: str) -> TeacherDataclass: + def create_teacher(self, name: str, email: str) -> TeacherDataclass: with Session(engine) as session: new_teacher = Teacher(name=name, email=email) session.add(new_teacher) diff --git a/backend/db/implementation/SqlUserDAO.py b/backend/db/implementation/SqlUserDAO.py index 18db371f..85e3dd18 100644 --- a/backend/db/implementation/SqlUserDAO.py +++ b/backend/db/implementation/SqlUserDAO.py @@ -4,11 +4,6 @@ from domain.models.UserDataclass import UserDataclass -class SqlUserDAO(UserDAO, SqlAbstractDAO[User, UserDataclass]): - @staticmethod - def get_all() -> list[UserDataclass]: - return SqlAbstractDAO.get_all() - - @staticmethod - def get_object(ident: int) -> UserDataclass: - return SqlAbstractDAO.get_object(ident) +class SqlUserDAO(SqlAbstractDAO[User, UserDataclass], UserDAO): + def __init__(self) -> None: + self.model_class = User diff --git a/backend/db/interface/AbstractDAO.py b/backend/db/interface/AbstractDAO.py index d5d846bd..d42b2551 100644 --- a/backend/db/interface/AbstractDAO.py +++ b/backend/db/interface/AbstractDAO.py @@ -7,12 +7,10 @@ class AbstractDAO(Generic[T, D], ABC): - @staticmethod @abstractmethod - def get_object(ident: int) -> D: + def get_object(self, ident: int) -> D: raise NotImplementedError - @staticmethod @abstractmethod - def get_all() -> list[D]: + def get_all(self) -> list[D]: raise NotImplementedError diff --git a/backend/db/interface/AdminDAO.py b/backend/db/interface/AdminDAO.py index b9132e17..b5dac9c6 100644 --- a/backend/db/interface/AdminDAO.py +++ b/backend/db/interface/AdminDAO.py @@ -1,9 +1,11 @@ -from abc import ABC, abstractmethod +from abc import abstractmethod +from db.interface.AbstractDAO import AbstractDAO +from db.models.models import Admin from domain.models.AdminDataclass import AdminDataclass -class AdminDAO(ABC): +class AdminDAO(AbstractDAO[Admin, AdminDataclass]): @abstractmethod def create_admin(self, name: str, email: str) -> AdminDataclass: """ diff --git a/backend/db/interface/GroupDAO.py b/backend/db/interface/GroupDAO.py index 6eb744b1..b1b9e9d3 100644 --- a/backend/db/interface/GroupDAO.py +++ b/backend/db/interface/GroupDAO.py @@ -1,13 +1,14 @@ -from abc import ABC, abstractmethod +from abc import abstractmethod +from db.interface.AbstractDAO import AbstractDAO +from db.models.models import Group from domain.models.GroupDataclass import GroupDataclass from domain.models.StudentDataclass import StudentDataclass -class GroupDAO(ABC): - @staticmethod +class GroupDAO(AbstractDAO[Group, GroupDataclass]): @abstractmethod - def create_group(project_id: int) -> GroupDataclass: + def create_group(self, project_id: int) -> GroupDataclass: """ Creëert een nieuw GroupDataClass in de database en associeert het met een ProjectDataClass. @@ -17,9 +18,8 @@ def create_group(project_id: int) -> GroupDataclass: """ raise NotImplementedError - @staticmethod @abstractmethod - def get_groups_of_project(project_id: int) -> list[GroupDataclass]: + def get_groups_of_project(self, project_id: int) -> list[GroupDataclass]: """ Haalt alle groepen op die bij een bepaald project horen. @@ -28,9 +28,8 @@ def get_groups_of_project(project_id: int) -> list[GroupDataclass]: """ raise NotImplementedError - @staticmethod @abstractmethod - def get_groups_of_student(student_id: int) -> list[GroupDataclass]: + def get_groups_of_student(self, student_id: int) -> list[GroupDataclass]: """ Haalt alle groepen op die bij een bepaalde student horen. @@ -39,9 +38,8 @@ def get_groups_of_student(student_id: int) -> list[GroupDataclass]: """ raise NotImplementedError - @staticmethod @abstractmethod - def add_student_to_group(student_id: int, group_id: int) -> None: + def add_student_to_group(self, student_id: int, group_id: int) -> None: """ Gaat een student toevoegen aan een groep @@ -51,8 +49,8 @@ def add_student_to_group(student_id: int, group_id: int) -> None: """ raise NotImplementedError - @staticmethod - def get_students_of_group(group_id: int) -> list[StudentDataclass]: + @abstractmethod + def get_students_of_group(self, group_id: int) -> list[StudentDataclass]: """ Gaat alle studenten geven die in een bepaalde groep zitten diff --git a/backend/db/interface/ProjectDAO.py b/backend/db/interface/ProjectDAO.py index cfab73f3..9864b47c 100644 --- a/backend/db/interface/ProjectDAO.py +++ b/backend/db/interface/ProjectDAO.py @@ -1,4 +1,4 @@ -from abc import ABC, abstractmethod +from abc import abstractmethod from datetime import datetime from db.interface.AbstractDAO import AbstractDAO @@ -6,11 +6,10 @@ from domain.models.ProjectDataclass import ProjectDataclass -class ProjectDAO(AbstractDAO[Project, ProjectDataclass], ABC): +class ProjectDAO(AbstractDAO[Project, ProjectDataclass]): - @staticmethod @abstractmethod - def create_project(subject_id: int, name: str, deadline: datetime, archived: bool, requirements: str, + def create_project(self, subject_id: int, name: str, deadline: datetime, archived: bool, requirements: str, visible: bool, max_students: int) -> ProjectDataclass: """ Creëert een nieuw ProjectDataClass in de database en associeert het met een SubjectDataClass. @@ -27,9 +26,8 @@ def create_project(subject_id: int, name: str, deadline: datetime, archived: boo """ raise NotImplementedError - @staticmethod @abstractmethod - def get_projects_of_subject(subject_id: int) -> list[ProjectDataclass]: + def get_projects_of_subject(self, subject_id: int) -> list[ProjectDataclass]: """ Haalt alle projecten op die bij een bepaald subject horen. diff --git a/backend/db/interface/StudentDAO.py b/backend/db/interface/StudentDAO.py index 95065dbb..5bc9deec 100644 --- a/backend/db/interface/StudentDAO.py +++ b/backend/db/interface/StudentDAO.py @@ -1,14 +1,13 @@ -from abc import ABC, abstractmethod +from abc import abstractmethod from db.interface.AbstractDAO import AbstractDAO from db.models.models import Student from domain.models.StudentDataclass import StudentDataclass -class StudentDAO(AbstractDAO[Student, StudentDataclass], ABC): - @staticmethod +class StudentDAO(AbstractDAO[Student, StudentDataclass]): @abstractmethod - def create_student(name: str, email: str) -> StudentDataclass: + def create_student(self, name: str, email: str) -> StudentDataclass: """ Maakt een nieuwe student aan. diff --git a/backend/db/interface/SubjectDAO.py b/backend/db/interface/SubjectDAO.py index a14b5520..b759ab12 100644 --- a/backend/db/interface/SubjectDAO.py +++ b/backend/db/interface/SubjectDAO.py @@ -1,14 +1,13 @@ -from abc import ABC, abstractmethod +from abc import abstractmethod from db.interface.AbstractDAO import AbstractDAO from db.models.models import Subject from domain.models.SubjectDataclass import SubjectDataclass -class SubjectDAO(AbstractDAO[Subject, SubjectDataclass], ABC): - @staticmethod +class SubjectDAO(AbstractDAO[Subject, SubjectDataclass]): @abstractmethod - def create_subject(name: str) -> SubjectDataclass: + def create_subject(self, name: str) -> SubjectDataclass: """ Creëert een nieuw SubjectDataclass in de database. @@ -17,9 +16,8 @@ def create_subject(name: str) -> SubjectDataclass: """ raise NotImplementedError - @staticmethod @abstractmethod - def get_subjects_of_teacher(teacher_id: int) -> list[SubjectDataclass]: + def get_subjects_of_teacher(self, teacher_id: int) -> list[SubjectDataclass]: """ Haalt de subjects op die door een bepaalde teacher worden gegeven. @@ -28,9 +26,8 @@ def get_subjects_of_teacher(teacher_id: int) -> list[SubjectDataclass]: """ raise NotImplementedError - @staticmethod @abstractmethod - def get_subjects_of_student(student_id: int) -> list[SubjectDataclass]: + def get_subjects_of_student(self, student_id: int) -> list[SubjectDataclass]: """ Haalt de subjects op die door een bepaalde student worden gevolgd. @@ -39,9 +36,8 @@ def get_subjects_of_student(student_id: int) -> list[SubjectDataclass]: """ raise NotImplementedError - @staticmethod @abstractmethod - def add_student_to_subject(student_id: int, subject_id: int) -> None: + def add_student_to_subject(self, student_id: int, subject_id: int) -> None: """ Voegt een student toe aan een vak. @@ -51,9 +47,8 @@ def add_student_to_subject(student_id: int, subject_id: int) -> None: """ raise NotImplementedError - @staticmethod @abstractmethod - def add_teacher_to_subject(teacher_id: int, subject_id: int) -> None: + def add_teacher_to_subject(self, teacher_id: int, subject_id: int) -> None: """ Voegt een teacher toe aan een vak. diff --git a/backend/db/interface/SubmissionDAO.py b/backend/db/interface/SubmissionDAO.py index b7ed0bdf..30356b8f 100644 --- a/backend/db/interface/SubmissionDAO.py +++ b/backend/db/interface/SubmissionDAO.py @@ -1,4 +1,4 @@ -from abc import ABC, abstractmethod +from abc import abstractmethod from datetime import datetime from db.interface.AbstractDAO import AbstractDAO @@ -6,10 +6,9 @@ from domain.models.SubmissionDataclass import SubmissionDataclass, SubmissionState -class SubmissionDAO(AbstractDAO[Submission, SubmissionDataclass], ABC): - @staticmethod +class SubmissionDAO(AbstractDAO[Submission, SubmissionDataclass]): @abstractmethod - def create_submission(student_id: int, group_id: int, message: str, + def create_submission(self, student_id: int, group_id: int, message: str, state: SubmissionState, date_time: datetime) -> SubmissionDataclass: """ Creëert een nieuw SubmissionDataClass in de database en associeert het met een StudentDataclass en een @@ -23,9 +22,8 @@ def create_submission(student_id: int, group_id: int, message: str, """ raise NotImplementedError - @staticmethod @abstractmethod - def get_submissions_of_student(student_id: int) -> list[SubmissionDataclass]: + def get_submissions_of_student(self, student_id: int) -> list[SubmissionDataclass]: """ Haalt alle projecten op die bij een bepaalde student horen. @@ -34,9 +32,8 @@ def get_submissions_of_student(student_id: int) -> list[SubmissionDataclass]: """ raise NotImplementedError - @staticmethod @abstractmethod - def get_submissions_of_group(group_id: int) -> list[SubmissionDataclass]: + def get_submissions_of_group(self, group_id: int) -> list[SubmissionDataclass]: """ Haalt alle projecten op die bij een bepaalde groep horen. diff --git a/backend/db/interface/TeacherDAO.py b/backend/db/interface/TeacherDAO.py index 10c6faee..925c2906 100644 --- a/backend/db/interface/TeacherDAO.py +++ b/backend/db/interface/TeacherDAO.py @@ -1,14 +1,13 @@ -from abc import ABC, abstractmethod +from abc import abstractmethod from db.interface.AbstractDAO import AbstractDAO from db.models.models import Teacher from domain.models.TeacherDataclass import TeacherDataclass -class TeacherDAO(AbstractDAO[Teacher, TeacherDataclass], ABC): - @staticmethod +class TeacherDAO(AbstractDAO[Teacher, TeacherDataclass]): @abstractmethod - def create_teacher(name: str, email: str) -> TeacherDataclass: + def create_teacher(self, name: str, email: str) -> TeacherDataclass: """ Maakt een nieuwe teacher aan. diff --git a/backend/db/interface/UserDAO.py b/backend/db/interface/UserDAO.py index 2e73beff..84acb923 100644 --- a/backend/db/interface/UserDAO.py +++ b/backend/db/interface/UserDAO.py @@ -1,9 +1,8 @@ -from abc import ABC from db.interface.AbstractDAO import AbstractDAO from db.models.models import User from domain.models.UserDataclass import UserDataclass -class UserDAO(AbstractDAO[User, UserDataclass], ABC): +class UserDAO(AbstractDAO[User, UserDataclass]): pass diff --git a/backend/db/models/models.py b/backend/db/models/models.py index 1e8cf809..f1fc5659 100644 --- a/backend/db/models/models.py +++ b/backend/db/models/models.py @@ -1,13 +1,13 @@ from abc import abstractmethod from dataclasses import dataclass from datetime import datetime +from typing import Any from sqlalchemy import Column, ForeignKey, Table from sqlalchemy.orm import Mapped, mapped_column, relationship from db.extensions import Base from domain.models.AdminDataclass import AdminDataclass -from domain.models.base_model import JsonRepresentable from domain.models.GroupDataclass import GroupDataclass from domain.models.ProjectDataclass import ProjectDataclass from domain.models.StudentDataclass import StudentDataclass @@ -20,7 +20,7 @@ @dataclass() class AbstractModel: @abstractmethod - def to_domain_model(self) -> JsonRepresentable: + def to_domain_model(self) -> Any: pass diff --git a/backend/requirements.txt b/backend/requirements.txt index ec792dec..679534f5 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,7 +1,9 @@ annotated-types==0.6.0 anyio==4.3.0 +click==8.1.7 fastapi==0.110.0 greenlet==3.0.3 +h11==0.14.0 idna==3.6 nodeenv==1.8.0 psycopg2-binary==2.9.9 @@ -14,3 +16,4 @@ sniffio==1.3.1 SQLAlchemy==2.0.27 starlette==0.36.3 typing_extensions==4.10.0 +uvicorn==0.27.1 diff --git a/backend/routes/teachers.py b/backend/routes/teachers.py index 7d990480..ff298241 100644 --- a/backend/routes/teachers.py +++ b/backend/routes/teachers.py @@ -1,9 +1,8 @@ - from fastapi import APIRouter from db.implementation.SqlTeacherDAO import SqlTeacherDAO from db.interface.TeacherDAO import TeacherDAO -from domain.models.TeacherDataclass import TeacherDataclass, TeacherDataClassRequest +from domain.models.TeacherDataclass import TeacherDataclass teachers_router = APIRouter() @@ -20,9 +19,9 @@ def get_teacher(teacher_id: int) -> TeacherDataclass: return dao.get_object(teacher_id) +""" @teachers_router.post("/teachers") def create_teacher(teacher_data: TeacherDataClassRequest) -> TeacherDataclass: - # can be commented because of the validation that happens through pydantic and FastAPI # woordjes if not teacher_data: # woordjes return Response(json.dumps({"error": "Foute JSON of Content-Type"}), status=HTTPStatus.BAD_REQUEST) @@ -36,3 +35,4 @@ def create_teacher(teacher_data: TeacherDataClassRequest) -> TeacherDataclass: # is niet meer nodig omdat teacher_data een instance is van TeacherDataclass # woordjes lesgever = TeacherDataclass(**teacher_data) # Vul alle velden van het dataobject in met de json return dao.create_teacher(teacher_data.name, teacher_data.email) +""" From f810d0ad4e2c442a7fc7feb469e9f3ac23990f4f Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Wed, 28 Feb 2024 23:05:33 +0100 Subject: [PATCH 11/13] Remove Any type --- backend/db/models/models.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/backend/db/models/models.py b/backend/db/models/models.py index f1fc5659..03019e09 100644 --- a/backend/db/models/models.py +++ b/backend/db/models/models.py @@ -1,13 +1,14 @@ from abc import abstractmethod from dataclasses import dataclass from datetime import datetime -from typing import Any +from typing import Generic, TypeVar from sqlalchemy import Column, ForeignKey, Table from sqlalchemy.orm import Mapped, mapped_column, relationship from db.extensions import Base from domain.models.AdminDataclass import AdminDataclass +from domain.models.base_model import JsonRepresentable from domain.models.GroupDataclass import GroupDataclass from domain.models.ProjectDataclass import ProjectDataclass from domain.models.StudentDataclass import StudentDataclass @@ -16,11 +17,12 @@ from domain.models.TeacherDataclass import TeacherDataclass from domain.models.UserDataclass import UserDataclass +D = TypeVar("D", bound=JsonRepresentable) @dataclass() -class AbstractModel: +class AbstractModel(Generic[D]): @abstractmethod - def to_domain_model(self) -> Any: + def to_domain_model(self) -> D: pass From cd23febe46f50bebb0f04b0565f345b7594b2d4f Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Thu, 29 Feb 2024 17:45:01 +0000 Subject: [PATCH 12/13] Rename get_object() to get() --- backend/db/implementation/SqlAbstractDAO.py | 2 +- backend/db/interface/AbstractDAO.py | 2 +- backend/routes/teachers.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/db/implementation/SqlAbstractDAO.py b/backend/db/implementation/SqlAbstractDAO.py index 06a5cffa..cc7f2c2a 100644 --- a/backend/db/implementation/SqlAbstractDAO.py +++ b/backend/db/implementation/SqlAbstractDAO.py @@ -16,7 +16,7 @@ class SqlAbstractDAO(Generic[T, D]): def __init__(self) -> None: self.model_class: type[T] - def get_object(self, ident: int) -> D: + def get(self, ident: int) -> D: with Session(engine) as session: generic_object: T | None = session.get(self.model_class, ident) diff --git a/backend/db/interface/AbstractDAO.py b/backend/db/interface/AbstractDAO.py index d42b2551..2c4ade30 100644 --- a/backend/db/interface/AbstractDAO.py +++ b/backend/db/interface/AbstractDAO.py @@ -8,7 +8,7 @@ class AbstractDAO(Generic[T, D], ABC): @abstractmethod - def get_object(self, ident: int) -> D: + def get(self, ident: int) -> D: raise NotImplementedError @abstractmethod diff --git a/backend/routes/teachers.py b/backend/routes/teachers.py index ff298241..c69579b1 100644 --- a/backend/routes/teachers.py +++ b/backend/routes/teachers.py @@ -16,7 +16,7 @@ def get_teachers() -> list[TeacherDataclass]: @teachers_router.get("/teachers/{teacher_id}") def get_teacher(teacher_id: int) -> TeacherDataclass: dao: TeacherDAO = SqlTeacherDAO() - return dao.get_object(teacher_id) + return dao.get(teacher_id) """ From 29c48fa3af789419c6eac78e1d80f73f986cda82 Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Fri, 1 Mar 2024 14:56:15 +0100 Subject: [PATCH 13/13] Add description field to projects --- backend/db/implementation/SqlProjectDAO.py | 26 +++++++++++++++++----- backend/db/interface/ProjectDAO.py | 19 +++++++++++----- backend/db/models/models.py | 3 +++ backend/domain/models/ProjectDataclass.py | 1 + backend/fill_database_mock.py | 3 ++- 5 files changed, 41 insertions(+), 11 deletions(-) diff --git a/backend/db/implementation/SqlProjectDAO.py b/backend/db/implementation/SqlProjectDAO.py index c8632aa0..c3ae7674 100644 --- a/backend/db/implementation/SqlProjectDAO.py +++ b/backend/db/implementation/SqlProjectDAO.py @@ -14,17 +14,33 @@ class SqlProjectDAO(SqlAbstractDAO[Project, ProjectDataclass], ProjectDAO): def __init__(self) -> None: self.model_class = Project - def create_project(self, subject_id: int, name: str, deadline: datetime, archived: bool, requirements: str, - visible: bool, max_students: int) -> ProjectDataclass: + def create_project( + self, + subject_id: int, + name: str, + deadline: datetime, + archived: bool, + description: str, + requirements: str, + visible: bool, + max_students: int, + ) -> ProjectDataclass: with Session(engine) as session: subject: Subject | None = session.get(Subject, subject_id) if not subject: msg = f"Subject with id {subject_id} not found" raise ItemNotFoundError(msg) - new_project: Project = Project(subject_id=subject_id, name=name, deadline=deadline, - archived=archived, requirements=requirements, visible=visible, - max_students=max_students) + new_project: Project = Project( + subject_id=subject_id, + name=name, + deadline=deadline, + archived=archived, + description=description, + requirements=requirements, + visible=visible, + max_students=max_students, + ) session.add(new_project) session.commit() diff --git a/backend/db/interface/ProjectDAO.py b/backend/db/interface/ProjectDAO.py index 9864b47c..ed2d1e6a 100644 --- a/backend/db/interface/ProjectDAO.py +++ b/backend/db/interface/ProjectDAO.py @@ -7,16 +7,25 @@ class ProjectDAO(AbstractDAO[Project, ProjectDataclass]): - @abstractmethod - def create_project(self, subject_id: int, name: str, deadline: datetime, archived: bool, requirements: str, - visible: bool, max_students: int) -> ProjectDataclass: + def create_project( + self, + subject_id: int, + name: str, + deadline: datetime, + archived: bool, + description: str, + requirements: str, + visible: bool, + max_students: int, + ) -> ProjectDataclass: """ Creëert een nieuw ProjectDataClass in de database en associeert het met een SubjectDataClass. :param max_students: maximaal aantal studenten per groep per project - :param visible: of het project zichtbaar is voor de studenten - :param requirements: Uitleg van het project + :param visible: of het project zichtbaar is voor de studentent + :param description: Beschrijving van het project + :param requirements: Vereisten van het project :param archived: Of het project gearchiveerd is :param name: De naam van het project :param deadline: De deadline van het project diff --git a/backend/db/models/models.py b/backend/db/models/models.py index 03019e09..b63d8261 100644 --- a/backend/db/models/models.py +++ b/backend/db/models/models.py @@ -19,6 +19,7 @@ D = TypeVar("D", bound=JsonRepresentable) + @dataclass() class AbstractModel(Generic[D]): @abstractmethod @@ -107,6 +108,7 @@ class Project(Base, AbstractModel): name: Mapped[str] deadline: Mapped[datetime] archived: Mapped[bool] + description: Mapped[str] requirements: Mapped[str] visible: Mapped[bool] max_students: Mapped[int] @@ -121,6 +123,7 @@ def to_domain_model(self) -> ProjectDataclass: name=self.name, deadline=self.deadline, archived=self.archived, + description=self.description, requirements=self.requirements, visible=self.visible, max_students=self.max_students, diff --git a/backend/domain/models/ProjectDataclass.py b/backend/domain/models/ProjectDataclass.py index 37dd6975..2c45bf36 100644 --- a/backend/domain/models/ProjectDataclass.py +++ b/backend/domain/models/ProjectDataclass.py @@ -10,6 +10,7 @@ class ProjectDataclass(JsonRepresentable): name: str deadline: datetime archived: bool + description: str requirements: str visible: bool max_students: int diff --git a/backend/fill_database_mock.py b/backend/fill_database_mock.py index f379f5d4..da04f028 100644 --- a/backend/fill_database_mock.py +++ b/backend/fill_database_mock.py @@ -29,7 +29,8 @@ name="PROJECT", archived=False, visible=True, - requirements="Maak iets in javafx", + description="Maak iets in javafx", + requirements="Een zip bestand met java-code", max_students=2, deadline=datetime(2000, 1, 1, 0, 0, 0, tzinfo=tz.LOCAL), )