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,
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
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.
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
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.
__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
__init__(…) method, before long we would end up creating more than one profile instance per user!
__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.