diff --git a/backend/app/base/utils.py b/backend/app/base/utils.py index 91e6feb..2085510 100644 --- a/backend/app/base/utils.py +++ b/backend/app/base/utils.py @@ -1,6 +1,6 @@ from typing import TypedDict, Tuple -from flask import current_app +from flask import current_app, abort from flask_sqlalchemy import BaseQuery from sqlalchemy import desc @@ -20,14 +20,9 @@ def order_by_column_name(query: BaseQuery, model_field: str, order_by_col_name: return query -def paginate_models(page: str, query: BaseQuery, per_page = 10) -> PaginationResponse or Tuple[dict, int]: +def paginate_models(page: int, query: BaseQuery, per_page=10) -> PaginationResponse: default_page = 1 - if page is not None: - try: - page = int(page) - except ValueError: - return {"error": f"Invalid page! Page must be integer!"}, 400 query = query.paginate(page=page, per_page=per_page, error_out=False) else: query = query.paginate(page=default_page, per_page=per_page, error_out=False) diff --git a/backend/app/coordinator/routes/__init__.py b/backend/app/coordinator/routes/__init__.py index 6da1701..433899d 100644 --- a/backend/app/coordinator/routes/__init__.py +++ b/backend/app/coordinator/routes/__init__.py @@ -3,9 +3,11 @@ from flask import Blueprint from .students import bp as students_bp from .project_supervisor import bp as project_supervisor_bp from .groups import bp as groups_bp +from .project_supervisor import bp as project_supervisor_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(project_supervisor_bp) diff --git a/backend/app/coordinator/routes/groups.py b/backend/app/coordinator/routes/groups.py index 54a768d..393ff3f 100644 --- a/backend/app/coordinator/routes/groups.py +++ b/backend/app/coordinator/routes/groups.py @@ -5,8 +5,8 @@ from flask_sqlalchemy import get_debug_queries from ...students.models import Group, Student from ...project_supervisor.models import ProjectSupervisor from ..schemas import GroupSchema, GroupEditSchema, GroupsPaginationSchema, \ - GroupCreateSchema, MessageSchema, FileSchema, GroupQuerySchema -from ...dependencies import db, ma + GroupCreateSchema, MessageSchema, GroupQuerySchema +from ...dependencies import db from ...base.utils import paginate_models bp = APIBlueprint("groups", __name__, url_prefix="/groups") @@ -19,15 +19,14 @@ def list_groups(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) - response = paginate_models(page, groups_query, per_page) - if (message := response.get('message')) is not None: - abort(response['status_code'], message) + data = paginate_models(page, groups_query, per_page) + return { - "groups": response['items'], - "max_pages": response['max_pages'] + "groups": data['items'], + "max_pages": data['max_pages'] } @@ -36,29 +35,36 @@ def list_groups(query: dict) -> dict: @bp.output(MessageSchema) def create_group(data: dict) -> dict: name = data['name'] - students = data['students'] + students_indexes = data['students'] project_supervisor_id = data['project_supervisor_id'] - group = Group.query.filter_by(name=name).first() - if group is not None: - abort(400, "Group has already exists!") - - project_supervisor = ProjectSupervisor.query.filter_by(id=project_supervisor_id).first() - if project_supervisor is None: - abort(404, f"Student with id {project_supervisor_id} doesn't exist!") - - group = Group(name=name, project_supervisor_id = data['project_supervisor_id']) + # can assign a new group to project_supervisor + result = db.session.query(ProjectSupervisor.count_groups - db.func.count(ProjectSupervisor.id)). \ + join(Group).filter(ProjectSupervisor.id == project_supervisor_id). \ + group_by(ProjectSupervisor.id).scalar() + if result is None: + abort(400, "Project Supervisor doesnt exist") + elif result <= 0: + abort(400, "Can't create new group, project supervisor achieved a limit of groups") + + group = Group(name=name, project_supervisor_id=project_supervisor_id) + + students_without_groups = db.session.query(Student).join(Group, isouter=True) \ + .filter(Group.id.is_(None)).filter(Student.index.in_(students_indexes)).count() + + if students_without_groups != len(students_indexes): + abort(400, "One or more students have already belonged to group!") + + students = db.session.query(Student).filter(Student.index.in_(students_indexes)).all() for student in students: - st = Student.query(index = student.index).first() - if st is None: - abort(404, 'Not found student!') - st.group_id = group.id - + student.group_id = group.id + + db.session.add_all(students) db.session.add(group) db.session.commit() - return {"message": "Student was created!"} + return {"message": "Project Supervisor was created!"} @bp.route("//", methods=["GET"]) @@ -87,13 +93,13 @@ def delete_group(id: int) -> dict: def edit_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 = group_query.first() if group is None: abort(400, f"Group with id {id} doesn't exist!") - - group_query.update(group) + + group_query.update(data) db.session.commit() return {"message": "Group was updated!"} diff --git a/backend/app/coordinator/routes/project_supervisor.py b/backend/app/coordinator/routes/project_supervisor.py index c8bd0a2..ddeeda7 100644 --- a/backend/app/coordinator/routes/project_supervisor.py +++ b/backend/app/coordinator/routes/project_supervisor.py @@ -3,9 +3,10 @@ from apiflask import APIBlueprint from flask_sqlalchemy import get_debug_queries from ...project_supervisor.models import ProjectSupervisor +from ...students.models import Group from ..schemas import ProjectSupervisorSchema, ProjectSupervisorEditSchema, ProjectSupervisorsPaginationSchema, \ - ProjectSupervisorCreateSchema, MessageSchema, FileSchema, ProjectSupervisorQuerySchema -from ...dependencies import db, ma + ProjectSupervisorCreateSchema, MessageSchema, ProjectSupervisorQuerySchema +from ...dependencies import db from ...base.utils import paginate_models bp = APIBlueprint("project_supervisor", __name__, url_prefix="/project_supervisor") @@ -25,13 +26,11 @@ def list_project_supervisors(query: dict) -> dict: 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) - response = paginate_models(page, project_supervisor_query, per_page) - if (message := response.get('message')) is not None: - abort(response['status_code'], message) + data = paginate_models(page, project_supervisor_query, per_page) # print(get_debug_queries()[0]) return { - "project_supervisors": response['items'], - "max_pages": response['max_pages'] + "project_supervisors": data['items'], + "max_pages": data['max_pages'] } @@ -41,18 +40,11 @@ def list_project_supervisors(query: dict) -> dict: def create_project_supervisor(data: dict) -> dict: first_name = data['first_name'] last_name = data['last_name'] - email = data['email'] - limit_group = data['limit_group'] - mode = data['mode'] 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(first_name=first_name, - last_name = last_name, - email = email, - limit_group = limit_group, - mode = mode) + project_supervisor = ProjectSupervisor(**data) db.session.add(project_supervisor) db.session.commit() @@ -75,8 +67,10 @@ 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!") - - if project_supervisor.count_groups > 0: + + count_groups = db.session.query(db.func.count(ProjectSupervisor.id)).join(Group).\ + filter(ProjectSupervisor.id == id).group_by(ProjectSupervisor.id).scalar() + if count_groups > 0: abort(400, f"Project Supervisor with id {id} has gropus!") db.session.delete(project_supervisor) @@ -97,6 +91,6 @@ def edit_project_supervisor(id: int, data: dict) -> dict: if project_supervisor is None: abort(400, f"Project Supervisor with id {id} doesn't exist!") - project_supervisor_query.update(project_supervisor) + project_supervisor_query.update(data) db.session.commit() return {"message": "Project Supervisor was updated!"} diff --git a/backend/app/coordinator/routes/students.py b/backend/app/coordinator/routes/students.py index 74c19fc..7546445 100644 --- a/backend/app/coordinator/routes/students.py +++ b/backend/app/coordinator/routes/students.py @@ -33,13 +33,11 @@ def list_students(query: dict) -> dict: 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) - response = paginate_models(page, student_query, per_page) - if (message := response.get('message')) is not None: - abort(response['status_code'], message) + data = paginate_models(page, student_query, per_page) # print(get_debug_queries()[0]) return { - "students": response['items'], - "max_pages": response['max_pages'] + "students": data['items'], + "max_pages": data['max_pages'] } diff --git a/backend/app/coordinator/schemas.py b/backend/app/coordinator/schemas.py index 419cbc9..c507625 100644 --- a/backend/app/coordinator/schemas.py +++ b/backend/app/coordinator/schemas.py @@ -3,6 +3,7 @@ from ..students.models import Student, Group from ..project_supervisor.models import ProjectSupervisor from marshmallow import fields, validate, ValidationError + def validate_index(index): if len(str(index)) > 6: raise ValidationError("Length of index is too long!") @@ -18,7 +19,7 @@ class ProjectSupervisorSchema(ma.SQLAlchemyAutoSchema): class GroupSchema(ma.SQLAlchemyAutoSchema): project_supervisor = fields.Nested(ProjectSupervisorSchema) - + class Meta: model = Group @@ -81,8 +82,8 @@ class GroupsPaginationSchema(ma.Schema): class GroupCreateSchema(ma.Schema): name = fields.Str(validate=validate.Length(min=1, max=255), required=True) - project_supervisor_id = fields.Integer(validate=validate_index, required=True) - students = fields.List(fields.Nested(StudentSchema), 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)) class GroupEditSchema(ma.Schema): diff --git a/backend/app/project_supervisor/models.py b/backend/app/project_supervisor/models.py index 6d296a6..5f0bd6b 100644 --- a/backend/app/project_supervisor/models.py +++ b/backend/app/project_supervisor/models.py @@ -6,6 +6,7 @@ from sqlalchemy import or_ from sqlalchemy.sql import text from ..base.utils import order_by_column_name + class ProjectSupervisor(Base, Person): __tablename__ = "project_supervisors" @@ -21,16 +22,15 @@ class ProjectSupervisor(Base, Person): project_supervisors_query = cls.query if mode is not None: - project_supervisors_query = project_supervisors_query.filter(mode != 1-mode) + project_supervisors_query = project_supervisors_query.filter(mode != 1 - mode) if fullname is not None: - """This works only for sqlite3 database - concat function doesn't exist so i used builtin concat - operator specific only for sqlite db - || """ project_supervisors_query = project_supervisors_query.filter( - text("project_supervisorss_first_name || ' ' || project_supervisorss_last_name LIKE :fullname ") - ).params(fullname=f'{fullname}%') + (ProjectSupervisor.first_name + ' ' + ProjectSupervisor.last_name).like(f'{fullname}%')) - project_supervisors_query = order_by_column_name(project_supervisors_query, ProjectSupervisor.first_name, order_by_first_name) - project_supervisors_query = order_by_column_name(project_supervisors_query, ProjectSupervisor.last_name, order_by_last_name) + project_supervisors_query = order_by_column_name(project_supervisors_query, ProjectSupervisor.first_name, + order_by_first_name) + project_supervisors_query = order_by_column_name(project_supervisors_query, ProjectSupervisor.last_name, + order_by_last_name) return project_supervisors_query diff --git a/backend/app/students/models.py b/backend/app/students/models.py index d41dd3d..4bfeef3 100644 --- a/backend/app/students/models.py +++ b/backend/app/students/models.py @@ -23,15 +23,11 @@ class Group(Base): group_query = cls.query if search_name is not None: - group_query = group_query.filter( - text("groups_name LIKE :search_name ") - ).params(search_name=f'{search_name}%') - + group_query = group_query.filter(Group.name.like(f'{search_name}%')) + return group_query - - class Student(Person): __tablename__ = "students" @@ -52,11 +48,7 @@ class Student(Person): student_query = student_query.filter_by(mode=mode) if fullname is not None: - """This works only for sqlite3 database - concat function doesn't exist so i used builtin concat - operator specific only for sqlite db - || """ - student_query = student_query.filter( - text("students_first_name || ' ' || students_last_name LIKE :fullname ") - ).params(fullname=f'{fullname}%') + student_query = student_query.filter((Student.first_name + ' ' + Student.last_name).like(f'{fullname}%')) student_query = order_by_column_name(student_query, Student.first_name, order_by_first_name) student_query = order_by_column_name(student_query, Student.last_name, order_by_last_name) diff --git a/backend/app/students/routes/registrations.py b/backend/app/students/routes/registrations.py index 5ee0a39..84cd123 100644 --- a/backend/app/students/routes/registrations.py +++ b/backend/app/students/routes/registrations.py @@ -24,16 +24,14 @@ def list_available_groups(query: dict) -> dict: ps_query = ps_query.filter(ProjectSupervisor.mode == mode) ps_query = ps_query.group_by(Group.id) - response = paginate_models(page, ps_query, per_page) - if (message := response.get('message')) is not None: - abort(response['status_code'], message) + data = paginate_models(page, ps_query, per_page) project_supervisors = [] - for project_supervisor, available_groups in response['items']: + for project_supervisor, available_groups in data['items']: setattr(project_supervisor, 'available_groups', available_groups) project_supervisors.append(project_supervisor) return { "project_supervisors": project_supervisors, - "max_pages": response['max_pages'] + "max_pages": data['max_pages'] }