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.
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!
- [Django]-How to reload modules in django shell?
- [Django]-Convert Django Model object to dict with all of the fields intact
- [Django]-How to get primary keys of objects created using django bulk_create
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.
- [Django]-How to allow only one radio button to be checked?
- [Django]-How to create a Django FloatField with maximum and minimum limits?
- [Django]-Django: multiple models in one template using forms
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!
- [Django]-Django won't refresh staticfiles
- [Django]-Django FileField with upload_to determined at runtime
- [Django]-Django 1.4 – can't compare offset-naive and offset-aware datetimes
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.
- [Django]-How to change field name in Django REST Framework
- [Django]-How do I get Django Admin to delete files when I remove an object from the database/model?
- [Django]-Rendering a value as text instead of field inside a Django Form
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
- [Django]-Django REST Framework: adding additional field to ModelSerializer
- [Django]-Split models.py into several files
- [Django]-Data Mining in a Django/Postgres application
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?
- [Django]-Testing nginx without domain name
- [Django]-How to assign items inside a Model object with Django?
- [Django]-Django switching, for a block of code, switch the language so translations are done in one language