如何在不知道子类名称的情况下访问 django 中对象的子类?
Posted
技术标签:
【中文标题】如何在不知道子类名称的情况下访问 django 中对象的子类?【英文标题】:How do I access the child classes of an object in django without knowing the name of the child class? 【发布时间】:2010-10-30 01:34:16 【问题描述】:在 Django 中,当您有一个父类和多个从其继承的子类时,您通常会通过 parentclass.childclass1_set 或 parentclass.childclass2_set 访问一个子类,但如果我不知道特定子类的名称怎么办我要吗?
有没有办法在不知道子类名的情况下获取父->子方向的相关对象?
【问题讨论】:
@S.Lott 这类回复真的过时了。仅仅因为你想不出一个用例并不意味着提问者没有。如果您将子类化用于任何类型的多态行为(您知道,OOP 的主要好处之一是?)这个问题是非常自然且明显的必要性。 @S.Lott 在这种情况下,请随意练习一些不粗鲁的版本,例如“我不确定我是否理解上下文。你能解释一下你的用例吗?” 【参考方案1】:您可以通过查找父项中所有作为 django.db.models.fields.related.RelatedManager 实例的字段来实现此目的。从您的示例看来,您所谈论的子类似乎不是子类。对吧?
【讨论】:
【参考方案2】:在 Python 中,给定一个(“新式”)类 X,您可以使用 X.__subclasses__()
获取它的(直接)子类,它返回一个类对象列表。 (如果你想要“更多的后代”,你还必须在每个直接子类上调用__subclasses__
,等等——如果你需要关于如何在 Python 中有效地做到这一点的帮助,请问!)。
一旦您以某种方式确定了一个感兴趣的子类(可能是所有子类,如果您想要所有子子类的实例等),getattr(parentclass,'%s_set' % childclass.__name__)
应该会有所帮助(如果子类的名称是 'foo'
,这只是就像访问parentclass.foo_set
——不多也不少)。同样,如果您需要澄清或示例,请询问!
【讨论】:
这是很好的信息(我不知道 subclasses),但我相信这个问题实际上更具体到 Django 模型如何实现继承。如果您查询“父”表,您将得到一个父实例。它可能事实上是 SomeChild 的一个实例,但 Django 不会自动为您解决这个问题(可能很昂贵)。您可以通过 Parent 实例上的属性访问 SomeChild 实例,但前提是您已经知道您想要的是 SomeChild,而不是 Parent 的其他子类。 抱歉,当我说“可能事实上是 SomeChild 的一个实例”时不清楚。您拥有的对象是 Python 中 Parent 的一个实例,但它可能在 SomeChild 表中有一个相关条目,这意味着您可能更喜欢将它作为 SomeChild 实例使用。 这很有趣...当时我不需要这些具体信息,但我只是在考虑一个不同的问题,结果证明这正是我所需要的,所以再次感谢您! 这是真实的答案。说到底,Django 只是 Python。【参考方案3】:(更新:对于 Django 1.2 和更新版本,它可以在反向 OneToOneField 关系中跟踪 select_related 查询(从而向下继承层次结构),有一种更好的技术可用,不需要添加 @987654323父模型上的 @ 字段。它在 django-model-utils 项目中以 InheritanceManager 的形式提供。)
执行此操作的常用方法是在 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 直接放在特定继承层次结构中的父类中。
如果您无法修改父模型,此解决方案将不起作用。在这种情况下,您几乎无法手动检查所有子类。
【讨论】:
谢谢。这很漂亮,绝对节省了我的时间。 这非常有帮助,但我想知道您为什么要使用 null=True 定义 FK。我们或多或少地复制了代码,并且被一个错误所困扰,如果 FK 是强制性的,这个错误很容易被检测到并解决(还要注意 cast() 方法将其视为强制性的)。 @ShaiBerger 很好的问题。三年多后,我不知道为什么原来是这样 :-) 编辑删除 null=True。 @sunprophit 您需要更仔细地重新阅读我的回复。是的,当然您可以使用real_type
字段(在上面的代码中作为cast()
方法)来执行self.child_object()
,并且对于一个实例只需要一个查询。但是如果你有一个充满实例的查询集,那就变成了 N 个查询。在单个查询中获取整个对象查询集的子类数据的唯一方法是使用 InheritanceManager 所做的连接。
确实是我的错,真丢脸。感谢您注意到它并修复它。【参考方案4】:
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 的发展而稳定,但它可以解决问题,并且可以在需要时即时使用。
【讨论】:
【参考方案5】:原来我真正需要的是这个:
Model inheritance with content type and inheritance-aware manager
这对我来说非常有效。不过,感谢其他所有人。看了你的回答,我学到了很多!
【讨论】:
【参考方案6】:可以在this blog post 中找到使用代理的替代方法。与其他解决方案一样,它也有其优点和缺点,这些都很好地放在了文章的末尾。
【讨论】:
【参考方案7】:这是我的解决方案,它再次使用_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
【讨论】:
【参考方案8】:您可以为此使用django-polymorphic。
它允许自动将派生类转换回它们的实际类型。它还提供 Django 管理支持、更高效的 SQL 查询处理以及代理模型、内联和表单集支持。
基本原则似乎被重新发明了很多次(包括 Wagtail 的 .specific
,或本文中概述的示例)。然而,要确保它不会导致 N 查询问题,或者与管理员、表单集/内联或第三方应用程序很好地集成,需要付出更多的努力。
【讨论】:
这看起来不错,我想试试。迁移时只需使用适当的内容类型填充 polymorphic_ctype 字段就足够了(在南迁移中)? @joshua:是的,这正是你唯一需要做的事情。 您可能会通过解释与 django-model-utils 中采用的方法的区别来充实您的答案(请参阅 Carl Meyer 的回答)。 接受的答案已过时,这是现在最好的答案。以上是关于如何在不知道子类名称的情况下访问 django 中对象的子类?的主要内容,如果未能解决你的问题,请参考以下文章
如何在不将 nib 名称指定为字符串的情况下使用 nib 加载 View Controller 子类?
在不使用上下文处理器的情况下访问 django 1.7 模板中的 URL 参数
如何在不使用字段名的情况下运行 django orm 查询?