From eea98dd22588c3f6d400033630cc6178bf093a27 Mon Sep 17 00:00:00 2001 From: dominik24c Date: Sat, 12 Nov 2022 16:18:07 +0100 Subject: [PATCH] add year group and change logic of endpoints v2 --- backend/Dockerfile | 1 - backend/app/base/mode.py | 7 + backend/app/commands/init_db.py | 50 +++---- backend/app/coordinator/routes/__init__.py | 10 +- backend/app/coordinator/routes/enrollments.py | 2 +- .../routes/examination_schedule.py | 28 ++-- backend/app/coordinator/routes/groups.py | 39 ++--- .../coordinator/routes/project_supervisor.py | 114 ++++++++++++-- backend/app/coordinator/routes/students.py | 33 ++-- backend/app/coordinator/routes/year_group.py | 72 +++++++++ backend/app/coordinator/schemas/__init__.py | 3 +- .../schemas/examination_schedule.py | 20 +-- .../coordinator/schemas/project_supervisor.py | 22 ++- backend/app/coordinator/schemas/students.py | 4 +- backend/app/coordinator/schemas/year_group.py | 30 ++++ backend/app/coordinator/utils.py | 4 +- backend/app/examination_schedule/models.py | 47 +++--- .../examination_schedule/routes/__init__.py | 6 - .../routes/enrollments.py | 30 +--- .../routes/examination_schedule.py | 18 +-- backend/app/examination_schedule/schemas.py | 14 +- backend/app/examination_schedule/utils.py | 2 +- backend/app/project_supervisor/models.py | 21 ++- backend/app/project_supervisor/query.py | 10 +- .../project_supervisor/routes/enrollments.py | 118 +++++++++++---- backend/app/project_supervisor/schemas.py | 16 +- backend/app/students/models.py | 39 +++-- backend/app/students/routes/enrollments.py | 90 +++++++---- backend/app/students/routes/registrations.py | 21 ++- backend/app/students/schemas.py | 13 +- backend/migrations/versions/5c3c4c4e1a72_.py | 28 ++++ backend/migrations/versions/a96f91e5b556_.py | 141 ++++++++++++++++++ 32 files changed, 762 insertions(+), 291 deletions(-) create mode 100644 backend/app/base/mode.py create mode 100644 backend/app/coordinator/routes/year_group.py create mode 100644 backend/app/coordinator/schemas/year_group.py create mode 100644 backend/migrations/versions/5c3c4c4e1a72_.py create mode 100644 backend/migrations/versions/a96f91e5b556_.py diff --git a/backend/Dockerfile b/backend/Dockerfile index 69d11e4..78bc831 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -13,4 +13,3 @@ COPY requirements.txt . RUN pip install -r requirements.txt COPY . . - diff --git a/backend/app/base/mode.py b/backend/app/base/mode.py new file mode 100644 index 0000000..b1603b2 --- /dev/null +++ b/backend/app/base/mode.py @@ -0,0 +1,7 @@ +from enum import Enum + + +class ModeGroups(str, Enum): + STATIONARY = 's' + NON_STATIONARY = 'n' + ENGLISH_SPEAKING_STATIONARY = 'e' diff --git a/backend/app/commands/init_db.py b/backend/app/commands/init_db.py index cb28c6f..f6f4d0f 100644 --- a/backend/app/commands/init_db.py +++ b/backend/app/commands/init_db.py @@ -2,7 +2,7 @@ from flask.cli import with_appcontext from click import command from ..dependencies import db -from ..factory import ProjectSupervisorFactory, GroupFactory, StudentFactory +# from ..factory import ProjectSupervisorFactory, GroupFactory, StudentFactory @command('init_db') @@ -11,27 +11,27 @@ 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 + # + # 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/routes/__init__.py b/backend/app/coordinator/routes/__init__.py index 9e2e32f..d796e0d 100644 --- a/backend/app/coordinator/routes/__init__.py +++ b/backend/app/coordinator/routes/__init__.py @@ -1,17 +1,19 @@ from flask import Blueprint from .examination_schedule import bp as examination_schedule_bp -from .enrollments import bp as enrollments_bp +# from .enrollments import bp as enrollments_bp from .groups import bp as groups_bp from .project_supervisor import bp as project_supervisor_bp from .students import bp as students_bp -from .workloads import bp as workloads_bp +from .year_group import bp as year_group_bp +# from .workloads import bp as workloads_bp bp = Blueprint("coordinator", __name__, url_prefix="/coordinator") bp.register_blueprint(students_bp) bp.register_blueprint(project_supervisor_bp) bp.register_blueprint(groups_bp) +bp.register_blueprint(year_group_bp) bp.register_blueprint(examination_schedule_bp) -bp.register_blueprint(enrollments_bp) -bp.register_blueprint(workloads_bp) +# bp.register_blueprint(enrollments_bp) +# bp.register_blueprint(workloads_bp) diff --git a/backend/app/coordinator/routes/enrollments.py b/backend/app/coordinator/routes/enrollments.py index b8a1878..10174eb 100644 --- a/backend/app/coordinator/routes/enrollments.py +++ b/backend/app/coordinator/routes/enrollments.py @@ -4,7 +4,7 @@ from apiflask import APIBlueprint from flask import abort, current_app from ..schemas import MessageSchema, EnrollmentCreateSchema -from ...examination_schedule.models import Enrollment, Committee, ExaminationSchedule +from ...examination_schedule.models import ExaminationSchedule from ...dependencies import db bp = APIBlueprint("enrollments", __name__, url_prefix="/enrollments") diff --git a/backend/app/coordinator/routes/examination_schedule.py b/backend/app/coordinator/routes/examination_schedule.py index a0c0df9..8684444 100644 --- a/backend/app/coordinator/routes/examination_schedule.py +++ b/backend/app/coordinator/routes/examination_schedule.py @@ -5,8 +5,8 @@ from flask import abort, Response, make_response from ...base.utils import paginate_models from ...dependencies import db -from ...examination_schedule.models import ExaminationSchedule, Enrollment -from ...students.models import Group +from ...examination_schedule.models import ExaminationSchedule +from ...students.models import Group, YearGroup from ...project_supervisor.models import ProjectSupervisor from ..schemas import ExaminationScheduleSchema, ExaminationScheduleUpdateSchema, MessageSchema, \ ExaminationSchedulesQuerySchema, ExaminationSchedulesPaginationSchema @@ -15,21 +15,29 @@ from ..utils import generate_examination_schedule_pdf_file bp = APIBlueprint("examination_schedule", __name__, url_prefix="/examination_schedule") -@bp.get('/') +@bp.get('//') @bp.input(ExaminationSchedulesQuerySchema, location='query') @bp.output(ExaminationSchedulesPaginationSchema) -def list_examination_schedule(query: dict) -> dict: +def list_examination_schedule(year_group_id: int, query: dict) -> dict: page = query.get('page') per_page = query.get('per_page') - data = paginate_models(page, ExaminationSchedule.query, per_page) + es_query = ExaminationSchedule.query.filter(ExaminationSchedule.year_group_id == year_group_id) + data = paginate_models(page, es_query, per_page) return {'examination_schedules': data['items'], 'max_pages': data['max_pages']} -@bp.post('/') +@bp.post('//') @bp.input(ExaminationScheduleSchema) @bp.output(MessageSchema) -def create_examination_schedule(data: dict) -> dict: - examination_schedule = ExaminationSchedule(**data) +def create_examination_schedule(year_group_id: int, data: dict) -> dict: + yg = YearGroup.query.filter(YearGroup.id == year_group_id).first() + if yg is None: + abort(404, "Year group doesn't exist!") + + if data['start_date'] > data['end_date']: + abort(400, "Invalid data! End date must be greater than start date!") + + examination_schedule = ExaminationSchedule(**data, year_group_id=year_group_id) db.session.add(examination_schedule) db.session.commit() return {"message": "Examination schedule was created!"} @@ -70,7 +78,7 @@ def set_date_of_examination_schedule(id: int, data: dict) -> dict: if examination_schedule is None: abort(404, "Examination schedule doesn't exist!") - if data['start_date'] > data['end_date']: + if data['start_date_for_enrollment_students'] > data['end_date_for_enrollment_students']: abort(400, "Invalid data! End date must be greater than start date!") examination_schedule_query.update(data) @@ -91,7 +99,7 @@ def download_examination_schedule(examination_schedule_id: int) -> Response: nested_enrollments = [] for d in distinct_dates: date_tmp = datetime.datetime.strptime(d[0], "%Y-%m-%d").date() - enrollment = db.session.query(Enrollment).join(ExaminationSchedule, isouter=True).\ + enrollment = db.session.query(Enrollment).join(ExaminationSchedule, isouter=True). \ join(Group, isouter=True).join(ProjectSupervisor, isouter=True). \ filter(ExaminationSchedule.id == examination_schedule_id).filter( db.func.Date(Enrollment.start_date) == date_tmp).all() diff --git a/backend/app/coordinator/routes/groups.py b/backend/app/coordinator/routes/groups.py index 94e3424..a8de18a 100644 --- a/backend/app/coordinator/routes/groups.py +++ b/backend/app/coordinator/routes/groups.py @@ -2,8 +2,8 @@ from flask import abort, current_app from apiflask import APIBlueprint from flask_sqlalchemy import get_debug_queries -from ...students.models import Group, Student -from ...project_supervisor.models import ProjectSupervisor +from ...students.models import Group, Student, YearGroup +from ...project_supervisor.models import ProjectSupervisor, YearGroupProjectSupervisors from ..schemas import GroupSchema, GroupEditSchema, GroupsPaginationSchema, \ GroupCreateSchema, MessageSchema, GroupQuerySchema from ...dependencies import db @@ -12,15 +12,15 @@ from ...base.utils import paginate_models bp = APIBlueprint("groups", __name__, url_prefix="/groups") -@bp.route("/", methods=["GET"]) +@bp.get("//") @bp.input(GroupQuerySchema, location='query') @bp.output(GroupsPaginationSchema) -def list_groups(query: dict) -> dict: +def list_groups(year_group_id: int, query: dict) -> dict: search_name = query.get('name') page = query.get('page') per_page = query.get('per_page') - groups_query = Group.search_by_name(search_name) + groups_query = Group.search_by_name(year_group_id, search_name) data = paginate_models(page, groups_query, per_page) @@ -30,14 +30,18 @@ def list_groups(query: dict) -> dict: } -@bp.route("/", methods=["POST"]) +@bp.post("//") @bp.input(GroupCreateSchema) @bp.output(MessageSchema) -def create_group(data: dict) -> dict: +def create_group(year_group_id: int, data: dict) -> dict: name = data['name'] students_indexes = data['students'] project_supervisor_id = data['project_supervisor_id'] + yg = YearGroup.query.filter(YearGroup.id == year_group_id).first() + if yg is None: + abort(404, "YearGroup doesn't exist!") + project_supervisor = ProjectSupervisor.query.filter_by(id=project_supervisor_id).first() limit_student_per_group = current_app.config.get('LIMIT_STUDENTS_PER_GROUP') @@ -47,13 +51,16 @@ def create_group(data: dict) -> dict: 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(ProjectSupervisor.limit_group - db.func.count(ProjectSupervisor.id)).join(Group).filter( - ProjectSupervisor.id == project_supervisor_id).group_by(ProjectSupervisor.id).scalar() + limit = db.session.query(db.func.count(ProjectSupervisor.id)). \ + join(Group).filter(ProjectSupervisor.id == project_supervisor_id).group_by(ProjectSupervisor.id).scalar() - if limit is not None and limit <= 0: + 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) + group = Group(name=name, project_supervisor_id=project_supervisor_id, year_group_id=year_group_id) students_without_groups = db.session.query(Student).join(Group, isouter=True) \ .filter(Group.id.is_(None)).filter(Student.index.in_(students_indexes)).count() @@ -65,7 +72,6 @@ def create_group(data: dict) -> dict: db.session.commit() students = db.session.query(Student).filter(Student.index.in_(students_indexes)).all() - project_supervisor.count_groups += 1 for student in students: student.group_id = group.id @@ -74,7 +80,7 @@ def create_group(data: dict) -> dict: return {"message": "Group was created!"} -@bp.route("//", methods=["GET"]) +@bp.get("//") @bp.output(GroupSchema) def detail_group(id: int) -> Group: group = Group.query.filter_by(id=id).first() @@ -83,16 +89,13 @@ def detail_group(id: int) -> Group: return group -@bp.route("//", methods=["DELETE"]) +@bp.delete("//") @bp.output(MessageSchema, status_code=202) def delete_group(id: int) -> dict: group = Group.query.filter_by(id=id).first() if group is None: abort(400, f"Group with id {id} doesn't exist!") - project_supervisor = ProjectSupervisor.query.filter_by(id=group.project_supervisor_id).first() - project_supervisor.count_groups -= 1 - students = db.session.query(Student).filter_by(group_id=id).all() for student in students: student.group_id = None @@ -102,7 +105,7 @@ def delete_group(id: int) -> dict: return {"message": "Group was deleted!"} -@bp.route("/", methods=["PUT"]) +@bp.put("//") @bp.input(GroupEditSchema) @bp.output(MessageSchema) def edit_group(id: int, data: dict) -> dict: diff --git a/backend/app/coordinator/routes/project_supervisor.py b/backend/app/coordinator/routes/project_supervisor.py index 4605d0c..21283a2 100644 --- a/backend/app/coordinator/routes/project_supervisor.py +++ b/backend/app/coordinator/routes/project_supervisor.py @@ -2,29 +2,28 @@ from flask import abort from apiflask import APIBlueprint from flask_sqlalchemy import get_debug_queries -from ...project_supervisor.models import ProjectSupervisor -from ...students.models import Group +from ...project_supervisor.models import ProjectSupervisor, YearGroupProjectSupervisors +from ...students.models import Group, YearGroup from ..schemas import ProjectSupervisorSchema, ProjectSupervisorEditSchema, ProjectSupervisorsPaginationSchema, \ - ProjectSupervisorCreateSchema, MessageSchema, ProjectSupervisorQuerySchema + ProjectSupervisorCreateSchema, MessageSchema, ProjectSupervisorQuerySchema, ProjectSupervisorYearGroupSchema from ...dependencies import db from ...base.utils import paginate_models bp = APIBlueprint("project_supervisor", __name__, url_prefix="/project_supervisor") -@bp.route("/", methods=["GET"]) +@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') - mode = query.get('mode') 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( - fullname, mode, order_by_first_name, order_by_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]) @@ -34,7 +33,28 @@ def list_project_supervisors(query: dict) -> dict: } -@bp.route("/", methods=["POST"]) +@bp.get("//") +@bp.input(ProjectSupervisorQuerySchema, location='query') +@bp.output(ProjectSupervisorsPaginationSchema) +def list_project_supervisors_by_year_group(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') + 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( + year_group_id, 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.post("/") @bp.input(ProjectSupervisorCreateSchema) @bp.output(MessageSchema) def create_project_supervisor(data: dict) -> dict: @@ -43,7 +63,7 @@ def create_project_supervisor(data: dict) -> dict: project_supervisor = ProjectSupervisor.query.filter_by(first_name=first_name).filter_by(last_name=last_name).first() if project_supervisor is not None: abort(400, "Project Supervisor has already exists!") - + project_supervisor = ProjectSupervisor(**data) db.session.add(project_supervisor) @@ -52,7 +72,7 @@ def create_project_supervisor(data: dict) -> dict: return {"message": "Project Supervisor was created!"} -@bp.route("//", methods=["GET"]) +@bp.get("//") @bp.output(ProjectSupervisorSchema) def detail_project_supervisor(id: int) -> ProjectSupervisor: project_supervisor = ProjectSupervisor.query.filter_by(id=id).first() @@ -61,37 +81,99 @@ def detail_project_supervisor(id: int) -> ProjectSupervisor: return project_supervisor -@bp.route("//", methods=["DELETE"]) +@bp.delete("//") @bp.output(MessageSchema, status_code=202) def delete_project_supervisor(id: int) -> dict: project_supervisor = ProjectSupervisor.query.filter_by(id=id).first() if project_supervisor is None: abort(400, f"Project Supervisor with id {id} doesn't exist!") - count_groups = db.session.query(db.func.count(ProjectSupervisor.id)).join(Group).\ + count_groups = db.session.query(db.func.count(ProjectSupervisor.id)).join(Group). \ filter(ProjectSupervisor.id == id).group_by(ProjectSupervisor.id) - + if count_groups is not None: - abort(400, f"Project Supervisor with id {id} has gropus!") + abort(400, f"Project Supervisor with id {id} has groups!") db.session.delete(project_supervisor) db.session.commit() return {"message": "Project Supervisor was deleted!"} -@bp.route("/", methods=["PUT"]) +@bp.put("//") @bp.input(ProjectSupervisorEditSchema) @bp.output(MessageSchema) def edit_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 = project_supervisor_query.first() if project_supervisor is None: abort(400, f"Project Supervisor with id {id} doesn't exist!") - + project_supervisor_query.update(data) db.session.commit() return {"message": "Project Supervisor was updated!"} + + +@bp.post("//year-group/") +@bp.input(ProjectSupervisorYearGroupSchema) +@bp.output(MessageSchema) +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(400, f"Project Supervisor with id {id} doesn't exist!") + + year_group = YearGroup.query.filter(YearGroup.id == year_group_id).first() + if year_group is None: + abort(400, "Year group doesn't exist!") + + 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!") + + ygps = YearGroupProjectSupervisors(year_group_id=year_group_id, project_supervisor_id=id, + limit_group=limit_group) + db.session.add(ygps) + db.session.commit() + return {"message": "Project Supervisor was added to year group!"} + + +@bp.delete("//year-group/") +@bp.input(ProjectSupervisorYearGroupSchema) +@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). \ + filter(YearGroup.id == year_group_id).first() + if project_supervisor is None: + abort(400, "Project Supervisor doesn't exist!") + + ygps = YearGroupProjectSupervisors.query.filter(YearGroupProjectSupervisors.project_supervisor_id == id). \ + filter(YearGroupProjectSupervisors.year_group_id == year_group_id).first() + db.session.delete(project_supervisor) + db.session.delete(ygps) + 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). \ + filter(YearGroup.id == year_group_id).first() + if project_supervisor is None: + abort(400, "Project Supervisor doesn't exist!") + + 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!"} diff --git a/backend/app/coordinator/routes/students.py b/backend/app/coordinator/routes/students.py index 0ea47c5..e7e54d7 100644 --- a/backend/app/coordinator/routes/students.py +++ b/backend/app/coordinator/routes/students.py @@ -1,13 +1,12 @@ from random import randint from itertools import islice -from typing import List from flask import Response, abort from apiflask import APIBlueprint from sqlalchemy.exc import IntegrityError from flask_sqlalchemy import get_debug_queries -from ...students.models import Student, Group +from ...students.models import Student, Group, YearGroup, YearGroupStudents from ...project_supervisor.models import ProjectSupervisor from ..schemas import StudentSchema, StudentEditSchema, StudentsPaginationSchema, \ StudentCreateSchema, MessageSchema, FileSchema, StudentQuerySchema, StudentListFileDownloaderSchema @@ -19,19 +18,19 @@ from ...base.utils import paginate_models, is_allowed_extensions bp = APIBlueprint("students", __name__, url_prefix="/students") -@bp.route("/", methods=["GET"]) +@bp.get("//") @bp.input(StudentQuerySchema, location='query') @bp.output(StudentsPaginationSchema) -def list_students(query: dict) -> dict: +def list_students(year_group_id: int, query: dict) -> dict: + # add filter by year group fullname = query.get('fullname') order_by_first_name = query.get('order_by_first_name') order_by_last_name = query.get('order_by_last_name') - mode = query.get('mode') page = query.get('page') per_page = query.get('per_page') student_query = Student.search_by_fullname_and_mode_and_order_by_first_name_or_last_name( - fullname, mode, order_by_first_name, order_by_last_name) + year_group_id, fullname, order_by_first_name, order_by_last_name) data = paginate_models(page, student_query, per_page) # print(get_debug_queries()[0]) @@ -41,7 +40,7 @@ def list_students(query: dict) -> dict: } -@bp.route("//", methods=["GET"]) +@bp.get("//") @bp.output(StudentSchema) def detail_student(index: int) -> Student: student = Student.query.filter_by(index=index).first() @@ -50,7 +49,7 @@ def detail_student(index: int) -> Student: return student -@bp.route("//", methods=["DELETE"]) +@bp.delete("//") @bp.output(MessageSchema, status_code=202) def delete_student(index: int) -> dict: student = Student.query.filter_by(index=index).first() @@ -61,7 +60,7 @@ def delete_student(index: int) -> dict: return {"message": "Student was deleted!"} -@bp.route("//", methods=["PUT"]) +@bp.put("//") @bp.input(StudentEditSchema) @bp.output(MessageSchema) def edit_student(index: int, data: dict) -> dict: @@ -80,11 +79,13 @@ def edit_student(index: int, data: dict) -> dict: return {"message": "Student was updated!"} -@bp.route("/", methods=["POST"]) +@bp.post("/") @bp.input(StudentCreateSchema) @bp.output(MessageSchema) def create_student(data: dict) -> dict: index = data['index'] + yg_id = data['year_group_id'] + del data['year_group_id'] student = Student.query.filter_by(index=index).first() if student is not None: abort(400, "Student has already exists!") @@ -92,12 +93,20 @@ def create_student(data: dict) -> dict: dummy_email = f'student{randint(1, 300_000)}@gmail.com' student = Student(**data, email=dummy_email) db.session.add(student) + + # add student to the chosen year group + year_group = YearGroup.query.filter(YearGroup.id == yg_id).first() + if year_group is None: + abort(400, "Year group doesn't exist!") + ygs = YearGroupStudents(student_index=student.index, year_group_id=year_group.id) + db.session.add(ygs) + db.session.commit() return {"message": "Student was created!"} -@bp.route("/upload/", methods=["POST"]) +@bp.post("/upload/") @bp.input(FileSchema, location='form_and_files') @bp.output(MessageSchema) def upload_students(file: dict) -> dict: @@ -125,7 +134,7 @@ def upload_students(file: dict) -> dict: return {"message": "Students was created by uploading csv file!"} -@bp.route("/download/", methods=["POST"]) +@bp.post("/download/") @bp.input(StudentListFileDownloaderSchema, location='query') def download_students(query: dict) -> Response: mode = query.get('mode') diff --git a/backend/app/coordinator/routes/year_group.py b/backend/app/coordinator/routes/year_group.py new file mode 100644 index 0000000..a64320d --- /dev/null +++ b/backend/app/coordinator/routes/year_group.py @@ -0,0 +1,72 @@ +from flask import abort +from apiflask import APIBlueprint +from flask_sqlalchemy import get_debug_queries + +from ...students.models import YearGroup +from ..schemas import YearGroupSchema, MessageSchema, YearGroupPaginationSchema, YearGroupQuerySchema +from ...dependencies import db +from ...base.utils import paginate_models + +bp = APIBlueprint("year_group", __name__, url_prefix="/year-group") + + +@bp.post('/') +@bp.input(YearGroupSchema) +@bp.output(MessageSchema, status_code=200) +def create_year_group(data: dict) -> dict: + name = data['name'] + year_group = YearGroup.query.filter(YearGroup.name == name).first() + if year_group is not None: + abort(400, "Year group has already exists!") + + yg = YearGroup(**data) + db.session.add(yg) + db.session.commit() + + return {"message": "Year group was created!"} + + +@bp.get('/') +@bp.input(YearGroupQuerySchema, location='query') +@bp.output(YearGroupPaginationSchema) +def list_of_year_groups(query: dict) -> dict: + page = query.get('page') + per_page = query.get('per_page') + + year_group_query = YearGroup.query.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'] + } + + +@bp.put('//') +@bp.input(YearGroupSchema) +@bp.output(MessageSchema) +def update_year_of_group(id: int, data: dict) -> dict: + if not data: + abort(400, 'You have passed empty data!') + + year_group_query = YearGroup.query.filter(YearGroup.id == id) + year_group = year_group_query.first() + + if year_group is None: + abort(404, 'Not found year group!') + + year_group_query.update(data) + db.session.commit() + + return {"message": "Year group was updated!"} + + +@bp.delete('//') +@bp.output(MessageSchema, status_code=202) +def delete_year_of_group(id: int) -> dict: + year_group = YearGroup.query.filter_by(id=id).first() + if year_group is None: + abort(404, f"Year group doesn't exist!") + db.session.delete(year_group) + db.session.commit() + return {"message": "Year group was deleted!"} diff --git a/backend/app/coordinator/schemas/__init__.py b/backend/app/coordinator/schemas/__init__.py index 6599de1..12fb332 100644 --- a/backend/app/coordinator/schemas/__init__.py +++ b/backend/app/coordinator/schemas/__init__.py @@ -3,7 +3,8 @@ from .examination_schedule import ExaminationScheduleSchema, ExaminationSchedule ExaminationSchedulesPaginationSchema, ExaminationSchedulesQuerySchema, WorkloadSchema from .groups import GroupQuerySchema, GroupsPaginationSchema, GroupCreateSchema, GroupEditSchema from .project_supervisor import ProjectSupervisorQuerySchema, ProjectSupervisorsPaginationSchema, \ - ProjectSupervisorCreateSchema, ProjectSupervisorEditSchema + ProjectSupervisorCreateSchema, ProjectSupervisorEditSchema, ProjectSupervisorYearGroupSchema from .students import ProjectSupervisorSchema, GroupSchema, StudentSchema, StudentsPaginationSchema, \ StudentListFileDownloaderSchema, StudentCreateSchema, StudentEditSchema, MessageSchema, FileSchema, \ StudentQuerySchema +from .year_group import YearGroupSchema, YearGroupPaginationSchema, YearGroupQuerySchema diff --git a/backend/app/coordinator/schemas/examination_schedule.py b/backend/app/coordinator/schemas/examination_schedule.py index a6c2925..4c7ffe2 100644 --- a/backend/app/coordinator/schemas/examination_schedule.py +++ b/backend/app/coordinator/schemas/examination_schedule.py @@ -5,20 +5,22 @@ from ..validators import validate_datetime_greater_than_now class ExaminationScheduleSchema(Schema): title = fields.Str(validate=validate.Length(min=1, max=100), required=True) - mode = fields.Boolean(required=True) + start_date = fields.DateTime(validate=validate_datetime_greater_than_now, required=True) + end_date = fields.DateTime(validate=validate_datetime_greater_than_now, required=True) class ExaminationScheduleUpdateSchema(Schema): - start_date = fields.DateTime(validate=validate_datetime_greater_than_now, required=True) - end_date = fields.DateTime(validate=validate_datetime_greater_than_now, required=True) + start_date_for_enrollment_students = fields.DateTime(validate=validate_datetime_greater_than_now, required=True) + end_date_for_enrollment_students = fields.DateTime(validate=validate_datetime_greater_than_now, required=True) class ExaminationScheduleListItemSchema(Schema): - id = fields.Integer(required=True) - title = fields.Str(validate=validate.Length(min=1, max=100), required=True) - mode = fields.Boolean(required=True) - start_date = fields.DateTime(validate=validate_datetime_greater_than_now, required=True) - end_date = fields.DateTime(validate=validate_datetime_greater_than_now, required=True) + id = fields.Integer() + title = fields.Str() + start_date = fields.DateTime() + end_date = fields.DateTime() + start_date_for_enrollment_students = fields.DateTime() + end_date_for_enrollment_students = fields.DateTime() class ExaminationSchedulesPaginationSchema(Schema): @@ -32,7 +34,7 @@ class ExaminationSchedulesQuerySchema(Schema): class ProjectSupervisorStatisticsSchema(Schema): - full_name = fields.Str() + fullname = fields.Str() assigned_to_committee = fields.Integer() groups_assigned_to_his_committee = fields.Integer() diff --git a/backend/app/coordinator/schemas/project_supervisor.py b/backend/app/coordinator/schemas/project_supervisor.py index 389959f..b8b8c2f 100644 --- a/backend/app/coordinator/schemas/project_supervisor.py +++ b/backend/app/coordinator/schemas/project_supervisor.py @@ -1,36 +1,32 @@ -from marshmallow import fields, validate +from marshmallow import fields, validate, Schema -from ...dependencies import ma -from ..validators import validate_index from .students import ProjectSupervisorSchema -class ProjectSupervisorQuerySchema(ma.Schema): +class ProjectSupervisorQuerySchema(Schema): fullname = fields.Str() order_by_first_name = fields.Str() order_by_last_name = fields.Str() page = fields.Integer() per_page = fields.Integer() - mode = fields.Integer() -class ProjectSupervisorsPaginationSchema(ma.Schema): +class ProjectSupervisorsPaginationSchema(Schema): project_supervisors = fields.List(fields.Nested(ProjectSupervisorSchema)) max_pages = fields.Integer() -class ProjectSupervisorCreateSchema(ma.Schema): +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), required=True) - limit_group = fields.Integer() - mode = fields.Integer(required=True) -class ProjectSupervisorEditSchema(ma.Schema): +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=11), required=True) - limit_group = fields.Integer(validate=validate_index) - count_groups = fields.Integer(validate=validate_index) - mode = fields.Integer(required=True) + + +class ProjectSupervisorYearGroupSchema(Schema): + limit_group = fields.Integer(required=True) diff --git a/backend/app/coordinator/schemas/students.py b/backend/app/coordinator/schemas/students.py index 747cb0e..d32154e 100644 --- a/backend/app/coordinator/schemas/students.py +++ b/backend/app/coordinator/schemas/students.py @@ -40,7 +40,7 @@ class StudentCreateSchema(ma.Schema): 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) - mode = fields.Boolean(required=True) + year_group_id = fields.Integer() class StudentEditSchema(ma.Schema): @@ -48,7 +48,6 @@ class StudentEditSchema(ma.Schema): last_name = fields.Str(validate=validate.Length(min=1, max=255)) pesel = fields.Str(validate=validate.Length(min=0, max=11)) index = fields.Integer(validate=validate_index) - mode = fields.Boolean() class MessageSchema(ma.Schema): @@ -65,4 +64,3 @@ class StudentQuerySchema(ma.Schema): order_by_last_name = fields.Str() page = fields.Integer() per_page = fields.Integer() - mode = fields.Boolean() diff --git a/backend/app/coordinator/schemas/year_group.py b/backend/app/coordinator/schemas/year_group.py new file mode 100644 index 0000000..5cd0108 --- /dev/null +++ b/backend/app/coordinator/schemas/year_group.py @@ -0,0 +1,30 @@ +from marshmallow import Schema, fields, validate, ValidationError + +from ...base.mode import ModeGroups + + +def validate_mode(value: str) -> str: + if value not in [m.value for m in ModeGroups]: + raise ValidationError("Invalid mode!") + return value + + +class YearGroupSchema(Schema): + name = fields.Str(validate=validate.Regexp('^\d{4}/\d{4}$'), required=True) + mode = fields.Str(validate=validate_mode, required=True) + + +class YearGroupItemSchema(Schema): + id = fields.Integer() + name = fields.Str() + mode = fields.Str() + + +class YearGroupPaginationSchema(Schema): + year_groups = fields.List(fields.Nested(YearGroupItemSchema)) + max_pages = fields.Integer() + + +class YearGroupQuerySchema(Schema): + page = fields.Integer() + per_page = fields.Integer() diff --git a/backend/app/coordinator/utils.py b/backend/app/coordinator/utils.py index cacd399..70b64e0 100644 --- a/backend/app/coordinator/utils.py +++ b/backend/app/coordinator/utils.py @@ -13,7 +13,7 @@ from reportlab.platypus import SimpleDocTemplate, Paragraph, PageBreak, Table from .exceptions import InvalidNameOrTypeHeaderException from ..students.models import Student -from ..examination_schedule.models import Enrollment +# from ..examination_schedule.models import Enrollment def check_columns(df: pd.DataFrame) -> bool: @@ -66,7 +66,7 @@ def generate_csv(students: List[Student]) -> str: return df.to_csv(index=False) -def generate_examination_schedule_pdf_file(title: str, nested_enrollments: List[List[Enrollment]]) -> bytes: +def generate_examination_schedule_pdf_file(title: str, nested_enrollments: List[List[object]]) -> bytes: pagesize = (297 * mm, 210 * mm) headers = ["lp.", "Godzina", "Nazwa projektu", "Opiekun", "Zespol", "Komisja"] pdf_buffer = BytesIO() diff --git a/backend/app/examination_schedule/models.py b/backend/app/examination_schedule/models.py index 3ce553b..6b37ff8 100644 --- a/backend/app/examination_schedule/models.py +++ b/backend/app/examination_schedule/models.py @@ -6,33 +6,40 @@ class ExaminationSchedule(Base): __tablename__ = 'examination_schedules' title = db.Column(db.String(100), unique=True, nullable=False) - mode = db.Column(db.Boolean, default=True, nullable=False) # True - stationary, False - non-stationary - start_date = db.Column(db.DateTime) - end_date = db.Column(db.DateTime) + duration_time = db.Column(db.Integer) # in minutes + start_date_for_enrollment_students = db.Column(db.DateTime) + end_date_for_enrollment_students = db.Column(db.DateTime) + start_date = db.Column(db.DateTime, nullable=False) + end_date = db.Column(db.DateTime, nullable=False) + year_group_id = db.Column(db.Integer, db.ForeignKey('year_groups.id'), nullable=False) + year_group = db.relationship('YearGroup', backref='examination_schedules') -class Enrollment(Base): - __tablename__ = 'enrollments' +committee = db.Table( + "committees", + db.Column("term_of_defence_id", db.ForeignKey("term_of_defences.id")), + db.Column("project_supervisor_id", db.ForeignKey("project_supervisors.id")), +) + + +class TermOfDefence(Base): + __tablename__ = 'term_of_defences' start_date = db.Column(db.DateTime, nullable=False) end_date = db.Column(db.DateTime, nullable=False) examination_schedule_id = db.Column(db.Integer, db.ForeignKey('examination_schedules.id')) - examination_schedule = db.relationship('ExaminationSchedule', backref='enrollments') - committee = db.relationship("Committee", uselist=False, backref=db.backref('enrollment', passive_deletes=True)) + examination_schedule = db.relationship('ExaminationSchedule', backref='term_of_defences') group_id = db.Column(db.Integer, db.ForeignKey('groups.id')) - group = db.relationship("Group", uselist=False, backref='enrollment') + group = db.relationship("Group", uselist=False, backref='term_of_defence') + members_of_committee = db.relationship("ProjectSupervisor", secondary=committee) -class Committee(Base): - __tablename__ = 'committees' +class TemporaryAvailability(Base): + __tablename__ = 'temporary_availabilities' - enrollment_id = db.Column(db.Integer, db.ForeignKey('enrollments.id', ondelete='CASCADE')) - members = db.relationship('ProjectSupervisor', secondary='committees_projects_supervisors', backref='committees') - - -class CommitteeProjectSupervisor(Base): - __tablename__ = 'committees_projects_supervisors' - - chairman = db.Column(db.Boolean, default=False, nullable=False) - committee_id = db.Column(db.Integer, db.ForeignKey('committees.id')) - member_id = db.Column(db.Integer, db.ForeignKey('project_supervisors.id')) + start_date = db.Column(db.DateTime, nullable=False) + end_date = db.Column(db.DateTime, nullable=False) + examination_schedule_id = db.Column(db.Integer, db.ForeignKey('examination_schedules.id'), nullable=False) + examination_schedule = db.relationship("ExaminationSchedule", backref='temporary_availabilities') + project_supervisor_id = db.Column(db.Integer, db.ForeignKey('project_supervisors.id'), nullable=False) + project_supervisor = db.relationship("ProjectSupervisor", backref='temporary_availabilities') diff --git a/backend/app/examination_schedule/routes/__init__.py b/backend/app/examination_schedule/routes/__init__.py index d4cecbb..738fb6f 100644 --- a/backend/app/examination_schedule/routes/__init__.py +++ b/backend/app/examination_schedule/routes/__init__.py @@ -1,9 +1,3 @@ from flask import Blueprint -from .enrollments import bp as enrollments_bp -from .examination_schedule import bp as examination_schedule_bp - bp = Blueprint("examination_schedule", __name__, url_prefix="/examination_schedule") - -bp.register_blueprint(enrollments_bp) -bp.register_blueprint(examination_schedule_bp) diff --git a/backend/app/examination_schedule/routes/enrollments.py b/backend/app/examination_schedule/routes/enrollments.py index 945f6e1..e01027b 100644 --- a/backend/app/examination_schedule/routes/enrollments.py +++ b/backend/app/examination_schedule/routes/enrollments.py @@ -3,7 +3,7 @@ import datetime from apiflask import APIBlueprint from flask import abort -from ..schemas import EnrollmentPaginationSchema, EnrollmentQuerySchema +from ..schemas import EnrollmentPaginationSchema, EnrollmentQuerySchema, ExaminationScheduleProjectSupervisorViewSchema from ..utils import check_examination_schedule_is_exist, get_list_of_enrollments_response bp = APIBlueprint("enrollments", __name__, url_prefix="/enrollments") @@ -33,29 +33,7 @@ def list_enrollments_for_students(examination_schedule_id: int, query: dict) -> @bp.get('//coordinator-view/') -@bp.input(EnrollmentQuerySchema, location='query') -@bp.output(EnrollmentPaginationSchema) -def list_enrollments_for_coordinator(examination_schedule_id: int, query: dict) -> dict: - page = query.get('page') - per_page = query.get('per_page') +@bp.output(ExaminationScheduleProjectSupervisorViewSchema) +def list_enrollments_for_coordinator(examination_schedule_id: int) -> dict: + return check_examination_schedule_is_exist(examination_schedule_id) - check_examination_schedule_is_exist(examination_schedule_id) - - return get_list_of_enrollments_response(examination_schedule_id, page, per_page) - - -@bp.get('//project-supervisor-view/') -@bp.input(EnrollmentQuerySchema, location='query') -@bp.output(EnrollmentPaginationSchema) -def list_enrollments_for_project_supervisor(examination_schedule_id: int, query: dict) -> dict: - page = query.get('page') - per_page = query.get('per_page') - - examination_schedule = check_examination_schedule_is_exist(examination_schedule_id) - - now = datetime.datetime.utcnow() - - if examination_schedule.start_date.timestamp() < now.timestamp(): - abort(403, "Forbidden! Enrollment has just started! You cannot assign to the exam committees!") - - return get_list_of_enrollments_response(examination_schedule_id, page, per_page) diff --git a/backend/app/examination_schedule/routes/examination_schedule.py b/backend/app/examination_schedule/routes/examination_schedule.py index 69d9cc4..aa1fcfa 100644 --- a/backend/app/examination_schedule/routes/examination_schedule.py +++ b/backend/app/examination_schedule/routes/examination_schedule.py @@ -1,32 +1,18 @@ import datetime from apiflask import APIBlueprint -from flask import abort from ...dependencies import db from ..models import ExaminationSchedule -from ..schemas import ExaminationScheduleListSchema bp = APIBlueprint("list_of_examination_schedule", __name__, url_prefix="/") -@bp.get('/project-supervisor-view/') -@bp.output(ExaminationScheduleListSchema) -def list_examination_schedule_for_project_supervisors() -> dict: - now = datetime.datetime.utcnow() - examination_schedules = db.session.query(ExaminationSchedule). \ - filter(ExaminationSchedule.start_date > now). \ - all() - return {'examination_schedules': examination_schedules} - - @bp.get('/students-view/') -@bp.output(ExaminationScheduleListSchema) def list_examination_schedule_for_students() -> dict: # in the future filter after the mode of examination schedule if we will have authorization module now = datetime.datetime.utcnow() examination_schedules = db.session.query(ExaminationSchedule).\ - filter(ExaminationSchedule.start_date < now).\ - filter(ExaminationSchedule.end_date > now).\ - all() + filter(ExaminationSchedule.start_date_for_enrollment_students < now).\ + filter(ExaminationSchedule.end_date_for_enrollment_students > now).all() return {'examination_schedules': examination_schedules} diff --git a/backend/app/examination_schedule/schemas.py b/backend/app/examination_schedule/schemas.py index 96a9dd9..b7d5114 100644 --- a/backend/app/examination_schedule/schemas.py +++ b/backend/app/examination_schedule/schemas.py @@ -27,15 +27,13 @@ class EnrollmentPaginationSchema(Schema): max_pages = fields.Integer() +class ExaminationScheduleProjectSupervisorViewSchema(Schema): + id = fields.Integer() + start_date = fields.DateTime() + end_date = fields.DateTime() + + class EnrollmentQuerySchema(Schema): page = fields.Integer() per_page = fields.Integer() - -class ExaminationScheduleSchema(Schema): - id = fields.Integer() - title = fields.Str() - - -class ExaminationScheduleListSchema(Schema): - examination_schedules = fields.List(fields.Nested(ExaminationScheduleSchema)) diff --git a/backend/app/examination_schedule/utils.py b/backend/app/examination_schedule/utils.py index b2cb0fa..2a283c3 100644 --- a/backend/app/examination_schedule/utils.py +++ b/backend/app/examination_schedule/utils.py @@ -1,7 +1,7 @@ from flask import abort from ..dependencies import db -from .models import Enrollment, Committee, ExaminationSchedule +from .models import ExaminationSchedule from ..students.models import Group from ..base.utils import paginate_models diff --git a/backend/app/project_supervisor/models.py b/backend/app/project_supervisor/models.py index d1f39bd..06eea70 100644 --- a/backend/app/project_supervisor/models.py +++ b/backend/app/project_supervisor/models.py @@ -3,24 +3,31 @@ from flask_sqlalchemy import BaseQuery from ..dependencies import db from ..base.models import Person, Base 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" - limit_group = db.Column(db.Integer, default=3, nullable=False) - count_groups = db.Column(db.Integer, default=0, nullable=False) - mode = db.Column(db.Integer, default=0, nullable=False) # 0 - stationary, 1 - non-stationary, 2 - both + year_groups = db.relationship('YearGroupProjectSupervisors', lazy=True) @classmethod - def search_by_fullname_and_mode_and_order_by_first_name_or_last_name(cls, fullname: str = None, - mode: int = None, + def search_by_fullname_and_mode_and_order_by_first_name_or_last_name(cls, year_group_id: int = None, + fullname: str = None, order_by_first_name: str = None, order_by_last_name: str = None) -> BaseQuery: project_supervisors_query = cls.query - if mode is not None: - project_supervisors_query = project_supervisors_query.filter(mode != 1 - mode) + if year_group_id is not None: + project_supervisors_query = project_supervisors_query.filter(YearGroup.id == year_group_id) if fullname is not None: project_supervisors_query = project_supervisors_query.filter( diff --git a/backend/app/project_supervisor/query.py b/backend/app/project_supervisor/query.py index 620a2d4..f4ee19e 100644 --- a/backend/app/project_supervisor/query.py +++ b/backend/app/project_supervisor/query.py @@ -3,11 +3,11 @@ import datetime from flask import abort from ..dependencies import db -from ..examination_schedule.models import Enrollment, ExaminationSchedule, Committee +from ..examination_schedule.models import ExaminationSchedule def get_enrollment_by_enrollment_and_examination_schedule_ids(examination_schedule_id: int, - enrollment_id: int) -> Enrollment: + enrollment_id: int) -> None: enrollment = db.session.query(Enrollment). \ join(ExaminationSchedule, isouter=True).join(Committee, isouter=True). \ filter(ExaminationSchedule.id == examination_schedule_id). \ @@ -20,12 +20,6 @@ def get_enrollment_by_enrollment_and_examination_schedule_ids(examination_schedu return enrollment -def check_the_project_supervisor_is_in_committee(enrollment_id: int, project_supervisor_id) -> bool: - return db.session.query( - Committee.query.join(Committee.members).filter(Committee.enrollment_id == enrollment_id).filter( - Committee.members.any(id=project_supervisor_id)).exists()).scalar() - - def check_the_enrollments_has_just_started(start_date: datetime.datetime, action: str) -> None: now = datetime.datetime.utcnow() diff --git a/backend/app/project_supervisor/routes/enrollments.py b/backend/app/project_supervisor/routes/enrollments.py index 9ea7d8c..c5fe196 100644 --- a/backend/app/project_supervisor/routes/enrollments.py +++ b/backend/app/project_supervisor/routes/enrollments.py @@ -1,20 +1,21 @@ -from apiflask import APIBlueprint -from flask import abort, current_app +from datetime import datetime -from ..schemas import MessageSchema, CommitteeCreateSchema, TemporaryProjectSupervisorSchema -from ...examination_schedule.models import Committee +from apiflask import APIBlueprint +from flask import abort + +from ..schemas import MessageSchema, TimeAvailabilityCreateSchema, TemporaryProjectSupervisorSchema, \ + ListOfFreeTimesSchema from ...dependencies import db from ..models import ProjectSupervisor -from ..query import get_enrollment_by_enrollment_and_examination_schedule_ids, \ - check_the_project_supervisor_is_in_committee, check_the_enrollments_has_just_started +from ...examination_schedule.models import ExaminationSchedule, TemporaryAvailability, TermOfDefence bp = APIBlueprint("enrollments", __name__, url_prefix="/") -@bp.post('//enrollments//') -@bp.input(CommitteeCreateSchema) +@bp.post('//enrollments/') +@bp.input(TimeAvailabilityCreateSchema) @bp.output(MessageSchema) -def assign_yourself_to_committee(examination_schedule_id: int, enrollment_id: int, data: dict) -> dict: +def set_your_free_time_to_examination_schedule(examination_schedule_id: int, data: dict) -> dict: # this code will be removed project_supervisor = db.session.query(ProjectSupervisor).filter( ProjectSupervisor.id == data['project_supervisor_id']).first() @@ -22,43 +23,100 @@ def assign_yourself_to_committee(examination_schedule_id: int, enrollment_id: in abort(404, "ProjectSupervisor doesn't exist!") ################ - limit_of_committtee = current_app.config['LIMIT_PERSONS_PER_COMMITTEE'] + es = ExaminationSchedule.query.filter(ExaminationSchedule.id == examination_schedule_id).first() + if es is None: + abort(404, "Examination schedule doesn't exist!") - enrollment = get_enrollment_by_enrollment_and_examination_schedule_ids(examination_schedule_id, enrollment_id) - check_the_enrollments_has_just_started(enrollment.examination_schedule.start_date, "assign") + sd = data['start_date'] + ed = data['end_date'] + if sd > ed: + abort(400, "Invalid data! End date must be greater than start date!") - size_of_committee = db.session.query(Committee).join(Committee.members). \ - filter(Committee.enrollment_id == enrollment.id).count() + if not (es.start_date >= sd and es.end_date <= ed): + abort(400, "Invalid date ranges!") - if size_of_committee >= limit_of_committtee: - abort(400, "The committee is full!") + now = datetime.utcnow() + if es.start_date_for_enrollment_students is not None and \ + es.start_date_for_enrollment_students.timestamp() < now.timestamp(): + abort(403, "Enrollment has started! You cannot set your free time!") - if check_the_project_supervisor_is_in_committee(enrollment.id, project_supervisor.id): - abort(400, "You have already in this committee!") + ta_query = TemporaryAvailability.query.filter( + TemporaryAvailability.examination_schedule_id == examination_schedule_id). \ + filter(TemporaryAvailability.project_supervisor_id == project_supervisor.id) + if ta_query.first() is not None: + # implement logic + pass - enrollment.committee.members.append(project_supervisor) - db.session.add(enrollment) + ta = TemporaryAvailability(**data, examination_schedule_id=examination_schedule_id, + project_supervisor_id=project_supervisor.id) + db.session.add(ta) db.session.commit() - return {"message": "You have just assigned yourself to committee!"} + return {"message": "You have just assigned your free time!"} -@bp.delete('//enrollments//') +@bp.delete('//enrollments//') @bp.input(TemporaryProjectSupervisorSchema) @bp.output(MessageSchema) -def delete_yourself_from_committee(examination_schedule_id: int, enrollment_id: int, data: dict) -> dict: +def delete_your_free_time_from_examination_schedule(examination_schedule_id: int, id: int, data: dict) -> dict: # this code will be removed project_supervisor = db.session.query(ProjectSupervisor).filter(ProjectSupervisor.id == data['id']).first() if project_supervisor is None: abort(404, "ProjectSupervisor doesn't exist!") ################ + ta = TemporaryAvailability.query.filter(TemporaryAvailability.examination_schedule_id == examination_schedule_id, + TemporaryAvailability.project_supervisor_id == project_supervisor.id, + TemporaryAvailability.id == id).first() - enrollment = get_enrollment_by_enrollment_and_examination_schedule_ids(examination_schedule_id, enrollment_id) - check_the_enrollments_has_just_started(enrollment.examination_schedule.start_date, "delete") + if ta is None: + abort(404, "Your free time doesn't exist!") - if not check_the_project_supervisor_is_in_committee(enrollment.id, project_supervisor.id): - abort(400, "You are not assigned to this committee!") - - enrollment.committee.members.remove(project_supervisor) + db.session.delete(ta) db.session.commit() - return {"message": "You have just removed from committee!"} + return {"message": "You have just removed your free time!"} + + +@bp.get('//') +@bp.input(TemporaryProjectSupervisorSchema, location='query') +@bp.output(ListOfFreeTimesSchema) +def list_enrollments_for_project_supervisor(examination_schedule_id: int, data: dict) -> dict: + # this code will be removed + project_supervisor = db.session.query(ProjectSupervisor).filter(ProjectSupervisor.id == data['id']).first() + if project_supervisor is None: + abort(404, "ProjectSupervisor doesn't exist!") + ################ + es = ExaminationSchedule.query.filter(ExaminationSchedule.id == examination_schedule_id, + ExaminationSchedule.year_group_id).first() + + if es is None: + abort(404, "Examination schedule doesn't exist!") + + now = datetime.utcnow() + if es.start_date_for_enrollment_students.timestamp() < now.timestamp(): + abort(403, "Forbidden! Enrollment has just started! You cannot assign to the exam committees!") + + # list of your term of defences first enrollment + ta = TemporaryAvailability.query.filter(TemporaryAvailability.examination_schedule_id == examination_schedule_id, + TemporaryAvailability.project_supervisor_id == project_supervisor.id).all() + return ta + + +@bp.get('//term-of-defences/') +@bp.input(TemporaryProjectSupervisorSchema, location='query') +@bp.output(ListOfFreeTimesSchema) +def list_created_enrollment_by_coordinator_for_project_supervisor(examination_schedule_id: int, data: dict) -> dict: + # this code will be removed + project_supervisor = db.session.query(ProjectSupervisor).filter(ProjectSupervisor.id == data['id']).first() + if project_supervisor is None: + abort(404, "ProjectSupervisor doesn't exist!") + ################ + es = ExaminationSchedule.query.filter(ExaminationSchedule.id == examination_schedule_id, + ExaminationSchedule.year_group_id).first() + if es is None: + abort(404, "Examination schedule doesn't exist!") + + # list of your free times first enrollment + td = TermOfDefence.query.join(TermOfDefence.members_of_committee). \ + filter(TermOfDefence.examination_schedule_id == examination_schedule_id). \ + filter_by(project_supervisor_id=project_supervisor.id).all() + return td diff --git a/backend/app/project_supervisor/schemas.py b/backend/app/project_supervisor/schemas.py index f040c11..e956ab1 100644 --- a/backend/app/project_supervisor/schemas.py +++ b/backend/app/project_supervisor/schemas.py @@ -1,13 +1,23 @@ from marshmallow import fields, validate, Schema -# MessageSchema, CommitteeCreateSchema - class MessageSchema(Schema): message = fields.Str() -class CommitteeCreateSchema(Schema): +class FreeTimeSchema(Schema): + id = fields.Integer() + start_date = fields.DateTime(required=True) + end_date = fields.DateTime(required=True) + + +class ListOfFreeTimesSchema(Schema): + free_times = fields.List(fields.Nested(FreeTimeSchema)) + + +class TimeAvailabilityCreateSchema(Schema): + start_date = fields.DateTime(required=True) + end_date = fields.DateTime(required=True) project_supervisor_id = fields.Integer(required=True) # temporary field it will be removed in the future diff --git a/backend/app/students/models.py b/backend/app/students/models.py index d927e82..217fd0c 100644 --- a/backend/app/students/models.py +++ b/backend/app/students/models.py @@ -1,9 +1,27 @@ +from datetime import datetime + from flask_sqlalchemy import BaseQuery from ..dependencies import db from ..base.models import Person, Base from ..base.utils import order_by_column_name -from ..examination_schedule.models import Enrollment +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), unique=True, nullable=False) + mode = db.Column(db.String(1), unique=True, nullable=False) + created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) + students = db.relationship("YearGroupStudents") class Group(Base): @@ -15,13 +33,14 @@ class Group(Base): 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=True) + year_group_id = db.Column(db.Integer, db.ForeignKey('year_groups.id')) + year_group = db.relationship('YearGroup', backref='groups', lazy=True) points_for_first_term = db.Column(db.Integer, default=0, nullable=False) points_for_second_term = db.Column(db.Integer, default=0, nullable=False) - # enrollment = db.relationship('Enrollment', uselist=False, backref='group') @classmethod - def search_by_name(cls, search_name: str = None) -> BaseQuery: - group_query = cls.query + def search_by_name(cls, year_group_id: int, search_name: str = None) -> BaseQuery: + group_query = cls.query.filter(Group.year_group_id == year_group_id) if search_name is not None: group_query = group_query.filter(Group.name.like(f'{search_name}%')) @@ -35,18 +54,14 @@ class Student(Person): pesel = db.Column(db.String(11), default='') index = db.Column(db.Integer, primary_key=True) group_id = db.Column(db.Integer, db.ForeignKey('groups.id')) - group = db.relationship('Group', backref='students', lazy=True) - mode = db.Column(db.Boolean, default=True, nullable=False) # True - stationary, False - non-stationary + groups = db.relationship('Group', backref='students', lazy=True) + year_groups = db.relationship("YearGroupStudents") @classmethod - def search_by_fullname_and_mode_and_order_by_first_name_or_last_name(cls, fullname: str = None, - mode: bool = None, + 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 - - if mode is not None: - student_query = student_query.filter_by(mode=mode) + student_query = cls.query.filter(YearGroup.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/enrollments.py b/backend/app/students/routes/enrollments.py index a8ab304..2293d2a 100644 --- a/backend/app/students/routes/enrollments.py +++ b/backend/app/students/routes/enrollments.py @@ -3,14 +3,11 @@ import datetime from apiflask import APIBlueprint from flask import abort -from ..schemas import MessageSchema, TemporaryStudentSchema +from ..schemas import MessageSchema, TemporaryStudentSchema, ExaminationScheduleListSchema from ...dependencies import db -from ...examination_schedule.models import Enrollment -from ..models import Student, Group +from ..models import Student, Group, TermOfDefence +from ...examination_schedule.models import ExaminationSchedule from ...project_supervisor.models import ProjectSupervisor -from ...project_supervisor.query import get_enrollment_by_enrollment_and_examination_schedule_ids, \ - check_the_project_supervisor_is_in_committee -from ..query import check_the_enrollments_has_just_started bp = APIBlueprint("enrollments", __name__, url_prefix="/") @@ -18,52 +15,93 @@ bp = APIBlueprint("enrollments", __name__, url_prefix="/") @bp.post('//enrollments//') @bp.input(TemporaryStudentSchema) @bp.output(MessageSchema) -def assign_group_for_this_exam_date(examination_schedule_id: int, enrollment_id: int, data: dict) -> dict: +def assign_your_group_to_term_of_defence(examination_schedule_id: int, data: dict) -> dict: # this code will be removed student = Student.query.filter(Student.index == data['student_index']).first() if student is None: abort(404, "Student doesn't exist!") ################ st = Student.query.join(Group).join(ProjectSupervisor).filter(Student.index == student.index).first() - - enrollment = db.session.query(Enrollment.id).filter(Enrollment.group_id == st.group.id).first() - if enrollment is not None: - abort(400, "Your group has already assigned to any exam date!") - - enrollment = get_enrollment_by_enrollment_and_examination_schedule_ids(examination_schedule_id, enrollment_id) - check_the_enrollments_has_just_started(enrollment.examination_schedule) - if st is None or st.group.project_supervisor is None: abort(400, "You don't have a group or your group doesn't have an assigned project supervisor!") - if not check_the_project_supervisor_is_in_committee(enrollment_id, st.group.project_supervisor.id): + defence = TermOfDefence.query.filter(TermOfDefence.group_id == st.group.id, + TermOfDefence.examination_schedule_id == examination_schedule_id).first() + if defence is not None: + abort(400, "Your group has already assigned to any exam date!") + + g = Group.query.filter(id=st.group_id).first() + td = TermOfDefence.query.join(TermOfDefence.members_of_committee). \ + filter_by(project_supervisor_id=g.project_supervisor_id).first() + + if td is None: abort(400, "Your project supervisor is not in committee!") - enrollment.group_id = st.group.id - db.session.add(enrollment) + defence.group_id = st.group.id + db.session.add(defence) db.session.commit() return {"message": "You have just assigned the group for this exam date!"} -@bp.delete('//enrollments//') +@bp.delete('//enrollments//') @bp.input(TemporaryStudentSchema) @bp.output(MessageSchema) -def delete_group_for_this_exam_date(examination_schedule_id: int, enrollment_id: int, data: dict) -> dict: +def delete_your_group_from_term_of_defence(examination_schedule_id: int, term_of_defence_id: int, data: dict) -> dict: # this code will be removed student = Student.query.filter(Student.index == data['student_index']).first() if student is None: abort(404, "Student doesn't exist!") ################ - enrollment = get_enrollment_by_enrollment_and_examination_schedule_ids(examination_schedule_id, enrollment_id) + term_of_defence = TermOfDefence.query.filter(TermOfDefence.id == term_of_defence_id). \ + filter(TermOfDefence.examination_schedule_id == examination_schedule_id).first() - if student.group.id != enrollment.group_id: - abort(400, "You are not assigned to this committee!") + if term_of_defence is None: + abort(404, "Term of defence doesn't exist!") - check_the_enrollments_has_just_started(enrollment.examination_schedule) + if student.group.id != term_of_defence.group_id: + abort(400, "You are not assigned to this group!") - enrollment.group = None - db.session.add(enrollment) + term_of_defence.group_id = None + db.session.add(term_of_defence) db.session.commit() return {"message": "You have just removed the group for this exam date!"} + + +@bp.get('/examination-schedule/year-group//') +@bp.input(TemporaryStudentSchema) +@bp.output(ExaminationScheduleListSchema) +def list_examination_schedule_for_students(year_group_id: int, data: dict) -> dict: + # this code will be removed + student = Student.query.filter(Student.index == data['student_index']).first() + if student is None: + abort(404, "Student doesn't exist!") + ################ + + # in the future filter after the mode of examination schedule if we will have authorization module + now = datetime.datetime.utcnow() + examination_schedules = ExaminationSchedule.query. \ + filter(ExaminationSchedule.year_group_id == year_group_id). \ + filter(ExaminationSchedule.start_date_for_enrollment_students < now). \ + filter(ExaminationSchedule.end_date_for_enrollment_students > now).all() + return {'examination_schedules': examination_schedules} + + +@bp.get('/enrollments/') +@bp.input(TemporaryStudentSchema) +@bp.output(ExaminationScheduleListSchema) +def list_term_of_defences_for_students(data: dict) -> dict: + # this code will be removed + student = Student.query.filter(Student.index == data['student_index']).first() + if student is None: + abort(404, "Student doesn't exist!") + ################ + # in the future filter after the mode of examination schedule if we will have authorization module + now = datetime.datetime.utcnow() + term_of_defences = TermOfDefence.query.join(ExaminationSchedule, isouter=True). \ + filter(ExaminationSchedule.start_date_for_enrollment_students < now). \ + filter(ExaminationSchedule.end_date_for_enrollment_students > now). \ + filter(TermOfDefence.group_id == student.group_id). \ + all() + return {'examination_schedules': term_of_defences} diff --git a/backend/app/students/routes/registrations.py b/backend/app/students/routes/registrations.py index f7fe5e2..fb4ceff 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 +from ...project_supervisor.models import ProjectSupervisor, YearGroupProjectSupervisors from ..models import Group from ...dependencies import db from ..schemas import ProjectSupervisorQuerySchema, ProjectSupervisorPaginationSchema @@ -9,21 +9,20 @@ from ...base.utils import paginate_models bp = APIBlueprint("registrations", __name__, url_prefix="/registrations") -@bp.get('/') +@bp.get('//') @bp.input(ProjectSupervisorQuerySchema, location='query') @bp.output(ProjectSupervisorPaginationSchema) -def list_available_groups(query: dict) -> dict: - mode = 0 if query.get('mode') else 1 +def list_available_groups(year_group_id: int, query: dict) -> dict: page = query.get('page') per_page = query.get('per_page') - available_groups = (ProjectSupervisor.limit_group - ProjectSupervisor.count_groups) - ps_query = db.session.query(ProjectSupervisor, available_groups).join(Group, isouter=True) - - if mode is not None: - ps_query = ps_query.filter(ProjectSupervisor.mode != 1-mode) - - ps_query = ps_query.group_by(ProjectSupervisor.id) + available_groups = (YearGroupProjectSupervisors.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).\ + group_by(ProjectSupervisor.id) data = paginate_models(page, ps_query, per_page) diff --git a/backend/app/students/schemas.py b/backend/app/students/schemas.py index 768ca22..6a70137 100644 --- a/backend/app/students/schemas.py +++ b/backend/app/students/schemas.py @@ -5,7 +5,6 @@ class ProjectSupervisorSchema(Schema): first_name = fields.Str() last_name = fields.Str() email = fields.Str() - mode = fields.Integer() available_groups = fields.Integer() @@ -17,7 +16,6 @@ class ProjectSupervisorPaginationSchema(Schema): class ProjectSupervisorQuerySchema(Schema): page = fields.Integer() per_page = fields.Integer() - mode = fields.Boolean() class TemporaryStudentSchema(Schema): @@ -26,3 +24,14 @@ class TemporaryStudentSchema(Schema): class MessageSchema(Schema): message = fields.Str() + + +class ExaminationScheduleSchema(Schema): + id = fields.Integer() + title = fields.Str() + start_date = fields.DateTime() + end_date = fields.DateTime() + + +class ExaminationScheduleListSchema(Schema): + examination_schedules = fields.List(fields.Nested(ExaminationScheduleSchema)) diff --git a/backend/migrations/versions/5c3c4c4e1a72_.py b/backend/migrations/versions/5c3c4c4e1a72_.py new file mode 100644 index 0000000..ca71701 --- /dev/null +++ b/backend/migrations/versions/5c3c4c4e1a72_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: 5c3c4c4e1a72 +Revises: a96f91e5b556 +Create Date: 2022-11-12 11:48:38.377516 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '5c3c4c4e1a72' +down_revision = 'a96f91e5b556' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('examination_schedules', sa.Column('duration_time', sa.Integer(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('examination_schedules', 'duration_time') + # ### end Alembic commands ### diff --git a/backend/migrations/versions/a96f91e5b556_.py b/backend/migrations/versions/a96f91e5b556_.py new file mode 100644 index 0000000..544eeb4 --- /dev/null +++ b/backend/migrations/versions/a96f91e5b556_.py @@ -0,0 +1,141 @@ +"""empty message + +Revision ID: a96f91e5b556 +Revises: +Create Date: 2022-11-12 11:34:01.223667 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'a96f91e5b556' +down_revision = None +branch_labels = None +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('year_groups', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('name', sa.String(length=50), nullable=False), + sa.Column('mode', sa.String(length=1), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('mode'), + sa.UniqueConstraint('name') + ) + op.create_table('examination_schedules', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('title', sa.String(length=100), nullable=False), + sa.Column('year_group_id', sa.Integer(), nullable=False), + sa.Column('start_date_for_enrollment_students', sa.DateTime(), nullable=True), + sa.Column('end_date_for_enrollment_students', sa.DateTime(), nullable=True), + sa.Column('start_date', sa.DateTime(), nullable=False), + sa.Column('end_date', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['year_group_id'], ['year_groups.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('title') + ) + op.create_table('groups', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('name', sa.String(length=60), nullable=False), + sa.Column('cdyd_kod', sa.String(length=60), nullable=True), + sa.Column('prz_kod', sa.String(length=60), nullable=True), + sa.Column('tzaj_kod', sa.String(length=60), nullable=True), + sa.Column('project_supervisor_id', sa.Integer(), nullable=True), + sa.Column('year_group_id', sa.Integer(), nullable=True), + sa.Column('points_for_first_term', sa.Integer(), nullable=False), + sa.Column('points_for_second_term', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['project_supervisor_id'], ['project_supervisors.id'], ), + sa.ForeignKeyConstraint(['year_group_id'], ['year_groups.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('year_group_project_supervisors', + sa.Column('id', sa.Integer(), autoincrement=True, 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(['project_supervisor_id'], ['project_supervisors.id'], ), + sa.ForeignKeyConstraint(['year_group_id'], ['year_groups.id'], ), + sa.PrimaryKeyConstraint('id') + ) + 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.Column('group_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['group_id'], ['groups.id'], ), + 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('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') + ) + op.create_table('term_of_defences', + 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=True), + sa.Column('group_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['examination_schedule_id'], ['examination_schedules.id'], ), + sa.ForeignKeyConstraint(['group_id'], ['groups.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('committees', + sa.Column('term_of_defence_id', sa.Integer(), nullable=True), + sa.Column('project_supervisor_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['project_supervisor_id'], ['project_supervisors.id'], ), + sa.ForeignKeyConstraint(['term_of_defence_id'], ['term_of_defences.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') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('year_group_students') + op.drop_table('committees') + op.drop_table('term_of_defences') + op.drop_table('temporary_availabilities') + 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_table('students') + op.drop_table('year_group_project_supervisors') + op.drop_table('groups') + op.drop_table('examination_schedules') + op.drop_table('year_groups') + 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_table('project_supervisors') + # ### end Alembic commands ###