Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add official support for Django 5.0 #49

Merged
merged 9 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ RUN apt-get install -q -y --no-install-recommends \
python3.9 python3.9-distutils \
python3.10 python3.10-distutils \
python3.11 python3.11-distutils \
python3.12 python3.12-distutils \
python3-pip \
libgdal30
RUN pip3 install setuptools tox
Expand Down
32 changes: 23 additions & 9 deletions django_zero_downtime_migrations/backends/postgres/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,19 @@ class DatabaseSchemaEditorMixin:
)
sql_delete_check = PGAccessExclusive(PostgresDatabaseSchemaEditor.sql_delete_check)

sql_create_unique = MultiStatementSQL(
PGShareUpdateExclusive("CREATE UNIQUE INDEX CONCURRENTLY %(name)s ON %(table)s (%(columns)s)",
disable_statement_timeout=True),
PGAccessExclusive("ALTER TABLE %(table)s ADD CONSTRAINT %(name)s UNIQUE USING INDEX %(name)s"),
)
if django.VERSION[:2] >= (5, 0):
sql_create_unique = MultiStatementSQL(
PGShareUpdateExclusive("CREATE UNIQUE INDEX CONCURRENTLY %(name)s ON %(table)s "
"(%(columns)s)%(nulls_distinct)s",
disable_statement_timeout=True),
PGAccessExclusive("ALTER TABLE %(table)s ADD CONSTRAINT %(name)s UNIQUE USING INDEX %(name)s"),
)
else:
sql_create_unique = MultiStatementSQL(
PGShareUpdateExclusive("CREATE UNIQUE INDEX CONCURRENTLY %(name)s ON %(table)s (%(columns)s)",
disable_statement_timeout=True),
PGAccessExclusive("ALTER TABLE %(table)s ADD CONSTRAINT %(name)s UNIQUE USING INDEX %(name)s"),
)
sql_delete_unique = PGAccessExclusive(PostgresDatabaseSchemaEditor.sql_delete_unique)

sql_create_fk = MultiStatementSQL(
Expand All @@ -203,10 +211,16 @@ class DatabaseSchemaEditorMixin:
PostgresDatabaseSchemaEditor.sql_create_index_concurrently,
disable_statement_timeout=True
)
sql_create_unique_index = PGShareUpdateExclusive(
"CREATE UNIQUE INDEX CONCURRENTLY %(name)s ON %(table)s (%(columns)s)%(condition)s",
disable_statement_timeout=True
)
if django.VERSION[:2] >= (5, 0):
sql_create_unique_index = PGShareUpdateExclusive(
"CREATE UNIQUE INDEX CONCURRENTLY %(name)s ON %(table)s (%(columns)s)%(condition)s%(nulls_distinct)s",
disable_statement_timeout=True
)
else:
sql_create_unique_index = PGShareUpdateExclusive(
"CREATE UNIQUE INDEX CONCURRENTLY %(name)s ON %(table)s (%(columns)s)%(condition)s",
disable_statement_timeout=True
)
sql_delete_index = PGShareUpdateExclusive("DROP INDEX CONCURRENTLY IF EXISTS %(name)s")
sql_delete_index_concurrently = PGShareUpdateExclusive(
PostgresDatabaseSchemaEditor.sql_delete_index_concurrently
Expand Down
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,13 @@ def _get_long_description():
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Framework :: Django',
'Framework :: Django :: 3.2',
'Framework :: Django :: 4.0',
'Framework :: Django :: 4.1',
'Framework :: Django :: 4.2',
'Framework :: Django :: 5.0',
],
keywords='django postgres postgresql migrations',
packages=find_packages(exclude=['manage*', 'tests*']),
Expand Down
74 changes: 74 additions & 0 deletions tests/unit/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -1656,6 +1656,80 @@ def test_add_meta_conditional_multicolumn_unique_constraint__ok():
]


@pytest.mark.django_db
@pytest.mark.skipif(django.VERSION[:2] < (5, 0), reason='functionality provided in django 5.0')
@override_settings(ZERO_DOWNTIME_MIGRATIONS_RAISE_FOR_UNSAFE=True)
def test_add_meta_unique_constraint_nulls_distinct_fields__ok():
with cmp_schema_editor() as editor:
editor.add_constraint(
Model,
models.UniqueConstraint(fields=('field1',), name='field1_uniq', nulls_distinct=True),
)
assert editor.collected_sql == [
'CREATE UNIQUE INDEX CONCURRENTLY "field1_uniq" ON "tests_model" ("field1") NULLS DISTINCT;',
] + timeouts(
'ALTER TABLE "tests_model" ADD CONSTRAINT "field1_uniq" '
'UNIQUE USING INDEX "field1_uniq";',
)
assert editor.django_sql == [
'ALTER TABLE "tests_model" ADD CONSTRAINT "field1_uniq" UNIQUE NULLS DISTINCT ("field1");',
]


@pytest.mark.django_db
@pytest.mark.skipif(django.VERSION[:2] < (5, 0), reason='functionality provided in django 5.0')
@override_settings(ZERO_DOWNTIME_MIGRATIONS_RAISE_FOR_UNSAFE=True)
def test_add_meta_unique_constraint_nulls_not_distinct_fields__ok():
with cmp_schema_editor() as editor:
editor.add_constraint(
Model,
models.UniqueConstraint(fields=('field1',), name='field1_uniq', nulls_distinct=False),
)
assert editor.collected_sql == [
'CREATE UNIQUE INDEX CONCURRENTLY "field1_uniq" ON "tests_model" ("field1") NULLS NOT DISTINCT;',
] + timeouts(
'ALTER TABLE "tests_model" ADD CONSTRAINT "field1_uniq" '
'UNIQUE USING INDEX "field1_uniq";',
)
assert editor.django_sql == [
'ALTER TABLE "tests_model" ADD CONSTRAINT "field1_uniq" UNIQUE NULLS NOT DISTINCT ("field1");',
]


@pytest.mark.django_db
@pytest.mark.skipif(django.VERSION[:2] < (5, 0), reason='functionality provided in django 5.0')
@override_settings(ZERO_DOWNTIME_MIGRATIONS_RAISE_FOR_UNSAFE=True)
def test_add_meta_unique_constraint_nulls_distinct_expression__ok():
with cmp_schema_editor() as editor:
editor.add_constraint(
Model,
models.UniqueConstraint(models.F('field1'), name='field1_uniq', nulls_distinct=True),
)
assert editor.collected_sql == [
'CREATE UNIQUE INDEX CONCURRENTLY "field1_uniq" ON "tests_model" ("field1") NULLS DISTINCT;',
]
assert editor.django_sql == [
'CREATE UNIQUE INDEX "field1_uniq" ON "tests_model" ("field1") NULLS DISTINCT;',
]


@pytest.mark.django_db
@pytest.mark.skipif(django.VERSION[:2] < (5, 0), reason='functionality provided in django 5.0')
@override_settings(ZERO_DOWNTIME_MIGRATIONS_RAISE_FOR_UNSAFE=True)
def test_add_meta_unique_constraint_nulls_not_distinct_expression__ok():
with cmp_schema_editor() as editor:
editor.add_constraint(
Model,
models.UniqueConstraint(models.F('field1'), name='field1_uniq', nulls_distinct=False),
)
assert editor.collected_sql == [
'CREATE UNIQUE INDEX CONCURRENTLY "field1_uniq" ON "tests_model" ("field1") NULLS NOT DISTINCT;',
]
assert editor.django_sql == [
'CREATE UNIQUE INDEX "field1_uniq" ON "tests_model" ("field1") NULLS NOT DISTINCT;',
]


@pytest.mark.django_db
@override_settings(ZERO_DOWNTIME_MIGRATIONS_RAISE_FOR_UNSAFE=True)
def test_drop_meta_unique_constraint__ok():
Expand Down
38 changes: 20 additions & 18 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
[tox]
envlist = py{3.8,3.9,3.10,3.11}-django{4.2}-psycopg{2,3},py{3.8,3.9,3.10,3.11}-django{4.0,4.1},py{3.6,3.7,3.8,3.9,3.10}-django{3.2}
envlist =
py{3.10,3.11,3.12}-django{5.0}-psycopg{2,3}
py{3.8,3.9,3.10,3.11,3.12}-django{4.2}-psycopg{2,3}
py{3.8,3.9,3.10,3.11}-django{4.0,4.1}
py{3.6,3.7,3.8,3.9,3.10}-django{3.2}

[testenv]
usedevelop = True
allowlist_externals = bash
commands =
py{3.11}-django{4.2}-psycopg{3}: flake8
py{3.11}-django{4.2}-psycopg{3}: isort . --check --diff
py{3.12}-django{5.0}-psycopg{3}: flake8
py{3.12}-django{5.0}-psycopg{3}: isort . --check --diff

py{3.8,3.9,3.10,3.11}-django{4.2}-psycopg{2,3}: bash -c "DB_HOST=pg15 DB_USER=test pytest tests/unit"
py{3.8,3.9,3.10,3.11}-django{4.2}-psycopg{2,3}: bash -c "DB_HOST=postgis15 DB_USER=root DB_ENGINE=django_zero_downtime_migrations.backends.postgis pytest tests/unit"
py{3.8,3.9,3.10,3.11,3.12}-django{4.2,5.0}-psycopg{2,3}: bash -c "DB_HOST=pg15 DB_USER=test pytest tests/unit"
py{3.8,3.9,3.10,3.11,3.12}-django{4.2,5.0}-psycopg{2,3}: bash -c "DB_HOST=postgis15 DB_USER=root DB_ENGINE=django_zero_downtime_migrations.backends.postgis pytest tests/unit"

py{3.8,3.9,3.10,3.11}-django{4.0,4.1}: bash -c "DB_HOST=pg15 DB_USER=test pytest tests/unit"
py{3.8,3.9,3.10,3.11}-django{4.0,4.1}: bash -c "DB_HOST=postgis15 DB_USER=root DB_ENGINE=django_zero_downtime_migrations.backends.postgis pytest tests/unit"
Expand All @@ -26,21 +30,19 @@ commands =
py{3.11}-django{4.2}-psycopg{3}: bash -c "DB_HOST=pg12 DB_USER=test pytest tests/integration"

deps =
py{3.11}-django{4.2}-psycopg{3}: flake8
py{3.11}-django{4.2}-psycopg{3}: isort
py{3.12}-django{5.0}-psycopg{3}: flake8
py{3.12}-django{5.0}-psycopg{3}: isort

pytest
pytest-django
pytest-mock

django{3.2}: psycopg2-binary
django{4.0}: psycopg2-binary
django{4.1}: psycopg2-binary
django{4.2}-psycopg{2}: psycopg2-binary
django{4.2}-psycopg{3}: psycopg[binary]

django{3.2}: django>=3.2,<4.0
django{4.0}: django>=4.0,<4.1
django{4.1}: django>=4.1,<4.2
django{4.2}-psycopg{2}: django>=4.2,<5.0
django{4.2}-psycopg{3}: django>=4.2,<5.0
django{3.2,4.0,4.1}: psycopg2-binary
psycopg2: psycopg2-binary
psycopg3: psycopg[binary]

django3.2: django>=3.2,<4.0
django4.0: django>=4.0,<4.1
django4.1: django>=4.1,<4.2
django4.2: django>=4.2,<5.0
django5.0: django>=5.0,<5.1
Loading