Skip to content

Django 4.x support #167

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

Merged
merged 5 commits into from
Jan 30, 2022
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
6 changes: 3 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ jobs:
extra: test
- run-tests:
pyversion: 38
djversions: 20,21,22,30,31,32
djversions: 20,21,22,30,31,32,40

test-python39:
executor:
Expand All @@ -90,7 +90,7 @@ jobs:
extra: test
- run-tests:
pyversion: 39
djversions: 21,22,30,31,32
djversions: 21,22,30,31,32,40

test-python310:
executor:
Expand All @@ -102,7 +102,7 @@ jobs:
extra: test
- run-tests:
pyversion: 310
djversions: 21,22,30,31,32
djversions: 21,22,30,31,32,40
- store_test_results:
path: reports
- run:
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
| :memo: | **License** | [![License](https://img.shields.io/:license-mit-blue.svg)](http://doge.mit-license.org) |
| :package: | **PyPi** | [![PyPi](https://badge.fury.io/py/django-postgres-extra.svg)](https://pypi.python.org/pypi/django-postgres-extra) |
| :four_leaf_clover: | **Code coverage** | [![Coverage Status](https://coveralls.io/repos/github/SectorLabs/django-postgres-extra/badge.svg?branch=coveralls)](https://coveralls.io/github/SectorLabs/django-postgres-extra?branch=master) |
| <img src="https://cdn.iconscout.com/icon/free/png-256/django-1-282754.png" width="22px" height="22px" align="center" /> | **Django Versions** | 2.0, 2.1, 2.2, 3.0, 3.1, 3.2 |
| <img src="https://cdn.iconscout.com/icon/free/png-256/django-1-282754.png" width="22px" height="22px" align="center" /> | **Django Versions** | 2.0, 2.1, 2.2, 3.0, 3.1, 3.2, 4.0 |
| <img src="http://www.iconarchive.com/download/i73027/cornmanthe3rd/plex/Other-python.ico" width="22px" height="22px" align="center" /> | **Python Versions** | 3.6, 3.7, 3.8, 3.9, 3.10 |
| :book: | **Documentation** | [Read The Docs](https://django-postgres-extra.readthedocs.io/en/master/) |
| :warning: | **Upgrade** | [Upgrade from v1.x](https://django-postgres-extra.readthedocs.io/en/master/major_releases.html#new-features)
Expand Down Expand Up @@ -59,7 +59,7 @@ With seamless we mean that any features we add will work truly seamlessly. You s
### Prerequisites

* PostgreSQL 10 or newer.
* Django 2.0 or newer (including 3.x).
* Django 2.0 or newer (including 3.x, 4.x).
* Python 3.6 or newer.

### Getting started
Expand Down
2 changes: 1 addition & 1 deletion docs/source/conflict_handling.rst
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ This is preferable when the data you're about to insert is the same as the one t
# obj2 is none! object alreaddy exists
obj2 = MyModel.objects.on_conflict(['name'], ConflictAction.NOTHING).insert(name="me")

This applies to both :meth:`~psqlextra.query.PostgresQuerySet.insert` and :meth:`~psqlextra.query.PostgresQuerySet.bulk_insert`
This applies all methods: :meth:`~psqlextra.query.PostgresQuerySet.insert`, :meth:`~psqlextra.query.PostgresQuerySet.insert_and_get`, :meth:`~psqlextra.query.PostgresQuerySet.bulk_insert`


Bulk
Expand Down
150 changes: 108 additions & 42 deletions psqlextra/backend/migrations/patched_autodetector.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from contextlib import contextmanager
from unittest import mock

import django

from django.db.migrations import (
AddField,
AlterField,
Expand All @@ -11,7 +13,6 @@
)
from django.db.migrations.autodetector import MigrationAutodetector
from django.db.migrations.operations.base import Operation
from django.db.models import Model

from psqlextra.models import (
PostgresMaterializedViewModel,
Expand All @@ -21,6 +22,11 @@
from psqlextra.types import PostgresPartitioningMethod

from . import operations
from .state import (
PostgresMaterializedViewModelState,
PostgresPartitionedModelState,
PostgresViewModelState,
)

# original `MigrationAutodetector.add_operation`
# function, saved here so the patched version can
Expand Down Expand Up @@ -88,62 +94,108 @@ def _transform_view_field_operations(self, operation: Operation):
actually applying it.
"""

model = self.autodetector.new_apps.get_model(
self.app_label, operation.model_name
)
if django.VERSION >= (4, 0):
model_identifier = (self.app_label, operation.model_name.lower())
model_state = (
self.autodetector.to_state.models.get(model_identifier)
or self.autodetector.from_state.models[model_identifier]
)

if issubclass(model, PostgresViewModel):
return self.add(operations.ApplyState(state_operation=operation))
if isinstance(model_state, PostgresViewModelState):
return self.add(
operations.ApplyState(state_operation=operation)
)
else:
model = self.autodetector.new_apps.get_model(
self.app_label, operation.model_name
)

if issubclass(model, PostgresViewModel):
return self.add(
operations.ApplyState(state_operation=operation)
)

return self.add(operation)

def add_create_model(self, operation: CreateModel):
"""Adds the specified :see:CreateModel operation to the list of
operations to execute in the migration."""

model = self.autodetector.new_apps.get_model(
self.app_label, operation.name
)
if django.VERSION >= (4, 0):
model_state = self.autodetector.to_state.models[
self.app_label, operation.name.lower()
]

if isinstance(model_state, PostgresPartitionedModelState):
return self.add_create_partitioned_model(operation)
elif isinstance(model_state, PostgresMaterializedViewModelState):
return self.add_create_materialized_view_model(operation)
elif isinstance(model_state, PostgresViewModelState):
return self.add_create_view_model(operation)
else:
model = self.autodetector.new_apps.get_model(
self.app_label, operation.name
)

if issubclass(model, PostgresPartitionedModel):
return self.add_create_partitioned_model(model, operation)
elif issubclass(model, PostgresMaterializedViewModel):
return self.add_create_materialized_view_model(model, operation)
elif issubclass(model, PostgresViewModel):
return self.add_create_view_model(model, operation)
if issubclass(model, PostgresPartitionedModel):
return self.add_create_partitioned_model(operation)
elif issubclass(model, PostgresMaterializedViewModel):
return self.add_create_materialized_view_model(operation)
elif issubclass(model, PostgresViewModel):
return self.add_create_view_model(operation)

return self.add(operation)

def add_delete_model(self, operation: DeleteModel):
"""Adds the specified :see:Deletemodel operation to the list of
operations to execute in the migration."""

model = self.autodetector.old_apps.get_model(
self.app_label, operation.name
)
if django.VERSION >= (4, 0):
model_state = self.autodetector.from_state.models[
self.app_label, operation.name.lower()
]

if isinstance(model_state, PostgresPartitionedModelState):
return self.add_delete_partitioned_model(operation)
elif isinstance(model_state, PostgresMaterializedViewModelState):
return self.add_delete_materialized_view_model(operation)
elif isinstance(model_state, PostgresViewModelState):
return self.add_delete_view_model(operation)
else:
model = self.autodetector.old_apps.get_model(
self.app_label, operation.name
)

if issubclass(model, PostgresPartitionedModel):
return self.add_delete_partitioned_model(model, operation)
elif issubclass(model, PostgresMaterializedViewModel):
return self.add_delete_materialized_view_model(model, operation)
elif issubclass(model, PostgresViewModel):
return self.add_delete_view_model(model, operation)
if issubclass(model, PostgresPartitionedModel):
return self.add_delete_partitioned_model(operation)
elif issubclass(model, PostgresMaterializedViewModel):
return self.add_delete_materialized_view_model(operation)
elif issubclass(model, PostgresViewModel):
return self.add_delete_view_model(operation)

return self.add(operation)

def add_create_partitioned_model(
self, model: Model, operation: CreateModel
):
def add_create_partitioned_model(self, operation: CreateModel):
"""Adds a :see:PostgresCreatePartitionedModel operation to the list of
operations to execute in the migration."""

partitioning_options = model._partitioning_meta.original_attrs
if django.VERSION >= (4, 0):
model_state = self.autodetector.to_state.models[
self.app_label, operation.name.lower()
]
partitioning_options = model_state.partitioning_options
else:
model = self.autodetector.new_apps.get_model(
self.app_label, operation.name
)
partitioning_options = model._partitioning_meta.original_attrs

_, args, kwargs = operation.deconstruct()

if partitioning_options["method"] != PostgresPartitioningMethod.HASH:
self.add(
operations.PostgresAddDefaultPartition(
model_name=model.__name__, name="default"
model_name=operation.name, name="default"
)
)

Expand All @@ -153,9 +205,7 @@ def add_create_partitioned_model(
)
)

def add_delete_partitioned_model(
self, model: Model, operation: DeleteModel
):
def add_delete_partitioned_model(self, operation: DeleteModel):
"""Adds a :see:PostgresDeletePartitionedModel operation to the list of
operations to execute in the migration."""

Expand All @@ -164,11 +214,21 @@ def add_delete_partitioned_model(
operations.PostgresDeletePartitionedModel(*args, **kwargs)
)

def add_create_view_model(self, model: Model, operation: CreateModel):
def add_create_view_model(self, operation: CreateModel):
"""Adds a :see:PostgresCreateViewModel operation to the list of
operations to execute in the migration."""

view_options = model._view_meta.original_attrs
if django.VERSION >= (4, 0):
model_state = self.autodetector.to_state.models[
self.app_label, operation.name.lower()
]
view_options = model_state.view_options
else:
model = self.autodetector.new_apps.get_model(
self.app_label, operation.name
)
view_options = model._view_meta.original_attrs

_, args, kwargs = operation.deconstruct()

self.add(
Expand All @@ -177,20 +237,28 @@ def add_create_view_model(self, model: Model, operation: CreateModel):
)
)

def add_delete_view_model(self, model: Model, operation: DeleteModel):
def add_delete_view_model(self, operation: DeleteModel):
"""Adds a :see:PostgresDeleteViewModel operation to the list of
operations to execute in the migration."""

_, args, kwargs = operation.deconstruct()
return self.add(operations.PostgresDeleteViewModel(*args, **kwargs))

def add_create_materialized_view_model(
self, model: Model, operation: CreateModel
):
def add_create_materialized_view_model(self, operation: CreateModel):
"""Adds a :see:PostgresCreateMaterializedViewModel operation to the
list of operations to execute in the migration."""

view_options = model._view_meta.original_attrs
if django.VERSION >= (4, 0):
model_state = self.autodetector.to_state.models[
self.app_label, operation.name.lower()
]
view_options = model_state.view_options
else:
model = self.autodetector.new_apps.get_model(
self.app_label, operation.name
)
view_options = model._view_meta.original_attrs

_, args, kwargs = operation.deconstruct()

self.add(
Expand All @@ -199,9 +267,7 @@ def add_create_materialized_view_model(
)
)

def add_delete_materialized_view_model(
self, model: Model, operation: DeleteModel
):
def add_delete_materialized_view_model(self, operation: DeleteModel):
"""Adds a :see:PostgresDeleteMaterializedViewModel operation to the
list of operations to execute in the migration."""

Expand Down
3 changes: 2 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist = py36-dj{20,21,22,30,31,32}, py37-dj{20,21,22,30,31,32}, py38-dj{20,21,22,30,31,32}, py39-dj{21,22,30,31,32}, py310-dj{21,22,30,31,32}
envlist = py36-dj{20,21,22,30,31,32}, py37-dj{20,21,22,30,31,32}, py38-dj{20,21,22,30,31,32,40}, py39-dj{21,22,30,31,32,40}, py310-dj{21,22,30,31,32,40}

[testenv]
deps =
Expand All @@ -9,6 +9,7 @@ deps =
dj30: Django~=3.0.0
dj31: Django~=3.1.0
dj32: Django~=3.2.0
dj40: Django~=4.0.0
.[test]
setenv =
DJANGO_SETTINGS_MODULE=settings
Expand Down