So far, we have used ForeignKey relationships for linking different model types together. These create one-to-many relationships. For example, the model Photo has a ForeignKey relationship to User via the field uploader . You can only link each instance of Photo to one instance of User , but you can link an instance of User to many different instances of Photo .

You can also use the OneToOneField to create one-to-one relationships. In this case, you can only link each instance of Model A to one instance of Model B, and vice versa.
A common use case for one-to-one relationships is using profile models to extend the User model. In our case, as we have two user types - creators and subscribers - we may want to create a Creator and a Subscriber model, each with a OneToOneField relating to the User. These will contain information needed for each user, but that is only relevant to the creator and subscriber types.

Finally, you also have the option of the ManyToManyField , which you can use to create many-to-many relationships. Suppose Model A has a ManyToManyField linking it to Model B. That means that it can be linked to many different instances of Model B, and also that an instance of Model B can also be linked to many different instances of Model A.
On our site, we are going to use many-to-many relationships to allow users to follow other users. We want a user to be able to subscribe to many different creators and to be subscribed to by multiple other users, so a many-to-many relationship is perfect.

Now to implement this pattern in our models.
To facilitate a many-to-many relationship between users, you need to specify a ManyToManyField on the User model that links to another User . Let's call ours follows .
# authentication/models.py
class User(AbstractUser):
...
follows = models.ManyToManyField(
'self',
limit_choices_to={'role': CREATOR},
symmetrical=False
)The first argument in the ManyToManyField is the model that you are forming a relationship with. In our case, it is another User model, which we reference with 'self' .
You can limit which users can be followed by using the optional limit_choices_to keyword. We only want to allow users with the CREATOR role to be followed.
In this special case, where both models in the many-to-many relationship are the same, you also have to specify whether the relationship is symmetrical. Symmetrical relationships are ones in which there is no difference between the two parties in the relationship, such as linking two friends. One user follows another, so you specify symmetrical=False . The symmetrical argument is not required if you link to model other than the one in which the ManyToManyField is declared.
Now make and run the migrations.
(ENV) ~/fotoblog (master)
→ python manage.py makemigrations
Migrations for 'authentication':
authentication/migrations/0003_user_follows.py
- Add field follows to user
(ENV) ~/fotoblog (master)
→ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, authentication, blog, contenttypes, sessions
Running migrations:
Applying authentication.0004_user_follows... OKNext, let’s see how to use forms to create the many-to-many relationships in a view.
To create the many-to-many relationship in the view, you first need to define the appropriate form. As the fields are on the User model, you can use a ModelForm .
Create a form that allows the user to select other users to follow.
# blog/forms.py
from django.contrib.auth import get_user_model
User = get_user_model()
class FollowUsersForm(forms.ModelForm):
class Meta:
model = User
fields = ['follows']Now handle the form in a view.
# blog/views.py
@login_required
def follow_users(request):
form = forms.FollowUsersForm(instance=request.user)
if request.method == 'POST':
form = forms.FollowUsersForm(request.POST, instance=request.user)
if form.is_valid():
form.save()
return redirect('home')
return render(request, 'blog/follow_users_form.html', context={'form': form})With ManyToManyField you can use the ModelForm.save() function to create the many-to-many relationship.
Next, add the template as usual.
# blog/templates/blog/follow_users_form.html
{% extends 'base.html' %}
{% block content %}
<h2>Follow Users</h2>
<form method="post">
{{ form.as_p }}
{% csrf_token %}
<button type="submit" >Confirm</button>
</form>
{% endblock content %}Now add the URL pattern for the new page.
# fotoblog/urls.py
urlpatterns = [
…
path('follow-users/', blog.views.follow_users, name='follow_users')
]Add a link from the sidebar.
# templates/base.html
…
{% if perms.blog.add_photo %}
<p><a href="{% url 'photo_upload' %}">Upload Photo</a></p>
<p><a href="{% url 'create_multiple_photos' %}">Upload Multiple Photos</a></p>
{% endif %}
<p><a href="{% url 'follow_users' %}">Follow Users</a></p>
<p><a href="{% url 'upload_profile_photo' %}">Change Profile Photo</a></p>
...Let's see how that form looks.

We’ve done it. The form contains all of the creators and allows the user to select and follow them. It also allows you to select multiple users by holding the CTRL/Command button; the form then stores them in a many-to-many field.
Next, let’s see how to store information about many-to-many relationships.
Sometimes, you will want to store extra data on the relationship between two model instances linked by a many-to-many relationship.
Let's say you want to allow a Blog model to have multiple contributors and store a log of what each author has added.
To do this, create an intermediary table to store data on the relationship. When you use the ManyToManyField , Django produces this table on its own. All you are doing is creating it manually instead.
The intermediary table requires two ForeignKey relationships to the two models involved in the many-to-many relationship. For our application, one to the User and one to the Blog .
We are also going to add the field contribution to store information on which author contributed what.
Let’s see how this looks.
# blog/models.py
class BlogContributor(models.Model):
contributor = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
contribution = models.CharField(max_length=255, blank=True)
class Meta:
unique_together = ('contributor', 'blog')We set the unique_together attribute in the Meta class to ensure that there is only one instance of BlogContributor for each contributor - blog pairing.
ManyToManyField to Use the Intermediary TableNow add a ManyToManyField to Blog and tell it to use our intermediary table.
Do this by specifying the through keyword argument when declaring the field. You just need to point it towards the new model. For now, let's leave the old author field in place, but we will make it optional by allowing it to be null .
class Blog(models.Model):
...
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True)
contributors = models.ManyToManyField(settings.AUTH_USER_MODEL, through='BlogContributor')
...Now make the migrations.
→ python manage.py makemigrations
SystemCheckError: System check identified some issues:
ERRORS:
blog.Blog.author: (fields.E304) Reverse accessor for 'blog.Blog.author' clashes with reverse accessor for 'blog.Blog.contributors'.
HINT: Add or change a related_name argument to the definition for 'blog.Blog.author' or 'blog.Blog.contributors'.
blog.Blog.contributors: (fields.E304) Reverse accessor for 'blog.Blog.contributors' clashes with reverse accessor for 'blog.Blog.author'.
HINT: Add or change a related_name argument to the definition for 'blog.Blog.contributors' or 'blog.Blog.author'.Oh no! Django doesn't seem happy with the changes! The reason you see this is because there is a clash in our reverse accessor.
Before adding this field, you could have used user.blog_set to access a QuerySet of a particular author's Blog instances.
But now if we want to do the same for the contributors field - by retrieving all of the Blog instances for a User for which the user is present in the contributors of the Blog instance - again, the reverse look up would be user.blog_set .
To get around this, give the field a custom reverse accessor by specifying the related_name argument. If you set this to contributions , you can then access all of the Blog instances with the User as a contributor via user.contributions instead.
Let’s update the model accordingly.
# blog/models.py
class Blog(models.Model):
...
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True)
contributors = models.ManyToManyField(
settings.AUTH_USER_MODEL, through='BlogContributor', related_name='contributions')
...And then try those migrations again.
(ENV) ~/fotoblog (master)
→ python manage.py makemigrations
Migrations for 'blog':
blog/migrations/0003_auto_20210427_2325.py
- Create model BlogContributor
- Add field contributors to blog
(ENV) ~/fotoblog (master)
→ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, authentication, blog, contenttypes, sessions
Running migrations:
Applying blog.0003_auto_20210427_2325... OKNext, update the blog_and_photo_upload view to reflect the changes.
Let’s see how the view currently handles a POST request.
def blog_and_photo_upload(request):
...
if request.method == 'POST':
blog_form = forms.BlogForm(request.POST)
photo_form = forms.PhotoForm(request.POST, request.FILES)
if all([blog_form.is_valid(), photo_form.is_valid()]):
photo = photo_form.save(commit=False)
photo.uploader = request.user
photo.save()
blog = blog_form.save(commit=False)
blog.photo = photo
blog.author = request.user
blog.save()
...You can only store the relationships in the ManyToManyField once the model has been saved into the database.
In our case, we want to add the logged in user as a contributor once the blog instance is saved.
To do that, use the add method to create the many-to-many relationship. You can also specify the contents of the extra fields with the through_defaults keyword argument.
Let’s update the view to use the new ManyToManyField .
def blog_and_photo_upload(request):
...
if request.method == 'POST':
blog_form = forms.BlogForm(request.POST)
photo_form = forms.PhotoForm(request.POST, request.FILES)
if all([blog_form.is_valid(), photo_form.is_valid()]):
photo = photo_form.save(commit=False)
photo.uploader = request.user
photo.save()
blog = blog_form.save(commit=False)
blog.photo = photo
blog.save()
blog.contributors.add(request.user, through_defaults={'contribution': 'Primary Author'})
return redirect('home')
...The contributor relationship has now been saved as a many-to-many relationship instead.
We have added the capability to add multiple contributors for a blog post, but we still need to migrate the old data stored in the author field to the new format. That is what you are going to do next.
ForeignKey to a ManyToManyField Using a Custom Migration Now that our new relationship is set up, you can migrate the data from the old one-to-many relationship to a many-to-many relationship.
To do this, you will need to:
Create an empty migration in the blog app.
In the migration, iterate through each instance of the Blog model. Add the value of author to contributors with a default contribution of 'Primary Author' for each one.
Run the migration. If you are happy with the result, delete the author field on Blog .
Make and run the new migrations. You should have successfully migrated all of the existing data to the new dataset!
Once you’ve written your custom migration, see how it matches up against the solution in the GitHub repo.
Use the ManyToManyField to store many-to-many relationships.
Many-to-many relationships are stored in intermediary tables.
To store data on a relationship, create custom intermediary tables with additional fields and point to them with the through keyword.
Now that you’re familiar with many-to-many relationships, let’s check your model and form skills before moving on to the Django ORM and templates.