many_to_many.txt 10.2 KB
Newer Older
1
==========================
2
Many-to-many relationships
3
==========================
4 5 6

.. highlight:: pycon

7 8
To define a many-to-many relationship, use
:class:`~django.db.models.ManyToManyField`.
9 10 11 12 13 14 15 16 17 18 19

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

.. code-block:: python

    from django.db import models

    class Publication(models.Model):
        title = models.CharField(max_length=30)

20
        def __str__(self):              # __unicode__ on Python 2
21 22 23 24 25 26 27 28 29
            return self.title

        class Meta:
            ordering = ('title',)

    class Article(models.Model):
        headline = models.CharField(max_length=100)
        publications = models.ManyToManyField(Publication)

30
        def __str__(self):              # __unicode__ on Python 2
31 32 33 34 35 36
            return self.headline

        class Meta:
            ordering = ('headline',)

What follows are examples of operations that can be performed using the Python
37 38 39 40
API facilities. Note that if you are using :ref:`an intermediate model
<intermediary-manytomany>` for a many-to-many relationship, some of the related
manager's methods are disabled, so some of these examples won't work with such
models.
41

42
Create a couple of ``Publications``::
43 44 45 46 47 48 49 50

    >>> p1 = Publication(title='The Python Journal')
    >>> p1.save()
    >>> p2 = Publication(title='Science News')
    >>> p2.save()
    >>> p3 = Publication(title='Science Weekly')
    >>> p3.save()

51
Create an ``Article``::
52 53 54

    >>> a1 = Article(headline='Django lets you build Web apps easily')

55
You can't associate it with a ``Publication`` until it's been saved::
56 57 58 59 60 61 62 63 64 65 66

    >>> a1.publications.add(p1)
    Traceback (most recent call last):
    ...
    ValueError: 'Article' instance needs to have a primary key value before a many-to-many relationship can be used.

Save it!
::

    >>> a1.save()

67
Associate the ``Article`` with a ``Publication``::
68 69 70

    >>> a1.publications.add(p1)

71
Create another ``Article``, and set it to appear in both ``Publications``::
72 73 74 75 76 77 78 79 80 81

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

Adding a second time is OK::

    >>> a2.publications.add(p3)

82
Adding an object of the wrong type raises :exc:`TypeError`::
83 84 85 86 87 88

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

89 90
Create and add a ``Publication`` to an ``Article`` in one step using
:meth:`~django.db.models.fields.related.RelatedManager.create`::
91 92 93

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

94
``Article`` objects have access to their related ``Publication`` objects::
95 96

    >>> a1.publications.all()
97
    <QuerySet [<Publication: The Python Journal>]>
98
    >>> a2.publications.all()
99
    <QuerySet [<Publication: Highlights for Children>, <Publication: Science News>, <Publication: Science Weekly>, <Publication: The Python Journal>]>
100

101
``Publication`` objects have access to their related ``Article`` objects::
102 103

    >>> p2.article_set.all()
104
    <QuerySet [<Article: NASA uses Python>]>
105
    >>> p1.article_set.all()
106
    <QuerySet [<Article: Django lets you build Web apps easily>, <Article: NASA uses Python>]>
107
    >>> Publication.objects.get(id=4).article_set.all()
108
    <QuerySet [<Article: NASA uses Python>]>
109

110 111
Many-to-many relationships can be queried using :ref:`lookups across
relationships <lookups-that-span-relationships>`::
112

113
    >>> Article.objects.filter(publications__id=1)
114
    <QuerySet [<Article: Django lets you build Web apps easily>, <Article: NASA uses Python>]>
115
    >>> Article.objects.filter(publications__pk=1)
116
    <QuerySet [<Article: Django lets you build Web apps easily>, <Article: NASA uses Python>]>
117
    >>> Article.objects.filter(publications=1)
118
    <QuerySet [<Article: Django lets you build Web apps easily>, <Article: NASA uses Python>]>
119
    >>> Article.objects.filter(publications=p1)
120
    <QuerySet [<Article: Django lets you build Web apps easily>, <Article: NASA uses Python>]>
121 122

    >>> Article.objects.filter(publications__title__startswith="Science")
123
    <QuerySet [<Article: NASA uses Python>, <Article: NASA uses Python>]>
124 125

    >>> Article.objects.filter(publications__title__startswith="Science").distinct()
126
    <QuerySet [<Article: NASA uses Python>]>
127

128 129
The :meth:`~django.db.models.query.QuerySet.count` function respects
:meth:`~django.db.models.query.QuerySet.distinct` as well::
130 131 132 133 134 135 136 137

    >>> Article.objects.filter(publications__title__startswith="Science").count()
    2

    >>> Article.objects.filter(publications__title__startswith="Science").distinct().count()
    1

    >>> Article.objects.filter(publications__in=[1,2]).distinct()
138
    <QuerySet [<Article: Django lets you build Web apps easily>, <Article: NASA uses Python>]>
139
    >>> Article.objects.filter(publications__in=[p1,p2]).distinct()
140
    <QuerySet [<Article: Django lets you build Web apps easily>, <Article: NASA uses Python>]>
141 142

Reverse m2m queries are supported (i.e., starting at the table that doesn't have
143
a :class:`~django.db.models.ManyToManyField`)::
144

145
    >>> Publication.objects.filter(id=1)
146
    <QuerySet [<Publication: The Python Journal>]>
147
    >>> Publication.objects.filter(pk=1)
148
    <QuerySet [<Publication: The Python Journal>]>
149 150

    >>> Publication.objects.filter(article__headline__startswith="NASA")
151
    <QuerySet [<Publication: Highlights for Children>, <Publication: Science News>, <Publication: Science Weekly>, <Publication: The Python Journal>]>
152

153
    >>> Publication.objects.filter(article__id=1)
154
    <QuerySet [<Publication: The Python Journal>]>
155
    >>> Publication.objects.filter(article__pk=1)
156
    <QuerySet [<Publication: The Python Journal>]>
157
    >>> Publication.objects.filter(article=1)
158
    <QuerySet [<Publication: The Python Journal>]>
159
    >>> Publication.objects.filter(article=a1)
160
    <QuerySet [<Publication: The Python Journal>]>
161 162

    >>> Publication.objects.filter(article__in=[1,2]).distinct()
163
    <QuerySet [<Publication: Highlights for Children>, <Publication: Science News>, <Publication: Science Weekly>, <Publication: The Python Journal>]>
164
    >>> Publication.objects.filter(article__in=[a1,a2]).distinct()
165
    <QuerySet [<Publication: Highlights for Children>, <Publication: Science News>, <Publication: Science Weekly>, <Publication: The Python Journal>]>
166 167 168 169 170

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

    >>> Article.objects.exclude(publications=p2)
171
    <QuerySet [<Article: Django lets you build Web apps easily>]>
172

173
If we delete a ``Publication``, its ``Articles`` won't be able to access it::
174 175 176

    >>> p1.delete()
    >>> Publication.objects.all()
177
    <QuerySet [<Publication: Highlights for Children>, <Publication: Science News>, <Publication: Science Weekly>]>
178 179
    >>> a1 = Article.objects.get(pk=1)
    >>> a1.publications.all()
180
    <QuerySet []>
181

182
If we delete an ``Article``, its ``Publications`` won't be able to access it::
183 184 185

    >>> a2.delete()
    >>> Article.objects.all()
186
    <QuerySet [<Article: Django lets you build Web apps easily>]>
187
    >>> p2.article_set.all()
188
    <QuerySet []>
189 190 191 192 193 194 195

Adding via the 'other' end of an m2m::

    >>> a4 = Article(headline='NASA finds intelligent life on Earth')
    >>> a4.save()
    >>> p2.article_set.add(a4)
    >>> p2.article_set.all()
196
    <QuerySet [<Article: NASA finds intelligent life on Earth>]>
197
    >>> a4.publications.all()
198
    <QuerySet [<Publication: Science News>]>
199 200 201 202 203

Adding via the other end using keywords::

    >>> new_article = p2.article_set.create(headline='Oxygen-free diet works wonders')
    >>> p2.article_set.all()
204
    <QuerySet [<Article: NASA finds intelligent life on Earth>, <Article: Oxygen-free diet works wonders>]>
205 206
    >>> a5 = p2.article_set.all()[1]
    >>> a5.publications.all()
207
    <QuerySet [<Publication: Science News>]>
208

209
Removing ``Publication`` from an ``Article``::
210 211 212

    >>> a4.publications.remove(p2)
    >>> p2.article_set.all()
213
    <QuerySet [<Article: Oxygen-free diet works wonders>]>
214
    >>> a4.publications.all()
215
    <QuerySet []>
216 217 218 219 220

And from the other end::

    >>> p2.article_set.remove(a5)
    >>> p2.article_set.all()
221
    <QuerySet []>
222
    >>> a5.publications.all()
223
    <QuerySet []>
224

225
Relation sets can be set::
226 227

    >>> a4.publications.all()
228
    <QuerySet [<Publication: Science News>]>
229
    >>> a4.publications.set([p3])
230
    >>> a4.publications.all()
231
    <QuerySet [<Publication: Science Weekly>]>
232 233 234 235 236

Relation sets can be cleared::

    >>> p2.article_set.clear()
    >>> p2.article_set.all()
237
    <QuerySet []>
238 239 240 241 242

And you can clear from the other end::

    >>> p2.article_set.add(a4, a5)
    >>> p2.article_set.all()
243
    <QuerySet [<Article: NASA finds intelligent life on Earth>, <Article: Oxygen-free diet works wonders>]>
244
    >>> a4.publications.all()
245
    <QuerySet [<Publication: Science News>, <Publication: Science Weekly>]>
246 247
    >>> a4.publications.clear()
    >>> a4.publications.all()
248
    <QuerySet []>
249
    >>> p2.article_set.all()
250
    <QuerySet [<Article: Oxygen-free diet works wonders>]>
251

252
Recreate the ``Article`` and ``Publication`` we have deleted::
253 254 255 256 257 258 259

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

260 261
Bulk delete some ``Publications`` - references to deleted publications should
go::
262 263 264

    >>> Publication.objects.filter(title__startswith='Science').delete()
    >>> Publication.objects.all()
265
    <QuerySet [<Publication: Highlights for Children>, <Publication: The Python Journal>]>
266
    >>> Article.objects.all()
267
    <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>]>
268
    >>> a2.publications.all()
269
    <QuerySet [<Publication: The Python Journal>]>
270 271 272 273

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

    >>> q = Article.objects.filter(headline__startswith='Django')
274
    >>> print(q)
275
    <QuerySet [<Article: Django lets you build Web apps easily>]>
276 277
    >>> q.delete()

278 279 280
After the :meth:`~django.db.models.query.QuerySet.delete`, the
:class:`~django.db.models.query.QuerySet` cache needs to be cleared, and the
referenced objects should be gone::
281

282
    >>> print(q)
283
    <QuerySet []>
284
    >>> p1.article_set.all()
285
    <QuerySet [<Article: NASA uses Python>]>