Fixed #26648 -- Added a system check for invalid related_query_name's containing underscores.
This commit is contained in:
parent
effb4ed6f5
commit
686a593aaa
|
@ -9,6 +9,7 @@ from django.core import checks, exceptions
|
||||||
from django.db import connection, router
|
from django.db import connection, router
|
||||||
from django.db.backends import utils
|
from django.db.backends import utils
|
||||||
from django.db.models import Q
|
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.deletion import CASCADE, SET_DEFAULT, SET_NULL
|
||||||
from django.db.models.query_utils import PathInfo
|
from django.db.models.query_utils import PathInfo
|
||||||
from django.db.models.utils import make_model_tuple
|
from django.db.models.utils import make_model_tuple
|
||||||
|
@ -115,6 +116,7 @@ class RelatedField(Field):
|
||||||
def check(self, **kwargs):
|
def check(self, **kwargs):
|
||||||
errors = super(RelatedField, self).check(**kwargs)
|
errors = super(RelatedField, self).check(**kwargs)
|
||||||
errors.extend(self._check_related_name_is_valid())
|
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_relation_model_exists())
|
||||||
errors.extend(self._check_referencing_to_swapped_model())
|
errors.extend(self._check_referencing_to_swapped_model())
|
||||||
errors.extend(self._check_clashes())
|
errors.extend(self._check_clashes())
|
||||||
|
@ -148,6 +150,35 @@ class RelatedField(Field):
|
||||||
]
|
]
|
||||||
return []
|
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):
|
def _check_relation_model_exists(self):
|
||||||
rel_is_missing = self.remote_field.model not in self.opts.apps.get_models()
|
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)
|
rel_is_string = isinstance(self.remote_field.model, six.string_types)
|
||||||
|
|
|
@ -206,6 +206,10 @@ Related Fields
|
||||||
* **fields.E307**: The field ``<app label>.<model>.<field name>`` was declared
|
* **fields.E307**: The field ``<app label>.<model>.<field name>`` was declared
|
||||||
with a lazy reference to ``<app label>.<model>``, but app ``<app label>``
|
with a lazy reference to ``<app label>.<model>``, but app ``<app label>``
|
||||||
isn't installed or doesn't provide model ``<model>``.
|
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
|
* **fields.E310**: No subset of the fields ``<field1>``, ``<field2>``, ... on
|
||||||
model ``<model>`` is unique. Add ``unique=True`` on any of those fields or
|
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.
|
add at least a subset of them to a unique_together constraint.
|
||||||
|
|
|
@ -714,7 +714,7 @@ class RelativeFieldTests(SimpleTestCase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
for invalid_related_name in invalid_related_names:
|
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),
|
'parent': models.ForeignKey('Parent', models.CASCADE, related_name=invalid_related_name),
|
||||||
'__module__': Parent.__module__,
|
'__module__': Parent.__module__,
|
||||||
})
|
})
|
||||||
|
@ -723,7 +723,7 @@ class RelativeFieldTests(SimpleTestCase):
|
||||||
errors = Child.check()
|
errors = Child.check()
|
||||||
expected = [
|
expected = [
|
||||||
Error(
|
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),
|
% (invalid_related_name, invalid_related_name),
|
||||||
hint="Related name must be a valid Python identifier or end with a '+'",
|
hint="Related name must be a valid Python identifier or end with a '+'",
|
||||||
obj=field,
|
obj=field,
|
||||||
|
@ -743,7 +743,6 @@ class RelativeFieldTests(SimpleTestCase):
|
||||||
'_starts_with_underscore',
|
'_starts_with_underscore',
|
||||||
'contains_%s_digit' % digit,
|
'contains_%s_digit' % digit,
|
||||||
'ends_with_plus+',
|
'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')
|
@isolate_apps('invalid_models_tests')
|
||||||
class AccessorClashTests(SimpleTestCase):
|
class AccessorClashTests(SimpleTestCase):
|
||||||
|
|
Loading…
Reference in New Issue