Browse Source

Fixed #23862 -- Made ManyToManyRel.get_related_field() respect to_field.

Simon Charette 10 years ago
parent
commit
c7087bc777
3 changed files with 62 additions and 5 deletions
  1. 11 4
      django/db/models/fields/related.py
  2. 22 0
      tests/m2m_through/models.py
  3. 29 1
      tests/m2m_through/tests.py

+ 11 - 4
django/db/models/fields/related.py

@@ -1348,11 +1348,18 @@ class ManyToManyRel(object):
 
     def get_related_field(self):
         """
-        Returns the field in the to' object to which this relationship is tied
-        (this is always the primary key on the target model). Provided for
-        symmetry with ManyToOneRel.
+        Returns the field in the 'to' object to which this relationship is tied.
+        Provided for symmetry with ManyToOneRel.
         """
-        return self.to._meta.pk
+        opts = self.through._meta
+        if self.through_fields:
+            field = opts.get_field(self.through_fields[0])
+        else:
+            for field in opts.fields:
+                rel = getattr(field, 'rel', None)
+                if rel and rel.to == self.to:
+                    break
+        return field.foreign_related_fields[0]
 
 
 class ForeignObject(RelatedField):

+ 22 - 0
tests/m2m_through/models.py

@@ -113,3 +113,25 @@ class Relationship(models.Model):
     another = models.ForeignKey(Employee, related_name="rel_another_set", null=True)
     target = models.ForeignKey(Employee, related_name="rel_target_set")
     source = models.ForeignKey(Employee, related_name="rel_source_set")
+
+
+class Ingredient(models.Model):
+    iname = models.CharField(max_length=20, unique=True)
+
+    class Meta:
+        ordering = ('iname',)
+
+
+class Recipe(models.Model):
+    rname = models.CharField(max_length=20, unique=True)
+    ingredients = models.ManyToManyField(
+        Ingredient, through='RecipeIngredient', related_name='recipes',
+    )
+
+    class Meta:
+        ordering = ('rname',)
+
+
+class RecipeIngredient(models.Model):
+    ingredient = models.ForeignKey(Ingredient, to_field='iname')
+    recipe = models.ForeignKey(Recipe, to_field='rname')

+ 29 - 1
tests/m2m_through/tests.py

@@ -6,7 +6,8 @@ from operator import attrgetter
 from django.test import TestCase
 
 from .models import (Person, Group, Membership, CustomMembership,
-    PersonSelfRefM2M, Friendship, Event, Invitation, Employee, Relationship)
+    PersonSelfRefM2M, Friendship, Event, Invitation, Employee, Relationship,
+    Ingredient, Recipe, RecipeIngredient)
 
 
 class M2mThroughTests(TestCase):
@@ -426,3 +427,30 @@ class M2mThroughReferentialTests(TestCase):
             ['peter', 'mary', 'harry'],
             attrgetter('name')
         )
+
+
+class M2mThroughToFieldsTests(TestCase):
+    def setUp(self):
+        self.pea = Ingredient.objects.create(iname='pea')
+        self.potato = Ingredient.objects.create(iname='potato')
+        self.tomato = Ingredient.objects.create(iname='tomato')
+        self.curry = Recipe.objects.create(rname='curry')
+        RecipeIngredient.objects.create(recipe=self.curry, ingredient=self.potato)
+        RecipeIngredient.objects.create(recipe=self.curry, ingredient=self.pea)
+        RecipeIngredient.objects.create(recipe=self.curry, ingredient=self.tomato)
+
+    def test_retrieval(self):
+        # Forward retrieval
+        self.assertQuerysetEqual(
+            self.curry.ingredients.all(),
+            [self.pea, self.potato, self.tomato], lambda x: x
+        )
+        # Backward retrieval
+        self.assertEqual(self.tomato.recipes.get(), self.curry)
+
+    def test_choices(self):
+        field = Recipe._meta.get_field('ingredients')
+        self.assertEqual(
+            [choice[0] for choice in field.get_choices(include_blank=False)],
+            ['pea', 'potato', 'tomato']
+        )