From 686a593aaadb1c2e2053fee8c401476dcb4617a5 Mon Sep 17 00:00:00 2001 From: Michal Petrucha Date: Sun, 22 May 2016 21:10:24 +0200 Subject: [PATCH] Fixed #26648 -- Added a system check for invalid related_query_name's containing underscores. --- django/db/models/fields/related.py | 31 +++++++++++++++++++ docs/ref/checks.txt | 4 +++ .../test_relative_fields.py | 31 +++++++++++++++++-- 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 ``..`` was declared with a lazy reference to ``.``, but app ```` isn't installed or doesn't provide model ````. +* **fields.E308**: Reverse query name ```` must not end + with an underscore. +* **fields.E309**: Reverse query name ```` must not contain + ``'__'``. * **fields.E310**: No subset of the fields ````, ````, ... on 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):