From a9a7421ab83361746549d574ca13438ba93c95fe Mon Sep 17 00:00:00 2001 From: manav014 Date: Wed, 30 Dec 2020 07:52:17 +0530 Subject: [PATCH] Fixed #32294 -- Prevented ManyToManyField's hidden related name collisions between apps. --- django/db/models/fields/related.py | 6 +++- .../test_relative_fields.py | 28 ++++++++++++++++++- tests/model_meta/results.py | 22 +++++++-------- tests/model_meta/tests.py | 2 +- 4 files changed, 44 insertions(+), 14 deletions(-) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 4c2247c08b..899ae8efe8 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -1614,7 +1614,11 @@ class ManyToManyField(RelatedField): # 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.remote_field.related_name = "_%s_%s_+" % (cls.__name__.lower(), name) + self.remote_field.related_name = '_%s_%s_%s_+' % ( + cls._meta.app_label, + cls.__name__.lower(), + name, + ) super().contribute_to_class(cls, name, **kwargs) diff --git a/tests/invalid_models_tests/test_relative_fields.py b/tests/invalid_models_tests/test_relative_fields.py index 82b1b426cf..8909b12214 100644 --- a/tests/invalid_models_tests/test_relative_fields.py +++ b/tests/invalid_models_tests/test_relative_fields.py @@ -3,7 +3,7 @@ from unittest import mock from django.core.checks import Error, Warning as DjangoWarning from django.db import connection, models from django.test.testcases import SimpleTestCase -from django.test.utils import isolate_apps, override_settings +from django.test.utils import isolate_apps, modify_settings, override_settings @isolate_apps('invalid_models_tests') @@ -1025,6 +1025,32 @@ class ReverseQueryNameClashTests(SimpleTestCase): ), ]) + @modify_settings(INSTALLED_APPS={'append': 'basic'}) + @isolate_apps('basic', 'invalid_models_tests') + def test_no_clash_across_apps_without_accessor(self): + class Target(models.Model): + class Meta: + app_label = 'invalid_models_tests' + + class Model(models.Model): + m2m = models.ManyToManyField(Target, related_name='+') + + class Meta: + app_label = 'basic' + + def _test(): + # Define model with the same name. + class Model(models.Model): + m2m = models.ManyToManyField(Target, related_name='+') + + class Meta: + app_label = 'invalid_models_tests' + + self.assertEqual(Model.check(), []) + + _test() + self.assertEqual(Model.check(), []) + @isolate_apps('invalid_models_tests') class ExplicitRelatedNameClashTests(SimpleTestCase): diff --git a/tests/model_meta/results.py b/tests/model_meta/results.py index db8ccf650e..00aae72d60 100644 --- a/tests/model_meta/results.py +++ b/tests/model_meta/results.py @@ -321,7 +321,7 @@ TEST_RESULTS = { 'get_all_related_objects_with_model_hidden_local': { Person: ( ('+', None), - ('_relating_people_hidden_+', None), + ('_model_meta_relating_people_hidden_+', None), ('Person_following_inherited+', None), ('Person_following_inherited+', None), ('Person_friends_inherited+', None), @@ -339,7 +339,7 @@ TEST_RESULTS = { ), ProxyPerson: ( ('+', Person), - ('_relating_people_hidden_+', Person), + ('_model_meta_relating_people_hidden_+', Person), ('Person_following_inherited+', Person), ('Person_following_inherited+', Person), ('Person_friends_inherited+', Person), @@ -357,7 +357,7 @@ TEST_RESULTS = { ), BasePerson: ( ('+', None), - ('_relating_basepeople_hidden_+', None), + ('_model_meta_relating_basepeople_hidden_+', None), ('BasePerson_following_abstract+', None), ('BasePerson_following_abstract+', None), ('BasePerson_following_base+', None), @@ -408,8 +408,8 @@ TEST_RESULTS = { Person: ( ('+', BasePerson), ('+', None), - ('_relating_basepeople_hidden_+', BasePerson), - ('_relating_people_hidden_+', None), + ('_model_meta_relating_basepeople_hidden_+', BasePerson), + ('_model_meta_relating_people_hidden_+', None), ('BasePerson_following_abstract+', BasePerson), ('BasePerson_following_abstract+', BasePerson), ('BasePerson_following_base+', BasePerson), @@ -446,8 +446,8 @@ TEST_RESULTS = { ProxyPerson: ( ('+', BasePerson), ('+', Person), - ('_relating_basepeople_hidden_+', BasePerson), - ('_relating_people_hidden_+', Person), + ('_model_meta_relating_basepeople_hidden_+', BasePerson), + ('_model_meta_relating_people_hidden_+', Person), ('BasePerson_following_abstract+', BasePerson), ('BasePerson_following_abstract+', BasePerson), ('BasePerson_following_base+', BasePerson), @@ -483,7 +483,7 @@ TEST_RESULTS = { ), BasePerson: ( ('+', None), - ('_relating_basepeople_hidden_+', None), + ('_model_meta_relating_basepeople_hidden_+', None), ('BasePerson_following_abstract+', None), ('BasePerson_following_abstract+', None), ('BasePerson_following_base+', None), @@ -822,7 +822,7 @@ TEST_RESULTS = { ('friends_base_rel_+', None), ('followers_base', None), ('relating_basepeople', None), - ('_relating_basepeople_hidden_+', None), + ('_model_meta_relating_basepeople_hidden_+', None), ), Person: ( ('friends_abstract_rel_+', BasePerson), @@ -830,7 +830,7 @@ TEST_RESULTS = { ('friends_base_rel_+', BasePerson), ('followers_base', BasePerson), ('relating_basepeople', BasePerson), - ('_relating_basepeople_hidden_+', BasePerson), + ('_model_meta_relating_basepeople_hidden_+', BasePerson), ('friends_inherited_rel_+', None), ('followers_concrete', None), ('relating_people', None), @@ -849,7 +849,7 @@ TEST_RESULTS = { 'friends_base_rel_+', 'followers_base', 'relating_basepeople', - '_relating_basepeople_hidden_+', + '_model_meta_relating_basepeople_hidden_+', ], Person: [ 'friends_inherited_rel_+', diff --git a/tests/model_meta/tests.py b/tests/model_meta/tests.py index 32bcfbc9e6..b3d469a903 100644 --- a/tests/model_meta/tests.py +++ b/tests/model_meta/tests.py @@ -257,7 +257,7 @@ class RelationTreeTests(SimpleTestCase): self.assertEqual( sorted(field.related_query_name() for field in BasePerson._meta._relation_tree), sorted([ - '+', '_relating_basepeople_hidden_+', 'BasePerson_following_abstract+', + '+', '_model_meta_relating_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+',