For the sake of example, let’s consider the following UML diagram describing two model classes and how they are interrelated:

We can see that there are two classes, User
and UserProfile
, with a one-to-one association between them. That is, each instance of class User
is associated with one and only one instance of UserProfile
and vice versa. This type of association is frequently encountered when modelling all sorts of real-world domains.
In Django, this kind of relationship between two entities is expressed as a OneToOneField
defined within some Model
class and pointing to another (or the same, in the case of a reflexive relationship). This raises the question of where to put the OneToOneField
: Should it be an attribute of User
or UserProfile
?
You might be doubting the relevance of this question as–thanks to the related_name
mechanism (aka reverse references)–we can later on traverse the association in either direction. This is certainly true, but there are other considerations to the association’s semantics that should be taken into account.
Existential dependence
One of these considerations is whether one object’s existence depends on the existence of the other. In the context of our example, does a UserProfile
depend on the existence of a User
? In most cases that would arguably hold true. The opposite statement could more likely turn out to not be true. Depending on the application, you could argue that a User
can exist without having a UserProfile
.
In this case, it would make sense to define the reference as an attribute of the UserProfile
class. This way, you can express that whenever we delete a User
instance, the associated UserProfile
will be deleted as well. This would result in the following two Django model definitions:
class User(models.Model):
pass
class UserProfile(models.Model):
user = models.OneToOneField('User', on_delete=models.CASCADE)
If we defined the association within the User
model class, the result would be semantically different:
class User(models.Model):
profile = models.OneToOneField('User', on_delete=models.CASCADE)
class UserProfile(models.Model):
pass
In the latter code example, the existence of a User
instance depends on the existence of its associated UserProfile
. Per se, neither piece of code is in any aspect “wronger” than the other. To know right from wrong, we would simply need to know more about the modeled domain.
Order of instantiation
Another factor to consider when deciding on how to associate models is the order of instantiation. In neither of the above model definitions is it possible to create both a User
and a UserProfile
instance at the same time. It is therefore necessary to decide which object to instantiate first.
As an example, let’s consider two possible scenarios of a user registration system. In the first scenario, a user completes the registration process and can later on fill out an optional UserProfile
. In the second system, potential users are first asked to complete a profile as part of an application process. Only after they have been approved will an actual User
instance be created.
Where to instantiate the associated objects
Once one has decided on where to put the associating attribute, it’s time to think about where to actually create the model instances. In the spirit of our example, we would like to create one and only one UserProfile
whenever a new User
has succesfully registered. At first glance, multiple places look like promising candidates for this functionality.
The __init__(…)
magic method
Arguably the most obvious candidate is Python’s __init__(…)
constructor method. After all, we would like to create a UserProfile
whenever a new User
is added or, in other words, initialized. However, this logic disregards the distinction between what happens in the database and what happens on the Python level.
__init__(…)
is a Python construct and will be executed whenever a model instance is created in Python. This happens when we create a new User
object for the first time, but it also happens whenever we retrieve an already existing instance from the database. In other words, if we were to instantiate a user’s UserProfile
within the User
model’s __init__(…)
method, before long we would end up creating more than one profile instance per user!
The __new__(…)
magic method
Another of Python’s magic methods, __new__(…)
, poses the same problem as __init__(…)
, as does Django’s save(…)
method. The latter is called not only when creating a User
object, but also when updating it. This behavior is not what we are looking for.
Signals to the rescue
Luckily, there is another mechanism that we can leverage to achieve what we want. Django features a variety of built-in signals which are a way for a piece of code to get notified when actions occur elsewhere in the framework. Beyond the built-in signals, a developer can easily define custom ones for application-specific events.
post_save
is one of Django’s built-in signals. As its name strongly suggests, it is fired whenever an object has been saved to the application database. Though it’s not exactly what we need, we can still go ahead and base a new custom signal on the existing one:
# In myapp/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver, Signal
post_create = Signal()
@receiver(post_save)
def send_post_create(created, **kwargs):
if created:
post_create.send(**kwargs)
In Django, all signals are instances of the django.dispatch.Signal
class. All you have to do to define a new signal is to name and instantiate a Signal
instance. To actually fire the signal, we hook into post_save
. Upon receiving an instance of this built-in signal and checking the created
flag, we simply send a post_create
signal. From now on we can react to the instantiation of a new object simply by defining another @receiver
function with post_create
as its signal.