[Django3.0]商品登録時にレコードidを付番したqrコードを生成したい

### template側
widget_tweaksでforms.pyからformを作っていきます。

### views.py
mysqlに生成した直後に、生成したばかりのレコードidを*.objects.order_by(“id”).last()で取得して、qrコードのurlに付番する。

def stock_complete(request):
	if(request.method == 'POST'):
		data = StocksForm(request.POST)
		if data.is_valid():
			// データ作成
			category_id = request.POST['category']
			name = request.POST['name']
			unit = request.POST['unit']
			price = request.POST['price']
			total = request.POST['total']
			remark = request.POST['remark']
			stock = Stocks(category_id=category_id, name=name, unit=unit, price=price, total=total, remark=remark)
			stock.save()

			// qrコード作成
			new_data = Stocks.objects.order_by("id").last()
			qr = qrcode.QRCode()
			qr.add_data('http://192.168.33.10:8000/qrcode/' + str(new_data.id))
			qr.make()
			img = qr.make_image()
			img.save('./sales/media/img/qrcode/'+ str(new_data.id) +'.png')

			// 完了画面に遷移
			return render(request, 'sales/stock_complete.html')
		else:
			params = {
				'form': StocksForm(request.POST),
			}
			return render(request, 'sales/stock_input.html', params)
	return redirect('/stock/input')

### mysql側

mysql> select * from sales_stocks;
+—-+——————————+——+——-+——–+——————-+—————————-+—————————-+————-+
| id | name | unit | price | total | remark | created_at | updated_at | category_id |
+—-+——————————+——+——-+——–+——————-+—————————-+—————————-+————-+
| 4 | Foot Massage Machine Shiatsu | 5 | 50880 | 254400 | US MakertPlace社 | 2020-10-09 18:44:48.012892 | 2020-10-09 18:44:48.012925 | 5 |
+—-+——————————+——+——-+——–+——————-+—————————-+—————————-+————-+
1 row in set (0.00 sec)

### qrコード
id4でqrコードが生成されています。

urlもきちんと反映されています。

商品登録時にレコードidを取得するアイディアが思いつかず、商品詳細画面表示に初めてqrコードを生成するべきかずっと悩んでいましたが、order_by(“id”).last()を思いついたら割と簡単にできた。

続けて登録完了画面にqrコードを表示させて、ダウンロードできるように少し改良しました。

[Django3.0]メディアファイルの取り扱い

プロジェクトルートにmediaフォルダを作成します。

settings.pyでmediaルートを設定します。os.path.joinの第二引数はアプリケーションのフォルダ、第三引数はフォルダ名(media)です。

settings.py

MEDIA_ROOT = os.path.join(BASE_DIR, 'sales', 'media')
MEDIA_URL = '/media/'

STATIC_ROOT = os.path.join(BASE_DIR, 'sales', 'static')
STATIC_URL = '/static/'

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),
]
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
	// 省略
]


urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

mediaルートに画像を置きます。

templateで呼び出してみましょう。

<img src="/media/img/qrcode.png" width=100 height=100>

うまく表示されています。

さて、次はどこでqrコードを生成するかです。
商品生成時にidで作りたいが、idはmysql側で付与されるから、
*save() とした後に以下のようにすれば良いのかな。
*.objects.order_by(“id”).last()

Pythonでqrコードを作りたい

QRコード画像生成ライブラリ「qrcode」をインストールします。

$ pip install qrcode
$ pip install pillow

### テキストを画像にする

import qrcode

qr = qrcode.QRCode()
qr.add_data('test text')
qr.make()
img = qr.make_image()
img.save('qrcode.png')

### URLを画像にする

import qrcode

qr = qrcode.QRCode()
qr.add_data('https://www.google.com/')
qr.make()
img = qr.make_image()
img.save('qrcode.png')

アラ? 凄い簡単に出来ますね。。。簡単過ぎてビックリした。

[Django3.0]親モデルからForeignKeyの子モデルを逆参照して各アイテムの関連レコード数をforループで表示したい

現在Djangoで、見積モデル(Estimates)と受注モデル(Orders)が、それぞれ顧客モデル(Clients)をForeignKeyで参照しています。
顧客一覧ページで、各顧客ごとの見積数および受注数を表示する為、顧客モデル(Clients)から見積モデル(Estimates)および受注モデル(Orders)を逆参照して、顧客ごとの見積件数、受注件数を表示したい
モデルの関係は以下の通りです。

models.py

class Clients(models.Model):
	name = models.CharField(max_length=255)
	name_kana = models.CharField(max_length=255, null=True, blank=True)
        // 省略

class Estimates(models.Model):
	client = models.ForeignKey(Clients, null=True, blank=True, on_delete=models.PROTECT)
	estimate_date = models.DateField()
        // 省略

class Orders(models.Model):
	client = models.ForeignKey(Clients, null=True, blank=True, on_delete=models.PROTECT)
	order_date = models.DateField()
        // 省略

顧客一覧ページのUIは以下の通りで、Clientsモデルからデータを取得しforループで表示させています。

views.py

data = Clients.objects.all()
page = Paginator(data, 3)
params = {
	'data' : page.get_page(num)
}
return render(request, 'sales/client.html', params)

template

{% for item in data %}
						<tr>
							<td>{{item.id}}</td>
							<td class="text-nowrap">{{item.name}}</td>
							<td class="text-nowrap">〒{{item.zipcode}} {{item.prefecture}}{{item.address|truncatechars:20}}</td>
							<td>5</td>
							<td>6</td>
							<td class="text-nowrap"><button class="btn btn-light" onclick="location.href='/client/detail/{{item.id}}'">詳細</button> <button class="btn btn-light" onclick="location.href='/client/edit/{{item.id}}'">編集</button> <a href="#modal"><button class="btn btn-light del" id="{{item.id}}" value="{{item.name}}">削除</button></a></td>
						</tr>
						{% endfor %}

やり方を色々調べたが、同じようなことをやってるトラブルシューティングがなく、大苦戦。。。。
だが、さらに調べていると、どうやら「1データを基準にするだけなら *.${modelName}_set という書き方で逆参照可能」らしい。
逆参照は、forループを回しているテンプレート側でやると上手くいかなかったので、views.py側で処理をする事にした。
具体的には、Clientsモデルから顧客データを取得し、forループで各顧客データごとの見積数、受注数を逆参照して、配列に追加し、テンプレート側では呼び出すだけにした。
client.estimates_set.count()、client.orders_set.count()で、レコード数を取得している。

		data = Clients.objects.all()
		i = 0
		for item in data:
			client = Clients.objects.get(id=item.id)
			data[i].estimate = client.estimates_set.count()
			data[i].order = client.orders_set.count()
			i += 1
		page = Paginator(data, 3)
		params = {
			'data' : page.get_page(num)
		}
	return render(request, 'sales/client.html', params)
{% for item in data %}
						<tr>
							<td>{{item.id}}</td>
							<td class="text-nowrap">{{item.name}}</td>
							<td class="text-nowrap">〒{{item.zipcode}} {{item.prefecture}}{{item.address|truncatechars:20}}</td>
							<td>{{item.estimate}}</td>
							<td>{{item.order}}</td>
							<td class="text-nowrap"><button class="btn btn-light" onclick="location.href='/client/detail/{{item.id}}'">詳細</button> <button class="btn btn-light" onclick="location.href='/client/edit/{{item.id}}'">編集</button> <a href="#modal"><button class="btn btn-light del" id="{{item.id}}" value="{{item.name}}">削除</button></a></td>
						</tr>
						{% endfor %}

おおおおおおおおお、上手くいきました。
疲れたわ。。😇😇😇

[Django3.0]widget_tweaksでradioボタンの使い方が解らない時

widget_tweaks Django公式サイトを見たが、textformやtextarea、select formなどはわかるが、radioボタンの使い方がイマイチよくわからない。
django-widget-tweaks 1.4.8

やりたい事としては、銀行口座編集画面で、A.銀行口座(value=1)or郵便貯金口座(value=2)、B.普通預金(value=1)or当座預金(value=2)の登録情報を編集できるようにしたい。
なお、フロント側では、Javascriptで銀行口座の場合は銀行口座入力のみ、郵便貯金口座の場合は郵便貯金口座入力のフォームのみ入力できるようにformのdisableで制御している。

結局色々調べたが不明のため、views.py側で口座タイプ(bank_type)の値を取得し、ビュー側でif endifで制御する事にした。
{% if bank_type == 1 %}checked{% endif %}

views.py

def bank_edit(request):
	data = Bank.objects.get(id=1)
	params = {
		'form': BankForm(instance=data),
		'bank_type': data.bank_type,
		'account_type': data.account_type,
	}
	return render(request, 'sales/bank_edit.html', params)

template

<div class="row col-md-12">
	          			<div class="form-check col-md-2">
						  <input class="form-check-input" type="radio" name="bank_type" id="bank" value="1" {% if bank_type == 1 %}checked{% endif %} onClick="flg0(this.checked);">
						  <label class="form-check-label" for="bank">銀行口座</label>
						</div>
						<div class="form-check col-md-2">
						  <input class="form-check-input" type="radio" name="bank_type" id="jpbank" value="2"  {% if bank_type == 2 %}checked{% endif %} onClick="flg1(this.checked);">
						  <label class="form-check-label" for="jpbank">郵便貯金口座</label>
						</div>
					</div>

formの初期値でdisabled=”disabled”を付けるかどうかも同様にif endifで制御した。

{% if bank_type == 1 %}
								{% render_field form.bank_name class="form-control" id="bank_name" placeholder="銀行名を入力してください" %}
							{% else %}
								{% render_field form.bank_name class="form-control" id="bank_name" placeholder="銀行名を入力してください" disabled="disabled" %}
							{% endif %}

想定通りの動きになっているが、何か気持ち悪いので、widget_tweaksでradioの使い方があれば教えて欲しい。

### PDFで表示
上記を請求書に実装する

if bank.bank_type == 1:
			bank_name = bank.bank_name + ' ' + bank.bank_branch
			if bank.account_type == 1:
				account = '普通預金 ' + bank.account_number
			else:
				account = '当座預金 ' + bank.account_number
			holder = '法人口座名義 ' + bank.account_holder
		elif bank.bank_type == 2:
			bank_name = '郵貯銀行'
			account = '記号 ' + bank.jp_number + '  番号' + bank.jp_holder
			holder = '法人口座名義 ' + bank.account_holder
		else:
			bank_name = '別途ご連絡'
			account = ''
			holder = ''

		delivery_date = data.delivery_date.strftime("%Y年%m月%d日") + '納品予定' if data.delivery_date != None else ''

		# 振込先
		pdf_canvas.drawString(60, 155, 'お手数でございますが、お支払いは下記銀行口座へ振込くださいますようお願い申し上げます。')
		pdf_canvas.drawString(60, 135, '振込先: ' + bank_name)
		pdf_canvas.drawString(60, 125, account)
		pdf_canvas.drawString(60, 115, holder)
		pdf_canvas.drawString(60, 105, '恐れ入りますが、振込手数料は貴社にてご負担ください。')
		pdf_canvas.drawString(60, 85, delivery_date)

Let’s Gooooooo

ラジオボタンで分岐する銀行口座or郵貯口座登録のUIを実装しよう

販売管理システムで、請求書に振込先を表示させる為、口座登録の画面を作ります。

amazon seller accountの口座登録画面
-> 銀行口座のみの登録

どちらかというと、郵便貯金口座も表示できるようにしたい。
チェックボックスで銀行口座、郵便貯金口座を振り分けられるようなUIになるようWireframeを書きます。

### フロント実装
– チェックボックスで銀行口座を選択した場合、郵貯口座の入力ができないようにする
– 同様にチェックボックスで郵貯口座を選択した場合、銀行口座の入力ができないようにする
– 入力途中で口座区分を変更した場合は、document.getElementById(“*”).value = ”として、valueを空に変更する

<form>
          			<br>
					<div class="row col-md-12">
	          			<div class="form-check col-md-2">
						  <input class="form-check-input" type="radio" name="bank_type" id="bank" checked onClick="flg0(this.checked);">
						  <label class="form-check-label" for="bank">銀行口座</label>
						</div>
						<div class="form-check col-md-2">
						  <input class="form-check-input" type="radio" name="bank_type" id="jpbank" onClick="flg1(this.checked);">
						  <label class="form-check-label" for="jpbank">郵便貯金口座</label>
						</div>
					</div>

          			<br><br>

					<div class="form-group mb-0">
						<label for="title">金融機関</label>
						<input type="text" class="form-control col-md-4" name="bank_name" id="bank_name" placeholder="銀行名を入力してください">
					</div>
					<br>
					<div class="row col-md-12">
	          			<div class="form-check col-md-2">
						  <input class="form-check-input" type="radio" name="account_type" id="account_type" checked>
						  <label class="form-check-label" for="account_type">普通預金</label>
						</div>
						<div class="form-check col-md-2">
						  <input class="form-check-input" type="radio" name="account_type" id="account_type">
						  <label class="form-check-label" for="account_type">当座預金</label>
						</div>
					</div>
					<br>
					<div class="row">
						<div class="col-md-3">
							<label for="account_number">口座番号</label>
							<input type="text" id="account_number" class="form-control" placeholder="口座番号を入力してください">
						</div>
						<div class="col-md-3">
							<label for="account_holder">口座名義人(カナ)</label>
							<input type="text" id="account_holder" class="form-control" placeholder="口座名義人(カナ)を入力してください">
						</div>
					</div>
					<br><br>

					<div class="row">
						<div class="col-md-3">
							<label for="jp_name">記号(郵貯)</label>
							<input type="text" id="jp_name" class="form-control" name="jp_name" placeholder="見積日を入力してください" disabled="disabled">
						</div>
						<div class="col-md-3">
							<label for="jp_number">番号(郵貯)</label>
							<input type="text" class="form-control" id="jp_number" name="jp_number" placeholder="部署名を入力してください"  disabled="disabled">
						</div>
						<div class="col-md-3">
							<label for="jp_holder">口座名義カナ(郵貯)</label>
							<input type="text" class="form-control" id="jp_holder" name="jp_holder" placeholder="見積担当者の名前を入力してください"  disabled="disabled">
						</div>
					</div>
					<br><br><br>

					<div class="col text-center">
						<button class="btn" type="submit">更新</button>
					</div>
					
				</form>

<script>
		function flg0(ischecked){
			if(ischecked == true){
				document.getElementById("jp_name").value = '';
				document.getElementById("jp_number").value = '';
				document.getElementById("jp_holder").value = '';

				document.getElementById("jp_name").disabled = true;
				document.getElementById("jp_number").disabled = true;
				document.getElementById("jp_holder").disabled = true;

				document.getElementById("bank_name").disabled = false;
				document.getElementById("account_number").disabled = false;
				document.getElementById("account_holder").disabled = false;
			} else {
			}
		}
		function flg1(ischecked){
			if(ischecked == true){
				document.getElementById("jp_name").disabled = false;
				document.getElementById("jp_number").disabled = false;
				document.getElementById("jp_holder").disabled = false;

				document.getElementById("bank_name").value = '';
				document.getElementById("account_number").value = '';
				document.getElementById("account_holder").value = '';

				document.getElementById("bank_name").disabled = true;
				document.getElementById("account_number").disabled = true;
				document.getElementById("account_holder").disabled = true;
			} else {
			}
		}
	</script>

OK、 これをDjangoに実装します。

[Django3.0] PDFの見積書ダウンロード機能を作る

まずviewから

### template
対象の見積書のidを渡します。

<tbody>
						{% for item in data %}
						<tr>
							// 省略
							<td class="text-nowrap"><button class="btn btn-danger" onclick="location.href='/pdf/{{item.id}}'">PDF</button></td>
							// 省略
						</tr>
						{% endfor %}
					</tbody>

### urls.py
views.pyにint:idを渡します。

urlpatterns = [
	// 省略
	path('pdf/<int:id>', views.pdf, name="pdf"),
]

### views.py
reportlabを読み込んで、対象IDの見積データおよび自社情報をmodelから引っ張ってきて見積書pdfをmake()し、作成したpdfをreturnする
modelから引っ張ってくるところ以外はdjangoを使わずにローカルでテストした書き方と同じ
3桁でカンマをつけるには”{:,d}”.format()と書く

from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
from reportlab.lib.pagesizes import A4, portrait
from reportlab.platypus import Table, TableStyle
from reportlab.lib.units import mm
from reportlab.lib import colors

def pdf(request, id):
	make(id)
	response = HttpResponse(open('./sales/static/sales/estimate.pdf','rb').read(), content_type='application/pdf')
	response["Content-Disposition"] = "filename=app.pdf"
	return response


def make(id): # ファイル名
    pdf_canvas = set_info(filename="estimate") # キャンバス名
    print_string(pdf_canvas, id)
    pdf_canvas.save() # 保存

def set_info(filename):
	pdf_canvas = canvas.Canvas("./sales/static/sales/{0}.pdf".format(filename))
	pdf_canvas.setAuthor("hpscript")
	pdf_canvas.setTitle("見積書")
	pdf_canvas.setSubject("見積書")
	return pdf_canvas

def print_string(pdf_canvas, id):
	try:
		data = Estimates.objects.get(id=id)
		master = Master.objects.get(id=1)
        // 省略
        except Estimates.DoesNotExist:
		return redirect('/estimate/1')

見積書のレイアウトを作るところが少し時間がかかるが、結構達成感あるね
うむ、OK 続いて受注管理を作っていこう

Python & reportlab でPDF見積書を作成

pythonでPDFの見積書を作っていきます。
reportlabでテーブルではなくテキスト配置で右寄せがわからないのが脛に傷。

# -*- coding: utf-8 -*-

from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
from reportlab.lib.pagesizes import A4, portrait
from reportlab.platypus import Table, TableStyle
from reportlab.lib.units import mm
from reportlab.lib import colors


def make(filename="estimate"): # ファイル名
    pdf_canvas = set_info(filename) # キャンバス名
    print_string(pdf_canvas)
    pdf_canvas.save() # 保存

def set_info(filename):
	pdf_canvas = canvas.Canvas("./{0}.pdf".format(filename))
	pdf_canvas.setAuthor("hpscript")
	pdf_canvas.setTitle("見積書")
	pdf_canvas.setSubject("見積書")
	return pdf_canvas

def print_string(pdf_canvas):
	# フォント登録
	pdfmetrics.registerFont(UnicodeCIDFont('HeiseiKakuGo-W5'))

	width, height = A4

	# 見積日
	font_size = 9
	pdf_canvas.setFont('HeiseiKakuGo-W5', font_size)
	pdf_canvas.drawString(440, 810, '見積日: 2020年10月1日')

	# title
	font_size = 24
	pdf_canvas.setFont('HeiseiKakuGo-W5', font_size)
	pdf_canvas.drawString(245, 770, '御 見 積 書')

	# 線
	pdf_canvas.line(50, 750, 550, 750)

	# 宛先
	font_size = 14
	pdf_canvas.setFont('HeiseiKakuGo-W5', font_size)
	pdf_canvas.drawString(60, 710, '六本木ソフトウェア株式会社 御中')
	pdf_canvas.drawString(60, 690, '営業部  山田太郎 様')

	# 線
	pdf_canvas.line(50, 680, 350, 680)

	# 注釈
	font_size = 9
	pdf_canvas.setFont('HeiseiKakuGo-W5', font_size)
	pdf_canvas.drawString(190, 670, '下記の通りお見積もり申し上げます。')

	# 納期、支払条件、有効期限
	font_size = 12
	pdf_canvas.setFont('HeiseiKakuGo-W5', font_size)
	pdf_canvas.drawString(100, 635, '納期:')
	pdf_canvas.drawString(200, 635, '別途ご相談')
	pdf_canvas.line(200, 633, 350, 633)

	pdf_canvas.drawString(100, 615, '支払い条件:')
	pdf_canvas.drawString(200, 615, '月末締め翌月末払い')
	pdf_canvas.line(200, 612, 350, 612)

	pdf_canvas.drawString(100, 595, '有効期限:')
	pdf_canvas.drawString(200, 595, 'お見積り後2週間')
	pdf_canvas.line(200, 593, 350, 593)

	# 自社情報
	font_size = 9
	pdf_canvas.setFont('HeiseiKakuGo-W5', font_size)
	pdf_canvas.drawString(360, 680, '丸の内ソフトウェア株式会社')
	pdf_canvas.drawString(360, 670, '〒100-0001')
	pdf_canvas.drawString(360, 660, '東京都千代田区千代田1-1-1')
	pdf_canvas.drawString(360, 645, 'TEL: 03-1234-5678')
	pdf_canvas.drawString(360, 635, 'E-mail: info@marunouchi-soft.com')
	pdf_canvas.drawString(360, 625, '担当: 田中一郎')

	# 合計金額
	font_size = 14
	pdf_canvas.setFont('HeiseiKakuGo-W5', font_size)
	pdf_canvas.drawString(80, 550, '合計金額')
	pdf_canvas.drawString(180, 550, '800,000 円 (税込)')

	# 線
	pdf_canvas.line(50, 540, 350, 538)

	# 分類、型番、品名、規格寸法、基準単価
	data = [
		['分類', '型番','品名', '規格寸法','数量','基準単価'],
		[' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' '],
	]
	table = Table(data, colWidths=(25*mm, 25*mm, 55*mm, 25*mm, 15*mm,30*mm), rowHeights=7.5*mm)
	table.setStyle(TableStyle([
			('FONT', (0, 0), (-1, -1), 'HeiseiKakuGo-W5', 8),
			('BOX', (0, 0), (-1, -1), 1, colors.black),
			('INNERGRID', (0, 0), (-1, -1), 1, colors.black),
			('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
		]))
	# table.wrapOn(pdf_canvas, 20*mm, 20*mm)
	table.wrapOn(pdf_canvas, 20*mm, 20*mm)
	table.drawOn(pdf_canvas, 18*mm, 100*mm)

	# 小計、消費税、合計
	font_size = 9
	pdf_canvas.setFont('HeiseiKakuGo-W5', font_size)
	pdf_canvas.drawString(360, 250, '小計:')
	pdf_canvas.drawString(450, 250, '700000円')
	pdf_canvas.line(360, 245, 550, 245)	

	pdf_canvas.drawString(360, 230, '消費税:')
	pdf_canvas.drawString(450, 230, '70000円')
	pdf_canvas.line(360, 225, 550, 225)	

	pdf_canvas.drawString(360, 210, '合計:')
	pdf_canvas.drawString(450, 210, '770000円')
	pdf_canvas.line(360, 205, 550, 207)	

	# 宛先
	font_size = 9
	pdf_canvas.setFont('HeiseiKakuGo-W5', font_size)
	pdf_canvas.drawString(60, 175, '備考')

	pdf_canvas.rect(50, 50, 500, 120)



	pdf_canvas.showPage()
	
if __name__ == '__main__':
	make()

$ python estimate.py

OK、これをDjangoに組み込みたい。この関数はviews.pyに書くけば良いのか???

Python&reportlabでPDFを生成2

# -*- coding: utf-8 -*-

from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
import reportlab.lib.colors as color

def make(filename="test"):
	pdf_canvas = set_info(filename)
	print_string(pdf_canvas)
	# print_figure(pdf_canvas)
	# print_line(pdf_canvas)
	pdf_canvas.save()

# 初期設定
def set_info(filename):
	pdf_canvas = canvas.Canvas("./{0}.pdf".format(filename), bottomup=False) # 原点は左上

	pdf_canvas.setAuthor("hpscript")
	pdf_canvas.setTitle("make pdf using reportlab")
	pdf_canvas.setSubject("reportlab")

	return pdf_canvas


# 文字
def print_string(pdf_canvas):
	pdfmetrics.registerFont(UnicodeCIDFont("HeiseiKakuGo-W5"))
	pdfmetrics.registerFont(UnicodeCIDFont("HeiseiMin-W3"))

	pdf_canvas.setFont("HeiseiKakuGo-W5", 15)
	pdf_canvas.drawString(50, 50, "日経平均レバレッジ上場投信")

	pdf_canvas.setFont("HeiseiMin-W3", 30)
	pdf_canvas.drawString(300, 100, "日経ダブルインバース上場投信")

if __name__ == '__main__':
	make()

$ python test.py

いけますね、なんとなく掴みました。
続いて図形

def print_line(pdf_canvas):
	pdf_canvas.rect(50, 150, 200, 250)

	pdf_canvas.setFillColor(color.blue)
	pdf_canvas.circle(400, 350, 50, stroke=False, fill=True)

# 線
def print_line(pdf_canvas):
	# 普通の線
	pdf_canvas.line(50, 450, 500, 450)

	# 赤い太い線
	pdf_canvas.setStrokeColor(color.red)
	pdf_canvas.setLineWidth(10)
	pdf_canvas.line(100, 500, 550, 500)

	# 破線
	pdf_canvas.setStrokeColor(color.black)
	pdf_canvas.setLineWidth(5)
	pdf_canvas.setDash([2, 8, 5, 10])
	pdf_canvas.line(150, 550, 600, 550)

	# 複数の線
	pdf_canvas.setLineWidth(1)
	pdf_canvas.setDash([])
	lines = [(100, 650, 200, 750),(200, 750, 300, 650),(300, 650, 300, 750),(100, 700, 400, 700)]
	pdf_canvas.lines(lines)

OK^^

PythonでPDFの見積書を描画したい1

販売管理システムの見積一覧ページで、詳細ボタンを押下するとPDFの見積書を表示し、ダウンロードボタンを押下するとPDFの見積書をダウンロードできるようにしたい。

最初なのでデザインはなるべくシンプルに
▼見積書イメージ

## reportlab
PDF作成ライブラリのreportlabを使用します。

– reportlabのインストール
$ pip install reportlab

### 初期設定
app.py

def make(filename="estimate"):
	pdf_canvas = set_info(filename)
	print_string(pdf_canvas)
	pdf_canvas.save() # pdfを保存

def set_info(filename):
	pdf_canvas = canvas.Canvas("./{0}.pdf".format(filename))
	pdf_canvas.setAuthor("hpscript")
	pdf_canvas.setTitle("見積書")
	pdf_canvas.setSubject("見積書")
	return pdf_canvas

### 日本語設定

def print_string(pdf_canvas)
	# フォント登録
	pdfmetrics.registerFont(UnicodeCIDFont('HeiseiKakuGo-W5'))

	width, height = A4

	font_size = 24

	pdf_canvas.setFont('HeiseiKakuGo-W5', font_size)

	pdf_canvas.drawString(60, 770, '見積書')

### 表の描画
データ入力
-> 二次元配列で書く
e.g. 三行四列の場合

	data = [
		['(0,0)', '(1,0)', '(2,0)'],
		['(0,1)', '(1,1)', '(2,1)'],
		['(0,2)', '(1,2)', '(2,2)'],
		['(0,3)', '(1,3)', '(2,3)'],
	]

tableの大きさ指定
->全列30mm, 全行40mmの場合

table = Table(data, colWidths=30*mm, rowHeights=40*mm)

->1列目10mm、2列目20mm、3列目30mm、1〜2行目30mm、3〜4行目50mmの場合

table = Table(data, colWidths=(10*mm, 20*mm, 30*mm), rowHeights=(30*mm, 30*mm, 50*mm, 50*mm))

tableの装飾

	table.setStyle(TableStyle([
		...
	]))

表にフォントを設定する

('FONT', 始点, 終点, fontname, size)
('FONT', (0, 0), (2, 3), self.font_name, 10)
('FONT', (0, 0), (-1, -1), self.font_name, 10)

表を罫線で囲む

	('BOX', 始点, 終点, 太さ, color)
	('BOX', (0, 0), (2, 3), 1, colors.black)
	('BOX', (0, 0), (-1, -1), 1, colors.black)

四角の内側に罫線を書く

	('INNERGRID', 始点, 終点, 太さ, color)
	('INNERGRID', (0, 0), (2, 3), 1, colors.black)
	('INNERGRID', (0, 0), (-1, -1), 1, colors.black)

フォントの場所を指定

	('VALIGN', 始点, 終点, TOP or MIDDLE or BOTTOM)
	('VALIGN', (0, 0), (0, 3), TOP)
	('VALIGN', (1, 0), (1, 3), MIDDLE)
	('VALIGN', (2, 0), (2, 3), BOTTOM)

セルの結合

	('SPAN', 始点, 終点)
	('SPAN', (0,0), (0, 1))
	('SPAN', (2, 2) (2, 3))


指定した場所に線を引く
- 横線

	('LINEBEFORE', 始点, 終点, 太さ, color)
	('LINEBEFORE', (2, 1), (2, 2), 1, colors.black)

– 縦線

	('LINEABOVE', 始点, 終点, 太さ, color)
	('LINEABOVE', (0, 0), (0, 0), 1, colors.black)

テーブルを書き出す位置を指定

	table.wrapOn(pdf_canvas, 145*mm, 235*mm)
	table.wrapOn(pdf_canvas, 145*mm, 235*mm)

なんとなく基礎を理解したので、次は実際に書いていきたいと思います。