Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 874
Kevin Whitaker Avatar asked Sep 26 '10 13:09

Kevin Whitaker


People also ask

What is abstract base class in Django?

An abstract model is a base class in which you define fields you want to include in all child models. Django doesn't create any database table for abstract models. A database table is created for each child model, including the fields inherited from the abstract class and the ones defined in the child model.

Are abstract classes Pythonic?

Python doesn't directly support abstract classes. But it does offer a module that allows you to define abstract classes. To define an abstract class, you use the abc (abstract base class) module. The abc module provides you with the infrastructure for defining abstract base classes.

Why do we use abstract models in Django?

Abstract Base Class are useful when you want to put some common information into a number of other models. You write your base class and put abstract = True in the Meta Class.

How do you inherit models in Django?

Models inheritance works the same way as normal Python class inheritance works, the only difference is, whether we want the parent models to have their own table in the database or not. When the parent model tables are not created as tables it just acts as a container for common fields and methods.


4 Answers

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.

like image 82
Daniel Roseman Avatar answered Oct 20 '22 20:10

Daniel Roseman


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.

like image 28
ZlobnyiSerg Avatar answered Oct 20 '22 20:10

ZlobnyiSerg


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

like image 9
Carl Meyer Avatar answered Oct 20 '22 20:10

Carl Meyer


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.

like image 9
thclark Avatar answered Oct 20 '22 20:10

thclark