Skip to content

Commit a7c02a1

Browse files
committed
Add field-default-null
1 parent 3819ce8 commit a7c02a1

File tree

6 files changed

+42
-0
lines changed

6 files changed

+42
-0
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
### Unreleased
44

5+
- Add checks:
6+
- field-default-null
7+
58
### 0.5.0
69

710
- Fix ignore_checks

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ EXTRA_CHECKS = {
7474
- **field-boolean-null** - prefer using `BooleanField(null=True)` instead of `NullBooleanField`.
7575
- **field-null** - don't pass `null=False` to model fields (this is django default).
7676
- **field-foreign-key-index** - ForeignKey fields must specify `db_index` explicitly (to apply to unique together only: `when: unique_together`).
77+
- **field-default-null** - If field nullable (`null=True`), then
78+
`default=None` argument is redundant and should be removed.
79+
**WARNING** Be aware that setting is database dependent,
80+
eg. Oracle interprets empty strings as nulls as a result
81+
django uses empty string instead of null as default.
7782

7883
### DRF Serializers
7984

src/extra_checks/check_id.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ class CheckId(str, enum.Enum):
1515
X056 = "field-boolean-null"
1616
X057 = "field-null"
1717
X058 = "field-foreign-key-db-index"
18+
X059 = "field-default-null"
1819
X301 = "drf-model-serializer-extra-kwargs"
1920
X302 = "drf-model-serializer-meta-attribute"

src/extra_checks/checks/model_field_checks.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,3 +231,18 @@ def apply(
231231
hint="Specify `db_index` field argument.",
232232
obj=field,
233233
)
234+
235+
236+
@registry.register(django.core.checks.Tags.models)
237+
class CheckFieldDefaultNull(CheckModelField):
238+
Id = CheckId.X059
239+
240+
def apply(
241+
self, field: models.fields.Field, field_ast: FieldAST, **kwargs: Any
242+
) -> Iterator[django.core.checks.CheckMessage]:
243+
if field.null and field.default is None and "default" in field_ast.kwargs:
244+
yield self.message(
245+
"Argument `default=None` is redundant if `null=True` is set. (see docs about exceptions).",
246+
hint="Remove `default=None` from field arguments.",
247+
obj=field,
248+
)

tests/example/models.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ class ModelFieldNullFalse(models.Model):
9292
null_fail = models.NullBooleanField()
9393

9494

95+
class ModelFieldNullDefault(models.Model):
96+
myfield = models.IntegerField(default=None)
97+
myfield_fail = models.IntegerField(null=True, default=None)
98+
99+
95100
class ModelFieldForeignKeyIndex(models.Model):
96101
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name="+")
97102
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="+")

tests/test_model_field_checks.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,3 +228,16 @@ def test_field_ignore_types(test_case):
228228
)
229229
assert len(messages) == 1
230230
assert {m.obj.name for m in messages} == {"file_fail"}
231+
232+
233+
def test_field_null_default_null(test_case):
234+
messages = (
235+
test_case.settings(
236+
{"checks": [model_field_checks.CheckFieldDefaultNull.Id.value]}
237+
)
238+
.models(models.ModelFieldNullDefault)
239+
.check(model_field_checks.CheckFieldDefaultNull)
240+
.run()
241+
)
242+
assert len(messages) == 1
243+
assert messages[0].obj.name == "myfield_fail"

0 commit comments

Comments
 (0)