在 Django 中,当您有一个父类和多个从它继承的子类时,您通常会通过 parentclass.childclass1_set 或 parentclass.childclass2_set 访问一个子类,但是如果我不知道我想要的特定子类的名称怎么办?
有没有办法在不知道子类名的情况下获取父->子方向的相关对象?
在 Django 中,当您有一个父类和多个从它继承的子类时,您通常会通过 parentclass.childclass1_set 或 parentclass.childclass2_set 访问一个子类,但是如果我不知道我想要的特定子类的名称怎么办?
有没有办法在不知道子类名的情况下获取父->子方向的相关对象?
(更新:对于 Django 1.2 和更新版本,它可以在反向 OneToOneField 关系中跟踪 select_related 查询(因此向下继承层次结构),有一种更好的技术可用,它不需要real_type
在父模型上添加字段。它可以作为InheritanceManager在django-model-utils项目。)
执行此操作的常用方法是在 Parent 模型上向 ContentType 添加一个 ForeignKey,该模型存储正确的“叶”类的内容类型。如果没有这个,您可能必须对子表执行大量查询才能找到实例,具体取决于您的继承树有多大。以下是我在一个项目中的做法:
from django.contrib.contenttypes.models import ContentType
from django.db import models
class InheritanceCastModel(models.Model):
"""
An abstract base class that provides a ``real_type`` FK to ContentType.
For use in trees of inherited models, to be able to downcast
parent instances to their child types.
"""
real_type = models.ForeignKey(ContentType, editable=False)
def save(self, *args, **kwargs):
if self._state.adding:
self.real_type = self._get_real_type()
super(InheritanceCastModel, self).save(*args, **kwargs)
def _get_real_type(self):
return ContentType.objects.get_for_model(type(self))
def cast(self):
return self.real_type.get_object_for_this_type(pk=self.pk)
class Meta:
abstract = True
这被实现为抽象基类以使其可重用;您还可以将这些方法和 FK 直接放在特定继承层次结构中的父类中。
如果您无法修改父模型,此解决方案将不起作用。在这种情况下,您几乎无法手动检查所有子类。
在 Python 中,给定一个(“新式”)类 X,您可以使用 获取它的(直接)子类X.__subclasses__()
,它返回一个类对象列表。(如果您想要“进一步的后代”,您还必须调用__subclasses__
每个直接子类等 - 如果您需要有关如何在 Python 中有效地做到这一点的帮助,请问!)。
一旦您以某种方式确定了一个感兴趣的子类(可能是所有子类,如果您想要所有子子类的实例等),getattr(parentclass,'%s_set' % childclass.__name__)
应该会有所帮助(如果子类的名称是'foo'
,这就像访问parentclass.foo_set
- 不多也不少)。同样,如果您需要澄清或示例,请询问!
Carl 的解决方案是一个很好的解决方案,如果有多个相关的子类,这是一种手动执行的方法:
def get_children(self):
rel_objs = self._meta.get_all_related_objects()
return [getattr(self, x.get_accessor_name()) for x in rel_objs if x.model != type(self)]
它使用 _meta 中的一个函数,不能保证随着 django 的发展而稳定,但它可以解决问题,并且可以在需要时即时使用。
您可以为此使用django-polymorphic。
它允许自动将派生类转换回它们的实际类型。它还提供 Django 管理支持、更高效的 SQL 查询处理以及代理模型、内联和表单集支持。
基本原则似乎被重新发明了很多次(包括 Wagtail 的.specific
,或本文中概述的示例)。然而,它需要更多的努力,以确保它不会导致 N 查询问题,或者与管理员、表单集/内联或第三方应用程序很好地集成。
这是我的解决方案,它再次使用_meta
,因此不能保证稳定。
class Animal(models.model):
name = models.CharField()
number_legs = models.IntegerField()
...
def get_child_animal(self):
child_animal = None
for r in self._meta.get_all_related_objects():
if r.field.name == 'animal_ptr':
child_animal = getattr(self, r.get_accessor_name())
if not child_animal:
raise Exception("No subclass, you shouldn't create Animals directly")
return child_animal
class Dog(Animal):
...
for a in Animal.objects.all():
a.get_child_animal() # returns the dog (or whatever) instance
您可以通过查找作为 django.db.models.fields.related.RelatedManager 实例的父级中的所有字段来实现此目的。从您的示例看来,您所谈论的子类似乎不是子类。对?
可以在此博客文章中找到使用代理的替代方法。与其他解决方案一样,它也有其优点和缺点,这些都很好地放在了文章的末尾。