diff --git a/CHANGELOG.md b/CHANGELOG.md index a4bdffa..8d6bb67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ -### unreleased +### 1.3.1 - fix: #99 update value containing "where" cause exception. - fix: #97 JSONField error in ClickHouse 24.8. - fix: tuple function error in ClickHouse 24.8. +- support Django 5.1, update clickhouse-driver to 0.2.9. ### 1.3.0 diff --git a/README.md b/README.md index cc56005..aa93e87 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Django ClickHouse Database Backend [![Coverage Status](https://coveralls.io/repos/github/jayvynl/django-clickhouse-backend/badge.svg?branch=main)](https://coveralls.io/github/jayvynl/django-clickhouse-backend?branch=main) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) -Django clickhouse backend is a [django database backend](https://docs.djangoproject.com/en/4.1/ref/databases/) for +Django clickhouse backend is a [django database backend](https://docs.djangoproject.com/en/5.1/ref/databases/) for [clickhouse](https://clickhouse.com/docs/en/home/) database. This project allows using django ORM to interact with clickhouse, the goal of the project is to operate clickhouse like operating mysql, postgresql in django. @@ -152,7 +152,7 @@ class ClickHouseRouter: return None ``` -You should use [database router](https://docs.djangoproject.com/en/4.1/topics/db/multi-db/#automatic-database-routing) to +You should use [database router](https://docs.djangoproject.com/en/5.1/topics/db/multi-db/#automatic-database-routing) to automatically route your queries to the right database. In the preceding example, I write a database router which route all queries from subclasses of `clickhouse_backend.models.ClickhouseModel` or custom migrations with a `clickhouse` hint key to clickhouse. All other queries are routed to the default database (postgresql). diff --git a/clickhouse_backend/__init__.py b/clickhouse_backend/__init__.py index 6afa7d1..c6185ed 100644 --- a/clickhouse_backend/__init__.py +++ b/clickhouse_backend/__init__.py @@ -1,5 +1,5 @@ from clickhouse_backend.utils.version import get_version -VERSION = (1, 3, 0, "final", 0) +VERSION = (1, 3, 1, "final", 0) __version__ = get_version(VERSION) diff --git a/clickhouse_backend/backend/schema.py b/clickhouse_backend/backend/schema.py index d7b3f23..bc30f44 100644 --- a/clickhouse_backend/backend/schema.py +++ b/clickhouse_backend/backend/schema.py @@ -211,9 +211,8 @@ def _model_indexes_sql(self, model): "Refer to https://clickhouse.com/docs/en/engines/table-engines/" "mergetree-family/mergetree/#table_engine-mergetree-data_skipping-indexes" ) - if ( - any(field.db_index for field in model._meta.local_fields) - or getattr(model._meta, "index_together", None) + if any(field.db_index for field in model._meta.local_fields) or getattr( + model._meta, "index_together", None ): warnings.warn(msg) diff --git a/docs/Fields.md b/docs/Fields.md index 7e32fa0..0ff810d 100644 --- a/docs/Fields.md +++ b/docs/Fields.md @@ -6,8 +6,8 @@ Clickhouse backend support django builtin fields and clickhouse specific fields. **Note:** You should always use clickhouse specific fields in new projects. Support for django built-in fields is only for compatibility with existing third-party apps. -**Note:** [ForeignKey](https://docs.djangoproject.com/en/4.1/ref/models/fields/#foreignkey), [ManyToManyField](https://docs.djangoproject.com/en/4.1/ref/models/fields/#manytomanyfield) -or even [OneToOneField](https://docs.djangoproject.com/en/4.1/ref/models/fields/#onetoonefield) could be used with clickhouse backend. +**Note:** [ForeignKey](https://docs.djangoproject.com/en/5.1/ref/models/fields/#foreignkey), [ManyToManyField](https://docs.djangoproject.com/en/5.1/ref/models/fields/#manytomanyfield) +or even [OneToOneField](https://docs.djangoproject.com/en/5.1/ref/models/fields/#onetoonefield) could be used with clickhouse backend. But no database level constraints will be added, so there could be some consistency problems. @@ -171,9 +171,9 @@ DATABASES = { Fields importing path: `clickhouse_backend.models.Date[32]Field` -[Dates query](https://docs.djangoproject.com/en/4.1/ref/models/querysets/#dates) is supported by DateField and Date32Field. +[Dates query](https://docs.djangoproject.com/en/5.1/ref/models/querysets/#dates) is supported by DateField and Date32Field. -All [date lookup](https://docs.djangoproject.com/en/4.1/ref/models/querysets/#date) are supported by DateField and Date32Field. +All [date lookup](https://docs.djangoproject.com/en/5.1/ref/models/querysets/#date) are supported by DateField and Date32Field. Both Nullable and LowCardinality are supported. @@ -206,10 +206,10 @@ Fields importing path: `clickhouse_backend.models.DateTime[64]Field` DateTime64Field have a [`precision`](https://clickhouse.com/docs/en/sql-reference/data-types/datetime64) parameter which default to 6. -[Dates query](https://docs.djangoproject.com/en/4.1/ref/models/querysets/#dates) and [datetimes query](https://docs.djangoproject.com/en/4.1/ref/models/querysets/#datetimes) +[Dates query](https://docs.djangoproject.com/en/5.1/ref/models/querysets/#dates) and [datetimes query](https://docs.djangoproject.com/en/5.1/ref/models/querysets/#datetimes) are supported by DateTimeField and DateTime64Field. -All [date lookup](https://docs.djangoproject.com/en/4.1/ref/models/querysets/#date) are supported by DateTimeField and DateTime64Field. +All [date lookup](https://docs.djangoproject.com/en/5.1/ref/models/querysets/#date) are supported by DateTimeField and DateTime64Field. Both Nullable and LowCardinality are supported by DateTime. But LowCardinality is not supported by DateTime64. diff --git a/pyproject.toml b/pyproject.toml index efd114d..60fc426 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ classifiers = [ "Framework :: Django :: 4.1", "Framework :: Django :: 4.2", "Framework :: Django :: 5.0", + "Framework :: Django :: 5.1", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python", @@ -34,7 +35,7 @@ classifiers = [ ] dependencies = [ "django>=3.2", - "clickhouse-driver==0.2.8", + "clickhouse-driver==0.2.9", ] dynamic = ["version", "readme"] diff --git a/tests/aggregation/tests.py b/tests/aggregation/tests.py index f472bdc..f20674f 100644 --- a/tests/aggregation/tests.py +++ b/tests/aggregation/tests.py @@ -1106,25 +1106,6 @@ def as_sql(self, compiler, connection): ): Book.objects.annotate(Max("id")).annotate(my_max=MyMax("id__max", "price")) - def test_multi_arg_aggregate(self): - class MyMax(Max): - output_field = DecimalField() - - def as_sql(self, compiler, connection): - copy = self.copy() - copy.set_source_expressions(copy.get_source_expressions()[0:1]) - return super(MyMax, copy).as_sql(compiler, connection) - - with self.assertRaisesMessage(TypeError, "Complex aggregates require an alias"): - Book.objects.aggregate(MyMax("pages", "price")) - - with self.assertRaisesMessage( - TypeError, "Complex annotations require an alias" - ): - Book.objects.annotate(MyMax("pages", "price")) - - Book.objects.aggregate(max_field=MyMax("pages", "price")) - def test_add_implementation(self): class MySum(Sum): pass diff --git a/tests/expressions_window/tests.py b/tests/expressions_window/tests.py index 24a4833..5409ecb 100644 --- a/tests/expressions_window/tests.py +++ b/tests/expressions_window/tests.py @@ -1157,28 +1157,3 @@ def test_invalid_type_start_value_range(self): ) ) ) - - def test_invalid_type_end_row_range(self): - msg = "end argument must be a positive integer, zero, or None, but got 'a'." - with self.assertRaisesMessage(ValueError, msg): - list( - Employee.objects.annotate( - test=Window( - expression=Sum("salary"), - frame=RowRange(end="a"), - ) - ) - ) - - def test_invalid_type_start_row_range(self): - msg = "start argument must be a negative integer, zero, or None, but got 'a'." - with self.assertRaisesMessage(ValueError, msg): - list( - Employee.objects.annotate( - test=Window( - expression=Sum("salary"), - order_by=F("hire_date").asc(), - frame=RowRange(start="a"), - ) - ) - ) diff --git a/tests/migrations/test_autodetector.py b/tests/migrations/test_autodetector.py index fb4e378..2a665e1 100644 --- a/tests/migrations/test_autodetector.py +++ b/tests/migrations/test_autodetector.py @@ -2436,41 +2436,6 @@ def test_order_fields_indexes(self): changes, "otherapp", 0, 1, model_name="book", index=added_index ) - def test_create_model_with_check_constraint(self): - """Test creation of new model with constraints already defined.""" - author = ModelState( - "otherapp", - "Author", - [ - ("id", models.BigAutoField(primary_key=True)), - ("name", models.CharField(max_length=200)), - ], - { - "constraints": [ - models.CheckConstraint( - check=models.Q(name__contains="Bob"), name="name_contains_bob" - ) - ] - }, - ) - changes = self.get_changes([], [author]) - added_constraint = models.CheckConstraint( - check=models.Q(name__contains="Bob"), name="name_contains_bob" - ) - # Right number of migrations? - self.assertEqual(len(changes["otherapp"]), 1) - # Right number of actions? - migration = changes["otherapp"][0] - self.assertEqual(len(migration.operations), 2) - # Right actions order? - self.assertOperationTypes( - changes, "otherapp", 0, ["CreateModel", "AddConstraint"] - ) - self.assertOperationAttributes(changes, "otherapp", 0, 0, name="Author") - self.assertOperationAttributes( - changes, "otherapp", 0, 1, model_name="author", constraint=added_constraint - ) - def test_add_constraints(self): """Test change detection of new constraints.""" changes = self.get_changes( @@ -4276,93 +4241,6 @@ def test_rename_index_together_to_index(self): old_fields=("author", "title"), ) - def test_rename_index_together_to_index_extra_options(self): - # Indexes with extra options don't match indexes in index_together. - book_partial_index = ModelState( - "otherapp", - "Book", - [ - ("id", models.BigAutoField(primary_key=True)), - ("author", models.ForeignKey("testapp.Author", models.CASCADE)), - ("title", models.CharField(max_length=200)), - ], - { - "indexes": [ - models.Index( - fields=["author", "title"], - condition=models.Q(title__startswith="The"), - name="book_title_author_idx", - ) - ], - }, - ) - changes = self.get_changes( - [AutodetectorTests.author_empty, self.book_index_together], - [AutodetectorTests.author_empty, book_partial_index], - ) - self.assertNumberMigrations(changes, "otherapp", 1) - self.assertOperationTypes( - changes, - "otherapp", - 0, - ["AlterIndexTogether", "AddIndex"], - ) - - def test_rename_index_together_to_index_order_fields(self): - # Indexes with reordered fields don't match indexes in index_together. - changes = self.get_changes( - [AutodetectorTests.author_empty, self.book_index_together], - [AutodetectorTests.author_empty, AutodetectorTests.book_unordered_indexes], - ) - self.assertNumberMigrations(changes, "otherapp", 1) - self.assertOperationTypes( - changes, - "otherapp", - 0, - ["AlterIndexTogether", "AddIndex"], - ) - - def test_add_index_together(self): - changes = self.get_changes( - [AutodetectorTests.author_empty, AutodetectorTests.book], - [AutodetectorTests.author_empty, self.book_index_together], - ) - self.assertNumberMigrations(changes, "otherapp", 1) - self.assertOperationTypes(changes, "otherapp", 0, ["AlterIndexTogether"]) - self.assertOperationAttributes( - changes, "otherapp", 0, 0, name="book", index_together={("author", "title")} - ) - - def test_remove_index_together(self): - changes = self.get_changes( - [AutodetectorTests.author_empty, self.book_index_together], - [AutodetectorTests.author_empty, AutodetectorTests.book], - ) - self.assertNumberMigrations(changes, "otherapp", 1) - self.assertOperationTypes(changes, "otherapp", 0, ["AlterIndexTogether"]) - self.assertOperationAttributes( - changes, "otherapp", 0, 0, name="book", index_together=set() - ) - - def test_index_together_remove_fk(self): - changes = self.get_changes( - [AutodetectorTests.author_empty, self.book_index_together], - [AutodetectorTests.author_empty, AutodetectorTests.book_with_no_author], - ) - self.assertNumberMigrations(changes, "otherapp", 1) - self.assertOperationTypes( - changes, - "otherapp", - 0, - ["AlterIndexTogether", "RemoveField"], - ) - self.assertOperationAttributes( - changes, "otherapp", 0, 0, name="book", index_together=set() - ) - self.assertOperationAttributes( - changes, "otherapp", 0, 1, model_name="book", name="author" - ) - def test_index_together_no_changes(self): """ index_together doesn't generate a migration if no changes have been @@ -4374,302 +4252,6 @@ def test_index_together_no_changes(self): ) self.assertEqual(len(changes), 0) - def test_index_together_ordering(self): - """index_together triggers on ordering changes.""" - changes = self.get_changes( - [AutodetectorTests.author_empty, self.book_index_together], - [AutodetectorTests.author_empty, self.book_index_together_2], - ) - self.assertNumberMigrations(changes, "otherapp", 1) - self.assertOperationTypes( - changes, - "otherapp", - 0, - ["AlterIndexTogether"], - ) - self.assertOperationAttributes( - changes, - "otherapp", - 0, - 0, - name="book", - index_together={("title", "author")}, - ) - - def test_add_field_and_index_together(self): - """ - Added fields will be created before using them in index_together. - """ - changes = self.get_changes( - [AutodetectorTests.author_empty, AutodetectorTests.book], - [AutodetectorTests.author_empty, self.book_index_together_3], - ) - self.assertNumberMigrations(changes, "otherapp", 1) - self.assertOperationTypes( - changes, - "otherapp", - 0, - ["AddField", "AlterIndexTogether"], - ) - self.assertOperationAttributes( - changes, - "otherapp", - 0, - 1, - name="book", - index_together={("title", "newfield")}, - ) - - def test_create_model_and_index_together(self): - author = ModelState( - "otherapp", - "Author", - [ - ("id", models.BigAutoField(primary_key=True)), - ("name", models.CharField(max_length=200)), - ], - ) - book_with_author = ModelState( - "otherapp", - "Book", - [ - ("id", models.BigAutoField(primary_key=True)), - ("author", models.ForeignKey("otherapp.Author", models.CASCADE)), - ("title", models.CharField(max_length=200)), - ], - { - "index_together": {("title", "author")}, - }, - ) - changes = self.get_changes( - [AutodetectorTests.book_with_no_author], [author, book_with_author] - ) - self.assertEqual(len(changes["otherapp"]), 1) - migration = changes["otherapp"][0] - self.assertEqual(len(migration.operations), 3) - self.assertOperationTypes( - changes, - "otherapp", - 0, - ["CreateModel", "AddField", "AlterIndexTogether"], - ) - - def test_remove_field_and_index_together(self): - """ - Removed fields will be removed after updating index_together. - """ - changes = self.get_changes( - [AutodetectorTests.author_empty, self.book_index_together_3], - [AutodetectorTests.author_empty, self.book_index_together], - ) - self.assertNumberMigrations(changes, "otherapp", 1) - self.assertOperationTypes( - changes, - "otherapp", - 0, - ["AlterIndexTogether", "RemoveField"], - ) - self.assertOperationAttributes( - changes, - "otherapp", - 0, - 0, - name="book", - index_together={("author", "title")}, - ) - self.assertOperationAttributes( - changes, - "otherapp", - 0, - 1, - model_name="book", - name="newfield", - ) - - if compat.dj_ge41: - - def test_alter_field_and_index_together(self): - """Fields are altered after deleting some index_together.""" - initial_author = ModelState( - "testapp", - "Author", - [ - ("id", models.BigAutoField(primary_key=True)), - ("name", models.CharField(max_length=200)), - ("age", models.IntegerField(db_index=True)), - ], - { - "index_together": {("name",)}, - }, - ) - author_reversed_constraints = ModelState( - "testapp", - "Author", - [ - ("id", models.BigAutoField(primary_key=True)), - ("name", models.CharField(max_length=200, unique=True)), - ("age", models.IntegerField()), - ], - { - "index_together": {("age",)}, - }, - ) - changes = self.get_changes([initial_author], [author_reversed_constraints]) - - self.assertNumberMigrations(changes, "testapp", 1) - self.assertOperationTypes( - changes, - "testapp", - 0, - [ - "AlterIndexTogether", - "AlterField", - "AlterField", - "AlterIndexTogether", - ], - ) - self.assertOperationAttributes( - changes, - "testapp", - 0, - 0, - name="author", - index_together=set(), - ) - self.assertOperationAttributes( - changes, - "testapp", - 0, - 1, - model_name="author", - name="age", - ) - self.assertOperationAttributes( - changes, - "testapp", - 0, - 2, - model_name="author", - name="name", - ) - self.assertOperationAttributes( - changes, - "testapp", - 0, - 3, - name="author", - index_together={("age",)}, - ) - - def test_partly_alter_index_together_increase(self): - initial_author = ModelState( - "testapp", - "Author", - [ - ("id", models.BigAutoField(primary_key=True)), - ("name", models.CharField(max_length=200)), - ("age", models.IntegerField()), - ], - { - "index_together": {("name",)}, - }, - ) - author_new_constraints = ModelState( - "testapp", - "Author", - [ - ("id", models.BigAutoField(primary_key=True)), - ("name", models.CharField(max_length=200)), - ("age", models.IntegerField()), - ], - { - "index_together": {("name",), ("age",)}, - }, - ) - changes = self.get_changes([initial_author], [author_new_constraints]) - - self.assertNumberMigrations(changes, "testapp", 1) - self.assertOperationTypes( - changes, - "testapp", - 0, - ["AlterIndexTogether"], - ) - self.assertOperationAttributes( - changes, - "testapp", - 0, - 0, - name="author", - index_together={("name",), ("age",)}, - ) - - def test_partly_alter_index_together_decrease(self): - initial_author = ModelState( - "testapp", - "Author", - [ - ("id", models.BigAutoField(primary_key=True)), - ("name", models.CharField(max_length=200)), - ("age", models.IntegerField()), - ], - { - "index_together": {("name",), ("age",)}, - }, - ) - author_new_constraints = ModelState( - "testapp", - "Author", - [ - ("id", models.BigAutoField(primary_key=True)), - ("name", models.CharField(max_length=200)), - ("age", models.IntegerField()), - ], - { - "index_together": {("age",)}, - }, - ) - changes = self.get_changes([initial_author], [author_new_constraints]) - - self.assertNumberMigrations(changes, "testapp", 1) - self.assertOperationTypes( - changes, - "testapp", - 0, - ["AlterIndexTogether"], - ) - self.assertOperationAttributes( - changes, - "testapp", - 0, - 0, - name="author", - index_together={("age",)}, - ) - - def test_rename_field_and_index_together(self): - """Fields are renamed before updating index_together.""" - changes = self.get_changes( - [AutodetectorTests.author_empty, self.book_index_together_3], - [AutodetectorTests.author_empty, self.book_index_together_4], - MigrationQuestioner({"ask_rename": True}), - ) - self.assertNumberMigrations(changes, "otherapp", 1) - self.assertOperationTypes( - changes, - "otherapp", - 0, - ["RenameField", "AlterIndexTogether"], - ) - self.assertOperationAttributes( - changes, - "otherapp", - 0, - 1, - name="book", - index_together={("title", "newfield2")}, - ) - def test_add_model_order_with_respect_to_index_together(self): changes = self.get_changes( [], @@ -4703,29 +4285,3 @@ def test_add_model_order_with_respect_to_index_together(self): "index_together": {("name", "_order")}, }, ) - - def test_set_alter_order_with_respect_to_index_together(self): - after = ModelState( - "testapp", - "Author", - [ - ("id", models.BigAutoField(primary_key=True)), - ("name", models.CharField(max_length=200)), - ("book", models.ForeignKey("otherapp.Book", models.CASCADE)), - ], - options={ - "order_with_respect_to": "book", - "index_together": {("name", "_order")}, - }, - ) - changes = self.get_changes( - [AutodetectorTests.book, AutodetectorTests.author_with_book], - [AutodetectorTests.book, after], - ) - self.assertNumberMigrations(changes, "testapp", 1) - self.assertOperationTypes( - changes, - "testapp", - 0, - ["AlterOrderWithRespectTo", "AlterIndexTogether"], - ) diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py index 893cf42..4a2b8ad 100644 --- a/tests/migrations/test_operations.py +++ b/tests/migrations/test_operations.py @@ -2132,7 +2132,6 @@ def test_rename_index_state_forwards_unnamed_index(self): operation.state_forwards(app_label, new_state) new_model = new_state.apps.get_model(app_label, "Pony") self.assertIsNot(old_model, new_model) - self.assertEqual(new_model._meta.index_together, tuple()) self.assertEqual(new_model._meta.indexes[0].name, "new_pony_pink_idx") @skipUnlessDBFeature("supports_expression_indexes") diff --git a/tests/migrations/test_state.py b/tests/migrations/test_state.py index b028d82..d5ec34d 100644 --- a/tests/migrations/test_state.py +++ b/tests/migrations/test_state.py @@ -22,7 +22,6 @@ FoodQuerySet, ModelWithCustomBase, NoMigrationFoodManager, - UnicodeModel, ) @@ -47,7 +46,6 @@ class Meta: app_label = "migrations" apps = new_apps unique_together = ["name", "bio"] - index_together = ["bio", "age"] class AuthorProxy(Author): class Meta: @@ -139,7 +137,6 @@ class Meta: author_state.options, { "unique_together": {("name", "bio")}, - "index_together": {("bio", "age")}, "indexes": [], "constraints": [], }, @@ -1657,25 +1654,6 @@ def test_bound_field_sanity_check(self): ): ModelState("app", "Model", [("field", field)]) - def test_sanity_check_to(self): - field = models.ForeignKey(UnicodeModel, models.CASCADE) - with self.assertRaisesMessage( - ValueError, - 'ModelState.fields cannot refer to a model class - "field.to" does. ' - "Use a string reference instead.", - ): - ModelState("app", "Model", [("field", field)]) - - def test_sanity_check_through(self): - field = models.ManyToManyField("UnicodeModel") - field.remote_field.through = UnicodeModel - with self.assertRaisesMessage( - ValueError, - 'ModelState.fields cannot refer to a model class - "field.through" does. ' - "Use a string reference instead.", - ): - ModelState("app", "Model", [("field", field)]) - def test_sanity_index_name(self): field = models.IntegerField() options = {"indexes": [models.Index(fields=["field"])]} diff --git a/tests/runtests.py b/tests/runtests.py index b07e9ae..efcac14 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -6,7 +6,7 @@ from django.conf import settings from django.test.utils import get_runner -from clickhouse_backend.compat import dj_ge4 +from clickhouse_backend import compat RUNTESTS_DIR = os.path.abspath(os.path.dirname(__file__)) SKIP_DIRS = ["unsupported"] @@ -29,7 +29,19 @@ def get_test_modules(): yield test_module +# assertQuerysetEqual is removed from django 5.1 +def patch_assertQuerysetEqual(): + if compat.dj_ge5: + from django.test import TransactionTestCase + + def assertQuerysetEqual(self, *args, **kw): + return self.assertQuerySetEqual(*args, **kw) + + TransactionTestCase.assertQuerysetEqual = assertQuerysetEqual + + if __name__ == "__main__": + patch_assertQuerysetEqual() parser = argparse.ArgumentParser(description="Run the Django test suite.") parser.add_argument( "modules", @@ -67,7 +79,7 @@ def get_test_modules(): action="store_true", help="Turn on the SQL query logger within tests.", ) - if not dj_ge4: + if not compat.dj_ge4: from django.test.runner import default_test_processes parser.add_argument( @@ -103,7 +115,7 @@ def get_test_modules(): django.setup() parallel = options.parallel - if dj_ge4 and parallel in {0, "auto"}: + if compat.dj_ge4 and parallel in {0, "auto"}: # This doesn't work before django.setup() on some databases. from django.db import connections from django.test.runner import get_max_test_processes diff --git a/tox.ini b/tox.ini index 85699a2..f94251f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] requires = tox>=4 -env_list = py3.7-django3.2, py{3.8,3.9,3.10,3.11,3.12}-django{3.2,4.0,4.1,4.2}, py{3.10,3.11,3.12}-django5.0 +env_list = py3.7-django3.2, py{3.8,3.9,3.10,3.11,3.12}-django{3.2,4.0,4.1,4.2}, py{3.10,3.11,3.12}-django{5.0,5.1} [variables] code = clickhouse_backend example tests @@ -11,8 +11,9 @@ deps = 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,<4.3 + django4.2: Django>=4.2,<5.0 django5.0: Django>=5.0,<5.1 + django5.1: Django>=5.1,<5.2 coverage commands = # Use local clickhouse_backend package so that coverage works properly.