LoginSignup
1
1

DjangoでValueObjectを使いたい

Last updated at Posted at 2024-03-23

はじめに

以前、:point_down:こんな記事を書きました。
このクラスをDjangoでいい感じに使用できないかを検討したところ、
いい方法を見つけたのでこちらに残しておきます。

何も考えずMoneyクラスを使う場合

私含め、多くの方は以下のようになるのではないでしょうか。
この場合だと、いちいちWalletモデルのamountをMoneyクラスに詰め替えなければいけないため、だいぶ面倒だと思います。

この記事では、詰替えの手間を省く方法を説明します。

models.py
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クラスをフィールドとして定義します。
書き方は:point_down:こんな感じです。

models.py
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に定義するときは普通のフィールドと同じ使い方をします。

models.py
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パターンに移行するかを検討する必要があるかもしれませんね。:angel_tone2::angel_tone2:

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1