[1.2.X] Fixed #15161 - Corrected handling of ManyToManyField with through table using to_field on its ForeignKeys. Thanks to adehnert for the report.

Backport of r15330 from trunk.

git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@15331 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Carl Meyer 2011-01-26 19:27:46 +00:00
parent 5b8c593053
commit 10b4d93f50
5 changed files with 62 additions and 5 deletions

View File

@ -131,6 +131,12 @@ class GenericRelation(RelatedField, Field):
def m2m_reverse_name(self): def m2m_reverse_name(self):
return self.rel.to._meta.pk.column 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): def contribute_to_class(self, cls, name):
super(GenericRelation, self).contribute_to_class(cls, name) super(GenericRelation, self).contribute_to_class(cls, name)

View File

@ -1144,6 +1144,11 @@ class ManyToManyField(RelatedField, Field):
self.m2m_field_name = curry(self._get_m2m_attr, related, 'name') self.m2m_field_name = curry(self._get_m2m_attr, related, 'name')
self.m2m_reverse_field_name = curry(self._get_m2m_reverse_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): def set_attributes_from_rel(self):
pass pass

View File

@ -1283,12 +1283,14 @@ class Query(object):
to_col2, opts, target) = cached_data to_col2, opts, target) = cached_data
else: else:
table1 = field.m2m_db_table() 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() to_col1 = field.m2m_column_name()
opts = field.rel.to._meta opts = field.rel.to._meta
table2 = opts.db_table table2 = opts.db_table
from_col2 = field.m2m_reverse_name() 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 target = opts.pk
orig_opts._join_cache[name] = (table1, from_col1, orig_opts._join_cache[name] = (table1, from_col1,
to_col1, table2, from_col2, to_col2, opts, to_col1, table2, from_col2, to_col2, opts,
@ -1336,12 +1338,14 @@ class Query(object):
to_col2, opts, target) = cached_data to_col2, opts, target) = cached_data
else: else:
table1 = field.m2m_db_table() 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() to_col1 = field.m2m_reverse_name()
opts = orig_field.opts opts = orig_field.opts
table2 = opts.db_table table2 = opts.db_table
from_col2 = field.m2m_column_name() 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 target = opts.pk
orig_opts._join_cache[name] = (table1, from_col1, orig_opts._join_cache[name] = (table1, from_col1,
to_col1, table2, from_col2, to_col2, opts, to_col1, table2, from_col2, to_col2, opts,

View File

@ -53,3 +53,25 @@ class Through(ThroughBase):
class B(models.Model): class B(models.Model):
b_text = models.CharField(max_length=20) b_text = models.CharField(max_length=20)
a_list = models.ManyToManyField(A, through=Through) 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)

View File

@ -7,7 +7,8 @@ from django.core import management
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.test import TestCase 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): 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(),
["<Driver: Ryan Briscoe>"]
)
def test_to_field_reverse(self):
self.assertQuerysetEqual(
self.driver.car_set.all(),
["<Car: Toyota>"]
)
class ThroughLoadDataTestCase(TestCase): class ThroughLoadDataTestCase(TestCase):
fixtures = ["m2m_through"] fixtures = ["m2m_through"]