From b5b83d6544cee7f92ba5a141a5e77ff73047f6fa Mon Sep 17 00:00:00 2001 From: Josh Peng Yu Date: Wed, 6 Mar 2024 15:45:24 +0800 Subject: [PATCH] feat: django 4.2, CMS 4.1 and Python 3.10 compatibility (#50) * feat: django 4.2, CMS 4.1 and Python 3.10 compatibility * fix: fix circleci config * fix: fix tox.ini * fix: fix flake8 and isort errors * fix: fix isort errors --- .circleci/config.yml | 99 +++++++++---------- CHANGELOG.rst | 4 + djangocms_references/__init__.py | 2 - djangocms_references/compat.py | 6 +- djangocms_references/monkeypatch/admin.py | 16 +-- .../test_utils/app_1/__init__.py | 1 - .../nested_references_app/__init__.py | 1 - djangocms_references/urls.py | 6 +- setup.py | 2 +- tests/__init__.py | 7 ++ tests/requirements/dj22_cms40.txt | 3 - tests/requirements/dj32_cms40.txt | 5 + tests/requirements/dj42_cms41.txt | 7 ++ tests/requirements/requirements_base.txt | 5 - tests/test_admin.py | 8 ++ tests/test_cms_toolbars.py | 5 +- tests/test_helpers.py | 6 -- tests/test_views.py | 11 +-- tests/urls.py | 2 +- tox.ini | 10 +- 20 files changed, 107 insertions(+), 99 deletions(-) delete mode 100644 tests/requirements/dj22_cms40.txt create mode 100644 tests/requirements/dj42_cms41.txt diff --git a/.circleci/config.yml b/.circleci/config.yml index 85a204f..5331240 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,17 +1,5 @@ version: 2.0 -py37default: &py37default - docker: - - image: circleci/python:3.7 - steps: - - setup_remote_docker: - docker_layer_caching: false - - checkout - - attach_workspace: - at: /tmp/images - - run: docker load -i /tmp/images/py37.tar || true - - run: docker run py37 tox -e $CIRCLE_JOB - py38default: &py38default docker: - image: circleci/python:3.8 @@ -36,10 +24,17 @@ py39default: &py39default - run: docker load -i /tmp/images/py39.tar || true - run: docker run py39 tox -e $CIRCLE_JOB - -py37_requires: &py37_requires - requires: - - py37_base +py310default: &py310default + docker: + - image: circleci/python:3.10 + steps: + - setup_remote_docker: + docker_layer_caching: false + - checkout + - attach_workspace: + at: /tmp/images + - run: docker load -i /tmp/images/py310.tar || true + - run: docker run py310 tox -e $CIRCLE_JOB py38_requires: &py38_requires requires: @@ -49,20 +44,11 @@ py39_requires: &py39_requires requires: - py39_base +py310_requires: &py310_requires + requires: + - py310_base + jobs: - py37_base: - docker: - - image: circleci/python:3.7 - steps: - - checkout - - setup_remote_docker: - docker_layer_caching: false - - run: docker build -f .circleci/Dockerfile --build-arg PYTHON_VERSION=3.7 -t py37 . - - run: mkdir images - - run: docker save -o images/py37.tar py37 - - persist_to_workspace: - root: images - paths: py37.tar py38_base: docker: - image: circleci/python:3.8 @@ -89,26 +75,36 @@ jobs: - persist_to_workspace: root: images paths: py39.tar - + py310_base: + docker: + - image: circleci/python:3.10 + steps: + - checkout + - setup_remote_docker: + docker_layer_caching: false + - run: docker build -f .circleci/Dockerfile --build-arg PYTHON_VERSION=3.10 -t py310 . + - run: mkdir images + - run: docker save -o images/py310.tar py310 + - persist_to_workspace: + root: images + paths: py310.tar flake8: - <<: *py39default + <<: *py310default isort: - <<: *py39default + <<: *py310default - py37-dj22-sqlite-cms40: - <<: *py37default - py38-dj22-sqlite-cms40: + py38-dj32-sqlite-cms40: <<: *py38default - py39-dj22-sqlite-cms40: + py39-dj32-sqlite-cms40: <<: *py39default - py37-dj32-sqlite-cms40: - <<: *py37default - py38-dj32-sqlite-cms40: + py38-dj42-sqlite-cms41: <<: *py38default - py39-dj32-sqlite-cms40: + py39-dj42-sqlite-cms41: <<: *py39default + py310-dj42-sqlite-cms41: + <<: *py310default ####################### @@ -116,32 +112,29 @@ workflows: version: 2 build: jobs: - - py37_base - py38_base - py39_base + - py310_base - flake8: requires: - - py39_base + - py310_base - isort: requires: - - py39_base + - py310_base - - py37-dj22-sqlite-cms40: - requires: - - py37_base - - py38-dj22-sqlite-cms40: + - py38-dj32-sqlite-cms40: requires: - py38_base - - py39-dj22-sqlite-cms40: + - py39-dj32-sqlite-cms40: requires: - py39_base - - py37-dj32-sqlite-cms40: - requires: - - py37_base - - py38-dj32-sqlite-cms40: + - py38-dj42-sqlite-cms41: requires: - py38_base - - py39-dj32-sqlite-cms40: + - py39-dj42-sqlite-cms41: requires: - py39_base + - py310-dj42-sqlite-cms41: + requires: + - py310_base diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4425990..8150335 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Changelog Unreleased ========== +* Python 3.10 support added +* Python 3.7 support removed +* Django 4.2 support added +* Django CMS 4.1 support added 1.4.3 (2024-02-07) ========== diff --git a/djangocms_references/__init__.py b/djangocms_references/__init__.py index 6ddc87c..aa56ed4 100644 --- a/djangocms_references/__init__.py +++ b/djangocms_references/__init__.py @@ -1,3 +1 @@ __version__ = "1.4.3" - -default_app_config = "djangocms_references.apps.ReferencesConfig" diff --git a/djangocms_references/compat.py b/djangocms_references/compat.py index 7122ec3..31b2afd 100644 --- a/djangocms_references/compat.py +++ b/djangocms_references/compat.py @@ -1,9 +1,9 @@ -from distutils.version import LooseVersion +from cms import __version__ as cms_version -import django +from packaging.version import Version -DJANGO_GTE_21 = LooseVersion(django.get_version()) >= LooseVersion("2.1") +DJANGO_CMS_4_1 = Version(cms_version) >= Version('4.1') def is_versioning_installed(): diff --git a/djangocms_references/monkeypatch/admin.py b/djangocms_references/monkeypatch/admin.py index 0dfb1d0..4e7d6c2 100644 --- a/djangocms_references/monkeypatch/admin.py +++ b/djangocms_references/monkeypatch/admin.py @@ -4,6 +4,8 @@ from djangocms_alias import admin +from djangocms_references.compat import DJANGO_CMS_4_1 + def _get_references_link(self, obj, request): alias_content_type = ContentType.objects.get( @@ -19,9 +21,6 @@ def _get_references_link(self, obj, request): return render_to_string("djangocms_references/references_icon.html", {"url": url}) -admin.AliasContentAdmin._get_references_link = _get_references_link - - def get_list_actions(func): """ Add references action to alias list display @@ -33,6 +32,11 @@ def inner(self, *args, **kwargs): return inner -admin.AliasContentAdmin.get_list_actions = get_list_actions( - admin.AliasContentAdmin.get_list_actions -) +if not DJANGO_CMS_4_1: + admin.AliasContentAdmin._get_references_link = _get_references_link + admin.AliasContentAdmin.get_list_actions = get_list_actions( + admin.AliasContentAdmin.get_list_actions + ) +else: + # TODO: add reference button for django cms 4.1 + pass diff --git a/djangocms_references/test_utils/app_1/__init__.py b/djangocms_references/test_utils/app_1/__init__.py index 1b49f09..e69de29 100644 --- a/djangocms_references/test_utils/app_1/__init__.py +++ b/djangocms_references/test_utils/app_1/__init__.py @@ -1 +0,0 @@ -default_app_config = "djangocms_references.test_utils.app_1.apps.App1Config" diff --git a/djangocms_references/test_utils/nested_references_app/__init__.py b/djangocms_references/test_utils/nested_references_app/__init__.py index 77fb63d..e69de29 100644 --- a/djangocms_references/test_utils/nested_references_app/__init__.py +++ b/djangocms_references/test_utils/nested_references_app/__init__.py @@ -1 +0,0 @@ -default_app_config = "djangocms_references.test_utils.nested_references_app.apps.NestedReferencesAppConfig" diff --git a/djangocms_references/urls.py b/djangocms_references/urls.py index b55cd3f..eeafc70 100644 --- a/djangocms_references/urls.py +++ b/djangocms_references/urls.py @@ -1,13 +1,13 @@ from django.contrib.admin.views.decorators import staff_member_required -from django.urls import re_path +from django.urls import path from .views import ReferencesView app_name = "djangocms_references" urlpatterns = [ - re_path( - r"^references/(?P\d+)/(?P\d+)/$", + path( + "references///", staff_member_required(ReferencesView.as_view()), name="references-index", ) diff --git a/setup.py b/setup.py index 4c27218..28764e6 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ INSTALL_REQUIREMENTS = [ - "Django>=2.2,<4.0", + "Django>=3.2,<5.0", "django-cms" ] diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..a03ec33 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,7 @@ +import django + + +if django.VERSION < (4, 2): # TODO: remove when dropping support for Django < 4.2 + from django.test.testcases import TransactionTestCase + + TransactionTestCase.assertQuerySetEqual = TransactionTestCase.assertQuerysetEqual diff --git a/tests/requirements/dj22_cms40.txt b/tests/requirements/dj22_cms40.txt deleted file mode 100644 index cc6832d..0000000 --- a/tests/requirements/dj22_cms40.txt +++ /dev/null @@ -1,3 +0,0 @@ --r ./requirements_base.txt - -Django>=2.2,<4.0 diff --git a/tests/requirements/dj32_cms40.txt b/tests/requirements/dj32_cms40.txt index 0a44eca..4033d92 100644 --- a/tests/requirements/dj32_cms40.txt +++ b/tests/requirements/dj32_cms40.txt @@ -1,3 +1,8 @@ -r ./requirements_base.txt Django>=3.2,<4.0 + +# Unreleased django-cms 4.0 compatible packages +https://github.com/django-cms/django-cms/tarball/4.0.0#egg=django-cms +https://github.com/django-cms/djangocms-versioning/tarball/1.2.2#egg=djangocms-versioning +https://github.com/django-cms/djangocms-alias/tarball/1.11.0#egg=djangocms-alias \ No newline at end of file diff --git a/tests/requirements/dj42_cms41.txt b/tests/requirements/dj42_cms41.txt new file mode 100644 index 0000000..f4bfd63 --- /dev/null +++ b/tests/requirements/dj42_cms41.txt @@ -0,0 +1,7 @@ +-r ./requirements_base.txt + +Django>=4.2,<5.0 + +https://github.com/django-cms/django-cms/tarball/develop-4#egg=django-cms +https://github.com/django-cms/djangocms-versioning/tarball/2.0.0#egg=djangocms-versioning +https://github.com/django-cms/djangocms-alias/tarball/2.0.0#egg=djangocms-alias \ No newline at end of file diff --git a/tests/requirements/requirements_base.txt b/tests/requirements/requirements_base.txt index 80d4a7a..95964b6 100644 --- a/tests/requirements/requirements_base.txt +++ b/tests/requirements/requirements_base.txt @@ -4,8 +4,3 @@ factory-boy flake8 isort tox - -# Unreleased django-cms 4.0 compatible packages -https://github.com/django-cms/django-cms/tarball/4.0.0#egg=django-cms -https://github.com/django-cms/djangocms-versioning/tarball/1.2.2#egg=djangocms-versioning -https://github.com/django-cms/djangocms-alias/tarball/1.11.0#egg=djangocms-alias diff --git a/tests/test_admin.py b/tests/test_admin.py index 6fb48c5..5032d27 100644 --- a/tests/test_admin.py +++ b/tests/test_admin.py @@ -1,3 +1,5 @@ +from unittest import skipIf + from django.contrib import admin from django.contrib.contenttypes.models import ContentType from django.urls import reverse_lazy @@ -8,8 +10,14 @@ from djangocms_alias.models import Alias, AliasContent, Category from djangocms_versioning.models import Version +from djangocms_references.compat import DJANGO_CMS_4_1 + class AliasAdminReferencesMonkeyPatchTestCase(CMSTestCase): + @skipIf( + DJANGO_CMS_4_1, + "AliasContentAdmin doesn't derive from `ExtendedVersionAdminMixin`, so no `get_list_display` exist", + ) def test_list_display(self): """ The monkeypatch extends the alias admin, adding the show references link diff --git a/tests/test_cms_toolbars.py b/tests/test_cms_toolbars.py index 1e5883e..ab33896 100644 --- a/tests/test_cms_toolbars.py +++ b/tests/test_cms_toolbars.py @@ -1,9 +1,8 @@ -from django.conf.urls import include from django.contrib import admin from django.contrib.auth.models import Permission from django.test.client import RequestFactory from django.test.utils import override_settings -from django.urls import re_path +from django.urls import include, path, re_path from cms.middleware.toolbar import ToolbarMiddleware from cms.test_utils.testcases import CMSTestCase @@ -18,7 +17,7 @@ urlpatterns = [ - re_path(r"^references/", include("djangocms_references.urls")), + path("references/", include("djangocms_references.urls")), re_path(r"^admin/", admin.site.urls), ] diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 23fefa6..b5ce9e0 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -1,4 +1,3 @@ -from unittest import skipIf from unittest.mock import Mock, patch from django.apps import apps @@ -9,7 +8,6 @@ from cms.api import add_plugin from djangocms_references import helpers -from djangocms_references.compat import DJANGO_GTE_21 from djangocms_references.helpers import ( _get_reference_models, combine_querysets_of_same_models, @@ -135,10 +133,6 @@ def test__get_reference_models_versioned(self): ) -@skipIf( - not DJANGO_GTE_21, - "Reliable Q object comparison is available starting with Django 2.1", -) class GetFiltersTestCase(TestCase): def test_get_filters_empty(self): self.assertEqual(get_filters("foo", []), Q()) diff --git a/tests/test_views.py b/tests/test_views.py index b9cdfeb..2c2e7bf 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -1,13 +1,12 @@ from unittest.mock import patch -from django.conf.urls import include from django.contrib import admin from django.contrib.auth.models import Permission from django.contrib.contenttypes.models import ContentType from django.shortcuts import reverse from django.test import RequestFactory from django.test.utils import override_settings -from django.urls import re_path +from django.urls import include, path, re_path from cms.api import add_plugin from cms.test_utils.testcases import CMSTestCase @@ -31,7 +30,7 @@ urlpatterns = [ - re_path(r"^references/", include(djangocms_references.urls)), + path("references/", include(djangocms_references.urls)), re_path(r"^admin/", admin.site.urls), ] @@ -373,7 +372,7 @@ def test_view_unpublised_filter_applied(self): response = self.client.get(self.admin_endpoint + f"?state={filter_applied}") self.assertEqual(response.status_code, 200) - self.assertQuerysetEqual( + self.assertQuerySetEqual( response.context["querysets"][0], latest_versions, transform=lambda x: x.pk, @@ -406,7 +405,7 @@ def test_view_no_filter_applied(self): response = self.client.get(self.admin_endpoint + "?state=all") self.assertEqual(response.status_code, 200) - self.assertQuerysetEqual( + self.assertQuerySetEqual( response.context["querysets"][0], draft_latest_versions + published_latest_versions + archived_latest_versions + unpublished_latest_versions, @@ -419,7 +418,7 @@ def test_view_no_filter_applied(self): response = self.client.get(self.admin_endpoint) self.assertEqual(response.status_code, 200) - self.assertQuerysetEqual( + self.assertQuerySetEqual( response.context["querysets"][0], draft_latest_versions + published_latest_versions + archived_latest_versions + unpublished_latest_versions, diff --git a/tests/urls.py b/tests/urls.py index 97f32f8..0c670d2 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -1,8 +1,8 @@ from django.conf import settings -from django.conf.urls import include, re_path from django.conf.urls.i18n import i18n_patterns from django.contrib import admin from django.contrib.staticfiles.urls import staticfiles_urlpatterns +from django.urls import include, re_path from django.views.static import serve diff --git a/tox.ini b/tox.ini index 77efdb6..2b903e3 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,7 @@ envlist = flake8 isort - py{37,38,39}-dj{22,32}-sqlite-cms40 + py{38,39,310}-dj{32,42}-sqlite-cms{40,41} skip_missing_interpreters=True @@ -11,13 +11,13 @@ deps = flake8: -r{toxinidir}/tests/requirements/requirements_base.txt isort: -r{toxinidir}/tests/requirements/requirements_base.txt - dj22: -r{toxinidir}/tests/requirements/dj22_cms40.txt dj32: -r{toxinidir}/tests/requirements/dj32_cms40.txt + dj42: -r{toxinidir}/tests/requirements/dj42_cms41.txt basepython = - py37: python3.7 py38: python3.8 py39: python3.9 + py310: python3.10 commands = {envpython} --version @@ -27,8 +27,8 @@ commands = [testenv:flake8] commands = flake8 -basepython = python3.9 +basepython = python3.10 [testenv:isort] commands = isort --recursive --check-only --diff {toxinidir} -basepython = python3.9 +basepython = python3.10