Djangoのモデル継承でポリモーフィズムを実現する

Djangoのモデルクラスは継承が可能ですが、基底クラスのオブジェクトを派生クラスのオブジェクトに変換する手段がデフォルトで存在しないため、普通のオブジェクト指向プログラミングのように、派生クラスでメソッドをオーバーライドしてオブジェクトの振る舞いを変えるといったことができません。

具体例として、次のサンプルプログラムを実行してみます。

  • without_polymorphic/models.py
from django.db import models

class Base(models.Model):
  pass

class Derived1(Base):
  pass

class Derived2(Base):
  pass
  • sample1.py
import settings
from django.core import management
management.setup_environ(settings)
from django.db.transaction import commit_on_success

from without_polymorphic import models

@commit_on_success
def delete_instances():
  for base in models.Base.objects.all():
    base.delete()

@commit_on_success
def create_instances():
  print '='*40
  print 'create_instances'
  print '='*40
  objs = (models.Derived1(), models.Derived1(), models.Derived2(), models.Derived2())
  for obj in objs:
    print obj
    obj.save()

def show_instances():
  print '='*40
  print 'show_instances'
  print '='*40
  for obj in models.Base.objects.all():
    print obj

def main():
  delete_instances()
  create_instances()
  show_instances()

if __name__ == '__main__':
  main()
  • 実行結果
========================================
create_instances
========================================
Derived1 object
Derived1 object
Derived2 object
Derived2 object
========================================
show_instances
========================================
Base object
Base object
Base object
Base object

オブジェクト作成時はオブジェクトのクラスは派生後のクラスですが、データベースから取り出したオブジェクトのクラスは基底クラスとなっていることが分かります。これでは、派生クラスでいくらメソッドをオーバーライドしても、そのメソッドを使うことができません。
(もちろん、オブジェクト取得の部分でmodels.Base.objects.all()の代わりにmodels.Derived1.objects.all()と書けば派生クラスのオブジェクトを得ることが出来ますが、その方法で全てのオブジェクトを得るには、派生クラスを全て知っている必要があります。)


基底クラスのオブジェクトから派生クラスのオブジェクトに変換するには、django-polymorphic-modelsというライブラリを使用します。このライブラリをDjangoのアプリに追加し、次のプログラムを実行してみます。

  • with_polymorphic/models.py
from django.db import models
from polymorphic.models import PolymorphicMetaclass

class Base(models.Model):
  __metaclass__ = PolymorphicMetaclass

class Derived1(Base):
  pass

class Derived2(Base):
  pass
  • sample2.py
import settings
from django.core import management
management.setup_environ(settings)
from django.db.transaction import commit_on_success

from with_polymorphic import models

@commit_on_success
def delete_instances():
  for base in models.Base.objects.all():
    base.delete()

@commit_on_success
def create_instances():
  print '='*40
  print 'create_instances'
  print '='*40
  objs = (models.Derived1(), models.Derived1(), models.Derived2(), models.Derived2())
  for obj in objs:
    print obj
    obj.save()

def show_instances():
  print '='*40
  print 'show_instances'
  print '='*40
  for obj in models.Base.objects.all():
    obj = obj.downcast()
    print obj

def main():
  delete_instances()
  create_instances()
  show_instances()

if __name__ == '__main__':
  main()

最初の例との違いは、Baseクラスのメタクラスをpolymorphic.models.PolymorphicMetaclassにした点と、基底クラスのオブジェクトを派生クラスのオブジェクトに変換するために、downcasetメソッドを実行している点です。

このプログラムの実行結果は次のようになります。

========================================
create_instances
========================================
Derived1 object
Derived1 object
Derived2 object
Derived2 object
========================================
show_instances
========================================
Derived1 object
Derived1 object
Derived2 object
Derived2 object

この例では、派生クラスのオブジェクトを得るためにdowncastメソッドを実行していますが、基底クラスのメタクラスをPolymorphicMetaclassではなくDowncastMetaclassとすると、自動的にdowncastメソッドを実行してくれるようです。