diff --git a/backend/app/coordinator/routes/groups.py b/backend/app/coordinator/routes/groups.py index 16b824f..5f13583 100644 --- a/backend/app/coordinator/routes/groups.py +++ b/backend/app/coordinator/routes/groups.py @@ -80,7 +80,7 @@ def create_group(year_group_id: int, data: dict) -> dict: return {"message": "Group was created!"} -@bp.get("//") +@bp.get("//detail/") @bp.output(GroupSchema) def detail_group(id: int) -> Group: group = Group.query.filter_by(id=id).first() diff --git a/backend/app/coordinator/routes/students.py b/backend/app/coordinator/routes/students.py index 203c423..804a00e 100644 --- a/backend/app/coordinator/routes/students.py +++ b/backend/app/coordinator/routes/students.py @@ -3,12 +3,12 @@ from itertools import islice from flask import Response, abort from apiflask import APIBlueprint -from sqlalchemy.exc import IntegrityError +from sqlalchemy import or_ from flask_sqlalchemy import get_debug_queries from ...students.models import Student, Group, YearGroup, YearGroupStudents from ...project_supervisor.models import ProjectSupervisor -from ..schemas import StudentSchema, StudentEditSchema, StudentsPaginationSchema, \ +from ..schemas import StudentSchema, StudentEditSchema, StudentsPaginationSchema, YearGroupInfoQuery, \ StudentCreateSchema, MessageSchema, FileSchema, StudentQuerySchema, StudentListFileDownloaderSchema from ...dependencies import db from ..utils import parse_csv, generate_csv @@ -109,27 +109,49 @@ def create_student(data: dict) -> dict: @bp.post("/upload/") +@bp.input(YearGroupInfoQuery, location='query') @bp.input(FileSchema, location='form_and_files') @bp.output(MessageSchema) -def upload_students(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, + they will be omitted""" + year_group_id = query.get('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') if uploaded_file and is_allowed_extensions(uploaded_file.filename): try: - students = parse_csv(uploaded_file, mode=True) + students = parse_csv(uploaded_file) while True: sliced_students = islice(students, 5) list_of_students = list(sliced_students) if len(list_of_students) == 0: break - db.session.add_all(list_of_students) + + students_in_db = Student.query.filter(or_(Student.index == s.index for s in list_of_students)).all() + students_in_db_and_assigned_to_year_group = Student.query.join(YearGroupStudents, isouter=True). \ + filter(YearGroupStudents.year_group_id == year_group_id). \ + filter(or_(Student.index == s.index for s in list_of_students)).all() + + student_index_in_db = [s.index for s in students_in_db] + student_index_in_year_group = [s.index for s in students_in_db_and_assigned_to_year_group] + students_in_db_and_not_assigned_to_year_group = list( + filter(lambda s: s.index not in student_index_in_year_group, students_in_db)) + + students_not_exists_in_db = list(filter(lambda s: s.index not in student_index_in_db, list_of_students)) + db.session.add_all(students_not_exists_in_db) db.session.commit() + + ygs = [YearGroupStudents(year_group_id=year_group_id, student_index=student.index) for student in + students_in_db_and_not_assigned_to_year_group + students_not_exists_in_db] + db.session.add_all(ygs) + db.session.commit() + except InvalidNameOrTypeHeaderException: abort(400, "Invalid format of csv file!") - except IntegrityError as e: - # in the future create sql query checks index and add only these students, which didn't exist in db - abort(400, "These students have already exist!") - else: abort(400, "Invalid extension of file") @@ -139,14 +161,15 @@ def upload_students(file: dict) -> dict: @bp.post("/download/") @bp.input(StudentListFileDownloaderSchema, location='query') def download_students(query: dict) -> Response: - mode = query.get('mode') - mode = mode if mode is not None else True - students = db.session.query(Student).join(Group). \ - join(ProjectSupervisor).filter(Student.mode == mode).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) == 0: + if len(students_and_groups) == 0: abort(404, "Not found students, which are assigned to group!") - csv_file = generate_csv(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") return response diff --git a/backend/app/coordinator/schemas/__init__.py b/backend/app/coordinator/schemas/__init__.py index b5a0cfc..caa8e29 100644 --- a/backend/app/coordinator/schemas/__init__.py +++ b/backend/app/coordinator/schemas/__init__.py @@ -6,5 +6,5 @@ from .project_supervisor import ProjectSupervisorQuerySchema, ProjectSupervisors ProjectSupervisorCreateSchema, ProjectSupervisorEditSchema, ProjectSupervisorYearGroupSchema from .students import ProjectSupervisorSchema, GroupSchema, StudentSchema, StudentsPaginationSchema, \ StudentListFileDownloaderSchema, StudentCreateSchema, StudentEditSchema, MessageSchema, FileSchema, \ - StudentQuerySchema + StudentQuerySchema, YearGroupInfoQuery from .year_group import YearGroupSchema, YearGroupPaginationSchema, YearGroupQuerySchema diff --git a/backend/app/coordinator/schemas/students.py b/backend/app/coordinator/schemas/students.py index c29af19..269f574 100644 --- a/backend/app/coordinator/schemas/students.py +++ b/backend/app/coordinator/schemas/students.py @@ -1,4 +1,4 @@ -from marshmallow import fields, validate +from marshmallow import fields, validate, Schema from ...dependencies import ma from ...students.models import Student, Group @@ -32,7 +32,7 @@ class StudentsPaginationSchema(ma.Schema): class StudentListFileDownloaderSchema(ma.Schema): - mode = fields.Integer() + year_group_id = fields.Integer(required=True) class StudentCreateSchema(ma.Schema): @@ -64,3 +64,7 @@ class StudentQuerySchema(ma.Schema): order_by_last_name = fields.Str() page = fields.Integer() per_page = fields.Integer() + + +class YearGroupInfoQuery(Schema): + id = fields.Integer(required=True) diff --git a/backend/app/coordinator/utils.py b/backend/app/coordinator/utils.py index 70b64e0..a337871 100644 --- a/backend/app/coordinator/utils.py +++ b/backend/app/coordinator/utils.py @@ -2,7 +2,7 @@ import datetime from collections import defaultdict from io import BytesIO from itertools import chain -from typing import Generator, Any, List +from typing import Generator, Any, List, Tuple import pandas as pd from reportlab.lib import colors @@ -10,10 +10,10 @@ 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 werkzeug.datastructures import FileStorage from .exceptions import InvalidNameOrTypeHeaderException -from ..students.models import Student -# from ..examination_schedule.models import Enrollment +from ..students.models import Student, Group def check_columns(df: pd.DataFrame) -> bool: @@ -34,7 +34,7 @@ def check_columns(df: pd.DataFrame) -> bool: return flag -def parse_csv(file, mode) -> Generator[Student, Any, None]: +def parse_csv(file: FileStorage) -> Generator[Student, Any, None]: df = pd.read_csv(file) if not check_columns(df): @@ -45,18 +45,17 @@ def parse_csv(file, mode) -> Generator[Student, Any, None]: index=dict(item.items())['INDEKS'], pesel=str(int(dict(item.items())['PESEL'])) if not pd.isna( dict(item.items())['PESEL']) else None, - email=dict(item.items())['EMAIL'], - mode=mode) + email=dict(item.items())['EMAIL']) for _, item in df.iterrows()) return students -def generate_csv(students: List[Student]) -> 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'] data = [(student.pesel, student.index, student.first_name, student.last_name, student.email, - student.group.cdyd_kod, student.group.prz_kod, student.group.tzaj_kod, student.group.project_supervisor_id, - None) for student in students] + 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):