Skip to content

Commit fd89a1e

Browse files
committed
Raise error if using db_default=None with null=False
1 parent 667a363 commit fd89a1e

File tree

8 files changed

+87
-2
lines changed

8 files changed

+87
-2
lines changed

src/django_migration_linter/sql_analyser/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ def has_not_null_column(sql_statements: list[str], **kwargs) -> bool:
4242
or sql.startswith("CREATE UNIQUE INDEX")
4343
):
4444
not_null_column = True
45-
if re.search("DEFAULT.*NOT NULL", sql):
45+
if re.search("DEFAULT.*NOT NULL", sql) and "DEFAULT NULL NOT NULL" not in sql:
4646
has_default_value = True
47-
if "SET DEFAULT" in sql:
47+
if "SET DEFAULT" in sql and "SET DEFAULT NULL" not in sql:
4848
has_default_value = True
4949
if "DROP DEFAULT" in sql:
5050
has_default_value = False

tests/fixtures.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
ADD_NOT_NULL_COLUMN_FOLLOWED_BY_DB_DEFAULT = (
1212
"app_add_not_null_column_followed_by_db_default"
1313
)
14+
ADD_NOT_NULL_COLUMN_WITH_NULL_DB_DEFAULT = (
15+
"app_add_not_null_column_with_null_db_default"
16+
)
1417
ALTER_COLUMN = "app_alter_column"
1518
ALTER_COLUMN_DROP_NOT_NULL = "app_alter_column_drop_not_null"
1619
DROP_UNIQUE_TOGETHER = "app_unique_together"

tests/functional/test_migration_linter.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import django
88
from django.conf import settings
99
from django.core.management import call_command
10+
from django.test import override_settings
1011

1112
from django_migration_linter import MigrationLinter
1213
from tests import fixtures
@@ -140,6 +141,15 @@ def test_accept_not_null_column_followed_by_adding_db_default(self):
140141
app = fixtures.ADD_NOT_NULL_COLUMN_FOLLOWED_BY_DB_DEFAULT
141142
self._test_linter_finds_errors(app)
142143

144+
@skipIf(django.VERSION[0] < 5, "db_default was implemented in Django 5.0")
145+
@override_settings(
146+
INSTALLED_APPS=settings.INSTALLED_APPS
147+
+ ["tests.test_project.app_add_not_null_column_with_null_db_default"]
148+
)
149+
def test_detected_not_null_column_with_null_db_default(self):
150+
app = fixtures.ADD_NOT_NULL_COLUMN_WITH_NULL_DB_DEFAULT
151+
self._test_linter_finds_errors(app)
152+
143153
def test_detect_make_column_not_null_with_lib_default(self):
144154
# The 'django-add-default-value' doesn't handle sqlite correctly
145155
app = fixtures.MAKE_NOT_NULL_WITH_LIB_DEFAULT
@@ -151,6 +161,9 @@ class MySqlBackwardCompatibilityDetectionTestCase(
151161
):
152162
databases = ["mysql"]
153163

164+
# test_detected_not_null_column_with_null_db_default is not included for mysql, because the setup raises
165+
# django.db.utils.OperationalError: (1067, "Invalid default value for 'not_null_field_db_default_null'")
166+
154167

155168
class PostgresqlBackwardCompatibilityDetectionTestCase(
156169
BaseBackwardCompatibilityDetection, unittest.TestCase
@@ -165,3 +178,12 @@ def test_create_index_exclusive(self):
165178
linter = self._launch_linter(fixtures.CREATE_INDEX_EXCLUSIVE)
166179
self.assertFalse(linter.has_errors)
167180
self.assertTrue(linter.nb_warnings)
181+
182+
@skipIf(django.VERSION[0] < 5, "db_default was implemented in Django 5.0")
183+
@override_settings(
184+
INSTALLED_APPS=settings.INSTALLED_APPS
185+
+ ["tests.test_project.app_add_not_null_column_with_null_db_default"]
186+
)
187+
def test_detected_not_null_column_with_null_db_default(self):
188+
app = fixtures.ADD_NOT_NULL_COLUMN_WITH_NULL_DB_DEFAULT
189+
self._test_linter_finds_errors(app)

tests/test_project/app_add_not_null_column_with_null_db_default/__init__.py

Whitespace-only changes.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Generated by Django 5.0.1 on 2024-02-04 20:07
2+
3+
from __future__ import annotations
4+
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
initial = True
11+
12+
dependencies = []
13+
14+
operations = [
15+
migrations.CreateModel(
16+
name="A",
17+
fields=[
18+
(
19+
"id",
20+
models.AutoField(
21+
auto_created=True,
22+
primary_key=True,
23+
serialize=False,
24+
verbose_name="ID",
25+
),
26+
),
27+
("field", models.IntegerField()),
28+
],
29+
),
30+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Generated by Django 5.0.1 on 2024-02-04 20:07
2+
3+
from __future__ import annotations
4+
5+
import django.db.models.functions.math
6+
from django.db import migrations, models
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
dependencies = [
12+
("app_add_not_null_column_with_null_db_default", "0001_initial"),
13+
]
14+
15+
operations = [
16+
migrations.AddField(
17+
model_name="a",
18+
name="not_null_field_db_default_null",
19+
field=models.IntegerField(db_default=None, default=None, null=False),
20+
),
21+
]

tests/test_project/app_add_not_null_column_with_null_db_default/migrations/__init__.py

Whitespace-only changes.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from __future__ import annotations
2+
3+
from django.db import models
4+
from django.db.models.functions import Pi
5+
6+
7+
class A(models.Model):
8+
field = models.IntegerField()
9+
not_null_field_db_default_null = models.IntegerField(null=False, db_default=None)

0 commit comments

Comments
 (0)