30

モデルがあるとします:

class Animal(models.Model):
    type = models.CharField(max_length=255)

class Dog(Animal):
    def make_sound(self):
        print "Woof!"
    class Meta:
        proxy = True

class Cat(Animal):
    def make_sound(self):
        print "Meow!"
    class Meta:
        proxy = True

私がやりたいとしましょう:

 animals = Animal.objects.all()
 for animal in animals:
     animal.make_sound()

ウーフとニャーのシリーズを取り戻したい。明らかに、animal_type に基づいて分岐する元のモデルで make_sound を定義することもできますが、新しい動物の種類を追加するたびに (それらが別のアプリにあると想像してください)、その make_sound 関数に移動して編集する必要があります。 . むしろ、プロキシ モデルを定義して、動作自体を定義させたいと思います。私が知る限り、混合した Cat または Dog インスタンスを返す方法はありませんが、猫または犬のモデルを返すメイン クラスで「get_proxy_model」メソッドを定義できるのではないかと考えました。

確かにこれを行うことができ、主キーのようなものを渡してから、Cat.objects.get(pk = passed_in_primary_key) を実行するだけです。しかし、それは、すでに持っているデータに対して追加のクエリを実行することを意味し、冗長に見えます。動物を猫や犬のインスタンスに効率的に変える方法はありますか? 私が達成したいことをする正しい方法は何ですか?

4

5 に答える 5

11

thedk によって提案されたメタクラス アプローチは確かに非常に強力な方法ですが、ここでの質問への回答と組み合わせて、クエリがプロキシモデル インスタンスを返すようにする必要がありました。前の例に適応したコードの簡略化されたバージョンは次のようになります。

from django.db.models.base import ModelBase

class InheritanceMetaclass(ModelBase):
    def __call__(cls, *args, **kwargs):
        obj = super(InheritanceMetaclass, cls).__call__(*args, **kwargs)
        return obj.get_object()

class Animal(models.Model):
    __metaclass__ = InheritanceMetaclass
    type = models.CharField(max_length=255)
    object_class = models.CharField(max_length=20)

    def save(self, *args, **kwargs):
        if not self.object_class:
            self.object_class = self._meta.module_name
        super(Animal, self).save( *args, **kwargs)

    def get_object(self):
        if self.object_class in SUBCLASSES_OF_ANIMAL:
            self.__class__ = SUBCLASSES_OF_ANIMAL[self.object_class]
        return self

class Dog(Animal):
    class Meta:
        proxy = True
    def make_sound(self):
        print "Woof!"


class Cat(Animal):
    class Meta:
        proxy = True
    def make_sound(self):
        print "Meow!"


SUBCLASSES_OF_ANIMAL = dict([(cls.__name__, cls) for cls in ANIMAL.__subclasses__()])

このプロキシ アプローチの利点は、新しいサブクラスの作成時にデータベースの移行が必要ないことです。欠点は、特定のフィールドをサブクラスに追加できないことです。

このアプローチについてフィードバックをいただければ幸いです。

于 2014-05-14T19:05:08.783 に答える
5

人類に知られている唯一の方法は、メタクラス プログラミングを使用することです。

ここに短い答えがあります:

from django.db.models.base import ModelBase

class InheritanceMetaclass(ModelBase):
    def __call__(cls, *args, **kwargs):
        obj = super(InheritanceMetaclass, cls).__call__(*args, **kwargs)
        return obj.get_object()

class Animal(models.Model):
    __metaclass__ = InheritanceMetaclass
    type = models.CharField(max_length=255)
    object_class = models.CharField(max_length=20)

    def save(self, *args, **kwargs):
        if not self.object_class:
            self.object_class = self._meta.module_name
        super(Animal, self).save( *args, **kwargs)
    def get_object(self):
        if not self.object_class or self._meta.module_name == self.object_class:
            return self
        else:
            return getattr(self, self.object_class)

class Dog(Animal):
    def make_sound(self):
        print "Woof!"


class Cat(Animal):
    def make_sound(self):
        print "Meow!"

そして望ましい結果:

shell$ ./manage.py shell_plus
From 'models' autoload: Animal, Dog, Cat
Python 2.6.5 (r265:79063, Apr 16 2010, 13:57:41) 
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> dog1=Dog(type="Ozzie").save()
>>> cat1=Cat(type="Kitty").save()
>>> dog2=Dog(type="Dozzie").save()
>>> cat2=Cat(type="Kinnie").save()
>>> Animal.objects.all()
[<Dog: Dog object>, <Cat: Cat object>, <Dog: Dog object>, <Cat: Cat object>]
>>> for a in Animal.objects.all():
...    print a.type, a.make_sound()
... 
Ozzie Woof!
None
Kitty Meow!
None
Dozzie Woof!
None
Kinnie Meow!
None
>>> 

それはどのように機能しますか?

  1. 動物のクラス名に関する情報を保存します - そのために object_class を使用します
  2. 「プロキシ」メタ属性を削除します - Django で関係を逆にする必要があります (これの悪い面は、子モデルごとに追加の DB テーブルを作成し、そのための追加の DB ヒットを無駄にします。良い面は、いくつかの子モデルに依存するフィールドを追加できることです)。
  3. アニマルの save() をカスタマイズして、save を呼び出したオブジェクトの object_class にクラス名を保存します。
  4. object_class にキャッシュされた名前の Model を Django の逆リレーションで参照するには get_object メソッドが必要です。
  5. この .get_object() の「キャスト」は、Animal モデルの Metaclass を再定義して Animal がインスタンス化されるたびに自動的に行われます。メタクラスは、クラスのテンプレートのようなものです (クラスがオブジェクトのテンプレートであるように)。

Python のメタクラスについて詳しくは、http ://www.ibm.com/developerworks/linux/library/l-pymeta.html をご覧ください。

于 2011-03-13T23:30:53.520 に答える
2

ここで説明されているアプローチを使用して、おそらく Django モデルをポリモーフィックにすることができます。そのコードは開発の初期段階にあると思いますが、調査する価値はあります。

于 2010-02-07T23:56:29.953 に答える
0

この回答は、プロキシ モデルを使用していないため、質問を多少回避している可能性があります。ただし、質問が尋ねるように、次のように記述できます(Animal新しいタイプが追加された場合にクラスを更新する必要はありません)--

animals = Animal.objects.all()
for animal in animals:
    animal.make_sound()

メタクラス プログラミングを回避するために、継承より合成を使用できます。例えば ​​-

class Animal(models.Model):

    type = models.CharField(max_length=255)

    @property
    def type_instance(self):
        """Return a Dog or Cat object, etc."""
        return globals()[self.type]()

    def make_sound(self):
        return self.type_instance.make_sound()

class Dog(object):
    def make_sound(self):
        print "Woof!"

class Cat(object):
    def make_sound(self):
        print "Meow!"

DogおよびCatクラスがインスタンスにアクセスする必要がある場合は、上記Animalのメソッドを調整して、type_instance()必要なものをクラス コンストラクターに渡すこともできます (例: self)。

于 2014-02-22T18:16:29.403 に答える