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)
|
- **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.
|
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:
|
Miscellaneous:
|
||||||
- Add py.typed file (#303)
|
- Add py.typed file (#303)
|
||||||
- Add support for Django 5.2 (#304)
|
- 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")
|
or sql.startswith("CREATE UNIQUE INDEX")
|
||||||
):
|
):
|
||||||
not_null_column = True
|
not_null_column = True
|
||||||
if re.search("DEFAULT.*NOT NULL", sql):
|
if re.search("DEFAULT (?!NULL).*NOT NULL", sql):
|
||||||
has_default_value = True
|
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
|
has_default_value = True
|
||||||
if "DROP DEFAULT" in sql:
|
if "DROP DEFAULT" in sql:
|
||||||
has_default_value = False
|
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 = (
|
ADD_NOT_NULL_COLUMN_FOLLOWED_BY_DB_DEFAULT = (
|
||||||
"app_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 = "app_alter_column"
|
||||||
ALTER_COLUMN_DROP_NOT_NULL = "app_alter_column_drop_not_null"
|
ALTER_COLUMN_DROP_NOT_NULL = "app_alter_column_drop_not_null"
|
||||||
DROP_UNIQUE_TOGETHER = "app_unique_together"
|
DROP_UNIQUE_TOGETHER = "app_unique_together"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from unittest import skipIf
|
|||||||
import django
|
import django
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
|
from django.test import override_settings
|
||||||
|
|
||||||
from django_migration_linter import MigrationLinter
|
from django_migration_linter import MigrationLinter
|
||||||
from tests import fixtures
|
from tests import fixtures
|
||||||
@@ -166,6 +167,15 @@ class SqliteBackwardCompatibilityDetectionTestCase(
|
|||||||
app = fixtures.ADD_NOT_NULL_COLUMN_FOLLOWED_BY_DB_DEFAULT
|
app = fixtures.ADD_NOT_NULL_COLUMN_FOLLOWED_BY_DB_DEFAULT
|
||||||
self._test_linter_finds_errors(app)
|
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):
|
def test_detect_make_column_not_null_with_lib_default(self):
|
||||||
# The 'django-add-default-value' doesn't handle sqlite correctly
|
# The 'django-add-default-value' doesn't handle sqlite correctly
|
||||||
app = fixtures.MAKE_NOT_NULL_WITH_LIB_DEFAULT
|
app = fixtures.MAKE_NOT_NULL_WITH_LIB_DEFAULT
|
||||||
@@ -177,6 +187,9 @@ class MySqlBackwardCompatibilityDetectionTestCase(
|
|||||||
):
|
):
|
||||||
databases = ["mysql"]
|
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(
|
class PostgresqlBackwardCompatibilityDetectionTestCase(
|
||||||
BaseBackwardCompatibilityDetection, unittest.TestCase
|
BaseBackwardCompatibilityDetection, unittest.TestCase
|
||||||
@@ -191,3 +204,12 @@ class PostgresqlBackwardCompatibilityDetectionTestCase(
|
|||||||
linter = self._launch_linter(fixtures.CREATE_INDEX_EXCLUSIVE)
|
linter = self._launch_linter(fixtures.CREATE_INDEX_EXCLUSIVE)
|
||||||
self.assertFalse(linter.has_errors)
|
self.assertFalse(linter.has_errors)
|
||||||
self.assertTrue(linter.nb_warnings)
|
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