はじめに
Django Rest Framework(DRF) で、出力レイアウトの変換をDjango Pandasで実現した記事となります。(EXCELで言うところのピボットテーブル)
Pandasのシリアライズ化するのにDjango REST Pandasを使っています。
https://django-rest-pandas.wq.io/
クライアント側はDanfo.jsを使っています。
https://danfo.jsdata.org/
説明
例えば下記のような、預金項目、登録年月、金額のレイアウトの資産テーブル(Tt_Assets)があるとします。
資産ID(PK) | 預金項目ID | 登録年月 | 金額 |
---|---|---|---|
1 | 1(積立) | 2023/01 | 1,230 |
2 | 1(積立) | 2023/02 | 500 |
3 | 1(積立) | 2023/03 | 400 |
4 | 2(給与) | 2023/01 | 100,000 |
5 | 2(給与) | 2023/02 | 200,000 |
6 | 2(給与) | 2023/03 | 300,000 |
7 | 3(支払) | 2023/01 | 110,000 |
8 | 3(支払) | 2023/02 | 210,000 |
9 | 3(支払) | 2023/03 | 310,000 |
下記のレイアウトのように、行を年月単位にして、列を預金項目ID、値を金額にしたい。
(預金項目IDは預金マスタと紐づいているため列数は固定にならない)
登録年月 | 1(積立) | 2(給与) | 3(支払) |
---|---|---|---|
2023/01 | 1,230 | 100,000 | 110,000 |
2023/02 | 500 | 200,000 | 210,000 |
2023/03 | 100 | 300,000 | 310,000 |
モデル定義
#預金項目
class Tm_DepositItem(ModelBase):
class Meta:
db_table = 'Tm_DepositItem'
verbose_name = '預金項目(Tm_DepositItem)'
verbose_name_plural = '預金項目(Tm_DepositItem)'
unique_together = (
('depositItem_name',),
)
ordering = ('order_dsp',)
depositItem_key = models.AutoField(primary_key=True, verbose_name='預金項目ID')
depositItem_name = models.CharField(max_length=40, verbose_name='預金項目名')
order_dsp = models.IntegerField(verbose_name='表示順序')
def __str__(self):
return self.depositItem_name
#資産トラン
class Tt_Assets(ModelBase):
class Meta:
db_table = 'Tt_Assets'
verbose_name = '資産トラン(Tt_Assets)'
verbose_name_plural = '資産トラン(Tt_Assets)'
assets_key = models.AutoField(primary_key=True, verbose_name='資産ID')
depositItem_key = models.ForeignKey(Tm_DepositItem, on_delete=models.PROTECT, related_name='assets_deposititem_key', verbose_name='預金項目ID')
deposit_value = models.IntegerField(verbose_name='金額')
insert_yyyymm = models.CharField(max_length=7, verbose_name='登録年月')
View実装
from rest_pandas import PandasViewSet, PandasUnstackedSerializer
class AssetsPandasViewSet(PandasViewSet):
'''
資産トランのPivotデータ取得
insert_yyyymm_from, insert_yyyymm_toが設定されている場合は絞込も行う
'''
queryset = Tt_Assets.objects.all()
serializer_class = Tt_AssetsPandasSerializer
pandas_serializer_class = PandasUnstackedSerializer
@log_decorate('AssetsPandasViewSet::get_queryset()')
def get_queryset(self):
insert_yyyymm_from = None
insert_yyyymm_to = None
if 'insert_yyyymm_from' in self.request.GET:
insert_yyyymm_from = self.request.GET.get('insert_yyyymm_from')
if 'insert_yyyymm_to' in self.request.GET:
insert_yyyymm_to = self.request.GET.get('insert_yyyymm_to')
records = Tt_Assets.objects.all()
if insert_yyyymm_from :
records = records.filter(insert_yyyymm__gte=insert_yyyymm_from)
if insert_yyyymm_to :
records = records.filter(insert_yyyymm__lte=insert_yyyymm_to)
return records
シリアライズ実装
from rest_framework import serializers
from .models import Tt_Assets
class Tt_AssetsPandasSerializer(serializers.ModelSerializer):
class Meta:
model = Tt_Assets
# 利用列
fields = ['depositItem_key', 'deposit_value', 'insert_yyyymm']
# 行
pandas_index = ['insert_yyyymm']
# 列
pandas_unstacked_header = ['depositItem_key']
(fields、index, unstacked は、Pandasを理解するとなんとなくわかるのだと思います・・。)
確認
テストプログラムで中身を確認してみます。
パラメータにはJSONで受取るために?format=jsonを指定しています。
class AssetsandasTestCase(TestCase):
def test_AssetsPandasViewSet(self):
#
# テスト対象モジュールを呼出す
#
search_dates = ['2022/12', '2023/01', '2023/02', '2023/03', '2023/04', '2023/05', '2023/06']
TEST_URL = '/api/assets_pandas/?format=json&insert_yyyymm_from=' \
+ search_dates[0] + '&insert_yyyymm_to=' + search_dates[-1]
#TEST_URL = '/api/assets_pandas/?format=json'
logger.debug(TEST_URL)
res = self.client.get(TEST_URL)
# 確認
print('res.data.index')
print(res.data.index)
print('res.data.columns')
print(res.data.columns)
print('res.data')
print(res.data)
実行すると下記のように出力されます。データ型もPandasとなります。
res.data.index
RangeIndex(start=0, stop=7, step=1)
res.data.columns
MultiIndex([('insert_yyyymm', ''),
('deposit_value', 1),
('deposit_value', 2),
('deposit_value', 3),
('deposit_value', 4),
('deposit_value', 5),
('deposit_value', 6),
('deposit_value', 7),
('deposit_value', 8),
('deposit_value', 9),
('deposit_value', 10)],
names=['', 'depositItem_key'])
res.data
<class 'pandas.core.frame.DataFrame'>
insert_yyyymm deposit_value
depositItem_key 1 2 3 4 5 6 7 8 9 10
0 2022/12 0 10 20 30 40 50 60 70 80 90
1 2023/01 2 12 22 32 42 52 62 72 82 92
2 2023/02 4 14 24 34 44 54 64 74 84 94
3 2023/03 6 16 26 36 46 56 66 76 86 96
4 2023/04 8 18 28 38 48 58 68 78 88 98
5 2023/05 10 20 30 40 50 60 70 80 90 100
6 2023/06 12 22 32 42 52 62 72 82 92 102
クライアント側
クライアント(React)で受信してみるとこんな感じのデータを受信出来ました。
どうもPandasフォーマットらしいです。まぁこれでもいいっちゃー良いのだけど。
そこでPandasをJavaScriptで利用出来るDanfo.jsというライブラリを使ってみました。
https://danfo.jsdata.org/
JSONフォーマットからDataFrameへの変換が出来ます。
https://danfo.jsdata.org/api-reference/dataframe/dataframe.to_json
記載のある通りにJSに書いてみました
import * as dfd from "danfojs"
console.debug('-*----------apiResult2Rowdata');
//console.debug(resultDatas);
// Response結果をPandasへ
const df = new dfd.DataFrame(resultDatas)
console.debug(df.print())
console.log出力結果をみるとちゃんとPandasとして扱えるのが確認出来ました。
列情報が、('insert_yyyymm')や('deposit_value')などになっているため多少の加工が必要となりますが
クラアントとしてデータ表示することが出来ました。
注意事項
Django REST Pandas の列指定は、リレーション先の列情報を指定するのはダメそうでした。
参考サイト
https://django-rest-pandas.wq.io/serializers/PandasUnstackedSerializer
https://danfo.jsdata.org/api-reference/dataframe/dataframe.to_json