Modeling custom behavior without touching the database
Django’s proxy models are one of those features that I remember reading about and thinking, “oh, cool… I guess” and then moving along. From time to time, they do come in very handy, but there’s not that much written about how to best make use of them
What are they?
A proxy model is just another class that provides a different interface for the same underlying database model.
That’s it. Really.
A proxy model is a subclass of a database-table defining model. Typically creating a subclass of a model results in a new database table with a reference back to the original model’s table - multi-table inheritance.
A proxy model doesn’t get its own database table. Instead it operates on the original table.
In the contrived example above we’ve created a second model class, a proxy class. If you were to compare the field values for the instance at primary key
12
for either an instance of MyModel
or UpperModel
they would be exactly the same. The only difference is that the UpperModel
instance would print as the uppercase name of the model.
It’s a contrived example, so let’s examine the real usefulness.
When to use them?
There are certainly legacy or brownfield use cases, when you need to fit your models around an existing database, but they’re useful in new projects too.
Let’s say you’ve got a content type that has some different sub-types which all differ in some minor way. There’s the main content type, which we’ll call a “story”. Some stories have slightly different content needs - one may require an image file that is displayed and another a geographic reference. With these exceptions the content types all look roughly similar and behave in the same way.
You could create distinct models for each of these with a distinct database table for each, but do we really need a separate table for each of these? This is unnecessary and it’ll make aggregating these stories significantly more challenging if we want to use the ORM.
An alternative is to create a field - a column, if you like - for tracking the type of story and then creating proxy models for each story type based on this value.
Working with them in practice
In our example we want to be able to provide simple editing interfaces to all of these models to the content editors. On the front end, there’s going to be a single list view of all stories, and then list views for the different types, as well as detailed views for individual stories.
Aggregating heterogeneous types isn’t straight forward using Django’s ORM. We could make use of the
ContentType
model and filter on objects that were in one of our several story model types, or we could join together several querysets as Python lists. We could also perform a UNION query in a Raw queryset. That’s pretty appealing, but the one thing I don’t like about adding too many raw queries into Django apps is the fragility.
So what we want to do is use proxy models to create unique editing experiences with one underlying database model.
We’ll start with the base model.
Here’s a simplified and contrived example
Story
model.
The field of note here is
type
. This is going to be used to toggle the story type.
Our proxy models will look like this:
The
proxy = True
statement in the class Meta
section indicates that these classes are proxy models. It’s the manager class that we’re using here to differentiate the classes.
Each manager class simply returns a queryset that filters for the appropriate
type
value.
Well what good is this, you say?
For one, it provides a nice interface for content type specific views. If you have an infographic view, for example, the view can fetch the specific infographic from
Infographic.objects.get(pk=view_pk)
.
And moreover, now we can create distinct admin interfaces.
In the admin
The benefit here is that we can create different admin interfaces for different objects that happen to be stored in the same database table. Perhaps a blog post has an author and an author photo while a news item should only have a title, summary, and outbound link.
Since we have proxy models for these, we can create different admin interfaces (remember that Django picks up on registered models).
Managers and a better interface
In this second example, we have media assets that need to be used in a gallery. There are two useful models: an image and a video.
Again with the managers. Here’s what these managers look like though:
You see what we did here is extend the
create
method available for the Video
class and not for the Image
model. What we want is to be able to create an instance of one of our classes without having to specify the type
value. Since the default value for the base model is image
, we don’t need to specify a create
method for Image
instances - they’re the default.
The base model’s default type is
image
, so if we do this…
…we get back an image, or rather, a
MediaAsset
with its type
attribute set to image
. For a video, we need to ensure that the create
method updates the keyword argument to the base manager, and then this is valid:
Now, why bother with this? It’s not necessary, but what it allows us to do is provide an interface that differs in class only. If you’re using class based views, all you need to do is specify the class name in the view and everything else flows from that.
Now we have a gallery view that displays all of the media assets for a given gallery, and for any asset CRUD needs we can provide class specific views without checking for the
type
value in the code and filtering on that in our views.
In the case of the gallery view, we don’t want logic checks in the template for the type of asset used for generating the thumbnails, so we’ll add this method to the underlying
MediaAsset
class to provide a consistent interface.
Seeing the specific class name is also helpful for understanding exactly what you’re working with.
Having unique manager classes allows you to treat the proxy models as first class models in the rest of your code. You can use the managers to ensure the standard model interface is applied.
To use and not to use
There’s nothing complicated about proxy models, there’s just a little bit of thought required in regards to how they can solve your problems.
The use case for proxy models, I’ve found, is the exception rather than the rule. The key here is that our models, our end models that is, are all pretty closely related. The content attributes don’t differ all that much. If your models differ greatly and you’ve no need for simple aggregation, then skip the proxy models.