How to query abstract-class-based objects in Django?

DjangoAbstract Class

Django Problem Overview


Let's say I have an abstract base class that looks like this:

class StellarObject(BaseModel):
  title = models.CharField(max_length=255)
  description = models.TextField()
  slug = models.SlugField(blank=True, null=True)

  class Meta:
    abstract = True

Now, let's say I have two actual database classes that inherit from StellarObject

class Planet(StellarObject):
  type = models.CharField(max_length=50)
  size = models.IntegerField(max_length=10)

class Star(StellarObject):
  mass = models.IntegerField(max_length=10)

So far, so good. If I want to get Planets or Stars, all I do is this:

Thing.objects.all() #or
Thing.objects.filter() #or count(), etc...

But what if I want to get ALL StellarObjects? If I do:

StellarObject.objects.all()

It of course returns an error, because an abstract class isn't an actual database object, and therefore cannot be queried. Everything I've read says I need to do two queries, one each on Planets and Stars, and then merge them. That seems horribly inefficient. Is that the only way?

Django Solutions


Solution 1 - Django

At its root, this is part of the mismatch between objects and relational databases. The ORM does a great job in abstracting out the differences, but sometimes you just come up against them anyway.

Basically, you have to choose between abstract inheritance, in which case there is no database relationship between the two classes, or multi-table inheritance, which keeps the database relationship at a cost of efficiency (an extra database join) for each query.

Solution 2 - Django

You can't query abstract base classes. For multi-table inheritance you can use django-model-utils and it's InheritanceManager, which extends standard QuerySet with select_subclasses() method, which does right that you need: it left-joins all inherited tables and returns appropriate type instance for each row.

Solution 3 - Django

Don't use an abstract base class if you need to query on the base. Use a concrete base class instead.

Solution 4 - Django

This is an example of polymorphism in your models (polymorph - many forms of one).

Option 1 - If there's only one place you deal with this:

For the sake of a little bit of if-else code in one or two places, just deal with it manually - it'll probably be much quicker and clearer in terms of dev/maintenance (i.e. maybe worth it unless these queries are seriously hammering your database - that's your judgement call and depends on circumstance).

Option 2 - If you do this quite a bit, or really demand elegance in your query syntax:

Luckily there's a library to deal with polymorphism in django, django-polymorphic - those docs will show you how to do this precisely. This is probably the "right answer" for querying straightforwardly as you've described, especially if you want to do model inheritance in lots of places.

Option 3 - If you want a halfway house:

This kind of has the drawbacks of both of the above, but I've used it successfully in the past to automatically do all the zipping together from multiple query sets, whilst keeping the benefits of having one query set object containing both types of models.

Check out django-querysetsequence which manages the merge of multiple query sets together.

It's not as well supported or as stable as django-polymorphic, but worth a mention nevertheless.

Solution 5 - Django

In this case I think there's no other way.

For optimization, you could avoid inheritance from abstract StellarObject and use it as separate table connected via FK to Star and Planet objects.

That way both of them would have ie. star.stellar_info.description.

Other way would be to add additional model for handling information and using StellarObject as through in many2many relation.

Solution 6 - Django

I would consider moving away from either an abstract inheritance pattern or the concrete base pattern if you're looking to tie distinct sub-class behaviors to the objects based on their respective child class.

When you query via the parent class -- which it sounds like you want to do -- Django treats the resulting ojects as objects of the parent class, so accessing child-class-level methods requires re-casting the objects into their 'proper' child class on the fly so they can see those methods... at which point a series of if statements hanging off a parent-class-level method would arguably be a cleaner approach.

If the sub-class behavior described above isn't an issue, you could consider a custom manager attached to an abstract base class sewing the models together via raw SQL.

If you're interested mainly in assigning a discrete set of identical data fields to a bunch of objects, I'd relate along a foreign-key, like bx2 suggests.

Solution 7 - Django

> That seems horribly inefficient. Is that the only way?

As far as I know it is the only way with Django's ORM. As implemented currently abstract classes are a convenient mechanism for abstracting common attributes of classes out to super classes. The ORM does not provide a similar abstraction for querying.

You'd be better off using another mechanism for implementing hierarchy in the database. One way to do this would be to use a single table and "tag" rows using type. Or you can implement a generic foreign key to another model that holds properties (the latter doesn't sound right even to me).

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKevin WhitakerView Question on Stackoverflow
Solution 1 - DjangoDaniel RosemanView Answer on Stackoverflow
Solution 2 - DjangoZlobnyiSergView Answer on Stackoverflow
Solution 3 - DjangoCarl MeyerView Answer on Stackoverflow
Solution 4 - DjangothclarkView Answer on Stackoverflow
Solution 5 - Djangobx2View Answer on Stackoverflow
Solution 6 - DjangodaemianmackView Answer on Stackoverflow
Solution 7 - DjangoManoj GovindanView Answer on Stackoverflow