はじめに
以前、こんな記事を書きました。
このクラスをDjangoでいい感じに使用できないかを検討したところ、
いい方法を見つけたのでこちらに残しておきます。
何も考えずMoneyクラスを使う場合
私含め、多くの方は以下のようになるのではないでしょうか。
この場合だと、いちいちWalletモデルのamountをMoneyクラスに詰め替えなければいけないため、だいぶ面倒だと思います。
この記事では、詰替えの手間を省く方法を説明します。
class Wallet(models.Model):
amount = models.IntegerField()
wallet = Wallet.objects.create(amount=100)
money = Money(wallet.amount) + Money(200)
wallet.amount = money.amount
wallet.save()
実装方法
今回説明する実装方法はDjango公式の以下のページに記載がありました。
詳しく知りたい方はこちらをご参照ください。
https://docs.djangoproject.com/en/5.0/howto/custom-model-fields/#converting-values-to-python-objects
Model編
まずは、Moneyクラスをフィールドとして定義します。
書き方はこんな感じです。
from django.db import models
from django.core.exceptions import ValidationError
class MoneyField(models.IntegerField):
description = 'お金クラスのフィールド'
def __init__(self, *args, **kwargs):
kwargs['validators'] = [self.validate_amount]
kwargs['blank'] = kwargs.get('blank', False)
kwargs['null'] = kwargs.get('null', False)
super().__init__(*args, **kwargs)
def validate_amount(self, value):
''' この部分はMoneyクラスのバリデーションと重複しているので、やり方を検討中 '''
if value is not None and value < 0:
raise ValidationError('金額が0以上ではありません。')
def to_python(self, value):
if isinstance(value, Money):
return value
return Money(value)
def from_db_value(self, value, expression, connection):
''' DBからPythonのオブジェクトに変換する部分 '''
if value is None:
return None
return Money(value)
def get_prep_value(self, value):
''' PythonのオブジェクトからDBに変換する部分 '''
if value is None:
return None
if isinstance(value, Money):
return value.amount
return super().get_prep_value(value)
実際にModelに定義するときは普通のフィールドと同じ使い方をします。
class Wallet(models.Model):
amount = MoneyField()
あとはMoneyクラスと同じ要領で使用できます。
wallet = Wallet.objects.create(amount=Money(100))
print(wallet.amount) # -> 100 JPY
wallet.amount = wallet.amount + Money(200)
print(wallet.amount) # -> 300 JPY
wallet.save()
Forms
準備中...
ModelFormsを使えばForm側の定義は不要かもしれません。
最後に
DDDではORマッパーを使わないほうがいいと言われていますが、ValueObjectパターンだけを取り入れたいときには便利な機能だと思います。
DBの定義とかドメインモデルやエンティティなど色々複雑な要素が出てきたら、Reposiotryパターンに移行するかを検討する必要があるかもしれませんね。