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)

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

quandlから日経225データを取得する

$ pip install quandl

ダブルインバース(1357)のデータを見てみる
>>> import quandl
>>> quandl.ApiConfig.api_key = ‘hogehoge’
>>> data = quandl.get(‘TSE/1357’)
>>> data
Open High Low Close Volume
Date
2014-07-16 5270.0 5280.0 5230.0 5260.0 43965.0
2014-07-17 5230.0 5270.0 5190.0 5260.0 52325.0
2014-07-18 5400.0 5440.0 5350.0 5350.0 125707.0
2014-07-22 5320.0 5320.0 5240.0 5270.0 71590.0
2014-07-23 5250.0 5290.0 5250.0 5290.0 19141.0
… … … … … …
2017-12-18 1285.0 1289.0 1267.0 1268.0 20980910.0
2017-12-19 1263.0 1274.0 1259.0 1272.0 8550773.0
2017-12-20 1277.0 1281.0 1266.0 1268.0 8632009.0
2017-12-21 1274.0 1290.0 1269.0 1274.0 10881922.0
2017-12-22 1275.0 1281.0 1268.0 1270.0 8738199.0

2017年末までのデータしか取得できない。

### 日経平均の取得
https://www.quandl.com/api/v3/datasets/CHRIS/CME_NK2/data.json?api_key=${api_key}

レスポンス結果
{“dataset_data”:{“limit”:null,”transform”:null,”column_index”:null,”column_names”:[“Date”,”Open”,”High”,”Low”,”Last”,”Change”,”Settle”,”Volume”,”Previous Day Open Interest”],”start_date”:”1990-09-26″,”end_date”:”2020-08-05″,”frequency”:”daily”,”data”:[[“2020-08-05”,22530.0,22555.0,22355.0,22395.0,-30.0,22390.0,22.0,6.0],[“2020-08-04”,null,22555.0,null,22435.0,135.0,22420.0,0.0,6.0],…// 省略

$ sudo apt-get install libpng-dev
$ sudo apt-get install libfreetype6-dev
$ sudo pip install matplotlib
$ sudo pip install ipython jupyter

$ python
Python 3.8.0 (default, Oct 28 2019, 16:14:01)
[GCC 8.3.0] on linux
Type “help”, “copyright”, “credits” or “license” for more information.
>>> import json
>>> json.dumps([‘foo’, {‘bar’: (‘baz’, None, 1.0, 2)}])
‘[“foo”, {“bar”: [“baz”, null, 1.0, 2]}]’

### quandlからデータ取得
app.py

# API
import requests
import json
import datetime as dt
# date
from dateutil.relativedelta import relativedelta
from pytz import timezone

# データ前処理、グラフ表示
import numpy as np
import pandas as pd
from IPython import get_ipython
ipy = get_ipython()
if ipy is not None:
    ipy.run_line_magic('matplotlib', 'inline')
import matplotlib.pyplot as plt

fig = plt.figure()

# エンドポイントを定義
url = 'https://www.quandl.com/api/v3/datasets/CHRIS/CME_NK2/data.json?api_key=hogehoge'

# json でデータ取得
catched_response = requests.get(url)
json_data = catched_response.json()


columns = json_data['dataset_data']['column_names']
values = json_data['dataset_data']['data']

df = pd.DataFrame(values, columns=columns)
df.loc[:,'Date'] = pd.to_datetime(df.loc[:,'Date'])
df.set_index('Date', inplace=True)

df.loc[:,'High'].interpolate(inplace=True)
df.loc[:,'Last'] = np.minimum(df.loc[:,'Last'].interpolate(),df.loc[:,'High'])

today = dt.datetime.now(timezone('Asia/Tokyo'))
t_year = today - relativedelta(years=1)
t_date = t_year.strftime('%Y-%m')

df_target = df.loc[:t_date,'Last'].sort_index()
df_target.plot()
df_target.rolling(window = 5).mean().plot()
df_target.rolling(window = 25).mean().plot()

fig.savefig("dist/img/225.png")

$ python app.py

トップページに嵌め込みます。

matplotlibのグラフはクーロンで日次でバッチ処理すれば良いですね。
景気動向指数の予定だったが、CIの各指標がAPIで公開されていないので、225のチャートで良しとするか。
致し方がない。

luigi

データフロー制御フレームワーク
spotifyが開発
タスク同士の依存関係を解決し、オブジェクト指向でデータ処理のタスクが書ける
Hadoopとの連携

class Artists(luigi.Task):

	date_interval = luigi.DateIntervalParameter()
	use_hadoop = luigi.BoolParameter()

	def requires(self):
		if self.use_hadoop:
			return AggregateArtistsHadoop(self.date_interval)
		else:
			return AggregateArtists(self.date_interval)

	def output(self):
		return luigi.LocalTarget("data/top_artists_%s.tsv" % self.date_interval)

	def run(self):
		top_10 = nlargest(10, self._input_iterator())
		with self.output().open('w') as out_file:
			for streams, artist in top_10:
				out_line = '\t'.join([
						str(self.date_interval.date_a),
						str(self.date_interval.date_b),
						artist,
						str(streams)
				])
				out_file.write((out_line + '\n'))

	def _input_iterator(self):
		with self.input().open('r') as in_file:
			for line in in_file:
				artist, streams = line.strip().split()
				yield int(streams), artist

statsmodel

[vagrant@localhost python]$ pip install statsmodels

import numpy as np 
import statsmodels.api as sm 
import matplotlib.pyplot as plt 

data = np.loadtxt("data.txt")
x = data.T[0]
y = data.T[1]

nsample = x.size 

X = np.column_stack((np.repeat(1, nsample), x))

model = sm.OLS(y, X)
results = model.fit()

print(results.summary())

python datetime

datetimeのオブジェクト
datetime(year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None)

import datetime 

dt_now = datetime.datetime.now()
print(dt_now)

print(type(dt_now))

print(dt_now.year)
print(dt_now.minute)
print(dt_now.microsecond)

[vagrant@localhost python]$ python app.py
2019-10-18 13:18:20.323816

2019
18
323816

datetimeあたりだと、言語ごとの違いも然程ないでしょうね。

python json

jsonモジュールを使用すると、Json形式のファイルや文字列をパースして、dictなどのオブジェクトとして読み込める。

import json
from collections import OrderedDict 
import pprint 

s = r'{"C": "\u3042", "A": {"i":1, "j":2}, "B":[{"X":1, "Y":10}, {"X":2, "Y": 20}]}'

print(s)

d = json.loads(s)

pprint.pprint(d, width=40)

print(type(d))

[vagrant@localhost python]$ python main.py
{“C”: “\u3042”, “A”: {“i”:1, “j”:2}, “B”:[{“X”:1, “Y”:10}, {“X”:2, “Y”: 20}]}
{‘A’: {‘i’: 1, ‘j’: 2},
‘B’: [{‘X’: 1, ‘Y’: 10},
{‘X’: 2, ‘Y’: 20}],
‘C’: ‘あ’}

Python hashlib

hashlib: セキュアハッシュやメッセージダイジェスト用の様々なアルゴリズムを実装。sha256は有名ですよね。
e.x. sha1, sha224, sha256, sha384, sha512, RSA md5

コンストラクタがあり、ハッシュオブジェクトを返す

import hashlib 
m = hashlib.sha256()
m.update(b"National Security Agency")
print(m.digest())

print(m.digest_size)
print(m.block_size)

[vagrant@localhost python]$ python main.py
b’t\xc6!\xc4\xd2\xd3W&r4\xa2\xe2\xa9\x1f\x15k\xb9\xf1\x08\xa9\x10\xb6\xd2\xb5\xe6\x05|\xe2d\x81\xc8\xe6′
32
64

sha224で実行すると
b’\xe9\xf0\xee\xd4F\xd0;-MM\xa1\xc9\x8cY\x05\x1c\x81H\xde8\x95\x94\x0f\xcb\x13he\x89′

hash.update()でオブジェクトを更新
“b”でエンコード
よく使われるのは、md5とsha256

教師あり、教師なし

教師あり学習: 正解ラベルの付いたデータセット
教師なし学習: 正解ラベルの付いていないデータセット
-> 人間の解釈が必要な場合が多い

import numpy as np 
import matplotlib.pyplot as plt 
%matplotlib inline

from sklearn import datasets 
iris = datasets.load_iris()

print(iris.DESCR)
import numpy as np 
import numpy.random as random 
import scipy as sp 
import pandas as pd 
from pandas import Series, DataFrame 

from sklearn.model_selection import train_test_split 
from sklearn.datasets import load_iris 

iris = load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df["target"] = iris.target_names[iris.target]

df.head()

X = df.drop('target', axis=1)
Y = df['target']

X_train, X_test, y_train, y_test = train_test_split(X,Y,random_state=0)

# K-NN
from sklearn.neighbors import KNeighborsClassifier 
model = KNeighborsClassifier(n_neighbors=3)
model.fit(X_train, y_train)

print("train score:",model.score(X_train,y_train))
print("test score:",model.score(X_test,y_test))

# decision tree
from sklearn.tree import DecisionTreeClassifier 
model = DecisionTreeClassifier(max_depth=3)
model.fit(X_train, y_train)

print("train score:",model.score(X_train,y_train))
print("test score:",model.score(X_test,y_test))

# SVM
from sklearn.svm import LinearSVC
model = LinearSVC()
model.fit(X_train, y_train)

print("train score:",model.score(X_train,y_train))
print("test score:",model.score(X_test,y_test))

# Linear Regression
from sklearn.linear_model import LogisticRegression

model = LogisticRegression()
model.fit(X_train, y_train)

print("train score:",model.score(X_train,y_train))
print("test score:",model.score(X_test,y_test))