Commit 686a593a authored by Michal Petrucha's avatar Michal Petrucha Committed by Tim Graham
Browse files

Fixed #26648 -- Added a system check for invalid related_query_name's containing underscores.

parent effb4ed6
Loading
Loading
Loading
Loading
+31 −0
Original line number Diff line number Diff line
@@ -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)
+4 −0
Original line number Diff line number Diff line
@@ -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.
+28 −3
Original line number Diff line number Diff line
@@ -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):