Browse Source

Fixed #30613 -- Moved index name validation to system checks.

can 5 years ago
parent
commit
53209f7830

+ 25 - 2
django/db/models/base.py

@@ -1567,9 +1567,32 @@ class Model(metaclass=ModelBase):
 
     @classmethod
     def _check_indexes(cls):
-        """Check the fields of indexes."""
+        """Check the fields and names of indexes."""
+        errors = []
+        for index in cls._meta.indexes:
+            # Index name can't start with an underscore or a number, restricted
+            # for cross-database compatibility with Oracle.
+            if index.name[0] == '_' or index.name[0].isdigit():
+                errors.append(
+                    checks.Error(
+                        "The index name '%s' cannot start with an underscore "
+                        "or a number." % index.name,
+                        obj=cls,
+                        id='models.E033',
+                    ),
+                )
+            if len(index.name) > index.max_name_length:
+                errors.append(
+                    checks.Error(
+                        "The index name '%s' cannot be longer than %d "
+                        "characters." % (index.name, index.max_name_length),
+                        obj=cls,
+                        id='models.E034',
+                    ),
+                )
         fields = [field for index in cls._meta.indexes for field, _ in index.fields_orders]
-        return cls._check_local_fields(fields, 'indexes')
+        errors.extend(cls._check_local_fields(fields, 'indexes'))
+        return errors
 
     @classmethod
     def _check_local_fields(cls, fields, option):

+ 2 - 19
django/db/models/indexes.py

@@ -33,28 +33,10 @@ class Index:
             for field_name in self.fields
         ]
         self.name = name or ''
-        if self.name:
-            errors = self.check_name()
-            if len(self.name) > self.max_name_length:
-                errors.append('Index names cannot be longer than %s characters.' % self.max_name_length)
-            if errors:
-                raise ValueError(errors)
         self.db_tablespace = db_tablespace
         self.opclasses = opclasses
         self.condition = condition
 
-    def check_name(self):
-        errors = []
-        # Name can't start with an underscore on Oracle; prepend D if needed.
-        if self.name[0] == '_':
-            errors.append('Index names cannot start with an underscore (_).')
-            self.name = 'D%s' % self.name[1:]
-        # Name can't start with a number on Oracle; prepend D if needed.
-        elif self.name[0].isdigit():
-            errors.append('Index names cannot start with a number (0-9).')
-            self.name = 'D%s' % self.name[1:]
-        return errors
-
     def _get_condition_sql(self, model, schema_editor):
         if self.condition is None:
             return None
@@ -122,7 +104,8 @@ class Index:
             'Index too long for multiple database support. Is self.suffix '
             'longer than 3 characters?'
         )
-        self.check_name()
+        if self.name[0] == '_' or self.name[0].isdigit():
+            self.name = 'D%s' % self.name[1:]
 
     def __repr__(self):
         return "<%s: fields='%s'%s>" % (

+ 4 - 0
docs/ref/checks.txt

@@ -313,6 +313,10 @@ Models
   ``<model>``.
 * **models.E032**: constraint name ``<constraint>`` is not unique amongst
   models: ``<model list>``.
+* **models.E033**: The index name ``<index>`` cannot start with an underscore
+  or a number.
+* **models.E034**: The index name ``<index>`` cannot be longer than
+  ``<max_length>`` characters.
 
 Security
 --------

+ 33 - 0
tests/invalid_models_tests/test_models.py

@@ -296,6 +296,39 @@ class IndexesTests(SimpleTestCase):
 
         self.assertEqual(Bar.check(), [])
 
+    def test_name_constraints(self):
+        class Model(models.Model):
+            class Meta:
+                indexes = [
+                    models.Index(fields=['id'], name='_index_name'),
+                    models.Index(fields=['id'], name='5index_name'),
+                ]
+
+        self.assertEqual(Model.check(), [
+            Error(
+                "The index name '%sindex_name' cannot start with an "
+                "underscore or a number." % prefix,
+                obj=Model,
+                id='models.E033',
+            ) for prefix in ('_', '5')
+        ])
+
+    def test_max_name_length(self):
+        index_name = 'x' * 31
+
+        class Model(models.Model):
+            class Meta:
+                indexes = [models.Index(fields=['id'], name=index_name)]
+
+        self.assertEqual(Model.check(), [
+            Error(
+                "The index name '%s' cannot be longer than 30 characters."
+                % index_name,
+                obj=Model,
+                id='models.E034',
+            ),
+        ])
+
 
 @isolate_apps('invalid_models_tests')
 class FieldNamesTests(SimpleTestCase):

+ 0 - 14
tests/model_indexes/tests.py

@@ -63,20 +63,6 @@ class SimpleIndexesTests(SimpleTestCase):
         with self.assertRaisesMessage(ValueError, 'Index.condition must be a Q instance.'):
             models.Index(condition='invalid', name='long_book_idx')
 
-    def test_max_name_length(self):
-        msg = 'Index names cannot be longer than 30 characters.'
-        with self.assertRaisesMessage(ValueError, msg):
-            models.Index(fields=['title'], name='looooooooooooong_index_name_idx')
-
-    def test_name_constraints(self):
-        msg = 'Index names cannot start with an underscore (_).'
-        with self.assertRaisesMessage(ValueError, msg):
-            models.Index(fields=['title'], name='_name_starting_with_underscore')
-
-        msg = 'Index names cannot start with a number (0-9).'
-        with self.assertRaisesMessage(ValueError, msg):
-            models.Index(fields=['title'], name='5name_starting_with_number')
-
     def test_name_auto_generation(self):
         index = models.Index(fields=['author'])
         index.set_name_with_model(Book)