Django - 检查多表继承Queryset的类型

时间:2011-11-30 22:38:37

标签: python django database-design

我正在尝试在我的数据库中保存一种目录结构。简化示例:

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错误。 / p>

我一直在寻找这类问题的替代方案,而且我正在浏览contenttypes包中的Generic Foreign Keys。然而,这似乎会让Django Admin方面更加令人头疼。任何人都可以建议一个比上面更好的解决方案吗?

编辑:由于order字段,我避免了抽象继承。我必须一起加入两个QuerySet并按顺序对它们进行排序

4 个答案:

答案 0 :(得分: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

答案 1 :(得分: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)

如果您正在浏览“基础”对象,我认为此解决方案非常适合使用。

答案 2 :(得分: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

现在,这不是很漂亮,但它从中心位置返回子类实例,所以我不需要用try包装其他语句。也许可以避免子类反向管理器名称的硬编码,但这种方法足以满足我的需求。

答案 3 :(得分: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}}

结果是我只使用了两个查询,一个用于获取SectionClickable个对象,另一个用于SectionHeading个对象