2011-11-30 95 views
0

我試圖在數據庫中保存一種內容表結構。簡單的例子:Django - 檢查多表繼承查詢集的類型

models.py

class Section (models.Model): 
    title = models.CharField(max_length=80) 
    order = models.IntegerField() 

class SectionClickable(Section): 
    link = models.CharField(max_length=80) 

class SectionHeading(Section): 
    background_color = models.CharField(max_length=6) 

views.py

sections = Section.objects.filter(title="Hello!") 
for section in sections: 
     if(section.sectionheading): 
      logger.debug("It's a heading") 

我需要做一些處理操作,如果它是一個SectionHeading實例,但(如Django的手冊),訪問如果對象不是類型SectionHeading,則section.sectionheading將引發DoesNotExist錯誤。

我一直在尋找這類問題的替代品,並且我正在瀏覽contenttypes包中的通用外鍵。但是,這似乎會在Django Admin方面造成更多麻煩。任何人都可以提供比上述更好的解決方案嗎?

編輯:由於order字段,我避免了抽象繼承。我將不得不在兩個查詢集連接在一起,並通過訂單

+0

你怎麼關聯'Section's和'SectionHeading's? – second

+0

SectionHeading是Section – bcoughlan

+0

的子類啊,道歉,沒注意 – second

回答

2

以及對它們進行排序,你可以檢查類型:

if isinstance(section, SectionHeading) 

duck typing一般最好

編輯:

實際,那可能不起作用。該對象將是一個Section。但你可以看看屬性:

if hasattr(section, 'sectionheading') 

try: 
    do_something_with(section.sectionheading) 
except AttributeError: 
    pass # i guess it wasn't one of those 
0

我一直在使用類似於second在他編輯提示了一句:

class SomeBaseModel(models.Model): 
    reverse_name_cache = models.CharField(_('relation cache'), max_length=10, 
              null=True, editable=False) 

    def get_reverse_instance(self): 
     try: 
      return getattr(self, self.reverse_name_cache) 
     except AttributeError: 
      for name in ['sectionclickable', 'sectionheading']: 
       try: 
        i = getattr(self, name) 
        self.reverse_name_cache = name 
        return i 
       except ObjectDoesNotExist: 
        pass 

現在,這」不是個t非常漂亮,但它從中心位置返回子類實例,所以我不需要用try來包裝其他語句。也許可以避免子類逆向管理器名稱的硬編碼,但這種方法足以滿足我的需求。

1

我想出了利用該解決方案涉及指向(相當有用)的額外字段ContentType類:

class Section(models.Model): 
    name = models.CharField(max_length=50) 
    content_type = models.ForeignKey(ContentType,editable=False,null=True) 

    def __unicode__(self): 
     try: 
      return self.as_leaf_class().__unicode__() 
     except: 
      return self.name 

    def save(self, *args, **kwargs): 
     if(not self.content_type): 
      self.content_type = ContentType.objects.get_for_model(self.__class__) 
     super(Section, self).save(*args, **kwargs) 

    def as_leaf_class(self): 
     content_type = self.content_type 
     model = content_type.model_class() 
     if(model == Section): 
      return self 
     return model.objects.get(id=self.id) 

如果你想通過「基地」對象,我認爲這個解決方案是相當不錯的,並舒適的工作。

0

OP在這裏。

儘管second的回答對於這個問題是正確的,但我想補充一點,我認爲對於這種情況,多表繼承是一種效率低下的方法。訪問子類模型的屬性將導致查詢發生 - 因此需要對返回的每一行進行查詢。哎喲。據我所知,select_related不適用於多表繼承。

我也排除了ContentTypes,因爲它不夠高雅,而且似乎也需要很多查詢。

我定居在使用抽象類:

class Section (models.Model): 
    title = models.CharField(max_length=80) 
    order = models.IntegerField() 

    class Meta: 
     abstract=True 
     ordering=['order'] 

所查詢兩個表:

section_clickables = SectionClickable.objects.filter(video=video) 
section_headings= SectionHeading.objects.filter(video=video) 

,並加入了兩個查詢集在一起

#Join querysets http://stackoverflow.com/questions/431628/how-to-combine-2-or-more-querysets-in-a-django-view 
s = sorted(chain(section_headings, section_clickables), key=attrgetter('order')) 

最後我做了一個模板標籤檢查實例:

from my.models import SectionHeading, SectionClickable 

@register.filter() 
def is_instance(obj, c): 
    try: 
     return isinstance(obj, eval(c)) 
    except: 
     raise ObjectDoesNotExist('Class supplied to is_instance could not be found. Import it in the template tag file.') 

,這樣在我的模板(HamlPy)我可以這樣做:

- if s|is_instance:"SectionClickable" 
    %span {{s.title}} 
- if s|is_instance:"SectionHeading" 
    %span{'style':'color: #{{s.color}};'} 
     {{s.title}} 

的結果是,我只用了兩個疑問,一是得到了SectionHeading對象SectionClickable對象,一個