diff --git a/AUTHORS b/AUTHORS
index 3c39a273f2..ca89ff4a76 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -263,6 +263,7 @@ answer newbie questions, and generally made Django that much better:
Filip Noetzel
Filip Wasilewski
Finn Gruwier Larsen
+ Flávio Juvenal da Silva Junior
flavio.curella@gmail.com
Florian Apolloner
Francisco Albarran Cristobal
diff --git a/django/contrib/postgres/fields/array.py b/django/contrib/postgres/fields/array.py
index 7992740663..5c9bb1b2ee 100644
--- a/django/contrib/postgres/fields/array.py
+++ b/django/contrib/postgres/fields/array.py
@@ -10,17 +10,19 @@ from django.utils.inspect import func_supports_parameter
from django.utils.translation import gettext_lazy as _
from ..utils import prefix_validation_error
+from .mixins import CheckFieldDefaultMixin
from .utils import AttributeSetter
__all__ = ['ArrayField']
-class ArrayField(Field):
+class ArrayField(CheckFieldDefaultMixin, Field):
empty_strings_allowed = False
default_error_messages = {
'item_invalid': _('Item %(nth)s in the array did not validate: '),
'nested_array_mismatch': _('Nested arrays must have the same length.'),
}
+ _default_hint = ('list', '[]')
def __init__(self, base_field, size=None, **kwargs):
self.base_field = base_field
diff --git a/django/contrib/postgres/fields/jsonb.py b/django/contrib/postgres/fields/jsonb.py
index a06187c4bc..4ed441003b 100644
--- a/django/contrib/postgres/fields/jsonb.py
+++ b/django/contrib/postgres/fields/jsonb.py
@@ -9,6 +9,8 @@ from django.db.models import (
)
from django.utils.translation import gettext_lazy as _
+from .mixins import CheckFieldDefaultMixin
+
__all__ = ['JSONField']
@@ -25,12 +27,13 @@ class JsonAdapter(Json):
return json.dumps(obj, **options)
-class JSONField(Field):
+class JSONField(CheckFieldDefaultMixin, Field):
empty_strings_allowed = False
description = _('A JSON object')
default_error_messages = {
'invalid': _("Value must be valid JSON."),
}
+ _default_hint = ('dict', '{}')
def __init__(self, verbose_name=None, name=None, encoder=None, **kwargs):
if encoder and not callable(encoder):
diff --git a/django/contrib/postgres/fields/mixins.py b/django/contrib/postgres/fields/mixins.py
new file mode 100644
index 0000000000..254b80c4a2
--- /dev/null
+++ b/django/contrib/postgres/fields/mixins.py
@@ -0,0 +1,29 @@
+from django.core import checks
+
+
+class CheckFieldDefaultMixin:
+ _default_hint = ('', '')
+
+ def _check_default(self):
+ if self.has_default() and self.default is not None and not callable(self.default):
+ return [
+ checks.Warning(
+ "%s default should be a callable instead of an instance so "
+ "that it's not shared between all field instances." % (
+ self.__class__.__name__,
+ ),
+ hint=(
+ 'Use a callable instead, e.g., use `%s` instead of '
+ '`%s`.' % self._default_hint
+ ),
+ obj=self,
+ id='postgres.E003',
+ )
+ ]
+ else:
+ return []
+
+ def check(self, **kwargs):
+ errors = super().check(**kwargs)
+ errors.extend(self._check_default())
+ return errors
diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt
index 05d2b55d34..dc70022354 100644
--- a/docs/ref/checks.txt
+++ b/docs/ref/checks.txt
@@ -687,6 +687,8 @@ fields:
* **postgres.E001**: Base field for array has errors: ...
* **postgres.E002**: Base field for array cannot be a related field.
+* **postgres.E003**: ```` default should be a callable instead of an
+ instance so that it's not shared between all field instances.
``sites``
---------
diff --git a/tests/postgres_tests/models.py b/tests/postgres_tests/models.py
index fa390daaa8..35e76d0622 100644
--- a/tests/postgres_tests/models.py
+++ b/tests/postgres_tests/models.py
@@ -41,7 +41,7 @@ class PostgreSQLModel(models.Model):
class IntegerArrayModel(PostgreSQLModel):
- field = ArrayField(models.IntegerField(), default=[], blank=True)
+ field = ArrayField(models.IntegerField(), default=list, blank=True)
class NullableIntegerArrayModel(PostgreSQLModel):
diff --git a/tests/postgres_tests/test_array.py b/tests/postgres_tests/test_array.py
index e2e4ccdeb2..77ac049ce4 100644
--- a/tests/postgres_tests/test_array.py
+++ b/tests/postgres_tests/test_array.py
@@ -4,7 +4,7 @@ import unittest
import uuid
from django import forms
-from django.core import exceptions, serializers, validators
+from django.core import checks, exceptions, serializers, validators
from django.core.exceptions import FieldError
from django.core.management import call_command
from django.db import IntegrityError, connection, models
@@ -424,6 +424,38 @@ class TestChecks(PostgreSQLTestCase):
self.assertEqual(len(errors), 1)
self.assertEqual(errors[0].id, 'postgres.E002')
+ def test_invalid_default(self):
+ class MyModel(PostgreSQLModel):
+ field = ArrayField(models.IntegerField(), default=[])
+
+ model = MyModel()
+ self.assertEqual(model.check(), [
+ checks.Warning(
+ msg=(
+ "ArrayField default should be a callable instead of an "
+ "instance so that it's not shared between all field "
+ "instances."
+ ),
+ hint='Use a callable instead, e.g., use `list` instead of `[]`.',
+ obj=MyModel._meta.get_field('field'),
+ id='postgres.E003',
+ )
+ ])
+
+ def test_valid_default(self):
+ class MyModel(PostgreSQLModel):
+ field = ArrayField(models.IntegerField(), default=list)
+
+ model = MyModel()
+ self.assertEqual(model.check(), [])
+
+ def test_valid_default_none(self):
+ class MyModel(PostgreSQLModel):
+ field = ArrayField(models.IntegerField(), default=None)
+
+ model = MyModel()
+ self.assertEqual(model.check(), [])
+
def test_nested_field_checks(self):
"""
Nested ArrayFields are permitted.
diff --git a/tests/postgres_tests/test_json.py b/tests/postgres_tests/test_json.py
index cdabca04d6..acbd855f1a 100644
--- a/tests/postgres_tests/test_json.py
+++ b/tests/postgres_tests/test_json.py
@@ -2,13 +2,14 @@ import datetime
import uuid
from decimal import Decimal
-from django.core import exceptions, serializers
+from django.core import checks, exceptions, serializers
from django.core.serializers.json import DjangoJSONEncoder
from django.forms import CharField, Form, widgets
+from django.test.utils import isolate_apps
from django.utils.html import escape
from . import PostgreSQLTestCase
-from .models import JSONModel
+from .models import JSONModel, PostgreSQLModel
try:
from django.contrib.postgres import forms
@@ -259,6 +260,42 @@ class TestQuerying(PostgreSQLTestCase):
self.assertTrue(JSONModel.objects.filter(field__foo__iregex=r'^bAr$').exists())
+@isolate_apps('postgres_tests')
+class TestChecks(PostgreSQLTestCase):
+
+ def test_invalid_default(self):
+ class MyModel(PostgreSQLModel):
+ field = JSONField(default={})
+
+ model = MyModel()
+ self.assertEqual(model.check(), [
+ checks.Warning(
+ msg=(
+ "JSONField default should be a callable instead of an "
+ "instance so that it's not shared between all field "
+ "instances."
+ ),
+ hint='Use a callable instead, e.g., use `dict` instead of `{}`.',
+ obj=MyModel._meta.get_field('field'),
+ id='postgres.E003',
+ )
+ ])
+
+ def test_valid_default(self):
+ class MyModel(PostgreSQLModel):
+ field = JSONField(default=dict)
+
+ model = MyModel()
+ self.assertEqual(model.check(), [])
+
+ def test_valid_default_none(self):
+ class MyModel(PostgreSQLModel):
+ field = JSONField(default=None)
+
+ model = MyModel()
+ self.assertEqual(model.check(), [])
+
+
class TestSerialization(PostgreSQLTestCase):
test_data = (
'[{"fields": {"field": {"a": "b", "c": null}, "field_custom": null}, '