1
0
mirror of https://github.com/django/django.git synced 2025-07-18 00:29:13 +00:00
django/tests/postgres_tests/test_app_installed_check.py
Clifford Gama 74b31cd26b Fixed #32770 -- Added system check to ensure django.contrib.postgres is installed when using its features.
Added postgres.E005 to validate 'django.contrib.postgres' is in INSTALLED_APPS
when using:
* PostgreSQL-specific fields (ArrayField, HStoreField, range fields, SearchVectorField),
* PostgreSQL indexes (PostgresIndex and all subclasses), and
* ExclusionConstraint

The check provides immediate feedback during system checks rather than failing
later with obscure runtime and database errors.

Thanks to Simon Charette and Sarah Boyce for reviews.
2025-06-18 08:36:49 +02:00

143 lines
4.8 KiB
Python

from django.core import checks
from django.db import models
from django.test import modify_settings
from django.test.utils import isolate_apps
from . import PostgreSQLTestCase
from .fields import (
BigIntegerRangeField,
DateRangeField,
DateTimeRangeField,
DecimalRangeField,
HStoreField,
IntegerRangeField,
SearchVectorField,
)
from .models import IntegerArrayModel, NestedIntegerArrayModel, PostgreSQLModel
try:
from django.contrib.postgres.constraints import ExclusionConstraint
from django.contrib.postgres.fields.ranges import RangeOperators
from django.contrib.postgres.indexes import GinIndex, PostgresIndex
from django.contrib.postgres.search import SearchQueryField
except ImportError:
pass
@isolate_apps("postgres_tests")
class TestPostgresAppInstalledCheck(PostgreSQLTestCase):
def _make_error(self, obj, klass_name):
"""Helper to create postgres.E005 error for specific objects."""
return checks.Error(
"'django.contrib.postgres' must be in INSTALLED_APPS in order to "
f"use {klass_name}.",
obj=obj,
id="postgres.E005",
)
def assert_model_check_errors(self, model_class, expected_errors):
errors = model_class.check(databases=self.databases)
self.assertEqual(errors, [])
with modify_settings(INSTALLED_APPS={"remove": "django.contrib.postgres"}):
errors = model_class.check(databases=self.databases)
self.assertEqual(errors, expected_errors)
def test_indexes(self):
class IndexModel(PostgreSQLModel):
field = models.IntegerField()
class Meta:
indexes = [
PostgresIndex(fields=["id"], name="postgres_index_test"),
GinIndex(fields=["field"], name="gin_index_test"),
]
self.assert_model_check_errors(
IndexModel,
[
self._make_error(IndexModel, "PostgresIndex"),
self._make_error(IndexModel, "GinIndex"),
],
)
def test_exclusion_constraint(self):
class ExclusionModel(PostgreSQLModel):
value = models.IntegerField()
class Meta:
constraints = [
ExclusionConstraint(
name="exclude_equal",
expressions=[("value", RangeOperators.EQUAL)],
)
]
self.assert_model_check_errors(
ExclusionModel, [self._make_error(ExclusionModel, "ExclusionConstraint")]
)
def test_array_field(self):
field = IntegerArrayModel._meta.get_field("field")
self.assert_model_check_errors(
IntegerArrayModel,
[self._make_error(field, "ArrayField")],
)
def test_nested_array_field(self):
"""Inner ArrayField does not cause a postgres.E001 error."""
field = NestedIntegerArrayModel._meta.get_field("field")
self.assert_model_check_errors(
NestedIntegerArrayModel,
[
self._make_error(field, "ArrayField"),
],
)
def test_hstore_field(self):
class HStoreFieldModel(PostgreSQLModel):
field = HStoreField()
field = HStoreFieldModel._meta.get_field("field")
self.assert_model_check_errors(
HStoreFieldModel,
[
self._make_error(field, "HStoreField"),
],
)
def test_range_fields(self):
class RangeFieldsModel(PostgreSQLModel):
int_range = IntegerRangeField()
bigint_range = BigIntegerRangeField()
decimal_range = DecimalRangeField()
datetime_range = DateTimeRangeField()
date_range = DateRangeField()
expected_errors = [
self._make_error(field, field.__class__.__name__)
for field in [
RangeFieldsModel._meta.get_field("int_range"),
RangeFieldsModel._meta.get_field("bigint_range"),
RangeFieldsModel._meta.get_field("decimal_range"),
RangeFieldsModel._meta.get_field("datetime_range"),
RangeFieldsModel._meta.get_field("date_range"),
]
]
self.assert_model_check_errors(RangeFieldsModel, expected_errors)
def test_search_vector_field(self):
class SearchModel(PostgreSQLModel):
search_vector = SearchVectorField()
search_query = SearchQueryField()
vector_field = SearchModel._meta.get_field("search_vector")
query_field = SearchModel._meta.get_field("search_query")
self.assert_model_check_errors(
SearchModel,
[
self._make_error(vector_field, "SearchVectorField"),
self._make_error(query_field, "SearchQueryField"),
],
)