In previous chapters, you created many-to-many relationships by adding ManyToManyField to one of the related models and letting Django create the database table for the relationship. This is suitable for most of the cases, but sometimes you may need to create an intermediate model for the relation. Creating an intermediary model is necessary when you want to store additional information for the relationship, for example, the date when the relation was created, or a field that describes the nature of the relationship.
We will create an intermediary model to build relationships between users. There are two reasons why we want to use an intermediate model:
- We are using the User model provided by Django, and we want to avoid altering it
- We want to store the time when the relation is created
Edit the models.py file of your account application and add the following code to it:
class Contact(models.Model):
user_from = models.ForeignKey('auth.User',
related_name='rel_from_set',
on_delete=models.CASCADE)
user_to = models.ForeignKey('auth.User',
related_name='rel_to_set',
on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True,
db_index=True)
class Meta:
ordering = ('-created',)
def __str__(self):
return '{} follows {}'.format(self.user_from,
self.user_to)
The preceding code shows the Contact model we will use for user relationships. It contains the following fields:
- user_from: ForeignKey for the user that creates the relationship
- user_to: ForeignKey for the user being followed
- created: A DateTimeField field with auto_now_add=True to store the time when the relationship was created
A database index is automatically created on the ForeignKey fields. We use db_index=True to create a database index for the created field. This will improve query performance when ordering QuerySets by this field.
Using the ORM, we could create a relationship for a user—user1—following another user, user2, like this:
user1 = User.objects.get(id=1)
user2 = User.objects.get(id=2)
Contact.objects.create(user_from=user1, user_to=user2)
The related managers rel_from_set and rel_to_set will return a QuerySet for the Contact model. In order to access the end side of the relationship from the User model, it would be desirable that User contained ManyToManyField, as follows:
following = models.ManyToManyField('self',
through=Contact,
related_name='followers',
symmetrical=False)
In the preceding example, we tell Django to use our custom intermediary model for the relationship by adding through=Contact to the ManyToManyField. This is a many-to-many relationship from the User model to itself: we refer to 'self' in the ManyToManyField field to create a relationship to the same model.
If the User model was part of our application, we could add the previous field to the model. However, we cannot alter the User class directly because it belongs to the django.contrib.auth application. We will take a slightly different approach by adding this field dynamically to the User model. Edit the models.py file of the account application and add the following lines:
from django.contrib.auth.models import User
# Add following field to User dynamically
User.add_to_class('following',
models.ManyToManyField('self',
through=Contact,
related_name='followers',
symmetrical=False))
In the preceding code, we use the add_to_class() method of Django models to monkey patch the User model. Be aware that using add_to_class() is not the recommended way of adding fields to models. However, we take advantage of using it in this case because of the following reasons:
- We simplify the way we retrieve related objects using the Django ORM with user.followers.all() and user.following.all(). We use the intermediary Contact model and avoid complex queries that would involve additional database joins, as it would have been, had we defined the relationship in our custom Profile model.
- The table for this many-to-many relationship will be created using the Contact model. Thus, the ManyToManyField added dynamically will not imply any database changes for the Django User model.
- We avoid creating a custom user model, keeping all the advantages of Django's built-in User.
Keep in mind that, in most cases, it is preferable to add fields to the Profile model we created before, instead of monkey-patching the User model. Django also allows you to use custom user models. If you want to use your custom user model, take a look at the documentation at https://docs.djangoproject.com/en/2.0/topics/auth/customizing/#specifying-a-custom-user-model.
You can note that the relationship includes symmetrical=False. When you define a ManyToManyField to the model itself, Django forces the relationship to be symmetrical. In this case, we are setting symmetrical=False to define a non-symmetric relation. This is, if I follow you, it doesn't mean that you automatically follow me.
Run the following command to generate the initial migrations for the account application:
python manage.py makemigrations account
You will obtain the following output:
Migrations for 'account':
account/migrations/0002_contact.py
- Create model Contact
Now, run the following command to sync the application with the database:
python manage.py migrate account
You should see an output that includes the following line:
Applying account.0002_contact... OK
The Contact model is now synced to the database, and we are able to create relationships between users. However, our site doesn't offer a way to browse users or see a particular user profile yet. Let's build list and detail views for the User model.