Django:Views.pyでmodels.modelを使い日付毎の集計を行う方法
Django:Views.pyでmodels.modelを使い日付毎の集計を行う方法
本当はpandasによる集計で完結したかったのですが、グラフを書こうとしたら「そうもいかない事情」が発生。
グラフ描写プラグインに【 [UNIX時, 値] 】の配列で渡さないといけない
せかっくPandasで集計したのでコレを生かしたかったのですが、DataFrameにしたものをまたListに戻すと言うどうも納得できない処理が発生したので「だったらSQL側で抽出しよう」との考えに至りました。
構成の概要
■models.py
from django.db import models
from django.core.validators import RegexValidator
class Dailies(models.Model):
"""日計基礎DATA"""
observation_date = models.DateField(verbose_name='観測日')
client = models.ForeignKey(Clients, verbose_name='クライアント', on_delete=models.PROTECT)
shop = models.ForeignKey(Shops, verbose_name='店舗', on_delete=models.PROTECT)
TYPE_CHOICES = (
(1, '総合'),
(2, 'PC'),
(3, 'SP'),
(4, 'アプリ'),
)
types = models.IntegerField(verbose_name='販売メディア', choices=TYPE_CHOICES)
sales = models.FloatField(verbose_name='売上高(税別)')
contracts = models.FloatField(verbose_name='売上件数', null=True)
access_count = models.FloatField(verbose_name='アクセス数', null=True)
conversion_rate = models.FloatField(verbose_name='転換率', null=True)
customer_unit_price = models.FloatField(verbose_name='客単価', null=True)
average_purchases = models.FloatField(verbose_name='平均購入点数', null=True)
items_ordered = models.FloatField(verbose_name='注文点数', null=True)
page_views = models.FloatField(verbose_name='ページビュー', null=True)
staying_time = models.FloatField(verbose_name='平均滞在時間', null=True)
page_migrations = models.FloatField(verbose_name='平均回遊ページ数', null=True)
created_at = models.DateTimeField(verbose_name='作成日時', auto_now_add=True)
updated_at = models.DateTimeField(verbose_name='更新日時', auto_now=True)
class Meta:
verbose_name_plural = 'Dailies'
def __str__(self):
return self.observation_date
■urls.py
from django.urls import path
from . import views
app_name = 'repo'
urlpatterns = [
path('<int:pk>/', views.DetailView, name="client_show"),
]
こんな感じでmodelsに登録しました。
repo/<int:pk>でクライアント毎の集計データをグラフで表現し最終的に下のようなグラフに描写します。

Views.pyでmodels.modelクラスを継承してGroup_by & SUMを実行
import logging, datetime
from datetime import date, timedelta
from dateutil.relativedelta import relativedelta
from django.shortcuts import render
from django.views import generic
from .models import Dailies
from django.views.decorators.csrf import csrf_exempt
from django.db.models import Avg, Count, Min, Sum
logger = logging.getLogger(__name__)
@login_required
@csrf_exempt
def DetailView(request, pk):
(中略)
"""データ抽出日時調整"""
d = datetime.date.today()# 今日
yd = (d - datetime.timedelta(days=1))# 昨日
fd = yd.replace(day=1)# 同月1日
ed = yd.replace(day=calendar.monthrange(d.year, d.month)[1])# 同月末日
"""Models.Modelクラスからクライアントの情報を抽出"""
d_box = Dailies.objects.filter(observation_date__range=[fd, ed])\
.filter(types=1)\
.filter(client_id=pk)
"""salesカラムを日付ごとにsum集計""""
list_sales = d_box.values('observation_date').order_by('observation_date')\
.annotate(total_sale=Sum('sales'))
(後略)
30行目~31行目:日毎sum集計の解説
前段の25行目~27行目でクライアントデータと端末(総合)の抽出が終わっています。
30行目では d_box で抽出した値から日付データを選択し昇順並替えを実行しています。
注目点はこの【 オブジェクト名.values('集計区分')】の場所です。
例えば、ここを [ d_box.values('observation_date','sales') ] とした場合、observation_dateとsalesの2軸でGroup_byが実行されます。
なので、例えば端末ごとの集計を出したい時などは下のようにすればOKです。
list_sales = d_box.values('observation_date').order_by('observation_date','types').annotate(total_sale=Sum('sales'))
31行目はWEB上に誤った情報を多く見かけました。
sumでも間違いと言う事はできません。ただし、select でSQLを直接呼び出すなど今回とは違うルートで合算する場合です。models.modelからのルートの場合は【 Sum 】が正しいです。
unsupported operand type(s) for +: 'int' and 'str'が発生する
models.modelクラスでsumを使うと文字列は集計できないとエラー発生
これは「modelから送られてくるときに数値をテキスト化しているから」なのだそうです。
※この理由については真実性を確かめる事が出来ませんでしたが、sumとした場合のエラー再現性は100%でした。
上記エラーを回避するために利用するのが【from django.db.models import Avg, Count, Min, Sum】です。
ぱっと見でわかる通り、modelsクラスで平均、カウント、集計などを使える様にします。
models.modelクラスを継承しての集計はこのSumを使うので「sumではないよ、Sumなのよ」となります。
まとめ
分析ページ内に複数のグラフを配置したいのですが、グラフ用の配列を作るのが結構大変です。
「きっと上手くやる方法あるんだろうなぁ」と思いながら取り合えず力業でもがいています。


