From 9d93e35c207a001de1aa9ca9165bdec824da9021 Mon Sep 17 00:00:00 2001 From: Clifford Gama Date: Sun, 3 Nov 2024 16:32:55 +0200 Subject: [PATCH] Fixed #17461 -- Doc'd the presumed order of foreign keys on the intermediary model of a self-referential m2m. Thanks Giannis Terzopoulos and Sarah Boyce for the reviews. --- docs/ref/models/fields.txt | 36 ++++++++++++++++++++++++++++++++++++ docs/topics/db/models.txt | 9 ++++++--- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 169d791c3d..845a2fb515 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -2041,6 +2041,42 @@ that control how the relationship functions. prefer Django not to create a backwards relation, set ``related_name`` to ``'+'``. + .. admonition:: Foreign key order in intermediary models + + When defining an asymmetric many-to-many relationship from a model to + itself using an intermediary model without defining + :attr:`through_fields`, the first foreign key in the intermediary model + will be treated as representing the source side of the + ``ManyToManyField``, and the second as the target side. For example:: + + from django.db import models + + + class Manufacturer(models.Model): + name = models.CharField(max_length=255) + clients = models.ManyToManyField( + "self", symmetrical=False, related_name="suppliers", through="Supply" + ) + + + class Supply(models.Model): + supplier = models.ForeignKey( + Manufacturer, models.CASCADE, related_name="supplies_given" + ) + client = models.ForeignKey( + Manufacturer, models.CASCADE, related_name="supplies_received" + ) + product = models.CharField(max_length=255) + + Here, the ``Manufacturer`` model defines the many-to-many relationship + with ``clients`` in its role as a supplier. Therefore, the ``supplier`` + foreign key (the source) must come before the ``client`` foreign key + (the target) in the intermediary ``Supply`` model. + + Specifying :attr:`through_fields=("supplier", "client") + <.ManyToManyField.through_fields>` on the ``ManyToManyField`` makes the + order of foreign keys on the ``through`` model irrelevant. + If you don't specify an explicit ``through`` model, there is still an implicit ``through`` model class you can use to directly access the table created to hold the association. It has three fields to link the models, a diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt index 98fb149b98..14233cbefd 100644 --- a/docs/topics/db/models.txt +++ b/docs/topics/db/models.txt @@ -533,9 +533,12 @@ There are a few restrictions on the intermediate model: * For a model which has a many-to-many relationship to itself through an intermediary model, two foreign keys to the same model are permitted, but they will be treated as the two (different) sides of the many-to-many - relationship. If there are *more* than two foreign keys though, you - must also specify ``through_fields`` as above, or a validation error - will be raised. + relationship. If :attr:`~.ManyToManyField.through_fields` is not specified, + the first foreign key will be taken to represent the source side of the + ``ManyToManyField``, while the second will be taken to represent the target + side. If there are *more* than two foreign keys though, you must specify + :attr:`~.ManyToManyField.through_fields` to explicitly indicate which foreign + keys to use, otherwise a validation error will be raised. Now that you have set up your :class:`~django.db.models.ManyToManyField` to use your intermediary model (``Membership``, in this case), you're ready to start