2011-06-10 54 views
7

假設一組簡單的繼承模型類,像這樣:如何從模型庫到Django的派生類?

class BaseObject(models.Model): 
    some_field = models.SomeField(...) 

class AwesomeObject(BaseObject): 
    awesome_field = models.AwesomeField(...) 

class ExcellentObject(BaseObject): 
    excellent_field = models.ExcellentField(...) 

和看起來像這樣的查詢:

found_objects = BaseObject.objects.filter(some_field='bogus') 

什麼是採取每個found對象,並把它放回的最佳方式它是派生類?我現在使用的代碼是這樣的:

for found in found_objects: 
    if hasattr(found, 'awesomeobject'): 
     ProcessAwesome(found.awesomeobject) 
    elif hasattr(found, 'excellentobject'): 
     ProcessExcellent(found.excellentobject): 

但是,感覺就像這是濫用「hasattr」。有沒有更好的方式來做到這一點,而不需要在基類中創建一個明確的「類型」字段?

回答

2

這是我知道的最好的方法。不幸的是,在這方面繼承有點笨拙。多表繼承基本上只是父模型和子項添加的額外字段之間的一對一關係,這就是爲什麼hasattr技巧可行。您可以將其中的每一個視爲您父級模型上的OneToOneField屬性。當你這麼想的時候,Django無法知道哪個孩子會回來,甚至不知道要回到孩子身上,所以你必須自己處理這個邏輯:

我傾向於在父類上創建一個方法,例如get_child,它只是通過屬性週期,並返回彈出一個:

class BaseObject(models.Model): 
    some_field = models.SomeField(...) 

    def get_child(self): 
     if hasattr(self, 'awesomeobject'): 
      return ProcessAwesome(found.awesomeobject) 
     elif hasattr(self, 'excellentobject'): 
      return ProcessExcellent(found.excellentobject): 
     else: 
      return None 

至少這樣,你可以叫found.get_child(),也許忘了,讓你在那裏的兩輪牛車。

+1

那麼..最好看看django-polymorphic ;-) – vdboor 2012-07-10 12:48:34

2

我使用自省;

class Base(models.Model): 
[ we have some unique 'key' attribute ] 
class_name = models.CharField(..., editable=False) 

def get_base(self): 
    if self.__class__ == Base: 
     return self 
    # if we are not an instance of Base we 'go up' 
    return Base.objects.get(key=self.key) 

def get_specific(self): 
    if self.__class__ != Base: 
     return self 
    # if we are an instance of Base we find the specific class 
    class_type = getattr(sys.modules["project.app.models"], 
     self.class_name) 
    return class_type.objects.get(key=self.key) 

你需要一些工廠來創建特定的類,所以你一定要正確保存STR(自我。)在CLASS_NAME

+0

是的,存儲self.class_name是我試圖避免... – slacy 2011-06-10 20:37:32

3

從基類會派生類通常是一個標誌一個程序中糟糕的設計。您提出的方法,使用hasattr,可能是一個嚴重的問題。我會告訴你:

# defined in some open source library 
class MyObject(object): 
    def what_is_derived(self): 
     if hasattr(self, 'derived1'): 
      return 'derived1' 
     elif hasattr(self, 'derived2'): 
      return 'derived2' 
     else: 
      return 'base' 

讓我們假設類Derived1Derived2在同一庫中定義。現在,您想要使用MyObject的功能,所以您可以使用自己的代碼從中派生出來。

# defined in your own code 
class MyBetterObject(MyObject): 
    pass 

better_object = MyBetterObject() 
better_object.what_is_derived() # prints 'base' 

多態性的要點在於,您可以擁有許多派生類,而無需更改基類。通過讓基類知道它的所有派生類,就會嚴重降低這個類的有用性。不能在不更改基類的情況下創建派生類。

要麼你想使用派生類,要麼你不關心具體類是什麼,你需要的只是基類的屬性/方法。所有OOP語言都是一樣的。有設備可以找出派生類是什麼,但通常這是一個壞主意。

從Django模型的角度來看,我通常會以這樣的方式使用繼承:

class Address(models.Model): 
    # fields... 

class Person(Address): 
    # fields... 

class Business(Address): 
    # fields... 

Address.objects.all() # find all addresses for whatever reason 
Person.objects.all() # im only interested in people 
Business.objects.all() # need to work with businesses 

# need to show all addresses in a postcode, and what type of address they are? 
businesses = Business.objects.filter(postcode='90210') 
people = Person.objects.filter(postcode='90210') 
# use the address properties on both 

與Django模型深度嵌套繼承鏈是尷尬。在大多數情況下,它們也相當不必要。用hasattr檢查來代替污染你的基類,定義一個輔助方法,該方法能夠查詢所需的派生類,如果需要這樣的事情。只是不要在Base類中定義它。

+1

「從...一個派生類的基類通常是程序中糟糕設計的標誌。「胡說些什麼。從基類轉到派生類稱爲多態,是面向對象的基礎。只是在這個特定的情況下,它沒有黑客就無法工作。 – jwg 2014-08-26 17:42:21

+1

@jwg你讀過我寫的其餘部分,還是隻關注那個特定的句子?多態性在您擁有樹中常見的方法/屬性時起作用。如果您需要派生屬性,那麼您應該使用派生模型,而不是基礎模型。 Django多表模型繼承是多態性的一個很差的近似,並且在我看來,(幾乎)所有的django繼承應該用抽象基礎來完成。 – 2014-08-27 03:26:34

+1

而且..就這麼清楚。 「從基礎班到派生班通常是程序設計不好的標誌。」 - 向下轉換爲派生類時。使用多態性動態分派到子類方法或屬性是很好的。 – 2014-08-27 03:27:50

4

對於這個特定的問題,有django-polymorphic。它通過使用Django中的內容類型框架來存儲派生表指向的模型ID。當您評估查詢集時,它會將所有模型的特定類型上傳。

您將獲得:

>>> BaseProject.objects.all() 
[ <AwesomeObject>, <ExcellentObject>, <BaseObject>, <AwesomeObject> ] 
+0

好吧,我可以看到你可以使用管理器方法來獲得派生類實例。作者從父模型實例到派生模型實例的問題如何? django-polymorphic是否也提供瞭解決方案? – eugene 2015-03-18 02:01:38

+1

是的,單個模型也有'get_real_instance()'方法。大多數時候你不需要它,因爲經理已經給出派生模型。此外,它還通過在單個查詢中獲取同一類型的多個對象來避免N-query問題。 – vdboor 2015-03-20 12:41:10

0

您還可以的情況下,使用InheritanceQuerySetdjango-model-utils要明確說明哪些查詢影響,就像這樣:

from model_utils.managers import InheritanceQuerySet 

class UserManager([...]): 

    def get_queryset(self): 
     return InheritanceQuerySet(self.model).select_subclasses() 

(代碼https://stackoverflow.com/a/25108201