Skip to content

Commit

Permalink
Fixed #15161 - Corrected handling of ManyToManyField with through tab…
Browse files Browse the repository at this point in the history
…le 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
  • Loading branch information
carljm committed Jan 26, 2011
1 parent 33c1556 commit 84291b7
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 5 deletions.
6 changes: 6 additions & 0 deletions django/contrib/contenttypes/generic.py
Expand Up @@ -135,6 +135,12 @@ def m2m_column_name(self):
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)


Expand Down
5 changes: 5 additions & 0 deletions django/db/models/fields/related.py
Expand Up @@ -1131,6 +1131,11 @@ def contribute_to_related_class(self, cls, related):
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


Expand Down
12 changes: 8 additions & 4 deletions django/db/models/sql/query.py
Expand Up @@ -1282,12 +1282,14 @@ def setup_joins(self, names, opts, alias, dupe_multis, allow_many=True,
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,
Expand Down Expand Up @@ -1335,12 +1337,14 @@ def setup_joins(self, names, opts, alias, dupe_multis, allow_many=True,
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,
Expand Down
22 changes: 22 additions & 0 deletions tests/regressiontests/m2m_through_regress/models.py
Expand Up @@ -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)
22 changes: 21 additions & 1 deletion tests/regressiontests/m2m_through_regress/tests.py
Expand Up @@ -7,7 +7,8 @@
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):
Expand Down Expand Up @@ -118,6 +119,25 @@ def test_join_trimming(self):
] ]
) )



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"]


Expand Down

0 comments on commit 84291b7

Please sign in to comment.