Django
前回、足し算テストアプリで答え合わせのページ(answercheck.html)のレイアウトを整理しました。
これでアプリケーションとしては大体出来上がったのですが、こういう計算テストは紙に印刷して使うことが多いだろうと予想されます。
そこで今回からPDFとして出力し、ダウンロードできるようにプログラムを変更していきますが、今回はとりあえずDjangoでPDF出力する方法を学んでいきます。
今回修正するファイルはこの3つ。
- /webapp/tashizan1/urls.py
- /webapp/tashizan1/views.py
- /webapp/tashizan1/templates/tashizan1/index.html
webapp
├── ...
└── tashizan1
├── __init__.py
├── admin.py
├── apps.py
├── forms.py
├── migrations
│ └── __init__.py
├── models.py
├── templates
│ └── tashizan1
│ ├── answercheck.html
│ └── index.html <- これ
├── tests.py
├── urls.py <- これ
└── views.py <- これ
Reportlabのインストール
今回、PDFファイルを作成するのに、新しいライブラリ「Reportlab」を使用します。
PythonでPDFファイルを作成するためのライブラリはいくつかあるのですが、多くのものは出来上がっているHTMLファイルをPDFとして出力するというものでした。
もちろんこの足し算テストアプリはHTMLファイルを作成し、ブラウザ上で表示しています。
つまりそのままPDF出力のライブラリに渡してやれば、PDF出力は簡単にできそうでした。
しかし今後、他のアプリやツールでPDF出力をする可能性を考えると、自由度の高いライブラリを勉強しておく方がいいのかなと思って選んだのが、このReportlabです。
Reportlabがなぜ自由度が高いかというと、先ほど述べたように(調べた限りでは)他のライブラリはあくまでもHTMLファイルをPDF化するもの。
しかしReportlabはPythonでプログラミングすることで、レイアウトを指定することができるのです。
そして結構メジャーに使われているおり、ネット上に情報も多いということから、今回はReportlabを使ってみることにしました。
ということでライブラリのインストールを行いましょう。
pip install reportlab
とりあえずこれでReportlabのインストールは完了です。
/webapp/tashizan1/urls.py
Django内でReportlabを使い、PDFを作成する場合、views.py内でPDFのレイアウトを指定し、PDFを作成します。
そしてviews.py内の「return」で返すページにアクセスすることで、表示する、もしくはダウンロードをするという仕組みになっています。
つまり今回はHTMLファイルは作らないのですが、ReportlabがPDFファイルを出力し、ユーザーがアクセスするためのURLが必要となり、そのURLをDjangoに教える、つまりurls.pyに記述する必要があります。
今回は足し算テストアプリの中にPDF出力機能をつけるため、ページの名前を「pdf」としておきましょう。
ということでurls.pyはこんな感じです。
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
path('answercheck', views.answercheck, name='answercheck'),
path('pdf', views.pdf, name='pdf')
]
これで「pdf」というページ(正確には/tashizan1/pdf)があり、その内容は「views.pyのpdf関数」に定義されていることがDjangoに定義できました。
/webapp/tashizan1/views.py
次にviews.pyにPDFのレイアウトを記述していきます。
まずはライブラリのインポートから。
今まで使っていたライブラリに加え、下の三つが今回追加したReportlab関連のライブラリです。
from django.shortcuts import render
from django.http import HttpResponse
from .forms import Tashizan1Form
import sys
sys.path.append('../')
from webapp import definitions
import random
from reportlab.lib.pagesizes import A4, portrait
from reportlab.lib.units import mm
from reportlab.pdfgen import canvas
Reportlab関連のインポートのうち、一番上はA4サイズで縦向きのページを指定する際に使用するモジュールです。
from reportlab.lib.pagesizes import A4, portrait
次にインポートしているのは、単位をmmで指定するためのモジュール。
from reportlab.lib.units import mm
最後はPDFファイルを作成する際、レイアウトをしたり、PDFを書き出したりするためのモジュールです。
from reportlab.pdfgen import canvas
次に新しい関数「pdf」を作成します。
作成する場所はこれまで作成した関数(index、answercheck)の下で大丈夫です。
def index(request):
params = definitions.readjson()
(中略)
return render(request, 'tashizan1/index.html', params)
def answercheck(request):
params = definitions.readjson()
(中略)
return render(request, 'tashizan1/answercheck.html', params)
def pdf(request):
今回はPDFファイルを作成する方法を学ぶということで、「test」と書かれたPDFファイルを出力してみます。
ということで作成してみたpdf関数はこんな感じ。
def pdf(request):
response = HttpResponse(content_type='application/pdf')
pdf_name = 'tashizan1.pdf'
response['Content-Disposition'] = 'attachment; filename=' + pdf_name
pdf_size = portrait(A4)
pdf_file = canvas.Canvas(response, pagesize=pdf_size, bottomup=False)
pdf_file.drawString(20*mm, 10*mm, 'test')
pdf_file.save()
return response
まず最初の3行はこの「pdf関数」が呼び出された時の動作を規定しています。
response = HttpResponse(content_type='application/pdf')
pdf_name = 'tashizan1.pdf'
response['Content-Disposition'] = 'attachment; filename=' + pdf_name
最初の「response = HttpResponse(content_type=’application/pdf’)」はサーバーからのレスポンスとして「PDF」ファイルを返すよという意味です。
ちなみに最後の行の「return response」が実際にサーバーからレスポンスを返している部分です。
次の「pdf_name = ‘tashizan1.pdf’」はPDFファイルの名前です。
最後の「response[‘Content-Disposition’] = ‘attachment; filename=’ + pdf_name」でPDFファイルをブラウザ上に表示するか、それともアクセスされたらダウンロードさせるかを定義しています。
直接ダウンロードさせる場合は「’attachment; filename=’」、ブラウザ上に表示する場合は「’filename=’」とします。
次からの部分がPDFのレイアウトを決め、出力している部分です。
pdf_size = portrait(A4)
pdf_file = canvas.Canvas(response, pagesize=pdf_size, bottomup=False)
pdf_file.drawString(20*mm, 10*mm, 'test')
pdf_file.save()
最初の「pdf_size = portrait(A4)」はPDFの用紙のサイズと向きを定義し、変数pdf_sizeに格納しています。
「portrait」は縦向きで、横向きにしたい場合は「landscape」を使います。
その場合はインポートするモジュールを変更するのをお忘れなく。
そして次の「pdf_file = canvas.Canvas(response, pagesize=pdf_size, bottomup=False)」ですが、最初の引数はファイル名、pagesizeが用紙のサイズと向きです。
最後の「bottomup」はどこを原点(つまり0, 0)とするかの定義です。
デフォルトは「bottomup=Ture」で用紙の左下を原点(0, 0)、「bottomup=False」とすると、左上を原点(0, 0)とすることができます。
次の「pdf_file.drawString(20*mm, 10*mm, ‘test’)」が書き出す内容の一つです。
先ほどのcanvan.Canva(ファイル名)を格納したpdf_fileに「.drawString(横の位置, 縦の位置, ‘書き出す内容’)」とすることで書き出す内容を位置とともに指定します。
先ほどcanvas.Canvasで「bottomup=False」としていることから、横の位置、縦の位置はそれぞれ左上からになります。
またインポート部分で単位の「mm(ミリメートル)」をインポートしていますので、「20*mm」と数字とmmの間に*(アスタリスク)を入れて記述することで、mm単位での指定が可能になります。
もし書き出す内容が複数ある場合は、この「.drawString()」を何度も使い、追加していきます。
そして最後の「pdf_file.save()」はPDFファイルの保存(作成)です。
/webapp/tashizan1/templates/tashizan1/index.html
次にHTML上にPDFを作成するボタンを作っていきます。
ということでこんな感じです。
{% extends "basetemplates/base.html" %}
{% block title %}
<title>{{ title }}|足し算テスト1</title>
{% endblock %}
{% block content %}
<h1>足し算テスト1</h1>
<form action="{% url 'answercheck' %}" method="post">
{% csrf_token %}
<div class="row">
<div class="col">
<table class="mx-auto">
<tr><td>\(1.\)</td><td>\({{ qa.0 }} + {{ qb.0 }} = \){{ forms.answer0 }}</td></tr>
<tr><td>\(2.\)</td><td>\({{ qa.1 }} + {{ qb.1 }} = \){{ forms.answer1 }}</td></tr>
<tr><td>\(3.\)</td><td>\({{ qa.2 }} + {{ qb.2 }} = \){{ forms.answer2 }}</td></tr>
<tr><td>\(4.\)</td><td>\({{ qa.3 }} + {{ qb.3 }} = \){{ forms.answer3 }}</td></tr>
<tr><td>\(5.\)</td><td>\({{ qa.4 }} + {{ qb.4 }} = \){{ forms.answer4 }}</td></tr>
<tr><td>\(6.\)</td><td>\({{ qa.5 }} + {{ qb.5 }} = \){{ forms.answer5 }}</td></tr>
<tr><td>\(7.\)</td><td>\({{ qa.6 }} + {{ qb.6 }} = \){{ forms.answer6 }}</td></tr>
<tr><td>\(8.\)</td><td>\({{ qa.7 }} + {{ qb.7 }} = \){{ forms.answer7 }}</td></tr>
<tr><td>\(9.\)</td><td>\({{ qa.8 }} + {{ qb.8 }} = \){{ forms.answer8 }}</td></tr>
<tr><td>\(10.\)</td><td>\({{ qa.9 }} + {{ qb.9 }} = \){{ forms.answer9 }}</td></tr>
</table>
{% for qa_val in qa %}
<input type='hidden' name='qa' value={{qa_val}}>
{% endfor %}
{% for qb_val in qb %}
<input type='hidden' name='qb' value={{qb_val}}>
{% endfor %}
</div>
</div>
<p style="margin:2rem 0rem 0rem 0rem;"></p>
<p style='text-align: center;'><input type='submit' value="答え合わせ"></p>
</form>
<form action="{% url 'pdf' %}" method="post">
{% csrf_token %}
<p style='text-align: center;'><input type='submit' value="PDF作成"></p>
</form>
{% endblock %}
追加したのは最後の4行。
<form action="{% url 'pdf' %}" method="post">
{% csrf_token %}
<p style='text-align: center;'><input type='submit' value="PDF作成"></p>
</form>
ここではフォームのボタンを使って、PDF作成ボタンを作っています。
(単なるボタンではなく、フォームを使ったのは理由がありますが、それに関しては次回ということで)
「<form action=”{% url ‘pdf’ %}” method=”post”>」でこのボタンをクリックした際に実行する関数(ここではviews.pyのpdf)を指定していますので、ボタンをクリックすると先ほどのPDFが作成されるというわけです。
実際にサーバーを起動して、アクセスしてみるとこんな感じです。
下の方に「PDF作成」というボタンが現れました。
これをクリックすると「tashizan1.pdf」がダウンロードされます。
ダウンロードした「tashizan1.pdf」はこんな感じです。
ちゃんと左上に「test」と書かれています。
このままでは作成されたPDFが縦向きか分からないので、縮小してみました。
確かに縦向きのPDFファイルが出力されています。
次回はこのPDFファイルに問題を出力していきましょう。
ではでは今回はこんな感じで。
コメント