Fixed #32321 -- Added system checks for invalid model field names in functional indexes.
This commit is contained in:
parent
76ae6ccf85
commit
f750377318
|
@ -1624,6 +1624,7 @@ class Model(metaclass=ModelBase):
|
||||||
def _check_indexes(cls, databases):
|
def _check_indexes(cls, databases):
|
||||||
"""Check fields, names, and conditions of indexes."""
|
"""Check fields, names, and conditions of indexes."""
|
||||||
errors = []
|
errors = []
|
||||||
|
references = set()
|
||||||
for index in cls._meta.indexes:
|
for index in cls._meta.indexes:
|
||||||
# Index name can't start with an underscore or a number, restricted
|
# Index name can't start with an underscore or a number, restricted
|
||||||
# for cross-database compatibility with Oracle.
|
# for cross-database compatibility with Oracle.
|
||||||
|
@ -1645,6 +1646,11 @@ class Model(metaclass=ModelBase):
|
||||||
id='models.E034',
|
id='models.E034',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
if index.contains_expressions:
|
||||||
|
for expression in index.expressions:
|
||||||
|
references.update(
|
||||||
|
ref[0] for ref in cls._get_expr_references(expression)
|
||||||
|
)
|
||||||
for db in databases:
|
for db in databases:
|
||||||
if not router.allow_migrate_model(db, cls):
|
if not router.allow_migrate_model(db, cls):
|
||||||
continue
|
continue
|
||||||
|
@ -1699,6 +1705,7 @@ class Model(metaclass=ModelBase):
|
||||||
)
|
)
|
||||||
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]
|
||||||
fields += [include for index in cls._meta.indexes for include in index.include]
|
fields += [include for index in cls._meta.indexes for include in index.include]
|
||||||
|
fields += references
|
||||||
errors.extend(cls._check_local_fields(fields, 'indexes'))
|
errors.extend(cls._check_local_fields(fields, 'indexes'))
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import unittest
|
||||||
from django.core.checks import Error, Warning
|
from django.core.checks import Error, Warning
|
||||||
from django.core.checks.model_checks import _check_lazy_references
|
from django.core.checks.model_checks import _check_lazy_references
|
||||||
from django.db import connection, connections, models
|
from django.db import connection, connections, models
|
||||||
from django.db.models.functions import Lower
|
from django.db.models.functions import Abs, Lower, Round
|
||||||
from django.db.models.signals import post_init
|
from django.db.models.signals import post_init
|
||||||
from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
|
from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
|
||||||
from django.test.utils import isolate_apps, override_settings, register_lookup
|
from django.test.utils import isolate_apps, override_settings, register_lookup
|
||||||
|
@ -525,6 +525,99 @@ class IndexesTests(TestCase):
|
||||||
|
|
||||||
self.assertEqual(Model.check(databases=self.databases), [])
|
self.assertEqual(Model.check(databases=self.databases), [])
|
||||||
|
|
||||||
|
def test_func_index_complex_expression_custom_lookup(self):
|
||||||
|
class Model(models.Model):
|
||||||
|
height = models.IntegerField()
|
||||||
|
weight = models.IntegerField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
indexes = [
|
||||||
|
models.Index(
|
||||||
|
models.F('height') / (models.F('weight__abs') + models.Value(5)),
|
||||||
|
name='name',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
with register_lookup(models.IntegerField, Abs):
|
||||||
|
self.assertEqual(Model.check(), [])
|
||||||
|
|
||||||
|
def test_func_index_pointing_to_missing_field(self):
|
||||||
|
class Model(models.Model):
|
||||||
|
class Meta:
|
||||||
|
indexes = [models.Index(Lower('missing_field').desc(), name='name')]
|
||||||
|
|
||||||
|
self.assertEqual(Model.check(), [
|
||||||
|
Error(
|
||||||
|
"'indexes' refers to the nonexistent field 'missing_field'.",
|
||||||
|
obj=Model,
|
||||||
|
id='models.E012',
|
||||||
|
),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_func_index_pointing_to_missing_field_nested(self):
|
||||||
|
class Model(models.Model):
|
||||||
|
class Meta:
|
||||||
|
indexes = [
|
||||||
|
models.Index(Abs(Round('missing_field')), name='name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assertEqual(Model.check(), [
|
||||||
|
Error(
|
||||||
|
"'indexes' refers to the nonexistent field 'missing_field'.",
|
||||||
|
obj=Model,
|
||||||
|
id='models.E012',
|
||||||
|
),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_func_index_pointing_to_m2m_field(self):
|
||||||
|
class Model(models.Model):
|
||||||
|
m2m = models.ManyToManyField('self')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
indexes = [models.Index(Lower('m2m'), name='name')]
|
||||||
|
|
||||||
|
self.assertEqual(Model.check(), [
|
||||||
|
Error(
|
||||||
|
"'indexes' refers to a ManyToManyField 'm2m', but "
|
||||||
|
"ManyToManyFields are not permitted in 'indexes'.",
|
||||||
|
obj=Model,
|
||||||
|
id='models.E013',
|
||||||
|
),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_func_index_pointing_to_non_local_field(self):
|
||||||
|
class Foo(models.Model):
|
||||||
|
field1 = models.CharField(max_length=15)
|
||||||
|
|
||||||
|
class Bar(Foo):
|
||||||
|
class Meta:
|
||||||
|
indexes = [models.Index(Lower('field1'), name='name')]
|
||||||
|
|
||||||
|
self.assertEqual(Bar.check(), [
|
||||||
|
Error(
|
||||||
|
"'indexes' refers to field 'field1' which is not local to "
|
||||||
|
"model 'Bar'.",
|
||||||
|
hint='This issue may be caused by multi-table inheritance.',
|
||||||
|
obj=Bar,
|
||||||
|
id='models.E016',
|
||||||
|
),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_func_index_pointing_to_fk(self):
|
||||||
|
class Foo(models.Model):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Bar(models.Model):
|
||||||
|
foo_1 = models.ForeignKey(Foo, models.CASCADE, related_name='bar_1')
|
||||||
|
foo_2 = models.ForeignKey(Foo, models.CASCADE, related_name='bar_2')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
indexes = [
|
||||||
|
models.Index(Lower('foo_1_id'), Lower('foo_2'), name='index_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assertEqual(Bar.check(), [])
|
||||||
|
|
||||||
|
|
||||||
@isolate_apps('invalid_models_tests')
|
@isolate_apps('invalid_models_tests')
|
||||||
class FieldNamesTests(TestCase):
|
class FieldNamesTests(TestCase):
|
||||||
|
|
Loading…
Reference in New Issue