From c7087bc777a078f71a0705d9d0eba51d2679a206 Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Sun, 16 Nov 2014 17:21:33 +0100 Subject: [PATCH] Fixed #23862 -- Made ManyToManyRel.get_related_field() respect to_field. --- django/db/models/fields/related.py | 15 +++++++++++---- tests/m2m_through/models.py | 22 ++++++++++++++++++++++ tests/m2m_through/tests.py | 30 +++++++++++++++++++++++++++++- 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index beb9122e48..91e97bc0b3 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 aaee0aea3b..c30b21612b 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 25ec074d91..80c1e6c393 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'] + )