Detect NULL default value as no default value.
This can happen when using `db_default=None` with `null=False` on a Django model field.
This commit is contained in:
1 parent
8c8a0c65ce
commit
28a0686d3b
9 files changed
+90
-2
No files matched your search
@@ -4,6 +4,9 @@ Feature:
|
||||
- **Breaking change**: Handle custom Django app label when gathering migrations from git reference. (#262)
|
||||
This means that, an app that was previously referenced by its folder name, will now be referenced by its Django app label.
|
||||
|
||||
Bug:
|
||||
- Consider `DEFAULT NULL` as no default value being defined (#302)
|
||||
|
||||
Miscellaneous:
|
||||
- Add py.typed file (#303)
|
||||
- Add support for Django 5.2 (#304)
|
||||
|
||||
@@ -42,9 +42,9 @@ def has_not_null_column(sql_statements: list[str], **kwargs) -> bool:
|
||||
or sql.startswith("CREATE UNIQUE INDEX")
|
||||
):
|
||||
not_null_column = True
|
||||
if re.search("DEFAULT.*NOT NULL", sql):
|
||||
if re.search("DEFAULT (?!NULL).*NOT NULL", sql):
|
||||
has_default_value = True
|
||||
if "SET DEFAULT" in sql:
|
||||
if "SET DEFAULT" in sql and "SET DEFAULT NULL" not in sql:
|
||||
has_default_value = True
|
||||
if "DROP DEFAULT" in sql:
|
||||
has_default_value = False
|
||||
|
||||
@@ -11,6 +11,9 @@ ADD_NOT_NULL_COLUMN_FOLLOWED_BY_DEFAULT = "app_add_not_null_column_followed_by_d
|
||||
ADD_NOT_NULL_COLUMN_FOLLOWED_BY_DB_DEFAULT = (
|
||||
"app_add_not_null_column_followed_by_db_default"
|
||||
)
|
||||
ADD_NOT_NULL_COLUMN_WITH_NULL_DB_DEFAULT = (
|
||||
"app_add_not_null_column_with_null_db_default"
|
||||
)
|
||||
ALTER_COLUMN = "app_alter_column"
|
||||
ALTER_COLUMN_DROP_NOT_NULL = "app_alter_column_drop_not_null"
|
||||
DROP_UNIQUE_TOGETHER = "app_unique_together"
|
||||
|
||||
@@ -7,6 +7,7 @@ from unittest import skipIf
|
||||
import django
|
||||
from django.conf import settings
|
||||
from django.core.management import call_command
|
||||
from django.test import override_settings
|
||||
|
||||
from django_migration_linter import MigrationLinter
|
||||
from tests import fixtures
|
||||
@@ -166,6 +167,15 @@ class SqliteBackwardCompatibilityDetectionTestCase(
|
||||
app = fixtures.ADD_NOT_NULL_COLUMN_FOLLOWED_BY_DB_DEFAULT
|
||||
self._test_linter_finds_errors(app)
|
||||
|
||||
@skipIf(django.VERSION[0] < 5, "db_default was implemented in Django 5.0")
|
||||
@override_settings(
|
||||
INSTALLED_APPS=settings.INSTALLED_APPS
|
||||
+ ["tests.test_project.app_add_not_null_column_with_null_db_default"]
|
||||
)
|
||||
def test_detected_not_null_column_with_null_db_default(self):
|
||||
app = fixtures.ADD_NOT_NULL_COLUMN_WITH_NULL_DB_DEFAULT
|
||||
self._test_linter_finds_errors(app)
|
||||
|
||||
def test_detect_make_column_not_null_with_lib_default(self):
|
||||
# The 'django-add-default-value' doesn't handle sqlite correctly
|
||||
app = fixtures.MAKE_NOT_NULL_WITH_LIB_DEFAULT
|
||||
@@ -177,6 +187,9 @@ class MySqlBackwardCompatibilityDetectionTestCase(
|
||||
):
|
||||
databases = ["mysql"]
|
||||
|
||||
# test_detected_not_null_column_with_null_db_default is not included for mysql, because the setup raises
|
||||
# django.db.utils.OperationalError: (1067, "Invalid default value for 'not_null_field_db_default_null'")
|
||||
|
||||
|
||||
class PostgresqlBackwardCompatibilityDetectionTestCase(
|
||||
BaseBackwardCompatibilityDetection, unittest.TestCase
|
||||
@@ -191,3 +204,12 @@ class PostgresqlBackwardCompatibilityDetectionTestCase(
|
||||
linter = self._launch_linter(fixtures.CREATE_INDEX_EXCLUSIVE)
|
||||
self.assertFalse(linter.has_errors)
|
||||
self.assertTrue(linter.nb_warnings)
|
||||
|
||||
@skipIf(django.VERSION[0] < 5, "db_default was implemented in Django 5.0")
|
||||
@override_settings(
|
||||
INSTALLED_APPS=settings.INSTALLED_APPS
|
||||
+ ["tests.test_project.app_add_not_null_column_with_null_db_default"]
|
||||
)
|
||||
def test_detected_not_null_column_with_null_db_default(self):
|
||||
app = fixtures.ADD_NOT_NULL_COLUMN_WITH_NULL_DB_DEFAULT
|
||||
self._test_linter_finds_errors(app)
|
||||
Whitespace-only changes.
+30
@@ -0,0 +1,30 @@
|
||||
# Generated by Django 5.0.1 on 2024-02-04 20:07
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="A",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("field", models.IntegerField()),
|
||||
],
|
||||
),
|
||||
]
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
# Generated by Django 5.0.1 on 2024-02-04 20:07
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import django.db.models.functions.math
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("app_add_not_null_column_with_null_db_default", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="a",
|
||||
name="not_null_field_db_default_null",
|
||||
field=models.IntegerField(db_default=None, default=None, null=False),
|
||||
),
|
||||
]
|
||||
Whitespace-only changes.
@@ -0,0 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from django.db import models
|
||||
from django.db.models.functions import Pi
|
||||
|
||||
|
||||
class A(models.Model):
|
||||
field = models.IntegerField()
|
||||
not_null_field_db_default_null = models.IntegerField(null=False, db_default=None)
|
||||
Reference in new issue
Block a user