add endpoint of download examination schedule pdf file

This commit is contained in:
dominik24c 2022-11-02 23:21:08 +01:00
parent 03ffd355ed
commit c71531ca10
6 changed files with 146 additions and 21 deletions

View File

@ -5,7 +5,8 @@ ENV PYTHONUNBUFFERED 1
WORKDIR /app WORKDIR /app
RUN apt update && pip install --upgrade pip RUN apt update && \
pip install --upgrade pip
COPY requirements.txt . COPY requirements.txt .

View File

@ -1,11 +1,16 @@
from apiflask import APIBlueprint import datetime
from flask import abort
from apiflask import APIBlueprint
from flask import abort, Response, make_response
from ...base.utils import paginate_models
from ...dependencies import db
from ...examination_schedule.models import ExaminationSchedule, Enrollment
from ...students.models import Group
from ...project_supervisor.models import ProjectSupervisor
from ..schemas import ExaminationScheduleSchema, ExaminationScheduleUpdateSchema, MessageSchema, \ from ..schemas import ExaminationScheduleSchema, ExaminationScheduleUpdateSchema, MessageSchema, \
ExaminationSchedulesQuerySchema, ExaminationSchedulesPaginationSchema ExaminationSchedulesQuerySchema, ExaminationSchedulesPaginationSchema
from ...examination_schedule.models import ExaminationSchedule from ..utils import generate_examination_schedule_pdf_file
from ...dependencies import db
from ...base.utils import paginate_models
bp = APIBlueprint("examination_schedule", __name__, url_prefix="/examination_schedule") bp = APIBlueprint("examination_schedule", __name__, url_prefix="/examination_schedule")
@ -71,3 +76,33 @@ def set_date_of_examination_schedule(id: int, data: dict) -> dict:
examination_schedule_query.update(data) examination_schedule_query.update(data)
db.session.commit() db.session.commit()
return {"message": "You set date of examination schedule!"} return {"message": "You set date of examination schedule!"}
@bp.post('/<int:examination_schedule_id>/download/')
def download_examination_schedule(examination_schedule_id: int) -> Response:
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(Enrollment.start_date)).distinct().all()
# print(distinct_dates)
nested_enrollments = []
for d in distinct_dates:
date_tmp = datetime.datetime.strptime(d[0], "%Y-%m-%d").date()
enrollment = db.session.query(Enrollment).join(ExaminationSchedule, isouter=True).\
join(Group, isouter=True).join(ProjectSupervisor, isouter=True). \
filter(ExaminationSchedule.id == examination_schedule_id).filter(
db.func.Date(Enrollment.start_date) == date_tmp).all()
nested_enrollments.append(enrollment)
# print(nested_enrollments)
pdf = generate_examination_schedule_pdf_file(examination_schedule.title, nested_enrollments)
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
return response

View File

@ -1,10 +1,19 @@
from typing import Generator, Any, List import datetime
from collections import defaultdict from collections import defaultdict
from io import BytesIO
from itertools import chain
from typing import Generator, Any, List
import pandas as pd import pandas as pd
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 .exceptions import InvalidNameOrTypeHeaderException from .exceptions import InvalidNameOrTypeHeaderException
from ..students.models import Student from ..students.models import Student
from ..examination_schedule.models import Enrollment
def check_columns(df: pd.DataFrame) -> bool: def check_columns(df: pd.DataFrame) -> bool:
@ -55,3 +64,82 @@ def generate_csv(students: List[Student]) -> str:
df = pd.DataFrame(dataframe) df = pd.DataFrame(dataframe)
return df.to_csv(index=False) return df.to_csv(index=False)
def generate_examination_schedule_pdf_file(title: str, nested_enrollments: List[List[Enrollment]]) -> bytes:
pagesize = (297 * mm, 210 * mm)
headers = ["lp.", "Godzina", "Nazwa projektu", "Opiekun", "Zespol", "Komisja"]
pdf_buffer = BytesIO()
my_doc = SimpleDocTemplate(
pdf_buffer,
pagesize=pagesize,
topMargin=1 * inch,
leftMargin=1 * inch,
rightMargin=1 * inch,
bottomMargin=1 * inch,
title=title
)
style = getSampleStyleSheet()
bodyText = style['BodyText']
bodyText.fontName = 'Helvetica'
normal = style["Heading1"]
normal.alignment = TA_CENTER
flowables = []
# print(nested_enrollments)
for enrollments in nested_enrollments:
if len(enrollments) == 0:
continue
date = datetime.datetime.strftime(enrollments[0].start_date, '%d/%m/%Y')
paragraph_1 = Paragraph(f"{title} ~ {date}", normal)
flowables.append(paragraph_1)
data = [headers]
for idx, e in enumerate(enrollments, start=1):
new_date = e.start_date + datetime.timedelta(hours=2)
group_name = e.group.name if e.group is not None else ""
if group_name != '':
ps = e.group.project_supervisor
project_supervisor_fullname = f"{ps.first_name[0]}. {ps.last_name}"
students = e.group.students
# print(students)
team = ", ".join([f"{s.first_name} {s.last_name}" for s in students])
else:
project_supervisor_fullname = ""
team = ""
members = e.committee.members
# print(members)
if len(members) == 0:
committee = ''
else:
members_iter = (f"{m.first_name[0]} {m.last_name}" for m in members)
if project_supervisor_fullname != '':
members_iter = chain(members_iter, [project_supervisor_fullname])
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),
])
# 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, 4 * inch, 3 * inch]
)
flowables.append(table)
flowables.append(PageBreak())
my_doc.build(flowables)
pdf_value = pdf_buffer.getvalue()
pdf_buffer.close()
return pdf_value

View File

@ -20,6 +20,7 @@ class Enrollment(Base):
examination_schedule = db.relationship('ExaminationSchedule', backref='enrollments') examination_schedule = db.relationship('ExaminationSchedule', backref='enrollments')
committee = db.relationship("Committee", uselist=False, backref=db.backref('enrollment', passive_deletes=True)) committee = db.relationship("Committee", uselist=False, backref=db.backref('enrollment', passive_deletes=True))
group_id = db.Column(db.Integer, db.ForeignKey('groups.id')) group_id = db.Column(db.Integer, db.ForeignKey('groups.id'))
group = db.relationship("Group", uselist=False, backref='enrollment')
class Committee(Base): class Committee(Base):

View File

@ -17,7 +17,7 @@ class Group(Base):
project_supervisor = db.relationship('ProjectSupervisor', backref='groups', lazy=True) project_supervisor = db.relationship('ProjectSupervisor', backref='groups', lazy=True)
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)
enrollment = db.relationship('Enrollment', uselist=False, backref='group') # enrollment = db.relationship('Enrollment', uselist=False, backref='group')
@classmethod @classmethod
def search_by_name(cls, search_name: str = None) -> BaseQuery: def search_by_name(cls, search_name: str = None) -> BaseQuery:

View File

@ -1,15 +1,15 @@
alembic==1.7.7 click>=8.1.3,<8.2.0
click==8.1.3 Flask>=2.1.2,<2.2.0
Flask==2.1.2 Flask-Cors>=3.0.10,<3.1.0
Flask-Cors==3.0.10 flask-marshmallow>=0.14.0,<0.15.0
flask-marshmallow==0.14.0 Flask-Migrate>=3.1.0,<3.2.0
Flask-Migrate==3.1.0 Flask-SQLAlchemy>=2.5.1,<2.6.0
Flask-SQLAlchemy==2.5.1 MarkupSafe>=2.1.1,<2.2.0
MarkupSafe==2.1.1 marshmallow-sqlalchemy>=0.28.0,<0.29.0
marshmallow-sqlalchemy==0.28.0 pandas>=1.4.2,<1.5.0
pandas==1.4.2 pytest>=7.1.2,<7.2.0
pytest==7.1.2 pytest-flask>=1.2.0,<1.3.0
pytest-flask==1.2.0
apiflask>=1.0.2,<1.1.0 apiflask>=1.0.2,<1.1.0
python-dotenv==0.21.0 python-dotenv==0.21.0
factory_boy==3.2.1 factory_boy>=3.2.1,<3.3.0
reportlab>=3.6.12,<3.7.0