fix group, students, project_supervisor endpoints for coordinator, fix importing csv students endpoint and add copy project supervisors from last year group, refactor code

This commit is contained in:
dominik24c 2023-01-14 01:37:31 +01:00
parent 48be4c6345
commit 038f9da93d
29 changed files with 198 additions and 529 deletions

View File

@ -7,11 +7,10 @@ from flask_cors import CORS
from .config import config from .config import config
from .dependencies import db, ma from .dependencies import db, ma
from .commands.startapp import startapp from .commands.startapp import startapp
from .commands.init_db import init_db
from .commands.clear_db import clear_db from .commands.clear_db import clear_db
from .utils import import_models from .utils import import_models
from .api import api_bp from .api import api_bp
from .errors import request_entity_too_large, register_error_handlers from .errors import register_error_handlers
def create_app(config_name: str = '') -> APIFlask: def create_app(config_name: str = '') -> APIFlask:
@ -37,11 +36,9 @@ def create_app(config_name: str = '') -> APIFlask:
# register commands # register commands
app.cli.add_command(startapp) app.cli.add_command(startapp)
app.cli.add_command(init_db)
app.cli.add_command(clear_db) app.cli.add_command(clear_db)
# register errors # register errors
register_error_handlers(app) register_error_handlers(app)
# app.register_error_handler(413, request_entity_too_large)
return app return app

View File

@ -12,4 +12,4 @@ class Person(db.Model):
first_name = db.Column(db.String(255), index=True, nullable=False) first_name = db.Column(db.String(255), index=True, nullable=False)
last_name = db.Column(db.String(255), index=True, nullable=False) last_name = db.Column(db.String(255), index=True, nullable=False)
email = db.Column(db.String(120), unique=True) email = db.Column(db.String(120), index=True, nullable=False)

View File

@ -0,0 +1,5 @@
from marshmallow import fields, Schema
class MessageSchema(Schema):
message = fields.Str()

View File

@ -1,37 +0,0 @@
from flask.cli import with_appcontext
from click import command
from ..dependencies import db
# from ..factory import ProjectSupervisorFactory, GroupFactory, StudentFactory
@command('init_db')
@with_appcontext
def init_db() -> None:
"""Fill database with some data"""
db.drop_all()
db.create_all()
#
# num_of_supervisors = 5
#
# projects_supervisors = [ProjectSupervisorFactory() for _ in range(num_of_supervisors)]
# db.session.add_all(projects_supervisors)
# db.session.commit()
#
# groups = [GroupFactory(project_supervisor=projects_supervisors[i]) for i in range(num_of_supervisors)]
# db.session.add_all(groups)
# db.session.commit()
#
# num_of_students = num_of_supervisors * 3
# students = [StudentFactory(group=groups[i % num_of_supervisors]) for i in range(num_of_students)]
#
# max_count = 10
# max_length = len(students)
# start_count = 0
#
# while True:
# if start_count > max_length:
# break
# db.session.add_all(students[start_count:max_count])
# db.session.commit()
# start_count += max_count

View File

@ -1,5 +0,0 @@
# from ..base.models import Base
#
#
# class Coordinator(Base):
# __tablename__ = 'coordinators'

View File

@ -5,12 +5,13 @@ from flask import abort, current_app
from sqlalchemy import or_, and_ from sqlalchemy import or_, and_
from flask_sqlalchemy import get_debug_queries from flask_sqlalchemy import get_debug_queries
from ..schemas import MessageSchema, TermOfDefenceSchema, TermOfDefenceListSchema, \ from ..schemas import TermOfDefenceSchema, TemporaryAvailabilityListSchema, AssignedGroupToTermOfDefenceListSchema, \
TemporaryAvailabilityListSchema, AssignedGroupToTermOfDefenceListSchema, GroupIdSchema GroupIdSchema
from ...examination_schedule.models import TermOfDefence, TemporaryAvailability, committee from ...examination_schedule.models import TermOfDefence, TemporaryAvailability
from ...students.models import YearGroup from ...students.models import YearGroup
from ...project_supervisor.models import ProjectSupervisor from ...project_supervisor.models import ProjectSupervisor
from ...dependencies import db from ...dependencies import db
from ...base.schemas import MessageSchema
from ..utils import generate_range_dates 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, \ 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 get_examination_schedule_by_id
@ -18,68 +19,6 @@ from ..query.enrollments import get_term_of_defence_by_id_and_examination_schedu
bp = APIBlueprint("enrollments", __name__, url_prefix="/enrollments") bp = APIBlueprint("enrollments", __name__, url_prefix="/enrollments")
@bp.post('/<int:examination_schedule_id>/generate')
@bp.output(MessageSchema)
def generate_term_of_defence_for_this_examination_schedule(examination_schedule_id: int) -> dict:
ex = get_examination_schedule_by_id(examination_schedule_id)
limit = current_app.config.get('LIMIT_MEMBERS_PER_COMMITTEE', 3)
term_of_defences = TermOfDefence.query. \
filter(TermOfDefence.examination_schedule_id == examination_schedule_id).first()
if term_of_defences is not None:
abort(400,
"First you have to delete all term of defences for this examination schedule to generate them again!")
temporary_availabilities = TemporaryAvailability.query. \
filter(TemporaryAvailability.examination_schedule_id == examination_schedule_id). \
join(TemporaryAvailability.project_supervisor). \
all()
if len(temporary_availabilities) == 0:
abort(404, "Not found temporary availabilities for project supervisors")
dates = generate_range_dates(ex.start_date, ex.end_date, ex.duration_time)
term_of_defences = []
for d in dates:
e = d + datetime.timedelta(minutes=ex.duration_time)
t = list(filter(lambda ta: ta.start_date <= d and ta.end_date >= e, temporary_availabilities))
if len(t) >= limit:
projects_supervisors = [t[i].project_supervisor for i in range(limit)]
term_of_defence = TermOfDefence(start_date=d, end_date=e, examination_schedule_id=examination_schedule_id)
term_of_defence.members_of_committee = projects_supervisors
term_of_defences.append(term_of_defence)
db.session.add_all(term_of_defences)
db.session.commit()
return {"message": "Term of defences was generated!"}
@bp.post('/<int:examination_schedule_id>/clear-term-of-defences/')
@bp.output(MessageSchema)
def clear_generated_term_of_defences(examination_schedule_id: int) -> dict:
get_examination_schedule_by_id(examination_schedule_id)
# count_defences = db.func.count(ProjectSupervisor.id)
# td = db.session.query(TermOfDefence, count_defences). \
# join(ProjectSupervisor, isouter=True). \
# filter(TermOfDefence.examination_schedule_id == examination_schedule_id). \
# group_by(TermOfDefence.id).having(count_defences > 0).first()
# print(td)
# if td is not None:
# abort(400, "First you remove group assigned to term of defence to clear term of defences!")
td = TermOfDefence.query. \
filter(TermOfDefence.examination_schedule_id == examination_schedule_id).all()
while True:
for t in td[0:10]:
db.session.delete(t)
db.session.commit()
if len(td) == 0:
break
td[0:10] = []
# print(len(get_debug_queries()))
return {"message": "Term of defences was deleted!"}
@bp.post('/<int:examination_schedule_id>/add') @bp.post('/<int:examination_schedule_id>/add')
@bp.input(TermOfDefenceSchema) @bp.input(TermOfDefenceSchema)
@bp.output(MessageSchema) @bp.output(MessageSchema)

View File

@ -9,9 +9,10 @@ from ...dependencies import db
from ...examination_schedule.models import ExaminationSchedule, TermOfDefence from ...examination_schedule.models import ExaminationSchedule, TermOfDefence
from ...students.models import Group, YearGroup from ...students.models import Group, YearGroup
from ...project_supervisor.models import ProjectSupervisor from ...project_supervisor.models import ProjectSupervisor
from ..schemas import ExaminationScheduleSchema, ExaminationScheduleUpdateSchema, MessageSchema, \ from ..schemas import ExaminationScheduleSchema, ExaminationScheduleUpdateSchema, \
ExaminationSchedulesQuerySchema, ExaminationSchedulesPaginationSchema ExaminationSchedulesQuerySchema, ExaminationSchedulesPaginationSchema
from ..utils import generate_examination_schedule_pdf_file from ..utils import generate_examination_schedule_pdf_file
from ...base.schemas import MessageSchema
bp = APIBlueprint("examination_schedule", __name__, url_prefix="/examination_schedule") bp = APIBlueprint("examination_schedule", __name__, url_prefix="/examination_schedule")

View File

@ -1,13 +1,13 @@
from flask import abort, current_app from flask import abort
from apiflask import APIBlueprint from apiflask import APIBlueprint
from flask_sqlalchemy import get_debug_queries from flask_sqlalchemy import get_debug_queries
from ...students.models import Group, Student, YearGroup, ProjectGradeSheet from ...students.models import Group, Student, YearGroup, ProjectGradeSheet
from ...project_supervisor.models import ProjectSupervisor, YearGroupProjectSupervisors from ...project_supervisor.models import ProjectSupervisor
from ..schemas import GroupEditSchema, GroupsPaginationSchema, GroupCreateSchema, MessageSchema, GroupQuerySchema, \ from ..schemas import GroupEditSchema, GroupsPaginationSchema, GroupCreateSchema, GroupQuerySchema, DetailGroupSchema
DetailGroupSchema
from ...dependencies import db from ...dependencies import db
from ...base.utils import paginate_models from ...base.utils import paginate_models
from ...base.schemas import MessageSchema
from ..utils import attach_points_for_first_and_second_term_to_group_models from ..utils import attach_points_for_first_and_second_term_to_group_models
bp = APIBlueprint("groups", __name__, url_prefix="/groups") bp = APIBlueprint("groups", __name__, url_prefix="/groups")
@ -38,7 +38,7 @@ def list_groups(year_group_id: int, query: dict) -> dict:
@bp.output(MessageSchema, status_code=201) @bp.output(MessageSchema, status_code=201)
def create_group(year_group_id: int, data: dict) -> dict: def create_group(year_group_id: int, data: dict) -> dict:
name = data['name'] name = data['name']
students_indexes = data['students'] students_ids = data['students']
project_supervisor_id = data['project_supervisor_id'] project_supervisor_id = data['project_supervisor_id']
yg = YearGroup.query.filter(YearGroup.id == year_group_id).first() yg = YearGroup.query.filter(YearGroup.id == year_group_id).first()
@ -47,33 +47,21 @@ def create_group(year_group_id: int, data: dict) -> dict:
project_supervisor = ProjectSupervisor.query.filter_by(id=project_supervisor_id).first() project_supervisor = ProjectSupervisor.query.filter_by(id=project_supervisor_id).first()
limit_student_per_group = current_app.config.get('LIMIT_STUDENTS_PER_GROUP')
if project_supervisor is None: if project_supervisor is None:
abort(404, f"Not found project supervisor!") abort(404, f"Not found project supervisor!")
elif limit_student_per_group is not None and limit_student_per_group < len(students_indexes):
abort(400, f"Too much students you want add to group, "
f"The group can have only {limit_student_per_group} students")
limit = db.session.query(db.func.count(ProjectSupervisor.id)). \
join(Group).filter(ProjectSupervisor.id == project_supervisor_id).group_by(ProjectSupervisor.id).scalar()
ygps = YearGroupProjectSupervisors.query. \
filter(YearGroupProjectSupervisors.year_group_id == year_group_id,
YearGroupProjectSupervisors.project_supervisor_id == project_supervisor_id).first()
if limit is not None and ygps is not None and limit >= ygps.limit_group:
abort(400, "Can't create new group, project supervisor achieved a limit of groups")
group = Group(name=name, project_supervisor_id=project_supervisor_id, year_group_id=year_group_id) group = Group(name=name, project_supervisor_id=project_supervisor_id, year_group_id=year_group_id)
students_without_groups = db.session.query(Student, Group). \ students_without_groups = db.session.query(Student, Group). \
join(Group, Student.groups). \ join(Group, Student.groups). \
filter(Group.year_group_id == year_group_id). \ filter(Group.year_group_id == year_group_id). \
filter(db.or_(*[Student.index == idx for idx in students_indexes])).all() filter(db.or_(*[Student.id == st_id for st_id in students_ids])).all()
if len(students_without_groups) > 0: if len(students_without_groups) > 0:
abort(400, "One or more students have already belonged to group!") abort(400, "One or more students have already belonged to group!")
students = db.session.query(Student).filter(Student.index.in_(students_indexes)).all() students = db.session.query(Student).filter(Student.id.in_(students_ids)).all()
if len(students) != len(students_indexes): if len(students) != len(students_ids):
abort(404, "Not found students!") abort(404, "Not found students!")
db.session.add(group) db.session.add(group)
@ -81,9 +69,6 @@ def create_group(year_group_id: int, data: dict) -> dict:
for student in students: for student in students:
group.students.append(student) group.students.append(student)
db.session.commit()
pgs = ProjectGradeSheet(group_id=group.id) pgs = ProjectGradeSheet(group_id=group.id)
db.session.add(pgs) db.session.add(pgs)
db.session.commit() db.session.commit()
@ -91,19 +76,19 @@ def create_group(year_group_id: int, data: dict) -> dict:
return {"message": "Group was created!"} return {"message": "Group was created!"}
@bp.get("/<int:id>/detail/") @bp.get("/<int:group_id>/detail/")
@bp.output(DetailGroupSchema) @bp.output(DetailGroupSchema)
def detail_group(id: int) -> Group: def detail_group(group_id: int) -> Group:
group = Group.query.filter_by(id=id).first() group = Group.query.filter_by(id=group_id).first()
if group is None: if group is None:
abort(404, f"Not found group!") abort(404, f"Not found group!")
return group return group
@bp.delete("/<int:id>/") @bp.delete("/<int:group_id>/")
@bp.output(MessageSchema, status_code=202) @bp.output(MessageSchema, status_code=202)
def delete_group(id: int) -> dict: def delete_group(group_id: int) -> dict:
group = Group.query.filter_by(id=id).first() group = Group.query.filter_by(id=group_id).first()
if group is None: if group is None:
abort(404, f"Not found group!") abort(404, f"Not found group!")
@ -113,26 +98,26 @@ def delete_group(id: int) -> dict:
return {"message": "Group was deleted!"} return {"message": "Group was deleted!"}
@bp.put("/<int:id>/") @bp.put("/<int:group_id>/")
@bp.input(GroupEditSchema) @bp.input(GroupEditSchema)
@bp.output(MessageSchema) @bp.output(MessageSchema)
def edit_group(id: int, data: dict) -> dict: def edit_group(group_id: int, data: dict) -> dict:
if not data: if not data:
abort(400, 'You have passed empty data!') abort(400, 'You have passed empty data!')
group_query = Group.query.filter_by(id=id) group_query = Group.query.filter_by(id=group_id)
group = group_query.first() group = group_query.first()
if group is None: if group is None:
abort(404, f"Not found group!") abort(404, f"Not found group!")
students_indexes = data.get('students') students_ids = data.get('students')
name = data.get('name') name = data.get('name')
project_supervisor_id = data.get('project_supervisor_id') project_supervisor_id = data.get('project_supervisor_id')
if students_indexes is not None: if students_ids is not None:
students = db.session.query(Student).filter(Student.index.in_(students_indexes)).all() students = db.session.query(Student).filter(Student.id.in_(students_ids)).all()
if len(students_indexes) != len(students): if len(students_ids) != len(students):
abort(404, 'Not found students!') abort(404, 'Not found students!')
group.students = students group.students = students

View File

@ -2,41 +2,21 @@ from flask import abort
from apiflask import APIBlueprint from apiflask import APIBlueprint
from flask_sqlalchemy import get_debug_queries from flask_sqlalchemy import get_debug_queries
from ...project_supervisor.models import ProjectSupervisor, YearGroupProjectSupervisors from ...project_supervisor.models import ProjectSupervisor
from ...students.models import Group, YearGroup from ...students.models import Group, YearGroup
from ..schemas import ProjectSupervisorSchema, ProjectSupervisorEditSchema, ProjectSupervisorsPaginationSchema, \ from ..schemas import ProjectSupervisorSchema, ProjectSupervisorEditSchema, ProjectSupervisorsPaginationSchema, \
ProjectSupervisorCreateSchema, MessageSchema, ProjectSupervisorQuerySchema, ProjectSupervisorYearGroupSchema ProjectSupervisorCreateSchema, MessageWithIdSchema, ProjectSupervisorQuerySchema
from ...base.schemas import MessageSchema
from ...dependencies import db from ...dependencies import db
from ...base.utils import paginate_models from ...base.utils import paginate_models
bp = APIBlueprint("project_supervisor", __name__, url_prefix="/project_supervisor") bp = APIBlueprint("project_supervisor", __name__, url_prefix="/project_supervisor")
@bp.get("/")
@bp.input(ProjectSupervisorQuerySchema, location='query')
@bp.output(ProjectSupervisorsPaginationSchema)
def list_project_supervisors(query: dict) -> dict:
fullname = query.get('fullname')
order_by_first_name = query.get('order_by_first_name')
order_by_last_name = query.get('order_by_last_name')
page = query.get('page')
per_page = query.get('per_page')
project_supervisor_query = ProjectSupervisor.search_by_fullname_and_mode_and_order_by_first_name_or_last_name(
None, fullname, order_by_first_name, order_by_last_name)
data = paginate_models(page, project_supervisor_query, per_page)
# print(get_debug_queries()[0])
return {
"project_supervisors": data['items'],
"max_pages": data['max_pages']
}
@bp.get("/<int:year_group_id>/") @bp.get("/<int:year_group_id>/")
@bp.input(ProjectSupervisorQuerySchema, location='query') @bp.input(ProjectSupervisorQuerySchema, location='query')
@bp.output(ProjectSupervisorsPaginationSchema) @bp.output(ProjectSupervisorsPaginationSchema)
def list_project_supervisors_by_year_group(year_group_id: int, query: dict) -> dict: def list_project_supervisors(year_group_id: int, query: dict) -> dict:
fullname = query.get('fullname') fullname = query.get('fullname')
order_by_first_name = query.get('order_by_first_name') order_by_first_name = query.get('order_by_first_name')
order_by_last_name = query.get('order_by_last_name') order_by_last_name = query.get('order_by_last_name')
@ -54,44 +34,44 @@ def list_project_supervisors_by_year_group(year_group_id: int, query: dict) -> d
} }
@bp.post("/") @bp.post("/<int:year_group_id>/")
@bp.input(ProjectSupervisorCreateSchema) @bp.input(ProjectSupervisorCreateSchema)
@bp.output(MessageSchema, status_code=201) @bp.output(MessageWithIdSchema, status_code=201)
def create_project_supervisor(data: dict) -> dict: def create_project_supervisor(year_group_id: int, data: dict) -> dict:
first_name = data['first_name'] year_group = YearGroup.query.filter(YearGroup.id == year_group_id).first()
last_name = data['last_name'] if year_group is None:
project_supervisor = ProjectSupervisor.query.filter_by(first_name=first_name).filter_by(last_name=last_name).first() abort(404, "Not found year group!")
email = data['email']
project_supervisor = ProjectSupervisor.query.filter(ProjectSupervisor.email == email).first()
if project_supervisor is not None: if project_supervisor is not None:
abort(400, "Project Supervisor has already exists!") abort(400, "Project Supervisor has already exists!")
project_supervisor = ProjectSupervisor(**data) project_supervisor = ProjectSupervisor(**data, year_group_id=year_group_id)
db.session.add(project_supervisor) db.session.add(project_supervisor)
db.session.commit() db.session.commit()
return {"message": "Project Supervisor was created!", "id": project_supervisor.id} return {"message": "Project Supervisor was created!", "id": project_supervisor.id}
@bp.get("/<int:id>/detail/") @bp.get("/<int:project_supervisor_id>/detail/")
@bp.output(ProjectSupervisorSchema) @bp.output(ProjectSupervisorSchema)
def detail_project_supervisor(id: int) -> ProjectSupervisor: def detail_project_supervisor(project_supervisor_id: int) -> ProjectSupervisor:
project_supervisor = ProjectSupervisor.query.filter_by(id=id).first() project_supervisor = ProjectSupervisor.query.filter_by(id=project_supervisor_id).first()
if project_supervisor is None: if project_supervisor is None:
abort(404, 'Not found project supervisor!') abort(404, 'Not found project supervisor!')
return project_supervisor return project_supervisor
@bp.delete("/<int:id>/") @bp.delete("/<int:project_supervisor_id>/")
@bp.output(MessageSchema) @bp.output(MessageSchema)
def delete_project_supervisor(id: int) -> dict: def delete_project_supervisor(project_supervisor_id: int) -> dict:
project_supervisor = ProjectSupervisor.query.filter_by(id=id).first() project_supervisor = ProjectSupervisor.query.filter_by(id=project_supervisor_id).first()
if project_supervisor is None: if project_supervisor is None:
abort(404, "Not found project supervisor!") abort(404, "Not found project supervisor!")
count_groups = db.session.query(db.func.count(ProjectSupervisor.id)).join(Group). \ count_groups = len(Group.query.filter(Group.project_supervisor_id == project_supervisor.id).all())
filter(ProjectSupervisor.id == id).group_by(ProjectSupervisor.id).scalar() if count_groups > 0:
if count_groups is not None and count_groups > 0:
abort(400, "Project Supervisor has at least one group!") abort(400, "Project Supervisor has at least one group!")
db.session.delete(project_supervisor) db.session.delete(project_supervisor)
@ -99,14 +79,14 @@ def delete_project_supervisor(id: int) -> dict:
return {"message": "Project Supervisor was deleted!"} return {"message": "Project Supervisor was deleted!"}
@bp.put("/<int:id>/") @bp.put("/<int:project_supervisor_id>/")
@bp.input(ProjectSupervisorEditSchema) @bp.input(ProjectSupervisorEditSchema)
@bp.output(MessageSchema) @bp.output(MessageSchema)
def edit_project_supervisor(id: int, data: dict) -> dict: def edit_project_supervisor(project_supervisor_id: int, data: dict) -> dict:
if not data: 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=id) project_supervisor_query = ProjectSupervisor.query.filter_by(id=project_supervisor_id)
project_supervisor = project_supervisor_query.first() project_supervisor = project_supervisor_query.first()
if project_supervisor is None: if project_supervisor is None:
@ -117,65 +97,27 @@ def edit_project_supervisor(id: int, data: dict) -> dict:
return {"message": "Project Supervisor was updated!"} return {"message": "Project Supervisor was updated!"}
@bp.post("/<int:id>/year-group/<int:year_group_id>/") @bp.post("/copy-project-supervisors-from-last-year-group/<int:year_group_id>/")
@bp.input(ProjectSupervisorYearGroupSchema)
@bp.output(MessageSchema, status_code=201) @bp.output(MessageSchema, status_code=201)
def add_project_supervisor_to_year_group(id: int, year_group_id: int, data: dict) -> dict: def copy_project_supervisors_from_last_year_group(year_group_id: int) -> dict:
if not data:
abort(400, 'You have passed empty data!')
limit_group = data.get('limit_group')
project_supervisor = ProjectSupervisor.query.filter(ProjectSupervisor.id == id).first()
if project_supervisor is None:
abort(404, f"Not found project supervisor!")
year_group = YearGroup.query.filter(YearGroup.id == year_group_id).first() year_group = YearGroup.query.filter(YearGroup.id == year_group_id).first()
if year_group is None: if year_group is None:
abort(404, "Not found year group!") abort(404, "Not found year group!")
ygps = YearGroupProjectSupervisors.query.filter(YearGroupProjectSupervisors.project_supervisor_id == id). \ last_year_group = YearGroup.query.filter(YearGroup.mode == year_group.mode). \
filter(YearGroupProjectSupervisors.year_group_id == year_group_id).first() filter(YearGroup.id != year_group_id).filter(YearGroup.created_at < year_group.created_at). \
if ygps is not None: order_by(db.desc(YearGroup.created_at)).first()
abort(400, "Project supervisor is assigned to this year group!") if last_year_group is None:
abort(400, "The latest year group doesn't exist!")
ygps = YearGroupProjectSupervisors(year_group_id=year_group_id, project_supervisor_id=id, project_supervisors = ProjectSupervisor.query.filter(ProjectSupervisor.year_group_id == last_year_group.id).all()
limit_group=limit_group) current_project_supervisors_email_in_new_year_group = [ps.email for ps in ProjectSupervisor.query.filter(
db.session.add(ygps) ProjectSupervisor.year_group_id == year_group_id).all()]
for ps in project_supervisors:
if ps.email not in current_project_supervisors_email_in_new_year_group:
new_ps = ProjectSupervisor(first_name=ps.first_name, last_name=ps.last_name, email=ps.email,
limit_group=ps.limit_group, year_group_id=year_group_id)
db.session.add(new_ps)
db.session.commit() db.session.commit()
return {"message": "Project Supervisor was added to year group!"}
return {"message": "Project Supervisors was added!"}
@bp.delete("/<int:id>/year-group/<int:year_group_id>/")
@bp.output(MessageSchema)
def delete_project_supervisor_to_year_group(id: int, year_group_id: int) -> dict:
project_supervisor = ProjectSupervisor.query.filter(ProjectSupervisor.id == id).first()
if project_supervisor is None:
abort(404, f"Not found project supervisor!")
year_group = YearGroup.query.filter(YearGroup.id == year_group_id).first()
if year_group is None:
abort(404, "Not found year group!")
db.session.delete(project_supervisor)
db.session.commit()
return {"message": "Project Supervisor was removed from this year group!"}
@bp.put("/<int:id>/year-group/<int:year_group_id>/")
@bp.input(ProjectSupervisorYearGroupSchema)
@bp.output(MessageSchema)
def update_limit_of_group_for_project_supervisor(id: int, year_group_id: int, data: dict) -> dict:
project_supervisor = ProjectSupervisor.query.filter(ProjectSupervisor.id == id).first()
if project_supervisor is None:
abort(404, f"Not found project supervisor!")
year_group = YearGroup.query.filter(YearGroup.id == year_group_id).first()
if year_group is None:
abort(404, "Not found year group!")
ygps = YearGroupProjectSupervisors.query.filter(YearGroupProjectSupervisors.project_supervisor_id == id). \
filter(YearGroupProjectSupervisors.year_group_id == year_group_id)
ygps.update(data)
db.session.commit()
return {"message": "Limit of group was changed!"}

View File

@ -6,14 +6,15 @@ from apiflask import APIBlueprint
from sqlalchemy import or_ from sqlalchemy import or_
from flask_sqlalchemy import get_debug_queries from flask_sqlalchemy import get_debug_queries
from ...students.models import Student, Group, YearGroup, YearGroupStudents from ...students.models import Student, Group, YearGroup
from ...project_supervisor.models import ProjectSupervisor from ...project_supervisor.models import ProjectSupervisor
from ..schemas import StudentSchema, StudentEditSchema, StudentsPaginationSchema, YearGroupInfoQuery, \ from ..schemas import StudentSchema, StudentEditSchema, StudentsPaginationSchema, YearGroupInfoQuery, \
StudentCreateSchema, MessageSchema, FileSchema, StudentQuerySchema, StudentListFileDownloaderSchema StudentCreateSchema, FileSchema, StudentQuerySchema, StudentListFileDownloaderSchema
from ...dependencies import db from ...dependencies import db
from ..utils import parse_csv, generate_csv from ..utils import parse_csv, generate_csv
from ..exceptions import InvalidNameOrTypeHeaderException from ..exceptions import InvalidNameOrTypeHeaderException
from ...base.utils import paginate_models, is_allowed_extensions from ...base.utils import paginate_models, is_allowed_extensions
from ...base.schemas import MessageSchema
bp = APIBlueprint("students", __name__, url_prefix="/students") bp = APIBlueprint("students", __name__, url_prefix="/students")
@ -40,19 +41,19 @@ def list_students(year_group_id: int, query: dict) -> dict:
} }
@bp.get("/<int:index>/detail/") @bp.get("/<int:student_id>/detail/")
@bp.output(StudentSchema) @bp.output(StudentSchema)
def detail_student(index: int) -> Student: def detail_student(student_id: int) -> Student:
student = Student.query.filter_by(index=index).first() student = Student.query.filter_by(id=student_id).first()
if student is None: if student is None:
abort(404, "Not found student!") abort(404, "Not found student!")
return student return student
@bp.delete("/<int:index>/") @bp.delete("/<int:student_id>/")
@bp.output(MessageSchema, status_code=202) @bp.output(MessageSchema, status_code=202)
def delete_student(index: int) -> dict: def delete_student(student_id: int) -> dict:
student = Student.query.filter_by(index=index).first() student = Student.query.filter_by(id=student_id).first()
if student is None: if student is None:
abort(404, "Not found student!") abort(404, "Not found student!")
db.session.delete(student) db.session.delete(student)
@ -60,14 +61,14 @@ def delete_student(index: int) -> dict:
return {"message": "Student was deleted!"} return {"message": "Student was deleted!"}
@bp.put("/<int:index>/") @bp.put("/<int:student_id>/")
@bp.input(StudentEditSchema) @bp.input(StudentEditSchema)
@bp.output(MessageSchema) @bp.output(MessageSchema)
def edit_student(index: int, data: dict) -> dict: def edit_student(student_id: int, data: dict) -> dict:
if not data: if not data:
abort(400, 'You have passed empty data!') abort(400, 'You have passed empty data!')
student_query = Student.query.filter_by(index=index) student_query = Student.query.filter(Student.id==student_id)
student = student_query.first() student = student_query.first()
if student is None: if student is None:
@ -87,24 +88,18 @@ def create_student(data: dict) -> dict:
yg_id = data['year_group_id'] yg_id = data['year_group_id']
del data['year_group_id'] del data['year_group_id']
student = Student.query.filter(Student.index == index).first() student = Student.query.filter(Student.index == index, Student.year_group_id == yg_id).first()
# if student is not None: if student is not None:
# abort(400, "Student has already exists!") abort(400, "Student has already assigned to this year group!")
if student is None:
dummy_email = f'student{randint(1, 300_000)}@gmail.com'
student = Student(**data, email=dummy_email)
db.session.add(student)
# add student to the chosen year group # add student to the chosen year group
year_group = YearGroup.query.filter(YearGroup.id == yg_id).first() year_group = YearGroup.query.filter(YearGroup.id == yg_id).first()
if year_group is None: if year_group is None:
abort(404, "Not found year group!") abort(404, "Not found year group!")
if any((year_group.id == yg.id for yg in student.year_groups)):
abort(400, "You are assigned to this year group!")
ygs = YearGroupStudents(student_index=student.index, year_group_id=year_group.id)
db.session.add(ygs)
dummy_email = f'student{randint(1, 300_000)}@gmail.com'
student = Student(**data, email=dummy_email, year_group_id=yg_id)
db.session.add(student)
db.session.commit() db.session.commit()
return {"message": "Student was created!"} return {"message": "Student was created!"}
@ -117,7 +112,7 @@ def create_student(data: dict) -> dict:
def upload_students(query: dict, file: dict) -> dict: def upload_students(query: dict, file: dict) -> dict:
"""Add only Students to chosen year group if students exist in db and assigned to correct year group, """Add only Students to chosen year group if students exist in db and assigned to correct year group,
they will be omitted""" they will be omitted"""
year_group_id = query.get('id') year_group_id = query.get('year_group_id')
yg = YearGroup.query.filter(YearGroup.id == year_group_id).first() yg = YearGroup.query.filter(YearGroup.id == year_group_id).first()
if yg is None: if yg is None:
abort(404, "Not found year group!") abort(404, "Not found year group!")
@ -125,7 +120,7 @@ def upload_students(query: dict, file: dict) -> dict:
uploaded_file = file.get('file') uploaded_file = file.get('file')
if uploaded_file and is_allowed_extensions(uploaded_file.filename): if uploaded_file and is_allowed_extensions(uploaded_file.filename):
try: try:
students = parse_csv(uploaded_file) students = parse_csv(uploaded_file, year_group_id)
while True: while True:
sliced_students = islice(students, 5) sliced_students = islice(students, 5)
list_of_students = list(sliced_students) list_of_students = list(sliced_students)
@ -133,25 +128,13 @@ def upload_students(query: dict, file: dict) -> dict:
if len(list_of_students) == 0: if len(list_of_students) == 0:
break break
students_in_db = Student.query.filter(or_(Student.index == s.index for s in list_of_students)).all() students_in_db = Student.query.filter(or_(Student.index == s.index for s in list_of_students)).\
students_in_db_and_assigned_to_year_group = Student.query.join(YearGroupStudents, isouter=True). \ filter(Student.year_group_id==year_group_id).all()
filter(YearGroupStudents.year_group_id == year_group_id). \
filter(or_(Student.index == s.index for s in list_of_students)).all()
student_index_in_db = [s.index for s in students_in_db] student_index_in_db = [s.index for s in students_in_db]
student_index_in_year_group = [s.index for s in students_in_db_and_assigned_to_year_group]
students_in_db_and_not_assigned_to_year_group = list(
filter(lambda s: s.index not in student_index_in_year_group, students_in_db))
students_not_exists_in_db = list(filter(lambda s: s.index not in student_index_in_db, list_of_students)) 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.add_all(students_not_exists_in_db)
db.session.commit() db.session.commit()
ygs = [YearGroupStudents(year_group_id=year_group_id, student_index=student.index) for student in
students_in_db_and_not_assigned_to_year_group + students_not_exists_in_db]
db.session.add_all(ygs)
db.session.commit()
except InvalidNameOrTypeHeaderException: except InvalidNameOrTypeHeaderException:
abort(400, "Invalid format of csv file!") abort(400, "Invalid format of csv file!")
else: else:
@ -169,7 +152,7 @@ def download_students(query: dict) -> Response:
join(ProjectSupervisor).all() join(ProjectSupervisor).all()
if len(students_and_groups) == 0: if len(students_and_groups) == 0:
abort(404, "Not found students, which are assigned to group!") abort(404, "Not found students!")
csv_file = generate_csv(students_and_groups) csv_file = generate_csv(students_and_groups)
response = Response(csv_file, mimetype='text/csv') response = Response(csv_file, mimetype='text/csv')

View File

@ -2,9 +2,10 @@ from flask import abort
from apiflask import APIBlueprint from apiflask import APIBlueprint
from ...students.models import YearGroup from ...students.models import YearGroup
from ..schemas import YearGroupSchema, MessageSchema, YearGroupPaginationSchema, YearGroupQuerySchema from ..schemas import YearGroupSchema, YearGroupPaginationSchema, YearGroupQuerySchema
from ...dependencies import db from ...dependencies import db
from ...base.utils import paginate_models from ...base.utils import paginate_models
from ...base.schemas import MessageSchema
bp = APIBlueprint("year_group", __name__, url_prefix="/year-group") bp = APIBlueprint("year_group", __name__, url_prefix="/year-group")

View File

@ -4,8 +4,8 @@ from .examination_schedule import ExaminationScheduleSchema, ExaminationSchedule
ExaminationSchedulesPaginationSchema, ExaminationSchedulesQuerySchema, WorkloadSchema ExaminationSchedulesPaginationSchema, ExaminationSchedulesQuerySchema, WorkloadSchema
from .groups import GroupQuerySchema, GroupsPaginationSchema, GroupCreateSchema, GroupEditSchema, GroupIdSchema from .groups import GroupQuerySchema, GroupsPaginationSchema, GroupCreateSchema, GroupEditSchema, GroupIdSchema
from .project_supervisor import ProjectSupervisorQuerySchema, ProjectSupervisorsPaginationSchema, \ from .project_supervisor import ProjectSupervisorQuerySchema, ProjectSupervisorsPaginationSchema, \
ProjectSupervisorCreateSchema, ProjectSupervisorEditSchema, ProjectSupervisorYearGroupSchema ProjectSupervisorCreateSchema, ProjectSupervisorEditSchema
from .students import ProjectSupervisorSchema, GroupSchema, StudentSchema, StudentsPaginationSchema, \ from .students import ProjectSupervisorSchema, GroupSchema, StudentSchema, StudentsPaginationSchema, \
StudentListFileDownloaderSchema, StudentCreateSchema, StudentEditSchema, MessageSchema, FileSchema, \ StudentListFileDownloaderSchema, StudentCreateSchema, StudentEditSchema, MessageWithIdSchema, FileSchema, \
StudentQuerySchema, YearGroupInfoQuery, DetailGroupSchema StudentQuerySchema, YearGroupInfoQuery, DetailGroupSchema
from .year_group import YearGroupSchema, YearGroupPaginationSchema, YearGroupQuerySchema from .year_group import YearGroupSchema, YearGroupPaginationSchema, YearGroupQuerySchema

View File

@ -1,7 +1,7 @@
from marshmallow import Schema, fields, validate from marshmallow import Schema, fields, validate
from ..validators import validate_index from ..validators import validate_index
from .students import GroupSchema, StudentSchema from .students import GroupSchema
class GroupQuerySchema(Schema): class GroupQuerySchema(Schema):
@ -18,13 +18,13 @@ class GroupsPaginationSchema(Schema):
class GroupCreateSchema(Schema): class GroupCreateSchema(Schema):
name = fields.Str(validate=validate.Length(min=1, max=255), required=True) name = fields.Str(validate=validate.Length(min=1, max=255), required=True)
project_supervisor_id = fields.Integer(required=True) project_supervisor_id = fields.Integer(required=True)
students = fields.List(fields.Integer(validate=validate_index, required=True)) students = fields.List(fields.Integer(required=True))
class GroupEditSchema(Schema): class GroupEditSchema(Schema):
name = fields.Str(validate=validate.Length(min=1, max=255)) name = fields.Str(validate=validate.Length(min=1, max=255))
project_supervisor_id = fields.Integer() project_supervisor_id = fields.Integer()
students = fields.List(fields.Integer(validate=validate_index)) students = fields.List(fields.Integer())
class GroupIdSchema(Schema): class GroupIdSchema(Schema):

View File

@ -20,13 +20,11 @@ class ProjectSupervisorCreateSchema(Schema):
first_name = fields.Str(validate=validate.Length(min=1, max=255), required=True) 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) 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)
class ProjectSupervisorEditSchema(Schema): class ProjectSupervisorEditSchema(Schema):
first_name = fields.Str(validate=validate.Length(min=1, max=255), required=True) first_name = fields.Str(validate=validate.Length(min=1, max=255))
last_name = fields.Str(validate=validate.Length(min=1, max=255), required=True) last_name = fields.Str(validate=validate.Length(min=1, max=255))
email = fields.Str(validate=[validate.Length(min=0, max=255), validate.Email()], required=True) email = fields.Str(validate=[validate.Length(min=0, max=255), validate.Email()])
limit_group = fields.Integer()
class ProjectSupervisorYearGroupSchema(Schema):
limit_group = fields.Integer(required=True)

View File

@ -20,7 +20,7 @@ class GroupSchema(ma.SQLAlchemyAutoSchema):
class StudentSchema(ma.SQLAlchemyAutoSchema): class StudentSchema(ma.SQLAlchemyAutoSchema):
groups = fields.List(fields.Nested(GroupSchema)) group = fields.Nested(GroupSchema)
class Meta: class Meta:
model = Student model = Student
@ -38,7 +38,6 @@ class StudentListFileDownloaderSchema(ma.Schema):
class StudentCreateSchema(ma.Schema): class StudentCreateSchema(ma.Schema):
first_name = fields.Str(validate=validate.Length(min=1, max=255), required=True) 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) last_name = fields.Str(validate=validate.Length(min=1, max=255), required=True)
pesel = fields.Str(validate=validate.Length(min=0, max=11), required=True)
index = fields.Integer(validate=validate_index, required=True) index = fields.Integer(validate=validate_index, required=True)
year_group_id = fields.Integer() year_group_id = fields.Integer()
@ -50,7 +49,7 @@ class StudentEditSchema(ma.Schema):
index = fields.Integer(validate=validate_index) index = fields.Integer(validate=validate_index)
class MessageSchema(ma.Schema): class MessageWithIdSchema(ma.Schema):
message = fields.Str(required=True) message = fields.Str(required=True)
id = fields.Str(required=False) id = fields.Str(required=False)
@ -68,7 +67,7 @@ class StudentQuerySchema(ma.Schema):
class YearGroupInfoQuery(Schema): class YearGroupInfoQuery(Schema):
id = fields.Integer(required=True) year_group_id = fields.Integer(required=True)
class DetailGroupSchema(ma.SQLAlchemyAutoSchema): class DetailGroupSchema(ma.SQLAlchemyAutoSchema):
project_supervisor = fields.Nested(ProjectSupervisorSchema) project_supervisor = fields.Nested(ProjectSupervisorSchema)

View File

@ -24,42 +24,31 @@ from ..examination_schedule.models import TermOfDefence
def check_columns(df: pd.DataFrame) -> bool: def check_columns(df: pd.DataFrame) -> bool:
headers = set(df.keys().values) headers = set(df.keys().values)
columns = ['NAZWISKO', 'IMIE', 'INDEKS', 'PESEL', 'EMAIL'] column_names = ['NAZWISKO', 'IMIE', 'INDEKS', 'EMAIL']
column_types = ['object', 'object', 'int', 'object']
if len(headers - set(columns)) != 0: return all((column_name in headers for column_name in column_names)) and \
return False all((str(df.dtypes[column_name]).startswith(column_type) for column_name, column_type in
zip(column_names, column_types)))
flag = True
col_types = ['object', 'object', 'int', 'int64', 'object']
for name, col_type in zip(columns, col_types):
if not str(df.dtypes[name]).startswith(col_type):
flag = False
break
return flag
def parse_csv(file: Union[FileStorage, TextIO]) -> Generator[Student, Any, None]: def parse_csv(file: Union[FileStorage, TextIO], year_group_id: int) -> Generator[Student, Any, None]:
df = pd.read_csv(file) df = pd.read_csv(file)
# raise Exception(df.to_string())
if not check_columns(df): if not check_columns(df):
raise InvalidNameOrTypeHeaderException raise InvalidNameOrTypeHeaderException
students = (Student(last_name=dict(item.items())['NAZWISKO'], students = (Student(last_name=dict(item.items())['NAZWISKO'],
first_name=dict(item.items())['IMIE'], first_name=dict(item.items())['IMIE'],
index=dict(item.items())['INDEKS'], index=dict(item.items())['INDEKS'],
pesel=str(int(dict(item.items())['PESEL'])) if not pd.isna( email=dict(item.items())['EMAIL'],
dict(item.items())['PESEL']) else None, year_group_id=year_group_id)
email=dict(item.items())['EMAIL'])
for _, item in df.iterrows()) for _, item in df.iterrows())
return students return students
def generate_csv(students_and_groups: List[Tuple[Student, Group]]) -> str: def generate_csv(students_and_groups: List[Tuple[Student, Group]]) -> str:
headers = ['PESEL', 'INDEKS', 'IMIE', 'NAZWISKO', 'EMAIL', 'CDYD_KOD', 'PRZ_KOD', 'TZAJ_KOD', 'GR_NR', 'PRG_KOD'] headers = ['INDEKS', 'IMIE', 'NAZWISKO', 'EMAIL', 'CDYD_KOD', 'PRZ_KOD', 'TZAJ_KOD', 'GR_NR', 'PRG_KOD']
data = [(student.pesel, student.index, student.first_name, student.last_name, student.email, data = [(student.index, student.first_name, student.last_name, student.email,
group.cdyd_kod, group.prz_kod, group.tzaj_kod, group.project_supervisor_id, group.cdyd_kod, group.prz_kod, group.tzaj_kod, group.project_supervisor_id,
None) for student, group in students_and_groups] None) for student, group in students_and_groups]
dataframe = defaultdict(list) dataframe = defaultdict(list)

View File

@ -1,12 +1,7 @@
import json import json
from typing import Tuple
from apiflask import APIFlask from apiflask import APIFlask
from werkzeug.exceptions import RequestEntityTooLarge, HTTPException from werkzeug.exceptions import HTTPException
def request_entity_too_large(error: RequestEntityTooLarge) -> Tuple[dict, int]:
return {'error': 'File too large!'}, 413
def register_error_handlers(app: APIFlask): def register_error_handlers(app: APIFlask):

View File

@ -1,44 +0,0 @@
from factory import alchemy, Sequence
from factory.faker import Faker
from factory.fuzzy import FuzzyInteger, FuzzyChoice
from .dependencies import db
from .students.models import Student, Group
from .project_supervisor.models import ProjectSupervisor
class ProjectSupervisorFactory(alchemy.SQLAlchemyModelFactory):
class Meta:
model = ProjectSupervisor
sqlalchemy_session = db.session
first_name = Faker('first_name')
last_name = Faker('last_name')
email = Faker('email')
limit_group = 4 # FuzzyInteger(3, 5)
count_groups = 4
mode = 0
class GroupFactory(alchemy.SQLAlchemyModelFactory):
class Meta:
model = Group
sqlalchemy_session = db.session
name = Sequence(lambda n: f'Group-{n}')
points_for_first_term = FuzzyInteger(1, 5)
points_for_second_term = FuzzyInteger(1, 5)
# project_supervisor = RelatedFactory(ProjectSupervisorFactory, 'project_supervisor')
class StudentFactory(alchemy.SQLAlchemyModelFactory):
class Meta:
model = Student
sqlalchemy_session = db.session
first_name = Faker('first_name')
last_name = Faker('last_name')
email = Faker('email')
index = Sequence(lambda n: 400_000 + n)
# group = RelatedFactory(GroupFactory)
mode = FuzzyChoice([True, False])

View File

@ -6,18 +6,12 @@ from ..base.utils import order_by_column_name
from ..students.models import YearGroup from ..students.models import YearGroup
class YearGroupProjectSupervisors(Base):
__tablename__ = 'year_group_project_supervisors'
project_supervisor_id = db.Column(db.Integer, db.ForeignKey('project_supervisors.id'), nullable=False)
year_group_id = db.Column(db.Integer, db.ForeignKey('year_groups.id'), nullable=False)
limit_group = db.Column(db.Integer, default=3, nullable=False)
class ProjectSupervisor(Base, Person): class ProjectSupervisor(Base, Person):
__tablename__ = "project_supervisors" __tablename__ = "project_supervisors"
year_groups = db.relationship('YearGroupProjectSupervisors', lazy=True) limit_group = db.Column(db.Integer, default=3, nullable=False)
year_group_id = db.Column(db.Integer, db.ForeignKey('year_groups.id'))
year_group = db.relationship('YearGroup', backref='project_supervisors')
@classmethod @classmethod
def search_by_fullname_and_mode_and_order_by_first_name_or_last_name(cls, year_group_id: int = None, def search_by_fullname_and_mode_and_order_by_first_name_or_last_name(cls, year_group_id: int = None,
@ -27,8 +21,8 @@ class ProjectSupervisor(Base, Person):
project_supervisors_query = cls.query project_supervisors_query = cls.query
if year_group_id is not None: if year_group_id is not None:
project_supervisors_query = project_supervisors_query.join(YearGroupProjectSupervisors). \ project_supervisors_query = project_supervisors_query. \
filter(YearGroupProjectSupervisors.year_group_id == year_group_id) filter(ProjectSupervisor.year_group_id == year_group_id)
if fullname is not None: if fullname is not None:
project_supervisors_query = project_supervisors_query.filter( project_supervisors_query = project_supervisors_query.filter(

View File

@ -3,11 +3,12 @@ from datetime import datetime
from apiflask import APIBlueprint from apiflask import APIBlueprint
from flask import abort from flask import abort
from sqlalchemy import and_, or_ from sqlalchemy import and_, or_
from ..schemas import MessageSchema, TimeAvailabilityCreateSchema, TemporaryProjectSupervisorSchema, \ from ..schemas import TimeAvailabilityCreateSchema, TemporaryProjectSupervisorSchema, \
ListOfFreeTimesSchema, ListOfTermOfDefenceSchema ListOfFreeTimesSchema, ListOfTermOfDefenceSchema
from ...dependencies import db from ...dependencies import db
from ..models import ProjectSupervisor from ..models import ProjectSupervisor
from ...examination_schedule.models import ExaminationSchedule, TemporaryAvailability, TermOfDefence from ...examination_schedule.models import ExaminationSchedule, TemporaryAvailability, TermOfDefence
from ...base.schemas import MessageSchema
bp = APIBlueprint("enrollments", __name__, url_prefix="/") bp = APIBlueprint("enrollments", __name__, url_prefix="/")

View File

@ -3,10 +3,11 @@ from flask import abort
from ...dependencies import db from ...dependencies import db
from ..models import ProjectSupervisor from ..models import ProjectSupervisor
from ..schemas import ProjectSupervisorTermQuerySchema, MessageSchema, TemporaryProjectSupervisorSchema from ..schemas import ProjectSupervisorTermQuerySchema, TemporaryProjectSupervisorSchema
from ...students.schemas import ProjectGradeSheetDetailFirstTermSchema, ProjectGradeSheetDetailSecondTermSchema, \ from ...students.schemas import ProjectGradeSheetDetailFirstTermSchema, ProjectGradeSheetDetailSecondTermSchema, \
ProjectGradeSheetEditFirstTermSchema, ProjectGradeSheetEditSecondTermSchema ProjectGradeSheetEditFirstTermSchema, ProjectGradeSheetEditSecondTermSchema
from ...students.models import Group, ProjectGradeSheet from ...students.models import Group, ProjectGradeSheet
from ...base.schemas import MessageSchema
bp = APIBlueprint("project_grade_sheet_for_project_supervisor", __name__, url_prefix="/project-grade-sheet") bp = APIBlueprint("project_grade_sheet_for_project_supervisor", __name__, url_prefix="/project-grade-sheet")

View File

@ -1,10 +1,6 @@
from marshmallow import fields, validate, Schema from marshmallow import fields, validate, Schema
class MessageSchema(Schema):
message = fields.Str()
class FreeTimeSchema(Schema): class FreeTimeSchema(Schema):
id = fields.Integer() id = fields.Integer()
start_date = fields.DateTime(required=True) start_date = fields.DateTime(required=True)
@ -29,6 +25,7 @@ class TimeAvailabilityCreateSchema(Schema):
class TemporaryProjectSupervisorSchema(Schema): class TemporaryProjectSupervisorSchema(Schema):
id = fields.Integer(required=True) id = fields.Integer(required=True)
class ProjectSupervisorTermQuerySchema(Schema): class ProjectSupervisorTermQuerySchema(Schema):
id = fields.Integer(required=True) id = fields.Integer(required=True)
term = fields.Integer(required=True, validate=validate.OneOf([1, 2])) term = fields.Integer(required=True, validate=validate.OneOf([1, 2]))

View File

@ -8,20 +8,12 @@ from ..base.utils import order_by_column_name
from ..examination_schedule.models import TermOfDefence from ..examination_schedule.models import TermOfDefence
class YearGroupStudents(Base):
__tablename__ = 'year_group_students'
year_group_id = db.Column(db.Integer, db.ForeignKey('year_groups.id', ondelete='CASCADE'))
student_index = db.Column(db.Integer, db.ForeignKey('students.index', ondelete='CASCADE'))
class YearGroup(Base): class YearGroup(Base):
__tablename__ = 'year_groups' __tablename__ = 'year_groups'
name = db.Column(db.String(50), nullable=False) name = db.Column(db.String(50), nullable=False)
mode = db.Column(db.String(1), nullable=False) mode = db.Column(db.String(1), nullable=False)
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
students = db.relationship("YearGroupStudents", lazy='joined')
__table__args = ( __table__args = (
db.UniqueConstraint('name', 'mode', name='uc_name_mode_year_group') db.UniqueConstraint('name', 'mode', name='uc_name_mode_year_group')
@ -30,7 +22,7 @@ class YearGroup(Base):
students_groups = db.Table('students_groups', students_groups = db.Table('students_groups',
db.Column('group_id', db.ForeignKey('groups.id'), nullable=False), db.Column('group_id', db.ForeignKey('groups.id'), nullable=False),
db.Column('student_index', db.ForeignKey('students.index'), nullable=False)) db.Column('student_id', db.ForeignKey('students.id'), nullable=False))
class Group(Base): class Group(Base):
@ -41,12 +33,12 @@ class Group(Base):
prz_kod = db.Column(db.String(60), default='06-DPRILI0') prz_kod = db.Column(db.String(60), default='06-DPRILI0')
tzaj_kod = db.Column(db.String(60), default='LAB') tzaj_kod = db.Column(db.String(60), default='LAB')
project_supervisor_id = db.Column(db.Integer, db.ForeignKey('project_supervisors.id')) project_supervisor_id = db.Column(db.Integer, db.ForeignKey('project_supervisors.id'))
project_supervisor = db.relationship('ProjectSupervisor', backref='groups', lazy='joined') project_supervisor = db.relationship('ProjectSupervisor', backref='groups')
year_group_id = db.Column(db.Integer, db.ForeignKey('year_groups.id')) year_group_id = db.Column(db.Integer, db.ForeignKey('year_groups.id'))
year_group = db.relationship('YearGroup', backref='groups', lazy='joined') year_group = db.relationship('YearGroup', backref='groups', lazy='joined')
points_for_first_term = db.Column(db.Integer, default=0, nullable=False) points_for_first_term = db.Column(db.Integer, default=0, nullable=False)
points_for_second_term = db.Column(db.Integer, default=0, nullable=False) points_for_second_term = db.Column(db.Integer, default=0, nullable=False)
students = db.relationship('Student', secondary=students_groups, back_populates='groups', lazy='joined') students = db.relationship('Student', secondary=students_groups, back_populates='groups')
@classmethod @classmethod
def search_by_name(cls, year_group_id: int, search_name: str = None) -> BaseQuery: def search_by_name(cls, year_group_id: int, search_name: str = None) -> BaseQuery:
@ -62,7 +54,7 @@ class ProjectGradeSheet(Base):
__tablename__ = 'project_grade_sheets' __tablename__ = 'project_grade_sheets'
group_id = db.Column(db.Integer, db.ForeignKey('groups.id')) group_id = db.Column(db.Integer, db.ForeignKey('groups.id'))
group = db.relationship('Group', backref='project_grade_sheet', uselist=False, lazy='joined') group = db.relationship('Group', backref='project_grade_sheet', uselist=False)
presentation_required_content_1 = db.Column(db.Integer, default=0) presentation_required_content_1 = db.Column(db.Integer, default=0)
presentation_required_content_2 = db.Column(db.Integer, default=0) presentation_required_content_2 = db.Column(db.Integer, default=0)
@ -125,20 +117,19 @@ class ProjectGradeSheet(Base):
products_project_technology_2 = db.Column(db.Integer, default=0) products_project_technology_2 = db.Column(db.Integer, default=0)
class Student(Person): class Student(Base, Person):
__tablename__ = "students" __tablename__ = "students"
pesel = db.Column(db.String(11), default='') index = db.Column(db.Integer, nullable=False)
index = db.Column(db.Integer, primary_key=True) groups = db.relationship('Group', secondary=students_groups, back_populates='students')
groups = db.relationship('Group', secondary=students_groups, back_populates='students', lazy='joined') year_group_id = db.Column(db.Integer, db.ForeignKey('year_groups.id'))
year_groups = db.relationship("YearGroupStudents", lazy='joined') year_group = db.relationship('YearGroup', backref='students')
@classmethod @classmethod
def search_by_fullname_and_mode_and_order_by_first_name_or_last_name(cls, year_group_id: int, fullname: str = None, def search_by_fullname_and_mode_and_order_by_first_name_or_last_name(cls, year_group_id: int, fullname: str = None,
order_by_first_name: str = None, order_by_first_name: str = None,
order_by_last_name: str = None) -> BaseQuery: order_by_last_name: str = None) -> BaseQuery:
student_query = cls.query.join(YearGroupStudents, isouter=True). \ student_query = cls.query.filter(Student.year_group_id == year_group_id)
filter(YearGroupStudents.year_group_id == year_group_id)
if fullname is not None: 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}%'))

View File

@ -3,11 +3,9 @@ from flask import Blueprint
from .enrollments import bp as enrollments_bp from .enrollments import bp as enrollments_bp
from .project_grade_sheet import bp as project_grade_sheet_bp from .project_grade_sheet import bp as project_grade_sheet_bp
from .registrations import bp as registrations_bp from .registrations import bp as registrations_bp
from .year_group import bp as year_group_bp
bp = Blueprint("students", __name__, url_prefix="/students") bp = Blueprint("students", __name__, url_prefix="/students")
bp.register_blueprint(enrollments_bp) bp.register_blueprint(enrollments_bp)
bp.register_blueprint(project_grade_sheet_bp) bp.register_blueprint(project_grade_sheet_bp)
bp.register_blueprint(registrations_bp) bp.register_blueprint(registrations_bp)
bp.register_blueprint(year_group_bp)

View File

@ -3,12 +3,13 @@ import datetime
from apiflask import APIBlueprint from apiflask import APIBlueprint
from flask import abort from flask import abort
from ..schemas import MessageSchema, TemporaryStudentSchema, ExaminationScheduleListSchema, \ from ..schemas import TemporaryStudentSchema, ExaminationScheduleListSchema, \
TermOfDefenceStudentListSchema TermOfDefenceStudentListSchema
from ...dependencies import db from ...dependencies import db
from ..models import Student, Group, TermOfDefence from ..models import Student, Group, TermOfDefence
from ...examination_schedule.models import ExaminationSchedule from ...examination_schedule.models import ExaminationSchedule
from ...project_supervisor.models import ProjectSupervisor from ...project_supervisor.models import ProjectSupervisor
from ...base.schemas import MessageSchema
bp = APIBlueprint("enrollments", __name__, url_prefix="/") bp = APIBlueprint("enrollments", __name__, url_prefix="/")

View File

@ -1,6 +1,6 @@
from apiflask import APIBlueprint from apiflask import APIBlueprint
from ...project_supervisor.models import ProjectSupervisor, YearGroupProjectSupervisors from ...project_supervisor.models import ProjectSupervisor
from ..models import Group from ..models import Group
from ...dependencies import db from ...dependencies import db
from ..schemas import ProjectSupervisorQuerySchema, ProjectSupervisorPaginationSchema from ..schemas import ProjectSupervisorQuerySchema, ProjectSupervisorPaginationSchema
@ -16,12 +16,11 @@ def list_available_groups(year_group_id: int, query: dict) -> dict:
page = query.get('page') page = query.get('page')
per_page = query.get('per_page') per_page = query.get('per_page')
available_groups = (YearGroupProjectSupervisors.limit_group - db.func.count(Group.id)) available_groups = (ProjectSupervisor.limit_group - db.func.count(Group.id))
ps_query = db.session. \ ps_query = db.session. \
query(ProjectSupervisor, available_groups). \ query(ProjectSupervisor, available_groups). \
join(Group, isouter=True). \ join(Group, isouter=True). \
join(YearGroupProjectSupervisors, isouter=True). \ filter(ProjectSupervisor.year_group_id == year_group_id).\
filter(YearGroupProjectSupervisors.year_group_id == year_group_id).\
group_by(ProjectSupervisor.id) group_by(ProjectSupervisor.id)
data = paginate_models(page, ps_query, per_page) data = paginate_models(page, ps_query, per_page)

View File

@ -1,32 +0,0 @@
from apiflask import APIBlueprint
from flask import abort
from ...students.models import YearGroup, YearGroupStudents
from ...dependencies import db
from ...base.utils import paginate_models
from ..schemas import YearGroupQueryStudentSchema, YearGroupStudentPaginationSchema
from ..models import Student
bp = APIBlueprint("year_group", __name__, url_prefix="/year-group")
@bp.get('/')
@bp.input(YearGroupQueryStudentSchema, location='query')
@bp.output(YearGroupStudentPaginationSchema)
def list_of_year_groups(query: dict) -> dict:
index = query.get('index')
st = Student.query.filter(Student.index == index).first()
if st is None:
abort(404, "Not found student!")
####################################
page = query.get('page')
per_page = query.get('per_page')
year_group_query = db.session.query(YearGroup).join(YearGroupStudents, isouter=True). \
filter(YearGroupStudents.student_index == st.index).order_by(db.desc(YearGroup.created_at))
data = paginate_models(page, year_group_query, per_page)
return {
"year_groups": data['items'],
"max_pages": data['max_pages']
}

View File

@ -24,10 +24,6 @@ class TemporaryStudentSchema(Schema):
student_index = fields.Integer(required=True) student_index = fields.Integer(required=True)
class MessageSchema(Schema):
message = fields.Str()
class ExaminationScheduleSchema(Schema): class ExaminationScheduleSchema(Schema):
id = fields.Integer() id = fields.Integer()
title = fields.Str() title = fields.Str()
@ -51,39 +47,26 @@ class TermOfDefenceStudentItemSchema(Schema):
end_date = fields.DateTime() end_date = fields.DateTime()
members_of_committee = fields.List(fields.Nested(ProjectSupervisorCommitteeSchema)) members_of_committee = fields.List(fields.Nested(ProjectSupervisorCommitteeSchema))
class StudentDataItemSchema(Schema): class StudentDataItemSchema(Schema):
index = fields.Integer() index = fields.Integer()
first_name = fields.Str() first_name = fields.Str()
last_name = fields.Str() last_name = fields.Str()
class GroupDataItemSchema(Schema): class GroupDataItemSchema(Schema):
name = fields.Str() name = fields.Str()
students = fields.List(fields.Nested(StudentDataItemSchema)) students = fields.List(fields.Nested(StudentDataItemSchema))
class AssignedGroupToTermOfDefenceItemSchema(TermOfDefenceStudentItemSchema): class AssignedGroupToTermOfDefenceItemSchema(TermOfDefenceStudentItemSchema):
group = fields.Nested(GroupDataItemSchema) group = fields.Nested(GroupDataItemSchema)
class TermOfDefenceStudentListSchema(Schema): class TermOfDefenceStudentListSchema(Schema):
term_of_defences = fields.List(fields.Nested(AssignedGroupToTermOfDefenceItemSchema)) term_of_defences = fields.List(fields.Nested(AssignedGroupToTermOfDefenceItemSchema))
class YearGroupStudentSchema(Schema):
id = fields.Integer()
name = fields.Str()
mode = fields.Str()
class YearGroupStudentPaginationSchema(Schema):
year_groups = fields.List(fields.Nested(YearGroupStudentSchema))
max_pages = fields.Integer()
class YearGroupQueryStudentSchema(Schema):
index = fields.Integer(required=True) # it will be removed
page = fields.Integer()
per_page = fields.Integer()
class StudentIndexQueryTempSchema(Schema): class StudentIndexQueryTempSchema(Schema):
index = fields.Integer(required=True) # it will be removed index = fields.Integer(required=True) # it will be removed
term = fields.Integer(required=True, validate=validate.OneOf([1, 2])) term = fields.Integer(required=True, validate=validate.OneOf([1, 2]))

View File

@ -1,8 +1,8 @@
"""empty message """empty message
Revision ID: b3003ddd0564 Revision ID: 3fd120fc5e12
Revises: Revises:
Create Date: 2023-01-03 18:37:53.103562 Create Date: 2023-01-14 00:03:06.327441
""" """
from alembic import op from alembic import op
@ -10,7 +10,7 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision = 'b3003ddd0564' revision = '3fd120fc5e12'
down_revision = None down_revision = None
branch_labels = None branch_labels = None
depends_on = None depends_on = None
@ -18,27 +18,6 @@ depends_on = None
def upgrade(): def upgrade():
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.create_table('project_supervisors',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('first_name', sa.String(length=255), nullable=False),
sa.Column('last_name', sa.String(length=255), nullable=False),
sa.Column('email', sa.String(length=120), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('email')
)
op.create_index(op.f('ix_project_supervisors_first_name'), 'project_supervisors', ['first_name'], unique=False)
op.create_index(op.f('ix_project_supervisors_last_name'), 'project_supervisors', ['last_name'], unique=False)
op.create_table('students',
sa.Column('first_name', sa.String(length=255), nullable=False),
sa.Column('last_name', sa.String(length=255), nullable=False),
sa.Column('email', sa.String(length=120), nullable=True),
sa.Column('pesel', sa.String(length=11), nullable=True),
sa.Column('index', sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint('index'),
sa.UniqueConstraint('email')
)
op.create_index(op.f('ix_students_first_name'), 'students', ['first_name'], unique=False)
op.create_index(op.f('ix_students_last_name'), 'students', ['last_name'], unique=False)
op.create_table('year_groups', op.create_table('year_groups',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('name', sa.String(length=50), nullable=False), sa.Column('name', sa.String(length=50), nullable=False),
@ -59,6 +38,32 @@ def upgrade():
sa.PrimaryKeyConstraint('id'), sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('title') 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', op.create_table('groups',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('name', sa.String(length=60), nullable=False), sa.Column('name', sa.String(length=60), nullable=False),
@ -73,21 +78,14 @@ def upgrade():
sa.ForeignKeyConstraint(['year_group_id'], ['year_groups.id'], ), sa.ForeignKeyConstraint(['year_group_id'], ['year_groups.id'], ),
sa.PrimaryKeyConstraint('id') sa.PrimaryKeyConstraint('id')
) )
op.create_table('year_group_project_supervisors', op.create_table('temporary_availabilities',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('start_date', sa.DateTime(), nullable=False),
sa.Column('end_date', sa.DateTime(), nullable=False),
sa.Column('examination_schedule_id', sa.Integer(), nullable=False),
sa.Column('project_supervisor_id', sa.Integer(), nullable=False), sa.Column('project_supervisor_id', sa.Integer(), nullable=False),
sa.Column('year_group_id', sa.Integer(), nullable=False), sa.ForeignKeyConstraint(['examination_schedule_id'], ['examination_schedules.id'], ),
sa.Column('limit_group', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['project_supervisor_id'], ['project_supervisors.id'], ), sa.ForeignKeyConstraint(['project_supervisor_id'], ['project_supervisors.id'], ),
sa.ForeignKeyConstraint(['year_group_id'], ['year_groups.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('year_group_students',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('year_group_id', sa.Integer(), nullable=True),
sa.Column('student_index', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['student_index'], ['students.index'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['year_group_id'], ['year_groups.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id') sa.PrimaryKeyConstraint('id')
) )
op.create_table('project_grade_sheets', op.create_table('project_grade_sheets',
@ -154,19 +152,9 @@ def upgrade():
) )
op.create_table('students_groups', op.create_table('students_groups',
sa.Column('group_id', sa.Integer(), nullable=False), sa.Column('group_id', sa.Integer(), nullable=False),
sa.Column('student_index', sa.Integer(), nullable=False), sa.Column('student_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['group_id'], ['groups.id'], ), sa.ForeignKeyConstraint(['group_id'], ['groups.id'], ),
sa.ForeignKeyConstraint(['student_index'], ['students.index'], ) sa.ForeignKeyConstraint(['student_id'], ['students.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('term_of_defences', op.create_table('term_of_defences',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
@ -191,18 +179,18 @@ def downgrade():
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.drop_table('committees') op.drop_table('committees')
op.drop_table('term_of_defences') op.drop_table('term_of_defences')
op.drop_table('temporary_availabilities')
op.drop_table('students_groups') op.drop_table('students_groups')
op.drop_table('project_grade_sheets') op.drop_table('project_grade_sheets')
op.drop_table('year_group_students') op.drop_table('temporary_availabilities')
op.drop_table('year_group_project_supervisors')
op.drop_table('groups') op.drop_table('groups')
op.drop_table('examination_schedules')
op.drop_table('year_groups')
op.drop_index(op.f('ix_students_last_name'), table_name='students') op.drop_index(op.f('ix_students_last_name'), table_name='students')
op.drop_index(op.f('ix_students_first_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_table('students')
op.drop_index(op.f('ix_project_supervisors_last_name'), table_name='project_supervisors') 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_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('project_supervisors')
op.drop_table('examination_schedules')
op.drop_table('year_groups')
# ### end Alembic commands ### # ### end Alembic commands ###