diff --git a/backend/Readme.md b/backend/Readme.md index a17a328..5710eb2 100644 --- a/backend/Readme.md +++ b/backend/Readme.md @@ -25,8 +25,24 @@ Run tests ```bash pytest ``` + +Run all tests in specific python module +```bash +pytest ./tests/unit_tests/test_file.py +``` + +Run one test inside of python module +```bash +pytest ./tests/unit_tests/test_file.py::test_function_name +``` *** +### Format code: +```bash +black path/to/file.py +isort path/to/file.py +flake8 +``` ### Useful commands: Add new package ```bash @@ -45,15 +61,19 @@ flask startapp NAME_OF_APP ``` Above command create package structure: \ -Create serializer in `__schemas__.py` file: -```python3 -class ExampleSchema(ma.SQLAlchemyAutoSchema): - class Meta: - model = MODEL_NAME +Create serializer in `schemas.py` file: +```python3 +from marshmallow import Schema, fields + +class ExampleSchema(Schema): + id = fields.Integer() + name = fields.Str() ``` \ -Create models in `__models__.py` file: +Create models in `models.py` file: ```python3 +from ..dependencies import db + class Example(db.Model): __tablename__ = "examples" @@ -61,7 +81,7 @@ class Example(db.Model): title = db.Column(db.String(255), unique=True) ``` \ -Create api routes in ```__routes__.py``` file. You only have to register blueprint in `app/__init__.py`. +Create api routes in ```routes.py``` file. You only have to register blueprint in `app/__init__.py`. ```python3 from flask import Blueprint, make_response, jsonify, Response diff --git a/backend/app/__init__.py b/backend/app/__init__.py index 2a3db48..6da4acf 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -1,26 +1,26 @@ import os from apiflask import APIFlask -from flask_migrate import Migrate from flask_cors import CORS +from flask_migrate import Migrate +from .api import api_bp +from .commands.clear_db import clear_db +from .commands.startapp import startapp from .config import config from .dependencies import db, ma -from .commands.startapp import startapp -from .commands.clear_db import clear_db -from .utils import import_models -from .api import api_bp from .errors import register_error_handlers +from .utils import import_models -def create_app(config_name: str = '') -> APIFlask: +def create_app(config_name: str = "") -> APIFlask: if config_name is None: config_name = os.environ.get("FLASK_ENV") - app = APIFlask(__name__, docs_path='/') + app = APIFlask(__name__, docs_path="/") app.config.from_object(config.get(config_name) or config.get("development")) - if app.config['ENABLE_CORS']: + if app.config["ENABLE_CORS"]: CORS(app) db.init_app(app) diff --git a/backend/app/api.py b/backend/app/api.py index 10c6e52..041604d 100644 --- a/backend/app/api.py +++ b/backend/app/api.py @@ -1,10 +1,11 @@ from flask import Blueprint + from .coordinator.routes import bp as coordinator_bp +from .examination_schedule.routes import bp as examination_schedules_bp from .project_supervisor.routes import bp as project_supervisor_bp from .students.routes import bp as students_bp -from .examination_schedule.routes import bp as examination_schedules_bp -api_bp = Blueprint('api', __name__, url_prefix='/api') +api_bp = Blueprint("api", __name__, url_prefix="/api") # register blueprints here api_bp.register_blueprint(coordinator_bp) diff --git a/backend/app/base/mode.py b/backend/app/base/mode.py index b1603b2..921a0b7 100644 --- a/backend/app/base/mode.py +++ b/backend/app/base/mode.py @@ -2,6 +2,21 @@ from enum import Enum class ModeGroups(str, Enum): - STATIONARY = 's' - NON_STATIONARY = 'n' - ENGLISH_SPEAKING_STATIONARY = 'e' + STATIONARY = "s" + NON_STATIONARY = "n" + ENGLISH_SPEAKING_STATIONARY = "e" + + +class EnrollmentsMode(str, Enum): + """ + What means specific values?: + INIT - project supervisor set your availability time for this examination + schedule, students cannot use examination schedule + OPEN - students can assign to term of defence, project supervisor cannot + use examination schedule + CLOSE - students and project supervisor cannot use examination schedule + """ + + INIT = "i" + OPEN = "o" + CLOSE = "c" diff --git a/backend/app/base/schemas.py b/backend/app/base/schemas.py index 15f7738..621a191 100644 --- a/backend/app/base/schemas.py +++ b/backend/app/base/schemas.py @@ -1,5 +1,5 @@ -from marshmallow import fields, Schema +from marshmallow import Schema, fields class MessageSchema(Schema): - message = fields.Str() \ No newline at end of file + message = fields.Str() diff --git a/backend/app/base/utils.py b/backend/app/base/utils.py index b280690..267aff3 100644 --- a/backend/app/base/utils.py +++ b/backend/app/base/utils.py @@ -10,11 +10,13 @@ class PaginationResponse(TypedDict): max_pages: int -def order_by_column_name(query: BaseQuery, model_field: str, order_by_col_name: Union[str, None]) -> BaseQuery: +def order_by_column_name( + query: BaseQuery, model_field: str, order_by_col_name: Union[str, None] +) -> BaseQuery: if order_by_col_name is not None: - if order_by_col_name == 'asc': + if order_by_col_name == "asc": query = query.order_by(model_field) - elif order_by_col_name == 'desc': + elif order_by_col_name == "desc": query = query.order_by(desc(model_field)) return query @@ -27,12 +29,12 @@ def paginate_models(page: int, query: BaseQuery, per_page=10) -> PaginationRespo else: query = query.paginate(page=default_page, per_page=per_page, error_out=False) - return { - 'items': query.items, - 'max_pages': query.pages - } + return {"items": query.items, "max_pages": query.pages} def is_allowed_extensions(filename: str): - return '.' in filename and \ - filename.rsplit('.', 1)[1].lower() in current_app.config['ALLOWED_EXTENSIONS'] + return ( + "." in filename + and filename.rsplit(".", 1)[1].lower() + in current_app.config["ALLOWED_EXTENSIONS"] + ) diff --git a/backend/app/commands/clear_db.py b/backend/app/commands/clear_db.py index 66cbd15..95835ce 100644 --- a/backend/app/commands/clear_db.py +++ b/backend/app/commands/clear_db.py @@ -1,10 +1,10 @@ -from flask.cli import with_appcontext from click import command +from flask.cli import with_appcontext from ..dependencies import db -@command('clear_db') +@command("clear_db") @with_appcontext def clear_db() -> None: """Clear database""" diff --git a/backend/app/commands/startapp.py b/backend/app/commands/startapp.py index fba829e..99cea42 100644 --- a/backend/app/commands/startapp.py +++ b/backend/app/commands/startapp.py @@ -1,9 +1,9 @@ import os import re -from flask import current_app -from click import command, argument +from click import argument, command from click.exceptions import ClickException +from flask import current_app from flask.cli import with_appcontext @@ -14,7 +14,7 @@ def startapp(name: str) -> None: """Create the application structure""" if not re.match("^[a-zA-Z].*$", name): - raise ClickException(f"The name argument must be type of string!") + raise ClickException("The name argument must be type of string!") app_dir = current_app.config["SRC_DIR"] / name if os.path.exists(app_dir): @@ -26,12 +26,14 @@ def startapp(name: str) -> None: Below you write a schema of model Example: +from marshmallow import Schema + class ExampleSchema(ma.SQLAlchemyAutoSchema): class Meta: model = MODEL_NAME \'\'\' -from ..dependencies import ma +from marshmallow import Schema """ model_content = """\'\'\' @@ -43,8 +45,7 @@ class Example(db.Model): id = db.Column(db.Integer, primary_key=True, autoincrement=True) title = db.Column(db.String(255), unique=True) - -\'\'\' +\'\'\' from ..dependencies import db """ diff --git a/backend/app/config.py b/backend/app/config.py index 1d00a21..2731ceb 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -8,11 +8,11 @@ class Config: BASE_DIR = Path(__file__).resolve().parent.parent SRC_DIR = BASE_DIR / "app" EXCLUDED_DIRS = ["__pycache__", "commands"] - TIMEZONE = 'Europe/Warsaw' + TIMEZONE = "Europe/Warsaw" - ENABLE_CORS = os.environ.get('ENABLE_CORS') or False + ENABLE_CORS = os.environ.get("ENABLE_CORS") or False - ALLOWED_EXTENSIONS = {'csv'} + ALLOWED_EXTENSIONS = {"csv"} MAX_CONTENT_LENGTH = 10 * 1024 * 1024 # 10 MB SQLALCHEMY_TRACK_MODIFICATIONS = False @@ -20,8 +20,8 @@ class Config: LIMIT_STUDENTS_PER_GROUP = 5 LIMIT_MEMBERS_PER_COMMITTEE = 3 - DESCRIPTION = 'System PRI' - OPENAPI_VERSION = '3.0.2' + DESCRIPTION = "System PRI" + OPENAPI_VERSION = "3.0.2" # Weights for project grade sheet PRESENTATION_WEIGHT_FIRST_TERM = 1.5 diff --git a/backend/app/coordinator/exceptions.py b/backend/app/coordinator/exceptions.py index a02e7ba..dc5cee3 100644 --- a/backend/app/coordinator/exceptions.py +++ b/backend/app/coordinator/exceptions.py @@ -1,8 +1,6 @@ class CSVException(Exception): """Main csv exception""" - pass class InvalidNameOrTypeHeaderException(CSVException): """Throw if csv file has invalid name or type of header""" - pass diff --git a/backend/app/coordinator/query/enrollments.py b/backend/app/coordinator/query/enrollments.py index 97ae875..4f02cbd 100644 --- a/backend/app/coordinator/query/enrollments.py +++ b/backend/app/coordinator/query/enrollments.py @@ -1,13 +1,23 @@ -from flask import abort +from datetime import datetime +from typing import List +from flask import abort +from sqlalchemy import and_, or_ + +from ...base.mode import EnrollmentsMode +from ...dependencies import db from ...examination_schedule.models import ExaminationSchedule, TermOfDefence +from ...project_supervisor.models import ProjectSupervisor from ...students.models import Group -def get_term_of_defence_by_id_and_examination_schedule_id(examination_schedule_id: int, - term_of_defence_id: int) -> ExaminationSchedule: - td = TermOfDefence.query.filter(TermOfDefence.id == term_of_defence_id, - TermOfDefence.examination_schedule_id == examination_schedule_id).first() +def get_term_of_defence_by_id_and_examination_schedule_id( + examination_schedule_id: int, term_of_defence_id: int +) -> ExaminationSchedule: + td = TermOfDefence.query.filter( + TermOfDefence.id == term_of_defence_id, + TermOfDefence.examination_schedule_id == examination_schedule_id, + ).first() if td is None: abort(404, "Not found examination schedule or term of defence!") return td @@ -27,9 +37,12 @@ def check_the_group_has_assigned_to_term_of_defence(group_id: int) -> TermOfDefe return td -def set_new_group_to_term_of_defence(examination_schedule_id: int, term_of_defence_id: int, - group_id: int) -> TermOfDefence: - td = get_term_of_defence_by_id_and_examination_schedule_id(examination_schedule_id, term_of_defence_id) +def set_new_group_to_term_of_defence( + examination_schedule_id: int, term_of_defence_id: int, group_id: int +) -> TermOfDefence: + td = get_term_of_defence_by_id_and_examination_schedule_id( + examination_schedule_id, term_of_defence_id + ) get_group_by_id(group_id) check_the_group_has_assigned_to_term_of_defence(group_id) td.group_id = group_id @@ -37,7 +50,95 @@ def set_new_group_to_term_of_defence(examination_schedule_id: int, term_of_defen def get_examination_schedule_by_id(examination_schedule_id: int) -> ExaminationSchedule: - ex = ExaminationSchedule.query.filter(ExaminationSchedule.id == examination_schedule_id).first() + ex = ExaminationSchedule.query.filter( + ExaminationSchedule.id == examination_schedule_id + ).first() if ex is None: abort(404, "Not found examination schedule!") return ex + + +def get_and_check_the_project_supervisors_exists_in_db( + year_group_id: int, project_supervisors_ids: List[int] +) -> List[ProjectSupervisor]: + project_supervisors = ( + ProjectSupervisor.query.filter( + or_(*[ProjectSupervisor.id == i for i in project_supervisors_ids]) + ) + .filter(ProjectSupervisor.year_group_id == year_group_id) + .all() + ) + + if len(project_supervisors) != len(project_supervisors_ids): + abort(400, "Project Supervisors didn't exist!") + + return project_supervisors + + +def validate_enrollments_date( + ex_start_date: datetime, ex_end_date: datetime, duration_time: int, data: dict +) -> None: + start_date = data.get("start_date") + end_date = data.get("end_date") + + if not ( + ex_start_date.timestamp() <= start_date.timestamp() + and ex_end_date.timestamp() >= end_date.timestamp() + ): + abort(400, "Invalid date range!") + + if end_date <= start_date: + abort(400, "End date must be greater than start date!") + + delta_time = end_date - start_date + delta_time_in_minutes = delta_time.total_seconds() / 60 + print(delta_time_in_minutes, duration_time, delta_time_in_minutes != duration_time) + if delta_time_in_minutes % duration_time != 0: + abort(400, "Invalid duration time!") + + +def check_the_term_of_defence_not_exists_in_chosen_date_range( + examination_schedule_id: int, data: dict +) -> None: + start_date = data.get("start_date") + end_date = data.get("end_date") + td = ( + TermOfDefence.query.filter( + TermOfDefence.examination_schedule_id == examination_schedule_id + ) + .filter( + or_( + and_( + TermOfDefence.start_date >= start_date, + TermOfDefence.start_date < end_date, + TermOfDefence.end_date >= end_date, + ), + and_( + TermOfDefence.start_date <= start_date, + TermOfDefence.end_date > start_date, + TermOfDefence.end_date <= end_date, + ), + ) + ) + .first() + ) + + if td is not None: + abort(400, "This term of defence is taken! You choose other date!") + + +def set_enrollments_mode( + examination_schedule_id: int, mode: EnrollmentsMode, action_name: str +) -> dict: + examination_schedule = ( + db.session.query(ExaminationSchedule) + .filter(ExaminationSchedule.id == examination_schedule_id) + .first() + ) + + if examination_schedule is None: + abort(404, "Not found examination schedule!") + + examination_schedule.open_enrollments = mode.value + db.session.commit() + return {"message": f"You {action_name} enrollments for this examination schedule!"} diff --git a/backend/app/coordinator/routes/__init__.py b/backend/app/coordinator/routes/__init__.py index 47a62ae..a3b6ff4 100644 --- a/backend/app/coordinator/routes/__init__.py +++ b/backend/app/coordinator/routes/__init__.py @@ -1,12 +1,12 @@ from flask import Blueprint -from .examination_schedule import bp as examination_schedule_bp from .enrollments import bp as enrollments_bp +from .examination_schedule import bp as examination_schedule_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 .year_group import bp as year_group_bp from .workloads import bp as workloads_bp +from .year_group import bp as year_group_bp bp = Blueprint("coordinator", __name__, url_prefix="/coordinator") diff --git a/backend/app/coordinator/routes/enrollments.py b/backend/app/coordinator/routes/enrollments.py index bcc935c..dcb84b2 100644 --- a/backend/app/coordinator/routes/enrollments.py +++ b/backend/app/coordinator/routes/enrollments.py @@ -1,68 +1,53 @@ import datetime from apiflask import APIBlueprint -from flask import abort, current_app -from sqlalchemy import or_, and_ -from flask_sqlalchemy import get_debug_queries +from flask import abort -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 ...dependencies import db +from ...examination_schedule.models import TemporaryAvailability, TermOfDefence +from ..query.enrollments import ( + check_the_term_of_defence_not_exists_in_chosen_date_range, + get_and_check_the_project_supervisors_exists_in_db, + get_examination_schedule_by_id, + get_term_of_defence_by_id_and_examination_schedule_id, + set_new_group_to_term_of_defence, + validate_enrollments_date, +) +from ..schemas.enrollments import ( + AssignedGroupToTermOfDefenceListSchema, + TemporaryAvailabilityListSchema, + TermOfDefenceSchema, +) +from ..schemas.groups import GroupIdSchema 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 bp = APIBlueprint("enrollments", __name__, url_prefix="/enrollments") -@bp.post('//add') +@bp.post("//add") @bp.input(TermOfDefenceSchema) @bp.output(MessageSchema) def create_term_of_defence(examination_schedule_id: int, data: dict) -> dict: - if not data: - abort(400, "You have passed empty data!") + chairman_of_committee_id = data.pop("chairman_of_committee") + project_supervisors_ids = data.pop("project_supervisors") + if chairman_of_committee_id not in project_supervisors_ids: + abort(400, "Invalid id of chairman committee!") ex = get_examination_schedule_by_id(examination_schedule_id) - yg_id = ex.year_group_id - project_supervisors_ids = data.pop('project_supervisors') - project_supervisors = ProjectSupervisor.query.filter( - or_(*[ProjectSupervisor.id == i for i in project_supervisors_ids])).filter(YearGroup.id == yg_id).all() - - if len(project_supervisors) != len(project_supervisors_ids): - abort(404, "Project Supervisors didn't exist!") - - start_date = data['start_date'] - end_date = data['end_date'] - - if not (ex.start_date.timestamp() <= start_date.timestamp() and ex.end_date.timestamp() >= end_date.timestamp()): - abort(400, "Invalid date range!") - - if end_date <= start_date: - abort(400, "End date must be greater than start date!") - - delta_time = end_date - start_date - delta_time_in_minutes = delta_time.total_seconds() / 60 - if delta_time_in_minutes != ex.duration_time: - abort(400, "Invalid duration time!") - - td = TermOfDefence.query.filter(TermOfDefence.examination_schedule_id == examination_schedule_id). \ - filter( - or_(and_(TermOfDefence.start_date >= start_date, - TermOfDefence.start_date < end_date, - TermOfDefence.end_date >= end_date), - and_(TermOfDefence.start_date <= start_date, - TermOfDefence.end_date > start_date, - TermOfDefence.end_date <= end_date))).first() - - if td is not None: - abort(400, "This term of defence is taken! You choose other date!") - - td = TermOfDefence(**data, examination_schedule_id=examination_schedule_id) + project_supervisors = get_and_check_the_project_supervisors_exists_in_db( + ex.year_group_id, project_supervisors_ids + ) + validate_enrollments_date(ex.start_date, ex.end_date, ex.duration_time, data) + check_the_term_of_defence_not_exists_in_chosen_date_range( + examination_schedule_id, data + ) + td = TermOfDefence( + **data, + examination_schedule_id=examination_schedule_id, + chairman_of_committee=chairman_of_committee_id + ) db.session.add(td) db.session.commit() for p in project_supervisors: @@ -71,110 +56,76 @@ def create_term_of_defence(examination_schedule_id: int, data: dict) -> dict: return {"message": "Term of defence was created!"} -@bp.post('//add-term-of-defences/') +@bp.post("//add-term-of-defences/") @bp.input(TermOfDefenceSchema) @bp.output(MessageSchema) def create_many_term_of_defences(examination_schedule_id: int, data: dict) -> dict: - if not data: - abort(400, "You have passed empty data!") + chairman_of_committee_id = data.pop("chairman_of_committee") + project_supervisors_ids = data.pop("project_supervisors") + if chairman_of_committee_id not in project_supervisors_ids: + abort(400, "Invalid id of chairman committee!") ex = get_examination_schedule_by_id(examination_schedule_id) - yg_id = ex.year_group_id - project_supervisors_ids = data.pop('project_supervisors') - project_supervisors = ProjectSupervisor.query.filter( - or_(*[ProjectSupervisor.id == i for i in project_supervisors_ids])).filter(YearGroup.id == yg_id).all() - - if len(project_supervisors) != len(project_supervisors_ids): - abort(404, "Project Supervisors didn't exist!") - - start_date = data['start_date'] - end_date = data['end_date'] - if not (ex.start_date.timestamp() <= start_date.timestamp() and ex.end_date.timestamp() >= end_date.timestamp()): - abort(400, "Invalid date range!") - - if end_date <= start_date: - abort(400, "End date must be greater than start date!") - - delta_time = end_date - start_date - delta_time_in_minutes = delta_time.total_seconds() / 60 - if delta_time_in_minutes % ex.duration_time != 0: - abort(400, "Invalid duration time!") - - td = TermOfDefence.query.filter(TermOfDefence.examination_schedule_id == examination_schedule_id). \ - filter( - or_(and_(TermOfDefence.start_date >= start_date, - TermOfDefence.start_date < end_date, - TermOfDefence.end_date >= end_date), - and_(TermOfDefence.start_date <= start_date, - TermOfDefence.end_date > start_date, - TermOfDefence.end_date <= end_date))).first() - - if td is not None: - abort(400, "This term of defence is taken! You choose other date!") + project_supervisors = get_and_check_the_project_supervisors_exists_in_db( + ex.year_group_id, project_supervisors_ids + ) + validate_enrollments_date(ex.start_date, ex.end_date, ex.duration_time, data) + check_the_term_of_defence_not_exists_in_chosen_date_range( + examination_schedule_id, data + ) # create many here + start_date = data.get("start_date") + end_date = data.get("end_date") dates = generate_range_dates(start_date, end_date, ex.duration_time) for start_date in dates: end_date = start_date + datetime.timedelta(minutes=ex.duration_time) - td = TermOfDefence(start_date=start_date, end_date=end_date, examination_schedule_id=examination_schedule_id) + td = TermOfDefence( + start_date=start_date, + end_date=end_date, + examination_schedule_id=examination_schedule_id, + chairman_of_committee=chairman_of_committee_id, + ) td.members_of_committee = project_supervisors db.session.add(td) db.session.commit() return {"message": "Term of defences was created!"} -@bp.put('//update//') +@bp.put("//update//") @bp.input(TermOfDefenceSchema) @bp.output(MessageSchema) -def update_term_of_defence(examination_schedule_id: int, term_of_defence_id: int, data: dict) -> dict: - if not data: - abort(400, "You have passed empty data!") +def update_term_of_defence( + examination_schedule_id: int, term_of_defence_id: int, data: dict +) -> dict: + chairman_of_committee_id = data.pop("chairman_of_committee") + project_supervisors_ids = data.pop("project_supervisors") + if chairman_of_committee_id not in project_supervisors_ids: + abort(400, "Invalid id of chairman committee!") - td_query = TermOfDefence.query.filter(TermOfDefence.id == term_of_defence_id, - TermOfDefence.examination_schedule_id == examination_schedule_id) + td_query = TermOfDefence.query.filter( + TermOfDefence.id == term_of_defence_id, + TermOfDefence.examination_schedule_id == examination_schedule_id, + ) td = td_query.first() if td is None: abort(404, "Not found term of defence!") - ex = td.examination_schedule - yg_id = ex.year_group_id - project_supervisors_ids = data.pop('project_supervisors') - project_supervisors = ProjectSupervisor.query.filter( - or_(*[ProjectSupervisor.id == i for i in project_supervisors_ids])).filter(YearGroup.id == yg_id).all() + ex = get_examination_schedule_by_id(examination_schedule_id) - if len(project_supervisors) != len(project_supervisors_ids): - abort(404, "Project Supervisors didn't exist!") - - start_date = data['start_date'] - end_date = data['end_date'] - if not (ex.start_date.timestamp() <= start_date.timestamp() and ex.end_date.timestamp() >= end_date.timestamp()): - abort(400, "Invalid date range!") - - if end_date <= start_date: - abort(400, "End date must be greater than start date!") - - delta_time = end_date - start_date - delta_time_in_minutes = delta_time.total_seconds() / 60 - if delta_time_in_minutes != ex.duration_time: - abort(400, "Invalid duration time!") - - term_of_defence = TermOfDefence.query.filter(TermOfDefence.id != term_of_defence_id, - TermOfDefence.examination_schedule_id == examination_schedule_id). \ - filter( - or_(and_(TermOfDefence.start_date >= start_date, - TermOfDefence.start_date < end_date, - TermOfDefence.end_date >= end_date), - and_(TermOfDefence.start_date <= start_date, - TermOfDefence.end_date > start_date, - TermOfDefence.end_date <= end_date))).first() - - if term_of_defence is not None: - abort(400, "This term of defence is taken! You choose other date!") + project_supervisors = get_and_check_the_project_supervisors_exists_in_db( + ex.year_group_id, project_supervisors_ids + ) + validate_enrollments_date(ex.start_date, ex.end_date, ex.duration_time, data) + check_the_term_of_defence_not_exists_in_chosen_date_range( + examination_schedule_id, data + ) td_query.update(data) td.members_of_committee = [] + td.chairman_of_committee = chairman_of_committee_id db.session.commit() for p in project_supervisors: td.members_of_committee.append(p) @@ -183,75 +134,102 @@ def update_term_of_defence(examination_schedule_id: int, term_of_defence_id: int return {"message": "Term of defence was updated!"} -@bp.delete('//delete//') +@bp.delete("//delete//") @bp.output(MessageSchema) -def delete_term_of_defence(examination_schedule_id: int, term_of_defence_id: int) -> dict: - td = get_term_of_defence_by_id_and_examination_schedule_id(examination_schedule_id, term_of_defence_id) +def delete_term_of_defence( + examination_schedule_id: int, term_of_defence_id: int +) -> dict: + td = get_term_of_defence_by_id_and_examination_schedule_id( + examination_schedule_id, term_of_defence_id + ) db.session.delete(td) db.session.commit() return {"message": "Term of defence was deleted!"} -@bp.get('//term-of-defences/') +@bp.get("//term-of-defences/") @bp.output(AssignedGroupToTermOfDefenceListSchema) def list_of_term_of_defences(examination_schedule_id: int) -> dict: get_examination_schedule_by_id(examination_schedule_id) - td = TermOfDefence.query. \ - join(TermOfDefence.members_of_committee, isouter=True). \ - filter(TermOfDefence.examination_schedule_id == examination_schedule_id). \ - all() + td = ( + TermOfDefence.query.join(TermOfDefence.members_of_committee, isouter=True) + .filter(TermOfDefence.examination_schedule_id == examination_schedule_id) + .all() + ) return {"term_of_defences": td} -@bp.get('//temporary-availabilities/') +@bp.get("//temporary-availabilities/") @bp.output(TemporaryAvailabilityListSchema) def list_of_temporary_availability(examination_schedule_id: int) -> dict: get_examination_schedule_by_id(examination_schedule_id) - td = TemporaryAvailability.query. \ - filter(TemporaryAvailability.examination_schedule_id == examination_schedule_id). \ - join(TemporaryAvailability.project_supervisor). \ - all() + td = ( + TemporaryAvailability.query.filter( + TemporaryAvailability.examination_schedule_id == examination_schedule_id + ) + .join(TemporaryAvailability.project_supervisor) + .all() + ) return {"temporary_availabilities": td} -@bp.get('//assigned-group-to-term-of-defences/') +@bp.get("//assigned-group-to-term-of-defences/") @bp.output(AssignedGroupToTermOfDefenceListSchema) def list_of_assigned_group_to_term_of_defences(examination_schedule_id: int) -> dict: get_examination_schedule_by_id(examination_schedule_id) - td = TermOfDefence.query. \ - join(TermOfDefence.members_of_committee, isouter=True). \ - join(TermOfDefence.group). \ - filter(TermOfDefence.examination_schedule_id == examination_schedule_id). \ - filter(TermOfDefence.group_id.isnot(None)). \ - all() + td = ( + TermOfDefence.query.join(TermOfDefence.members_of_committee, isouter=True) + .join(TermOfDefence.group) + .filter(TermOfDefence.examination_schedule_id == examination_schedule_id) + .filter(TermOfDefence.group_id.isnot(None)) + .all() + ) return {"term_of_defences": td} -@bp.post('//term-of-defence//group/') +@bp.post( + "//term-of-defence//group/" +) @bp.input(GroupIdSchema) @bp.output(MessageSchema) -def add_group_to_term_of_defence(examination_schedule_id: int, term_of_defence_id: int, data: dict) -> dict: - set_new_group_to_term_of_defence(examination_schedule_id, term_of_defence_id, data.get("group_id")) +def add_group_to_term_of_defence( + examination_schedule_id: int, term_of_defence_id: int, data: dict +) -> dict: + set_new_group_to_term_of_defence( + examination_schedule_id, term_of_defence_id, data.get("group_id") + ) db.session.commit() return {"message": "Group was added to term of defences!"} -@bp.delete('//term-of-defence//group/') +@bp.delete( + "//term-of-defence//group/" +) @bp.output(MessageSchema) -def delete_group_to_term_of_defence(examination_schedule_id: int, term_of_defence_id: int) -> dict: - td = get_term_of_defence_by_id_and_examination_schedule_id(examination_schedule_id, term_of_defence_id) +def delete_group_to_term_of_defence( + examination_schedule_id: int, term_of_defence_id: int +) -> dict: + td = get_term_of_defence_by_id_and_examination_schedule_id( + examination_schedule_id, term_of_defence_id + ) td.group_id = None db.session.commit() return {"message": "Group was deleted from term of defences!"} -@bp.put('//term-of-defence//group/') +@bp.put( + "//term-of-defence//group/" +) @bp.input(GroupIdSchema) @bp.output(MessageSchema) -def update_group_for_term_of_defence(examination_schedule_id: int, term_of_defence_id: int, data: dict) -> dict: - set_new_group_to_term_of_defence(examination_schedule_id, term_of_defence_id, data.get("group_id")) +def update_group_for_term_of_defence( + examination_schedule_id: int, term_of_defence_id: int, data: dict +) -> dict: + set_new_group_to_term_of_defence( + examination_schedule_id, term_of_defence_id, data.get("group_id") + ) db.session.commit() return {"message": "Group for term of defence was updated!"} diff --git a/backend/app/coordinator/routes/examination_schedule.py b/backend/app/coordinator/routes/examination_schedule.py index b4b9800..089ee1e 100644 --- a/backend/app/coordinator/routes/examination_schedule.py +++ b/backend/app/coordinator/routes/examination_schedule.py @@ -1,34 +1,40 @@ import datetime from apiflask import APIBlueprint -from flask import abort, Response, make_response, current_app +from flask import Response, abort, current_app, make_response +from ...base.mode import EnrollmentsMode +from ...base.schemas import MessageSchema from ...base.utils import paginate_models -from ...base.mode import ModeGroups 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, \ - ExaminationSchedulesQuerySchema, ExaminationSchedulesPaginationSchema -from ..utils import generate_examination_schedule_pdf_file -from ...base.schemas import MessageSchema +from ...students.models import Group, YearGroup +from ..query.enrollments import set_enrollments_mode +from ..schemas.examination_schedule import ( + ExaminationScheduleSchema, + ExaminationSchedulesPaginationSchema, + ExaminationSchedulesQuerySchema, +) +from ..utils import generate_examination_schedule_pdf_file, get_duration_time bp = APIBlueprint("examination_schedule", __name__, url_prefix="/examination_schedule") -@bp.get('//') -@bp.input(ExaminationSchedulesQuerySchema, location='query') +@bp.get("//") +@bp.input(ExaminationSchedulesQuerySchema, location="query") @bp.output(ExaminationSchedulesPaginationSchema) def list_examination_schedule(year_group_id: int, query: dict) -> dict: - page = query.get('page') - per_page = query.get('per_page') - es_query = ExaminationSchedule.query.filter(ExaminationSchedule.year_group_id == year_group_id) + page = query.get("page") + per_page = query.get("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']} + return {"examination_schedules": data["items"], "max_pages": data["max_pages"]} -@bp.post('//') +@bp.post("//") @bp.input(ExaminationScheduleSchema) @bp.output(MessageSchema, status_code=201) def create_examination_schedule(year_group_id: int, data: dict) -> dict: @@ -36,29 +42,28 @@ def create_examination_schedule(year_group_id: int, data: dict) -> dict: if yg is None: abort(404, "Year group doesn't exist!") - if data['start_date'] > data['end_date']: + if data.get("start_date") > data.get("end_date"): abort(400, "Invalid data! End date must be greater than start date!") - duration_time = None - if yg.mode == ModeGroups.NON_STATIONARY.value: - duration_time = 20 - elif yg.mode in [ModeGroups.STATIONARY.value, ModeGroups.ENGLISH_SPEAKING_STATIONARY.value]: - duration_time = 30 - + duration_time = get_duration_time(yg.mode) if duration_time is None: - abort(400, "Invalid mode of year group!") + abort(400, "Invalid duration time!") - examination_schedule = ExaminationSchedule(**data, year_group_id=year_group_id, duration_time=duration_time) + examination_schedule = ExaminationSchedule( + **data, year_group_id=year_group_id, duration_time=duration_time + ) db.session.add(examination_schedule) db.session.commit() return {"message": "Examination schedule was created!"} -@bp.put('//') +@bp.put("//") @bp.input(ExaminationScheduleSchema) @bp.output(MessageSchema) -def update_examination_schedule(id: int, data: dict) -> dict: - examination_schedule_query = db.session.query(ExaminationSchedule).filter(ExaminationSchedule.id == id) +def update_examination_schedule(examination_schedule_id: int, data: dict) -> dict: + examination_schedule_query = db.session.query(ExaminationSchedule).filter( + ExaminationSchedule.id == examination_schedule_id + ) examination_schedule = examination_schedule_query.first() if examination_schedule is None: @@ -68,10 +73,14 @@ def update_examination_schedule(id: int, data: dict) -> dict: return {"message": "Examination schedule was updated!"} -@bp.delete('//') +@bp.delete("//") @bp.output(MessageSchema) -def delete_examination_schedule(id: int) -> dict: - examination_schedule = db.session.query(ExaminationSchedule).filter(ExaminationSchedule.id == id).first() +def delete_examination_schedule(examination_schedule_id: int) -> dict: + examination_schedule = ( + db.session.query(ExaminationSchedule) + .filter(ExaminationSchedule.id == examination_schedule_id) + .first() + ) if examination_schedule is None: abort(404, "Examination schedule doesn't exist!") db.session.delete(examination_schedule) @@ -79,52 +88,56 @@ def delete_examination_schedule(id: int) -> dict: return {"message": "Examination schedule was deleted!"} -@bp.put('//date/') -@bp.input(ExaminationScheduleUpdateSchema) +@bp.put("//open-enrollments/") @bp.output(MessageSchema) -def set_date_of_examination_schedule(id: int, data: dict) -> dict: - examination_schedule_query = db.session.query(ExaminationSchedule).filter(ExaminationSchedule.id == id) - examination_schedule = examination_schedule_query.first() - - if examination_schedule is None: - abort(404, "Examination schedule doesn't exist!") - - 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) - db.session.commit() - return {"message": "You set date of examination schedule!"} +def open_enrollments(examination_schedule_id: int) -> dict: + return set_enrollments_mode(examination_schedule_id, EnrollmentsMode.OPEN, "open") -@bp.post('//download/') +@bp.put("//close-enrollments/") +@bp.output(MessageSchema) +def close_enrollments(examination_schedule_id: int) -> dict: + return set_enrollments_mode(examination_schedule_id, EnrollmentsMode.CLOSE, "close") + + +@bp.post("//download/") def download_examination_schedule(examination_schedule_id: int) -> Response: - examination_schedule = db.session.query(ExaminationSchedule). \ - filter(ExaminationSchedule.id == examination_schedule_id).first() + examination_schedule = ( + db.session.query(ExaminationSchedule) + .filter(ExaminationSchedule.id == examination_schedule_id) + .first() + ) if examination_schedule is None: abort(404, "Examination schedule doesn't exist!") - distinct_dates = db.session.query(db.func.Date(TermOfDefence.start_date)).distinct().all() + distinct_dates = ( + db.session.query(db.func.Date(TermOfDefence.start_date)).distinct().all() + ) nested_term_of_defences = [] for d in distinct_dates: date_tmp = datetime.datetime.strptime(d[0], "%Y-%m-%d").date() - term_of_defences = db.session.query(TermOfDefence). \ - join(Group, isouter=True).join(ProjectSupervisor, isouter=True). \ - filter(TermOfDefence.examination_schedule_id == examination_schedule_id). \ - filter(TermOfDefence.group_id.isnot(None)). \ - filter(db.func.Date(TermOfDefence.start_date) == date_tmp). \ - all() + term_of_defences = ( + db.session.query(TermOfDefence) + .join(Group, isouter=True) + .join(ProjectSupervisor, isouter=True) + .filter(TermOfDefence.examination_schedule_id == examination_schedule_id) + .filter(TermOfDefence.group_id.isnot(None)) + .filter(db.func.Date(TermOfDefence.start_date) == date_tmp) + .all() + ) if len(term_of_defences) > 0: nested_term_of_defences.append(term_of_defences) # print(nested_term_of_defences) - base_dir = current_app.config.get('BASE_DIR') - pdf = generate_examination_schedule_pdf_file(examination_schedule.title, nested_term_of_defences, base_dir) + base_dir = current_app.config.get("BASE_DIR") + pdf = generate_examination_schedule_pdf_file( + examination_schedule.title, nested_term_of_defences, base_dir + ) title = examination_schedule.title.replace("-", "_").split() filename = "_".join(title) response = make_response(pdf) - response.headers['Content-Type'] = 'application/pdf' - response.headers['Content-Disposition'] = 'attachment; filename=%s.pdf' % filename + response.headers["Content-Type"] = "application/pdf" + response.headers["Content-Disposition"] = "attachment; filename=%s.pdf" % filename return response diff --git a/backend/app/coordinator/routes/groups.py b/backend/app/coordinator/routes/groups.py index 6e9b0d2..ed864f6 100644 --- a/backend/app/coordinator/routes/groups.py +++ b/backend/app/coordinator/routes/groups.py @@ -1,61 +1,72 @@ -from flask import abort from apiflask import APIBlueprint -from flask_sqlalchemy import get_debug_queries +from flask import abort -from ...students.models import Group, Student, YearGroup, ProjectGradeSheet -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 ...base.utils import paginate_models +from ...dependencies import db +from ...project_supervisor.models import ProjectSupervisor +from ...students.models import Group, ProjectGradeSheet, Student, YearGroup +from ..schemas.groups import ( + GroupCreateSchema, + GroupEditSchema, + GroupQuerySchema, + GroupsPaginationSchema, +) +from ..schemas.students import DetailGroupSchema from ..utils import attach_points_for_first_and_second_term_to_group_models bp = APIBlueprint("groups", __name__, url_prefix="/groups") @bp.get("//") -@bp.input(GroupQuerySchema, location='query') +@bp.input(GroupQuerySchema, location="query") @bp.output(GroupsPaginationSchema) 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') + search_name = query.get("name") + page = query.get("page") + per_page = query.get("per_page") groups_query = Group.search_by_name(year_group_id, search_name) data = paginate_models(page, groups_query, per_page) - items = data['items'] + items = data["items"] attach_points_for_first_and_second_term_to_group_models(items) - return { - "groups": items, - "max_pages": data['max_pages'] - } + return {"groups": items, "max_pages": data["max_pages"]} @bp.post("//") @bp.input(GroupCreateSchema) @bp.output(MessageSchema, status_code=201) def create_group(year_group_id: int, data: dict) -> dict: - name = data['name'] - students_ids = data['students'] - project_supervisor_id = data['project_supervisor_id'] + name = data["name"] + students_ids = 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, "Not found year group!") - project_supervisor = ProjectSupervisor.query.filter_by(id=project_supervisor_id).first() + project_supervisor = ProjectSupervisor.query.filter_by( + id=project_supervisor_id + ).first() if project_supervisor is None: - abort(404, f"Not found project supervisor!") + abort(404, "Not found project supervisor!") - group = Group(name=name, project_supervisor_id=project_supervisor_id, year_group_id=year_group_id) + 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.id == st_id for st_id in students_ids])).all() + students_without_groups = ( + db.session.query(Student, Group) + .join(Group, Student.groups) + .filter(Group.year_group_id == year_group_id) + .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!") @@ -81,7 +92,7 @@ def create_group(year_group_id: int, data: dict) -> dict: 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!") + abort(404, "Not found group!") return group @@ -90,7 +101,7 @@ def detail_group(group_id: int) -> Group: 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!") + abort(404, "Not found group!") group.students = [] db.session.delete(group) @@ -103,29 +114,31 @@ def delete_group(group_id: int) -> dict: @bp.output(MessageSchema) def edit_group(group_id: int, data: dict) -> dict: if not data: - abort(400, 'You have passed empty data!') + abort(400, "You have passed empty data!") group_query = Group.query.filter_by(id=group_id) group = group_query.first() if group is None: - abort(404, f"Not found group!") + abort(404, "Not found group!") - students_ids = data.get('students') - name = data.get('name') - project_supervisor_id = data.get('project_supervisor_id') + students_ids = data.get("students") + name = data.get("name") + project_supervisor_id = data.get("project_supervisor_id") 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!') + abort(404, "Not found students!") group.students = students if name is not None: group.name = name if project_supervisor_id is not None: - ps = ProjectSupervisor.query.filter(ProjectSupervisor.id == project_supervisor_id).first() + ps = ProjectSupervisor.query.filter( + ProjectSupervisor.id == project_supervisor_id + ).first() if ps is None: abort(404, "Not found project supervisor!") group.project_supervisor_id = project_supervisor_id diff --git a/backend/app/coordinator/routes/project_supervisor.py b/backend/app/coordinator/routes/project_supervisor.py index 9861fbe..f837cc7 100644 --- a/backend/app/coordinator/routes/project_supervisor.py +++ b/backend/app/coordinator/routes/project_supervisor.py @@ -1,37 +1,40 @@ -from flask import abort from apiflask import APIBlueprint -from flask_sqlalchemy import get_debug_queries +from flask import abort +from ...base.schemas import MessageSchema +from ...base.utils import paginate_models +from ...dependencies import db from ...project_supervisor.models import ProjectSupervisor from ...students.models import Group, YearGroup -from ..schemas import ProjectSupervisorSchema, ProjectSupervisorEditSchema, ProjectSupervisorsPaginationSchema, \ - ProjectSupervisorCreateSchema, MessageWithIdSchema, ProjectSupervisorQuerySchema -from ...base.schemas import MessageSchema -from ...dependencies import db -from ...base.utils import paginate_models +from ..schemas.project_supervisor import ( + ProjectSupervisorCreateSchema, + ProjectSupervisorEditSchema, + ProjectSupervisorQuerySchema, + ProjectSupervisorSchema, + ProjectSupervisorsPaginationSchema, +) +from ..schemas.students import MessageWithIdSchema bp = APIBlueprint("project_supervisor", __name__, url_prefix="/project_supervisor") @bp.get("//") -@bp.input(ProjectSupervisorQuerySchema, location='query') +@bp.input(ProjectSupervisorQuerySchema, location="query") @bp.output(ProjectSupervisorsPaginationSchema) 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') - page = query.get('page') - per_page = query.get('per_page') + 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) + project_supervisor_query = ProjectSupervisor.search_by_fullname( + 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'] - } + return {"project_supervisors": data["items"], "max_pages": data["max_pages"]} @bp.post("//") @@ -42,8 +45,11 @@ def create_project_supervisor(year_group_id: int, data: dict) -> dict: if year_group is None: abort(404, "Not found year group!") - email = data['email'] - project_supervisor = ProjectSupervisor.query.filter(ProjectSupervisor.email == email).first() + email = data["email"] + project_supervisor = ProjectSupervisor.query.filter( + ProjectSupervisor.email == email, + ProjectSupervisor.year_group_id == year_group_id, + ).first() if project_supervisor is not None: abort(400, "Project Supervisor has already exists!") @@ -57,20 +63,26 @@ def create_project_supervisor(year_group_id: int, data: dict) -> dict: @bp.get("//detail/") @bp.output(ProjectSupervisorSchema) def detail_project_supervisor(project_supervisor_id: int) -> ProjectSupervisor: - project_supervisor = ProjectSupervisor.query.filter_by(id=project_supervisor_id).first() + project_supervisor = ProjectSupervisor.query.filter_by( + id=project_supervisor_id + ).first() if project_supervisor is None: - abort(404, 'Not found project supervisor!') + abort(404, "Not found project supervisor!") return project_supervisor @bp.delete("//") @bp.output(MessageSchema) def delete_project_supervisor(project_supervisor_id: int) -> dict: - project_supervisor = ProjectSupervisor.query.filter_by(id=project_supervisor_id).first() + project_supervisor = ProjectSupervisor.query.filter_by( + id=project_supervisor_id + ).first() if project_supervisor is None: abort(404, "Not found project supervisor!") - count_groups = len(Group.query.filter(Group.project_supervisor_id == project_supervisor.id).all()) + 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!") @@ -84,13 +96,15 @@ def delete_project_supervisor(project_supervisor_id: int) -> dict: @bp.output(MessageSchema) def edit_project_supervisor(project_supervisor_id: int, data: dict) -> dict: if not data: - abort(400, 'You have passed empty data!') + abort(400, "You have passed empty data!") - project_supervisor_query = ProjectSupervisor.query.filter_by(id=project_supervisor_id) + project_supervisor_query = ProjectSupervisor.query.filter_by( + id=project_supervisor_id + ) project_supervisor = project_supervisor_query.first() if project_supervisor is None: - abort(404, f"Not found project supervisor!") + abort(404, "Not found project supervisor!") project_supervisor_query.update(data) db.session.commit() @@ -104,19 +118,34 @@ def copy_project_supervisors_from_last_year_group(year_group_id: int) -> dict: if year_group is None: abort(404, "Not found 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() + 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!") - 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()] + 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) + 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() diff --git a/backend/app/coordinator/routes/students.py b/backend/app/coordinator/routes/students.py index 042775f..f968052 100644 --- a/backend/app/coordinator/routes/students.py +++ b/backend/app/coordinator/routes/students.py @@ -1,44 +1,51 @@ -from random import randint from itertools import islice +from random import randint -from flask import Response, abort from apiflask import APIBlueprint +from flask import Response, abort from sqlalchemy import or_ -from flask_sqlalchemy import get_debug_queries -from ...students.models import Student, Group, YearGroup -from ...project_supervisor.models import ProjectSupervisor -from ..schemas import StudentSchema, StudentEditSchema, StudentsPaginationSchema, YearGroupInfoQuery, \ - 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 +from ...base.utils import is_allowed_extensions, paginate_models +from ...dependencies import db +from ...project_supervisor.models import ProjectSupervisor +from ...students.models import Group, Student, YearGroup +from ..exceptions import InvalidNameOrTypeHeaderException +from ..schemas.students import ( + FileSchema, + StudentCreateSchema, + StudentEditSchema, + StudentListFileDownloaderSchema, + StudentQuerySchema, + StudentSchema, + StudentsPaginationSchema, + YearGroupInfoQuery, +) +from ..utils import generate_csv, parse_csv bp = APIBlueprint("students", __name__, url_prefix="/students") @bp.get("//") -@bp.input(StudentQuerySchema, location='query') +@bp.input(StudentQuerySchema, location="query") @bp.output(StudentsPaginationSchema) 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') - page = query.get('page') - per_page = query.get('per_page') + 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") - student_query = Student.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) + student_query = ( + Student.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, student_query, per_page) # print(get_debug_queries()[0]) - return { - "students": data['items'], - "max_pages": data['max_pages'] - } + return {"students": data["items"], "max_pages": data["max_pages"]} @bp.get("//detail/") @@ -66,13 +73,13 @@ def delete_student(student_id: int) -> dict: @bp.output(MessageSchema) def edit_student(student_id: int, data: dict) -> dict: if not data: - abort(400, 'You have passed empty data!') + abort(400, "You have passed empty data!") - student_query = Student.query.filter(Student.id==student_id) + student_query = Student.query.filter(Student.id == student_id) student = student_query.first() if student is None: - abort(404, 'Not found student!') + abort(404, "Not found student!") student_query.update(data) db.session.commit() @@ -84,20 +91,21 @@ def edit_student(student_id: int, data: dict) -> dict: @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'] + index = data["index"] + yg_id = data["year_group_id"] + del data["year_group_id"] - student = Student.query.filter(Student.index == index, Student.year_group_id == yg_id).first() + 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!") - dummy_email = f'student{randint(1, 300_000)}@gmail.com' + 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() @@ -106,18 +114,18 @@ def create_student(data: dict) -> dict: @bp.post("/upload/") -@bp.input(YearGroupInfoQuery, location='query') -@bp.input(FileSchema, location='form_and_files') +@bp.input(YearGroupInfoQuery, location="query") +@bp.input(FileSchema, location="form_and_files") @bp.output(MessageSchema) 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('year_group_id') + """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("year_group_id") yg = YearGroup.query.filter(YearGroup.id == year_group_id).first() if yg is None: abort(404, "Not found year group!") - uploaded_file = file.get('file') + uploaded_file = file.get("file") if uploaded_file and is_allowed_extensions(uploaded_file.filename): try: students = parse_csv(uploaded_file, year_group_id) @@ -128,10 +136,19 @@ 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)).\ - filter(Student.year_group_id==year_group_id).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] - students_not_exists_in_db = list(filter(lambda s: s.index not in student_index_in_db, list_of_students)) + 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() @@ -144,17 +161,23 @@ def upload_students(query: dict, file: dict) -> dict: @bp.post("/download/") -@bp.input(StudentListFileDownloaderSchema, location='query') +@bp.input(StudentListFileDownloaderSchema, location="query") def download_students(query: dict) -> Response: - year_group_id = query.get('year_group_id') - students_and_groups = db.session.query(Student, Group).join(Group, Student.groups). \ - filter(Group.year_group_id == year_group_id). \ - join(ProjectSupervisor).all() + year_group_id = query.get("year_group_id") + students_and_groups = ( + db.session.query(Student, Group) + .join(Group, Student.groups) + .filter(Group.year_group_id == year_group_id) + .join(ProjectSupervisor) + .all() + ) if len(students_and_groups) == 0: abort(404, "Not found students!") csv_file = generate_csv(students_and_groups) - response = Response(csv_file, mimetype='text/csv') - response.headers.set("Content-Disposition", "attachment", filename="students_list.csv") + response = Response(csv_file, mimetype="text/csv") + response.headers.set( + "Content-Disposition", "attachment", filename="students_list.csv" + ) return response diff --git a/backend/app/coordinator/routes/workloads.py b/backend/app/coordinator/routes/workloads.py index 1c02824..ae55c89 100644 --- a/backend/app/coordinator/routes/workloads.py +++ b/backend/app/coordinator/routes/workloads.py @@ -1,32 +1,38 @@ from apiflask import APIBlueprint from flask import abort -from flask_sqlalchemy import get_debug_queries from ...dependencies import db from ...examination_schedule.models import ExaminationSchedule, TermOfDefence from ...project_supervisor.models import ProjectSupervisor -from ..schemas import WorkloadSchema +from ..schemas.examination_schedule import WorkloadSchema bp = APIBlueprint("workloads", __name__, url_prefix="/") -@bp.get('/examination_schedule//workloads/') +@bp.get("/examination_schedule//workloads/") @bp.output(WorkloadSchema) def workloads_statistics(examination_schedule_id: int) -> dict: es = ExaminationSchedule.query.filter_by(id=examination_schedule_id).first() if es is None: abort(404, "Not found examination schedule!") - statistics = db.session.query( - ProjectSupervisor.first_name + " " + ProjectSupervisor.last_name, - db.func.count(TermOfDefence.group_id), - db.func.count(TermOfDefence.id), - ).join(TermOfDefence.members_of_committee). \ - group_by(ProjectSupervisor.id).all() + statistics = ( + db.session.query( + ProjectSupervisor.first_name + " " + ProjectSupervisor.last_name, + db.func.count(TermOfDefence.group_id), + db.func.count(TermOfDefence.id), + ) + .join(TermOfDefence.members_of_committee) + .group_by(ProjectSupervisor.id) + .all() + ) - # print(statistics) - # print(len(statistics)) - # print(get_debug_queries()) - workloads = ({"full_name": s[0], "groups_assigned_to_his_committee": s[1], - "assigned_to_committee": s[2]} for s in statistics) - return {'workloads': workloads} + workloads = ( + { + "full_name": s[0], + "groups_assigned_to_his_committee": s[1], + "assigned_to_committee": s[2], + } + for s in statistics + ) + return {"workloads": workloads} diff --git a/backend/app/coordinator/routes/year_group.py b/backend/app/coordinator/routes/year_group.py index ff8a2ca..ea495c3 100644 --- a/backend/app/coordinator/routes/year_group.py +++ b/backend/app/coordinator/routes/year_group.py @@ -1,22 +1,28 @@ -from flask import abort from apiflask import APIBlueprint +from flask import abort -from ...students.models import YearGroup -from ..schemas import YearGroupSchema, YearGroupPaginationSchema, YearGroupQuerySchema -from ...dependencies import db -from ...base.utils import paginate_models from ...base.schemas import MessageSchema +from ...base.utils import paginate_models +from ...dependencies import db +from ...students.models import YearGroup +from ..schemas.year_group import ( + YearGroupPaginationSchema, + YearGroupQuerySchema, + YearGroupSchema, +) bp = APIBlueprint("year_group", __name__, url_prefix="/year-group") -@bp.post('/') +@bp.post("/") @bp.input(YearGroupSchema) @bp.output(MessageSchema, status_code=201) def create_year_group(data: dict) -> dict: - name = data['name'] - mode = data['mode'] - year_group = YearGroup.query.filter(YearGroup.name == name, YearGroup.mode == mode).first() + name = data["name"] + mode = data["mode"] + year_group = YearGroup.query.filter( + YearGroup.name == name, YearGroup.mode == mode + ).first() if year_group is not None: abort(400, "Year group has already exists!") @@ -27,38 +33,37 @@ def create_year_group(data: dict) -> dict: return {"message": "Year group was created!"} -@bp.get('/') -@bp.input(YearGroupQuerySchema, location='query') +@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') + 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'] - } + return {"year_groups": data["items"], "max_pages": data["max_pages"]} -@bp.put('//') +@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!') + 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!') + abort(404, "Not found year group!") - name = data['name'] - mode = data['mode'] - year_group = YearGroup.query.filter(YearGroup.name == name, YearGroup.mode == mode, YearGroup.id != id).first() + name = data["name"] + mode = data["mode"] + year_group = YearGroup.query.filter( + YearGroup.name == name, YearGroup.mode == mode, YearGroup.id != id + ).first() if year_group is not None: abort(400, "Year group has already exists!") @@ -68,12 +73,12 @@ def update_year_of_group(id: int, data: dict) -> dict: return {"message": "Year group was updated!"} -@bp.delete('//') +@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!") + abort(404, "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 bc56a80..e69de29 100644 --- a/backend/app/coordinator/schemas/__init__.py +++ b/backend/app/coordinator/schemas/__init__.py @@ -1,11 +0,0 @@ -from .enrollments import TermOfDefenceSchema, TermOfDefenceListSchema, TemporaryAvailabilityListSchema, \ - AssignedGroupToTermOfDefenceListSchema -from .examination_schedule import ExaminationScheduleSchema, ExaminationScheduleUpdateSchema, \ - ExaminationSchedulesPaginationSchema, ExaminationSchedulesQuerySchema, WorkloadSchema -from .groups import GroupQuerySchema, GroupsPaginationSchema, GroupCreateSchema, GroupEditSchema, GroupIdSchema -from .project_supervisor import ProjectSupervisorQuerySchema, ProjectSupervisorsPaginationSchema, \ - ProjectSupervisorCreateSchema, ProjectSupervisorEditSchema -from .students import ProjectSupervisorSchema, GroupSchema, StudentSchema, StudentsPaginationSchema, \ - StudentListFileDownloaderSchema, StudentCreateSchema, StudentEditSchema, MessageWithIdSchema, FileSchema, \ - StudentQuerySchema, YearGroupInfoQuery, DetailGroupSchema -from .year_group import YearGroupSchema, YearGroupPaginationSchema, YearGroupQuerySchema diff --git a/backend/app/coordinator/schemas/enrollments.py b/backend/app/coordinator/schemas/enrollments.py index 09390b3..df9d29a 100644 --- a/backend/app/coordinator/schemas/enrollments.py +++ b/backend/app/coordinator/schemas/enrollments.py @@ -4,9 +4,16 @@ from ..validators import validate_datetime_greater_than_now class TermOfDefenceSchema(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) - project_supervisors = fields.List(fields.Integer(required=True), validate=validate.Length(3, 3)) + start_date = fields.DateTime( + validate=validate_datetime_greater_than_now, required=True + ) + end_date = fields.DateTime( + validate=validate_datetime_greater_than_now, required=True + ) + project_supervisors = fields.List( + fields.Integer(required=True), validate=validate.Length(3, 3) + ) + chairman_of_committee = fields.Integer(required=True) class ProjectSupervisorForTermOfDefenceSchema(Schema): @@ -19,30 +26,35 @@ class TermOfDefenceItemSchema(Schema): id = fields.Integer() start_date = fields.DateTime() end_date = fields.DateTime() - members_of_committee = fields.List(fields.Nested(ProjectSupervisorForTermOfDefenceSchema)) + members_of_committee = fields.List( + fields.Nested(ProjectSupervisorForTermOfDefenceSchema) + ) + chairman_of_committee = fields.Integer() class TermOfDefenceListSchema(Schema): term_of_defences = fields.List(fields.Nested(TermOfDefenceItemSchema)) -class StudentDataItemSchema(Schema): +class StudentDataItemAssignedGroupSchema(Schema): index = fields.Integer() first_name = fields.Str() last_name = fields.Str() -class GroupDataItemSchema(Schema): +class GroupDataItemAssignedGroupSchema(Schema): name = fields.Str() - students = fields.List(fields.Nested(StudentDataItemSchema)) + students = fields.List(fields.Nested(StudentDataItemAssignedGroupSchema)) -class AssignedGroupToTermOfDefenceItemSchema(TermOfDefenceItemSchema): - group = fields.Nested(GroupDataItemSchema) +class AssignedGroupToTermOfDefenceDataItemSchema(TermOfDefenceItemSchema): + group = fields.Nested(GroupDataItemAssignedGroupSchema) class AssignedGroupToTermOfDefenceListSchema(Schema): - term_of_defences = fields.List(fields.Nested(AssignedGroupToTermOfDefenceItemSchema)) + term_of_defences = fields.List( + fields.Nested(AssignedGroupToTermOfDefenceDataItemSchema) + ) class ProjectSupervisorForTemporaryAvailabilitySchema(Schema): @@ -58,4 +70,6 @@ class TemporaryAvailabilityItemSchema(Schema): class TemporaryAvailabilityListSchema(Schema): - temporary_availabilities = fields.List(fields.Nested(TemporaryAvailabilityItemSchema)) + temporary_availabilities = fields.List( + fields.Nested(TemporaryAvailabilityItemSchema) + ) diff --git a/backend/app/coordinator/schemas/examination_schedule.py b/backend/app/coordinator/schemas/examination_schedule.py index 62fb2b4..6914a99 100644 --- a/backend/app/coordinator/schemas/examination_schedule.py +++ b/backend/app/coordinator/schemas/examination_schedule.py @@ -1,30 +1,30 @@ -from marshmallow import fields, validate, Schema +from marshmallow import Schema, fields, validate from ..validators import validate_datetime_greater_than_now class ExaminationScheduleSchema(Schema): title = fields.Str(validate=validate.Length(min=1, max=100), 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_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) + 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 ExaminationScheduleListItemSchema(Schema): id = fields.Integer() title = fields.Str() + open_enrollments = fields.String() 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): - examination_schedules = fields.List(fields.Nested(ExaminationScheduleListItemSchema)) + examination_schedules = fields.List( + fields.Nested(ExaminationScheduleListItemSchema) + ) max_pages = fields.Integer() diff --git a/backend/app/coordinator/schemas/groups.py b/backend/app/coordinator/schemas/groups.py index ca00d9d..65d3588 100644 --- a/backend/app/coordinator/schemas/groups.py +++ b/backend/app/coordinator/schemas/groups.py @@ -1,6 +1,5 @@ from marshmallow import Schema, fields, validate -from ..validators import validate_index from .students import GroupSchema diff --git a/backend/app/coordinator/schemas/project_supervisor.py b/backend/app/coordinator/schemas/project_supervisor.py index 857bc87..1d9fb50 100644 --- a/backend/app/coordinator/schemas/project_supervisor.py +++ b/backend/app/coordinator/schemas/project_supervisor.py @@ -1,6 +1,12 @@ -from marshmallow import fields, validate, Schema +from marshmallow import Schema, fields, validate -from .students import ProjectSupervisorSchema + +class ProjectSupervisorSchema(Schema): + id = fields.Integer() + first_name = fields.Str() + last_name = fields.Str() + email = fields.Str() + limit_group = fields.Integer() class ProjectSupervisorQuerySchema(Schema): @@ -19,7 +25,9 @@ class ProjectSupervisorsPaginationSchema(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), validate.Email()], required=True) + email = fields.Str( + validate=[validate.Length(min=1, max=255), validate.Email()], required=True + ) limit_group = fields.Integer(required=True) @@ -27,4 +35,4 @@ class ProjectSupervisorEditSchema(Schema): 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 + limit_group = fields.Integer() diff --git a/backend/app/coordinator/schemas/students.py b/backend/app/coordinator/schemas/students.py index b8bfb84..a01993a 100644 --- a/backend/app/coordinator/schemas/students.py +++ b/backend/app/coordinator/schemas/students.py @@ -1,15 +1,9 @@ -from marshmallow import fields, validate, Schema +from marshmallow import Schema, fields, validate from ...dependencies import ma -from ...students.models import Student, Group -from ...project_supervisor.models import ProjectSupervisor +from ...students.models import Group, Student from ..validators import validate_index - - -class ProjectSupervisorSchema(ma.SQLAlchemyAutoSchema): - class Meta: - model = ProjectSupervisor - include_relationships = False +from .project_supervisor import ProjectSupervisorSchema class GroupSchema(ma.SQLAlchemyAutoSchema): @@ -45,7 +39,6 @@ class StudentCreateSchema(ma.Schema): class StudentEditSchema(ma.Schema): first_name = fields.Str(validate=validate.Length(min=1, max=255)) 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) @@ -55,7 +48,7 @@ class MessageWithIdSchema(ma.Schema): class FileSchema(ma.Schema): - file = fields.Raw(metadata={'type': 'file'}, required=True) + file = fields.Raw(metadata={"type": "file"}, required=True) class StudentQuerySchema(ma.Schema): @@ -69,8 +62,12 @@ class StudentQuerySchema(ma.Schema): class YearGroupInfoQuery(Schema): year_group_id = fields.Integer(required=True) + class DetailGroupSchema(ma.SQLAlchemyAutoSchema): project_supervisor = fields.Nested(ProjectSupervisorSchema) - students = fields.List(fields.Nested(StudentSchema), validate=validate.Length(min=1, max=255)) + students = fields.List( + fields.Nested(StudentSchema), validate=validate.Length(min=1, max=255) + ) + class Meta: - model = Group + model = Group diff --git a/backend/app/coordinator/schemas/year_group.py b/backend/app/coordinator/schemas/year_group.py index 4f1e2d0..0826bd4 100644 --- a/backend/app/coordinator/schemas/year_group.py +++ b/backend/app/coordinator/schemas/year_group.py @@ -1,4 +1,4 @@ -from marshmallow import Schema, fields, validate, ValidationError +from marshmallow import Schema, ValidationError, fields, validate from ...base.mode import ModeGroups @@ -10,7 +10,7 @@ def validate_mode(value: str) -> str: class YearGroupSchema(Schema): - name = fields.Str(validate=validate.Regexp(r'^\d{4}\/\d{4}$'), required=True) + name = fields.Str(validate=validate.Regexp(r"^\d{4}\/\d{4}$"), required=True) mode = fields.Str(validate=validate_mode, required=True) diff --git a/backend/app/coordinator/utils.py b/backend/app/coordinator/utils.py index 6eac3e7..28b306c 100644 --- a/backend/app/coordinator/utils.py +++ b/backend/app/coordinator/utils.py @@ -3,54 +3,85 @@ import json from collections import defaultdict from datetime import datetime, timedelta from io import BytesIO -from typing import Generator, Union, Any, List, Tuple, TextIO from pathlib import Path +from typing import Any, Generator, List, TextIO, Tuple, Union import pandas as pd from flask import current_app from reportlab.lib import colors from reportlab.lib.enums import TA_CENTER from reportlab.lib.styles import getSampleStyleSheet -from reportlab.lib.units import mm, inch -from reportlab.platypus import SimpleDocTemplate, Paragraph, PageBreak, Table +from reportlab.lib.units import inch, mm from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont +from reportlab.platypus import PageBreak, Paragraph, SimpleDocTemplate, Table from werkzeug.datastructures import FileStorage -from .exceptions import InvalidNameOrTypeHeaderException -from ..students.models import Student, Group, ProjectGradeSheet +from ..base.mode import ModeGroups from ..examination_schedule.models import TermOfDefence +from ..students.models import Group, ProjectGradeSheet, Student +from .exceptions import InvalidNameOrTypeHeaderException def check_columns(df: pd.DataFrame) -> bool: headers = set(df.keys().values) - 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))) + 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], year_group_id: int) -> Generator[Student, Any, None]: +def parse_csv( + file: Union[FileStorage, TextIO], year_group_id: int +) -> Generator[Student, Any, None]: df = pd.read_csv(file) 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'], - email=dict(item.items())['EMAIL'], - year_group_id=year_group_id) - for _, item in df.iterrows()) + students = ( + Student( + last_name=dict(item.items())["NAZWISKO"], + first_name=dict(item.items())["IMIE"], + index=dict(item.items())["INDEKS"], + 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 = ['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] + 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) for row in data: for idx, item in enumerate(row): @@ -60,8 +91,9 @@ def generate_csv(students_and_groups: List[Tuple[Student, Group]]) -> str: return df.to_csv(index=False) -def generate_range_dates(start_date: datetime, end_date: datetime, step_in_minutes: int) -> \ - Generator[Union[datetime, timedelta], Any, None]: +def generate_range_dates( + start_date: datetime, end_date: datetime, step_in_minutes: int +) -> Generator[Union[datetime, timedelta], Any, None]: current_date = copy.copy(start_date) while True: next_date = current_date + timedelta(minutes=step_in_minutes) @@ -72,10 +104,19 @@ def generate_range_dates(start_date: datetime, end_date: datetime, step_in_minut current_date = copy.copy(next_date) -def generate_examination_schedule_pdf_file(title: str, nested_term_of_defences: List[List[TermOfDefence]], - base_dir: Path) -> bytes: +def generate_examination_schedule_pdf_file( + title: str, nested_term_of_defences: List[List[TermOfDefence]], base_dir: Path +) -> bytes: pagesize = (297 * mm, 210 * mm) - headers = ["lp.", "Godzina", "Nazwa projektu", "Opiekun", "Zespol", "Komisja", "Uwagi"] + headers = [ + "lp.", + "Godzina", + "Nazwa projektu", + "Opiekun", + "Zespol", + "Komisja", + "Uwagi", + ] pdf_buffer = BytesIO() my_doc = SimpleDocTemplate( pdf_buffer, @@ -84,13 +125,13 @@ def generate_examination_schedule_pdf_file(title: str, nested_term_of_defences: leftMargin=1 * inch, rightMargin=1 * inch, bottomMargin=1 * inch, - title=title + title=title, ) - pdfmetrics.registerFont(TTFont('Lato', base_dir / 'fonts' / 'Lato.ttf')) + pdfmetrics.registerFont(TTFont("Lato", base_dir / "fonts" / "Lato.ttf")) style = getSampleStyleSheet() - bodyText = style['BodyText'] - bodyText.fontName = 'Lato' + bodyText = style["BodyText"] + bodyText.fontName = "Lato" normal = style["Heading1"] normal.alignment = TA_CENTER flowables = [] @@ -99,7 +140,7 @@ def generate_examination_schedule_pdf_file(title: str, nested_term_of_defences: for term_of_defences in nested_term_of_defences: if len(term_of_defences) == 0: continue - date = datetime.strftime(term_of_defences[0].start_date.date(), '%d.%m.%Y') + date = datetime.strftime(term_of_defences[0].start_date.date(), "%d.%m.%Y") paragraph_1 = Paragraph(f"{title} ~ {date}", normal) flowables.append(paragraph_1) data = [headers] @@ -107,7 +148,7 @@ def generate_examination_schedule_pdf_file(title: str, nested_term_of_defences: for idx, td in enumerate(term_of_defences, start=1): new_date = td.start_date + timedelta(hours=2) group_name = td.group.name if td.group is not None else "" - if group_name != '': + if group_name != "": ps = td.group.project_supervisor project_supervisor_fullname = f"{ps.first_name[0]}. {ps.last_name}" students = td.group.students @@ -120,27 +161,40 @@ def generate_examination_schedule_pdf_file(title: str, nested_term_of_defences: members = td.members_of_committee # print(members) if len(members) == 0: - committee = '' + committee = "" else: members_iter = (f"{m.first_name[0]} {m.last_name}" for m in members) committee = ", ".join(members_iter) - data.append([str(idx), new_date.strftime("%H:%M"), - Paragraph(group_name, bodyText), - Paragraph(project_supervisor_fullname, bodyText), - Paragraph(team, bodyText), - Paragraph(committee, bodyText), - ]) + data.append( + [ + str(idx), + new_date.strftime("%H:%M"), + Paragraph(group_name, bodyText), + Paragraph(project_supervisor_fullname, bodyText), + Paragraph(team, bodyText), + Paragraph(committee, bodyText), + ] + ) # print(data) - table = Table(data=data, - style=[ - ('GRID', (0, 0), (-1, -1), 0.5, colors.black), - ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor("#A6F1A6")), - ('BACKGROUND', (0, 0), (1, -1), colors.HexColor("#A6F1A6")) - ], - colWidths=[0.25 * inch, 0.7 * inch, 1.6 * inch, 1.5 * inch, 2.5 * inch, 2.2 * inch, 2 * inch] - ) + table = Table( + data=data, + style=[ + ("GRID", (0, 0), (-1, -1), 0.5, colors.black), + ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#A6F1A6")), + ("BACKGROUND", (0, 0), (1, -1), colors.HexColor("#A6F1A6")), + ], + colWidths=[ + 0.25 * inch, + 0.7 * inch, + 1.6 * inch, + 1.5 * inch, + 2.5 * inch, + 2.2 * inch, + 2 * inch, + ], + ) flowables.append(table) flowables.append(PageBreak()) @@ -150,8 +204,20 @@ def generate_examination_schedule_pdf_file(title: str, nested_term_of_defences: return pdf_value +def get_duration_time(mode: str) -> int: + duration_time = None + if mode == ModeGroups.NON_STATIONARY.value: + duration_time = 20 + elif mode in [ + ModeGroups.STATIONARY.value, + ModeGroups.ENGLISH_SPEAKING_STATIONARY.value, + ]: + duration_time = 30 + return duration_time + + def load_weight_for_project_grade_sheet() -> Union[dict, None]: - base_dir = current_app.config.get('BASE_DIR') + base_dir = current_app.config.get("BASE_DIR") config_dir = base_dir / "config" with open(config_dir / "weights_project_grade_sheet.json") as f: @@ -160,7 +226,9 @@ def load_weight_for_project_grade_sheet() -> Union[dict, None]: return data -def calculate_points_for_one_term(weights: dict, project_grade_sheets: List[ProjectGradeSheet]) -> list: +def calculate_points_for_one_term( + weights: dict, project_grade_sheets: List[ProjectGradeSheet] +) -> list: terms = [] for pgs in project_grade_sheets: if pgs is None: @@ -168,28 +236,30 @@ def calculate_points_for_one_term(weights: dict, project_grade_sheets: List[Proj continue first_term_points = { - 'nominator': 0, - 'denominator': 0, + "nominator": 0, + "denominator": 0, } second_term_points = { - 'nominator': 0, - 'denominator': 0, + "nominator": 0, + "denominator": 0, } for weight_key, weight_value in weights.items(): - points = first_term_points if weight_key.endswith('1') else second_term_points + points = ( + first_term_points if weight_key.endswith("1") else second_term_points + ) try: attribute_value = getattr(pgs, weight_key) except AttributeError: attribute_value = 0 - points['nominator'] += attribute_value * weight_value * 1 / 4 - points['denominator'] += weight_value + points["nominator"] += attribute_value * weight_value * 1 / 4 + points["denominator"] += weight_value try: - fp = first_term_points['nominator'] / first_term_points['denominator'] + fp = first_term_points["nominator"] / first_term_points["denominator"] except ZeroDivisionError: fp = 0 try: - sp = second_term_points['nominator'] / second_term_points['denominator'] + sp = second_term_points["nominator"] / second_term_points["denominator"] except ZeroDivisionError: sp = 0 diff --git a/backend/app/dependencies.py b/backend/app/dependencies.py index b474658..21cf4a8 100644 --- a/backend/app/dependencies.py +++ b/backend/app/dependencies.py @@ -1,6 +1,5 @@ -from flask_sqlalchemy import SQLAlchemy from flask_marshmallow import Marshmallow - +from flask_sqlalchemy import SQLAlchemy ma = Marshmallow() diff --git a/backend/app/errors.py b/backend/app/errors.py index 7920737..0a91d81 100644 --- a/backend/app/errors.py +++ b/backend/app/errors.py @@ -8,6 +8,6 @@ def register_error_handlers(app: APIFlask): @app.errorhandler(HTTPException) def handle_http_exception(e): response = e.get_response() - response.data = json.dumps({'error': e.description}) - response.content_type = 'application/json' + response.data = json.dumps({"error": e.description}) + response.content_type = "application/json" return response diff --git a/backend/app/examination_schedule/models.py b/backend/app/examination_schedule/models.py index d50ced5..f51bbb8 100644 --- a/backend/app/examination_schedule/models.py +++ b/backend/app/examination_schedule/models.py @@ -1,18 +1,22 @@ -from ..dependencies import db +from ..base.mode import EnrollmentsMode from ..base.models import Base +from ..dependencies import db class ExaminationSchedule(Base): - __tablename__ = 'examination_schedules' + __tablename__ = "examination_schedules" title = db.Column(db.String(100), unique=True, nullable=False) duration_time = db.Column(db.Integer, nullable=False) # in minutes - start_date_for_enrollment_students = db.Column(db.DateTime) - end_date_for_enrollment_students = db.Column(db.DateTime) + open_enrollments = db.Column( + db.String(1), default=EnrollmentsMode.INIT.value, nullable=False + ) 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') + year_group_id = db.Column( + db.Integer, db.ForeignKey("year_groups.id"), nullable=False + ) + year_group = db.relationship("YearGroup", backref="examination_schedules") committee = db.Table( @@ -23,23 +27,42 @@ committee = db.Table( class TermOfDefence(Base): - __tablename__ = 'term_of_defences' + __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='term_of_defences') - group_id = db.Column(db.Integer, db.ForeignKey('groups.id')) - group = db.relationship("Group", uselist=False, backref='term_of_defence', lazy='joined') - members_of_committee = db.relationship("ProjectSupervisor", secondary=committee, lazy='joined') + examination_schedule_id = db.Column( + db.Integer, db.ForeignKey("examination_schedules.id") + ) + 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="term_of_defence", lazy="joined" + ) + members_of_committee = db.relationship( + "ProjectSupervisor", secondary=committee, lazy="joined" + ) + chairman_of_committee = db.Column( + db.Integer, db.ForeignKey("project_supervisors.id") + ) class TemporaryAvailability(Base): - __tablename__ = 'temporary_availabilities' + __tablename__ = "temporary_availabilities" 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') + 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/project_supervisor/models.py b/backend/app/project_supervisor/models.py index b88812e..e5e0b47 100644 --- a/backend/app/project_supervisor/models.py +++ b/backend/app/project_supervisor/models.py @@ -1,8 +1,8 @@ from flask_sqlalchemy import BaseQuery -from ..dependencies import db -from ..base.models import Person, Base +from ..base.models import Base, Person from ..base.utils import order_by_column_name +from ..dependencies import db from ..students.models import YearGroup @@ -10,27 +10,41 @@ class ProjectSupervisor(Base, Person): __tablename__ = "project_supervisors" 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') + is_coordinator = db.Column(db.Boolean, default=False, nullable=False) + year_group_id = db.Column(db.Integer, db.ForeignKey("year_groups.id")) + year_group = db.relationship("YearGroup", backref="project_supervisors") + + __table__args = db.UniqueConstraint( + "email", "year_group_id", name="uc_email_year_group_id" + ) @classmethod - 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: + def search_by_fullname( + 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 year_group_id is not None: - project_supervisors_query = project_supervisors_query. \ - filter(ProjectSupervisor.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( - (ProjectSupervisor.first_name + ' ' + ProjectSupervisor.last_name).like(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/project_supervisor/query/__init__.py b/backend/app/project_supervisor/query/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/project_supervisor/query/project_grade_sheet.py b/backend/app/project_supervisor/query/project_grade_sheet.py new file mode 100644 index 0000000..6d0d6f8 --- /dev/null +++ b/backend/app/project_supervisor/query/project_grade_sheet.py @@ -0,0 +1,31 @@ +from flask import abort + +from ...dependencies import db +from ...students.models import Group, ProjectGradeSheet +from ..models import ProjectSupervisor + + +def update_project_grade_sheet(group_id: int, query: dict, data: dict) -> None: + project_supervisor_id = query.get("id") + project_supervisor = ProjectSupervisor.query.filter( + ProjectSupervisor.id == project_supervisor_id + ).first() + if project_supervisor is None: + abort(404, "ProjectSupervisor doesn't exist!") + #################################### + if len(data) == 0: + abort(400, "You passed empty data!") + + group = Group.query.filter( + Group.project_supervisor_id == project_supervisor_id, Group.id == group_id + ).first() + if group is None: + abort(400, "You cannot update project grade sheet! It's not your group!") + + pgs_query = ProjectGradeSheet.query.filter(ProjectGradeSheet.group_id == group_id) + + if pgs_query.first() is None: + abort(404, "Not found project grade sheet!") + + pgs_query.update(data) + db.session.commit() diff --git a/backend/app/project_supervisor/routes/enrollments.py b/backend/app/project_supervisor/routes/enrollments.py index fefcdc8..919a65d 100644 --- a/backend/app/project_supervisor/routes/enrollments.py +++ b/backend/app/project_supervisor/routes/enrollments.py @@ -1,53 +1,92 @@ -from datetime import datetime - from apiflask import APIBlueprint from flask import abort from sqlalchemy import and_, or_ -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.mode import EnrollmentsMode from ...base.schemas import MessageSchema +from ...dependencies import db +from ...examination_schedule.models import ( + ExaminationSchedule, + TemporaryAvailability, + TermOfDefence, +) +from ..models import ProjectSupervisor +from ..schemas import ( + ListOfFreeTimesSchema, + ListOfTermOfDefenceSchema, + TemporaryProjectSupervisorSchema, + TimeAvailabilityCreateSchema, +) bp = APIBlueprint("enrollments", __name__, url_prefix="/") -@bp.post('//enrollments/') +@bp.post("//enrollments/") @bp.input(TimeAvailabilityCreateSchema) @bp.output(MessageSchema) -def set_your_free_time_to_examination_schedule(examination_schedule_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 = ProjectSupervisor.query.filter(ProjectSupervisor.id == data['project_supervisor_id']).first() + project_supervisor = ProjectSupervisor.query.filter( + ProjectSupervisor.id == data["project_supervisor_id"] + ).first() if project_supervisor is None: abort(404, "ProjectSupervisor doesn't exist!") + print(project_supervisor) ################ - es = ExaminationSchedule.query.filter(ExaminationSchedule.id == examination_schedule_id).first() - if es is None: + examination_schedule = ExaminationSchedule.query.filter( + ExaminationSchedule.id == examination_schedule_id + ).first() + if examination_schedule is None: abort(404, "Examination schedule doesn't exist!") + if examination_schedule.year_group_id != project_supervisor.year_group_id: + abort(400, "You are not assigned to this year group!") - sd = data['start_date'] - ed = data['end_date'] + if examination_schedule.open_enrollments != EnrollmentsMode.INIT.value: + abort(400, "Enrollments has started or closed! You have been delayed!") + + sd = data.get("start_date") + ed = data.get("end_date") if sd > ed: abort(400, "Invalid data! End date must be greater than start date!") - if not (es.start_date.timestamp() <= sd.timestamp() and es.end_date.timestamp() >= ed.timestamp()): - abort(400, "Date range is not within the examination schedule!") + start_date = examination_schedule.start_date + end_date = examination_schedule.end_date + if not ( + start_date.timestamp() <= sd.timestamp() + and end_date.timestamp() >= ed.timestamp() + ): + abort(400, "Invalid date range!") - 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!") - - ta = TemporaryAvailability.query.filter(TemporaryAvailability.examination_schedule_id == examination_schedule_id). \ - filter(TemporaryAvailability.project_supervisor_id == project_supervisor.id). \ - filter(or_(and_(TemporaryAvailability.start_date >= sd, TemporaryAvailability.start_date < ed, - TemporaryAvailability.end_date >= ed), - and_(TemporaryAvailability.start_date <= sd, TemporaryAvailability.end_date > sd, - TemporaryAvailability.end_date <= ed))).first() + ta = ( + TemporaryAvailability.query.filter( + TemporaryAvailability.examination_schedule_id == examination_schedule_id + ) + .filter(TemporaryAvailability.project_supervisor_id == project_supervisor.id) + .filter( + or_( + and_( + TemporaryAvailability.start_date >= sd, + TemporaryAvailability.start_date < ed, + TemporaryAvailability.end_date >= ed, + ), + and_( + TemporaryAvailability.start_date <= sd, + TemporaryAvailability.end_date > sd, + TemporaryAvailability.end_date <= ed, + ), + ) + ) + .first() + ) if ta is not None: - abort(400, "Invalid date ranges. You set your free time in this date range! Choose another date!") + abort( + 400, + "Invalid date ranges. You set your free time " + "in this date range! Choose another date!", + ) ta = TemporaryAvailability(**data, examination_schedule_id=examination_schedule_id) db.session.add(ta) @@ -56,19 +95,37 @@ def set_your_free_time_to_examination_schedule(examination_schedule_id: int, dat return {"message": "You have just assigned your free time!"} -@bp.delete('//enrollments//') +@bp.delete( + "//enrollments//" +) @bp.input(TemporaryProjectSupervisorSchema) @bp.output(MessageSchema) -def delete_your_free_time_from_examination_schedule(examination_schedule_id: int, temporary_availability_id: int, - data: dict) -> dict: +def delete_your_free_time_from_examination_schedule( + examination_schedule_id: int, temporary_availability_id: int, data: dict +) -> dict: # this code will be removed - project_supervisor = db.session.query(ProjectSupervisor).filter(ProjectSupervisor.id == data['id']).first() + 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 == temporary_availability_id).first() + examination_schedule = ExaminationSchedule.query.filter( + ExaminationSchedule.id == examination_schedule_id + ).first() + if examination_schedule is None: + abort(404, "Examination schedule doesn't exist!") + + if examination_schedule.open_enrollments != EnrollmentsMode.INIT.value: + abort(400, "Enrollments has started or closed! You have been delayed!") + + ta = TemporaryAvailability.query.filter( + TemporaryAvailability.examination_schedule_id == examination_schedule_id, + TemporaryAvailability.project_supervisor_id == project_supervisor.id, + TemporaryAvailability.id == temporary_availability_id, + ).first() if ta is None: abort(404, "Your free time doesn't exist!") @@ -78,49 +135,65 @@ def delete_your_free_time_from_examination_schedule(examination_schedule_id: int return {"message": "You have just removed your free time!"} -@bp.get('//temporary-availabilities/') -@bp.input(TemporaryProjectSupervisorSchema, location='query') +@bp.get("//temporary-availabilities/") +@bp.input(TemporaryProjectSupervisorSchema, location="query") @bp.output(ListOfFreeTimesSchema) -def list_enrollments_for_project_supervisor(examination_schedule_id: int, data: dict) -> dict: +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() + 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: + examination_schedule = ExaminationSchedule.query.filter( + ExaminationSchedule.id == examination_schedule_id + ).first() + if examination_schedule is None: abort(404, "Examination schedule doesn't exist!") - now = datetime.utcnow() - start_date = es.start_date_for_enrollment_students - if start_date is not None and start_date.timestamp() < now.timestamp(): - abort(403, "Forbidden! Enrollment has just started! You cannot assign to the exam committees!") + if examination_schedule.open_enrollments != EnrollmentsMode.INIT.value: + abort(400, "Enrollments has started or closed! You have been delayed!") # 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() + ta = TemporaryAvailability.query.filter( + TemporaryAvailability.examination_schedule_id == examination_schedule_id, + TemporaryAvailability.project_supervisor_id == project_supervisor.id, + ).all() return {"free_times": ta} -@bp.get('//term-of-defences/') -@bp.input(TemporaryProjectSupervisorSchema, location='query') +@bp.get("//term-of-defences/") +@bp.input(TemporaryProjectSupervisorSchema, location="query") @bp.output(ListOfTermOfDefenceSchema) -def list_created_term_of_defences_by_coordinator_for_project_supervisor(examination_schedule_id: int, - data: dict) -> dict: +def list_created_term_of_defences_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() + 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() + 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(id=project_supervisor.id).all() + td = ( + TermOfDefence.query.join(TermOfDefence.members_of_committee) + .filter(TermOfDefence.examination_schedule_id == examination_schedule_id) + .filter_by(id=project_supervisor.id) + .all() + ) return {"term_of_defences": td} diff --git a/backend/app/project_supervisor/routes/project_grade_sheet.py b/backend/app/project_supervisor/routes/project_grade_sheet.py index ea17b35..25a61b1 100644 --- a/backend/app/project_supervisor/routes/project_grade_sheet.py +++ b/backend/app/project_supervisor/routes/project_grade_sheet.py @@ -1,29 +1,43 @@ from apiflask import APIBlueprint from flask import abort -from ...dependencies import db -from ..models import ProjectSupervisor -from ..schemas import ProjectSupervisorTermQuerySchema, TemporaryProjectSupervisorSchema -from ...students.schemas import ProjectGradeSheetDetailFirstTermSchema, ProjectGradeSheetDetailSecondTermSchema, \ - ProjectGradeSheetEditFirstTermSchema, ProjectGradeSheetEditSecondTermSchema -from ...students.models import Group, ProjectGradeSheet from ...base.schemas import MessageSchema +from ...students.models import Group +from ...students.schemas import ( + ProjectGradeSheetDetailFirstTermSchema, + ProjectGradeSheetDetailSecondTermSchema, + ProjectGradeSheetEditFirstTermSchema, + ProjectGradeSheetEditSecondTermSchema, +) +from ..models import ProjectSupervisor +from ..query.project_grade_sheet import update_project_grade_sheet +from ..schemas import ProjectSupervisorTermQuerySchema, TemporaryProjectSupervisorSchema -bp = APIBlueprint("project_grade_sheet_for_project_supervisor", __name__, url_prefix="/project-grade-sheet") +bp = APIBlueprint( + "project_grade_sheet_for_project_supervisor", + __name__, + url_prefix="/project-grade-sheet", +) -@bp.get('/group//') -@bp.input(ProjectSupervisorTermQuerySchema, location='query') +@bp.get("/group//") +@bp.input(ProjectSupervisorTermQuerySchema, location="query") def detail_project_grade_sheet(group_id: int, query: dict) -> dict: - project_supervisor_id = query.get('id') - project_supervisor = ProjectSupervisor.query.filter(ProjectSupervisor.id == project_supervisor_id).first() + project_supervisor_id = query.get("id") + project_supervisor = ProjectSupervisor.query.filter( + ProjectSupervisor.id == project_supervisor_id + ).first() if project_supervisor is None: abort(404, "ProjectSupervisor doesn't exist!") #################################### - term = query.get('term') - group = Group.query.filter(Group.project_supervisor_id == project_supervisor_id, Group.id == group_id).first() + term = query.get("term") + group = Group.query.filter( + Group.project_supervisor_id == project_supervisor_id, Group.id == group_id + ).first() if group is None or len(group.project_grade_sheet) == 0: - abort(400, "Group doesn't exist!") + abort( + 400, "Group doesn't exist or you are not project supervisor of that group!" + ) pgs = group.project_grade_sheet[0] if term == 1: @@ -34,36 +48,23 @@ def detail_project_grade_sheet(group_id: int, query: dict) -> dict: return schema.dump(pgs) -def update_project_grade_sheet(group_id: int, query: dict, data: dict) -> None: - project_supervisor_id = query.get('id') - project_supervisor = ProjectSupervisor.query.filter(ProjectSupervisor.id == project_supervisor_id).first() - if project_supervisor is None: - abort(404, "ProjectSupervisor doesn't exist!") - #################################### - if len(data) == 0: - abort(400, "You passed empty data!") - pgs_query = ProjectGradeSheet.query.filter(ProjectGradeSheet.group_id == group_id) - - if pgs_query.first() is None: - abort(404, "Not found project grade sheet!") - - pgs_query.update(data) - db.session.commit() - - -@bp.patch('/group//first-term/') -@bp.input(TemporaryProjectSupervisorSchema, location='query') -@bp.input(ProjectGradeSheetEditFirstTermSchema, location='json') +@bp.patch("/group//first-term/") +@bp.input(TemporaryProjectSupervisorSchema, location="query") +@bp.input(ProjectGradeSheetEditFirstTermSchema, location="json") @bp.output(MessageSchema) -def update_project_grade_sheet_for_first_term(group_id: int, query: dict, data: dict) -> dict: +def update_project_grade_sheet_for_first_term( + group_id: int, query: dict, data: dict +) -> dict: update_project_grade_sheet(group_id, query, data) return {"message": "Your project grade sheet was updated!"} -@bp.patch('/group//second-term/') -@bp.input(TemporaryProjectSupervisorSchema, location='query') -@bp.input(ProjectGradeSheetEditSecondTermSchema, location='json') +@bp.patch("/group//second-term/") +@bp.input(TemporaryProjectSupervisorSchema, location="query") +@bp.input(ProjectGradeSheetEditSecondTermSchema, location="json") @bp.output(MessageSchema) -def update_project_grade_sheet_for_second_term(group_id: int, query: dict, data: dict) -> dict: +def update_project_grade_sheet_for_second_term( + group_id: int, query: dict, data: dict +) -> dict: update_project_grade_sheet(group_id, query, data) return {"message": "Your project grade sheet was updated!"} diff --git a/backend/app/project_supervisor/schemas.py b/backend/app/project_supervisor/schemas.py index f002b07..e2c5ff7 100644 --- a/backend/app/project_supervisor/schemas.py +++ b/backend/app/project_supervisor/schemas.py @@ -1,4 +1,4 @@ -from marshmallow import fields, validate, Schema +from marshmallow import Schema, fields, validate class FreeTimeSchema(Schema): @@ -18,7 +18,9 @@ class ListOfTermOfDefenceSchema(Schema): 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 + project_supervisor_id = fields.Integer( + required=True + ) # temporary field it will be removed in the future # temporary class it will be removed in the future diff --git a/backend/app/students/models.py b/backend/app/students/models.py index 82d3b05..bd8c5c9 100644 --- a/backend/app/students/models.py +++ b/backend/app/students/models.py @@ -2,59 +2,63 @@ from datetime import datetime from flask_sqlalchemy import BaseQuery -from ..dependencies import db -from ..base.models import Person, Base +from ..base.models import Base, Person from ..base.utils import order_by_column_name +from ..dependencies import db from ..examination_schedule.models import TermOfDefence class YearGroup(Base): - __tablename__ = 'year_groups' + __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) - __table__args = ( - db.UniqueConstraint('name', 'mode', name='uc_name_mode_year_group') - ) + __table__args = db.UniqueConstraint("name", "mode", name="uc_name_mode_year_group") -students_groups = db.Table('students_groups', - db.Column('group_id', db.ForeignKey('groups.id'), nullable=False), - db.Column('student_id', db.ForeignKey('students.id'), nullable=False)) +students_groups = db.Table( + "students_groups", + db.Column("group_id", db.ForeignKey("groups.id"), nullable=False), + db.Column("student_id", db.ForeignKey("students.id"), nullable=False), +) class Group(Base): __tablename__ = "groups" name = db.Column(db.String(60), nullable=False) - cdyd_kod = db.Column(db.String(60), default='2022/SZ') - 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') - year_group_id = db.Column(db.Integer, db.ForeignKey('year_groups.id')) - year_group = db.relationship('YearGroup', backref='groups', lazy='joined') + cdyd_kod = db.Column(db.String(60), default="2022/SZ") + 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") + 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') + 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: 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}%')) + group_query = group_query.filter(Group.name.like(f"{search_name}%")) return group_query class ProjectGradeSheet(Base): - __tablename__ = 'project_grade_sheets' + __tablename__ = "project_grade_sheets" - group_id = db.Column(db.Integer, db.ForeignKey('groups.id')) - group = db.relationship('Group', backref='project_grade_sheet', uselist=False) + group_id = db.Column(db.Integer, db.ForeignKey("groups.id")) + 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) @@ -62,8 +66,12 @@ class ProjectGradeSheet(Base): presentation_was_compatible_2 = db.Column(db.Integer, default=0) presentation_showing_1 = db.Column(db.Integer, default=0) presentation_showing_2 = db.Column(db.Integer, default=0) - presentation_answers_to_questions_from_committee_1 = db.Column(db.Integer, default=0) - presentation_answers_to_questions_from_committee_2 = db.Column(db.Integer, default=0) + presentation_answers_to_questions_from_committee_1 = db.Column( + db.Integer, default=0 + ) + presentation_answers_to_questions_from_committee_2 = db.Column( + db.Integer, default=0 + ) documentation_project_vision_1 = db.Column(db.Integer, default=0) documentation_project_vision_2 = db.Column(db.Integer, default=0) @@ -121,20 +129,36 @@ class Student(Base, Person): __tablename__ = "students" 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') + 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") + + __table__args = db.UniqueConstraint( + "index", "year_group_id", name="uc_index_year_group_id" + ) @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: + 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.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}%')) + 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) + 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 + ) return student_query diff --git a/backend/app/students/routes/enrollments.py b/backend/app/students/routes/enrollments.py index f8b3d91..a46e26b 100644 --- a/backend/app/students/routes/enrollments.py +++ b/backend/app/students/routes/enrollments.py @@ -1,46 +1,69 @@ -import datetime - from apiflask import APIBlueprint from flask import abort -from ..schemas import TemporaryStudentSchema, ExaminationScheduleListSchema, \ - TermOfDefenceStudentListSchema +from ...base.schemas import MessageSchema 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 +from ..models import Group, Student, TermOfDefence +from ..schemas import ( + ExaminationScheduleListSchema, + TemporaryStudentSchema, + TermOfDefenceStudentListSchema, +) bp = APIBlueprint("enrollments", __name__, url_prefix="/") -@bp.post('//enrollments//') +@bp.post("//enrollments//") @bp.input(TemporaryStudentSchema) @bp.output(MessageSchema) -def assign_your_group_to_term_of_defence(examination_schedule_id: int, term_of_defence_id: int, data: dict) -> dict: +def assign_your_group_to_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() + student = Student.query.filter(Student.id == data.get("student_id")).first() if student is None: - abort(404, "Student doesn't exist!") + abort(404, "Not found student!") ################ - term_of_defence = TermOfDefence.query.filter(TermOfDefence.id == term_of_defence_id, - TermOfDefence.examination_schedule_id == examination_schedule_id).first() - ex = term_of_defence.examination_schedule - if term_of_defence is None or ex is None: + term_of_defence = ( + TermOfDefence.query.filter( + TermOfDefence.id == term_of_defence_id, + TermOfDefence.examination_schedule_id == examination_schedule_id, + ) + .join(ExaminationSchedule) + .first() + ) + + if term_of_defence is None or (ex := term_of_defence.examination_schedule) is None: abort(400, "Term of defence not found!") - g = Group.query.join(ProjectSupervisor).filter(Group.year_group_id == ex.year_group_id). \ - join(Group.students).filter_by(index=student.index).first() + g = ( + Group.query.join(ProjectSupervisor) + .filter(Group.year_group_id == ex.year_group_id) + .join(Group.students) + .filter_by(index=student.index) + .first() + ) if g is None or g.project_supervisor is None: - abort(400, "You don't have a group or your group doesn't have an assigned project supervisor!") + abort( + 400, + "You don't have a group or your group doesn't" + " have an assigned project supervisor!", + ) - defence = TermOfDefence.query.filter(TermOfDefence.group_id == g.id, - TermOfDefence.examination_schedule_id == examination_schedule_id).first() + defence = TermOfDefence.query.filter( + TermOfDefence.group_id == g.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!") - td = TermOfDefence.query.join(TermOfDefence.members_of_committee). \ - filter_by(id=g.project_supervisor_id).first() + td = ( + TermOfDefence.query.join(TermOfDefence.members_of_committee) + .filter_by(id=g.project_supervisor_id) + .first() + ) if td is None: abort(400, "Your project supervisor is not in committee!") @@ -51,25 +74,35 @@ def assign_your_group_to_term_of_defence(examination_schedule_id: int, term_of_d 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_your_group_from_term_of_defence(examination_schedule_id: int, term_of_defence_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() + student = Student.query.filter(Student.id == data.get("student_id")).first() if student is None: - abort(404, "Student doesn't exist!") + abort(404, "Not found student!") ################ - term_of_defence = TermOfDefence.query.join(ExaminationSchedule).filter(TermOfDefence.id == term_of_defence_id). \ - filter(TermOfDefence.examination_schedule_id == examination_schedule_id).first() + term_of_defence = ( + TermOfDefence.query.join(ExaminationSchedule) + .filter(TermOfDefence.id == term_of_defence_id) + .filter(TermOfDefence.examination_schedule_id == examination_schedule_id) + .first() + ) ex = term_of_defence.examination_schedule if term_of_defence is None: abort(404, "Term of defence doesn't exist!") - group = Group.query.filter(Group.year_group_id == ex.year_group_id). \ - join(Group.students).filter_by(index=student.index).first() + group = ( + Group.query.filter(Group.year_group_id == ex.year_group_id) + .join(Group.students) + .filter_by(index=student.index) + .first() + ) if group.id != term_of_defence.group_id: abort(400, "You are not assigned to this group!") @@ -80,41 +113,51 @@ def delete_your_group_from_term_of_defence(examination_schedule_id: int, term_of return {"message": "You have just removed the group for this exam date!"} -@bp.get('/examination-schedule/year-group//') -@bp.input(TemporaryStudentSchema, location='query') +@bp.get("/examination-schedule/year-group//") +@bp.input(TemporaryStudentSchema, location="query") @bp.output(ExaminationScheduleListSchema) def list_examination_schedule(year_group_id: int, data: dict) -> dict: # this code will be removed - student = Student.query.filter(Student.index == data['student_index']).first() + student = Student.query.filter(Student.id == data.get("student_id")).first() if student is None: - abort(404, "Student doesn't exist!") + abort(404, "Not found student!") ################ - # 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} + examination_schedules = ExaminationSchedule.query.filter( + ExaminationSchedule.year_group_id == year_group_id + ).all() + return {"examination_schedules": examination_schedules} -@bp.get('/examination-schedule//enrollments/') -@bp.input(TemporaryStudentSchema, location='query') +@bp.get("/examination-schedule//enrollments/") +@bp.input(TemporaryStudentSchema, location="query") @bp.output(TermOfDefenceStudentListSchema) def list_term_of_defences(examination_schedule_id: int, data: dict) -> dict: # this code will be removed - student = Student.query.filter(Student.index == data['student_index']).first() + student = Student.query.filter(Student.id == data.get("student_id")).first() if student is None: - abort(404, "Student doesn't exist!") + abort(404, "Not found student!") ################ - # 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.filter(TermOfDefence.examination_schedule_id == examination_schedule_id). \ - join(ExaminationSchedule, isouter=True). \ - filter(ExaminationSchedule.start_date_for_enrollment_students < now). \ - filter(ExaminationSchedule.end_date_for_enrollment_students > now).all() + term_of_defences = ( + TermOfDefence.query.filter( + TermOfDefence.examination_schedule_id == examination_schedule_id + ) + .join(ExaminationSchedule, isouter=True) + .all() + ) - term_of_defences = list(filter(lambda n: len([d.id for d in n.members_of_committee if d.id == student.groups[0].project_supervisor.id]) > 0, term_of_defences)) + term_of_defences = list( + filter( + lambda n: len( + [ + d.id + for d in n.members_of_committee + if d.id == student.groups[0].project_supervisor.id + ] + ) + > 0, + term_of_defences, + ) + ) - return {'term_of_defences': term_of_defences} + return {"term_of_defences": term_of_defences} diff --git a/backend/app/students/routes/project_grade_sheet.py b/backend/app/students/routes/project_grade_sheet.py index 0d7a9de..5337d27 100644 --- a/backend/app/students/routes/project_grade_sheet.py +++ b/backend/app/students/routes/project_grade_sheet.py @@ -1,22 +1,25 @@ from apiflask import APIBlueprint from flask import abort -from ..schemas import StudentIndexQueryTempSchema, ProjectGradeSheetDetailFirstTermSchema, \ - ProjectGradeSheetDetailSecondTermSchema -from ..models import Student, ProjectGradeSheet +from ..models import ProjectGradeSheet, Student +from ..schemas import ( + ProjectGradeSheetDetailFirstTermSchema, + ProjectGradeSheetDetailSecondTermSchema, + StudentIndexQueryTempSchema, +) bp = APIBlueprint("project_grade_sheet", __name__, url_prefix="/project-grade-sheet") -@bp.get('/year-group//') -@bp.input(StudentIndexQueryTempSchema, location='query') +@bp.get("/year-group//") +@bp.input(StudentIndexQueryTempSchema, location="query") def detail_project_grade_sheet(year_group_id: int, query: dict) -> dict: - index = query.get('index') - st = Student.query.filter(Student.index == index).first() + student_id = query.get("student_id") + st = Student.query.filter(Student.id == student_id).first() if st is None: abort(404, "Not found student!") #################################### - term = int(query.get('term')) + term = int(query.get("term")) groups = [g for g in st.groups if g.year_group_id == year_group_id] if len(groups) == 0: diff --git a/backend/app/students/routes/registrations.py b/backend/app/students/routes/registrations.py index 955cbc2..8d1950d 100644 --- a/backend/app/students/routes/registrations.py +++ b/backend/app/students/routes/registrations.py @@ -1,36 +1,34 @@ from apiflask import APIBlueprint +from ...base.utils import paginate_models +from ...dependencies import db from ...project_supervisor.models import ProjectSupervisor from ..models import Group -from ...dependencies import db -from ..schemas import ProjectSupervisorQuerySchema, ProjectSupervisorPaginationSchema -from ...base.utils import paginate_models +from ..schemas import ProjectSupervisorPaginationSchema, ProjectSupervisorQuerySchema bp = APIBlueprint("registrations", __name__, url_prefix="/registrations") -@bp.get('//') -@bp.input(ProjectSupervisorQuerySchema, location='query') +@bp.get("//") +@bp.input(ProjectSupervisorQuerySchema, location="query") @bp.output(ProjectSupervisorPaginationSchema) def list_available_groups(year_group_id: int, query: dict) -> dict: - page = query.get('page') - per_page = query.get('per_page') + page = query.get("page") + per_page = query.get("per_page") - available_groups = (ProjectSupervisor.limit_group - db.func.count(Group.id)) - ps_query = db.session. \ - query(ProjectSupervisor, available_groups). \ - join(Group, isouter=True). \ - filter(ProjectSupervisor.year_group_id == year_group_id).\ - group_by(ProjectSupervisor.id) + available_groups = ProjectSupervisor.limit_group - db.func.count(Group.id) + ps_query = ( + db.session.query(ProjectSupervisor, available_groups) + .join(Group, isouter=True) + .filter(ProjectSupervisor.year_group_id == year_group_id) + .group_by(ProjectSupervisor.id) + ) data = paginate_models(page, ps_query, per_page) project_supervisors = [] - for project_supervisor, available_groups in data['items']: - setattr(project_supervisor, 'available_groups', available_groups) + 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": data['max_pages'] - } + return {"project_supervisors": project_supervisors, "max_pages": data["max_pages"]} diff --git a/backend/app/students/schemas.py b/backend/app/students/schemas.py index dae793b..a6d438a 100644 --- a/backend/app/students/schemas.py +++ b/backend/app/students/schemas.py @@ -1,9 +1,9 @@ -from marshmallow import fields, Schema, validate +from marshmallow import Schema, fields, validate POINTS = [0, 1, 3, 4] -class ProjectSupervisorSchema(Schema): +class ProjectSupervisorWithAvailableGroupsSchema(Schema): first_name = fields.Str() last_name = fields.Str() email = fields.Str() @@ -11,7 +11,9 @@ class ProjectSupervisorSchema(Schema): class ProjectSupervisorPaginationSchema(Schema): - project_supervisors = fields.List(fields.Nested(ProjectSupervisorSchema)) + project_supervisors = fields.List( + fields.Nested(ProjectSupervisorWithAvailableGroupsSchema) + ) max_pages = fields.Integer() @@ -21,18 +23,19 @@ class ProjectSupervisorQuerySchema(Schema): class TemporaryStudentSchema(Schema): - student_index = fields.Integer(required=True) + student_id = fields.Integer(required=True) -class ExaminationScheduleSchema(Schema): +class ExaminationScheduleStudentSchema(Schema): id = fields.Integer() title = fields.Str() start_date = fields.DateTime() end_date = fields.DateTime() + open_enrollments = fields.Boolean() class ExaminationScheduleListSchema(Schema): - examination_schedules = fields.List(fields.Nested(ExaminationScheduleSchema)) + examination_schedules = fields.List(fields.Nested(ExaminationScheduleStudentSchema)) class ProjectSupervisorCommitteeSchema(Schema): @@ -64,11 +67,13 @@ class AssignedGroupToTermOfDefenceItemSchema(TermOfDefenceStudentItemSchema): class TermOfDefenceStudentListSchema(Schema): - term_of_defences = fields.List(fields.Nested(AssignedGroupToTermOfDefenceItemSchema)) + term_of_defences = fields.List( + fields.Nested(AssignedGroupToTermOfDefenceItemSchema) + ) class StudentIndexQueryTempSchema(Schema): - index = fields.Integer(required=True) # it will be removed + student_id = fields.Integer(required=True) # it will be removed term = fields.Integer(required=True, validate=validate.OneOf([1, 2])) @@ -76,7 +81,9 @@ class ProjectGradeSheetEditFirstTermSchema(Schema): presentation_required_content_1 = fields.Integer(validate=validate.OneOf(POINTS)) presentation_was_compatible_1 = fields.Integer(validate=validate.OneOf(POINTS)) presentation_showing_1 = fields.Integer(validate=validate.OneOf(POINTS)) - presentation_answers_to_questions_from_committee_1 = fields.Integer(validate=validate.OneOf(POINTS)) + presentation_answers_to_questions_from_committee_1 = fields.Integer( + validate=validate.OneOf(POINTS) + ) documentation_project_vision_1 = fields.Integer(validate=validate.OneOf(POINTS)) documentation_requirements_1 = fields.Integer(validate=validate.OneOf(POINTS)) @@ -89,17 +96,31 @@ class ProjectGradeSheetEditFirstTermSchema(Schema): group_work_contact_with_client_1 = fields.Integer(validate=validate.OneOf(POINTS)) group_work_management_of_risk_1 = fields.Integer(validate=validate.OneOf(POINTS)) group_work_work_methodology_1 = fields.Integer(validate=validate.OneOf(POINTS)) - group_work_management_of_source_code_1 = fields.Integer(validate=validate.OneOf(POINTS)) + group_work_management_of_source_code_1 = fields.Integer( + validate=validate.OneOf(POINTS) + ) group_work_devops_1 = fields.Integer(validate=validate.OneOf(POINTS)) - products_project_complexity_of_product_1 = fields.Integer(validate=validate.OneOf(POINTS)) - products_project_access_to_application_1 = fields.Integer(validate=validate.OneOf(POINTS)) + products_project_complexity_of_product_1 = fields.Integer( + validate=validate.OneOf(POINTS) + ) + products_project_access_to_application_1 = fields.Integer( + validate=validate.OneOf(POINTS) + ) products_project_security_issues_1 = fields.Integer(validate=validate.OneOf(POINTS)) - products_project_access_to_test_application_1 = fields.Integer(validate=validate.OneOf(POINTS)) - products_project_acceptance_criteria_1 = fields.Integer(validate=validate.OneOf(POINTS)) - products_project_expected_functionality_1 = fields.Integer(validate=validate.OneOf(POINTS)) + products_project_access_to_test_application_1 = fields.Integer( + validate=validate.OneOf(POINTS) + ) + products_project_acceptance_criteria_1 = fields.Integer( + validate=validate.OneOf(POINTS) + ) + products_project_expected_functionality_1 = fields.Integer( + validate=validate.OneOf(POINTS) + ) products_project_promises_well_1 = fields.Integer(validate=validate.OneOf(POINTS)) - products_project_has_been_implemented_1 = fields.Integer(validate=validate.OneOf(POINTS)) + products_project_has_been_implemented_1 = fields.Integer( + validate=validate.OneOf(POINTS) + ) products_project_is_useful_1 = fields.Integer(validate=validate.OneOf(POINTS)) products_project_prototype_1 = fields.Integer(validate=validate.OneOf(POINTS)) products_project_tests_1 = fields.Integer(validate=validate.OneOf(POINTS)) @@ -114,7 +135,9 @@ class ProjectGradeSheetEditSecondTermSchema(Schema): presentation_required_content_2 = fields.Integer(validate=validate.OneOf(POINTS)) presentation_was_compatible_2 = fields.Integer(validate=validate.OneOf(POINTS)) presentation_showing_2 = fields.Integer(validate=validate.OneOf(POINTS)) - presentation_answers_to_questions_from_committee_2 = fields.Integer(validate=validate.OneOf(POINTS)) + presentation_answers_to_questions_from_committee_2 = fields.Integer( + validate=validate.OneOf(POINTS) + ) documentation_project_vision_2 = fields.Integer(validate=validate.OneOf(POINTS)) documentation_requirements_2 = fields.Integer(validate=validate.OneOf(POINTS)) @@ -127,17 +150,31 @@ class ProjectGradeSheetEditSecondTermSchema(Schema): group_work_contact_with_client_2 = fields.Integer(validate=validate.OneOf(POINTS)) group_work_management_of_risk_2 = fields.Integer(validate=validate.OneOf(POINTS)) group_work_work_methodology_2 = fields.Integer(validate=validate.OneOf(POINTS)) - group_work_management_of_source_code_2 = fields.Integer(validate=validate.OneOf(POINTS)) + group_work_management_of_source_code_2 = fields.Integer( + validate=validate.OneOf(POINTS) + ) group_work_devops_2 = fields.Integer(validate=validate.OneOf(POINTS)) - products_project_complexity_of_product_2 = fields.Integer(validate=validate.OneOf(POINTS)) - products_project_access_to_application_2 = fields.Integer(validate=validate.OneOf(POINTS)) + products_project_complexity_of_product_2 = fields.Integer( + validate=validate.OneOf(POINTS) + ) + products_project_access_to_application_2 = fields.Integer( + validate=validate.OneOf(POINTS) + ) products_project_security_issues_2 = fields.Integer(validate=validate.OneOf(POINTS)) - products_project_access_to_test_application_2 = fields.Integer(validate=validate.OneOf(POINTS)) - products_project_acceptance_criteria_2 = fields.Integer(validate=validate.OneOf(POINTS)) - products_project_expected_functionality_2 = fields.Integer(validate=validate.OneOf(POINTS)) + products_project_access_to_test_application_2 = fields.Integer( + validate=validate.OneOf(POINTS) + ) + products_project_acceptance_criteria_2 = fields.Integer( + validate=validate.OneOf(POINTS) + ) + products_project_expected_functionality_2 = fields.Integer( + validate=validate.OneOf(POINTS) + ) products_project_promises_well_2 = fields.Integer(validate=validate.OneOf(POINTS)) - products_project_has_been_implemented_2 = fields.Integer(validate=validate.OneOf(POINTS)) + products_project_has_been_implemented_2 = fields.Integer( + validate=validate.OneOf(POINTS) + ) products_project_is_useful_2 = fields.Integer(validate=validate.OneOf(POINTS)) products_project_prototype_2 = fields.Integer(validate=validate.OneOf(POINTS)) products_project_tests_2 = fields.Integer(validate=validate.OneOf(POINTS)) diff --git a/backend/app/utils.py b/backend/app/utils.py index a7de7c2..81ae249 100644 --- a/backend/app/utils.py +++ b/backend/app/utils.py @@ -1,5 +1,5 @@ -import os import importlib +import os import warnings from flask import current_app @@ -7,8 +7,8 @@ from flask import current_app def get_app_directories() -> list: directories = [] - src_dir = current_app.config['SRC_DIR'] - excluded_dirs = current_app.config['EXCLUDED_DIRS'] + src_dir = current_app.config["SRC_DIR"] + excluded_dirs = current_app.config["EXCLUDED_DIRS"] for dirname in os.listdir(src_dir): path = src_dir / dirname diff --git a/backend/main.py b/backend/main.py index 4d597d1..4a748f1 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,4 +1,5 @@ from dotenv import load_dotenv + from app import create_app load_dotenv() diff --git a/backend/migrations/env.py b/backend/migrations/env.py index 68feded..b80ce85 100644 --- a/backend/migrations/env.py +++ b/backend/migrations/env.py @@ -3,9 +3,8 @@ from __future__ import with_statement import logging from logging.config import fileConfig -from flask import current_app - from alembic import context +from flask import current_app # this is the Alembic Config object, which provides # access to the values within the .ini file in use. @@ -14,17 +13,17 @@ config = context.config # Interpret the config file for Python logging. # This line sets up loggers basically. fileConfig(config.config_file_name) -logger = logging.getLogger('alembic.env') +logger = logging.getLogger("alembic.env") # add your model's MetaData object here # for 'autogenerate' support # from myapp import mymodel # target_metadata = mymodel.Base.metadata config.set_main_option( - 'sqlalchemy.url', - str(current_app.extensions['migrate'].db.get_engine().url).replace( - '%', '%%')) -target_metadata = current_app.extensions['migrate'].db.metadata + "sqlalchemy.url", + str(current_app.extensions["migrate"].db.get_engine().url).replace("%", "%%"), +) +target_metadata = current_app.extensions["migrate"].db.metadata # other values from the config, defined by the needs of env.py, # can be acquired: @@ -45,9 +44,7 @@ def run_migrations_offline(): """ url = config.get_main_option("sqlalchemy.url") - context.configure( - url=url, target_metadata=target_metadata, literal_binds=True - ) + context.configure(url=url, target_metadata=target_metadata, literal_binds=True) with context.begin_transaction(): context.run_migrations() @@ -65,20 +62,20 @@ def run_migrations_online(): # when there are no changes to the schema # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html def process_revision_directives(context, revision, directives): - if getattr(config.cmd_opts, 'autogenerate', False): + if getattr(config.cmd_opts, "autogenerate", False): script = directives[0] if script.upgrade_ops.is_empty(): directives[:] = [] - logger.info('No changes in schema detected.') + logger.info("No changes in schema detected.") - connectable = current_app.extensions['migrate'].db.get_engine() + connectable = current_app.extensions["migrate"].db.get_engine() with connectable.connect() as connection: context.configure( connection=connection, target_metadata=target_metadata, process_revision_directives=process_revision_directives, - **current_app.extensions['migrate'].configure_args + **current_app.extensions["migrate"].configure_args ) with context.begin_transaction(): diff --git a/backend/migrations/versions/3fd120fc5e12_.py b/backend/migrations/versions/3fd120fc5e12_.py deleted file mode 100644 index 17e7840..0000000 --- a/backend/migrations/versions/3fd120fc5e12_.py +++ /dev/null @@ -1,196 +0,0 @@ -"""empty message - -Revision ID: 3fd120fc5e12 -Revises: -Create Date: 2023-01-14 00:03:06.327441 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '3fd120fc5e12' -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - 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') - ) - 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('duration_time', 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.Column('year_group_id', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['year_group_id'], ['year_groups.id'], ), - 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), - 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('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('project_grade_sheets', - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('group_id', sa.Integer(), nullable=True), - sa.Column('presentation_required_content_1', sa.Integer(), nullable=True), - sa.Column('presentation_required_content_2', sa.Integer(), nullable=True), - sa.Column('presentation_was_compatible_1', sa.Integer(), nullable=True), - sa.Column('presentation_was_compatible_2', sa.Integer(), nullable=True), - sa.Column('presentation_showing_1', sa.Integer(), nullable=True), - sa.Column('presentation_showing_2', sa.Integer(), nullable=True), - sa.Column('presentation_answers_to_questions_from_committee_1', sa.Integer(), nullable=True), - sa.Column('presentation_answers_to_questions_from_committee_2', sa.Integer(), nullable=True), - sa.Column('documentation_project_vision_1', sa.Integer(), nullable=True), - sa.Column('documentation_project_vision_2', sa.Integer(), nullable=True), - sa.Column('documentation_requirements_1', sa.Integer(), nullable=True), - sa.Column('documentation_requirements_2', sa.Integer(), nullable=True), - sa.Column('documentation_for_clients_1', sa.Integer(), nullable=True), - sa.Column('documentation_for_clients_2', sa.Integer(), nullable=True), - sa.Column('documentation_for_developers_1', sa.Integer(), nullable=True), - sa.Column('documentation_for_developers_2', sa.Integer(), nullable=True), - sa.Column('documentation_license_1', sa.Integer(), nullable=True), - sa.Column('documentation_license_2', sa.Integer(), nullable=True), - sa.Column('group_work_regularity_1', sa.Integer(), nullable=True), - sa.Column('group_work_regularity_2', sa.Integer(), nullable=True), - sa.Column('group_work_division_of_work_1', sa.Integer(), nullable=True), - sa.Column('group_work_division_of_work_2', sa.Integer(), nullable=True), - sa.Column('group_work_contact_with_client_1', sa.Integer(), nullable=True), - sa.Column('group_work_contact_with_client_2', sa.Integer(), nullable=True), - sa.Column('group_work_management_of_risk_1', sa.Integer(), nullable=True), - sa.Column('group_work_management_of_risk_2', sa.Integer(), nullable=True), - sa.Column('group_work_work_methodology_1', sa.Integer(), nullable=True), - sa.Column('group_work_work_methodology_2', sa.Integer(), nullable=True), - sa.Column('group_work_management_of_source_code_1', sa.Integer(), nullable=True), - sa.Column('group_work_management_of_source_code_2', sa.Integer(), nullable=True), - sa.Column('group_work_devops_1', sa.Integer(), nullable=True), - sa.Column('group_work_devops_2', sa.Integer(), nullable=True), - sa.Column('products_project_complexity_of_product_1', sa.Integer(), nullable=True), - sa.Column('products_project_complexity_of_product_2', sa.Integer(), nullable=True), - sa.Column('products_project_access_to_application_1', sa.Integer(), nullable=True), - sa.Column('products_project_access_to_application_2', sa.Integer(), nullable=True), - sa.Column('products_project_security_issues_1', sa.Integer(), nullable=True), - sa.Column('products_project_security_issues_2', sa.Integer(), nullable=True), - sa.Column('products_project_access_to_test_application_1', sa.Integer(), nullable=True), - sa.Column('products_project_access_to_test_application_2', sa.Integer(), nullable=True), - sa.Column('products_project_acceptance_criteria_1', sa.Integer(), nullable=True), - sa.Column('products_project_acceptance_criteria_2', sa.Integer(), nullable=True), - sa.Column('products_project_expected_functionality_1', sa.Integer(), nullable=True), - sa.Column('products_project_expected_functionality_2', sa.Integer(), nullable=True), - sa.Column('products_project_promises_well_1', sa.Integer(), nullable=True), - sa.Column('products_project_promises_well_2', sa.Integer(), nullable=True), - sa.Column('products_project_has_been_implemented_1', sa.Integer(), nullable=True), - sa.Column('products_project_has_been_implemented_2', sa.Integer(), nullable=True), - sa.Column('products_project_is_useful_1', sa.Integer(), nullable=True), - sa.Column('products_project_is_useful_2', sa.Integer(), nullable=True), - sa.Column('products_project_prototype_1', sa.Integer(), nullable=True), - sa.Column('products_project_prototype_2', sa.Integer(), nullable=True), - sa.Column('products_project_tests_1', sa.Integer(), nullable=True), - sa.Column('products_project_tests_2', sa.Integer(), nullable=True), - sa.Column('products_project_technology_1', sa.Integer(), nullable=True), - sa.Column('products_project_technology_2', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['group_id'], ['groups.id'], ), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('students_groups', - sa.Column('group_id', sa.Integer(), nullable=False), - sa.Column('student_id', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['group_id'], ['groups.id'], ), - sa.ForeignKeyConstraint(['student_id'], ['students.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'], ) - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('committees') - op.drop_table('term_of_defences') - op.drop_table('students_groups') - op.drop_table('project_grade_sheets') - op.drop_table('temporary_availabilities') - op.drop_table('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 ### diff --git a/backend/migrations/versions/559c8f18a125_.py b/backend/migrations/versions/559c8f18a125_.py new file mode 100644 index 0000000..13cfba3 --- /dev/null +++ b/backend/migrations/versions/559c8f18a125_.py @@ -0,0 +1,313 @@ +"""empty message + +Revision ID: 559c8f18a125 +Revises: +Create Date: 2023-01-14 15:25:59.137169 + +""" +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "559c8f18a125" +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + 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"), + ) + 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("duration_time", sa.Integer(), nullable=False), + sa.Column("open_enrollments", sa.String(length=1), nullable=False), + sa.Column("start_date", sa.DateTime(), nullable=False), + sa.Column("end_date", sa.DateTime(), nullable=False), + sa.Column("year_group_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["year_group_id"], + ["year_groups.id"], + ), + 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("is_coordinator", sa.Boolean(), 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), + 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( + "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( + "project_grade_sheets", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("group_id", sa.Integer(), nullable=True), + sa.Column("presentation_required_content_1", sa.Integer(), nullable=True), + sa.Column("presentation_required_content_2", sa.Integer(), nullable=True), + sa.Column("presentation_was_compatible_1", sa.Integer(), nullable=True), + sa.Column("presentation_was_compatible_2", sa.Integer(), nullable=True), + sa.Column("presentation_showing_1", sa.Integer(), nullable=True), + sa.Column("presentation_showing_2", sa.Integer(), nullable=True), + sa.Column( + "presentation_answers_to_questions_from_committee_1", + sa.Integer(), + nullable=True, + ), + sa.Column( + "presentation_answers_to_questions_from_committee_2", + sa.Integer(), + nullable=True, + ), + sa.Column("documentation_project_vision_1", sa.Integer(), nullable=True), + sa.Column("documentation_project_vision_2", sa.Integer(), nullable=True), + sa.Column("documentation_requirements_1", sa.Integer(), nullable=True), + sa.Column("documentation_requirements_2", sa.Integer(), nullable=True), + sa.Column("documentation_for_clients_1", sa.Integer(), nullable=True), + sa.Column("documentation_for_clients_2", sa.Integer(), nullable=True), + sa.Column("documentation_for_developers_1", sa.Integer(), nullable=True), + sa.Column("documentation_for_developers_2", sa.Integer(), nullable=True), + sa.Column("documentation_license_1", sa.Integer(), nullable=True), + sa.Column("documentation_license_2", sa.Integer(), nullable=True), + sa.Column("group_work_regularity_1", sa.Integer(), nullable=True), + sa.Column("group_work_regularity_2", sa.Integer(), nullable=True), + sa.Column("group_work_division_of_work_1", sa.Integer(), nullable=True), + sa.Column("group_work_division_of_work_2", sa.Integer(), nullable=True), + sa.Column("group_work_contact_with_client_1", sa.Integer(), nullable=True), + sa.Column("group_work_contact_with_client_2", sa.Integer(), nullable=True), + sa.Column("group_work_management_of_risk_1", sa.Integer(), nullable=True), + sa.Column("group_work_management_of_risk_2", sa.Integer(), nullable=True), + sa.Column("group_work_work_methodology_1", sa.Integer(), nullable=True), + sa.Column("group_work_work_methodology_2", sa.Integer(), nullable=True), + sa.Column( + "group_work_management_of_source_code_1", sa.Integer(), nullable=True + ), + sa.Column( + "group_work_management_of_source_code_2", sa.Integer(), nullable=True + ), + sa.Column("group_work_devops_1", sa.Integer(), nullable=True), + sa.Column("group_work_devops_2", sa.Integer(), nullable=True), + sa.Column( + "products_project_complexity_of_product_1", sa.Integer(), nullable=True + ), + sa.Column( + "products_project_complexity_of_product_2", sa.Integer(), nullable=True + ), + sa.Column( + "products_project_access_to_application_1", sa.Integer(), nullable=True + ), + sa.Column( + "products_project_access_to_application_2", sa.Integer(), nullable=True + ), + sa.Column("products_project_security_issues_1", sa.Integer(), nullable=True), + sa.Column("products_project_security_issues_2", sa.Integer(), nullable=True), + sa.Column( + "products_project_access_to_test_application_1", sa.Integer(), nullable=True + ), + sa.Column( + "products_project_access_to_test_application_2", sa.Integer(), nullable=True + ), + sa.Column( + "products_project_acceptance_criteria_1", sa.Integer(), nullable=True + ), + sa.Column( + "products_project_acceptance_criteria_2", sa.Integer(), nullable=True + ), + sa.Column( + "products_project_expected_functionality_1", sa.Integer(), nullable=True + ), + sa.Column( + "products_project_expected_functionality_2", sa.Integer(), nullable=True + ), + sa.Column("products_project_promises_well_1", sa.Integer(), nullable=True), + sa.Column("products_project_promises_well_2", sa.Integer(), nullable=True), + sa.Column( + "products_project_has_been_implemented_1", sa.Integer(), nullable=True + ), + sa.Column( + "products_project_has_been_implemented_2", sa.Integer(), nullable=True + ), + sa.Column("products_project_is_useful_1", sa.Integer(), nullable=True), + sa.Column("products_project_is_useful_2", sa.Integer(), nullable=True), + sa.Column("products_project_prototype_1", sa.Integer(), nullable=True), + sa.Column("products_project_prototype_2", sa.Integer(), nullable=True), + sa.Column("products_project_tests_1", sa.Integer(), nullable=True), + sa.Column("products_project_tests_2", sa.Integer(), nullable=True), + sa.Column("products_project_technology_1", sa.Integer(), nullable=True), + sa.Column("products_project_technology_2", sa.Integer(), nullable=True), + sa.ForeignKeyConstraint( + ["group_id"], + ["groups.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "students_groups", + sa.Column("group_id", sa.Integer(), nullable=False), + sa.Column("student_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["group_id"], + ["groups.id"], + ), + sa.ForeignKeyConstraint( + ["student_id"], + ["students.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.Column("chairman_of_committee", sa.Integer(), nullable=True), + sa.ForeignKeyConstraint( + ["chairman_of_committee"], + ["project_supervisors.id"], + ), + 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"], + ), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("committees") + op.drop_table("term_of_defences") + op.drop_table("students_groups") + op.drop_table("project_grade_sheets") + op.drop_table("temporary_availabilities") + op.drop_table("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 ### diff --git a/backend/pyproject.toml b/backend/pyproject.toml new file mode 100644 index 0000000..5d7bf33 --- /dev/null +++ b/backend/pyproject.toml @@ -0,0 +1,2 @@ +[tool.isort] +profile = "black" diff --git a/backend/requirements.txt b/backend/requirements.txt index ef1b6e0..52ca8a6 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -14,3 +14,6 @@ python-dotenv==0.21.0 factory_boy>=3.2.1,<3.3.0 reportlab>=3.6.12,<3.7.0 gunicorn>=20.1.0,<20.2.0 +black>=22.12.0,<22.13.0 +flake8>=6.0.0,<6.1.0 +isort>=5.11.4,<5.12.0 diff --git a/backend/setup.cfg b/backend/setup.cfg new file mode 100644 index 0000000..c0aff72 --- /dev/null +++ b/backend/setup.cfg @@ -0,0 +1,10 @@ +[flake8] +max-line-length = 88 +extend-ignore = + E203, +exclude = + migrations, + __pycache__, + tests +per-file-ignores = **/*/models.py:F401 + diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index a784b82..f8a7964 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -3,8 +3,8 @@ from typing import Generator import pytest from apiflask import APIFlask from flask import Flask -from flask.testing import FlaskClient from flask.ctx import AppContext +from flask.testing import FlaskClient from app import create_app from app.dependencies import db diff --git a/backend/tests/factory.py b/backend/tests/factory.py index 1df0a8f..040bea4 100644 --- a/backend/tests/factory.py +++ b/backend/tests/factory.py @@ -1,13 +1,13 @@ import datetime -from factory import alchemy, Sequence +from factory import Sequence, alchemy from factory.faker import Faker -from factory.fuzzy import FuzzyInteger, FuzzyDateTime +from factory.fuzzy import FuzzyDateTime, FuzzyInteger from app.dependencies import db -from app.students.models import Student, Group, YearGroupStudents -from app.project_supervisor.models import ProjectSupervisor, YearGroupProjectSupervisors from app.examination_schedule.models import ExaminationSchedule +from app.project_supervisor.models import ProjectSupervisor, YearGroupProjectSupervisors +from app.students.models import Group, Student, YearGroupStudents class ProjectSupervisorFactory(alchemy.SQLAlchemyModelFactory): @@ -15,9 +15,9 @@ class ProjectSupervisorFactory(alchemy.SQLAlchemyModelFactory): model = ProjectSupervisor sqlalchemy_session = db.session - first_name = Faker('first_name') - last_name = Faker('last_name') - email = Faker('email') + first_name = Faker("first_name") + last_name = Faker("last_name") + email = Faker("email") class YearGroupProjectSupervisorsFactory(alchemy.SQLAlchemyModelFactory): @@ -33,7 +33,7 @@ class GroupFactory(alchemy.SQLAlchemyModelFactory): model = Group sqlalchemy_session = db.session - name = Sequence(lambda n: f'Group-{n}') + name = Sequence(lambda n: f"Group-{n}") points_for_first_term = FuzzyInteger(1, 5) points_for_second_term = FuzzyInteger(1, 5) @@ -43,9 +43,9 @@ class StudentFactory(alchemy.SQLAlchemyModelFactory): model = Student sqlalchemy_session = db.session - first_name = Faker('first_name') - last_name = Faker('last_name') - email = Faker('email') + first_name = Faker("first_name") + last_name = Faker("last_name") + email = Faker("email") index = Sequence(lambda n: 400_000 + n) @@ -60,9 +60,13 @@ class ExaminationScheduleFactory(alchemy.SQLAlchemyModelFactory): model = ExaminationSchedule sqlalchemy_session = db.session - title = Sequence(lambda n: f'Examination schedule {n}') + title = Sequence(lambda n: f"Examination schedule {n}") duration_time = 30 - start_date = FuzzyDateTime(datetime.datetime(2020, 1, 1, tzinfo=datetime.timezone.utc), - datetime.datetime(2020, 1, 5, tzinfo=datetime.timezone.utc)) - end_date = FuzzyDateTime(datetime.datetime(2020, 1, 10, tzinfo=datetime.timezone.utc), - datetime.datetime(2020, 1, 20, tzinfo=datetime.timezone.utc)) + start_date = FuzzyDateTime( + datetime.datetime(2020, 1, 1, tzinfo=datetime.timezone.utc), + datetime.datetime(2020, 1, 5, tzinfo=datetime.timezone.utc), + ) + end_date = FuzzyDateTime( + datetime.datetime(2020, 1, 10, tzinfo=datetime.timezone.utc), + datetime.datetime(2020, 1, 20, tzinfo=datetime.timezone.utc), + ) diff --git a/backend/tests/fake_data.py b/backend/tests/fake_data.py index 60c740b..2091a24 100644 --- a/backend/tests/fake_data.py +++ b/backend/tests/fake_data.py @@ -1,30 +1,44 @@ from typing import List -from .factory import ProjectSupervisorFactory, YearGroupProjectSupervisorsFactory, \ - StudentFactory, YearGroupStudentsFactory, GroupFactory, ExaminationScheduleFactory -from app.dependencies import db -from app.project_supervisor.models import YearGroup, ProjectSupervisor -from app.students.models import Group, Student, YearGroupStudents -from app.examination_schedule.models import ExaminationSchedule from app.base.mode import ModeGroups +from app.dependencies import db +from app.examination_schedule.models import ExaminationSchedule +from app.project_supervisor.models import ProjectSupervisor, YearGroup +from app.students.models import Group, Student, YearGroupStudents + +from .factory import ( + ExaminationScheduleFactory, + GroupFactory, + ProjectSupervisorFactory, + StudentFactory, + YearGroupProjectSupervisorsFactory, + YearGroupStudentsFactory, +) def create_year_group(data: dict = None) -> YearGroup: if data is None: - data = {'mode': ModeGroups.STATIONARY.value, 'name': '2022/2023'} + data = {"mode": ModeGroups.STATIONARY.value, "name": "2022/2023"} yg = YearGroup(**data) db.session.add(yg) db.session.commit() return yg -def create_project_supervisors(yg: YearGroup, amount: int, limit_group: int = 3) -> List[ProjectSupervisorFactory]: +def create_project_supervisors( + yg: YearGroup, amount: int, limit_group: int = 3 +) -> List[ProjectSupervisorFactory]: ps = [ProjectSupervisorFactory() for _ in range(amount)] db.session.add_all(ps) db.session.commit() db.session.add_all( - [YearGroupProjectSupervisorsFactory(limit_group=limit_group, year_group_id=yg.id, project_supervisor_id=p.id) - for p in ps]) + [ + YearGroupProjectSupervisorsFactory( + limit_group=limit_group, year_group_id=yg.id, project_supervisor_id=p.id + ) + for p in ps + ] + ) db.session.commit() return ps @@ -47,7 +61,12 @@ def create_students(yg: YearGroup, amount: int) -> List[StudentFactory]: students = [StudentFactory() for _ in range(amount)] db.session.add_all(students) db.session.commit() - db.session.add_all([YearGroupStudentsFactory(year_group_id=yg.id, student_index=s.index) for s in students]) + db.session.add_all( + [ + YearGroupStudentsFactory(year_group_id=yg.id, student_index=s.index) + for s in students + ] + ) db.session.commit() return students @@ -57,7 +76,9 @@ def create_student(data: dict, year_group_id: int = None) -> Student: db.session.add(st) db.session.commit() if year_group_id is not None: - db.session.add(YearGroupStudents(year_group_id=year_group_id, student_index=st.index)) + db.session.add( + YearGroupStudents(year_group_id=year_group_id, student_index=st.index) + ) db.session.commit() return st @@ -76,8 +97,12 @@ def create_group(data: dict, yg: YearGroup) -> Group: return group -def create_examination_schedules(yg: YearGroup, amount: int) -> List[ExaminationScheduleFactory]: - examination_schedules = [ExaminationScheduleFactory(year_group_id=yg.id) for _ in range(amount)] +def create_examination_schedules( + yg: YearGroup, amount: int +) -> List[ExaminationScheduleFactory]: + examination_schedules = [ + ExaminationScheduleFactory(year_group_id=yg.id) for _ in range(amount) + ] db.session.add_all(examination_schedules) db.session.commit() return examination_schedules diff --git a/backend/tests/functional_tests/coordinator/test_examination_schedule.py b/backend/tests/functional_tests/coordinator/test_examination_schedule.py index fe48b1e..ea9fb68 100644 --- a/backend/tests/functional_tests/coordinator/test_examination_schedule.py +++ b/backend/tests/functional_tests/coordinator/test_examination_schedule.py @@ -1,28 +1,42 @@ import datetime -from ...utils import _test_case_client, _test_case_client_without_response, assert_model_changes -from ...fake_data import create_examination_schedule, create_year_group, create_examination_schedules from app.dependencies import db -valid_data = { - 'title': 'examination schedule summer', - 'start_date': datetime.datetime.now() + datetime.timedelta(days=5), - 'end_date': datetime.datetime.now() + datetime.timedelta(days=10), - 'duration_time': 30 +from ...fake_data import ( + create_examination_schedule, + create_examination_schedules, + create_year_group, +) +from ...utils import ( + _test_case_client, + _test_case_client_without_response, + assert_model_changes, +) +valid_data = { + "title": "examination schedule summer", + "start_date": datetime.datetime.now() + datetime.timedelta(days=5), + "end_date": datetime.datetime.now() + datetime.timedelta(days=10), + "duration_time": 30, } ex_data = { - 'title': 'new title', - 'start_date': (datetime.datetime.now() + datetime.timedelta(days=5)).strftime("%Y-%m-%dT%H:%M:%S.000Z"), - 'end_date': (datetime.datetime.now() + datetime.timedelta(days=10)).strftime("%Y-%m-%dT%H:%M:%S.000Z") + "title": "new title", + "start_date": (datetime.datetime.now() + datetime.timedelta(days=5)).strftime( + "%Y-%m-%dT%H:%M:%S.000Z" + ), + "end_date": (datetime.datetime.now() + datetime.timedelta(days=10)).strftime( + "%Y-%m-%dT%H:%M:%S.000Z" + ), } enrollments_data = { - 'start_date_for_enrollment_students': (datetime.datetime.now() + datetime.timedelta(days=2)).strftime( - "%Y-%m-%dT%H:%M:%S.000Z"), - 'end_date_for_enrollment_students': (datetime.datetime.now() + datetime.timedelta(days=4)).strftime( - "%Y-%m-%dT%H:%M:%S.000Z") + "start_date_for_enrollment_students": ( + datetime.datetime.now() + datetime.timedelta(days=2) + ).strftime("%Y-%m-%dT%H:%M:%S.000Z"), + "end_date_for_enrollment_students": ( + datetime.datetime.now() + datetime.timedelta(days=4) + ).strftime("%Y-%m-%dT%H:%M:%S.000Z"), } @@ -31,89 +45,169 @@ def test_list_examination_schedules(test_app_with_context) -> None: year_group = create_year_group() create_examination_schedules(year_group, 34) - url = f'/api/coordinator/examination_schedule/{year_group.id}/?per_page=10' - data = _test_case_client_without_response(client, url, None, 200, method='get') - assert data.get('max_pages') == 4 - assert len(data.get('examination_schedules')) == 10 + url = f"/api/coordinator/examination_schedule/{year_group.id}/?per_page=10" + data = _test_case_client_without_response(client, url, None, 200, method="get") + assert data.get("max_pages") == 4 + assert len(data.get("examination_schedules")) == 10 def test_delete_examination_schedule(test_app_with_context) -> None: with test_app_with_context.test_client() as client: year_group = create_year_group() ex = create_examination_schedules(year_group, 1)[0] - _test_case_client(client, f'/api/coordinator/examination_schedule/{ex.id}/', None, - 'Examination schedule was deleted!', 200, method='delete') + _test_case_client( + client, + f"/api/coordinator/examination_schedule/{ex.id}/", + None, + "Examination schedule was deleted!", + 200, + method="delete", + ) -def test_delete_examination_schedule_if_examination_schedule_doesnt_exist(test_app_with_context) -> None: +def test_delete_examination_schedule_if_examination_schedule_doesnt_exist( + test_app_with_context, +) -> None: with test_app_with_context.test_client() as client: - _test_case_client(client, '/api/coordinator/examination_schedule/32/', None, - "Examination schedule doesn't exist!", 404, method='delete', key='error') + _test_case_client( + client, + "/api/coordinator/examination_schedule/32/", + None, + "Examination schedule doesn't exist!", + 404, + method="delete", + key="error", + ) def test_update_examination_schedule(test_app_with_context) -> None: with test_app_with_context.test_client() as client: year_group = create_year_group() ex = create_examination_schedules(year_group, 1)[0] - _test_case_client(client, f'/api/coordinator/examination_schedule/{ex.id}/', ex_data, - 'Examination schedule was updated!', 200, method='put') + _test_case_client( + client, + f"/api/coordinator/examination_schedule/{ex.id}/", + ex_data, + "Examination schedule was updated!", + 200, + method="put", + ) assert_model_changes(ex, ex_data) -def test_update_examination_schedule_schedule_if_examination_schedule_doesnt_exist(test_app_with_context) -> None: +def test_update_examination_schedule_schedule_if_examination_schedule_doesnt_exist( + test_app_with_context, +) -> None: with test_app_with_context.test_client() as client: - _test_case_client(client, '/api/coordinator/examination_schedule/32/', ex_data, - "Examination schedule doesn't exist!", 404, method='put', key='error') + _test_case_client( + client, + "/api/coordinator/examination_schedule/32/", + ex_data, + "Examination schedule doesn't exist!", + 404, + method="put", + key="error", + ) def test_set_date_of_examination_schedule(test_app_with_context) -> None: with test_app_with_context.test_client() as client: year_group = create_year_group() ex = create_examination_schedule(valid_data, year_group) - _test_case_client(client, f'/api/coordinator/examination_schedule/{ex.id}/date/', enrollments_data, - 'You set date of examination schedule!', 200, method='put') + _test_case_client( + client, + f"/api/coordinator/examination_schedule/{ex.id}/date/", + enrollments_data, + "You set date of examination schedule!", + 200, + method="put", + ) assert_model_changes(ex, enrollments_data) -def test_set_date_of_examination_schedule_if_examination_schedule_doesnt_exist(test_app_with_context) -> None: +def test_set_date_of_examination_schedule_if_examination_schedule_doesnt_exist( + test_app_with_context, +) -> None: with test_app_with_context.test_client() as client: - _test_case_client(client, '/api/coordinator/examination_schedule/43/date/', enrollments_data, - 'Examination schedule doesn\'t exist!', 404, method='put', key='error') + _test_case_client( + client, + "/api/coordinator/examination_schedule/43/date/", + enrollments_data, + "Examination schedule doesn't exist!", + 404, + method="put", + key="error", + ) -def test_set_date_of_examination_schedule_with_invalid_date(test_app_with_context) -> None: +def test_set_date_of_examination_schedule_with_invalid_date( + test_app_with_context, +) -> None: invalid_dates = { - 'start_date_for_enrollment_students': enrollments_data['end_date_for_enrollment_students'], - 'end_date_for_enrollment_students': enrollments_data['start_date_for_enrollment_students'], + "start_date_for_enrollment_students": enrollments_data[ + "end_date_for_enrollment_students" + ], + "end_date_for_enrollment_students": enrollments_data[ + "start_date_for_enrollment_students" + ], } with test_app_with_context.test_client() as client: year_group = create_year_group() ex = create_examination_schedule(valid_data, year_group) - _test_case_client(client, f'/api/coordinator/examination_schedule/{ex.id}/date/', invalid_dates, - 'Invalid data! End date must be greater than start date!', 400, method='put', key='error') + _test_case_client( + client, + f"/api/coordinator/examination_schedule/{ex.id}/date/", + invalid_dates, + "Invalid data! End date must be greater than start date!", + 400, + method="put", + key="error", + ) def test_create_project_supervisors(test_app_with_context) -> None: with test_app_with_context.test_client() as client: year_group = create_year_group() - _test_case_client(client, f'/api/coordinator/examination_schedule/{year_group.id}/', ex_data, - 'Examination schedule was created!', 201, method='post') + _test_case_client( + client, + f"/api/coordinator/examination_schedule/{year_group.id}/", + ex_data, + "Examination schedule was created!", + 201, + method="post", + ) -def test_create_project_supervisors_if_year_group_doesnt_exist(test_app_with_context) -> None: +def test_create_project_supervisors_if_year_group_doesnt_exist( + test_app_with_context, +) -> None: with test_app_with_context.test_client() as client: - _test_case_client(client, '/api/coordinator/examination_schedule/33/', ex_data, - 'Year group doesn\'t exist!', 404, method='post', key='error') + _test_case_client( + client, + "/api/coordinator/examination_schedule/33/", + ex_data, + "Year group doesn't exist!", + 404, + method="post", + key="error", + ) def test_create_project_supervisors_with_invalid_dates(test_app_with_context) -> None: invalid_data = { - 'title': 'examination schedule winter', - 'start_date': ex_data['end_date'], - 'end_date': ex_data['start_date'] + "title": "examination schedule winter", + "start_date": ex_data["end_date"], + "end_date": ex_data["start_date"], } with test_app_with_context.test_client() as client: year_group = create_year_group() - _test_case_client(client, f'/api/coordinator/examination_schedule/{year_group.id}/', invalid_data, - 'Invalid data! End date must be greater than start date!', 400, method='post', key='error') + _test_case_client( + client, + f"/api/coordinator/examination_schedule/{year_group.id}/", + invalid_data, + "Invalid data! End date must be greater than start date!", + 400, + method="post", + key="error", + ) diff --git a/backend/tests/functional_tests/coordinator/test_groups.py b/backend/tests/functional_tests/coordinator/test_groups.py index 08a1bc4..ac90d4c 100644 --- a/backend/tests/functional_tests/coordinator/test_groups.py +++ b/backend/tests/functional_tests/coordinator/test_groups.py @@ -2,24 +2,32 @@ import copy from flask import current_app -from ...utils import _test_case_client, _test_case_client_without_response, assert_model_changes, _test_case_group -from ...fake_data import create_year_group, create_groups, create_group, create_students, create_project_supervisors from app.dependencies import db -from app.students.models import Group from app.project_supervisor.models import YearGroupProjectSupervisors +from app.students.models import Group -valid_data = { - 'name': 'System Pri' -} +from ...fake_data import ( + create_group, + create_groups, + create_project_supervisors, + create_students, + create_year_group, +) +from ...utils import ( + _test_case_client, + _test_case_client_without_response, + _test_case_group, + assert_model_changes, +) -new_data = { - 'name': 'Mobile app' -} +valid_data = {"name": "System Pri"} + +new_data = {"name": "Mobile app"} invalid_data = { - 'name': 'Mobile app v2', - 'students': [123_344, 455_444], - 'project_supervisor_id': 1 + "name": "Mobile app v2", + "students": [123_344, 455_444], + "project_supervisor_id": 1, } @@ -27,39 +35,69 @@ def test_list_groups(test_app_with_context) -> None: with test_app_with_context.test_client() as client: yg = create_year_group() create_groups(yg, 33) - data = _test_case_client_without_response(client, f'/api/coordinator/groups/{yg.id}/?per_page=10', None, 200, - method='get') - assert data.get('max_pages') == 4 - assert len(data.get('groups')) == 10 + data = _test_case_client_without_response( + client, + f"/api/coordinator/groups/{yg.id}/?per_page=10", + None, + 200, + method="get", + ) + assert data.get("max_pages") == 4 + assert len(data.get("groups")) == 10 def test_detail_group(test_app_with_context) -> None: with test_app_with_context.test_client() as client: yg = create_year_group() group = create_group(valid_data, yg) - data = _test_case_client_without_response(client, f'/api/coordinator/groups/{group.id}/detail/', None, 200, - method='get') + data = _test_case_client_without_response( + client, + f"/api/coordinator/groups/{group.id}/detail/", + None, + 200, + method="get", + ) assert_model_changes(group, data) def test_detail_group_if_group_doesnt_exist(test_app_with_context) -> None: with test_app_with_context.test_client() as client: - _test_case_client(client, '/api/coordinator/groups/11/detail/', None, 'Not found group!', 404, method='get', - key='error') + _test_case_client( + client, + "/api/coordinator/groups/11/detail/", + None, + "Not found group!", + 404, + method="get", + key="error", + ) def test_delete_group(test_app_with_context) -> None: with test_app_with_context.test_client() as client: yg = create_year_group() group = create_group(valid_data, yg) - _test_case_client(client, f'/api/coordinator/groups/{group.id}/', None, 'Group was deleted!', 202, - method='delete') + _test_case_client( + client, + f"/api/coordinator/groups/{group.id}/", + None, + "Group was deleted!", + 202, + method="delete", + ) def test_delete_group_if_group_doesnt_exist(test_app_with_context) -> None: with test_app_with_context.test_client() as client: - _test_case_client(client, '/api/coordinator/groups/32/', None, 'Not found group!', 404, method='delete', - key='error') + _test_case_client( + client, + "/api/coordinator/groups/32/", + None, + "Not found group!", + 404, + method="delete", + key="error", + ) def test_edit_group(test_app_with_context) -> None: @@ -69,11 +107,17 @@ def test_edit_group(test_app_with_context) -> None: students = create_students(yg, 3) ps = create_project_supervisors(yg, 1)[0] group = create_group(valid_data, yg) - data['students'] = [student.index for student in students] - data['project_supervisor_id'] = ps.id + data["students"] = [student.index for student in students] + data["project_supervisor_id"] = ps.id - _test_case_client(client, f'/api/coordinator/groups/{group.id}/', data, 'Group was updated!', 200, - method='put') + _test_case_client( + client, + f"/api/coordinator/groups/{group.id}/", + data, + "Group was updated!", + 200, + method="put", + ) _test_case_group(group, data) @@ -83,18 +127,32 @@ def test_edit_group_with_invalid_project_supervisor_id(test_app_with_context) -> yg = create_year_group() students = create_students(yg, 3) group = create_group(valid_data, yg) - data['students'] = [student.index for student in students] - data['project_supervisor_id'] = 10 - _test_case_client(client, f'/api/coordinator/groups/{group.id}/', data, 'Not found project supervisor!', 404, - method='put', key='error') + data["students"] = [student.index for student in students] + data["project_supervisor_id"] = 10 + _test_case_client( + client, + f"/api/coordinator/groups/{group.id}/", + data, + "Not found project supervisor!", + 404, + method="put", + key="error", + ) def test_edit_group_with_invalid_data(test_app_with_context) -> None: with test_app_with_context.test_client() as client: yg = create_year_group() group = create_group(valid_data, yg) - data = {'students': [123_4356, 243_533, 434_343]} - _test_case_client(client, f'/api/coordinator/groups/{group.id}/', data, 'Validation error', 400, method='put') + data = {"students": [123_4356, 243_533, 434_343]} + _test_case_client( + client, + f"/api/coordinator/groups/{group.id}/", + data, + "Validation error", + 400, + method="put", + ) def test_edit_group_with_invalid_student_indexes(test_app_with_context) -> None: @@ -102,21 +160,42 @@ def test_edit_group_with_invalid_student_indexes(test_app_with_context) -> None: with test_app_with_context.test_client() as client: yg = create_year_group() group = create_group(valid_data, yg) - data['students'] = [123_456, 243_533, 434_343] - _test_case_client(client, f'/api/coordinator/groups/{group.id}/', data, 'Not found students!', 404, - method='put', key='error') + data["students"] = [123_456, 243_533, 434_343] + _test_case_client( + client, + f"/api/coordinator/groups/{group.id}/", + data, + "Not found students!", + 404, + method="put", + key="error", + ) def test_edit_group_if_group_doesnt_exist(test_app_with_context) -> None: with test_app_with_context.test_client() as client: - _test_case_client(client, '/api/coordinator/groups/333/', new_data, 'Not found group!', 404, - method='put', key='error') + _test_case_client( + client, + "/api/coordinator/groups/333/", + new_data, + "Not found group!", + 404, + method="put", + key="error", + ) def test_edit_group_if_you_pass_empty_data(test_app_with_context) -> None: with test_app_with_context.test_client() as client: - _test_case_client(client, '/api/coordinator/groups/333/', {}, 'You have passed empty data!', 400, - method='put', key='error') + _test_case_client( + client, + "/api/coordinator/groups/333/", + {}, + "You have passed empty data!", + 400, + method="put", + key="error", + ) def test_create_group(test_app_with_context) -> None: @@ -125,26 +204,46 @@ def test_create_group(test_app_with_context) -> None: yg = create_year_group() students = create_students(yg, 3) ps = create_project_supervisors(yg, 1)[0] - data['students'] = [student.index for student in students] - data['project_supervisor_id'] = ps.id + data["students"] = [student.index for student in students] + data["project_supervisor_id"] = ps.id - _test_case_client(client, f'/api/coordinator/groups/{yg.id}/', data, 'Group was created!', 201, - method='post') + _test_case_client( + client, + f"/api/coordinator/groups/{yg.id}/", + data, + "Group was created!", + 201, + method="post", + ) assert Group.query.count() == 1 _test_case_group(Group.query.first(), data) def test_create_group_if_year_group_doesnt_exist(test_app_with_context) -> None: with test_app_with_context.test_client() as client: - _test_case_client(client, '/api/coordinator/groups/22/', invalid_data, 'Not found year group!', 404, - method='post', key='error') + _test_case_client( + client, + "/api/coordinator/groups/22/", + invalid_data, + "Not found year group!", + 404, + method="post", + key="error", + ) def test_create_group_if_project_supervisor_doesnt_exist(test_app_with_context) -> None: with test_app_with_context.test_client() as client: yg = create_year_group() - _test_case_client(client, f'/api/coordinator/groups/{yg.id}/', invalid_data, 'Not found project supervisor!', - 404, method='post', key='error') + _test_case_client( + client, + f"/api/coordinator/groups/{yg.id}/", + invalid_data, + "Not found project supervisor!", + 404, + method="post", + key="error", + ) def test_create_group_if_you_exceed_the_group_limit(test_app_with_context) -> None: @@ -152,14 +251,21 @@ def test_create_group_if_you_exceed_the_group_limit(test_app_with_context) -> No with test_app_with_context.test_client() as client: yg = create_year_group() ps = create_project_supervisors(yg, 1)[0] - data['project_supervisor_id'] = ps.id - limit_group = current_app.config.get('LIMIT_STUDENTS_PER_GROUP') + data["project_supervisor_id"] = ps.id + limit_group = current_app.config.get("LIMIT_STUDENTS_PER_GROUP") - data['students'].extend([999_000 + i for i in range(limit_group + 4)]) + data["students"].extend([999_000 + i for i in range(limit_group + 4)]) - _test_case_client(client, f'/api/coordinator/groups/{yg.id}/', data, - f"Too much students you want add to group, The group can have only {limit_group}" - " students", 400, method='post', key='error') + _test_case_client( + client, + f"/api/coordinator/groups/{yg.id}/", + data, + f"Too much students you want add to group, The group can have only {limit_group}" + " students", + 400, + method="post", + key="error", + ) def test_create_group_if_students_doesnt_exist(test_app_with_context) -> None: @@ -167,36 +273,55 @@ def test_create_group_if_students_doesnt_exist(test_app_with_context) -> None: with test_app_with_context.test_client() as client: yg = create_year_group() ps = create_project_supervisors(yg, 1)[0] - data['project_supervisor_id'] = ps.id - _test_case_client(client, f'/api/coordinator/groups/{yg.id}/', data, "Not found students!", 404, method='post', - key='error') + data["project_supervisor_id"] = ps.id + _test_case_client( + client, + f"/api/coordinator/groups/{yg.id}/", + data, + "Not found students!", + 404, + method="post", + key="error", + ) -def test_create_group_if_at_least_one_student_belong_to_other_group(test_app_with_context) -> None: +def test_create_group_if_at_least_one_student_belong_to_other_group( + test_app_with_context, +) -> None: data = copy.deepcopy(invalid_data) with test_app_with_context.test_client() as client: yg = create_year_group() ps = create_project_supervisors(yg, 1)[0] group = create_group(valid_data, yg) - data['project_supervisor_id'] = ps.id + data["project_supervisor_id"] = ps.id student = create_students(yg, 1)[0] group.students.append(student) db.session.commit() - data['students'].extend([student.index]) - _test_case_client(client, f'/api/coordinator/groups/{yg.id}/', data, - "One or more students have already belonged to group!", 400, method='post', key='error') + data["students"].extend([student.index]) + _test_case_client( + client, + f"/api/coordinator/groups/{yg.id}/", + data, + "One or more students have already belonged to group!", + 400, + method="post", + key="error", + ) -def test_create_group_if_limit_of_group_was_exceed_for_project_supervisor(test_app_with_context) -> None: +def test_create_group_if_limit_of_group_was_exceed_for_project_supervisor( + test_app_with_context, +) -> None: data = copy.deepcopy(invalid_data) with test_app_with_context.test_client() as client: yg = create_year_group() ps = create_project_supervisors(yg, 1)[0] - data['project_supervisor_id'] = ps.id + data["project_supervisor_id"] = ps.id - ygps = YearGroupProjectSupervisors.query.filter_by(project_supervisor_id=ps.id, year_group_id=yg.id). \ - first() + ygps = YearGroupProjectSupervisors.query.filter_by( + project_supervisor_id=ps.id, year_group_id=yg.id + ).first() limit_group = ygps.limit_group groups = create_groups(yg, limit_group) @@ -204,6 +329,12 @@ def test_create_group_if_limit_of_group_was_exceed_for_project_supervisor(test_a group.project_supervisor_id = ps.id db.session.commit() - _test_case_client(client, f'/api/coordinator/groups/{yg.id}/', data, - "Can't create new group, project supervisor achieved a limit of groups", - 400, method='post', key='error') + _test_case_client( + client, + f"/api/coordinator/groups/{yg.id}/", + data, + "Can't create new group, project supervisor achieved a limit of groups", + 400, + method="post", + key="error", + ) diff --git a/backend/tests/functional_tests/coordinator/test_project_supervisors.py b/backend/tests/functional_tests/coordinator/test_project_supervisors.py index c67f098..14dfdbe 100644 --- a/backend/tests/functional_tests/coordinator/test_project_supervisors.py +++ b/backend/tests/functional_tests/coordinator/test_project_supervisors.py @@ -1,27 +1,40 @@ -from ...utils import _test_case_client, _test_case_client_without_response, assert_model_changes -from ...fake_data import create_project_supervisors, create_year_group, create_dummy_group, create_dummy_ps from app.dependencies import db from app.project_supervisor.models import YearGroupProjectSupervisors +from ...fake_data import ( + create_dummy_group, + create_dummy_ps, + create_project_supervisors, + create_year_group, +) +from ...utils import ( + _test_case_client, + _test_case_client_without_response, + assert_model_changes, +) + valid_data = { - 'first_name': 'John', - 'last_name': 'Smith', - 'email': 'johnsmith@gmail.com' + "first_name": "John", + "last_name": "Smith", + "email": "johnsmith@gmail.com", } -year_group_ps_data = { - 'limit_group': 3 -} +year_group_ps_data = {"limit_group": 3} def test_list_project_supervisors(test_app_with_context) -> None: with test_app_with_context.test_client() as client: year_group = create_year_group() create_project_supervisors(year_group, 25) - data = _test_case_client_without_response(client, '/api/coordinator/project_supervisor/?per_page=10', None, 200, - method='get') - assert data.get('max_pages') == 3 - assert len(data.get('project_supervisors')) == 10 + data = _test_case_client_without_response( + client, + "/api/coordinator/project_supervisor/?per_page=10", + None, + 200, + method="get", + ) + assert data.get("max_pages") == 3 + assert len(data.get("project_supervisors")) == 10 def test_list_project_supervisors_by_year_group(test_app_with_context) -> None: @@ -30,184 +43,343 @@ def test_list_project_supervisors_by_year_group(test_app_with_context) -> None: year_group_2 = create_year_group() create_project_supervisors(year_group, 12) create_project_supervisors(year_group_2, 24) - data = _test_case_client_without_response(client, - f'/api/coordinator/project_supervisor/{year_group.id}/?per_page=10', - None, 200, method='get') - assert data.get('max_pages') == 2 - assert len(data.get('project_supervisors')) == 10 + data = _test_case_client_without_response( + client, + f"/api/coordinator/project_supervisor/{year_group.id}/?per_page=10", + None, + 200, + method="get", + ) + assert data.get("max_pages") == 2 + assert len(data.get("project_supervisors")) == 10 def test_create_project_supervisors(test_app_with_context) -> None: with test_app_with_context.test_client() as client: - _test_case_client(client, '/api/coordinator/project_supervisor/', valid_data, - 'Project Supervisor was created!', 201, method='post') + _test_case_client( + client, + "/api/coordinator/project_supervisor/", + valid_data, + "Project Supervisor was created!", + 201, + method="post", + ) def test_create_project_supervisors_with_invalid_data(test_app_with_context) -> None: - data = { - 'first_name': 'John', - 'last_name': 'Smith', - 'email': 'johnsmitl.com' - } + data = {"first_name": "John", "last_name": "Smith", "email": "johnsmitl.com"} with test_app_with_context.test_client() as client: - _test_case_client(client, '/api/coordinator/project_supervisor/', data, - 'Validation error', 400, method='post') + _test_case_client( + client, + "/api/coordinator/project_supervisor/", + data, + "Validation error", + 400, + method="post", + ) -def test_create_project_supervisors_if_project_supervisor_has_already_exist(test_app_with_context) -> None: +def test_create_project_supervisors_if_project_supervisor_has_already_exist( + test_app_with_context, +) -> None: with test_app_with_context.test_client() as client: create_dummy_ps(valid_data) - _test_case_client(client, '/api/coordinator/project_supervisor/', valid_data, - 'Project Supervisor has already exists!', 400, method='post', key='error') + _test_case_client( + client, + "/api/coordinator/project_supervisor/", + valid_data, + "Project Supervisor has already exists!", + 400, + method="post", + key="error", + ) def test_detail_project_supervisor(test_app_with_context) -> None: with test_app_with_context.test_client() as client: ps = create_dummy_ps(valid_data) - data = _test_case_client_without_response(client, f'/api/coordinator/project_supervisor/{ps.id}/detail/', - None, 200, method='get') + data = _test_case_client_without_response( + client, + f"/api/coordinator/project_supervisor/{ps.id}/detail/", + None, + 200, + method="get", + ) assert_model_changes(ps, data) -def test_detail_project_supervisor_if_project_supervisor_doesnt_exist(test_app_with_context) -> None: +def test_detail_project_supervisor_if_project_supervisor_doesnt_exist( + test_app_with_context, +) -> None: with test_app_with_context.test_client() as client: - _test_case_client(client, '/api/coordinator/project_supervisor/23/detail/', None, - 'Not found project supervisor!', 404, method='get', key='error') + _test_case_client( + client, + "/api/coordinator/project_supervisor/23/detail/", + None, + "Not found project supervisor!", + 404, + method="get", + key="error", + ) def test_delete_project_supervisor(test_app_with_context) -> None: with test_app_with_context.test_client() as client: ps = create_dummy_ps(valid_data) - _test_case_client(client, f'/api/coordinator/project_supervisor/{ps.id}/', None, - 'Project Supervisor was deleted!', 200, method='delete') + _test_case_client( + client, + f"/api/coordinator/project_supervisor/{ps.id}/", + None, + "Project Supervisor was deleted!", + 200, + method="delete", + ) -def test_delete_project_supervisor_if_project_supervisor_doesnt_exist(test_app_with_context) -> None: +def test_delete_project_supervisor_if_project_supervisor_doesnt_exist( + test_app_with_context, +) -> None: with test_app_with_context.test_client() as client: - _test_case_client(client, '/api/coordinator/project_supervisor/23/', None, - 'Not found project supervisor!', 404, method='delete', key='error') + _test_case_client( + client, + "/api/coordinator/project_supervisor/23/", + None, + "Not found project supervisor!", + 404, + method="delete", + key="error", + ) -def test_delete_project_supervisor_if_project_supervisor_has_got_at_least_one_group(test_app_with_context) -> None: +def test_delete_project_supervisor_if_project_supervisor_has_got_at_least_one_group( + test_app_with_context, +) -> None: with test_app_with_context.test_client() as client: ps = create_dummy_ps(valid_data) - create_dummy_group({'name': 'new project'}, ps.id) - _test_case_client(client, f'/api/coordinator/project_supervisor/{ps.id}/', None, - 'Project Supervisor has at least one group!', 400, method='delete', key='error') + create_dummy_group({"name": "new project"}, ps.id) + _test_case_client( + client, + f"/api/coordinator/project_supervisor/{ps.id}/", + None, + "Project Supervisor has at least one group!", + 400, + method="delete", + key="error", + ) def test_edit_project_supervisor(test_app_with_context) -> None: new_data = { - 'first_name': 'Albert', - 'last_name': 'Einstein', - 'email': 'albertmc2@gmail.com' + "first_name": "Albert", + "last_name": "Einstein", + "email": "albertmc2@gmail.com", } with test_app_with_context.test_client() as client: ps = create_dummy_ps(valid_data) - _test_case_client(client, f'/api/coordinator/project_supervisor/{ps.id}/', new_data, - 'Project Supervisor was updated!', 200, method='put') + _test_case_client( + client, + f"/api/coordinator/project_supervisor/{ps.id}/", + new_data, + "Project Supervisor was updated!", + 200, + method="put", + ) assert_model_changes(ps, new_data) def test_edit_project_supervisor_with_invalid_data(test_app_with_context) -> None: - invalid_data = { - 'first_name': 'Mark', - 'last_name': 'Smith', - 'email': 'invalidemail' - } + invalid_data = {"first_name": "Mark", "last_name": "Smith", "email": "invalidemail"} with test_app_with_context.test_client() as client: ps = create_dummy_ps(valid_data) - _test_case_client(client, f'/api/coordinator/project_supervisor/{ps.id}/', invalid_data, - 'Validation error', 400, method='put') + _test_case_client( + client, + f"/api/coordinator/project_supervisor/{ps.id}/", + invalid_data, + "Validation error", + 400, + method="put", + ) -def test_edit_project_supervisor_if_project_supervisor_doesnt_exist(test_app_with_context) -> None: +def test_edit_project_supervisor_if_project_supervisor_doesnt_exist( + test_app_with_context, +) -> None: with test_app_with_context.test_client() as client: - _test_case_client(client, '/api/coordinator/project_supervisor/2332/', valid_data, - 'Not found project supervisor!', 404, method='put', key='error') + _test_case_client( + client, + "/api/coordinator/project_supervisor/2332/", + valid_data, + "Not found project supervisor!", + 404, + method="put", + key="error", + ) def test_add_project_supervisor_to_year_group(test_app_with_context) -> None: with test_app_with_context.test_client() as client: ps = create_dummy_ps(valid_data) yg = create_year_group() - _test_case_client(client, f'/api/coordinator/project_supervisor/{ps.id}/year-group/{yg.id}/', - year_group_ps_data, "Project Supervisor was added to year group!", 201, method='post') + _test_case_client( + client, + f"/api/coordinator/project_supervisor/{ps.id}/year-group/{yg.id}/", + year_group_ps_data, + "Project Supervisor was added to year group!", + 201, + method="post", + ) -def test_add_project_supervisor_to_year_group_if_project_supervisor_doesnt_exist(test_app_with_context) -> None: +def test_add_project_supervisor_to_year_group_if_project_supervisor_doesnt_exist( + test_app_with_context, +) -> None: with test_app_with_context.test_client() as client: yg = create_year_group() - _test_case_client(client, f'/api/coordinator/project_supervisor/454/year-group/{yg.id}/', year_group_ps_data, - "Not found project supervisor!", 404, method='post', key='error') + _test_case_client( + client, + f"/api/coordinator/project_supervisor/454/year-group/{yg.id}/", + year_group_ps_data, + "Not found project supervisor!", + 404, + method="post", + key="error", + ) -def test_add_project_supervisor_to_year_group_if_year_group_doesnt_exist(test_app_with_context) -> None: +def test_add_project_supervisor_to_year_group_if_year_group_doesnt_exist( + test_app_with_context, +) -> None: with test_app_with_context.test_client() as client: ps = create_dummy_ps(valid_data) - _test_case_client(client, f'/api/coordinator/project_supervisor/{ps.id}/year-group/2/', year_group_ps_data, - "Not found year group!", 404, method='post', key='error') + _test_case_client( + client, + f"/api/coordinator/project_supervisor/{ps.id}/year-group/2/", + year_group_ps_data, + "Not found year group!", + 404, + method="post", + key="error", + ) def test_add_project_supervisor_to_year_group_if_project_supervisor_has_already_assigned_to_year_group( - test_app_with_context) -> None: + test_app_with_context, +) -> None: with test_app_with_context.test_client() as client: ps = create_dummy_ps(valid_data) yg = create_year_group() - ygps = YearGroupProjectSupervisors(project_supervisor_id=ps.id, year_group_id=yg.id, limit_group=2) + ygps = YearGroupProjectSupervisors( + project_supervisor_id=ps.id, year_group_id=yg.id, limit_group=2 + ) db.session.add(ygps) db.session.commit() - _test_case_client(client, f'/api/coordinator/project_supervisor/{ps.id}/year-group/{yg.id}/', - year_group_ps_data, "Project supervisor is assigned to this year group!", 400, method='post', - key='error') + _test_case_client( + client, + f"/api/coordinator/project_supervisor/{ps.id}/year-group/{yg.id}/", + year_group_ps_data, + "Project supervisor is assigned to this year group!", + 400, + method="post", + key="error", + ) def test_delete_project_supervisor_to_year_group(test_app_with_context) -> None: with test_app_with_context.test_client() as client: ps = create_dummy_ps(valid_data) yg = create_year_group() - _test_case_client(client, f'/api/coordinator/project_supervisor/{ps.id}/year-group/{yg.id}/', None, - "Project Supervisor was removed from this year group!", 200, method='delete') + _test_case_client( + client, + f"/api/coordinator/project_supervisor/{ps.id}/year-group/{yg.id}/", + None, + "Project Supervisor was removed from this year group!", + 200, + method="delete", + ) assert len(YearGroupProjectSupervisors.query.all()) == 0 -def test_delete_project_supervisor_to_year_group_if_project_supervisor_doesnt_exist(test_app_with_context) -> None: +def test_delete_project_supervisor_to_year_group_if_project_supervisor_doesnt_exist( + test_app_with_context, +) -> None: with test_app_with_context.test_client() as client: yg = create_year_group() - _test_case_client(client, f'/api/coordinator/project_supervisor/5/year-group/{yg.id}/', None, - "Not found project supervisor!", 404, method='delete', key='error') + _test_case_client( + client, + f"/api/coordinator/project_supervisor/5/year-group/{yg.id}/", + None, + "Not found project supervisor!", + 404, + method="delete", + key="error", + ) -def test_delete_project_supervisor_to_year_group_if_year_group_doesnt_exist(test_app_with_context) -> None: +def test_delete_project_supervisor_to_year_group_if_year_group_doesnt_exist( + test_app_with_context, +) -> None: with test_app_with_context.test_client() as client: ps = create_dummy_ps(valid_data) - _test_case_client(client, f'/api/coordinator/project_supervisor/{ps.id}/year-group/23/', None, - "Not found year group!", 404, method='delete', key='error') + _test_case_client( + client, + f"/api/coordinator/project_supervisor/{ps.id}/year-group/23/", + None, + "Not found year group!", + 404, + method="delete", + key="error", + ) def test_update_limit_of_group_for_project_supervisor(test_app_with_context) -> None: with test_app_with_context.test_client() as client: ps = create_dummy_ps(valid_data) yg = create_year_group() - ygps = YearGroupProjectSupervisors(project_supervisor_id=ps.id, year_group_id=yg.id, limit_group=6) + ygps = YearGroupProjectSupervisors( + project_supervisor_id=ps.id, year_group_id=yg.id, limit_group=6 + ) db.session.add(ygps) db.session.commit() - _test_case_client(client, f'/api/coordinator/project_supervisor/{ps.id}/year-group/{yg.id}/', - year_group_ps_data, "Limit of group was changed!", 200, method='put') + _test_case_client( + client, + f"/api/coordinator/project_supervisor/{ps.id}/year-group/{yg.id}/", + year_group_ps_data, + "Limit of group was changed!", + 200, + method="put", + ) assert_model_changes(ygps, year_group_ps_data) -def test_update_limit_of_group_for_project_supervisor_if_project_supervisor_doesnt_exist(test_app_with_context) -> None: +def test_update_limit_of_group_for_project_supervisor_if_project_supervisor_doesnt_exist( + test_app_with_context, +) -> None: with test_app_with_context.test_client() as client: yg = create_year_group() - _test_case_client(client, f'/api/coordinator/project_supervisor/34/year-group/{yg.id}/', - year_group_ps_data, "Not found project supervisor!", 404, method='put', key='error') + _test_case_client( + client, + f"/api/coordinator/project_supervisor/34/year-group/{yg.id}/", + year_group_ps_data, + "Not found project supervisor!", + 404, + method="put", + key="error", + ) -def test_update_limit_of_group_for_project_supervisor_if_year_group_doesnt_exist(test_app_with_context) -> None: +def test_update_limit_of_group_for_project_supervisor_if_year_group_doesnt_exist( + test_app_with_context, +) -> None: with test_app_with_context.test_client() as client: ps = create_dummy_ps(valid_data) - _test_case_client(client, f'/api/coordinator/project_supervisor/{ps.id}/year-group/34/', - year_group_ps_data, "Not found year group!", 404, method='put', key='error') + _test_case_client( + client, + f"/api/coordinator/project_supervisor/{ps.id}/year-group/34/", + year_group_ps_data, + "Not found year group!", + 404, + method="put", + key="error", + ) diff --git a/backend/tests/functional_tests/coordinator/test_students.py b/backend/tests/functional_tests/coordinator/test_students.py index 99303c4..f20b834 100644 --- a/backend/tests/functional_tests/coordinator/test_students.py +++ b/backend/tests/functional_tests/coordinator/test_students.py @@ -1,26 +1,26 @@ import copy -from ...utils import _test_case_client, _test_case_client_without_response, assert_model_changes -from ...fake_data import create_year_group, create_students, create_student +from ...fake_data import create_student, create_students, create_year_group +from ...utils import ( + _test_case_client, + _test_case_client_without_response, + assert_model_changes, +) valid_data = { - 'first_name': 'Albert', - 'last_name': 'Rose', - 'index': 234_343, - 'email': 'albert@gmail.com' + "first_name": "Albert", + "last_name": "Rose", + "index": 234_343, + "email": "albert@gmail.com", } -new_data = { - 'first_name': 'Martin', - 'last_name': 'Green', - 'pesel': '93030312894' -} +new_data = {"first_name": "Martin", "last_name": "Green", "pesel": "93030312894"} data_to_create_student = { - 'first_name': 'Albert', - 'last_name': 'Marcus', - 'pesel': '93030312896', - 'index': 123_456 + "first_name": "Albert", + "last_name": "Marcus", + "pesel": "93030312896", + "index": 123_456, } @@ -28,94 +28,174 @@ def test_list_students(test_app_with_context) -> None: with test_app_with_context.test_client() as client: yg = create_year_group() create_students(yg, 45) - data = _test_case_client_without_response(client, f'/api/coordinator/students/{yg.id}/?per_page=10', None, 200, - method='get') - assert data.get('max_pages') == 5 - assert len(data.get('students')) == 10 + data = _test_case_client_without_response( + client, + f"/api/coordinator/students/{yg.id}/?per_page=10", + None, + 200, + method="get", + ) + assert data.get("max_pages") == 5 + assert len(data.get("students")) == 10 def test_detail_student(test_app_with_context) -> None: with test_app_with_context.test_client() as client: yg = create_year_group() st = create_student(valid_data, yg.id) - data = _test_case_client_without_response(client, f'/api/coordinator/students/{st.index}/detail/', None, 200, - method='get') + data = _test_case_client_without_response( + client, + f"/api/coordinator/students/{st.index}/detail/", + None, + 200, + method="get", + ) assert_model_changes(st, data) def test_detail_student_if_student_doesnt_exist(test_app_with_context) -> None: with test_app_with_context.test_client() as client: - _test_case_client(client, '/api/coordinator/students/43/detail/', None, 'Not found student!', 404, - method='get', key='error') + _test_case_client( + client, + "/api/coordinator/students/43/detail/", + None, + "Not found student!", + 404, + method="get", + key="error", + ) def test_delete_student(test_app_with_context) -> None: with test_app_with_context.test_client() as client: yg = create_year_group() st = create_student(valid_data, yg.id) - _test_case_client(client, f'/api/coordinator/students/{st.index}/', None, 'Student was deleted!', 202, - method='delete') + _test_case_client( + client, + f"/api/coordinator/students/{st.index}/", + None, + "Student was deleted!", + 202, + method="delete", + ) def test_delete_student_if_student_doesnt_exist(test_app_with_context) -> None: with test_app_with_context.test_client() as client: - _test_case_client(client, '/api/coordinator/students/43/', None, 'Not found student!', 404, - method='delete', key='error') + _test_case_client( + client, + "/api/coordinator/students/43/", + None, + "Not found student!", + 404, + method="delete", + key="error", + ) def test_edit_student(test_app_with_context) -> None: with test_app_with_context.test_client() as client: yg = create_year_group() st = create_student(valid_data, yg.id) - _test_case_client(client, f'/api/coordinator/students/{st.index}/', new_data, 'Student was updated!', 200, - method='put') + _test_case_client( + client, + f"/api/coordinator/students/{st.index}/", + new_data, + "Student was updated!", + 200, + method="put", + ) def test_edit_student_with_invalid_data(test_app_with_context) -> None: data = copy.copy(new_data) - data['pesel'] = '43333333333433443' + data["pesel"] = "43333333333433443" with test_app_with_context.test_client() as client: yg = create_year_group() st = create_student(valid_data, yg.id) - _test_case_client(client, f'/api/coordinator/students/{st.index}/', data, 'Validation error', 400, method='put') + _test_case_client( + client, + f"/api/coordinator/students/{st.index}/", + data, + "Validation error", + 400, + method="put", + ) def test_edit_student_if_student_doesnt_exist(test_app_with_context) -> None: with test_app_with_context.test_client() as client: - _test_case_client(client, '/api/coordinator/students/54/', new_data, 'Not found student!', 404, method='put', - key='error') + _test_case_client( + client, + "/api/coordinator/students/54/", + new_data, + "Not found student!", + 404, + method="put", + key="error", + ) def test_create_student(test_app_with_context) -> None: data = copy.copy(data_to_create_student) with test_app_with_context.test_client() as client: yg = create_year_group() - data['year_group_id'] = yg.id - _test_case_client(client, '/api/coordinator/students/', data, 'Student was created!', 200, method='post') + data["year_group_id"] = yg.id + _test_case_client( + client, + "/api/coordinator/students/", + data, + "Student was created!", + 200, + method="post", + ) def test_create_student_with_invalid_data(test_app_with_context) -> None: data = copy.copy(data_to_create_student) - data['pesel'] = '434343434343344' + data["pesel"] = "434343434343344" with test_app_with_context.test_client() as client: yg = create_year_group() - data['year_group_id'] = yg.id - _test_case_client(client, '/api/coordinator/students/', data, 'Validation error', 400, method='post') + data["year_group_id"] = yg.id + _test_case_client( + client, + "/api/coordinator/students/", + data, + "Validation error", + 400, + method="post", + ) def test_create_student_if_year_group_doesnt_exist(test_app_with_context) -> None: data = copy.copy(data_to_create_student) with test_app_with_context.test_client() as client: - data['year_group_id'] = 34 - _test_case_client(client, '/api/coordinator/students/', data, 'Not found year group!', 404, method='post', - key='error') + data["year_group_id"] = 34 + _test_case_client( + client, + "/api/coordinator/students/", + data, + "Not found year group!", + 404, + method="post", + key="error", + ) -def test_create_student_if_student_has_already_assigned_to_this_group(test_app_with_context) -> None: +def test_create_student_if_student_has_already_assigned_to_this_group( + test_app_with_context, +) -> None: data = copy.copy(data_to_create_student) with test_app_with_context.test_client() as client: yg = create_year_group() create_student(data, yg.id) - data['year_group_id'] = yg.id - _test_case_client(client, '/api/coordinator/students/', data, 'You are assigned to this year group!', 400, - method='post', key='error') + data["year_group_id"] = yg.id + _test_case_client( + client, + "/api/coordinator/students/", + data, + "You are assigned to this year group!", + 400, + method="post", + key="error", + ) diff --git a/backend/tests/functional_tests/coordinator/test_year_group.py b/backend/tests/functional_tests/coordinator/test_year_group.py index f7f1566..fe02d0c 100644 --- a/backend/tests/functional_tests/coordinator/test_year_group.py +++ b/backend/tests/functional_tests/coordinator/test_year_group.py @@ -1,99 +1,154 @@ -from ...fake_data import create_year_group -from ...utils import _test_case_client_without_response, _test_case_client, assert_model_changes from app.base.mode import ModeGroups -valid_data = { - 'mode': ModeGroups.STATIONARY.value, - 'name': '2022/2023' -} +from ...fake_data import create_year_group +from ...utils import ( + _test_case_client, + _test_case_client_without_response, + assert_model_changes, +) -new_data = { - 'mode': ModeGroups.NON_STATIONARY.value, - 'name': '2021/2022' -} +valid_data = {"mode": ModeGroups.STATIONARY.value, "name": "2022/2023"} -example_data = { - 'mode': ModeGroups.STATIONARY.value, - 'name': '2021/2022' -} +new_data = {"mode": ModeGroups.NON_STATIONARY.value, "name": "2021/2022"} + +example_data = {"mode": ModeGroups.STATIONARY.value, "name": "2021/2022"} def test_create_year_group(test_client) -> None: - _test_case_client(test_client, '/api/coordinator/year-group/', valid_data, 'Year group was created!', 201) + _test_case_client( + test_client, + "/api/coordinator/year-group/", + valid_data, + "Year group was created!", + 201, + ) def test_create_year_group_with_invalid_name(test_client) -> None: - data = { - 'mode': ModeGroups.STATIONARY.value, - 'name': '2022/203232a' - } - _test_case_client(test_client, '/api/coordinator/year-group/', data, 'Validation error', 400) + data = {"mode": ModeGroups.STATIONARY.value, "name": "2022/203232a"} + _test_case_client( + test_client, "/api/coordinator/year-group/", data, "Validation error", 400 + ) def test_create_year_group_with_invalid_mode(test_client) -> None: - data = { - 'mode': 'xxxx', - 'name': '2022/2033' - } - _test_case_client(test_client, '/api/coordinator/year-group/', data, 'Validation error', 400) + data = {"mode": "xxxx", "name": "2022/2033"} + _test_case_client( + test_client, "/api/coordinator/year-group/", data, "Validation error", 400 + ) def test_create_year_group_if_year_group_already_exists(test_app_with_context) -> None: with test_app_with_context.test_client() as client: create_year_group(valid_data) - _test_case_client(client, '/api/coordinator/year-group/', valid_data, 'Year group has already exists!', 400, - 'error') + _test_case_client( + client, + "/api/coordinator/year-group/", + valid_data, + "Year group has already exists!", + 400, + "error", + ) def test_delete_year_group(test_app_with_context) -> None: with test_app_with_context.test_client() as client: yg = create_year_group(valid_data) - _test_case_client(client, f'/api/coordinator/year-group/{yg.id}/', None, 'Year group was deleted!', 202, - method='delete') + _test_case_client( + client, + f"/api/coordinator/year-group/{yg.id}/", + None, + "Year group was deleted!", + 202, + method="delete", + ) def test_delete_year_group_if_year_group_not_exists(test_app_with_context) -> None: with test_app_with_context.test_client() as client: - _test_case_client(client, '/api/coordinator/year-group/1/', None, 'Year group doesn\'t exist!', 404, - method='delete', key='error') + _test_case_client( + client, + "/api/coordinator/year-group/1/", + None, + "Year group doesn't exist!", + 404, + method="delete", + key="error", + ) def test_update_year_group(test_app_with_context) -> None: with test_app_with_context.test_client() as client: yg = create_year_group(valid_data) - _test_case_client(client, f'/api/coordinator/year-group/{yg.id}/', new_data, "Year group was updated!", 200, - method='put', key='message') + _test_case_client( + client, + f"/api/coordinator/year-group/{yg.id}/", + new_data, + "Year group was updated!", + 200, + method="put", + key="message", + ) assert_model_changes(yg, new_data) def test_update_year_group_with_invalid_data(test_app_with_context) -> None: with test_app_with_context.test_client() as client: yg = create_year_group(valid_data) - _test_case_client(client, f'/api/coordinator/year-group/{yg.id}/', {'name': '', 'mode': ''}, 'Validation error', - 400, method='put', key='message') + _test_case_client( + client, + f"/api/coordinator/year-group/{yg.id}/", + {"name": "", "mode": ""}, + "Validation error", + 400, + method="put", + key="message", + ) assert_model_changes(yg, valid_data) -def test_update_year_group_with_invalid_route_param_year_group_id(test_app_with_context) -> None: +def test_update_year_group_with_invalid_route_param_year_group_id( + test_app_with_context, +) -> None: with test_app_with_context.test_client() as client: - _test_case_client(client, f'/api/coordinator/year-group/23/', new_data, 'Not found year group!', - 404, method='put', key='error') + _test_case_client( + client, + f"/api/coordinator/year-group/23/", + new_data, + "Not found year group!", + 404, + method="put", + key="error", + ) -def test_update_year_group_with_valid_data_and_year_group_which_has_already_exist(test_app_with_context) -> None: +def test_update_year_group_with_valid_data_and_year_group_which_has_already_exist( + test_app_with_context, +) -> None: with test_app_with_context.test_client() as client: create_year_group(new_data) yg = create_year_group(valid_data) - _test_case_client(client, f'/api/coordinator/year-group/{yg.id}/', new_data, 'Year group has already exists!', - 400, method='put', key='error') + _test_case_client( + client, + f"/api/coordinator/year-group/{yg.id}/", + new_data, + "Year group has already exists!", + 400, + method="put", + key="error", + ) assert_model_changes(yg, valid_data) def test_list_year_group(test_app_with_context) -> None: with test_app_with_context.test_client() as client: ygs = [create_year_group(data) for data in (valid_data, new_data, example_data)] - data = _test_case_client_without_response(client, '/api/coordinator/year-group/', None, 200, method='get') - assert data.get('max_pages') == 1 - for year_group_data in data.get('year_groups'): - yg_id = year_group_data.get('id') - assert_model_changes(list(filter(lambda yg: yg.id == yg_id, ygs))[0], year_group_data) + data = _test_case_client_without_response( + client, "/api/coordinator/year-group/", None, 200, method="get" + ) + assert data.get("max_pages") == 1 + for year_group_data in data.get("year_groups"): + yg_id = year_group_data.get("id") + assert_model_changes( + list(filter(lambda yg: yg.id == yg_id, ygs))[0], year_group_data + ) diff --git a/backend/tests/functional_tests/students/test_registrations.py b/backend/tests/functional_tests/students/test_registrations.py index 7840b7f..704a7e3 100644 --- a/backend/tests/functional_tests/students/test_registrations.py +++ b/backend/tests/functional_tests/students/test_registrations.py @@ -1,13 +1,16 @@ -from ...utils import _test_case_client_without_response, assert_model_changes -from ...fake_data import create_year_group, create_project_supervisors, create_groups from app.dependencies import db +from ...fake_data import create_groups, create_project_supervisors, create_year_group +from ...utils import _test_case_client_without_response, assert_model_changes + def test_list_year_group_for_specific_student(test_app_with_context) -> None: with test_app_with_context.test_client() as client: year_group = create_year_group() amount_of_project_supervisors = 3 - project_supervisors = create_project_supervisors(year_group, amount_of_project_supervisors) + project_supervisors = create_project_supervisors( + year_group, amount_of_project_supervisors + ) groups = create_groups(year_group, 6) for i in range(1, 4): @@ -16,12 +19,14 @@ def test_list_year_group_for_specific_student(test_app_with_context) -> None: gr.project_supervisor_id = project_supervisors[i - 1].id db.session.commit() - url = f'/api/students/registrations/{year_group.id}/?per_page=10' - data = _test_case_client_without_response(client, url, None, 200, method='get') - assert data.get('max_pages') == 1 - project_supervisors_data = data.get('project_supervisors') + url = f"/api/students/registrations/{year_group.id}/?per_page=10" + data = _test_case_client_without_response(client, url, None, 200, method="get") + assert data.get("max_pages") == 1 + project_supervisors_data = data.get("project_supervisors") assert len(project_supervisors_data) == amount_of_project_supervisors for ps, expected_available_groups in zip(project_supervisors, [2, 1, 0]): - ps_dict = list(filter(lambda p: p.get('email') == ps.email, project_supervisors_data))[0] - assert ps_dict.get('available_groups') == expected_available_groups + ps_dict = list( + filter(lambda p: p.get("email") == ps.email, project_supervisors_data) + )[0] + assert ps_dict.get("available_groups") == expected_available_groups diff --git a/backend/tests/functional_tests/students/test_year_group.py b/backend/tests/functional_tests/students/test_year_group.py index b16a293..7d9dace 100644 --- a/backend/tests/functional_tests/students/test_year_group.py +++ b/backend/tests/functional_tests/students/test_year_group.py @@ -1,32 +1,17 @@ -from ...utils import _test_case_client, _test_case_client_without_response -from ...fake_data import create_year_group, create_student from app.base.mode import ModeGroups from app.dependencies import db from app.students.models import YearGroupStudents -valid_data = { - 'first_name': 'Dominic', - 'last_name': 'Mozart', - 'index': 123_345 -} +from ...fake_data import create_student, create_year_group +from ...utils import _test_case_client, _test_case_client_without_response + +valid_data = {"first_name": "Dominic", "last_name": "Mozart", "index": 123_345} year_group_data = [ - { - 'name': '2022/2023', - 'mode': ModeGroups.STATIONARY.value - }, - { - 'name': '2021/2022', - 'mode': ModeGroups.STATIONARY.value - }, - { - 'name': '2023/2024', - 'mode': ModeGroups.NON_STATIONARY.value - }, - { - 'name': '1997/1998', - 'mode': ModeGroups.NON_STATIONARY.value - }, + {"name": "2022/2023", "mode": ModeGroups.STATIONARY.value}, + {"name": "2021/2022", "mode": ModeGroups.STATIONARY.value}, + {"name": "2023/2024", "mode": ModeGroups.NON_STATIONARY.value}, + {"name": "1997/1998", "mode": ModeGroups.NON_STATIONARY.value}, ] @@ -35,16 +20,25 @@ def test_list_year_group_for_specific_student(test_app_with_context) -> None: year_groups = [create_year_group(data) for data in year_group_data] student = create_student(valid_data) for yg in year_groups[:-1]: - db.session.add(YearGroupStudents(year_group_id=yg.id, student_index=student.index)) + db.session.add( + YearGroupStudents(year_group_id=yg.id, student_index=student.index) + ) db.session.commit() - url = f'/api/students/year-group/?per_page=10&index={student.index}' - data = _test_case_client_without_response(client, url, None, 200, method='get') - assert data.get('max_pages') == 1 - assert len(data.get('year_groups')) == len(year_groups) - 1 + url = f"/api/students/year-group/?per_page=10&index={student.index}" + data = _test_case_client_without_response(client, url, None, 200, method="get") + assert data.get("max_pages") == 1 + assert len(data.get("year_groups")) == len(year_groups) - 1 def test_list_year_group_if_student_doesnt_exist(test_app_with_context) -> None: with test_app_with_context.test_client() as client: - _test_case_client(client, '/api/students/year-group/?per_page=10&index=23', None, 'Not found student!', 404, - method='get', key='error') + _test_case_client( + client, + "/api/students/year-group/?per_page=10&index=23", + None, + "Not found student!", + 404, + method="get", + key="error", + ) diff --git a/backend/tests/unit_tests/test_utils.py b/backend/tests/unit_tests/test_utils.py index 62996a2..709f1d9 100644 --- a/backend/tests/unit_tests/test_utils.py +++ b/backend/tests/unit_tests/test_utils.py @@ -4,80 +4,107 @@ import pandas as pd import pytest from flask import current_app -from app.dependencies import db from app.base.utils import is_allowed_extensions, order_by_column_name, paginate_models -from app.coordinator.utils import check_columns, parse_csv, generate_range_dates, generate_csv from app.coordinator.exceptions import InvalidNameOrTypeHeaderException -from app.students.models import Student, Group +from app.coordinator.utils import ( + check_columns, + generate_csv, + generate_range_dates, + parse_csv, +) +from app.dependencies import db +from app.students.models import Group, Student def test_is_allowed_extensions(test_app) -> None: with test_app.app_context(): - for ext in current_app.config.get('ALLOWED_EXTENSIONS'): - assert is_allowed_extensions(f'file.{ext}') is True + for ext in current_app.config.get("ALLOWED_EXTENSIONS"): + assert is_allowed_extensions(f"file.{ext}") is True def test_is_allowed_extensions_with_invalid_extensions(test_app) -> None: with test_app.app_context(): - assert is_allowed_extensions('file.invalid_ext') is False - assert is_allowed_extensions('file') is False + assert is_allowed_extensions("file.invalid_ext") is False + assert is_allowed_extensions("file") is False def test_order_by_column_name_ascending_mode(test_app) -> None: with test_app.app_context(): - query = order_by_column_name(Student.query, 'index', 'desc') + query = order_by_column_name(Student.query, "index", "desc") assert 'ORDER BY students."index"' in str(query) def test_order_by_column_name_descending_mode(test_app) -> None: with test_app.app_context(): - query = order_by_column_name(Student.query, 'index', 'desc') + query = order_by_column_name(Student.query, "index", "desc") assert 'ORDER BY students."index" DESC' in str(query) def test_paginate_models(test_app_ctx_with_db) -> None: with test_app_ctx_with_db: - st = Student(index=123456, first_name='Dominic', last_name='Smith', pesel='99010109876', email='xxx@gmail.com') - st1 = Student(index=123457, first_name='John', last_name='Newton', pesel='99010109871', email='zzz@gmail.com') + st = Student( + index=123456, + first_name="Dominic", + last_name="Smith", + pesel="99010109876", + email="xxx@gmail.com", + ) + st1 = Student( + index=123457, + first_name="John", + last_name="Newton", + pesel="99010109871", + email="zzz@gmail.com", + ) db.session.add_all([st, st1]) db.session.commit() result = paginate_models(1, Student.query, 1) - items = result.get('items', []) - max_pages = result.get('max_pages', 0) + items = result.get("items", []) + max_pages = result.get("max_pages", 0) assert len(items) == 1 assert max_pages == 2 def test_check_columns() -> None: - dummy_data = {'NAZWISKO': ['Smith'], 'IMIE': ['Dominic'], 'INDEKS': [343433], 'PESEL': [90020178654], - 'EMAIL': ['domsmi@gmail.com']} + dummy_data = { + "NAZWISKO": ["Smith"], + "IMIE": ["Dominic"], + "INDEKS": [343433], + "PESEL": [90020178654], + "EMAIL": ["domsmi@gmail.com"], + } df = pd.DataFrame(data=dummy_data) assert check_columns(df) is True def test_check_columns_with_invalid_column_names() -> None: - dummy_data = {'col1': [1, 2], 'col2': [2, 3]} + dummy_data = {"col1": [1, 2], "col2": [2, 3]} df = pd.DataFrame(data=dummy_data) assert check_columns(df) is False def test_check_columns_with_invalid_column_types() -> None: - dummy_data = {'NAZWISKO': [999], 'IMIE': ['Dominic'], 'INDEKS': [343433], 'PESEL': [90020178654], - 'EMAIL': ['domsmi@gmail.com']} + dummy_data = { + "NAZWISKO": [999], + "IMIE": ["Dominic"], + "INDEKS": [343433], + "PESEL": [90020178654], + "EMAIL": ["domsmi@gmail.com"], + } df = pd.DataFrame(data=dummy_data) assert check_columns(df) is False def get_path_to_fake_data(filename: str) -> str: - base_dir = current_app.config.get('BASE_DIR', '/') - return base_dir / 'tmp_data' / filename + base_dir = current_app.config.get("BASE_DIR", "/") + return base_dir / "tmp_data" / filename def test_parse_csv(test_app) -> None: with test_app.app_context(): - with open(get_path_to_fake_data('students.csv')) as f: + with open(get_path_to_fake_data("students.csv")) as f: students = sorted(list(parse_csv(f)), key=lambda s: s.index) indexes = [452790 + i for i in range(3)] assert len(students) == len(indexes) @@ -87,14 +114,14 @@ def test_parse_csv(test_app) -> None: def test_parse_csv_with_invalid_column_header_name_in_csv_file(test_app) -> None: with test_app.app_context(): - with open(get_path_to_fake_data('students_column_name.csv')) as f: + with open(get_path_to_fake_data("students_column_name.csv")) as f: with pytest.raises(InvalidNameOrTypeHeaderException): parse_csv(f) def test_parse_csv_with_invalid_column_type_in_csv_file(test_app) -> None: with test_app.app_context(): - with open(get_path_to_fake_data('students_column_type.csv')) as f: + with open(get_path_to_fake_data("students_column_type.csv")) as f: with pytest.raises(InvalidNameOrTypeHeaderException): parse_csv(f) @@ -114,12 +141,27 @@ def test_generate_range_dates() -> None: def test_generate_csv(test_app_ctx_with_db) -> None: students_data = [ - {'first_name': 'Dominic', 'last_name': 'Smith', 'email': 'xxe@gmail.com', 'index': 123456, - 'pesel': '98070234293'}, - {'first_name': 'Matthew', 'last_name': 'Cash', 'email': 'zze@gmail.com', 'index': 123455, - 'pesel': '98070234291'}, - {'first_name': 'Martin', 'last_name': 'Rose', 'email': 'nne@gmail.com', 'index': 123446, - 'pesel': '98070234223'}, + { + "first_name": "Dominic", + "last_name": "Smith", + "email": "xxe@gmail.com", + "index": 123456, + "pesel": "98070234293", + }, + { + "first_name": "Matthew", + "last_name": "Cash", + "email": "zze@gmail.com", + "index": 123455, + "pesel": "98070234291", + }, + { + "first_name": "Martin", + "last_name": "Rose", + "email": "nne@gmail.com", + "index": 123446, + "pesel": "98070234223", + }, ] with test_app_ctx_with_db: @@ -135,7 +177,11 @@ def test_generate_csv(test_app_ctx_with_db) -> None: db.session.add_all([gr1, gr2]) db.session.commit() - students_and_groups = [(students[0], gr1), (students[1], gr1), (students[2], gr2)] + students_and_groups = [ + (students[0], gr1), + (students[1], gr1), + (students[2], gr2), + ] generated_csv = generate_csv(students_and_groups) for data in students_data: for value in data.values(): diff --git a/backend/tests/unit_tests/test_validators.py b/backend/tests/unit_tests/test_validators.py index a3c8aa2..2be6056 100644 --- a/backend/tests/unit_tests/test_validators.py +++ b/backend/tests/unit_tests/test_validators.py @@ -3,7 +3,10 @@ import datetime import pytest from marshmallow import ValidationError -from app.coordinator.validators import validate_index, validate_datetime_greater_than_now +from app.coordinator.validators import ( + validate_datetime_greater_than_now, + validate_index, +) def test_validate_index() -> None: diff --git a/backend/tests/utils.py b/backend/tests/utils.py index c0aa8ed..78b831a 100644 --- a/backend/tests/utils.py +++ b/backend/tests/utils.py @@ -15,8 +15,13 @@ def assert_model_changes(model: db.Model, expected_data: dict) -> None: assert value == val -def _test_case_client_without_response(test_client: FlaskClient, url: str, data: Union[dict, None], status_code: int, - method: str = 'post') -> dict: +def _test_case_client_without_response( + test_client: FlaskClient, + url: str, + data: Union[dict, None], + status_code: int, + method: str = "post", +) -> dict: method_func = getattr(test_client, method) if data is not None: response = method_func(url, json=data) @@ -27,16 +32,25 @@ def _test_case_client_without_response(test_client: FlaskClient, url: str, data: return response.json -def _test_case_client(test_client: FlaskClient, url: str, data: Union[dict, None], message: str, status_code: int, - key: str = 'message', method: str = 'post') -> None: - response_data = _test_case_client_without_response(test_client, url, data, status_code, method) +def _test_case_client( + test_client: FlaskClient, + url: str, + data: Union[dict, None], + message: str, + status_code: int, + key: str = "message", + method: str = "post", +) -> None: + response_data = _test_case_client_without_response( + test_client, url, data, status_code, method + ) assert key in response_data.keys() assert response_data.get(key) == message def _test_case_group(group: Group, data: dict) -> None: - assert group.name == data['name'] - assert group.project_supervisor_id == data['project_supervisor_id'] + assert group.name == data["name"] + assert group.project_supervisor_id == data["project_supervisor_id"] for st in group.students: - assert st.index in data['students'] + assert st.index in data["students"]