如何在Django中访问对象的子类而不知道子类名称?

86 投票
7 回答
36878 浏览
提问于 2025-04-15 11:55

在Django中,当你有一个父类和多个从它继承的子类时,通常你可以通过父类来访问子类,比如用parentclass.childclass1_set或者parentclass.childclass2_set。但是,如果我不知道我想要的具体子类的名字,该怎么办呢?

有没有办法在不知道子类名字的情况下,从父类获取相关的子类对象呢?

7 个回答

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的发展,这个函数的稳定性没有保证。不过它确实能解决问题,如果需要的话,可以随时使用。

24

在Python中,如果你有一个叫做X的“新式”类,你可以通过调用 X.__subclasses__() 来获取它的直接子类,这个方法会返回一个类对象的列表。如果你想要获取更深层次的子类,你还需要对每个直接子类调用 __subclasses__,以此类推。如果你需要在Python中有效地做到这一点,可以随时问我!

一旦你找到了一个感兴趣的子类(也许是所有子类,如果你想要所有子类的实例等),你可以使用 getattr(parentclass,'%s_set' % childclass.__name__) 来帮助你(如果子类的名字是 'foo',这就像访问 parentclass.foo_set 一样,没什么不同)。如果你需要进一步的解释或例子,随时问我!

89

(更新: 对于Django 1.2及更新版本,它可以在反向的OneToOneField关系中跟踪select_related查询(因此也可以处理继承层级),现在有一种更好的方法,不需要在父模型上添加real_type字段。这个方法可以在InheritanceManager中找到,属于django-model-utils项目。)

通常的做法是在父模型上添加一个指向ContentType的外键,这样可以存储正确的“叶子”类的内容类型。如果没有这个,你可能需要对子表进行很多查询,才能找到实例,这取决于你的继承树有多大。以下是我在一个项目中是如何做的:

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

这个实现是作为一个抽象基类,以便可以重复使用;你也可以把这些方法和外键直接放到你特定继承层级的父类上。

如果你不能修改父模型,这个解决方案就不适用了。在这种情况下,你基本上只能手动检查所有子类。

撰写回答