From 038f9da93d3952eb52d0b24dcc508b9d99fb8843 Mon Sep 17 00:00:00 2001 From: dominik24c Date: Sat, 14 Jan 2023 01:37:31 +0100 Subject: [PATCH] fix group, students, project_supervisor endpoints for coordinator, fix importing csv students endpoint and add copy project supervisors from last year group, refactor code --- backend/app/__init__.py | 5 +- backend/app/base/models.py | 2 +- backend/app/base/schemas.py | 5 + backend/app/commands/init_db.py | 37 ----- backend/app/coordinator/models.py | 5 - backend/app/coordinator/routes/enrollments.py | 69 +-------- .../routes/examination_schedule.py | 3 +- backend/app/coordinator/routes/groups.py | 59 +++----- .../coordinator/routes/project_supervisor.py | 140 +++++------------- backend/app/coordinator/routes/students.py | 63 +++----- backend/app/coordinator/routes/year_group.py | 3 +- backend/app/coordinator/schemas/__init__.py | 4 +- backend/app/coordinator/schemas/groups.py | 6 +- .../coordinator/schemas/project_supervisor.py | 12 +- backend/app/coordinator/schemas/students.py | 7 +- backend/app/coordinator/utils.py | 33 ++--- backend/app/errors.py | 7 +- backend/app/factory.py | 44 ------ backend/app/project_supervisor/models.py | 16 +- .../project_supervisor/routes/enrollments.py | 3 +- .../routes/project_grade_sheet.py | 3 +- backend/app/project_supervisor/schemas.py | 5 +- backend/app/students/models.py | 29 ++-- backend/app/students/routes/__init__.py | 2 - backend/app/students/routes/enrollments.py | 3 +- backend/app/students/routes/registrations.py | 7 +- backend/app/students/routes/year_group.py | 32 ---- backend/app/students/schemas.py | 29 +--- .../{b3003ddd0564_.py => 3fd120fc5e12_.py} | 94 +++++------- 29 files changed, 198 insertions(+), 529 deletions(-) create mode 100644 backend/app/base/schemas.py delete mode 100644 backend/app/commands/init_db.py delete mode 100644 backend/app/factory.py delete mode 100644 backend/app/students/routes/year_group.py rename backend/migrations/versions/{b3003ddd0564_.py => 3fd120fc5e12_.py} (89%) diff --git a/backend/app/__init__.py b/backend/app/__init__.py index c66c151..2a3db48 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -7,11 +7,10 @@ from flask_cors import CORS from .config import config from .dependencies import db, ma from .commands.startapp import startapp -from .commands.init_db import init_db from .commands.clear_db import clear_db from .utils import import_models from .api import api_bp -from .errors import request_entity_too_large, register_error_handlers +from .errors import register_error_handlers def create_app(config_name: str = '') -> APIFlask: @@ -37,11 +36,9 @@ def create_app(config_name: str = '') -> APIFlask: # register commands app.cli.add_command(startapp) - app.cli.add_command(init_db) app.cli.add_command(clear_db) # register errors register_error_handlers(app) - # app.register_error_handler(413, request_entity_too_large) return app diff --git a/backend/app/base/models.py b/backend/app/base/models.py index a32f831..0012135 100644 --- a/backend/app/base/models.py +++ b/backend/app/base/models.py @@ -12,4 +12,4 @@ class Person(db.Model): first_name = db.Column(db.String(255), index=True, nullable=False) last_name = db.Column(db.String(255), index=True, nullable=False) - email = db.Column(db.String(120), unique=True) + email = db.Column(db.String(120), index=True, nullable=False) diff --git a/backend/app/base/schemas.py b/backend/app/base/schemas.py new file mode 100644 index 0000000..15f7738 --- /dev/null +++ b/backend/app/base/schemas.py @@ -0,0 +1,5 @@ +from marshmallow import fields, Schema + + +class MessageSchema(Schema): + message = fields.Str() \ No newline at end of file diff --git a/backend/app/commands/init_db.py b/backend/app/commands/init_db.py deleted file mode 100644 index f6f4d0f..0000000 --- a/backend/app/commands/init_db.py +++ /dev/null @@ -1,37 +0,0 @@ -from flask.cli import with_appcontext -from click import command - -from ..dependencies import db -# from ..factory import ProjectSupervisorFactory, GroupFactory, StudentFactory - - -@command('init_db') -@with_appcontext -def init_db() -> None: - """Fill database with some data""" - db.drop_all() - db.create_all() - # - # num_of_supervisors = 5 - # - # projects_supervisors = [ProjectSupervisorFactory() for _ in range(num_of_supervisors)] - # db.session.add_all(projects_supervisors) - # db.session.commit() - # - # groups = [GroupFactory(project_supervisor=projects_supervisors[i]) for i in range(num_of_supervisors)] - # db.session.add_all(groups) - # db.session.commit() - # - # num_of_students = num_of_supervisors * 3 - # students = [StudentFactory(group=groups[i % num_of_supervisors]) for i in range(num_of_students)] - # - # max_count = 10 - # max_length = len(students) - # start_count = 0 - # - # while True: - # if start_count > max_length: - # break - # db.session.add_all(students[start_count:max_count]) - # db.session.commit() - # start_count += max_count diff --git a/backend/app/coordinator/models.py b/backend/app/coordinator/models.py index 02de479..e69de29 100644 --- a/backend/app/coordinator/models.py +++ b/backend/app/coordinator/models.py @@ -1,5 +0,0 @@ -# from ..base.models import Base -# -# -# class Coordinator(Base): -# __tablename__ = 'coordinators' diff --git a/backend/app/coordinator/routes/enrollments.py b/backend/app/coordinator/routes/enrollments.py index eeda48f..bcc935c 100644 --- a/backend/app/coordinator/routes/enrollments.py +++ b/backend/app/coordinator/routes/enrollments.py @@ -5,12 +5,13 @@ from flask import abort, current_app from sqlalchemy import or_, and_ from flask_sqlalchemy import get_debug_queries -from ..schemas import MessageSchema, TermOfDefenceSchema, TermOfDefenceListSchema, \ - TemporaryAvailabilityListSchema, AssignedGroupToTermOfDefenceListSchema, GroupIdSchema -from ...examination_schedule.models import TermOfDefence, TemporaryAvailability, committee +from ..schemas import TermOfDefenceSchema, TemporaryAvailabilityListSchema, AssignedGroupToTermOfDefenceListSchema, \ + GroupIdSchema +from ...examination_schedule.models import TermOfDefence, TemporaryAvailability from ...students.models import YearGroup from ...project_supervisor.models import ProjectSupervisor from ...dependencies import db +from ...base.schemas import MessageSchema from ..utils import generate_range_dates from ..query.enrollments import get_term_of_defence_by_id_and_examination_schedule_id, set_new_group_to_term_of_defence, \ get_examination_schedule_by_id @@ -18,68 +19,6 @@ from ..query.enrollments import get_term_of_defence_by_id_and_examination_schedu bp = APIBlueprint("enrollments", __name__, url_prefix="/enrollments") -@bp.post('//generate') -@bp.output(MessageSchema) -def generate_term_of_defence_for_this_examination_schedule(examination_schedule_id: int) -> dict: - ex = get_examination_schedule_by_id(examination_schedule_id) - limit = current_app.config.get('LIMIT_MEMBERS_PER_COMMITTEE', 3) - - term_of_defences = TermOfDefence.query. \ - filter(TermOfDefence.examination_schedule_id == examination_schedule_id).first() - if term_of_defences is not None: - abort(400, - "First you have to delete all term of defences for this examination schedule to generate them again!") - - temporary_availabilities = TemporaryAvailability.query. \ - filter(TemporaryAvailability.examination_schedule_id == examination_schedule_id). \ - join(TemporaryAvailability.project_supervisor). \ - all() - if len(temporary_availabilities) == 0: - abort(404, "Not found temporary availabilities for project supervisors") - - dates = generate_range_dates(ex.start_date, ex.end_date, ex.duration_time) - - term_of_defences = [] - for d in dates: - e = d + datetime.timedelta(minutes=ex.duration_time) - t = list(filter(lambda ta: ta.start_date <= d and ta.end_date >= e, temporary_availabilities)) - if len(t) >= limit: - projects_supervisors = [t[i].project_supervisor for i in range(limit)] - term_of_defence = TermOfDefence(start_date=d, end_date=e, examination_schedule_id=examination_schedule_id) - term_of_defence.members_of_committee = projects_supervisors - term_of_defences.append(term_of_defence) - db.session.add_all(term_of_defences) - db.session.commit() - return {"message": "Term of defences was generated!"} - - -@bp.post('//clear-term-of-defences/') -@bp.output(MessageSchema) -def clear_generated_term_of_defences(examination_schedule_id: int) -> dict: - get_examination_schedule_by_id(examination_schedule_id) - - # count_defences = db.func.count(ProjectSupervisor.id) - # td = db.session.query(TermOfDefence, count_defences). \ - # join(ProjectSupervisor, isouter=True). \ - # filter(TermOfDefence.examination_schedule_id == examination_schedule_id). \ - # group_by(TermOfDefence.id).having(count_defences > 0).first() - # print(td) - # if td is not None: - # abort(400, "First you remove group assigned to term of defence to clear term of defences!") - - td = TermOfDefence.query. \ - filter(TermOfDefence.examination_schedule_id == examination_schedule_id).all() - while True: - for t in td[0:10]: - db.session.delete(t) - db.session.commit() - if len(td) == 0: - break - td[0:10] = [] - # print(len(get_debug_queries())) - return {"message": "Term of defences was deleted!"} - - @bp.post('//add') @bp.input(TermOfDefenceSchema) @bp.output(MessageSchema) diff --git a/backend/app/coordinator/routes/examination_schedule.py b/backend/app/coordinator/routes/examination_schedule.py index 67b89e9..b4b9800 100644 --- a/backend/app/coordinator/routes/examination_schedule.py +++ b/backend/app/coordinator/routes/examination_schedule.py @@ -9,9 +9,10 @@ from ...dependencies import db from ...examination_schedule.models import ExaminationSchedule, TermOfDefence from ...students.models import Group, YearGroup from ...project_supervisor.models import ProjectSupervisor -from ..schemas import ExaminationScheduleSchema, ExaminationScheduleUpdateSchema, MessageSchema, \ +from ..schemas import ExaminationScheduleSchema, ExaminationScheduleUpdateSchema, \ ExaminationSchedulesQuerySchema, ExaminationSchedulesPaginationSchema from ..utils import generate_examination_schedule_pdf_file +from ...base.schemas import MessageSchema bp = APIBlueprint("examination_schedule", __name__, url_prefix="/examination_schedule") diff --git a/backend/app/coordinator/routes/groups.py b/backend/app/coordinator/routes/groups.py index 706cdff..6e9b0d2 100644 --- a/backend/app/coordinator/routes/groups.py +++ b/backend/app/coordinator/routes/groups.py @@ -1,13 +1,13 @@ -from flask import abort, current_app +from flask import abort from apiflask import APIBlueprint from flask_sqlalchemy import get_debug_queries from ...students.models import Group, Student, YearGroup, ProjectGradeSheet -from ...project_supervisor.models import ProjectSupervisor, YearGroupProjectSupervisors -from ..schemas import GroupEditSchema, GroupsPaginationSchema, GroupCreateSchema, MessageSchema, GroupQuerySchema, \ - DetailGroupSchema +from ...project_supervisor.models import ProjectSupervisor +from ..schemas import GroupEditSchema, GroupsPaginationSchema, GroupCreateSchema, GroupQuerySchema, DetailGroupSchema from ...dependencies import db from ...base.utils import paginate_models +from ...base.schemas import MessageSchema from ..utils import attach_points_for_first_and_second_term_to_group_models bp = APIBlueprint("groups", __name__, url_prefix="/groups") @@ -38,7 +38,7 @@ def list_groups(year_group_id: int, query: dict) -> dict: @bp.output(MessageSchema, status_code=201) def create_group(year_group_id: int, data: dict) -> dict: name = data['name'] - students_indexes = data['students'] + students_ids = data['students'] project_supervisor_id = data['project_supervisor_id'] yg = YearGroup.query.filter(YearGroup.id == year_group_id).first() @@ -47,33 +47,21 @@ def create_group(year_group_id: int, data: dict) -> dict: project_supervisor = ProjectSupervisor.query.filter_by(id=project_supervisor_id).first() - limit_student_per_group = current_app.config.get('LIMIT_STUDENTS_PER_GROUP') if project_supervisor is None: abort(404, f"Not found project supervisor!") - elif limit_student_per_group is not None and limit_student_per_group < len(students_indexes): - abort(400, f"Too much students you want add to group, " - f"The group can have only {limit_student_per_group} students") - - limit = db.session.query(db.func.count(ProjectSupervisor.id)). \ - join(Group).filter(ProjectSupervisor.id == project_supervisor_id).group_by(ProjectSupervisor.id).scalar() - - ygps = YearGroupProjectSupervisors.query. \ - filter(YearGroupProjectSupervisors.year_group_id == year_group_id, - YearGroupProjectSupervisors.project_supervisor_id == project_supervisor_id).first() - if limit is not None and ygps is not None and limit >= ygps.limit_group: - abort(400, "Can't create new group, project supervisor achieved a limit of groups") group = Group(name=name, project_supervisor_id=project_supervisor_id, year_group_id=year_group_id) students_without_groups = db.session.query(Student, Group). \ join(Group, Student.groups). \ filter(Group.year_group_id == year_group_id). \ - filter(db.or_(*[Student.index == idx for idx in students_indexes])).all() + filter(db.or_(*[Student.id == st_id for st_id in students_ids])).all() + if len(students_without_groups) > 0: abort(400, "One or more students have already belonged to group!") - students = db.session.query(Student).filter(Student.index.in_(students_indexes)).all() - if len(students) != len(students_indexes): + students = db.session.query(Student).filter(Student.id.in_(students_ids)).all() + if len(students) != len(students_ids): abort(404, "Not found students!") db.session.add(group) @@ -81,9 +69,6 @@ def create_group(year_group_id: int, data: dict) -> dict: for student in students: group.students.append(student) - - db.session.commit() - pgs = ProjectGradeSheet(group_id=group.id) db.session.add(pgs) db.session.commit() @@ -91,19 +76,19 @@ def create_group(year_group_id: int, data: dict) -> dict: return {"message": "Group was created!"} -@bp.get("//detail/") +@bp.get("//detail/") @bp.output(DetailGroupSchema) -def detail_group(id: int) -> Group: - group = Group.query.filter_by(id=id).first() +def detail_group(group_id: int) -> Group: + group = Group.query.filter_by(id=group_id).first() if group is None: abort(404, f"Not found group!") return group -@bp.delete("//") +@bp.delete("//") @bp.output(MessageSchema, status_code=202) -def delete_group(id: int) -> dict: - group = Group.query.filter_by(id=id).first() +def delete_group(group_id: int) -> dict: + group = Group.query.filter_by(id=group_id).first() if group is None: abort(404, f"Not found group!") @@ -113,26 +98,26 @@ def delete_group(id: int) -> dict: return {"message": "Group was deleted!"} -@bp.put("//") +@bp.put("//") @bp.input(GroupEditSchema) @bp.output(MessageSchema) -def edit_group(id: int, data: dict) -> dict: +def edit_group(group_id: int, data: dict) -> dict: if not data: abort(400, 'You have passed empty data!') - group_query = Group.query.filter_by(id=id) + group_query = Group.query.filter_by(id=group_id) group = group_query.first() if group is None: abort(404, f"Not found group!") - students_indexes = data.get('students') + students_ids = data.get('students') name = data.get('name') project_supervisor_id = data.get('project_supervisor_id') - if students_indexes is not None: - students = db.session.query(Student).filter(Student.index.in_(students_indexes)).all() - if len(students_indexes) != len(students): + if students_ids is not None: + students = db.session.query(Student).filter(Student.id.in_(students_ids)).all() + if len(students_ids) != len(students): abort(404, 'Not found students!') group.students = students diff --git a/backend/app/coordinator/routes/project_supervisor.py b/backend/app/coordinator/routes/project_supervisor.py index 3c04e8f..9861fbe 100644 --- a/backend/app/coordinator/routes/project_supervisor.py +++ b/backend/app/coordinator/routes/project_supervisor.py @@ -2,41 +2,21 @@ from flask import abort from apiflask import APIBlueprint from flask_sqlalchemy import get_debug_queries -from ...project_supervisor.models import ProjectSupervisor, YearGroupProjectSupervisors +from ...project_supervisor.models import ProjectSupervisor from ...students.models import Group, YearGroup from ..schemas import ProjectSupervisorSchema, ProjectSupervisorEditSchema, ProjectSupervisorsPaginationSchema, \ - ProjectSupervisorCreateSchema, MessageSchema, ProjectSupervisorQuerySchema, ProjectSupervisorYearGroupSchema + ProjectSupervisorCreateSchema, MessageWithIdSchema, ProjectSupervisorQuerySchema +from ...base.schemas import MessageSchema from ...dependencies import db from ...base.utils import paginate_models bp = APIBlueprint("project_supervisor", __name__, url_prefix="/project_supervisor") -@bp.get("/") -@bp.input(ProjectSupervisorQuerySchema, location='query') -@bp.output(ProjectSupervisorsPaginationSchema) -def list_project_supervisors(query: dict) -> dict: - fullname = query.get('fullname') - order_by_first_name = query.get('order_by_first_name') - order_by_last_name = query.get('order_by_last_name') - page = query.get('page') - per_page = query.get('per_page') - - project_supervisor_query = ProjectSupervisor.search_by_fullname_and_mode_and_order_by_first_name_or_last_name( - None, fullname, order_by_first_name, order_by_last_name) - - data = paginate_models(page, project_supervisor_query, per_page) - # print(get_debug_queries()[0]) - return { - "project_supervisors": data['items'], - "max_pages": data['max_pages'] - } - - @bp.get("//") @bp.input(ProjectSupervisorQuerySchema, location='query') @bp.output(ProjectSupervisorsPaginationSchema) -def list_project_supervisors_by_year_group(year_group_id: int, query: dict) -> dict: +def list_project_supervisors(year_group_id: int, query: dict) -> dict: fullname = query.get('fullname') order_by_first_name = query.get('order_by_first_name') order_by_last_name = query.get('order_by_last_name') @@ -54,44 +34,44 @@ def list_project_supervisors_by_year_group(year_group_id: int, query: dict) -> d } -@bp.post("/") +@bp.post("//") @bp.input(ProjectSupervisorCreateSchema) -@bp.output(MessageSchema, status_code=201) -def create_project_supervisor(data: dict) -> dict: - first_name = data['first_name'] - last_name = data['last_name'] - project_supervisor = ProjectSupervisor.query.filter_by(first_name=first_name).filter_by(last_name=last_name).first() +@bp.output(MessageWithIdSchema, status_code=201) +def create_project_supervisor(year_group_id: int, data: dict) -> dict: + year_group = YearGroup.query.filter(YearGroup.id == year_group_id).first() + if year_group is None: + abort(404, "Not found year group!") + + email = data['email'] + project_supervisor = ProjectSupervisor.query.filter(ProjectSupervisor.email == email).first() if project_supervisor is not None: abort(400, "Project Supervisor has already exists!") - project_supervisor = ProjectSupervisor(**data) - + project_supervisor = ProjectSupervisor(**data, year_group_id=year_group_id) db.session.add(project_supervisor) db.session.commit() return {"message": "Project Supervisor was created!", "id": project_supervisor.id} -@bp.get("//detail/") +@bp.get("//detail/") @bp.output(ProjectSupervisorSchema) -def detail_project_supervisor(id: int) -> ProjectSupervisor: - project_supervisor = ProjectSupervisor.query.filter_by(id=id).first() +def detail_project_supervisor(project_supervisor_id: int) -> ProjectSupervisor: + project_supervisor = ProjectSupervisor.query.filter_by(id=project_supervisor_id).first() if project_supervisor is None: abort(404, 'Not found project supervisor!') return project_supervisor -@bp.delete("//") +@bp.delete("//") @bp.output(MessageSchema) -def delete_project_supervisor(id: int) -> dict: - project_supervisor = ProjectSupervisor.query.filter_by(id=id).first() +def delete_project_supervisor(project_supervisor_id: int) -> dict: + project_supervisor = ProjectSupervisor.query.filter_by(id=project_supervisor_id).first() if project_supervisor is None: abort(404, "Not found project supervisor!") - count_groups = db.session.query(db.func.count(ProjectSupervisor.id)).join(Group). \ - filter(ProjectSupervisor.id == id).group_by(ProjectSupervisor.id).scalar() - - if count_groups is not None and count_groups > 0: + count_groups = len(Group.query.filter(Group.project_supervisor_id == project_supervisor.id).all()) + if count_groups > 0: abort(400, "Project Supervisor has at least one group!") db.session.delete(project_supervisor) @@ -99,14 +79,14 @@ def delete_project_supervisor(id: int) -> dict: return {"message": "Project Supervisor was deleted!"} -@bp.put("//") +@bp.put("//") @bp.input(ProjectSupervisorEditSchema) @bp.output(MessageSchema) -def edit_project_supervisor(id: int, data: dict) -> dict: +def edit_project_supervisor(project_supervisor_id: int, data: dict) -> dict: if not data: abort(400, 'You have passed empty data!') - project_supervisor_query = ProjectSupervisor.query.filter_by(id=id) + project_supervisor_query = ProjectSupervisor.query.filter_by(id=project_supervisor_id) project_supervisor = project_supervisor_query.first() if project_supervisor is None: @@ -117,65 +97,27 @@ def edit_project_supervisor(id: int, data: dict) -> dict: return {"message": "Project Supervisor was updated!"} -@bp.post("//year-group//") -@bp.input(ProjectSupervisorYearGroupSchema) +@bp.post("/copy-project-supervisors-from-last-year-group//") @bp.output(MessageSchema, status_code=201) -def add_project_supervisor_to_year_group(id: int, year_group_id: int, data: dict) -> dict: - if not data: - abort(400, 'You have passed empty data!') - - limit_group = data.get('limit_group') - - project_supervisor = ProjectSupervisor.query.filter(ProjectSupervisor.id == id).first() - if project_supervisor is None: - abort(404, f"Not found project supervisor!") - +def copy_project_supervisors_from_last_year_group(year_group_id: int) -> dict: year_group = YearGroup.query.filter(YearGroup.id == year_group_id).first() if year_group is None: abort(404, "Not found year group!") - ygps = YearGroupProjectSupervisors.query.filter(YearGroupProjectSupervisors.project_supervisor_id == id). \ - filter(YearGroupProjectSupervisors.year_group_id == year_group_id).first() - if ygps is not None: - abort(400, "Project supervisor is assigned to this year group!") + last_year_group = YearGroup.query.filter(YearGroup.mode == year_group.mode). \ + filter(YearGroup.id != year_group_id).filter(YearGroup.created_at < year_group.created_at). \ + order_by(db.desc(YearGroup.created_at)).first() + if last_year_group is None: + abort(400, "The latest year group doesn't exist!") - ygps = YearGroupProjectSupervisors(year_group_id=year_group_id, project_supervisor_id=id, - limit_group=limit_group) - db.session.add(ygps) + project_supervisors = ProjectSupervisor.query.filter(ProjectSupervisor.year_group_id == last_year_group.id).all() + current_project_supervisors_email_in_new_year_group = [ps.email for ps in ProjectSupervisor.query.filter( + ProjectSupervisor.year_group_id == year_group_id).all()] + for ps in project_supervisors: + if ps.email not in current_project_supervisors_email_in_new_year_group: + new_ps = ProjectSupervisor(first_name=ps.first_name, last_name=ps.last_name, email=ps.email, + limit_group=ps.limit_group, year_group_id=year_group_id) + db.session.add(new_ps) db.session.commit() - return {"message": "Project Supervisor was added to year group!"} - -@bp.delete("//year-group//") -@bp.output(MessageSchema) -def delete_project_supervisor_to_year_group(id: int, year_group_id: int) -> dict: - project_supervisor = ProjectSupervisor.query.filter(ProjectSupervisor.id == id).first() - if project_supervisor is None: - abort(404, f"Not found project supervisor!") - - year_group = YearGroup.query.filter(YearGroup.id == year_group_id).first() - if year_group is None: - abort(404, "Not found year group!") - - db.session.delete(project_supervisor) - db.session.commit() - return {"message": "Project Supervisor was removed from this year group!"} - - -@bp.put("//year-group//") -@bp.input(ProjectSupervisorYearGroupSchema) -@bp.output(MessageSchema) -def update_limit_of_group_for_project_supervisor(id: int, year_group_id: int, data: dict) -> dict: - project_supervisor = ProjectSupervisor.query.filter(ProjectSupervisor.id == id).first() - if project_supervisor is None: - abort(404, f"Not found project supervisor!") - - year_group = YearGroup.query.filter(YearGroup.id == year_group_id).first() - if year_group is None: - abort(404, "Not found year group!") - - ygps = YearGroupProjectSupervisors.query.filter(YearGroupProjectSupervisors.project_supervisor_id == id). \ - filter(YearGroupProjectSupervisors.year_group_id == year_group_id) - ygps.update(data) - db.session.commit() - return {"message": "Limit of group was changed!"} + return {"message": "Project Supervisors was added!"} diff --git a/backend/app/coordinator/routes/students.py b/backend/app/coordinator/routes/students.py index 1932aae..042775f 100644 --- a/backend/app/coordinator/routes/students.py +++ b/backend/app/coordinator/routes/students.py @@ -6,14 +6,15 @@ from apiflask import APIBlueprint from sqlalchemy import or_ from flask_sqlalchemy import get_debug_queries -from ...students.models import Student, Group, YearGroup, YearGroupStudents +from ...students.models import Student, Group, YearGroup from ...project_supervisor.models import ProjectSupervisor from ..schemas import StudentSchema, StudentEditSchema, StudentsPaginationSchema, YearGroupInfoQuery, \ - StudentCreateSchema, MessageSchema, FileSchema, StudentQuerySchema, StudentListFileDownloaderSchema + StudentCreateSchema, FileSchema, StudentQuerySchema, StudentListFileDownloaderSchema from ...dependencies import db from ..utils import parse_csv, generate_csv from ..exceptions import InvalidNameOrTypeHeaderException from ...base.utils import paginate_models, is_allowed_extensions +from ...base.schemas import MessageSchema bp = APIBlueprint("students", __name__, url_prefix="/students") @@ -40,19 +41,19 @@ def list_students(year_group_id: int, query: dict) -> dict: } -@bp.get("//detail/") +@bp.get("//detail/") @bp.output(StudentSchema) -def detail_student(index: int) -> Student: - student = Student.query.filter_by(index=index).first() +def detail_student(student_id: int) -> Student: + student = Student.query.filter_by(id=student_id).first() if student is None: abort(404, "Not found student!") return student -@bp.delete("//") +@bp.delete("//") @bp.output(MessageSchema, status_code=202) -def delete_student(index: int) -> dict: - student = Student.query.filter_by(index=index).first() +def delete_student(student_id: int) -> dict: + student = Student.query.filter_by(id=student_id).first() if student is None: abort(404, "Not found student!") db.session.delete(student) @@ -60,14 +61,14 @@ def delete_student(index: int) -> dict: return {"message": "Student was deleted!"} -@bp.put("//") +@bp.put("//") @bp.input(StudentEditSchema) @bp.output(MessageSchema) -def edit_student(index: int, data: dict) -> dict: +def edit_student(student_id: int, data: dict) -> dict: if not data: abort(400, 'You have passed empty data!') - student_query = Student.query.filter_by(index=index) + student_query = Student.query.filter(Student.id==student_id) student = student_query.first() if student is None: @@ -87,24 +88,18 @@ def create_student(data: dict) -> dict: yg_id = data['year_group_id'] del data['year_group_id'] - student = Student.query.filter(Student.index == index).first() - # if student is not None: - # abort(400, "Student has already exists!") - if student is None: - dummy_email = f'student{randint(1, 300_000)}@gmail.com' - student = Student(**data, email=dummy_email) - db.session.add(student) + student = Student.query.filter(Student.index == index, Student.year_group_id == yg_id).first() + if student is not None: + abort(400, "Student has already assigned to this year group!") # add student to the chosen year group year_group = YearGroup.query.filter(YearGroup.id == yg_id).first() if year_group is None: abort(404, "Not found year group!") - if any((year_group.id == yg.id for yg in student.year_groups)): - abort(400, "You are assigned to this year group!") - - ygs = YearGroupStudents(student_index=student.index, year_group_id=year_group.id) - db.session.add(ygs) + dummy_email = f'student{randint(1, 300_000)}@gmail.com' + student = Student(**data, email=dummy_email, year_group_id=yg_id) + db.session.add(student) db.session.commit() return {"message": "Student was created!"} @@ -117,7 +112,7 @@ def create_student(data: dict) -> dict: def upload_students(query: dict, file: dict) -> dict: """Add only Students to chosen year group if students exist in db and assigned to correct year group, they will be omitted""" - year_group_id = query.get('id') + year_group_id = query.get('year_group_id') yg = YearGroup.query.filter(YearGroup.id == year_group_id).first() if yg is None: abort(404, "Not found year group!") @@ -125,7 +120,7 @@ def upload_students(query: dict, file: dict) -> dict: uploaded_file = file.get('file') if uploaded_file and is_allowed_extensions(uploaded_file.filename): try: - students = parse_csv(uploaded_file) + students = parse_csv(uploaded_file, year_group_id) while True: sliced_students = islice(students, 5) list_of_students = list(sliced_students) @@ -133,25 +128,13 @@ def upload_students(query: dict, file: dict) -> dict: if len(list_of_students) == 0: break - students_in_db = Student.query.filter(or_(Student.index == s.index for s in list_of_students)).all() - students_in_db_and_assigned_to_year_group = Student.query.join(YearGroupStudents, isouter=True). \ - filter(YearGroupStudents.year_group_id == year_group_id). \ - filter(or_(Student.index == s.index for s in list_of_students)).all() - + students_in_db = Student.query.filter(or_(Student.index == s.index for s in list_of_students)).\ + filter(Student.year_group_id==year_group_id).all() student_index_in_db = [s.index for s in students_in_db] - student_index_in_year_group = [s.index for s in students_in_db_and_assigned_to_year_group] - students_in_db_and_not_assigned_to_year_group = list( - filter(lambda s: s.index not in student_index_in_year_group, students_in_db)) - students_not_exists_in_db = list(filter(lambda s: s.index not in student_index_in_db, list_of_students)) db.session.add_all(students_not_exists_in_db) db.session.commit() - ygs = [YearGroupStudents(year_group_id=year_group_id, student_index=student.index) for student in - students_in_db_and_not_assigned_to_year_group + students_not_exists_in_db] - db.session.add_all(ygs) - db.session.commit() - except InvalidNameOrTypeHeaderException: abort(400, "Invalid format of csv file!") else: @@ -169,7 +152,7 @@ def download_students(query: dict) -> Response: join(ProjectSupervisor).all() if len(students_and_groups) == 0: - abort(404, "Not found students, which are assigned to group!") + abort(404, "Not found students!") csv_file = generate_csv(students_and_groups) response = Response(csv_file, mimetype='text/csv') diff --git a/backend/app/coordinator/routes/year_group.py b/backend/app/coordinator/routes/year_group.py index 3496a80..ff8a2ca 100644 --- a/backend/app/coordinator/routes/year_group.py +++ b/backend/app/coordinator/routes/year_group.py @@ -2,9 +2,10 @@ from flask import abort from apiflask import APIBlueprint from ...students.models import YearGroup -from ..schemas import YearGroupSchema, MessageSchema, YearGroupPaginationSchema, YearGroupQuerySchema +from ..schemas import YearGroupSchema, YearGroupPaginationSchema, YearGroupQuerySchema from ...dependencies import db from ...base.utils import paginate_models +from ...base.schemas import MessageSchema bp = APIBlueprint("year_group", __name__, url_prefix="/year-group") diff --git a/backend/app/coordinator/schemas/__init__.py b/backend/app/coordinator/schemas/__init__.py index 708faba..bc56a80 100644 --- a/backend/app/coordinator/schemas/__init__.py +++ b/backend/app/coordinator/schemas/__init__.py @@ -4,8 +4,8 @@ from .examination_schedule import ExaminationScheduleSchema, ExaminationSchedule ExaminationSchedulesPaginationSchema, ExaminationSchedulesQuerySchema, WorkloadSchema from .groups import GroupQuerySchema, GroupsPaginationSchema, GroupCreateSchema, GroupEditSchema, GroupIdSchema from .project_supervisor import ProjectSupervisorQuerySchema, ProjectSupervisorsPaginationSchema, \ - ProjectSupervisorCreateSchema, ProjectSupervisorEditSchema, ProjectSupervisorYearGroupSchema + ProjectSupervisorCreateSchema, ProjectSupervisorEditSchema from .students import ProjectSupervisorSchema, GroupSchema, StudentSchema, StudentsPaginationSchema, \ - StudentListFileDownloaderSchema, StudentCreateSchema, StudentEditSchema, MessageSchema, FileSchema, \ + StudentListFileDownloaderSchema, StudentCreateSchema, StudentEditSchema, MessageWithIdSchema, FileSchema, \ StudentQuerySchema, YearGroupInfoQuery, DetailGroupSchema from .year_group import YearGroupSchema, YearGroupPaginationSchema, YearGroupQuerySchema diff --git a/backend/app/coordinator/schemas/groups.py b/backend/app/coordinator/schemas/groups.py index 41c30ff..ca00d9d 100644 --- a/backend/app/coordinator/schemas/groups.py +++ b/backend/app/coordinator/schemas/groups.py @@ -1,7 +1,7 @@ from marshmallow import Schema, fields, validate from ..validators import validate_index -from .students import GroupSchema, StudentSchema +from .students import GroupSchema class GroupQuerySchema(Schema): @@ -18,13 +18,13 @@ class GroupsPaginationSchema(Schema): class GroupCreateSchema(Schema): name = fields.Str(validate=validate.Length(min=1, max=255), required=True) project_supervisor_id = fields.Integer(required=True) - students = fields.List(fields.Integer(validate=validate_index, required=True)) + students = fields.List(fields.Integer(required=True)) class GroupEditSchema(Schema): name = fields.Str(validate=validate.Length(min=1, max=255)) project_supervisor_id = fields.Integer() - students = fields.List(fields.Integer(validate=validate_index)) + students = fields.List(fields.Integer()) class GroupIdSchema(Schema): diff --git a/backend/app/coordinator/schemas/project_supervisor.py b/backend/app/coordinator/schemas/project_supervisor.py index 51982ec..857bc87 100644 --- a/backend/app/coordinator/schemas/project_supervisor.py +++ b/backend/app/coordinator/schemas/project_supervisor.py @@ -20,13 +20,11 @@ class ProjectSupervisorCreateSchema(Schema): first_name = fields.Str(validate=validate.Length(min=1, max=255), required=True) last_name = fields.Str(validate=validate.Length(min=1, max=255), required=True) email = fields.Str(validate=[validate.Length(min=1, max=255), validate.Email()], required=True) + limit_group = fields.Integer(required=True) class ProjectSupervisorEditSchema(Schema): - first_name = fields.Str(validate=validate.Length(min=1, max=255), required=True) - last_name = fields.Str(validate=validate.Length(min=1, max=255), required=True) - email = fields.Str(validate=[validate.Length(min=0, max=255), validate.Email()], required=True) - - -class ProjectSupervisorYearGroupSchema(Schema): - limit_group = fields.Integer(required=True) + first_name = fields.Str(validate=validate.Length(min=1, max=255)) + last_name = fields.Str(validate=validate.Length(min=1, max=255)) + email = fields.Str(validate=[validate.Length(min=0, max=255), validate.Email()]) + limit_group = fields.Integer() \ No newline at end of file diff --git a/backend/app/coordinator/schemas/students.py b/backend/app/coordinator/schemas/students.py index 34fc239..b8bfb84 100644 --- a/backend/app/coordinator/schemas/students.py +++ b/backend/app/coordinator/schemas/students.py @@ -20,7 +20,7 @@ class GroupSchema(ma.SQLAlchemyAutoSchema): class StudentSchema(ma.SQLAlchemyAutoSchema): - groups = fields.List(fields.Nested(GroupSchema)) + group = fields.Nested(GroupSchema) class Meta: model = Student @@ -38,7 +38,6 @@ class StudentListFileDownloaderSchema(ma.Schema): class StudentCreateSchema(ma.Schema): first_name = fields.Str(validate=validate.Length(min=1, max=255), required=True) last_name = fields.Str(validate=validate.Length(min=1, max=255), required=True) - pesel = fields.Str(validate=validate.Length(min=0, max=11), required=True) index = fields.Integer(validate=validate_index, required=True) year_group_id = fields.Integer() @@ -50,7 +49,7 @@ class StudentEditSchema(ma.Schema): index = fields.Integer(validate=validate_index) -class MessageSchema(ma.Schema): +class MessageWithIdSchema(ma.Schema): message = fields.Str(required=True) id = fields.Str(required=False) @@ -68,7 +67,7 @@ class StudentQuerySchema(ma.Schema): class YearGroupInfoQuery(Schema): - id = fields.Integer(required=True) + year_group_id = fields.Integer(required=True) class DetailGroupSchema(ma.SQLAlchemyAutoSchema): project_supervisor = fields.Nested(ProjectSupervisorSchema) diff --git a/backend/app/coordinator/utils.py b/backend/app/coordinator/utils.py index f140e88..6eac3e7 100644 --- a/backend/app/coordinator/utils.py +++ b/backend/app/coordinator/utils.py @@ -24,42 +24,31 @@ from ..examination_schedule.models import TermOfDefence def check_columns(df: pd.DataFrame) -> bool: headers = set(df.keys().values) - columns = ['NAZWISKO', 'IMIE', 'INDEKS', 'PESEL', 'EMAIL'] - - if len(headers - set(columns)) != 0: - return False - - flag = True - col_types = ['object', 'object', 'int', 'int64', 'object'] - - for name, col_type in zip(columns, col_types): - if not str(df.dtypes[name]).startswith(col_type): - flag = False - break - - return flag + column_names = ['NAZWISKO', 'IMIE', 'INDEKS', 'EMAIL'] + column_types = ['object', 'object', 'int', 'object'] + return all((column_name in headers for column_name in column_names)) and \ + all((str(df.dtypes[column_name]).startswith(column_type) for column_name, column_type in + zip(column_names, column_types))) -def parse_csv(file: Union[FileStorage, TextIO]) -> Generator[Student, Any, None]: +def parse_csv(file: Union[FileStorage, TextIO], year_group_id: int) -> Generator[Student, Any, None]: df = pd.read_csv(file) - # raise Exception(df.to_string()) + if not check_columns(df): raise InvalidNameOrTypeHeaderException - students = (Student(last_name=dict(item.items())['NAZWISKO'], first_name=dict(item.items())['IMIE'], index=dict(item.items())['INDEKS'], - pesel=str(int(dict(item.items())['PESEL'])) if not pd.isna( - dict(item.items())['PESEL']) else None, - email=dict(item.items())['EMAIL']) + email=dict(item.items())['EMAIL'], + year_group_id=year_group_id) for _, item in df.iterrows()) return students def generate_csv(students_and_groups: List[Tuple[Student, Group]]) -> str: - headers = ['PESEL', 'INDEKS', 'IMIE', 'NAZWISKO', 'EMAIL', 'CDYD_KOD', 'PRZ_KOD', 'TZAJ_KOD', 'GR_NR', 'PRG_KOD'] - data = [(student.pesel, student.index, student.first_name, student.last_name, student.email, + headers = ['INDEKS', 'IMIE', 'NAZWISKO', 'EMAIL', 'CDYD_KOD', 'PRZ_KOD', 'TZAJ_KOD', 'GR_NR', 'PRG_KOD'] + data = [(student.index, student.first_name, student.last_name, student.email, group.cdyd_kod, group.prz_kod, group.tzaj_kod, group.project_supervisor_id, None) for student, group in students_and_groups] dataframe = defaultdict(list) diff --git a/backend/app/errors.py b/backend/app/errors.py index b9c4b6b..7920737 100644 --- a/backend/app/errors.py +++ b/backend/app/errors.py @@ -1,12 +1,7 @@ import json -from typing import Tuple from apiflask import APIFlask -from werkzeug.exceptions import RequestEntityTooLarge, HTTPException - - -def request_entity_too_large(error: RequestEntityTooLarge) -> Tuple[dict, int]: - return {'error': 'File too large!'}, 413 +from werkzeug.exceptions import HTTPException def register_error_handlers(app: APIFlask): diff --git a/backend/app/factory.py b/backend/app/factory.py deleted file mode 100644 index e800204..0000000 --- a/backend/app/factory.py +++ /dev/null @@ -1,44 +0,0 @@ -from factory import alchemy, Sequence -from factory.faker import Faker -from factory.fuzzy import FuzzyInteger, FuzzyChoice - -from .dependencies import db -from .students.models import Student, Group -from .project_supervisor.models import ProjectSupervisor - - -class ProjectSupervisorFactory(alchemy.SQLAlchemyModelFactory): - class Meta: - model = ProjectSupervisor - sqlalchemy_session = db.session - - first_name = Faker('first_name') - last_name = Faker('last_name') - email = Faker('email') - limit_group = 4 # FuzzyInteger(3, 5) - count_groups = 4 - mode = 0 - - -class GroupFactory(alchemy.SQLAlchemyModelFactory): - class Meta: - model = Group - sqlalchemy_session = db.session - - name = Sequence(lambda n: f'Group-{n}') - points_for_first_term = FuzzyInteger(1, 5) - points_for_second_term = FuzzyInteger(1, 5) - # project_supervisor = RelatedFactory(ProjectSupervisorFactory, 'project_supervisor') - - -class StudentFactory(alchemy.SQLAlchemyModelFactory): - class Meta: - model = Student - sqlalchemy_session = db.session - - first_name = Faker('first_name') - last_name = Faker('last_name') - email = Faker('email') - index = Sequence(lambda n: 400_000 + n) - # group = RelatedFactory(GroupFactory) - mode = FuzzyChoice([True, False]) diff --git a/backend/app/project_supervisor/models.py b/backend/app/project_supervisor/models.py index 47d1ed7..b88812e 100644 --- a/backend/app/project_supervisor/models.py +++ b/backend/app/project_supervisor/models.py @@ -6,18 +6,12 @@ from ..base.utils import order_by_column_name from ..students.models import YearGroup -class YearGroupProjectSupervisors(Base): - __tablename__ = 'year_group_project_supervisors' - - project_supervisor_id = db.Column(db.Integer, db.ForeignKey('project_supervisors.id'), nullable=False) - year_group_id = db.Column(db.Integer, db.ForeignKey('year_groups.id'), nullable=False) - limit_group = db.Column(db.Integer, default=3, nullable=False) - - class ProjectSupervisor(Base, Person): __tablename__ = "project_supervisors" - year_groups = db.relationship('YearGroupProjectSupervisors', lazy=True) + limit_group = db.Column(db.Integer, default=3, nullable=False) + year_group_id = db.Column(db.Integer, db.ForeignKey('year_groups.id')) + year_group = db.relationship('YearGroup', backref='project_supervisors') @classmethod def search_by_fullname_and_mode_and_order_by_first_name_or_last_name(cls, year_group_id: int = None, @@ -27,8 +21,8 @@ class ProjectSupervisor(Base, Person): project_supervisors_query = cls.query if year_group_id is not None: - project_supervisors_query = project_supervisors_query.join(YearGroupProjectSupervisors). \ - filter(YearGroupProjectSupervisors.year_group_id == year_group_id) + project_supervisors_query = project_supervisors_query. \ + filter(ProjectSupervisor.year_group_id == year_group_id) if fullname is not None: project_supervisors_query = project_supervisors_query.filter( diff --git a/backend/app/project_supervisor/routes/enrollments.py b/backend/app/project_supervisor/routes/enrollments.py index 63ef9eb..fefcdc8 100644 --- a/backend/app/project_supervisor/routes/enrollments.py +++ b/backend/app/project_supervisor/routes/enrollments.py @@ -3,11 +3,12 @@ from datetime import datetime from apiflask import APIBlueprint from flask import abort from sqlalchemy import and_, or_ -from ..schemas import MessageSchema, TimeAvailabilityCreateSchema, TemporaryProjectSupervisorSchema, \ +from ..schemas import TimeAvailabilityCreateSchema, TemporaryProjectSupervisorSchema, \ ListOfFreeTimesSchema, ListOfTermOfDefenceSchema from ...dependencies import db from ..models import ProjectSupervisor from ...examination_schedule.models import ExaminationSchedule, TemporaryAvailability, TermOfDefence +from ...base.schemas import MessageSchema bp = APIBlueprint("enrollments", __name__, url_prefix="/") diff --git a/backend/app/project_supervisor/routes/project_grade_sheet.py b/backend/app/project_supervisor/routes/project_grade_sheet.py index b0a5a97..ea17b35 100644 --- a/backend/app/project_supervisor/routes/project_grade_sheet.py +++ b/backend/app/project_supervisor/routes/project_grade_sheet.py @@ -3,10 +3,11 @@ from flask import abort from ...dependencies import db from ..models import ProjectSupervisor -from ..schemas import ProjectSupervisorTermQuerySchema, MessageSchema, TemporaryProjectSupervisorSchema +from ..schemas import ProjectSupervisorTermQuerySchema, TemporaryProjectSupervisorSchema from ...students.schemas import ProjectGradeSheetDetailFirstTermSchema, ProjectGradeSheetDetailSecondTermSchema, \ ProjectGradeSheetEditFirstTermSchema, ProjectGradeSheetEditSecondTermSchema from ...students.models import Group, ProjectGradeSheet +from ...base.schemas import MessageSchema bp = APIBlueprint("project_grade_sheet_for_project_supervisor", __name__, url_prefix="/project-grade-sheet") diff --git a/backend/app/project_supervisor/schemas.py b/backend/app/project_supervisor/schemas.py index 5977a56..f002b07 100644 --- a/backend/app/project_supervisor/schemas.py +++ b/backend/app/project_supervisor/schemas.py @@ -1,10 +1,6 @@ from marshmallow import fields, validate, Schema -class MessageSchema(Schema): - message = fields.Str() - - class FreeTimeSchema(Schema): id = fields.Integer() start_date = fields.DateTime(required=True) @@ -29,6 +25,7 @@ class TimeAvailabilityCreateSchema(Schema): class TemporaryProjectSupervisorSchema(Schema): id = fields.Integer(required=True) + class ProjectSupervisorTermQuerySchema(Schema): id = fields.Integer(required=True) term = fields.Integer(required=True, validate=validate.OneOf([1, 2])) diff --git a/backend/app/students/models.py b/backend/app/students/models.py index 20a5243..82d3b05 100644 --- a/backend/app/students/models.py +++ b/backend/app/students/models.py @@ -8,20 +8,12 @@ from ..base.utils import order_by_column_name from ..examination_schedule.models import TermOfDefence -class YearGroupStudents(Base): - __tablename__ = 'year_group_students' - - year_group_id = db.Column(db.Integer, db.ForeignKey('year_groups.id', ondelete='CASCADE')) - student_index = db.Column(db.Integer, db.ForeignKey('students.index', ondelete='CASCADE')) - - class YearGroup(Base): __tablename__ = 'year_groups' name = db.Column(db.String(50), nullable=False) mode = db.Column(db.String(1), nullable=False) created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) - students = db.relationship("YearGroupStudents", lazy='joined') __table__args = ( db.UniqueConstraint('name', 'mode', name='uc_name_mode_year_group') @@ -30,7 +22,7 @@ class YearGroup(Base): students_groups = db.Table('students_groups', db.Column('group_id', db.ForeignKey('groups.id'), nullable=False), - db.Column('student_index', db.ForeignKey('students.index'), nullable=False)) + db.Column('student_id', db.ForeignKey('students.id'), nullable=False)) class Group(Base): @@ -41,12 +33,12 @@ class Group(Base): prz_kod = db.Column(db.String(60), default='06-DPRILI0') tzaj_kod = db.Column(db.String(60), default='LAB') project_supervisor_id = db.Column(db.Integer, db.ForeignKey('project_supervisors.id')) - project_supervisor = db.relationship('ProjectSupervisor', backref='groups', lazy='joined') + project_supervisor = db.relationship('ProjectSupervisor', backref='groups') year_group_id = db.Column(db.Integer, db.ForeignKey('year_groups.id')) year_group = db.relationship('YearGroup', backref='groups', lazy='joined') points_for_first_term = db.Column(db.Integer, default=0, nullable=False) points_for_second_term = db.Column(db.Integer, default=0, nullable=False) - students = db.relationship('Student', secondary=students_groups, back_populates='groups', lazy='joined') + students = db.relationship('Student', secondary=students_groups, back_populates='groups') @classmethod def search_by_name(cls, year_group_id: int, search_name: str = None) -> BaseQuery: @@ -62,7 +54,7 @@ class ProjectGradeSheet(Base): __tablename__ = 'project_grade_sheets' group_id = db.Column(db.Integer, db.ForeignKey('groups.id')) - group = db.relationship('Group', backref='project_grade_sheet', uselist=False, lazy='joined') + group = db.relationship('Group', backref='project_grade_sheet', uselist=False) presentation_required_content_1 = db.Column(db.Integer, default=0) presentation_required_content_2 = db.Column(db.Integer, default=0) @@ -125,20 +117,19 @@ class ProjectGradeSheet(Base): products_project_technology_2 = db.Column(db.Integer, default=0) -class Student(Person): +class Student(Base, Person): __tablename__ = "students" - pesel = db.Column(db.String(11), default='') - index = db.Column(db.Integer, primary_key=True) - groups = db.relationship('Group', secondary=students_groups, back_populates='students', lazy='joined') - year_groups = db.relationship("YearGroupStudents", lazy='joined') + index = db.Column(db.Integer, nullable=False) + groups = db.relationship('Group', secondary=students_groups, back_populates='students') + year_group_id = db.Column(db.Integer, db.ForeignKey('year_groups.id')) + year_group = db.relationship('YearGroup', backref='students') @classmethod def search_by_fullname_and_mode_and_order_by_first_name_or_last_name(cls, year_group_id: int, fullname: str = None, order_by_first_name: str = None, order_by_last_name: str = None) -> BaseQuery: - student_query = cls.query.join(YearGroupStudents, isouter=True). \ - filter(YearGroupStudents.year_group_id == year_group_id) + student_query = cls.query.filter(Student.year_group_id == year_group_id) if fullname is not None: student_query = student_query.filter((Student.first_name + ' ' + Student.last_name).like(f'{fullname}%')) diff --git a/backend/app/students/routes/__init__.py b/backend/app/students/routes/__init__.py index 06b34c0..0bf616e 100644 --- a/backend/app/students/routes/__init__.py +++ b/backend/app/students/routes/__init__.py @@ -3,11 +3,9 @@ from flask import Blueprint from .enrollments import bp as enrollments_bp from .project_grade_sheet import bp as project_grade_sheet_bp from .registrations import bp as registrations_bp -from .year_group import bp as year_group_bp bp = Blueprint("students", __name__, url_prefix="/students") bp.register_blueprint(enrollments_bp) bp.register_blueprint(project_grade_sheet_bp) bp.register_blueprint(registrations_bp) -bp.register_blueprint(year_group_bp) diff --git a/backend/app/students/routes/enrollments.py b/backend/app/students/routes/enrollments.py index 7fe5112..f8b3d91 100644 --- a/backend/app/students/routes/enrollments.py +++ b/backend/app/students/routes/enrollments.py @@ -3,12 +3,13 @@ import datetime from apiflask import APIBlueprint from flask import abort -from ..schemas import MessageSchema, TemporaryStudentSchema, ExaminationScheduleListSchema, \ +from ..schemas import TemporaryStudentSchema, ExaminationScheduleListSchema, \ TermOfDefenceStudentListSchema from ...dependencies import db from ..models import Student, Group, TermOfDefence from ...examination_schedule.models import ExaminationSchedule from ...project_supervisor.models import ProjectSupervisor +from ...base.schemas import MessageSchema bp = APIBlueprint("enrollments", __name__, url_prefix="/") diff --git a/backend/app/students/routes/registrations.py b/backend/app/students/routes/registrations.py index fb4ceff..955cbc2 100644 --- a/backend/app/students/routes/registrations.py +++ b/backend/app/students/routes/registrations.py @@ -1,6 +1,6 @@ from apiflask import APIBlueprint -from ...project_supervisor.models import ProjectSupervisor, YearGroupProjectSupervisors +from ...project_supervisor.models import ProjectSupervisor from ..models import Group from ...dependencies import db from ..schemas import ProjectSupervisorQuerySchema, ProjectSupervisorPaginationSchema @@ -16,12 +16,11 @@ def list_available_groups(year_group_id: int, query: dict) -> dict: page = query.get('page') per_page = query.get('per_page') - available_groups = (YearGroupProjectSupervisors.limit_group - db.func.count(Group.id)) + available_groups = (ProjectSupervisor.limit_group - db.func.count(Group.id)) ps_query = db.session. \ query(ProjectSupervisor, available_groups). \ join(Group, isouter=True). \ - join(YearGroupProjectSupervisors, isouter=True). \ - filter(YearGroupProjectSupervisors.year_group_id == year_group_id).\ + filter(ProjectSupervisor.year_group_id == year_group_id).\ group_by(ProjectSupervisor.id) data = paginate_models(page, ps_query, per_page) diff --git a/backend/app/students/routes/year_group.py b/backend/app/students/routes/year_group.py deleted file mode 100644 index 420fe77..0000000 --- a/backend/app/students/routes/year_group.py +++ /dev/null @@ -1,32 +0,0 @@ -from apiflask import APIBlueprint -from flask import abort - -from ...students.models import YearGroup, YearGroupStudents -from ...dependencies import db -from ...base.utils import paginate_models -from ..schemas import YearGroupQueryStudentSchema, YearGroupStudentPaginationSchema -from ..models import Student - -bp = APIBlueprint("year_group", __name__, url_prefix="/year-group") - - -@bp.get('/') -@bp.input(YearGroupQueryStudentSchema, location='query') -@bp.output(YearGroupStudentPaginationSchema) -def list_of_year_groups(query: dict) -> dict: - index = query.get('index') - st = Student.query.filter(Student.index == index).first() - if st is None: - abort(404, "Not found student!") - #################################### - page = query.get('page') - per_page = query.get('per_page') - - year_group_query = db.session.query(YearGroup).join(YearGroupStudents, isouter=True). \ - filter(YearGroupStudents.student_index == st.index).order_by(db.desc(YearGroup.created_at)) - data = paginate_models(page, year_group_query, per_page) - - return { - "year_groups": data['items'], - "max_pages": data['max_pages'] - } diff --git a/backend/app/students/schemas.py b/backend/app/students/schemas.py index e534ad3..dae793b 100644 --- a/backend/app/students/schemas.py +++ b/backend/app/students/schemas.py @@ -24,10 +24,6 @@ class TemporaryStudentSchema(Schema): student_index = fields.Integer(required=True) -class MessageSchema(Schema): - message = fields.Str() - - class ExaminationScheduleSchema(Schema): id = fields.Integer() title = fields.Str() @@ -51,39 +47,26 @@ class TermOfDefenceStudentItemSchema(Schema): end_date = fields.DateTime() members_of_committee = fields.List(fields.Nested(ProjectSupervisorCommitteeSchema)) + class StudentDataItemSchema(Schema): index = fields.Integer() first_name = fields.Str() - last_name = fields.Str() + last_name = fields.Str() + class GroupDataItemSchema(Schema): name = fields.Str() - students = fields.List(fields.Nested(StudentDataItemSchema)) + students = fields.List(fields.Nested(StudentDataItemSchema)) + class AssignedGroupToTermOfDefenceItemSchema(TermOfDefenceStudentItemSchema): group = fields.Nested(GroupDataItemSchema) + class TermOfDefenceStudentListSchema(Schema): term_of_defences = fields.List(fields.Nested(AssignedGroupToTermOfDefenceItemSchema)) -class YearGroupStudentSchema(Schema): - id = fields.Integer() - name = fields.Str() - mode = fields.Str() - - -class YearGroupStudentPaginationSchema(Schema): - year_groups = fields.List(fields.Nested(YearGroupStudentSchema)) - max_pages = fields.Integer() - - -class YearGroupQueryStudentSchema(Schema): - index = fields.Integer(required=True) # it will be removed - page = fields.Integer() - per_page = fields.Integer() - - class StudentIndexQueryTempSchema(Schema): index = fields.Integer(required=True) # it will be removed term = fields.Integer(required=True, validate=validate.OneOf([1, 2])) diff --git a/backend/migrations/versions/b3003ddd0564_.py b/backend/migrations/versions/3fd120fc5e12_.py similarity index 89% rename from backend/migrations/versions/b3003ddd0564_.py rename to backend/migrations/versions/3fd120fc5e12_.py index bcb7d0f..17e7840 100644 --- a/backend/migrations/versions/b3003ddd0564_.py +++ b/backend/migrations/versions/3fd120fc5e12_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: b3003ddd0564 +Revision ID: 3fd120fc5e12 Revises: -Create Date: 2023-01-03 18:37:53.103562 +Create Date: 2023-01-14 00:03:06.327441 """ from alembic import op @@ -10,7 +10,7 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. -revision = 'b3003ddd0564' +revision = '3fd120fc5e12' down_revision = None branch_labels = None depends_on = None @@ -18,27 +18,6 @@ depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.create_table('project_supervisors', - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('first_name', sa.String(length=255), nullable=False), - sa.Column('last_name', sa.String(length=255), nullable=False), - sa.Column('email', sa.String(length=120), nullable=True), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('email') - ) - op.create_index(op.f('ix_project_supervisors_first_name'), 'project_supervisors', ['first_name'], unique=False) - op.create_index(op.f('ix_project_supervisors_last_name'), 'project_supervisors', ['last_name'], unique=False) - op.create_table('students', - sa.Column('first_name', sa.String(length=255), nullable=False), - sa.Column('last_name', sa.String(length=255), nullable=False), - sa.Column('email', sa.String(length=120), nullable=True), - sa.Column('pesel', sa.String(length=11), nullable=True), - sa.Column('index', sa.Integer(), nullable=False), - sa.PrimaryKeyConstraint('index'), - sa.UniqueConstraint('email') - ) - op.create_index(op.f('ix_students_first_name'), 'students', ['first_name'], unique=False) - op.create_index(op.f('ix_students_last_name'), 'students', ['last_name'], unique=False) op.create_table('year_groups', sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), sa.Column('name', sa.String(length=50), nullable=False), @@ -59,6 +38,32 @@ def upgrade(): sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('title') ) + op.create_table('project_supervisors', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('first_name', sa.String(length=255), nullable=False), + sa.Column('last_name', sa.String(length=255), nullable=False), + sa.Column('email', sa.String(length=120), nullable=False), + sa.Column('limit_group', sa.Integer(), nullable=False), + sa.Column('year_group_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['year_group_id'], ['year_groups.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_project_supervisors_email'), 'project_supervisors', ['email'], unique=False) + op.create_index(op.f('ix_project_supervisors_first_name'), 'project_supervisors', ['first_name'], unique=False) + op.create_index(op.f('ix_project_supervisors_last_name'), 'project_supervisors', ['last_name'], unique=False) + op.create_table('students', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('first_name', sa.String(length=255), nullable=False), + sa.Column('last_name', sa.String(length=255), nullable=False), + sa.Column('email', sa.String(length=120), nullable=False), + sa.Column('index', sa.Integer(), nullable=False), + sa.Column('year_group_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['year_group_id'], ['year_groups.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_students_email'), 'students', ['email'], unique=False) + op.create_index(op.f('ix_students_first_name'), 'students', ['first_name'], unique=False) + op.create_index(op.f('ix_students_last_name'), 'students', ['last_name'], unique=False) op.create_table('groups', sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), sa.Column('name', sa.String(length=60), nullable=False), @@ -73,21 +78,14 @@ def upgrade(): sa.ForeignKeyConstraint(['year_group_id'], ['year_groups.id'], ), sa.PrimaryKeyConstraint('id') ) - op.create_table('year_group_project_supervisors', + op.create_table('temporary_availabilities', sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('start_date', sa.DateTime(), nullable=False), + sa.Column('end_date', sa.DateTime(), nullable=False), + sa.Column('examination_schedule_id', sa.Integer(), nullable=False), sa.Column('project_supervisor_id', sa.Integer(), nullable=False), - sa.Column('year_group_id', sa.Integer(), nullable=False), - sa.Column('limit_group', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['examination_schedule_id'], ['examination_schedules.id'], ), sa.ForeignKeyConstraint(['project_supervisor_id'], ['project_supervisors.id'], ), - sa.ForeignKeyConstraint(['year_group_id'], ['year_groups.id'], ), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('year_group_students', - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('year_group_id', sa.Integer(), nullable=True), - sa.Column('student_index', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['student_index'], ['students.index'], ondelete='CASCADE'), - sa.ForeignKeyConstraint(['year_group_id'], ['year_groups.id'], ondelete='CASCADE'), sa.PrimaryKeyConstraint('id') ) op.create_table('project_grade_sheets', @@ -154,19 +152,9 @@ def upgrade(): ) op.create_table('students_groups', sa.Column('group_id', sa.Integer(), nullable=False), - sa.Column('student_index', sa.Integer(), nullable=False), + sa.Column('student_id', sa.Integer(), nullable=False), sa.ForeignKeyConstraint(['group_id'], ['groups.id'], ), - sa.ForeignKeyConstraint(['student_index'], ['students.index'], ) - ) - op.create_table('temporary_availabilities', - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('start_date', sa.DateTime(), nullable=False), - sa.Column('end_date', sa.DateTime(), nullable=False), - sa.Column('examination_schedule_id', sa.Integer(), nullable=False), - sa.Column('project_supervisor_id', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['examination_schedule_id'], ['examination_schedules.id'], ), - sa.ForeignKeyConstraint(['project_supervisor_id'], ['project_supervisors.id'], ), - sa.PrimaryKeyConstraint('id') + sa.ForeignKeyConstraint(['student_id'], ['students.id'], ) ) op.create_table('term_of_defences', sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), @@ -191,18 +179,18 @@ def downgrade(): # ### commands auto generated by Alembic - please adjust! ### op.drop_table('committees') op.drop_table('term_of_defences') - op.drop_table('temporary_availabilities') op.drop_table('students_groups') op.drop_table('project_grade_sheets') - op.drop_table('year_group_students') - op.drop_table('year_group_project_supervisors') + op.drop_table('temporary_availabilities') op.drop_table('groups') - op.drop_table('examination_schedules') - op.drop_table('year_groups') op.drop_index(op.f('ix_students_last_name'), table_name='students') op.drop_index(op.f('ix_students_first_name'), table_name='students') + op.drop_index(op.f('ix_students_email'), table_name='students') op.drop_table('students') op.drop_index(op.f('ix_project_supervisors_last_name'), table_name='project_supervisors') op.drop_index(op.f('ix_project_supervisors_first_name'), table_name='project_supervisors') + op.drop_index(op.f('ix_project_supervisors_email'), table_name='project_supervisors') op.drop_table('project_supervisors') + op.drop_table('examination_schedules') + op.drop_table('year_groups') # ### end Alembic commands ###