Many-to-Many Relations
Learning Objectives
Use Django's
ManyToManyField
to enable a m:m relationshipUse
mainmodel.submodels
andsubmodel.mainmodel_set
to access related data from either Model.
Yesterday we learned the first of two common types of data relations: One-to-Many
or 1:m. In this relationship, one model is said to "own" multiple objects of another model. It's a very common relationship and we can readily think of examples: one customer has many orders; one conference has many attendees; one restaurant has many menu items; one collector has many cats.
The next kind of relationship we will learn about is the Many-to-Many
relationship, often abbreviated m:m
or n:m
. You can think of this as a two-way one-to-many relationship where each model "owns" multiple objects of the other model. Some examples of this include: one band can have many genres of music that they play, and one genre has many bands in it; one article has many category tags, and each category tag is linked to many articles; and today's example - one cat can have many toys, and each toy could be owned by multiple cats.
If we were writing all of this ourselves we would normally need an intermediary table, called a join table
, to enable the references between tables. We use a join table because if we didn't, the nature of a many-to-many relationship would require one of our related tables to contain duplicate data. BAD!
We aren't writing it ourselves, though. (Not yet, anyway.) Django has a very easy way to link models together in a many-to-many relationship that takes care of the join table and references in the database for us. Let's see how it works.
Adding the CatToy Model
We are creating a new model to represent toys that our cat owns. We will call it CatToy
. Each CatToy
can be owned by many Cats
and each Cat
can own many CatToys
. Open up your main_app/models.py
and add a class for the CatToy
model above the Cat
model class:
We don't need to represent much data about the toys to set up the relationship. We've included a __str__
method so that the Model will nicely print its name. We also included a get_absolute_url()
method that will allow us to omit all the success_urls
and redirect URLs from our generic editing views. Once we've added this, save the file, make new migrations, and run them:
Register the new model
Go ahead and add this model to the main_app/admin.py
:
Now we can easily read, create, update, and delete CatToys via the admin interface. Let's now add some basic CRUD routes for it.
CRUD Routes for the New CatToy Model
We do need the ability to create, read, update, and delete each CatToy since it is one of our models. Let's quickly set up URLs, views, and template forms for that just like we did in the CRUD Forms lesson:
URLs
We will need 5 total routes for this new Model: read all, read one, create one, update one, delete one. The URLs will largely follow the same pattern as the ones for Cat
. Here are all 5 that we need to add. In the interest of time, just paste these into the urlpatterns
list in your main_app/urls.py
:
Views
We can also use the same patterns for our corrsponding view functions as we used for Cat
. Don't forget to import
your CatToy model, then add these into your main_app/views.py
:
Templates
Lastly, we need a few templates for this new model. We need the two forms for our generic editing views and we need an index and a details page. Our two forms will go into main_app/templates/main_app
. One will be cattoy_form.html
and the other will be cattoy_confirm_delete.html
. Recall that this is the naming convention for these form templates when we are using generic editing views
. Let's add those now:
Now we'll add the two "read" pages, cattoys\index.html
and cattoys\show.html
. We need to make a directory inside our templates
directory named cattoys
and our pages will go in there:
TEST!
This is a great place to test what we've written. We've just added 5 new routes. We need to test them. Restart your server if it was running and let's test these routes:
Test
http://localhost:8000/cattoys/create
for creating new cat toys. Add a couple.Test
http://localhost:8000/cattoys
for showing the toys you've added.Click into one of the toys to test the
show
page.Test the update link on one of the toys.
Test the delete link for one of the toys.
Adding the Many-to-Many Field to the Model
With that code in place, we can start working on updating our existing Cat Model and pages. The way that we establish a m:m relationship is by adding a ManyToManyField
to a model. Django requires only one of our models to have this field and it takes care of the rest. We must only decide which model to put the field into. I think we will mostly be viewing toys by looking at the Cat that owns them so let's add cattoys
to the Cat
model:
You'll notice that this line doesn't know what CatToy
is if your CatToy
class is below you Cat
class. If you're running into this issues, swap the position of the two models in the file.
That is really all we need to do to set up the relationship. But we have changed a model so we now need to generate some new migrations and run them. Open up a terminal and run these commands from your project folder:
...then...
Now we need to update our CatCreate
and CatUpdate
view classes to include the new field:
Save everything, restart the server if necessary and visit the Cat Create page. Yay! Now we can add multiple cat toys to any of our Cats.
Add cattoys
to the Cat show page
cattoys
to the Cat show pageIn the style of our detail pages so far, let's add something to show toys. Right above the for
block for the photos in cats/show.html
, add this code:
Add Cat owners to CatToy detail page
Because this is a many-to-many, it probably makes sense to show all the related Cats that own any particular toy. Let's update the cattoys/show.html
page.
Conclusion
Congratulations! You've completed a substantial full-stack app in Python and Django.
Last updated