Models, a fundamental component of the Django framework, hold a central position in its architecture. Following Django’s design principles for models is essential. This involves maintaining explicit naming and functionality within our fields and encapsulating all relevant model functionality within the model itself, rather than dispersing it elsewhere—a core tenet of Django’s design philosophy.
If you have prior experience with Ruby on Rails, you’ll find these principles familiar, as both Django and Rails adopt the Active Record pattern for their object-relational mapping (ORM) systems to manage stored data effectively.
In this article, we explore strategies to maximize these design principles, tap into Django’s core features, and even utilize libraries to enhance our models. Looking to hire a remote Python developer? We’ve got you covered.
getter/setter/deleter
properties
As a feature of Python since version 2.2, a property’s usage looks like an attribute but is actually a method. While using a property on a model isn’t that advanced, we can use some underutilized features of the Python property to make our models more powerful.
If you’re using Django’s built-in authentication or have customized your authentication using AbstractBaseUser
, you’re probably familiar with the last_login
field defined on the User
model, which is a saved timestamp of the user’s last login to your application. If we want to use last_login
, but also have a field named last_seen
saved to a cache more frequently, we could do so pretty easily.
First, we’ll make a Python property that finds a value in the cache, and if it can’t, it returns the value from the database.
accounts/models.py

Note: I’ve slimmed the model down a bit as there’s a separate tutorial on this blog about specifically customizing the built-in Django user model.
The property above checks our cache for the user’s last_seen
value, and if it doesn’t find anything, it will return the user’s stored last_login
value from the model. Referencing <instance>.last_seen
now provides a much more customizable attribute on our model behind a very simple interface.
We can expand this to include custom behavior when a value is assigned to our property (some_user.last_seen = some_date_time
), or when a value is deleted from the property (del some_user.last_seen
).

Now, whenever a value is assigned to our last_seen
property, we save it to the cache, and when a value is removed with del
, we remove it from the cache. Using setter
and deleter
is described in the Python documentation but is rarely seen in the wild when looking at Django models.You may have a use case like this one, where you want to store something that doesn’t necessarily need to be persisted to a traditional database, or for performance reasons, shouldn’t be. Using a custom property like the above example is a great solution.
In a similar use case, the python-social-auth
library, a tool for managing user authentication using third-party platforms like GitHub and Twitter, will create and manage updating information in your database based on information from the platform the user logged-in with. In some cases, the information returned won’t match the fields in our database.
For example, the python-social-auth
library will pass a fullname
keyword argument when creating the user. If, perhaps in our database, we used full_name
as our attribute name then we might be in a pinch.
A simple way around this is by using the getter/setter
pattern from above:

Now, when python-social-auth
saves a user’s fullname
to our model (new_user.fullname = 'Some User'
), we’ll intercept it and save it to our database field, full_name
, instead.
through
model relationships
Django’s many-to-many relationships are excellent for managing complex object associations, but they lack the ability to include custom attributes in the intermediate models they generate. By default, these models include only an identifier and two foreign key references to connect the objects.
With Django’s ManyToManyField and the ‘through’ parameter, you can take control by creating your custom intermediate model and incorporating any additional fields as needed.
Imagine, for instance, your application requires users to have group memberships, and you also want to track when these memberships started. In such cases, employing a custom intermediate model is the solution. Looking to hire a Remote Python Developer? We’ve got the talent you need.
accounts/models.py

In the example above, we’re still using a ManyToManyField
to handle the relationship between a user and a group, but by passing the Membership
model using the through
keyword argument, we can now add our joined
custom attribute to the model to track when the group membership was started. This through
model is a standard Django model, it just requires a primary key (we use UUIDs here), and two foreign keys to join the objects together.
Using the same three model pattern, we could create a simple subscription database for our site:

In this context, we have the capability to monitor various user subscription events, including their initial subscription date, updates to their subscription, and if implemented, when they decide to cancel their subscription within our application.
Leveraging ‘through’ models in conjunction with the ‘ManyToManyField’ is a powerful technique to enrich our intermediate models with additional data. This enhances the overall user experience without requiring extensive additional efforts.
If you’re seeking to bolster your team’s capabilities, consider hiring a Remote Python Developer. They can contribute valuable expertise to projects like these and more.
Proxy models
Normally in Django, when you subclass a model (this doesn’t include abstract models) into a new class, the framework will create new database tables for that class and link them (via OneToOneField
) to the parent database tables. Django calls this “multi-table inheritance” and it’s a great way to re-use existing model fields and structures and add your own data to them. “Don’t repeat yourself,” as the Django design philosophies state.
Multi-table inheritance example:

This example generates two database tables: ‘vehicles_vehicle’ and ‘vehicles_airplane,’ linked with foreign keys. This approach enables us to harness the existing data stored in ‘vehicles_vehicle’ while incorporating unique attributes specific to each subclass, such as ‘vehicle_airplane.’
However, in certain scenarios, additional data storage might be unnecessary. Instead, you can modify the behavior of the parent model by introducing methods, properties, or model managers. Here’s where ‘proxy models’ come into play. They empower us to alter a model’s Python behavior effectively without making any alterations to the database structure.
vehicles/models.py

Proxy models
are declared just like normal models. In our example, we tell Django that Honda is a proxy model
by setting the proxy
attribute of the Honda Meta
class to True
. I’ve added a property and a method stub example, but you can see we’ve added a custom model manager to our Honda proxy model
.
This ensures that whenever we request objects from the database using our Honda model, we get only Car
instances back where model= 'Honda'
. Proxy models make it easy for us to quickly add customization on top of existing models using the same data. If we were to delete, create, or update any Car
instance using our Honda model or manager, it would be saved into the vehicles_car
database just as if we were using the parent (Car
) class.
Wrap up
If you’re well-versed in Python classes, Django’s models will be a breeze for you. They offer familiar concepts like inheritance, multiple inheritance, method overrides, and introspection. These features are integral to Django’s object-relational mapping (ORM) design.
While multi-table inheritance and custom intermediate tables for SQL joins may not be considered elementary, Django and Python expertise make their implementation straightforward. The synergy between the language and framework empowers developers to harness these capabilities effectively.
Looking to enhance your development team? Consider hiring a Remote Python Developer to leverage their expertise and elevate your Django projects.
For further reading, check out Django’s documentation topic for models.