•  


Reduce the complexity of Models by using Proxy Models | Django Unleashed

Reduce the complexity of Django Models by using Proxy Models

Unlock cleaner, more maintainable models with this powerful technique

Vlad Ogir
Django Unleashed
5 min read Jan 23, 2024

--

Photo by Brecht Corbeel on Unsplash

The reason we store data is to make a record of something that has happened. For example, a password change, an order made or an order delivered. Such things can be stand alone events or, in many cases, they are part of a process (a bigger picture) and they represent only a single state in that process.

States that are part of a process need to be managed somehow. Often, a column in a table is used to represent the current state (i.e. column ‘status’). But, this can become problematic when we try to pack all of the state-related functionality into one model. (We can end up with difficult to understand code stretching over thousands of lines!)

On the other hand, proxy models (proxies) can be a great way to simplify models. It works by capturing a certain state, enabling you to clearly represent parts of the model in simpler terms.

In this article, I will share the basics of what proxy models are, why we should use them and give practical examples.

?? This article is part of a series on the same subject ? Django proxy models series

What are proxy models?

Proxy models are there to “decorate” existing Django models. This is very similar to what Python’s decorators do, except that it’s done in class form. Also, similar to model abstraction, the Proxy model inherits the data and base methods from the parent model, but without affecting the underlying table that the model represents. (So, you can't add a new column but you can limit the scope of columns that the proxy model represents.)

For more information check out the link below:

Why use proxy models?

Models can easily get to the point where they become too big (bloated) and this can lead to difficulties in understanding the purpose of the model and when each method should be used. In the end, the code can end up being overcomplicated.

With proxies, we can resolve all three of these problems by simply extracting code specific to the state into the proxy model. As a result, you end up with smaller, targeted models that are easier to understand and to maintain.

Example Usage

Let's imagine we are working on a hypothetical blog, similar to Medium, and the posts all have to go through a review.

The first thing to note is that when a post is created, we don’t need to know things like performance or think about the review process. (These things are only applicable once the post is in certain states.)

Sample of what personas may care about during different post-states

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:

  1. Embedd key aspects into the code base
  2. Make these visible and easy-to-find
  3. 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

I’d love to hear your thoughts! Please share your questions and insights in the comments below or contact me directly.

Want more articles like this? Subscribe to my email list for exclusive content and updates on my latest work.

--

--

Vlad Ogir
Django Unleashed

Staff software engineer with passion for software delivery, architecture and design.

- "漢字路" 한글한자자동변환 서비스는 교육부 고전문헌국역지원사업의 지원으로 구축되었습니다.
- "漢字路" 한글한자자동변환 서비스는 전통문화연구회 "울산대학교한국어처리연구실 옥철영(IT융합전공)교수팀"에서 개발한 한글한자자동변환기를 바탕하여 지속적으로 공동 연구 개발하고 있는 서비스입니다.
- 현재 고유명사(인명, 지명등)을 비롯한 여러 변환오류가 있으며 이를 해결하고자 많은 연구 개발을 진행하고자 하고 있습니다. 이를 인지하시고 다른 곳에서 인용시 한자 변환 결과를 한번 더 검토하시고 사용해 주시기 바랍니다.
- 변환오류 및 건의,문의사항은 juntong@juntong.or.kr로 메일로 보내주시면 감사하겠습니다. .
Copyright ⓒ 2020 By '전통문화연구회(傳統文化硏究會)' All Rights reserved.
 한국   대만   중국   일본