summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Charette <charette.s@gmail.com>2016-02-07 18:05:14 -0500
committerSimon Charette <charette.s@gmail.com>2016-02-08 10:31:01 -0500
commit58723722e90a1b34dc1c7ea020c8065755f9c927 (patch)
treee4c3e7c519b4b0690dafbb872069cf55bd1538ff
parent6f14a1429aa2705e052fc01021a2381e8cae9f88 (diff)
Fixed #26162 -- Checked query name clashes of hidden relationships.
Although reverse accessor clashes should be skipped query name can't be hidden. Thanks to Ian Foote and Tim Graham for the review.
-rw-r--r--django/db/models/fields/related.py19
-rw-r--r--docs/releases/1.8.10.txt3
-rw-r--r--docs/releases/1.9.3.txt3
-rw-r--r--tests/invalid_models_tests/test_relative_fields.py36
4 files changed, 44 insertions, 17 deletions
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index ff820ae498..211c1cdf86 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -196,12 +196,6 @@ class RelatedField(Field):
if not isinstance(self.remote_field.model, ModelBase):
return []
- # If the field doesn't install backward relation on the target model (so
- # `is_hidden` returns True), then there are no clashes to check and we
- # can skip these fields.
- if self.remote_field.is_hidden():
- return []
-
# Consider that we are checking field `Model.foreign` and the models
# are:
#
@@ -213,12 +207,15 @@ class RelatedField(Field):
# foreign = models.ForeignKey(Target)
# m2m = models.ManyToManyField(Target)
- rel_opts = self.remote_field.model._meta
# rel_opts.object_name == "Target"
+ rel_opts = self.remote_field.model._meta
+ # If the field doesn't install a backward relation on the target model
+ # (so `is_hidden` returns True), then there are no clashes to check
+ # and we can skip these fields.
+ rel_is_hidden = self.remote_field.is_hidden()
rel_name = self.remote_field.get_accessor_name() # i. e. "model_set"
rel_query_name = self.related_query_name() # i. e. "model"
- field_name = "%s.%s" % (opts.object_name,
- self.name) # i. e. "Model.field"
+ field_name = "%s.%s" % (opts.object_name, self.name) # i. e. "Model.field"
# Check clashes between accessor or reverse query name of `field`
# and any other field name -- i.e. accessor for Model.foreign is
@@ -227,7 +224,7 @@ class RelatedField(Field):
for clash_field in potential_clashes:
clash_name = "%s.%s" % (rel_opts.object_name,
clash_field.name) # i. e. "Target.model_set"
- if clash_field.name == rel_name:
+ if not rel_is_hidden and clash_field.name == rel_name:
errors.append(
checks.Error(
"Reverse accessor for '%s' clashes with field name '%s'." % (field_name, clash_name),
@@ -257,7 +254,7 @@ class RelatedField(Field):
clash_name = "%s.%s" % ( # i. e. "Model.m2m"
clash_field.related_model._meta.object_name,
clash_field.field.name)
- if clash_field.get_accessor_name() == rel_name:
+ if not rel_is_hidden and clash_field.get_accessor_name() == rel_name:
errors.append(
checks.Error(
"Reverse accessor for '%s' clashes with reverse accessor for '%s'." % (field_name, clash_name),
diff --git a/docs/releases/1.8.10.txt b/docs/releases/1.8.10.txt
index c9e77552e6..1ceada6f54 100644
--- a/docs/releases/1.8.10.txt
+++ b/docs/releases/1.8.10.txt
@@ -11,3 +11,6 @@ Bugfixes
* Fixed a crash on PostgreSQL that prevented using ``TIME_ZONE=None`` and
``USE_TZ=False`` (:ticket:`26177`).
+
+* Added system checks for query name clashes of hidden relationships
+ (:ticket:`26162`).
diff --git a/docs/releases/1.9.3.txt b/docs/releases/1.9.3.txt
index ac62926583..994a018fbc 100644
--- a/docs/releases/1.9.3.txt
+++ b/docs/releases/1.9.3.txt
@@ -14,3 +14,6 @@ Bugfixes
* Fixed a crash on PostgreSQL that prevented using ``TIME_ZONE=None`` and
``USE_TZ=False`` (:ticket:`26177`).
+
+* Added system checks for query name clashes of hidden relationships
+ (:ticket:`26162`).
diff --git a/tests/invalid_models_tests/test_relative_fields.py b/tests/invalid_models_tests/test_relative_fields.py
index f157f2beb8..3a7bb6eaa6 100644
--- a/tests/invalid_models_tests/test_relative_fields.py
+++ b/tests/invalid_models_tests/test_relative_fields.py
@@ -1024,45 +1024,69 @@ class ExplicitRelatedNameClashTests(IsolatedModelsTestCase):
class ExplicitRelatedQueryNameClashTests(IsolatedModelsTestCase):
- def test_fk_to_integer(self):
+ def test_fk_to_integer(self, related_name=None):
self._test_explicit_related_query_name_clash(
target=models.IntegerField(),
relative=models.ForeignKey('Target',
models.CASCADE,
+ related_name=related_name,
related_query_name='clash'))
- def test_fk_to_fk(self):
+ def test_hidden_fk_to_integer(self, related_name=None):
+ self.test_fk_to_integer(related_name='+')
+
+ def test_fk_to_fk(self, related_name=None):
self._test_explicit_related_query_name_clash(
target=models.ForeignKey('Another', models.CASCADE),
relative=models.ForeignKey('Target',
models.CASCADE,
+ related_name=related_name,
related_query_name='clash'))
- def test_fk_to_m2m(self):
+ def test_hidden_fk_to_fk(self):
+ self.test_fk_to_fk(related_name='+')
+
+ def test_fk_to_m2m(self, related_name=None):
self._test_explicit_related_query_name_clash(
target=models.ManyToManyField('Another'),
relative=models.ForeignKey('Target',
models.CASCADE,
+ related_name=related_name,
related_query_name='clash'))
- def test_m2m_to_integer(self):
+ def test_hidden_fk_to_m2m(self):
+ self.test_fk_to_m2m(related_name='+')
+
+ def test_m2m_to_integer(self, related_name=None):
self._test_explicit_related_query_name_clash(
target=models.IntegerField(),
relative=models.ManyToManyField('Target',
+ related_name=related_name,
related_query_name='clash'))
- def test_m2m_to_fk(self):
+ def test_hidden_m2m_to_integer(self):
+ self.test_m2m_to_integer(related_name='+')
+
+ def test_m2m_to_fk(self, related_name=None):
self._test_explicit_related_query_name_clash(
target=models.ForeignKey('Another', models.CASCADE),
relative=models.ManyToManyField('Target',
+ related_name=related_name,
related_query_name='clash'))
- def test_m2m_to_m2m(self):
+ def test_hidden_m2m_to_fk(self):
+ self.test_m2m_to_fk(related_name='+')
+
+ def test_m2m_to_m2m(self, related_name=None):
self._test_explicit_related_query_name_clash(
target=models.ManyToManyField('Another'),
relative=models.ManyToManyField('Target',
+ related_name=related_name,
related_query_name='clash'))
+ def test_hidden_m2m_to_m2m(self):
+ self.test_m2m_to_m2m(related_name='+')
+
def _test_explicit_related_query_name_clash(self, target, relative):
class Another(models.Model):
pass