我正在尝试在我的数据库中保存一种目录结构。简化示例:
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并按顺序对它们进行排序
答案 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)
虽然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
个对象