2008-11-23 93 views
4

(Django的1.x中,巨蟒的2.6.x)Django管理界面不會使用子類的__unicode __()

我有模型的調整:我沒有實例化動物

class Animal(models.Model): 
    pass 

class Cat(Animal): 
    def __unicode__(self): 
    return "This is a cat" 

class Dog(Animal): 
    def __unicode__(self): 
    return "This is a dog" 

class AnimalHome(models.Model): 
    animal = models.ForeignKey(Animal) 

,因爲這應該是一個虛擬的類。我已經實例化貓和狗,但在AnimalHome的管理頁面中,我選擇的動物顯示爲「動物對象」(我猜是默認的__unicode __()),而不是我爲這兩個子類定義的__unicode__。幫幫我。


抽象基類問題是一個紅鯡魚WRT對這個問題,我想。即使Animal不應該是抽象的,但我仍然有這樣的問題:出於某種原因,由於ForeignKey是在Animal上定義的,而不是它的一個子類,所以超類方法被調用而不是子類。在調用object.method()時,在OO編程中,您應該獲得最低子類的實現,並且您必須做額外的工作才能獲得任何超類的實現。那麼爲什麼在子類上定義__unicode__是不夠的 - 實際上問題可能是__unicode__根本沒有被調用,因爲內省對Animal類的反映顯示它沒有被定義。所以也許如果我爲Animal定義__unicode__並讓它調用子類'__unicode__我可以得到想要的效果。


好吧,我認爲我理解ORM問題。這兩個答案幫助我理解了這一點,謝謝。在實驗中,我發現當Django保存一個子類模型時,它會做兩件事:(1)它爲超類表中的子類對象創建一行;(2)它使得子類表中的PK與在超類表中分配的PK。子類表中的這個PK被命名爲superclass_ptr。基於此,我編制了以下內容。我會很感激反饋。

Class Animal(models.Model) 
    def __unicode__(self): 
    if Dog.objects.filter(pk=self.pk).count() > 0: 
     return unicode(Dog.objects.get(pk=self.pk)) 
    elif Cat.objects.filter(pk=self.pk).count() > 0: 
     return unicode(Cat.objects.get(pk=self.pk)) 
    else: 
     return "An Animal!" 

看來,勞倫斯是最上點WRT這個問題。貓和狗將擁有不相交的PK集(動物的任何子類都將擁有與其超類記錄相同的PK),但不幸的是Django並沒有在幕後執行任何工作:「我是一隻動物,我知道動物有狗和貓的亞類,具體來說,我是動物3號,而且我只查了一下,也有貓3號,這意味着我實際上是貓3號。儘管這看起來完全可能並且非常合理(因爲貓不會做任何動物本身無法做到的事情),因爲它使用Python的內省。謝謝你們。

+0

我不認爲你應該這樣編碼一個Django模型。嘗試以這樣一種方式定義數據模型,即可以爲所有動物使用一個表(即一個模型)。不要把決定因素放在你的代碼中。 如果你把你的對象醃成一個數據庫行,那麼你得到的東西像一個對象存儲。 – Ber 2008-11-24 07:44:21

回答

5

ForeignKey(Animal)就是這樣一個對Animal表中一行的外鍵引用。底層SQL模式中沒有任何內容表明該表正被用作超類,因此您可以獲取一個Animal對象。

要解決此問題:

首先,你要在基類是非抽象的。無論如何,這對於ForeignKey是必要的,並且還確保Dog和Cat將具有不相關的主鍵集合。

現在,Django使用OneToOneField實現繼承。因此,具有子類實例的基類實例獲取對該實例的引用,並進行適當命名。這意味着你可以這樣做:

class Animal(models.Model): 
    def __unicode__(self): 
     if hasattr(self, 'dog'): 
      return self.dog.__unicode__() 
     elif hasattr(self, 'cat'): 
      return self.cat.__unicode__() 
     else: 
      return 'Animal' 

這也回答了你的問題的誤碼率約的Unicode()這是依賴於其他子類的屬性。你現在實際上在子類實例上調用適當的方法。

現在,這的確表明,自從Django的已經在尋找幕後子類的實例,代碼可能只是走一路,並返回一個貓或狗實例,而不是動物。你必須向開發者提出這個問題。 :)

3

Django(和一般的關係數據庫)不能這樣工作。即使使用像Django這樣的ORM,你也不能像這樣使用類層次結構。

有你的問題,有兩種可能的解決方案:

(1)給予「名」 attibute的動物模型,然後從[「狗」,「貓」]名稱添加的實體。這將在外鍵選擇框中顯示動物的名字。

(2)如果你真的需要鏈接你的外鍵不同型號(這確實是不使用的RDBMS通常的方式),你應該在上CONTENTTYPES框架的文檔閱讀Generic Relations

不過,我的建議是(1)。

+0

如果我使用你的第一個建議,我該如何克服Django沒有使用子類的__unicode __()的事實?說狗有財產is_german_shepherd和狗的__unicode __()返回:「狗:(是德國牧羊犬?:%s)」%(「是」,如果is_german_shepherd其他「否」) – 2008-11-24 00:28:16

6

你想要一個Abstract base class( 「虛擬」 並不意味着在Python什麼。)

從文檔:

class CommonInfo(models.Model): 
    name = models.CharField(max_length=100) 
    age = models.PositiveIntegerField() 

    class Meta: 
     abstract = True 

編輯

「在面向對象編程的時候你調用object.method()你應該得到最低子類的實現。「

是的。但不是全部。

這不是OO問題。甚至是Python或Django的問題。這是一個ORM問題。

問題是「什麼對象在FK參考結束時重建?」答案是,如何處理從FK值到對象的轉換沒有標準的明顯答案。

我已經在AnimalHome有一排,animals的值爲42.它指的是Animal.objects.get(pk=42)。動物的哪個子類?貓?狗? ORM層如何知道它是否應該做Dog.objects.get(pk=42)Cat.objects.get(pk=42)

「但是等等,」你說。 「它應該抓取Animal對象,而不是Dog或Cat對象。」你可以期待這一點,但這不是Django ORM的工作原理。每個班級都是一個不同的表格。貓和狗 - 根據定義 - 分開的表格,帶有單獨的查詢。你沒有使用對象存儲。您正在將ORM用於關係表。


編輯

首先,你的查詢,如果狗和貓共用一個密鑰生成器,並且沒有一組重疊的PK年代纔有效。

如果你有42 PK一隻狗和與42 PK貓,你已經有了一個問題。由於您無法輕鬆控制密鑰生成,因此您的解決方案無法工作。

運行時間類型識別不好。它不是以多種方式面向對象的。爲避免RTTI,幾乎所有可以做的事都比不斷擴展的if語句序列來區分子類更好。

但是,你要建立模型 - 專 - 對ORM系統的病理問題。事實上,如此具體的病態,我幾乎願意打賭它是作業。 [純SQL系統也存在病理問題。他們往往顯示爲作業。]

的問題是,ORM不能做你認爲它應該做的。所以你有兩個選擇。

  • 停止使用Django。
  • 做一些Django直接做的事。
  • 歇OO設計指南和訴諸RTTI一樣易碎的東西,這使得它非常難以增加動物的一個小類。

考慮這樣做RTTI - 它包含類的名稱,以及在PK

KIND_CHOICES = (
    ("DOG", "Dog"), 
    ("CAT", "Cat"), 
) 

class Animal(models.Model): 
    kind = models.CharField(max_length= 1, choices=KIND_CHOICES) 
    fk = models.IntegerField() 
    def get_kind(self): 
     if kind == "DOG": 
      return Dog.objects.get(pk = fk) 
     elif kind == "CAT": 
      return Cat.objects.get(pk = fk) 
+0

謝謝。新的python。 – 2008-11-24 00:28:58

2

這是一起什麼美國洛特提出的方針,但沒有如果/ elif的/ ...,隨着您需要支持的子類數量的增長,這會變得越來越尷尬和難以維護。

class Cat(models.Model): 
    def __unicode__(self): 
     return u'A Cat!' 

class Dog(models.Model): 
    def __unicode__(self): 
     return u'A Dog!'   

class Eel(models.Model): 
    def __unicode__(self): 
     return u'An Eel!'   

ANIMALS = { 
    'CAT': {'model': Cat, 'name': 'Cat'}, 
    'DOG': {'model': Dog, 'name': 'Dog'}, 
    'EEL': {'model': Eel, 'name': 'Eel'}, 
} 
KIND_CHOICES = tuple((key, ANIMALS[key]['name']) for key in ANIMALS) 

class Animal(models.Model): 
    kind = models.CharField(max_length=3, choices=KIND_CHOICES) 
    fk = models.IntegerField() 
    def get_kind(self): 
     return ANIMALS[self.kind]['model'].objects.get(pk=self.fk) 
    def __unicode__(self): 
     return unicode(self.get_kind()) 

也可以用Django的多表繼承(搜索Django的文檔)完成一些非常相似的事情。例如:

ANIMALS = { 
    'CAT': {'model_name': 'Cat', 'name': 'Cat'}, 
    'DOG': {'model_name': 'Dog', 'name': 'Dog'}, 
    'EEL': {'model_name': 'Eel', 'name': 'Eel'}, 
} 
KIND_CHOICES = tuple((key, ANIMALS[key]['name']) for key in ANIMALS) 

class Animal(models.Model): 
    kind = models.CharField(max_length=3, choices=KIND_CHOICES) 
    def get_kind(self): 
     return getattr(self, ANIMALS[self.kind]['model_name'].lower()) 
    def __unicode__(self): 
     return unicode(self.get_kind()) 

class Cat(Animal): 
    def __unicode__(self): 
     return u'A Cat!' 

class Dog(Animal): 
    def __unicode__(self): 
     return u'A Dog!'   

class Eel(Animal): 
    def __unicode__(self): 
     return u'An Eel!'   

我個人比較喜歡第二種選擇,因爲每個子類的實例將所有的父類自動神奇,它允許更清晰,更簡潔的代碼定義的字段。 (例如,如果動物類有'性別'字段,那麼Cat.objects.filter(gender ='MALE')將起作用)。

1

對於一般的關係,注意,普通的Django查詢不能跨越GenerecForeignKey關係。使用多表繼承,由於它是一個不太通用的解決方案的成本避免了這個問題。

從文檔:

由於GenericForeignKey的實現方式,你不能直接使用的過濾器等領域(過濾器(),不包括()等)通過數據庫API。他們是不正常的領域對象。這些例子是行不通的:

# This will fail 
>>> TaggedItem.objects.filter(content_object=guido) 
# This will also fail 
>>> TaggedItem.objects.get(content_object=guido)