summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichal Petrucha <michal.petrucha@koniiiik.org>2016-05-22 21:10:24 +0200
committerTim Graham <timograham@gmail.com>2016-06-09 11:57:32 -0400
commit686a593aaadb1c2e2053fee8c401476dcb4617a5 (patch)
treec2a9bc3012d80b188257134bdffaf9bf2552c80d
parenteffb4ed6f54bb747d26a2c1ce0ea6153ebc6d0a1 (diff)
Fixed #26648 -- Added a system check for invalid related_query_name's containing underscores.
-rw-r--r--django/db/models/fields/related.py31
-rw-r--r--docs/ref/checks.txt4
-rw-r--r--tests/invalid_models_tests/test_relative_fields.py31
3 files changed, 63 insertions, 3 deletions
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 7caba63c3e..4b8b1804b5 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -9,6 +9,7 @@ from django.core import checks, exceptions
from django.db import connection, router
from django.db.backends import utils
from django.db.models import Q
+from django.db.models.constants import LOOKUP_SEP
from django.db.models.deletion import CASCADE, SET_DEFAULT, SET_NULL
from django.db.models.query_utils import PathInfo
from django.db.models.utils import make_model_tuple
@@ -115,6 +116,7 @@ class RelatedField(Field):
def check(self, **kwargs):
errors = super(RelatedField, self).check(**kwargs)
errors.extend(self._check_related_name_is_valid())
+ errors.extend(self._check_related_query_name_is_valid())
errors.extend(self._check_relation_model_exists())
errors.extend(self._check_referencing_to_swapped_model())
errors.extend(self._check_clashes())
@@ -148,6 +150,35 @@ class RelatedField(Field):
]
return []
+ def _check_related_query_name_is_valid(self):
+ if self.remote_field.is_hidden():
+ return []
+ rel_query_name = self.related_query_name()
+ errors = []
+ if rel_query_name.endswith('_'):
+ errors.append(
+ checks.Error(
+ "Reverse query name '%s' must not end with an underscore."
+ % (rel_query_name,),
+ hint=("Add or change a related_name or related_query_name "
+ "argument for this field."),
+ obj=self,
+ id='fields.E308',
+ )
+ )
+ if LOOKUP_SEP in rel_query_name:
+ errors.append(
+ checks.Error(
+ "Reverse query name '%s' must not contain '%s'."
+ % (rel_query_name, LOOKUP_SEP),
+ hint=("Add or change a related_name or related_query_name "
+ "argument for this field."),
+ obj=self,
+ id='fields.E309',
+ )
+ )
+ return errors
+
def _check_relation_model_exists(self):
rel_is_missing = self.remote_field.model not in self.opts.apps.get_models()
rel_is_string = isinstance(self.remote_field.model, six.string_types)
diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt
index 18b6d88add..074a901de8 100644
--- a/docs/ref/checks.txt
+++ b/docs/ref/checks.txt
@@ -206,6 +206,10 @@ Related Fields
* **fields.E307**: The field ``<app label>.<model>.<field name>`` was declared
with a lazy reference to ``<app label>.<model>``, but app ``<app label>``
isn't installed or doesn't provide model ``<model>``.
+* **fields.E308**: Reverse query name ``<related query name>`` must not end
+ with an underscore.
+* **fields.E309**: Reverse query name ``<related query name>`` must not contain
+ ``'__'``.
* **fields.E310**: No subset of the fields ``<field1>``, ``<field2>``, ... on
model ``<model>`` is unique. Add ``unique=True`` on any of those fields or
add at least a subset of them to a unique_together constraint.
diff --git a/tests/invalid_models_tests/test_relative_fields.py b/tests/invalid_models_tests/test_relative_fields.py
index 2534b345c2..b681e35587 100644
--- a/tests/invalid_models_tests/test_relative_fields.py
+++ b/tests/invalid_models_tests/test_relative_fields.py
@@ -714,7 +714,7 @@ class RelativeFieldTests(SimpleTestCase):
pass
for invalid_related_name in invalid_related_names:
- Child = type(str('Child_%s') % str(invalid_related_name), (models.Model,), {
+ Child = type(str('Child%s') % str(invalid_related_name), (models.Model,), {
'parent': models.ForeignKey('Parent', models.CASCADE, related_name=invalid_related_name),
'__module__': Parent.__module__,
})
@@ -723,7 +723,7 @@ class RelativeFieldTests(SimpleTestCase):
errors = Child.check()
expected = [
Error(
- "The name '%s' is invalid related_name for field Child_%s.parent"
+ "The name '%s' is invalid related_name for field Child%s.parent"
% (invalid_related_name, invalid_related_name),
hint="Related name must be a valid Python identifier or end with a '+'",
obj=field,
@@ -743,7 +743,6 @@ class RelativeFieldTests(SimpleTestCase):
'_starts_with_underscore',
'contains_%s_digit' % digit,
'ends_with_plus+',
- '_',
'_+',
'+',
]
@@ -813,6 +812,32 @@ class RelativeFieldTests(SimpleTestCase):
),
])
+ def test_invalid_related_query_name(self):
+ class Target(models.Model):
+ pass
+
+ class Model(models.Model):
+ first = models.ForeignKey(Target, models.CASCADE, related_name='contains__double')
+ second = models.ForeignKey(Target, models.CASCADE, related_query_name='ends_underscore_')
+
+ self.assertEqual(Model.check(), [
+ Error(
+ "Reverse query name 'contains__double' must not contain '__'.",
+ hint=("Add or change a related_name or related_query_name "
+ "argument for this field."),
+ obj=Model._meta.get_field('first'),
+ id='fields.E309',
+ ),
+ Error(
+ "Reverse query name 'ends_underscore_' must not end with an "
+ "underscore.",
+ hint=("Add or change a related_name or related_query_name "
+ "argument for this field."),
+ obj=Model._meta.get_field('second'),
+ id='fields.E308',
+ ),
+ ])
+
@isolate_apps('invalid_models_tests')
class AccessorClashTests(SimpleTestCase):