diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index beb9122e481..91e97bc0b3f 100644 --- a/django/db/models/fields/related.py +++ b/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): diff --git a/tests/m2m_through/models.py b/tests/m2m_through/models.py index aaee0aea3b2..c30b21612bb 100644 --- a/tests/m2m_through/models.py +++ b/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') diff --git a/tests/m2m_through/tests.py b/tests/m2m_through/tests.py index 25ec074d912..80c1e6c3931 100644 --- a/tests/m2m_through/tests.py +++ b/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'] + )