環境について
この記事は以下の環境を基に記述しています。
- Windows 7 Home Premium sp1 64 bit
- Python 3.6.1
- django 1.11.2
- jquery 3.2.1
- Chart.js 2.6.0
筆者のdjangoアプリ環境はこちらを基して作成させていただきました。筆者の環境もモデル名やフィールド名は違えど概ねこれに沿っています。
##背景
django でweb家計簿アプリを自作しています。登録してある家計簿データから一ヶ月の間でカテゴリごとに出費がいくらだったかを集計して、どのカテゴリが一ヶ月の間何%出費であったかを円グラフで表示したところまでを紹介します。カテゴリとは例えば「食費」,「交際費」,「交通費」…などです。また、グラフ描画の部分は簡易的で動きのあるグラフを描画できるChart.jsを使用しています。
django のプロジェクトのルートは kakeibo です。ざっくりとツリー表示で以下です。
kakeibo
│ db.sqlite3
│ manage.py
│
├─api
│ admin.py
│ apps.py
│ models.py
│ tests.py
│ urls.py
│ views.py
│ __init__.py
│
├─cms
│ admin.py
│ apps.py
│ forms.py
│ models.py
│ tests.py
│ urls.py
│ views.py
│ __init__.py
│
├─kakeibo
│ settings.py
│ urls.py
│ views.py
│ wsgi.py
│ __init__.py
│
│
└─static
├─css
└js
Chart.js
jquery-3.2.1.min.js
model
以下のモデルを使用します。
- Category … 食費、交際費、交通費・・・その他諸々
- Kakeibo … 文字通り家計簿を表すモデル
Kakeibo は外部キーとしてCategory を参照します。家計簿ということで、何年何月何日、どのカテゴリに、いくら出費したかというデータがつらつら記録されています。
from django.db import models
class Category(models.Model):
name = models.CharField(u'名称', max_length=30)
def __str__(self):
return self.name
class Kakeibo(models.Model):
IN_OR_OUT = (
('IN', '収入'),
('OUT', '支出')
)
kakeibo_type = models.CharField(u'収支', max_length=10, choices=IN_OR_OUT)
price = models.IntegerField(u'金額')
date = models.DateField(u'日付')
category = models.ForeignKey(Category)
detail = models.TextField(u'備考', max_length=300,null=True, default=None, blank=True)
def __str__(self):
return self.kakeibo_type + "_" + self.category.name + "_" + self.date.strftime(u'%Y%m%d')
グラフを表示する部分のhtml
グラフを表示するhtml には以下のような記述をします。chartjsではhtml5から導入された<canvas> 要素にグラフ描画します。その下にjavascript & jQuery を書いています。
*$.getJSON("api/get_summary/2017/04")*の部分で、2017年4月の家計簿のカテゴリごとの集計データと表示形式json 文字列を取得してChartjs で表示ということをしています。
<!-- 必要なところのみ記載 -->
<head>
<meta charset="UTF-8">
<script src="{% static 'js/jquery-3.2.1.min.js' %}"></script>
<script src="{% static 'js/Chart.js' %}"></script>
</head>
<canvas id="myChart"></canvas>
<script type="text/javascript">
$(function() {
$.getJSON("/api/get_summary/2017/04", function(data) {
var canvas = document.getElementById("myChart");
var chart = new Chart(canvas.getContext('2d'), data);
});
});
</script>
python からjson文字列の取得
表示する前にまず、Chart.js で表示できる文字列の構造を理解する必要があります。円グラフを表示する場合のconfig は以下のような感じです。下の構造のjson文字列を頑張ってpythonで整形しました。
type: 'pie',
data: {
datasets: [{
data: [
300,
50,
100,
150,
],
backgroundColor: [
"#F7464A",
"#46BFBD",
"#FDB45C",
"#C4B45C",
],
label: 'TEST DATA'
}],
labels: [
"aaa",
"iii",
"uuu",
"eee"
]
},
options: {
responsive: true,
title: {
display: true,
text: 'yyyy年MM月の収支'
}
}
最後にjson 文字列を送信するコードを記述します。まずは url関係から。
アプリ自体のurlの指定は /api/~ だったら api/urls を見てよー(include)としてあります。
#coding:utf-8
from django.conf.urls import url
from django.conf.urls import include
urlpatterns = [
url(r'^api/',include('api.urls', namespace='api')),
]
api/urls.py は取りあえず以下です。"get_summary/yyyy/MM" 形式で受け取るようにします。
#coding:utf-8
from django.conf.urls import url
from api import views
urlpatterns = [
url(r'^get_summary/(\d{4})/(\d{2})', views.get_summary,name='get_summary_month'),
]
さて、上のようにすると、api/views.py の get_summary(request, y=today.year, m=today.month)が動く様になります。(\d{4})の部分が y(例: y=2017)、 (\d{2})が(例 : d=07)という感じです。y, m に初期値として、以下のように本日の年、本日の月がそれぞれ入ります。ここからがmodel の集計、json形式の文字列の整形と、送信までを行います。
from datetime import date
import sys
import json
from django.shortcuts import render
from django.http import HttpResponse
from django.http import JsonResponse
from django.core import serializers
from django.db.models import Sum
from cms.models import Kakeibo
from cms.models import Category
color_labels = ["#FF0000", "#00FFFF", "#00FF00", "#FF00FF", "#FF00FF", "#0000FF",
"#FFFF00", "#80FF00", "#FF8000", "#FF0080", "#8000FF", "#0080FF", "#00FF80",
"#cc0000", "#00cccc", "#00cc00", "#cc00cc", "#cc00cc", "#0000cc", ]
today = date.today()
def get_summary(request, y=today.year, m=today.month):
status = None
kakeibo_summary = Kakeibo.objects.filter(kakeibo_type='OUT',
date__year=y, date__month=m).values(
'category',).annotate(sum_price=Sum('price'))
data = []
labels = []
backgroundColor = []
for k in kakeibo_summary:
c_pk = k["category"]
c = Category.objects.get(pk=c_pk).name
data.append(k["sum_price"])
labels.append(c)
backgroundColor.append(color_labels[c_pk])
label = str(y) + "年" + str(m) + "月の収支"
config = create_chartjs_json_dict(
typestr='pie', data=data, backgroundColor=backgroundColor, label=label, labels=labels, text=label)
json_str = json.dumps(config, ensure_ascii=False, indent=2)
return _respose_json(request=request, json_str=json_str, status=status)
def create_chartjs_json_dict(typestr, data, backgroundColor, label, labels, text):
data_dict = {}
datasets_dict = {}
datasets_list = []
options_dict = {}
title_dict = {}
config = {}
config["type"] = typestr
datasets_dict["data"] = data
datasets_dict["backgroundColor"] = backgroundColor
datasets_dict["label"] = label
datasets_list.append(datasets_dict)
data_dict["datasets"] = datasets_list
data_dict["labels"] = labels
config["data"] = data_dict
options_dict["responsive"] = "true"
title_dict["display"] = "true"
title_dict["text"] = text
options_dict["title"] = title_dict
config["options"] = options_dict
return config
def _respose_json(request, json_str, status):
callback = request.GET.get('callback')
if not callback:
callback = request.POST.get('callback')
if callback:
json_str = "%s(%s)" % (callback, json_str)
response = HttpResponse(
json_str, content_type='application/javascript; charset=UTF-8', status=status)
else:
response = HttpResponse(
json_str, content_type='application/javascript; charset=UTF-8', status=status)
return response
以上を用いて上述のhtmlで表示すると、グラフ表示されました。結果は以下のようになります。まだまだ途中ですがご容赦ください。
最後に
今回は、model からデータを集計してのグラフ表示するところまで書きました。json文字列の整形の部分でコードが読みにくいため今後改善したいです。色々と盛りだくさんで書きましたが、ご参考になればと思います。