In the example below, we extend the parent
Blog
model with two proxy models. (Each of these proxy models is for a specific state, and both define state-specific functions.)
from django.db import models
class Post(models.Model):
subject = models.CharField(max_length=30)
content = models.TextField()
class DraftPost(Post):
# custom defaults for class properties can be define
type = BLOG_TYPE_DRAFT
class Meta:
proxy = True
def complete_state(self):
self.status = STATUS_READY_FOR_REVIEW
self.save()
class PublishedBlog(Post):
# we can override base object with a custom manager
objects = PublishedBlogManager()
type = BLOG_TYPE_PUBLISHED
class Meta:
proxy = True
# we can have our own custom methods or override parent's methods
def performance(self):
...
There are multiple benefits to this implementation, including:
? a visibility of states
? an ability to use proxy classes as type hints
? state-specific methods
? definition of how to transition to the next state
? custom managers to limit or expand the scope of QuerySet
Defining transition steps
Have you ever entered a code base and realised that you weren’t just dealing with a simple model? Annoying (and very time-consuming) isn’t it!
To resolve this issue we can document our code on the wiki or somewhere else.
That said, there are issues with any documentation. For example:
? the information
goes out of date
quickly (if not maintained)
? others
might not be aware
that information exists to begin with
? maintenance is a chore, and not everyone wants to do it
An alternative approach (I find helpful) helps keep data fresh and engineers aware of it. This consists of three simple steps:
- Embedd key aspects into the code base
- Make these visible and easy-to-find
- Make these part of the functional code
# list of status transitions
# can be kept in a config file
POST_STATUS_FLOW = {
STATUS_DRAFT => STATUS_SUBMITTED_FOR_REVIEW,
STATUS_SUBMITTED_FOR_REVIEW => STATUS_PUBLISHED,
STATUS_PUBLISHED => STATUS_DISTRIBUTE_POST,
STATUS_DISTRIBUTE_POST => None,
}
# a helper function
def next_status(status: str) -> str:
return POST_STATUS_FLOW.get(status)
...
class Post(model):
def complete_state(self):
# resolve next status on transition
self.status = next_status(self.status)
self.save()
class DraftPost(Post):
def complete_state(self):
# do something state specific...
self.complete_state()
This is a quick and simple addition and it has some surprisingly big rewards! For example:
? No need to pester other engineers to keep documentation up to date
? Engineers can dive quicker into the code
? Engineering delivering valuable features to end-users faster
Proxy model resolver: turning Model into Proxy equivalent
When working in Django we generally use the base model (i.e.
Post
) to run queries with, and when you get data back it’ll be a list of post models. (Not proxy representation. This also applies when querying using the proxy models.)
We want to turn this data into the correct proxy model to work with, and we can achieve this by setting up a function which does this. For example, with the blog post, we have the
status
column (which indicates the current state of the process) and we can use this column to help us resolve
Post
into a proxy model equivalent. The code below demonstrates how this can be accomplished by extending the
POST_STATUS_FLOW
class property and then using the
resolve_proxy_model
to grab the proxy model.
from django.db import models
class Post(models.Model):
POST_STATUS_FLOW = {
STATUS_DRAFT: DraftPost,
...
}
def resolve_proxy_model(self) -> Post:
proxy_class = MAPPER.get(self.status)
self.__class__ = proxy_class
return self
...
# usage
proxy_model = post.resolve_proxy_model()
?? You can also
automatically resolve the proxy model
without manually calling the resolver method.
Conclusion
When using Django, proxy models can be a very useful tool to manage state. It allows you to encapsulate all the relevant logic for the state in one single class (with the help of custom methods, query managers and transition-related requirements).
Additionally, this technique will improve the visibility of your states and make it easier for other engineers to understand the intended flow of the whole process.
So, why wait? Pick some models and start refactoring today!
P.S. This article is part of a series about using proxy models in Django. Those articles dive further into the subject! Here are some of the articles that might find interesting:
-
Three tips to make your Django proxy models journey easier
-
Guarding Your Data: How to Prevent Invalid State Changes with Django Proxy Models