diff --git a/django/db/models/fields/generated.py b/django/db/models/fields/generated.py index f89269b5e6..f90bc560e2 100644 --- a/django/db/models/fields/generated.py +++ b/django/db/models/fields/generated.py @@ -30,6 +30,7 @@ class GeneratedField(Field): self.expression = expression self.output_field = output_field self.db_persist = db_persist + self.has_null_arg = "null" in kwargs super().__init__(**kwargs) @cached_property @@ -82,6 +83,7 @@ class GeneratedField(Field): *super().check(**kwargs), *self._check_supported(databases), *self._check_persistence(databases), + *self._check_ignored_options(databases), ] output_field_clone = self.output_field.clone() output_field_clone.model = self.model @@ -188,6 +190,20 @@ class GeneratedField(Field): ) return errors + def _check_ignored_options(self, databases): + warnings = [] + + if self.has_null_arg: + warnings.append( + checks.Warning( + "null has no effect on GeneratedField.", + obj=self, + id="fields.W225", + ) + ) + + return warnings + def deconstruct(self): name, path, args, kwargs = super().deconstruct() del kwargs["blank"] diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt index 1e56464a6f..7250df692f 100644 --- a/docs/ref/checks.txt +++ b/docs/ref/checks.txt @@ -221,6 +221,7 @@ Model fields ``GeneratedField``\s. * **fields.E223**: ``GeneratedField.output_field`` has errors: ... * **fields.W224**: ``GeneratedField.output_field`` has warnings: ... +* **fields.W225**: ``null`` has no effect on ``GeneratedField``. * **fields.E900**: ``IPAddressField`` has been removed except for support in historical migrations. * **fields.W900**: ``IPAddressField`` has been deprecated. Support for it diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index a87d7261a7..fe4d764ab3 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -1377,6 +1377,9 @@ materialized view. backends that support it (SQLite, PostgreSQL, and Oracle) and marked as deferred otherwise. +:attr:`~Field.null` has no effect on ``GeneratedField`` since whether the +column is nullable depends on the database and expression used. + ``GenericIPAddressField`` ------------------------- diff --git a/tests/invalid_models_tests/test_ordinary_fields.py b/tests/invalid_models_tests/test_ordinary_fields.py index 04c18d7ddd..e2ce74c673 100644 --- a/tests/invalid_models_tests/test_ordinary_fields.py +++ b/tests/invalid_models_tests/test_ordinary_fields.py @@ -1510,3 +1510,26 @@ class GeneratedFieldTests(TestCase): Model._meta.get_field("field").check(databases={"default"}), expected_warnings, ) + + @skipUnlessDBFeature("supports_stored_generated_columns") + def test_with_null_argument(self): + class Model(models.Model): + value = models.IntegerField() + field = models.GeneratedField( + expression=models.F("value") * 2, + output_field=models.IntegerField(), + db_persist=True, + null=True, + ) + + expected_warnings = [ + DjangoWarning( + "null has no effect on GeneratedField.", + obj=Model._meta.get_field("field"), + id="fields.W225", + ), + ] + self.assertEqual( + Model._meta.get_field("field").check(databases={"default"}), + expected_warnings, + )