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)

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

[Django3.0]ページ内検索のページネーションをGETメソッドで実装する

得意先一覧ページで、会社名検索のページネーションの実装について考えます。

formで method=”post”とすれば、views.pyで request.POST[‘*’]と書けば、postされた値を取得することができるのですが、取得データの数が多い場合、検索結果が1ページに収まらないケースが出てきます。

その際に、ページネーションのリンクを以下のように、data.previous_page_numberやdata.next_page_numberなどと書くと、次のページにはGETで遷移してしまうため、検索クエリが引き継がれません。

<li class="page-item">
						<a class="page-link" href="/client/1">&laquo; first</a>
					</li>
					<li class="page-item">
						<a class="page-link" href="/client/{{data.previous_page_number}}">&laquo; prev</a>
					</li>

その為、ページ内検索の場合は、methodをPOSTではなく、GETに変更します。

form

<form action="/client/1" method="get">
				<div class="form-group row">
					<div class="col-md-10">
						<input type="text" class="form-control" name="name" placeholder="検索する会社名を入力してください">
					</div>
					<div class="col-md-2">
						<button class="btn search-btn" type="submit">検索</button>
					</div>
				</div>
			</form>

views.pyでは、以下のようにrequest.GET.get(‘*’,None)で、getMethodがあるか判定します。
views.py

def client(request, num=1):
	if(request.GET.get('name',None)):
		name= request.GET['name']
		data = Clients.objects.filter(name__contains=name)
		page = Paginator(data, 3)
		params = {
			'data' : page.get_page(num),
			'query' : "会社名に「" + name + "」を含む検索結果"
		}
	else:
		data = Clients.objects.all()
		page = Paginator(data, 3)
		params = {
			'data' : page.get_page(num)
		}
	return render(request, 'sales/client.html', params)

ページネーション
リンク先に、{% if request.GET.name %}?name={{ request.GET.name }}{% endif %}を追加してあげます。

<ul class="pagination justify-content-end">
					{% if data.has_previous %}
					<li class="page-item">
						<a class="page-link" href="/client/1{% if request.GET.name %}?name={{ request.GET.name }}{% endif %}">&laquo; first</a>
					</li>
					<li class="page-item">
						<a class="page-link" href="/client/{{data.previous_page_number}}{% if request.GET.name %}?name={{ request.GET.name }}{% endif %}">&laquo; prev</a>
					</li>
					{% else %}
					<li class="page-item">
						<a class="page-link">&laquo; first</a>
					</li>
					<li class="page-item">
						<a class="page-link">&laquo; prev</a>
					</li>
					{% endif %}
					<li class="page-item">
						<a class="page-link">{{data.number}}/{{data.paginator.num_pages}}</a>
					</li>
					{% if data.has_next %}
					<li class="page-item">
						<a class="page-link" href="/client/{{data.next_page_number}}{% if request.GET.name %}?name={{ request.GET.name }}{% endif %}">next &raquo;</a>
					</li>
					<li class="page-item">
						<a class="page-link" href="/client/{{data.paginator.num_pages}}{% if request.GET.name %}?name={{ request.GET.name }}{% endif %}">last &raquo;</a>
					</li>
					{% else %}
					<li class="page-item">
						<a class="page-link">next &raquo;</a>
					</li>
					<li class="page-item">
						<a class="page-link">last &raquo;</a>
					</li>
					{% endif %}
				</ul>

このように書くことで、ページネーションで遷移する場合でも、検索クエリを引き継ぐことができます。

検索項目が「会社名」だけだが、項目が増えるともう少し複雑になりそう。。
と思ったが、実際に書いてみたら割と簡単だった。

さー、いよいよ次はPythonでPDF。やっとここまで来ました。

[Django3.0]datepickerの範囲指定で検索して表示する

見積一覧ページで、「日付」「会社名」「件名」でページ内検索できるようにする。
なお、日付は、範囲指定か、以上以下で検索できるようにする

画面

### template

<form action="/estimate/1" method="post">
				{% csrf_token %}
				<div class="form-group row">
						<label for="datepicker_s" class="col-md-1 col-form-label">日付</label>
						<div class="col-md-2">
							<input name="datepicker_s" type="text" class="form-control align-bottom" id="start" placeholder="開始">
						</div>
						<div class="col-md-2">
							<input name="datepicker_e" type="text" class="form-control" id="end" placeholder="終了">
						</div>
						<label for="client_name" class="col-md-1 col-form-label">会社名</label>
						<div class="col-md-6">
							<input name="client_name" type="text" class="form-control" id="tel" placeholder="得意先会社名">
						</div>
				</div>

				<div class="form-group row">
						<label for="title" class="col-md-1 col-form-label">見積件名</label>
						<div class="col-md-11">
							<input type="text" class="form-control align-bottom" name="title" id="title" placeholder="見積件名">
						</div>
				</div>

				<div class="">
						<button class="btn search-btn text-center" type="submit">検索</button>
				</div>
			</form>

			<span>{{ query }}</span>

JS

$(function(){
			var format = 'yy-mm-dd';

			var start = $("[name=datepicker_s]").datepicker({
				dateFormat: 'yy-mm-dd'
			}).on("change", function(){
				end.datepicker("option", "minDate", getDate(this));
			});

			var end = $("[name=datepicker_e]").datepicker({
				dateFormat: 'yy-mm-dd'
			}).on("change", function(){
				start.datepicker("option", "maxDate", getDate(this));
			});

			function getDate(element){
				var date;
				try {
					date = $.datepicker.parseDate(format, element.value);
				} catch(error){
					date = null;
				}
				return date;
			}
		});

html側で「終了」が「開始」より前で指定できないようバリデーションがかかります。

### views.py
– 1.「開始」「終了」の入力があった場合、2.「開始」のみ入力があった場合、3.「終了」のみ入力があった場合、4.「開始」「終了」の入力がないPOSTの場合、5.「GET」の場合 でそれぞれレコードを取得します。
– ForeginKeyのクエリを検索する場合は、${model}__${column}で検索する。ここでは、顧客の会社名一部一致の検索のため、client__name__containsとしている。
– 検索内容を検索結果ページに表示させる

def estimate(request, num=1):
	if(request.method=='POST' and request.POST['datepicker_s'] and request.POST['datepicker_e']):
		data = Estimates.objects.filter(estimate_date__range=(request.POST['datepicker_s'], request.POST['datepicker_e']), client__name__contains=request.POST['client_name'], title__contains=request.POST['title']).order_by('-id')
		query = "「" + request.POST['datepicker_s'] + "〜" + request.POST['datepicker_e'] + "」"
		query += "「" + request.POST['client_name'] + "」" if request.POST['client_name'] else ""
		query += "「" + request.POST['title'] + "」" if request.POST['title'] else ""
		query += "の検索結果"
	elif(request.method=='POST' and request.POST['datepicker_s']):
		data = Estimates.objects.filter(estimate_date__gte=request.POST['datepicker_s'], client__name__contains=request.POST['client_name'], title__contains=request.POST['title']).order_by('-id')
		query = "「" + request.POST['datepicker_s'] + "〜」"
		query += "「" + request.POST['client_name'] + "」" if request.POST['client_name'] else ""
		query += "「" + request.POST['title'] + "」" if request.POST['title'] else ""
		query += "の検索結果"
	elif(request.method=='POST' and request.POST['datepicker_e']):
		data = Estimates.objects.filter(estimate_date__lte=request.POST['datepicker_e'], client__name__contains=request.POST['client_name'], title__contains=request.POST['title']).order_by('-id')	
		query = "「〜" + request.POST['datepicker_e'] + "」"
		query += "「" + request.POST['client_name'] + "」" if request.POST['client_name'] else ""
		query += "「" + request.POST['title'] + "」" if request.POST['title'] else ""
		query += "の検索結果"
	elif(request.method=='POST'):
		data = Estimates.objects.filter(client__name__contains=request.POST['client_name'], title__contains=request.POST['title']).order_by('-id')
		query += "「" + request.POST['client_name'] + "」" if request.POST['client_name'] else ""
		query += "「" + request.POST['title'] + "」" if request.POST['title'] else ""
	else:
		data = Estimates.objects.all().order_by('-id')
		query = ""
	page = Paginator(data, 3)
	count = data.count()
	total = data.aggregate(Sum('total'))
	params = {
		'data' : page.get_page(num),
		'count' : count,
		'total' : total,
		'query' : query,
	}
	return render(request, 'sales/estimate.html', params)

これで1ページ目は上手くいってるんだけど、2ページ目はpostではなくgetになってしまうから、上手くいかんな。どうしたらいいんだろうか。