From 0e2d3b93043676975aa921a70b5faafef02cac5c Mon Sep 17 00:00:00 2001 From: Marco Fucci Date: Thu, 26 Mar 2015 18:47:07 +0000 Subject: [PATCH] [1.8.x] Fixed #24505 -- Fixed clash with hidden m2m fields. Added support for multiple m2m fields with the same 'to' model and with related_name set to '+'. Backport of 4ee08958f154594b538207a53c1d457687b3f7ae from master --- django/db/models/fields/related.py | 6 ++++++ docs/releases/1.8.5.txt | 4 ++++ tests/m2m_regress/models.py | 11 +++++++++++ tests/m2m_regress/tests.py | 13 ++++++++++++- tests/model_meta/results.py | 20 ++++++++++---------- tests/model_meta/tests.py | 2 +- 6 files changed, 44 insertions(+), 12 deletions(-) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index dec9e555f3..83e2a0aa8f 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -2532,6 +2532,12 @@ class ManyToManyField(RelatedField): # clash. if self.rel.symmetrical and (self.rel.to == "self" or self.rel.to == cls._meta.object_name): self.rel.related_name = "%s_rel_+" % name + elif self.rel.is_hidden(): + # If the backwards relation is disabled, replace the original + # related_name with one generated from the m2m field name. Django + # still uses backwards relations internally and we need to avoid + # clashes between multiple m2m fields with related_name == '+'. + self.rel.related_name = "_%s_+" % name super(ManyToManyField, self).contribute_to_class(cls, name, **kwargs) diff --git a/docs/releases/1.8.5.txt b/docs/releases/1.8.5.txt index 14893f4a72..b951ff98d8 100644 --- a/docs/releases/1.8.5.txt +++ b/docs/releases/1.8.5.txt @@ -52,3 +52,7 @@ Bugfixes (:ticket:`24921`). * Fixed the ``manage.py test --keepdb`` option on Oracle (:ticket:`25421`). + +* Fixed incorrect queries with multiple many-to-many fields on a model with the + same 'to' model and with ``related_name`` set to '+' (:ticket:`24505`, + :ticket:`25486`). diff --git a/tests/m2m_regress/models.py b/tests/m2m_regress/models.py index 4c1538dcbf..57f02b8f90 100644 --- a/tests/m2m_regress/models.py +++ b/tests/m2m_regress/models.py @@ -54,9 +54,13 @@ class SelfReferChildSibling(SelfRefer): # Many-to-Many relation between models, where one of the PK's isn't an Autofield +@python_2_unicode_compatible class Line(models.Model): name = models.CharField(max_length=100) + def __str__(self): + return self.name + class Worksheet(models.Model): id = models.CharField(primary_key=True, max_length=100) @@ -87,3 +91,10 @@ class RegressionModelSplit(BadModelWithSplit): Model with a split method should not cause an error in add_lazy_relation """ others = models.ManyToManyField('self') + + +# Regression for #24505 -- Two ManyToManyFields with the same "to" model +# and related_name set to '+'. +class Post(models.Model): + primary_lines = models.ManyToManyField(Line, related_name='+') + secondary_lines = models.ManyToManyField(Line, related_name='+') diff --git a/tests/m2m_regress/tests.py b/tests/m2m_regress/tests.py index 885efa593f..5e2891b0b4 100644 --- a/tests/m2m_regress/tests.py +++ b/tests/m2m_regress/tests.py @@ -5,7 +5,7 @@ from django.test import TestCase from django.utils import six from .models import ( - Entry, RegressionModelSplit, SelfRefer, SelfReferChild, + Entry, Line, Post, RegressionModelSplit, SelfRefer, SelfReferChild, SelfReferChildSibling, Tag, TagCollection, Worksheet, ) @@ -111,3 +111,14 @@ class M2MRegressionTests(TestCase): c1.refresh_from_db() self.assertQuerysetEqual(c1.tags.order_by('name'), ["", ""]) + + def test_multiple_forwards_only_m2m(self): + # Regression for #24505 - Multiple ManyToManyFields to same "to" + # model with related_name set to '+'. + foo = Line.objects.create(name='foo') + bar = Line.objects.create(name='bar') + post = Post.objects.create() + post.primary_lines.add(foo) + post.secondary_lines.add(bar) + self.assertQuerysetEqual(post.primary_lines.all(), ['']) + self.assertQuerysetEqual(post.secondary_lines.all(), ['']) diff --git a/tests/model_meta/results.py b/tests/model_meta/results.py index d70963206f..858b70b73a 100644 --- a/tests/model_meta/results.py +++ b/tests/model_meta/results.py @@ -319,7 +319,7 @@ TEST_RESULTS = { 'get_all_related_objects_with_model_hidden_local': { Person: ( ('+', None), - ('+', None), + ('_people_hidden_+', None), ('Person_following_inherited+', None), ('Person_following_inherited+', None), ('Person_friends_inherited+', None), @@ -334,7 +334,7 @@ TEST_RESULTS = { ), BasePerson: ( ('+', None), - ('+', None), + ('_basepeople_hidden_+', None), ('BasePerson_following_abstract+', None), ('BasePerson_following_abstract+', None), ('BasePerson_following_base+', None), @@ -380,10 +380,10 @@ TEST_RESULTS = { }, 'get_all_related_objects_with_model_hidden': { Person: ( - ('+', BasePerson), ('+', BasePerson), ('+', None), - ('+', None), + ('_basepeople_hidden_+', BasePerson), + ('_people_hidden_+', None), ('BasePerson_following_abstract+', BasePerson), ('BasePerson_following_abstract+', BasePerson), ('BasePerson_following_base+', BasePerson), @@ -416,7 +416,7 @@ TEST_RESULTS = { ), BasePerson: ( ('+', None), - ('+', None), + ('_basepeople_hidden_+', None), ('BasePerson_following_abstract+', None), ('BasePerson_following_abstract+', None), ('BasePerson_following_base+', None), @@ -730,7 +730,7 @@ TEST_RESULTS = { ('friends_base_rel_+', None), ('followers_base', None), ('relating_basepeople', None), - ('+', None), + ('_basepeople_hidden_+', None), ), Person: ( ('friends_abstract_rel_+', BasePerson), @@ -738,11 +738,11 @@ TEST_RESULTS = { ('friends_base_rel_+', BasePerson), ('followers_base', BasePerson), ('relating_basepeople', BasePerson), - ('+', BasePerson), + ('_basepeople_hidden_+', BasePerson), ('friends_inherited_rel_+', None), ('followers_concrete', None), ('relating_people', None), - ('+', None), + ('_people_hidden_+', None), ), Relation: ( ('m2m_abstract_rel', None), @@ -757,13 +757,13 @@ TEST_RESULTS = { 'friends_base_rel_+', 'followers_base', 'relating_basepeople', - '+', + '_basepeople_hidden_+', ], Person: [ 'friends_inherited_rel_+', 'followers_concrete', 'relating_people', - '+', + '_people_hidden_+', ], Relation: [ 'm2m_abstract_rel', diff --git a/tests/model_meta/tests.py b/tests/model_meta/tests.py index c77a286308..6db9a0f710 100644 --- a/tests/model_meta/tests.py +++ b/tests/model_meta/tests.py @@ -237,7 +237,7 @@ class RelationTreeTests(TestCase): self.assertEqual( sorted([field.related_query_name() for field in BasePerson._meta._relation_tree]), sorted([ - '+', '+', 'BasePerson_following_abstract+', 'BasePerson_following_abstract+', + '+', '_basepeople_hidden_+', 'BasePerson_following_abstract+', 'BasePerson_following_abstract+', 'BasePerson_following_base+', 'BasePerson_following_base+', 'BasePerson_friends_abstract+', 'BasePerson_friends_abstract+', 'BasePerson_friends_base+', 'BasePerson_friends_base+', 'BasePerson_m2m_abstract+', 'BasePerson_m2m_base+', 'Relating_basepeople+',