Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion django/contrib/postgres/fields/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def check(self, **kwargs):
)
)
else:
base_checks = self.base_field.check()
base_checks = self.base_field.check(**kwargs)
if base_checks:
error_messages = "\n ".join(
"%s (%s)" % (base_check.msg, base_check.id)
Expand Down
9 changes: 9 additions & 0 deletions django/core/checks/registry.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from collections.abc import Iterable
from itertools import chain

from django.db import connections
from django.utils.inspect import func_accepts_kwargs


Expand Down Expand Up @@ -84,6 +85,14 @@ def run_checks(

if tags is not None:
checks = [check for check in checks if not set(check.tags).isdisjoint(tags)]
elif not databases:
# By default, 'database'-tagged checks are not run if an alias
# is not explicitly specified as they do more than mere static
# code analysis.
checks = [check for check in checks if Tags.database not in check.tags]

if databases is None:
databases = list(connections)

for check in checks:
new_errors = check(app_configs=app_configs, databases=databases)
Expand Down
7 changes: 5 additions & 2 deletions django/db/models/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,12 @@ def remove_sql(self, model, schema_editor):
def _expression_refs_exclude(cls, model, expression, exclude):
get_field = model._meta.get_field
for field_name, *__ in model._get_expr_references(expression):
if field_name in exclude:
if field_name == "pk":
field = model._meta.pk
else:
field = get_field(field_name)
if field_name in exclude or field.name in exclude:
return True
field = get_field(field_name)
if field.generated and cls._expression_refs_exclude(
model, field.expression, exclude
):
Expand Down
49 changes: 42 additions & 7 deletions tests/check_framework/tests.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import multiprocessing
import sys
from io import StringIO
from unittest import skipIf
from unittest import mock, skipIf

from django.apps import apps
from django.core import checks
from django.core.checks import Error, Warning
from django.core.checks import Error, Tags, Warning
from django.core.checks.messages import CheckMessage
from django.core.checks.registry import CheckRegistry
from django.core.management import call_command
Expand Down Expand Up @@ -92,6 +92,21 @@ def return_non_iterable(**kwargs):
with self.assertRaisesMessage(TypeError, msg):
registry.run_checks()

def test_run_checks_database_exclusion(self):
registry = CheckRegistry()

database_errors = [checks.Warning("Database Check")]

@registry.register(Tags.database)
def database_system_check(**kwargs):
return database_errors

errors = registry.run_checks()
self.assertEqual(errors, [])

errors = registry.run_checks(databases=["default"])
self.assertEqual(errors, database_errors)


class MessageTests(SimpleTestCase):
def test_printing(self):
Expand Down Expand Up @@ -190,10 +205,12 @@ def tearDown(self):
def test_simple_call(self):
call_command("check")
self.assertEqual(
simple_system_check.kwargs, {"app_configs": None, "databases": None}
simple_system_check.kwargs,
{"app_configs": None, "databases": ["default", "other"]},
)
self.assertEqual(
tagged_system_check.kwargs, {"app_configs": None, "databases": None}
tagged_system_check.kwargs,
{"app_configs": None, "databases": ["default", "other"]},
)

@override_system_checks([simple_system_check, tagged_system_check])
Expand All @@ -203,19 +220,26 @@ def test_given_app(self):
admin_config = apps.get_app_config("admin")
self.assertEqual(
simple_system_check.kwargs,
{"app_configs": [auth_config, admin_config], "databases": None},
{
"app_configs": [auth_config, admin_config],
"databases": ["default", "other"],
},
)
self.assertEqual(
tagged_system_check.kwargs,
{"app_configs": [auth_config, admin_config], "databases": None},
{
"app_configs": [auth_config, admin_config],
"databases": ["default", "other"],
},
)

@override_system_checks([simple_system_check, tagged_system_check])
def test_given_tag(self):
call_command("check", tags=["simpletag"])
self.assertIsNone(simple_system_check.kwargs)
self.assertEqual(
tagged_system_check.kwargs, {"app_configs": None, "databases": None}
tagged_system_check.kwargs,
{"app_configs": None, "databases": ["default", "other"]},
)

@override_system_checks([simple_system_check, tagged_system_check])
Expand Down Expand Up @@ -268,6 +292,17 @@ def test_fail_level(self):
with self.assertRaises(CommandError):
call_command("check", fail_level="WARNING")

def test_database_system_checks(self):
database_check = mock.Mock(return_value=[], tags=[Tags.database])

with override_system_checks([database_check]):
call_command("check")
database_check.assert_not_called()
call_command("check", databases=["default"])
database_check.assert_called_once_with(
app_configs=None, databases=["default"]
)


def custom_error_system_check(app_configs, **kwargs):
return [Error("Error", id="myerrorcheck.E001")]
Expand Down
21 changes: 20 additions & 1 deletion tests/migrations/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def test_migrate_with_system_checks(self, mocked_check):
out = io.StringIO()
call_command("migrate", skip_checks=False, no_color=True, stdout=out)
self.assertIn("Apply all migrations: migrated_app", out.getvalue())
mocked_check.assert_called_once()
mocked_check.assert_called_once_with(databases=["default"])

def test_migrate_with_custom_system_checks(self):
original_checks = registry.registered_checks.copy()
Expand All @@ -137,6 +137,25 @@ class CustomMigrateCommandWithSecurityChecks(MigrateCommand):
command = CustomMigrateCommandWithSecurityChecks()
call_command(command, skip_checks=False, stdout=io.StringIO())

@override_settings(
INSTALLED_APPS=[
"django.contrib.auth",
"django.contrib.contenttypes",
"migrations.migrations_test_apps.migrated_app",
]
)
def test_migrate_runs_database_system_checks(self):
original_checks = registry.registered_checks.copy()
self.addCleanup(setattr, registry, "registered_checks", original_checks)

out = io.StringIO()
mock_check = mock.Mock(return_value=[])
register(mock_check, Tags.database)

call_command("migrate", skip_checks=False, no_color=True, stdout=out)
self.assertIn("Apply all migrations: migrated_app", out.getvalue())
mock_check.assert_called_once_with(app_configs=None, databases=["default"])

@override_settings(
INSTALLED_APPS=[
"migrations",
Expand Down
19 changes: 19 additions & 0 deletions tests/model_forms/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,3 +543,22 @@ class Meta:
violation_error_message="Price must be greater than zero.",
),
]


class AttnameConstraintsModel(models.Model):
left = models.ForeignKey(
"self", related_name="+", null=True, on_delete=models.SET_NULL
)
right = models.ForeignKey(
"self", related_name="+", null=True, on_delete=models.SET_NULL
)

class Meta:
required_db_features = {"supports_table_check_constraints"}
constraints = [
models.CheckConstraint(
name="%(app_label)s_%(class)s_left_not_right",
# right_id here is the ForeignKey's attname, not name.
condition=~models.Q(left=models.F("right_id")),
),
]
15 changes: 15 additions & 0 deletions tests/model_forms/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from .models import (
Article,
ArticleStatus,
AttnameConstraintsModel,
Author,
Author1,
Award,
Expand Down Expand Up @@ -3766,3 +3767,17 @@ def test_check_constraint_refs_excluded_field(self):
self.assertEqual(
full_form.errors, {"__all__": ["Price must be greater than zero."]}
)

def test_check_constraint_refs_excluded_field_attname(self):
left = AttnameConstraintsModel.objects.create()
instance = AttnameConstraintsModel.objects.create(left=left)
data = {
"left": str(left.id),
"right": "",
}
AttnameConstraintsModelForm = modelform_factory(
AttnameConstraintsModel, fields="__all__"
)
full_form = AttnameConstraintsModelForm(data, instance=instance)
self.assertFalse(full_form.is_valid())
self.assertEqual(full_form.errors, {"right": ["This field is required."]})
19 changes: 19 additions & 0 deletions tests/postgres_tests/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,25 @@ class MyModel(PostgreSQLModel):
self.assertEqual(errors[0].id, "postgres.E001")
self.assertIn("max_length", errors[0].msg)

def test_base_field_check_kwargs(self):
passed_kwargs = None

class MyField(models.Field):
def check(self, **kwargs):
nonlocal passed_kwargs
passed_kwargs = kwargs
return []

class MyModel(PostgreSQLModel):
field = ArrayField(MyField())

self.assertEqual(MyModel.check(databases=["default"]), [])
self.assertEqual(
passed_kwargs,
{"databases": ["default"]},
"ArrayField.check kwargs should be passed to its base_field.",
)

def test_invalid_base_fields(self):
class MyModel(PostgreSQLModel):
field = ArrayField(
Expand Down