DjangoでPDF出力する方法

DjangoでPDF出力する方法をご紹介します。

以下で作成したモジュールに、PDF出力機能を追加します。
DjangoのクラスベースVeiwで2つのフォームを使用する方法

条件

  • Django 2.1.7
  • Python 3.7.0
  • reportlab 3.5.13

実装

urls.py

PDF出力のURLを追加します。

from django.urls import path
from . import views

app_name = 'works'

urlpatterns = [
    # トップ画面(一覧画面)
    path('', views.ListView.as_view(), name='index'),

    # 詳細画面
    path('works/<int:pk>/', views.DetailView.as_view(), name='detail'),

    # 登録画面
    path('create/', views.CreateView.as_view(), name='create'),

    # PDF出力
    path('pdf/', views.BasicPdf.as_view(), name='pdf'),
]

views.py抜粋

以下は、views.pyからPDF出力処理の箇所を抜粋しています。

PDF出力処理の詳細については、参考に載せているURLをご参照ください。

from reportlab.pdfgen import canvas
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
from reportlab.pdfbase import pdfmetrics
from reportlab.lib.utils import ImageReader
from reportlab.lib.pagesizes import A4
from reportlab.lib.pagesizes import portrait
from reportlab.lib.units import mm
from reportlab.platypus import Table
from reportlab.platypus import TableStyle
from reportlab.lib import colors
from django.http import HttpResponse


class BasicPdf(generic.View):
    filename = 'photo_work.pdf'  # 出力ファイル名
    title = 'title: Art Works'
    font_name = 'HeiseiKakuGo-W5'  # フォント
    is_bottomup = True

    def get(self, request, *args, **kwargs):

        # PDF出力
        response = HttpResponse(status=200, content_type='application/pdf')
        # response['Content-Disposition'] = 'attachment; filename="{}"'.format(self.filename)  # ダウンロードする場合
        response['Content-Disposition'] = 'filename="{}"'.format(self.filename)  # 画面に表示する場合

        # A4縦書きのpdfを作る
        size = portrait(A4)

        # pdfを描く場所を作成:位置を決める原点は左上にする(bottomup)
        # デフォルトの原点は左下
        p = canvas.Canvas(response, pagesize=size, bottomup=self.is_bottomup)

        pdfmetrics.registerFont(UnicodeCIDFont(self.font_name))
        p.setFont(self.font_name, 16)  # フォントを設定

        # pdfのタイトルを設定
        p.setTitle(self.title)

        # 全ての作品情報を出力する。(検索結果は無関係)
        id_array = list(Work.objects.all().values_list('pk', flat=True))

        for work_count, work_id in enumerate(id_array):

            logger.debug(work_count)

            if Image.objects.filter(work_id=work_id).exists():  # 画像が紐づく場合
                # 作品に紐づく画像パスを取得
                image = Image.objects.values_list('image', flat=True).get(work_id=work_id)
            else:
                # No Imageパス
                image = settings.MEDIA_URL + NO_IMAGE

            # 作品情報
            workInfo = Work.objects.filter(pk=work_id).first()

            # 表の情報
            data = [
                ['タイトル', workInfo.title, 'メモ', workInfo.memo],
            ]

            table = Table(data, (15 * mm, 50 * mm, 12 * mm, 50 * mm), None, hAlign='CENTER')
            # TableStyleを使って、Tableの装飾をします。
            table.setStyle(TableStyle([
                # 表で使うフォントとそのサイズを設定
                ('FONT', (0, 0), (-1, -1), self.font_name, 9),
                # 四角に罫線を引いて、0.5の太さで、色は黒
                ('BOX', (0, 0), (-1, -1), 1, colors.black),
                # 四角の内側に格子状の罫線を引いて、0.25の太さで、色は黒
                ('INNERGRID', (0, 0), (-1, -1), 0.25, colors.black),
                # セルの縦文字位置
                ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
                ("ALIGN", (0, 0), (-1, -1), "CENTER"),
                ('TEXTCOLOR', (0, 0), (0, 0), colors.darkblue),
                ('TEXTCOLOR', (2, 0), (2, 0), colors.darkblue),
            ]))

            if work_count % 2 == 0:  # 偶数の場合

                # 画像の描画
                p.drawImage(ImageReader(image[1:]), 10, 530, width=580, height=280, mask='auto',
                            preserveAspectRatio=True)

                # tableを描き出す位置を指定
                table.wrapOn(p, 50 * mm, 50 * mm)
                table.drawOn(p, 43 * mm, 160 * mm)

            else:  # 奇数の場合

                # 画像の描画
                p.drawImage(ImageReader(image[1:]), 10, 130, width=580, height=280, mask='auto',
                            preserveAspectRatio=True)

                # tableを描き出す位置を指定
                table.wrapOn(p, 50 * mm, 50 * mm)
                table.drawOn(p, 43 * mm, 19 * mm)

                p.showPage()  # Canvasに書き込み(改ページ)

        if len(id_array) % 2 != 0:  # 出力作品数が奇数の場合
            p.showPage()  # Canvasに書き込み
        p.save()  # ファイル保存

        self._draw(p)

        return response

    def _draw(self, p):
        pass

template

index.html

一覧画面にPDF出力ボタンを追加します。

{% extends 'base.html' %}

{% block content %}
<div class="col-lg-6 offset-lg-3">

    <h1>Index</h1>

    {% if object_list|length > 0 %}
        <a class="btn btn-primary w-150px" href="{% url 'works:pdf' %}" role="button" target=”_blank”>PDF出力</a>
        <hr>
    {% endif %}

    <section class="post-list">
        {% if object_list|length == 0 %}
            <p>作品が存在しません。</p>
        {% else %}
            <div id="checkbox-container">
            <table class="table table-hover table-bordered">
                <tr>
                    <th>タイトル</th>
                    <th>メモ</th>
                    <th>画像</th>
                </tr>
                <tbody>
                    {% for work in object_list %}
                        <tr>
                            <td width="35%"><a href="{% url 'works:detail' work.pk %}">{{ work.title }}</a></td>
                            <td width="35%">{{ work.memo }}</td>
                            {% if work.image_set.count > 0 %}
                                <td width="30%">
                                    <img src="{{ work.image_set.first.image }}" alt={{ work.title }} width="300" height="200" border="0" />
                                </td>
                            {% else %}
                                <td width="30%">-</td>
                            {% endif %}
                        </tr>
                    {% endfor %}
                </tbody>
            </table>
            </div>
        {% endif %}
    </section>
</div>

{% endblock %}

実行結果

PDF出力ボタンを押します。

ブラウザの別タブにPDFが出力されます。

サンプルソース

GitHubに当該記事のサンプルソースを公開しています。

https://github.com/kzmrt/photo

参考

ReportLab公式

https://www.reportlab.com/documentation/

PythonのReportLabで、表(TableやTableStyle)について調べてみた

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です