Reportlab – wygenerowanie i pobranie pdf-a we Flasku bez tworzenia pliku na serwerze

Rozwiązany problem: jak wygenerować pdf z danymi zamówienia i kodem paskowym rezerwacji bez zapisu pliku na dysku, który może być następnie pobrany przez użytkownika serwisu we Flasku.

Do osadzenia elementów dokumentu takich jak teksty, tabele itp. możemy użyć obiekt Canvas. Jako argument możemy podać nazwę, jaką będzie posiadał utworzony pdf, ale w tym przypadku zamiast tworzyć plik na serwerze użyję bufora. Jawnie podaję rozmiar strony jako A4.

from io import BytesIO
from reportlab.pdfgen.canvas import Canvas
from reportlab.lib.pagesizes import A4

buffer = BytesIO()
canvas = Canvas(buffer, pagesize=A4)

Aby użyć niestandardowych znaków np. polskich liter z ogonkami należy zarejestrować odpowiednie czcionki, które obsługują wymagane znaki. W przykładowym pliku pdf użyłem czcionki Vera.

from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont

pdfmetrics.registerFont(TTFont('Vera', 'Vera.ttf'))

Tak zarejestrowaną czcionkę można już użyć:

canvas.setFont("Vera", size=10)

Obrazy wygenerowane na podstawie danych z aplikacji (w tym przypadku kod kreskowy rezerwacji) umieszczam w tworzonym dokumencie za pomocą metody drawImage(), która jako pierwszy argument pobiera obiekt ImageReader tzn.

im = ImageReader(image)
canvas.drawImage(im, x=0, y=-5*cm, width=150, height=100)

Listę zawierającą łańcuchy tekstowe można dodać do dokumentu wykorzystując obiekt tekstowy tzn.

txt_obj = canvas.beginText(14, -6.5 * cm)
txt_lst = ["line of text 1", "line of text 2", "line of text 3"]
for line in txt_lst:
        txt_obj.textOut(line)
        txt_obj.moveCursor(0, 16)
canvas.drawText(txt_obj)

Tabelę do dokumentu można dodać za pomocą obiektu klasy Table tzn. osobno definiuję listę zawierającą dane poszczególnych wierszy tabeli (zmienna table_data). Osobno również definiuję style obowiązujące w całej lub w części tabeli.

t = Table(table_data, colWidths=[60, 230, 70, 60, 50], rowHeights=30)
style = [('BACKGROUND',(0,0),(-1,-2),colors.lightblue),
            ('ALIGN',(0,-1),(-1,-1),'CENTER'),
            ('BOX', (0,0), (-1,-1), 0.25, colors.black),
            ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
            ('FONTSIZE', (0,0), (-1,-1), 10),
            ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
            ('VALIGN', (0, 0), (-1, -1), 'MIDDLE')]
t.setStyle(tblstyle=style)
t.wrapOn(canvas, 10, 10)
    t.drawOn(canvas=canvas, x=20, y=-22*cm)

Następnie zapisuję tak utworzony obiekt Canvas i zwracam powstały bufor do kontrolera. W celu przesłania pliku pdf na podstawie danych z bufora wykorzystuję funkcję send_file() Flaska:

from flask.helpers import send_file

@bp.route('/get-pdf/<int:id>', methods=['GET'])
@login_required
def get_pdf(id):
    contract = Contract.query.get(id)
    contractor = User.query.get(contract.contractor_id)
    booking = Booking.query.filter_by(contract_id=contract.id).first()
    pdf = create_pdf(booking_no=booking.id,
                        contractor=contractor.username,
                        contractor_no=contractor.id,
                        truck_plate=booking.truck_reg_number,
                        warehouse=contract.warehouse,
                        date=contract.date_of_delivery,
                        time=booking.booking_time,
                        pallets_pos=contract.pallets_position,
                        pallets=contract.pallets_actual)
    pdf.seek(0)
    return send_file(pdf, as_attachment=True, mimetype='application/pdf',
        attachment_filename='booking.pdf', cache_timeout=0)
przykładowy plik wygenerowany po stronie serwera, pobrany przez zleceniobiorcę.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *