2010-02-07 86 views
24

說我有型號:在Django中從基礎模型實例返回代理模型實例的正確方法?

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實例,但我想我可以在返回cat或dog模型的主類上定義一個「get_proxy_model」方法。

當然,你可以做到這一點,並傳遞一些像主鍵,然後只是做Cat.objects.get(pk = passed_in_primary_key)。但這意味着要對已有的數據進行額外的查詢,這似乎是多餘的。有沒有什麼辦法可以有效地將動物變成貓或狗的實例?什麼是正確的方式來做我想達到的目標?

+0

您可以應用make_sound動物模型,並添加聲音= models.charField()到它。 – monkut 2010-02-08 01:22:54

+2

我的例子很簡單 - 我想要做的事情需要一堆取決於類型的工作,並且不能與模型一起存儲。 – sotangochips 2010-02-08 04:26:23

回答

1

你也許可以使用here描述的方法使Django模型變爲多態。我相信這些代碼處於開發的早期階段,但值得研究。

4

人類已知的唯一方法是使用Metaclass編程。

這裏是簡短的回答:

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. 自定義保存()動物拯救類 調用保存的對象 的object_class中的名稱。
  4. 方法get_object需要通過在Django 中通過反向關係引用 與名稱緩存在 object_class中的Model。
  5. 做這個.get_object()自動「投射」 每次動物被實例化 重新定義動物的Metaclass 模型。 Metaclass就像一個 類的模板(就像 類是一個對象的模板)。關於元類Python中

的更多信息:http://www.ibm.com/developerworks/linux/library/l-pymeta.html

0

這個答案可能是側步有些問題,因爲它不使用代理模式。然而,因爲這個問題問,它讓一個編寫以下(而無需更新Animal類,如果添加了新的類型) -

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

爲了避免元類編程,可以使用composition over inheritance。對於example--

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!" 

如果DogCat類需要訪問Animal情況下,你也可以調整上述type_instance()方法傳遞它所需要的類的構造函數(例如,self)。

7

通過thedk提出的元類的做法的確是去一個非常強大的方式,但是,我不得不把它與答案相結合的問題here有查詢返回代理模型實例。適應於前面的示例代碼的簡化版本是:

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__()]) 

這種代理方法的優點是沒有DB遷移在創建新的子類必須的。缺點是沒有特定的字段可以添加到子類中。

我很高興能有這種方法的反饋。

+0

偉大的工作,不知道這是否反抗真實生活測試。 – eugene 2016-04-29 10:53:12

+1

我認爲將SUBCLASSES_OF_ANIMAL註冊移動到元類「__init__」會很方便 – eugene 2016-04-29 11:16:19

相關問題