如何在不知道子类名称的情况下访问 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 查询?

如何在不使用自动布局的情况下设置 UISegmentedControl 子类的高度?

如何在不输入对象名称的情况下访问对象的数据?- JSON