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

This commit is contained in:
can 2019-07-04 19:21:50 +03:00 committed by Mariusz Felisiak
parent f197c3dd91
commit 53209f7830
5 changed files with 64 additions and 35 deletions

View File

@ -1567,9 +1567,32 @@ class Model(metaclass=ModelBase):
@classmethod @classmethod
def _check_indexes(cls): 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] 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 @classmethod
def _check_local_fields(cls, fields, option): def _check_local_fields(cls, fields, option):

View File

@ -33,28 +33,10 @@ class Index:
for field_name in self.fields for field_name in self.fields
] ]
self.name = name or '' 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.db_tablespace = db_tablespace
self.opclasses = opclasses self.opclasses = opclasses
self.condition = condition 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): def _get_condition_sql(self, model, schema_editor):
if self.condition is None: if self.condition is None:
return None return None
@ -122,7 +104,8 @@ class Index:
'Index too long for multiple database support. Is self.suffix ' 'Index too long for multiple database support. Is self.suffix '
'longer than 3 characters?' '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): def __repr__(self):
return "<%s: fields='%s'%s>" % ( return "<%s: fields='%s'%s>" % (

View File

@ -313,6 +313,10 @@ Models
``<model>``. ``<model>``.
* **models.E032**: constraint name ``<constraint>`` is not unique amongst * **models.E032**: constraint name ``<constraint>`` is not unique amongst
models: ``<model list>``. 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 Security
-------- --------

View File

@ -296,6 +296,39 @@ class IndexesTests(SimpleTestCase):
self.assertEqual(Bar.check(), []) 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') @isolate_apps('invalid_models_tests')
class FieldNamesTests(SimpleTestCase): class FieldNamesTests(SimpleTestCase):

View File

@ -63,20 +63,6 @@ class SimpleIndexesTests(SimpleTestCase):
with self.assertRaisesMessage(ValueError, 'Index.condition must be a Q instance.'): with self.assertRaisesMessage(ValueError, 'Index.condition must be a Q instance.'):
models.Index(condition='invalid', name='long_book_idx') 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): def test_name_auto_generation(self):
index = models.Index(fields=['author']) index = models.Index(fields=['author'])
index.set_name_with_model(Book) index.set_name_with_model(Book)