From 84291b7b8456b554f162b6eeaffa9b124907c1d2 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Wed, 26 Jan 2011 19:10:08 +0000 Subject: [PATCH] Fixed #15161 - Corrected handling of ManyToManyField with through table using to_field on its ForeignKeys. Thanks to adehnert for the report. git-svn-id: http://code.djangoproject.com/svn/django/trunk@15330 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/contenttypes/generic.py | 6 +++++ django/db/models/fields/related.py | 5 +++++ django/db/models/sql/query.py | 12 ++++++---- .../m2m_through_regress/models.py | 22 +++++++++++++++++++ .../m2m_through_regress/tests.py | 22 ++++++++++++++++++- 5 files changed, 62 insertions(+), 5 deletions(-) diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py index fd05a301b9..76f8eaf973 100644 --- a/django/contrib/contenttypes/generic.py +++ b/django/contrib/contenttypes/generic.py @@ -135,6 +135,12 @@ class GenericRelation(RelatedField, Field): def m2m_reverse_name(self): return self.rel.to._meta.pk.column + def m2m_target_field_name(self): + return self.model._meta.pk.name + + def m2m_reverse_target_field_name(self): + return self.rel.to._meta.pk.name + def contribute_to_class(self, cls, name): super(GenericRelation, self).contribute_to_class(cls, name) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index b9ffcbd238..6e18f6d470 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -1131,6 +1131,11 @@ class ManyToManyField(RelatedField, Field): self.m2m_field_name = curry(self._get_m2m_attr, related, 'name') self.m2m_reverse_field_name = curry(self._get_m2m_reverse_attr, related, 'name') + get_m2m_rel = curry(self._get_m2m_attr, related, 'rel') + self.m2m_target_field_name = lambda: get_m2m_rel().field_name + get_m2m_reverse_rel = curry(self._get_m2m_reverse_attr, related, 'rel') + self.m2m_reverse_target_field_name = lambda: get_m2m_reverse_rel().field_name + def set_attributes_from_rel(self): pass diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 178464c74d..43cb9d71b5 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -1282,12 +1282,14 @@ class Query(object): to_col2, opts, target) = cached_data else: table1 = field.m2m_db_table() - from_col1 = opts.pk.column + from_col1 = opts.get_field_by_name( + field.m2m_target_field_name())[0].column to_col1 = field.m2m_column_name() opts = field.rel.to._meta table2 = opts.db_table from_col2 = field.m2m_reverse_name() - to_col2 = opts.pk.column + to_col2 = opts.get_field_by_name( + field.m2m_reverse_target_field_name())[0].column target = opts.pk orig_opts._join_cache[name] = (table1, from_col1, to_col1, table2, from_col2, to_col2, opts, @@ -1335,12 +1337,14 @@ class Query(object): to_col2, opts, target) = cached_data else: table1 = field.m2m_db_table() - from_col1 = opts.pk.column + from_col1 = opts.get_field_by_name( + field.m2m_reverse_target_field_name())[0].column to_col1 = field.m2m_reverse_name() opts = orig_field.opts table2 = opts.db_table from_col2 = field.m2m_column_name() - to_col2 = opts.pk.column + to_col2 = opts.get_field_by_name( + field.m2m_target_field_name())[0].column target = opts.pk orig_opts._join_cache[name] = (table1, from_col1, to_col1, table2, from_col2, to_col2, opts, diff --git a/tests/regressiontests/m2m_through_regress/models.py b/tests/regressiontests/m2m_through_regress/models.py index ec87985765..121ef22470 100644 --- a/tests/regressiontests/m2m_through_regress/models.py +++ b/tests/regressiontests/m2m_through_regress/models.py @@ -53,3 +53,25 @@ class Through(ThroughBase): class B(models.Model): b_text = models.CharField(max_length=20) a_list = models.ManyToManyField(A, through=Through) + + +# Using to_field on the through model +class Car(models.Model): + make = models.CharField(max_length=20, unique=True) + drivers = models.ManyToManyField('Driver', through='CarDriver') + + def __unicode__(self, ): + return self.make + +class Driver(models.Model): + name = models.CharField(max_length=20, unique=True) + + def __unicode__(self, ): + return self.name + +class CarDriver(models.Model): + car = models.ForeignKey('Car', to_field='make') + driver = models.ForeignKey('Driver', to_field='name') + + def __unicode__(self, ): + return u"pk=%s car=%s driver=%s" % (str(self.pk), self.car, self.driver) diff --git a/tests/regressiontests/m2m_through_regress/tests.py b/tests/regressiontests/m2m_through_regress/tests.py index 406851acfd..fb2cb04d9b 100644 --- a/tests/regressiontests/m2m_through_regress/tests.py +++ b/tests/regressiontests/m2m_through_regress/tests.py @@ -7,7 +7,8 @@ from django.core import management from django.contrib.auth.models import User from django.test import TestCase -from models import Person, Group, Membership, UserMembership +from models import (Person, Group, Membership, UserMembership, + Car, Driver, CarDriver) class M2MThroughTestCase(TestCase): @@ -118,6 +119,25 @@ class M2MThroughTestCase(TestCase): ] ) + +class ToFieldThroughTests(TestCase): + def setUp(self): + self.car = Car.objects.create(make="Toyota") + self.driver = Driver.objects.create(name="Ryan Briscoe") + CarDriver.objects.create(car=self.car, driver=self.driver) + + def test_to_field(self): + self.assertQuerysetEqual( + self.car.drivers.all(), + [""] + ) + + def test_to_field_reverse(self): + self.assertQuerysetEqual( + self.driver.car_set.all(), + [""] + ) + class ThroughLoadDataTestCase(TestCase): fixtures = ["m2m_through"]