Many-to-many relationships

To define a many-to-many relationship, use ManyToManyField.

In this example, an Article can be published in multiple Publication objects, and a Publication has multiple Article objects:

  1. from django.db import models
  2. class Publication(models.Model):
  3. title = models.CharField(max_length=30)
  4. class Meta:
  5. ordering = ['title']
  6. def __str__(self):
  7. return self.title
  8. class Article(models.Model):
  9. headline = models.CharField(max_length=100)
  10. publications = models.ManyToManyField(Publication)
  11. class Meta:
  12. ordering = ['headline']
  13. def __str__(self):
  14. return self.headline

What follows are examples of operations that can be performed using the Python API facilities.

Create a few Publications:

  1. >>> p1 = Publication(title='The Python Journal')
  2. >>> p1.save()
  3. >>> p2 = Publication(title='Science News')
  4. >>> p2.save()
  5. >>> p3 = Publication(title='Science Weekly')
  6. >>> p3.save()

Create an Article:

  1. >>> a1 = Article(headline='Django lets you build web apps easily')

You can’t associate it with a Publication until it’s been saved:

  1. >>> a1.publications.add(p1)
  2. Traceback (most recent call last):
  3. ...
  4. ValueError: "<Article: Django lets you build web apps easily>" needs to have a value for field "id" before this many-to-many relationship can be used.

Save it!

  1. >>> a1.save()

Associate the Article with a Publication:

  1. >>> a1.publications.add(p1)

Create another Article, and set it to appear in the Publications:

  1. >>> a2 = Article(headline='NASA uses Python')
  2. >>> a2.save()
  3. >>> a2.publications.add(p1, p2)
  4. >>> a2.publications.add(p3)

Adding a second time is OK, it will not duplicate the relation:

  1. >>> a2.publications.add(p3)

Adding an object of the wrong type raises TypeError:

  1. >>> a2.publications.add(a1)
  2. Traceback (most recent call last):
  3. ...
  4. TypeError: 'Publication' instance expected

Create and add a Publication to an Article in one step using create():

  1. >>> new_publication = a2.publications.create(title='Highlights for Children')

Article objects have access to their related Publication objects:

  1. >>> a1.publications.all()
  2. <QuerySet [<Publication: The Python Journal>]>
  3. >>> a2.publications.all()
  4. <QuerySet [<Publication: Highlights for Children>, <Publication: Science News>, <Publication: Science Weekly>, <Publication: The Python Journal>]>

Publication objects have access to their related Article objects:

  1. >>> p2.article_set.all()
  2. <QuerySet [<Article: NASA uses Python>]>
  3. >>> p1.article_set.all()
  4. <QuerySet [<Article: Django lets you build web apps easily>, <Article: NASA uses Python>]>
  5. >>> Publication.objects.get(id=4).article_set.all()
  6. <QuerySet [<Article: NASA uses Python>]>

Many-to-many relationships can be queried using lookups across relationships:

  1. >>> Article.objects.filter(publications__id=1)
  2. <QuerySet [<Article: Django lets you build web apps easily>, <Article: NASA uses Python>]>
  3. >>> Article.objects.filter(publications__pk=1)
  4. <QuerySet [<Article: Django lets you build web apps easily>, <Article: NASA uses Python>]>
  5. >>> Article.objects.filter(publications=1)
  6. <QuerySet [<Article: Django lets you build web apps easily>, <Article: NASA uses Python>]>
  7. >>> Article.objects.filter(publications=p1)
  8. <QuerySet [<Article: Django lets you build web apps easily>, <Article: NASA uses Python>]>
  9. >>> Article.objects.filter(publications__title__startswith="Science")
  10. <QuerySet [<Article: NASA uses Python>, <Article: NASA uses Python>]>
  11. >>> Article.objects.filter(publications__title__startswith="Science").distinct()
  12. <QuerySet [<Article: NASA uses Python>]>

The count() function respects distinct() as well:

  1. >>> Article.objects.filter(publications__title__startswith="Science").count()
  2. 2
  3. >>> Article.objects.filter(publications__title__startswith="Science").distinct().count()
  4. 1
  5. >>> Article.objects.filter(publications__in=[1,2]).distinct()
  6. <QuerySet [<Article: Django lets you build web apps easily>, <Article: NASA uses Python>]>
  7. >>> Article.objects.filter(publications__in=[p1,p2]).distinct()
  8. <QuerySet [<Article: Django lets you build web apps easily>, <Article: NASA uses Python>]>

Reverse m2m queries are supported (i.e., starting at the table that doesn’t have a ManyToManyField):

  1. >>> Publication.objects.filter(id=1)
  2. <QuerySet [<Publication: The Python Journal>]>
  3. >>> Publication.objects.filter(pk=1)
  4. <QuerySet [<Publication: The Python Journal>]>
  5. >>> Publication.objects.filter(article__headline__startswith="NASA")
  6. <QuerySet [<Publication: Highlights for Children>, <Publication: Science News>, <Publication: Science Weekly>, <Publication: The Python Journal>]>
  7. >>> Publication.objects.filter(article__id=1)
  8. <QuerySet [<Publication: The Python Journal>]>
  9. >>> Publication.objects.filter(article__pk=1)
  10. <QuerySet [<Publication: The Python Journal>]>
  11. >>> Publication.objects.filter(article=1)
  12. <QuerySet [<Publication: The Python Journal>]>
  13. >>> Publication.objects.filter(article=a1)
  14. <QuerySet [<Publication: The Python Journal>]>
  15. >>> Publication.objects.filter(article__in=[1,2]).distinct()
  16. <QuerySet [<Publication: Highlights for Children>, <Publication: Science News>, <Publication: Science Weekly>, <Publication: The Python Journal>]>
  17. >>> Publication.objects.filter(article__in=[a1,a2]).distinct()
  18. <QuerySet [<Publication: Highlights for Children>, <Publication: Science News>, <Publication: Science Weekly>, <Publication: The Python Journal>]>

Excluding a related item works as you would expect, too (although the SQL involved is a little complex):

  1. >>> Article.objects.exclude(publications=p2)
  2. <QuerySet [<Article: Django lets you build web apps easily>]>

If we delete a Publication, its Articles won’t be able to access it:

  1. >>> p1.delete()
  2. >>> Publication.objects.all()
  3. <QuerySet [<Publication: Highlights for Children>, <Publication: Science News>, <Publication: Science Weekly>]>
  4. >>> a1 = Article.objects.get(pk=1)
  5. >>> a1.publications.all()
  6. <QuerySet []>

If we delete an Article, its Publications won’t be able to access it:

  1. >>> a2.delete()
  2. >>> Article.objects.all()
  3. <QuerySet [<Article: Django lets you build web apps easily>]>
  4. >>> p2.article_set.all()
  5. <QuerySet []>

Adding via the ‘other’ end of an m2m:

  1. >>> a4 = Article(headline='NASA finds intelligent life on Earth')
  2. >>> a4.save()
  3. >>> p2.article_set.add(a4)
  4. >>> p2.article_set.all()
  5. <QuerySet [<Article: NASA finds intelligent life on Earth>]>
  6. >>> a4.publications.all()
  7. <QuerySet [<Publication: Science News>]>

Adding via the other end using keywords:

  1. >>> new_article = p2.article_set.create(headline='Oxygen-free diet works wonders')
  2. >>> p2.article_set.all()
  3. <QuerySet [<Article: NASA finds intelligent life on Earth>, <Article: Oxygen-free diet works wonders>]>
  4. >>> a5 = p2.article_set.all()[1]
  5. >>> a5.publications.all()
  6. <QuerySet [<Publication: Science News>]>

Removing Publication from an Article:

  1. >>> a4.publications.remove(p2)
  2. >>> p2.article_set.all()
  3. <QuerySet [<Article: Oxygen-free diet works wonders>]>
  4. >>> a4.publications.all()
  5. <QuerySet []>

And from the other end:

  1. >>> p2.article_set.remove(a5)
  2. >>> p2.article_set.all()
  3. <QuerySet []>
  4. >>> a5.publications.all()
  5. <QuerySet []>

Relation sets can be set:

  1. >>> a4.publications.all()
  2. <QuerySet [<Publication: Science News>]>
  3. >>> a4.publications.set([p3])
  4. >>> a4.publications.all()
  5. <QuerySet [<Publication: Science Weekly>]>

Relation sets can be cleared:

  1. >>> p2.article_set.clear()
  2. >>> p2.article_set.all()
  3. <QuerySet []>

And you can clear from the other end:

  1. >>> p2.article_set.add(a4, a5)
  2. >>> p2.article_set.all()
  3. <QuerySet [<Article: NASA finds intelligent life on Earth>, <Article: Oxygen-free diet works wonders>]>
  4. >>> a4.publications.all()
  5. <QuerySet [<Publication: Science News>, <Publication: Science Weekly>]>
  6. >>> a4.publications.clear()
  7. >>> a4.publications.all()
  8. <QuerySet []>
  9. >>> p2.article_set.all()
  10. <QuerySet [<Article: Oxygen-free diet works wonders>]>

Recreate the Article and Publication we have deleted:

  1. >>> p1 = Publication(title='The Python Journal')
  2. >>> p1.save()
  3. >>> a2 = Article(headline='NASA uses Python')
  4. >>> a2.save()
  5. >>> a2.publications.add(p1, p2, p3)

Bulk delete some Publications - references to deleted publications should go:

  1. >>> Publication.objects.filter(title__startswith='Science').delete()
  2. >>> Publication.objects.all()
  3. <QuerySet [<Publication: Highlights for Children>, <Publication: The Python Journal>]>
  4. >>> Article.objects.all()
  5. <QuerySet [<Article: Django lets you build web apps easily>, <Article: NASA finds intelligent life on Earth>, <Article: NASA uses Python>, <Article: Oxygen-free diet works wonders>]>
  6. >>> a2.publications.all()
  7. <QuerySet [<Publication: The Python Journal>]>

Bulk delete some articles - references to deleted objects should go:

  1. >>> q = Article.objects.filter(headline__startswith='Django')
  2. >>> print(q)
  3. <QuerySet [<Article: Django lets you build web apps easily>]>
  4. >>> q.delete()

After the delete(), the QuerySet cache needs to be cleared, and the referenced objects should be gone:

  1. >>> print(q)
  2. <QuerySet []>
  3. >>> p1.article_set.all()
  4. <QuerySet [<Article: NASA uses Python>]>