如何在不知道子类名称的情况下访问Django对象的子类?


80

在Django中,当您有一个父类和从其继承的多个子类时,通常可以通过parentclass.childclass1_set或parentclass.childclass2_set访问一个子类,但是如果我不知道我想要的特定子类的名称怎么办?

有没有一种方法可以在不知道子类名称的情况下沿parent-> child方向获取相关对象?


79
@ S.Lott这些反应真的很老。仅仅因为您无法想到用例,并不意味着要求者没有一个。如果您将子类用于任何类型的多态行为(您知道,OOP的主要假定好处之一?),那么这个问题是非常自然且显而易见的必要性。
卡尔·梅耶

82
@ S.Lott在这种情况下,请随意练习一些不礼貌的版本,例如“我不确定我是否了解上下文。您能解释一下用例吗?”
卡尔·迈尔

Answers:


84

更新:对于Django 1.2及更高版本,它可以在反向的OneToOneField关系中遵循select_related查询(从而实现向下的继承层次结构),有一种更好的技术,它不需要real_type在父模型上添加字段。它可以在InheritanceManager中使用django-model-utils项目。)

这样做的通常方法是将ParentKey添加到Parent模型的ContentType上,该模型存储正确的“ leaf”类的内容类型。否则,您可能必须对子表进行大量查询才能找到实例,具体取决于继承树的大小。这是我在一个项目中做的事情:

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。我们按原样或多或少地复制了代码,并且被一个bug咬住了,如果FK是强制性的,将很容易检测到并解决该错误(还请注意,cast()方法将其视为强制性)。
Shai Berger

1
@ShaiBerger很好的问题。三年多以后,我不知道为什么会这样:-)编辑以删除null = True。
卡尔·梅耶

2
@sunprophit您需要更仔细地阅读我的回复。是的,您当然可以self.child_object()使用real_type字段(在上面的代码中作为cast()方法),并且只需要对一个实例进行一次查询。但是,如果您有一个充满实例的查询集,那么它将成为N个查询。在单个查询中获取整个对象查询集的子类数据的唯一方法是使用InheritanceManager所做的联接。
卡尔·梅耶

1
的确,我的错误使我感到羞耻。感谢您注意到并修复它。
Antoine Pinsard

22

在Python中,给定(“新样式”)类X,可以使用来获得其(直接)子类X.__subclasses__(),该子类返回类对象的列表。(如果需要“其他__subclasses__子类”,则还必须调用每个直接子类,等等,如果需要帮助,以帮助您有效地在Python中做到这一点,请问!)。

一旦以某种方式确定了感兴趣的子类(可能是所有子类,如果要所有子类的实例,等等),就getattr(parentclass,'%s_set' % childclass.__name__)应该有所帮助(如果子类的名称是'foo',这就像访问parentclass.foo_set-不多也不少) )。同样,如果您需要澄清或示例,请询问!


1
这是个很棒的信息(我不知道子类),但是我认为这个问题实际上更具体于Django模型如何实现继承。如果查询“父母”表,则将返回“父母”的实例。实际上,它可能是SomeChild的一个实例,但是Django不会自动为您找出答案(可能会很昂贵)。您可以通过Parent实例上的属性进入SomeChild实例,但前提是您已经知道它是SomeChild想要的,而不是Parent的某些其他子类。
卡尔·梅耶

2
抱歉,我说“实际上可能是SomeChild的一个实例”还不清楚。您拥有的对象是Python中Parent的实例,但在SomeChild表中可能具有相关的条目,这意味着您可能更喜欢将其作为SomeChild实例使用。
卡尔·迈尔

真有趣……我当时不需要此特定信息,但是我只是在考虑一个不同的问题,事实证明这正是我所需要的,再次感谢您!
加布里埃尔·赫利

这是真正的答案。Django最终还是Python。
snakesNbronies

5

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的发展而保持稳定,但是它可以解决问题,并且可以根据需要即时使用。



4

您可以为此使用django-polymorphic

它允许将派生类自动转换回其实际类型。它还提供Django管理员支持,更有效的SQL查询处理以及代理模型,内联和表单集支持。

基本原理似乎已被多次改造(包括Wagtail的方法.specific,或本文中概述的示例)。但是,要确保不会导致N-query问题,或者与管理员,表单集/内联或第三方应用很好地集成,需要花费更多的精力。


1
这看起来不错,我想尝试一下。迁移时,仅用适当的contenttypes填充polymorphic_ctype字段就足够了(在向南迁移中)?
约书亚

1
@joshua:是的,这正是您唯一要做的事情。
vdboor

您可以通过解释与django-model-utils中使用的方法的区别来充实您的答案(请参阅Carl Meyer的答案)。
瑞恩·埃弗里特

1
接受的答案已过时,这是现在最好的答案。
Pierre.Sassoulas

2

这是我的解决方案,它再次使用它,_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

0

您可以在父级中查找django.db.models.fields.related.RelatedManager实例的所有字段来实现此目的。从您的示例看来,您正在谈论的子类不是子类。对?


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.