モデルクラスを継承してメソッドを追加する(proxyを使う場合)

この記事は Django Advent Calendar 2017 の7日目の記事です。

Djangoのモデルクラスを継承した場合、Metaに特に指定が無ければ、マルチテーブル継承となり、migrateを実行すると2つのテーブルができます。

モデルを継承して単一のテーブルにする方法は2つあります。

  • 親のモデルクラスでMetaに abstract = True を指定する
  • 継承した子クラスでMetaに proxy = True を指定する

今回は後者のproxyを指定する方法を紹介します。

https://docs.djangoproject.com/en/2.0/topics/db/models/#proxy-models

この方法は、継承元の親クラスがサードパーティライブラリ等から提供されて、直接書き換えられない場合などに便利です。

継承してproxyを指定したモデル

Djangoに組込みの django.contrib.auth.models.User を継承してみます。

from django.contrib.auth.models import User


class MyUser(User):
    """
    Userモデルを継承するが、新たにテーブルは作られない。
    """
    class Meta:
        proxy = True

    def display(self):
        """追加で定義したメソッド
        """
        print("{full_name}さん".format(full_name=self.get_full_name()))

呼び出し例

>>> from myapp.models import MyUser
>>> MyUser(first_name="Shinya", last_name="Okano").display()
Shinya Okanoさん

ハマりどころ

ForeignKeyなどで参照した場合には、継承した子クラスではなく、ForeignKeyで指定された元のモデルのインスタンスが返されます。

たとえば上記の例だと、 django.contrib.auth.models.Group などから辿った場合は、Userクラスのインスタンスになります。

>>> user = Group.objects.first().user_set.filter(first_name="Shinya").first()
>>> user
<User: >
>>> user.display()  # User.displayはない
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'User' object has no attribute 'display'

回避するには、proxyのモデルを起点にQuerySetを作るか、インスタンス__class__ を無理やり書き換えるなどですが、スマートなやり方が現状ないですね。

>>> user.display()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'User' object has no attribute 'display'
>>> user.__class__ = MyUser
>>> user
<MyUser: >
>>> user.display()
Shinya Okanoさん

まあ、やっていきましょう。