86

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?

Gabriel Hurley
  • 39,690
  • 13
  • 62
  • 88
  • 80
    @S.Lott These kinds of responses really get old. Just because you can't think of a use case doesn't mean the asker doesn't have one. If you're using subclassing for any kind of polymorphic behavior (you know, one of the primary supposed benefits of OOP?) this question is a very natural and obvious necessity. – Carl Meyer May 30 '09 at 15:40
  • 82
    @S.Lott In that case, feel free to practice some not-rude versions, such as "I'm not sure I understand the context. Could you explain your use case?" – Carl Meyer May 30 '09 at 21:10

8 Answers8

89

(Update: For Django 1.2 and newer, which can follow select_related queries across reverse OneToOneField relations (and thus down inheritance hierarchies), there's a better technique available which doesn't require the added real_type field on the parent model. It's available as InheritanceManager in the django-model-utils project.)

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

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.

Antoine Pinsard
  • 33,148
  • 8
  • 67
  • 87
Carl Meyer
  • 122,012
  • 20
  • 106
  • 116
  • Thank you. This is beautiful and definitely saved me time. – Spike Jul 27 '10 at 20:37
  • This has been very helpful, but I wonder why you'd want to define the FK with null=True. We copied the code more-or-less as is, and were bit by a bug which would have been detected and solved easily if the FK were mandatory (note also that the cast() method treats it as mandatory). – Shai Berger Aug 01 '12 at 09:41
  • 1
    @ShaiBerger Excellent question. Three+ years later, I have no idea why it was that way originally :-) Editing to remove the null=True. – Carl Meyer Aug 06 '12 at 22:41
  • Beware of schema changes with this. The real_type will change. – Erik May 05 '13 at 21:01
  • Why InheritanceManager technique from django-utils is better? It seems a good practice to have real_type field in case you have many children. Otherwise django will be forced to make join for every child class to determine object real_type. – sunprophit Sep 21 '13 at 19:06
  • @sunprophit If you want to cast the objects to their leaf (real) type, then you need those joins anyway to get the relevant data, or you'll end up doing N queries as you cast each object one at a time (which is what you have to do with the real_type solution), which is quite likely to be slower than the joins. Any way you do this its not going to be real efficient, but I think the InheritanceManager approach is the best set of tradeoffs for real use cases. Only way real_type is better is if you want to know the real type, but not use its data; that seems like an unusual use case. – Carl Meyer Sep 21 '13 at 20:47
  • Well, I thought real_type could be used as pointer on correct child table, i.e. self.child_object(), where child_object will perform only one sql query on specific table, which we already know from real_type field. As far as I understand InheritanceManager don't know anything about child object till it find this object in some child table (which could take a lot of queries in case we have many children models). So I am not sure, but real_type with type cast function looks more effective than InheritanceManager – sunprophit Sep 23 '13 at 10:06
  • 2
    @sunprophit You need to re-read my response more carefully. Yes, of course you can do `self.child_object()` using the `real_type` field (that is in the code above as the `cast()` method), and that will take only one query for one instance. But if you have a queryset full of instances, that becomes N queries. The only way to get the subclass data for a whole queryset of objects in a single query is to use the joins that InheritanceManager does. – Carl Meyer Sep 23 '13 at 17:46
  • There is another built solution following this approach for [making your models polymorphic](https://django-polymorphic.readthedocs.org/en/latest/quickstart.html#making-your-models-polymorphic) and the app is in [github](https://github.com/chrisglass/django_polymorphic) – chaim Jul 28 '14 at 10:38
  • Given the score of this answer I can't believe it doesn't work. However, I don't understand how it can. The `cast()` method assumes the parent `pk` equals the child `pk`. Which seems not only not guaranteed, but very unlikely. May someone enlighten me on this one? – Antoine Pinsard Jul 04 '16 at 18:47
  • is there any easier solution for this in django2? – yukashima huksay Jul 09 '18 at 15:47
  • @AntoinePinsard I'm not sure how it works for abstract base class in django but with concrete base class the parent instance and child instance are created with the same PK - with abstract base class i guess no parent record is actually created so it's not possible for parent record to exist with a different pk to child – teebagz Oct 20 '20 at 10:57
  • Not sure what i'm doing differently, but i had to change to `if self._state.adding:` from `if not self._state.adding:` to get this to work – teebagz Oct 20 '20 at 11:30
  • i think it was a bug in previous edit from @AntoinePinsard – teebagz Oct 20 '20 at 11:55
  • 1
    My mistake indeed, shame on me. Thank you for noticing it and fixing it. – Antoine Pinsard Oct 21 '20 at 08:31
24

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
  • 854,459
  • 170
  • 1,222
  • 1,395
  • 1
    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 May 30 '09 at 15:38
  • 2
    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 May 30 '09 at 15:42
  • 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 Sep 28 '09 at 03:20
  • This is the *REAL* answer. Django is just Python at the end of the day. – snakesNbronies Nov 12 '17 at 15:03
5

You can use django-polymorphic for that.

It allows to automatically cast derived classes back to their actual type. It also provides Django admin support, more efficient SQL query handling, and proxy model, inlines and formset support.

The basic principle seems to be reinvented many times (including Wagtail's .specific, or the examples outlined in this post). It takes more effort however, to make sure it doesn't result in an N-query issue, or integrate nicely with the admin, formsets/inlines or third party apps.

vdboor
  • 21,914
  • 12
  • 83
  • 96
  • 1
    This looks good and I want to try it out. When migrating is it enough to just fill in the polymorphic_ctype fields with the appropriate contenttypes (in a south migration)? – joshua Jul 20 '13 at 05:49
  • 1
    @joshua: yes, that's exactly the only thing you'd have to do. – vdboor Jul 21 '13 at 14:45
  • You might flesh out your answer a bit by explaining the difference from the approach taken in django-model-utils (see Carl Meyer's answer). – Ryne Everett May 16 '16 at 02:46
  • 1
    Accepted answers is outdated, this is the best answer now. – Pierre.Sassoulas Jul 04 '17 at 15:43
5

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
  • 19,693
  • 9
  • 57
  • 71
5

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
  • 39,690
  • 13
  • 62
  • 88
2

Here's my solution, again it uses _meta so isn't guaranteed to be stable.

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
cerberos
  • 7,705
  • 5
  • 41
  • 43
0

An alternative approach using proxies can be found in this blog post. Like the other solutions, it has its benefits and liabilities, which are very well put in the end of the post.

Filipe Correia
  • 5,415
  • 6
  • 32
  • 47
0

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?