DjangoでPDFを出力する

背景

業務用WebでPDFを出力する機能が必要になり、経験がなかったのでいろいろ調べてみました。

検索結果によく表示されているのがwkhtmltopdfというツールでした。django用のプラグインもあったので、一旦こちらで実装を進めていこうと思います。

wkhtmltopdfとは?

wkhtmltopdfはその名の通りhtmlをPDFとして出力するツールです。

公式サイトはこちら

今回はWebブラウザに表示されている内容をそのままPDFで出力できるようにしようと思っているので、要件にはあっています。また、Django用のプラグインもあり、容易にPDF出力できそうです。

wkhtmltopdfのインストール

まずコンテナにwkhtmltopdfをインストールします。apt installでインストールも可能ですが、パッケージが新規で200ほど、容量で500Mほど増えてしまうので、一旦wkhtmltopdfのバイナリパッケージのみインストールしてみます。

wkhtmltopdfのダウンロード

こちらからダウンロードできます。debianはbullseyeを使っていますが、bullseyeのパッケージがないので、一番近いbusterのパッケージをダウンロードします。

Dockerfileの修正

次にDockerfileを修正します。ダウンロードしたパッケージをコピーするディレクトリ(wkhtmltopdfディレクトリを作成しその下)に置き、apt installコマンドを追加します。

1
2
3
WORKDIR /code
ADD . /code/
RUN apt install ./wkhtmltopdf/wkhtmltox_0.12.6-1.buster_amd64.deb

イメージを再ビルドし、無事コンテナがビルドできることを確認します。

Djangoプラグインのインストール

次にDjangoプラグイン(django-wkhtmltopdf)を追加します。

1
$ poetry add django-wkhtmltopdf

pyproject.tomlとpoetry.lockが更新されます。

Djangoの設定

settings.pyにwkhtmltopdfの実行ファイルの場所などを指定します。

1
2
3
4
5
STATIC_ROOT = '/code/static/'
WKHTMLTOPDF_CMD = '/usr/local/bin/wkhtmltopdf'
WKHTMLTOPDF_CMD_OPTIONS = {
'quiet': True,
}

テスト用Viewの実装

最後に動作確認用のViewを仮で実装します。

1
2
3
4
5
6
7
8
9
10
from wkhtmltopdf.views import PDFTemplateView

class SamplePDFView(PDFTemplateView):
filename = "sample.pdf"
template_name = "sample.html"

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["sample_data"] = SampleData.objects.get(pk=kwargs["pk"])
return context

ViewはPDFTemplateViewを継承して作成します。

filenameでダウンロードするときのファイル名を指定します。

PDFを出力する場合、ほとんどが動的に設定したい箇所だと思います。動的に設定したい箇所については、contextでデータを渡すことで対応できます。

日本語対応

先程作成したViewにアクセスできるURLを設定し、アクセスしてみます。

すると無事PDFをダウンロードできました。早速ファイルの中身を確認すると、日本語の部分が□になってしまっています…

フォントのインストール

日本語のフォントが入っていないDockerイメージでは日本語のフォントをインストールしないと日本語表示はできません。

幸い日本語フォントのパッケージがあったので、それをインストールします。Dockerfileを再度書き換えます。

1
RUN apt install -y fonts-takao-gothic

インストール後コンテナを再起動して、無事日本語が表示されました。

まとめ

Djangoの出力するHTMLをPDFに変換することができました。

いろんな選択肢があるようですが、今回利用したwkhtmltopdfは比較的利用しやすいものだと思います。

ヘッダー、フッターなどカスタマイズする場合があればまた記載したいと思います。