[Django]ModelをJsonEncodeする際に日付や数値を書式設定したい

# やりたいこと

- Djangoのモデルをシリアル化（JSON)してクライアントにレスポンスしたい
- この時に日付を任意の書式に整形したい（例えば "19/03/01 13:00") 
- 数値を書式設定および単位を付与したい(例えば 10000 を "10,000円"とか"10,000部"とか）
- 整形はサーバサイドで実行したい

# 課題

Djangoモデルをシリアル化するためにDjangoが用意しているシリアライザ－（django.core.serializers.serialize / DjangoJSONEncoder）を使うと簡単にシリアル化できますが、以下の問題があります。

## datetime 型が冗長な表示になる

```python
2019-03-06T17:46:04.013Z
```

## シリアル化対象はモデルフィールドだけ
またモデルのフィールドしか対象にしてくれません。書式設定したメソッドやプロパティを用意しても含めることができません。（この**できない**という結論に至るまでの調査に時間を要したのでこの記事を書きました。参考になれば幸いです）

## 余計な情報も含まれてしまう
またモデルのフィールドだけでなく、モデルの名称や主キー値も含まれてしまうので冗長です。（クライアント側で必要なデータだけを取り出すというDjango依存の処理が必要になってしまう）

```python
In [11]: serializers.serialize('json',[os], stream=sys.stdout, indent=2)
[{
  "model": "order.ordersummary",  ★ここ★
  "pk": 1, ★ここ★
  "fields": {
     ・・・
  }
}]
```

# 対処方法

## 案1

[公式ドキュメントにある手法](https://docs.djangoproject.com/ja/2.1/topics/serialization/#serialization-formats-json)です。
JSONシリアライズ用のエンコーダをカスタマイズして、それをシリアライザーに渡します。

```python
from django.core.serializers.json import DjangoJSONEncoder

class CustomJSONEncoder(DjangoJSONEncoder):

    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.strftime("%y/%m/%d %H:%M")  ★ここ★
        return super().default(obj)
```

```python
from django.core import serializers

serializers.serialize('json', cls=CustomJSONEncoder)
```

これで日時型については意図した書式に変更できます。が、数値型(たとえばint)は同じ要領で以下のコードを入れてもできません。
また、そもそも属性により"円"とか"部"を付加したいのですがデータの属性名はわからないので制御できません。

```python
        if isinstance(obj, int):
            return "{:,}".format(obj)
```

## 案2（最終型）

Djangoのシリアライザは使わずにPythonのJsonシリアライザ－(json.dumps(obj))を使います。
ただし、Djangoモデルは直接変換できません。（実行エラーになります）

```python
import json
json.dumps(fooModel)  
>> TypeError: Object of type 'FooModel' is not JSON serializable
```

そこで、model_to_dictを使って、モデルをdict化したものをシリアライズします。

```python
import json
from django.forms.models import model_to_dict
json.dumps(model_to_dict(FooModel))
```

ただし、モデルにdatetime 型が含まれているとエラーになります。

```python
TypeError: Object of type 'datetime' is not JSON serializable
```

これを回避するためにdatetime型が設定されている要素に書式変換後の文字列を設定してしまいます。

```python
from django.forms.models import model_to_dict
class FooModel(models.Model)

    updated_at = models.DateTimeField('更新日時', auto_now=True, null=True)

    def to_dict(self):
        ret = model_to_dict(self) ★この時点では ret['update_at']はdatetime型★
        ret['updated_at'] = self.updated_at.strftime("%y/%m/%d %H:%M") ★文字列にしてしまう★
        return ret
```


```python
import json
json.dumps(fooModel.to_dict())
```

同じ要領で数値型フィールドも任意の文字列に変換できます。

```python
from django.forms.models import model_to_dict
class FooModel(models.Model)

    total_charge = models.PositiveIntegerField('合計金額', default=0)
    updated_at = models.DateTimeField('更新日時', auto_now=True, null=True)

    def to_dict(self):
        ret = model_to_dict(self)
        ret['total_charge'] = "{:,}円".format(self.total_charge)  ★ここ★
        ret['updated_at'] = self.updated_at.strftime("%y/%m/%d %H:%M")
        return ret
```

この方法のメリットとして、レスポンスに**不要なフィールド(model名,主キー値)が含まれなくなる**こともあげられます。

# おまけ
なお、モデルインスタンスが複数の場合は、```to_dict()```を繰り返し実行するコードを書くと冗長になります。
以下の記事にあるようにカスタマイズしたJsonシリアライザーを```json.dumps```に渡すことで冗長な記述なしで実現できます。

[DjangoでJSONを使うための一考察 SQLの最適化など - グロブ](http://typea.info/blg/glob/2015/03/djangojson-sql.html)

```python
from django.db import models as django_models
from django.db.models.query import QuerySet
class CustomJSONEncoder(json.JSONEncoder):

    def default(self, obj):
        if isinstance(obj, django_models.Model) and hasattr(obj, 'to_dict'):
            return obj.to_dict()
        if isinstance(obj, QuerySet):
            return list(obj)
        json.JSONEncoder.default(self, obj)
```

```python
from <...> import CustomJSONEncoder
json.dumps(model_list, cls=CustomJSONEncoder)
```

以上です。

ここまでやるとちょっと冗長な感じがでてきます。
Djangoのシリアライザ－のオプションでモデルのメソッドやプロパティをシリアル化対象に追加できるといいですね。（[Django Rest frameworkだとできる](https://stackoverflow.com/questions/22677070/additional-field-while-serializing-django-rest-framework)ようなので、余力ができたら、実装を見てみようかと。。。）