diff --git a/backend/app.py b/backend/app.py index efaf32bc..0eb2e828 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1,24 +1,12 @@ -import os +import uvicorn +from fastapi import FastAPI -from flask import Flask +from routes.teachers import teachers_router -from db.extensions import db -from routes.teachers import teachers_blueprint - -app = Flask(__name__) +app = FastAPI() # Koppel routes uit andere modules. -app.register_blueprint(teachers_blueprint) - -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) +app.include_router(teachers_router) if __name__ == "__main__": - app.run(debug=True) + uvicorn.run("app:app") diff --git a/backend/db/extensions.py b/backend/db/extensions.py index 86ff1dad..137d3c8a 100644 --- a/backend/db/extensions.py +++ b/backend/db/extensions.py @@ -1,9 +1,17 @@ -from flask_sqlalchemy import SQLAlchemy +import os + +from sqlalchemy import create_engine from sqlalchemy.orm import DeclarativeBase +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") -class Base(DeclarativeBase): - pass +DB_URI = f"postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_database}" +engine = create_engine(DB_URI) -db = SQLAlchemy(model_class=Base) +class Base(DeclarativeBase): + pass diff --git a/backend/db/implementation/SqlAbstractDAO.py b/backend/db/implementation/SqlAbstractDAO.py new file mode 100644 index 00000000..cc7f2c2a --- /dev/null +++ b/backend/db/implementation/SqlAbstractDAO.py @@ -0,0 +1,32 @@ +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 db.models.models import AbstractModel +from domain.models.base_model import JsonRepresentable + +T = TypeVar("T", bound=AbstractModel) +D = TypeVar("D", bound=JsonRepresentable) + + +class SqlAbstractDAO(Generic[T, D]): + def __init__(self) -> None: + self.model_class: type[T] + + def get(self, ident: int) -> D: + with Session(engine) as session: + generic_object: T | None = session.get(self.model_class, ident) + + if not generic_object: + msg = f"object with id {ident} not found" + raise ItemNotFoundError(msg) + + return generic_object.to_domain_model() + + def get_all(self) -> list[D]: + with Session(engine) as session: + 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 2aff92ec..7f1349b7 100644 --- a/backend/db/implementation/SqlAdminDAO.py +++ b/backend/db/implementation/SqlAdminDAO.py @@ -1,27 +1,19 @@ -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(SqlAbstractDAO[Admin, AdminDataclass], AdminDAO): + def __init__(self) -> None: + self.model_class = Admin 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..7f7fae72 100644 --- a/backend/db/implementation/SqlGroupDAO.py +++ b/backend/db/implementation/SqlGroupDAO.py @@ -1,65 +1,69 @@ +from sqlalchemy.orm import Session + from db.errors.database_errors import ItemNotFoundError, UniqueConstraintError -from db.extensions import db +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 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() +class SqlGroupDAO(SqlAbstractDAO[Group, GroupDataclass], GroupDAO): + def __init__(self) -> None: + self.model_class = Group - 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() + 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: + msg = f"Project with id {project} not found" + raise ItemNotFoundError(msg) + new_group: Group = Group(project_id=project_id) + session.add(new_group) + session.commit() + 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] + 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] + 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) + 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] + 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..c3ae7674 100644 --- a/backend/db/implementation/SqlProjectDAO.py +++ b/backend/db/implementation/SqlProjectDAO.py @@ -1,40 +1,56 @@ from datetime import datetime +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.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, - 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() +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, + 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, + description=description, + requirements=requirements, + visible=visible, + max_students=max_students, + ) + + session.add(new_project) + session.commit() + return new_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, 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..e8556495 100644 --- a/backend/db/implementation/SqlStudentDAO.py +++ b/backend/db/implementation/SqlStudentDAO.py @@ -1,26 +1,19 @@ -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.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() - - 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] +class SqlStudentDAO(SqlAbstractDAO[Student, StudentDataclass], StudentDAO): + def __init__(self) -> None: + self.model_class = Student 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() + 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..5d368901 100644 --- a/backend/db/implementation/SqlSubjectDAO.py +++ b/backend/db/implementation/SqlSubjectDAO.py @@ -1,70 +1,74 @@ +from sqlalchemy.orm import Session + from db.errors.database_errors import ItemNotFoundError, UniqueConstraintError -from db.extensions import db +from db.extensions import engine +from db.implementation.SqlAbstractDAO import SqlAbstractDAO 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(SqlAbstractDAO[Subject, SubjectDataclass], SubjectDAO): + def __init__(self) -> None: + self.model_class = Subject - 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() + 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() 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] - - 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] + 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 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) + with Session(engine) as session: + student: Student | None = session.get(Student, ident=student_id) + subject: Subject | None = 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) - db.session.commit() + student.subjects.append(subject) + session.commit() 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) + with Session(engine) as session: + teacher: Teacher | None = session.get(Teacher, ident=teacher_id) + subject: Subject | None = 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() + 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: + 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..432296dd 100644 --- a/backend/db/implementation/SqlSubmissionDAO.py +++ b/backend/db/implementation/SqlSubmissionDAO.py @@ -1,49 +1,51 @@ from datetime import datetime +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.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, - date_time: datetime) -> SubmissionDataclass: - - 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() +class SqlSubmissionDAO(SqlAbstractDAO[Submission, SubmissionDataclass], SubmissionDAO): + def __init__(self) -> None: + self.model_class = Submission - 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 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) + 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() 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] + 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] + 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..6092e87d 100644 --- a/backend/db/implementation/SqlTeacherDAO.py +++ b/backend/db/implementation/SqlTeacherDAO.py @@ -1,28 +1,19 @@ -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.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) - - return teacher.to_domain_model() - - 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] +class SqlTeacherDAO(SqlAbstractDAO[Teacher, TeacherDataclass], TeacherDAO): + def __init__(self) -> None: + self.model_class = Teacher 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() + 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..85e3dd18 100644 --- a/backend/db/implementation/SqlUserDAO.py +++ b/backend/db/implementation/SqlUserDAO.py @@ -1,21 +1,9 @@ -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(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 new file mode 100644 index 00000000..2c4ade30 --- /dev/null +++ b/backend/db/interface/AbstractDAO.py @@ -0,0 +1,16 @@ +from abc import ABC, abstractmethod +from typing import Generic, TypeVar + +T = TypeVar("T") +D = TypeVar("D") + + +class AbstractDAO(Generic[T, D], ABC): + + @abstractmethod + def get(self, ident: int) -> D: + raise NotImplementedError + + @abstractmethod + def get_all(self) -> list[D]: + raise NotImplementedError diff --git a/backend/db/interface/AdminDAO.py b/backend/db/interface/AdminDAO.py index f776df7e..b5dac9c6 100644 --- a/backend/db/interface/AdminDAO.py +++ b/backend/db/interface/AdminDAO.py @@ -1,29 +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): - @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 - +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 c76ccd73..b1b9e9d3 100644 --- a/backend/db/interface/GroupDAO.py +++ b/backend/db/interface/GroupDAO.py @@ -1,10 +1,12 @@ -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): +class GroupDAO(AbstractDAO[Group, GroupDataclass]): @abstractmethod def create_group(self, project_id: int) -> GroupDataclass: """ @@ -16,17 +18,6 @@ def create_group(self, project_id: int) -> GroupDataclass: """ raise NotImplementedError - @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]: """ @@ -58,6 +49,7 @@ def add_student_to_group(self, student_id: int, group_id: int) -> None: """ raise NotImplementedError + @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 e1db39dd..ed2d1e6a 100644 --- a/backend/db/interface/ProjectDAO.py +++ b/backend/db/interface/ProjectDAO.py @@ -1,19 +1,31 @@ -from abc import ABC, abstractmethod +from abc import 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]): @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 @@ -23,17 +35,6 @@ def create_project(self, subject_id: int, name: str, deadline: datetime, archive """ raise NotImplementedError - @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]: """ diff --git a/backend/db/interface/StudentDAO.py b/backend/db/interface/StudentDAO.py index 19e7b6c6..5bc9deec 100644 --- a/backend/db/interface/StudentDAO.py +++ b/backend/db/interface/StudentDAO.py @@ -1,29 +1,11 @@ -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(ABC): - @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 - +class StudentDAO(AbstractDAO[Student, StudentDataclass]): @abstractmethod def create_student(self, name: str, email: str) -> StudentDataclass: """ diff --git a/backend/db/interface/SubjectDAO.py b/backend/db/interface/SubjectDAO.py index cf8a2d0a..b759ab12 100644 --- a/backend/db/interface/SubjectDAO.py +++ b/backend/db/interface/SubjectDAO.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 Subject from domain.models.SubjectDataclass import SubjectDataclass -class SubjectDAO(ABC): +class SubjectDAO(AbstractDAO[Subject, SubjectDataclass]): @abstractmethod def create_subject(self, name: str) -> SubjectDataclass: """ @@ -14,17 +16,6 @@ def create_subject(self, name: str) -> SubjectDataclass: """ raise NotImplementedError - @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]: """ diff --git a/backend/db/interface/SubmissionDAO.py b/backend/db/interface/SubmissionDAO.py index 09651dca..30356b8f 100644 --- a/backend/db/interface/SubmissionDAO.py +++ b/backend/db/interface/SubmissionDAO.py @@ -1,10 +1,12 @@ -from abc import ABC, abstractmethod +from abc import 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]): @abstractmethod def create_submission(self, student_id: int, group_id: int, message: str, state: SubmissionState, date_time: datetime) -> SubmissionDataclass: @@ -20,18 +22,6 @@ def create_submission(self, student_id: int, group_id: int, message: str, """ raise NotImplementedError - @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]: """ diff --git a/backend/db/interface/TeacherDAO.py b/backend/db/interface/TeacherDAO.py index 5ea45861..925c2906 100644 --- a/backend/db/interface/TeacherDAO.py +++ b/backend/db/interface/TeacherDAO.py @@ -1,29 +1,11 @@ -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(ABC): - @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 - +class TeacherDAO(AbstractDAO[Teacher, TeacherDataclass]): @abstractmethod def create_teacher(self, name: str, email: str) -> TeacherDataclass: """ diff --git a/backend/db/interface/UserDAO.py b/backend/db/interface/UserDAO.py index e4d85b25..84acb923 100644 --- a/backend/db/interface/UserDAO.py +++ b/backend/db/interface/UserDAO.py @@ -1,25 +1,8 @@ -from abc import ABC, abstractmethod +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]): + pass diff --git a/backend/db/models/models.py b/backend/db/models/models.py index 8aa1425c..b63d8261 100644 --- a/backend/db/models/models.py +++ b/backend/db/models/models.py @@ -1,11 +1,14 @@ +from abc import abstractmethod from dataclasses import dataclass from datetime import datetime +from typing import Generic, TypeVar from sqlalchemy import Column, ForeignKey, Table from sqlalchemy.orm import Mapped, mapped_column, relationship -from db.extensions import db +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 @@ -14,9 +17,19 @@ from domain.models.TeacherDataclass import TeacherDataclass from domain.models.UserDataclass import UserDataclass +D = TypeVar("D", bound=JsonRepresentable) + + +@dataclass() +class AbstractModel(Generic[D]): + @abstractmethod + def to_domain_model(self) -> D: + pass + @dataclass() -class User(db.Model): +class User(Base, AbstractModel): + __tablename__ = "users" name: Mapped[str] email: Mapped[str] id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) @@ -27,6 +40,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: @@ -35,26 +49,27 @@ def to_domain_model(self) -> AdminDataclass: teachers_subjects = Table( "teachers_subjects", - db.Model.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", - db.Model.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", - db.Model.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") @@ -64,6 +79,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") @@ -74,7 +90,8 @@ def to_domain_model(self) -> StudentDataclass: @dataclass() -class Subject(db.Model): +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") @@ -86,10 +103,12 @@ def to_domain_model(self) -> SubjectDataclass: @dataclass() -class Project(db.Model): +class Project(Base, AbstractModel): + __tablename__ = "projects" name: Mapped[str] deadline: Mapped[datetime] archived: Mapped[bool] + description: Mapped[str] requirements: Mapped[str] visible: Mapped[bool] max_students: Mapped[int] @@ -104,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, @@ -112,7 +132,8 @@ def to_domain_model(self) -> ProjectDataclass: @dataclass() -class Group(db.Model): +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") @@ -124,7 +145,8 @@ def to_domain_model(self) -> GroupDataclass: @dataclass() -class Submission(db.Model): +class Submission(Base, AbstractModel): + __tablename__ = "submissions" date_time: Mapped[datetime] state: Mapped[SubmissionState] message: Mapped[str] 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/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/fill_database_mock.py b/backend/fill_database_mock.py index 0981928a..da04f028 100644 --- a/backend/fill_database_mock.py +++ b/backend/fill_database_mock.py @@ -2,8 +2,7 @@ from psycopg2 import tz -from app import app -from db.extensions import db +from db.extensions import Base, engine from db.implementation.SqlAdminDAO import SqlAdminDAO from db.implementation.SqlGroupDAO import SqlGroupDAO from db.implementation.SqlProjectDAO import SqlProjectDAO @@ -12,43 +11,45 @@ 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) + Base.metadata.create_all(engine) + + 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 + objprog_project = project_dao.create_project( + subject_id=objeprog.id, + name="PROJECT", + archived=False, + visible=True, + 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), + ) + + # maak een groepje voor het project van objeprog + groep1 = group_dao.create_group(objprog_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/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/requirements.txt b/backend/requirements.txt index d10c6e2c..679534f5 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,14 +1,19 @@ -blinker==1.7.0 +annotated-types==0.6.0 +anyio==4.3.0 click==8.1.7 -Flask==3.0.2 -Flask-SQLAlchemy==3.1.1 +fastapi==0.110.0 greenlet==3.0.3 -itsdangerous==2.1.2 -Jinja2==3.1.3 -MarkupSafe==2.1.5 -psycopg2==2.9.9 +h11==0.14.0 +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 +uvicorn==0.27.1 diff --git a/backend/routes/teachers.py b/backend/routes/teachers.py index 69c69323..c69579b1 100644 --- a/backend/routes/teachers.py +++ b/backend/routes/teachers.py @@ -1,51 +1,38 @@ -import json -from http import HTTPStatus - -from flask import Blueprint, 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() -@teachers_blueprint.route("/teachers") -def get_teachers() -> Response: +@teachers_router.get("/teachers") +def get_teachers() -> list[TeacherDataclass]: dao: TeacherDAO = SqlTeacherDAO() + return dao.get_all() - 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") - -@teachers_blueprint.route("/teachers/") -def get_teacher(teacher_id: int) -> Response: +@teachers_router.get("/teachers/{teacher_id}") +def get_teacher(teacher_id: int) -> TeacherDataclass: dao: TeacherDAO = SqlTeacherDAO() + return dao.get(teacher_id) - teacher: TeacherDataclass = dao.get_teacher(teacher_id) - teacher_json = teacher.to_dict() - - return Response(json.dumps(teacher_json, indent=4), content_type="application/json") +""" +@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) -@teachers_blueprint.route("/teachers", methods=["POST"]) -def create_teacher() -> Response: - teacher_data: dict = request.get_json() - - 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) + # woordjes validation_result: ValidationResult = TeacherValidator.validate(teacher_data) + # + # woordjes if not validation_result: + # woordjes 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) - - return Response(json.dumps(lesgever.to_dict()), status=HTTPStatus.CREATED) + # 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) +""" 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