views:

1254

answers:

5

In Django, when you have a parent class and multiple child classes that inherit from it you would normally access a child through parentclass.childclass1_set or parentclass.childclass2_set, but what if I don't know the name of the specific child class I want?

Is there a way to get the related objects in the parent->child direction without knowing the child class name?

A: 

You can achieve this looking for all the fields in the parent that are an instance of django.db.models.fields.related.RelatedManager. From your example it seems that the child classes you are talking about are not subclasses. Right?

Chirayu Patel
+6  A: 

In Python, given a ("new-style") class X, you can get its (direct) subclasses with X.__subclasses__(), which returns a list of class objects. (If you want "further descendants", you'll also have to call __subclasses__ on each of the direct subclasses, etc etc -- if you need help on how to do that effectively in Python, just ask!).

Once you have somehow identified a child class of interest (maybe all of them, if you want instances of all child subclasses, etc), getattr(parentclass,'%s_set' % childclass.__name__) should help (if the child class's name is 'foo', this is just like accessing parentclass.foo_set -- no more, no less). Again, if you need clarification or examples, please ask!

Alex Martelli
This is great info (I didn't know about __subclasses__), but I believe the question is actually much more specific to how Django models implement inheritance. If you query the "Parent" table you will get back an instance of Parent. It may _in fact_ be an instance of SomeChild, but Django does not figure that out for you automatically (could be expensive). You can get to the SomeChild instance through an attribute on the Parent instance, but only if you already know that it's SomeChild you want, as opposed to some other subclass of Parent.
Carl Meyer
Sorry, it's not clear when I say "may _in fact_ be an instance of SomeChild." The object you have is an instance of Parent in Python, but it may have a related entry in the SomeChild table, which means you may prefer to work with it as a SomeChild instance.
Carl Meyer
It's funny... I didn't need this specific info at the time, but I was just thinking about a different issue and this turned out to be exactly what I needed, so thank you again!
Gabriel Hurley
+13  A: 

The usual way to do this is to add a ForeignKey to ContentType on the Parent model which stores the content type of the proper "leaf" class. Without this, you may have to do quite a number of queries on child tables to find the instance, depending how large your inheritance tree is. Here's how I did it in one project:

from django.contrib.contenttypes.models import ContentType

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, null=True)

    def save(self, *args, **kwargs):
        if not self.id:
            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

This is implemented as an abstract base class to make it reusable; you could also put these methods and the FK directly onto the parent class in your particular inheritance hierarchy.

This solution won't work if you aren't able to modify the parent model. In that case you're pretty much stuck checking all the subclasses manually.

Carl Meyer
This is delovely.
James Thigpen
Thank you. This is beautiful and definitely saved me time.
Spike
+2  A: 

Carl's solution is a good one, here's one way to do it manually if there are multiple related child classes:

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)]

It uses a function out of _meta, which is not guaranteed to be stable as django evolves, but it does the trick and can be used on-the-fly if need be.

Paul McMillan
+2  A: 

It turns out that what I really needed was this:

Model inheritance with content type and inheritance-aware manager

That has worked perfectly for me. Thanks to everyone else, though. I learned a lot just reading your answers!

Gabriel Hurley