5504b7be9027 — Hugo Rodger-Brown 6 months ago
Add Django 5.0 compatibility (reverse function)

I have added a `compat` module to handle the migration of the `reverse`
function from django.core.urlresolvers to django.urls.

I have updated the tests to handle the change in 4.2 to 5.0 where request
params passed to a SimpleListFilter are passed as list of values, not just
one.

I have updated the build matrix to include Django5.0 and the main branch, as
well as Python 3.12.
M .hgignore +3 -1
@@ 6,10 6,12 @@ settings_local.py
 **.pyc
 MANIFEST
 .idea
+.venv
+*.egg-info
 
 syntax:regexp
 ^htmlcov$
 ^env$
 syntax: glob
 *.komodoproject
-.DS_Store
  No newline at end of file
+.DS_Store

          
M MANIFEST.in +1 -1
@@ 1,4 1,4 @@ 
-include BSD-LICENSE
 include CHANGELOG
+include LICENSE
 include README.rst
 recursive-include impersonate/templates *

          
M impersonate/admin.py +1 -5
@@ 5,15 5,11 @@ from django.contrib import admin
 from django.db.utils import NotSupportedError
 from django.utils.html import format_html
 
+from .compat import reverse
 from .helpers import User, check_allow_impersonate
 from .models import ImpersonationLog
 from .settings import settings
 
-try:
-    from django.urls import reverse
-except ImportError:
-    from django.core.urlresolvers import reverse
-
 logger = logging.getLogger(__name__)
 
 

          
A => impersonate/compat.py +7 -0
@@ 0,0 1,7 @@ 
+# compatibility module for handling Django upgrade namespace changes
+try:
+    from django.urls import reverse
+except ImportError:
+    from django.core.urlresolvers import reverse
+
+__all__ = ["reverse"]

          
M impersonate/middleware.py +2 -1
@@ 2,10 2,11 @@ 
 from datetime import datetime, timedelta, timezone
 
 from django.http import HttpResponseNotAllowed
-from django.shortcuts import redirect, reverse
+from django.shortcuts import redirect
 from django.utils.deprecation import MiddlewareMixin
 from django.utils.functional import SimpleLazyObject
 
+from .compat import reverse
 from .helpers import User, check_allow_for_uri, check_allow_for_user, check_read_only
 from .settings import settings
 

          
M impersonate/tests.py +13 -8
@@ 35,17 35,15 @@ from django.urls import include, path
 from django.utils.duration import duration_string
 
 from .admin import (
-    ImpersonationLogAdmin, ImpersonatorFilter, SessionStateFilter,
+    ImpersonationLogAdmin,
+    ImpersonatorFilter,
+    SessionStateFilter,
 )
+from .compat import reverse
 from .helpers import users_impersonable
 from .models import ImpersonationLog
 from .signals import session_begin, session_end
 
-try:
-    from django.urls import reverse
-except ImportError:
-    from django.core.urlresolvers import reverse
-
 User = get_user_model()
 django_version_loose = LooseVersion(django.get_version())
 

          
@@ 725,9 723,16 @@ class TestImpersonation(TestCase):
         qs = _filter.queryset(None, ImpersonationLog.objects.all())
         self.assertEqual(qs.count(), 2)
 
+        def _format_params(val):
+            # Django 5.x returns request querystring params as a list
+            # https://github.com/django/django/commit/d03dc63177ad3ba6e685e314eed45d6a8ec5cb0c
+            if django_version_loose >= '5.0':
+                return {'session': [val]}
+            return {'session': val}
+
         _filter = SessionStateFilter(
             None,
-            {'session': 'complete'},
+            _format_params('complete'),
             ImpersonationLog,
             ImpersonationLogAdmin,
         )

          
@@ 736,7 741,7 @@ class TestImpersonation(TestCase):
 
         _filter = SessionStateFilter(
             None,
-            {'session': 'incomplete'},
+            _format_params('incomplete'),
             ImpersonationLog,
             ImpersonationLogAdmin,
         )

          
M impersonate/views.py +6 -2
@@ 8,8 8,12 @@ from django.shortcuts import get_object_
 
 from .decorators import allowed_user_required
 from .helpers import (
-    check_allow_for_user, get_paginator, get_redir_arg, get_redir_field,
-    get_redir_path, users_impersonable,
+    check_allow_for_user,
+    get_paginator,
+    get_redir_arg,
+    get_redir_field,
+    get_redir_path,
+    users_impersonable,
 )
 from .settings import User, settings
 from .signals import session_begin, session_end

          
M pyproject.toml +1 -1
@@ 1,7 1,7 @@ 
 [tool.black]
 line-length = 79
 skip-string-normalization = true
-target-version = ['py37', 'py38']
+target-version = ['py37', 'py38', 'py39', 'py310', 'py311', 'py312']
 include = '\.pyi?$'
 exclude = '''
 

          
M setup.py +5 -0
@@ 48,11 48,16 @@ setup(
         'Framework :: Django :: 2.2',
         'Framework :: Django :: 3.2',
         'Framework :: Django :: 4.0',
+        'Framework :: Django :: 4.1',
+        'Framework :: Django :: 4.2',
+        'Framework :: Django :: 5.0',
         'Programming Language :: Python',
         'Programming Language :: Python :: 3.7',
         'Programming Language :: Python :: 3.8',
         'Programming Language :: Python :: 3.9',
         'Programming Language :: Python :: 3.10',
+        'Programming Language :: Python :: 3.11',
+        'Programming Language :: Python :: 3.12',
         'Environment :: Web Environment',
     ],
 )

          
M tox.ini +20 -2
@@ 1,10 1,28 @@ 
 [tox]
 downloadcache = {toxworkdir}/cache/
-envlist = py{37,38,39}-django{2.2,3.2},py{38,39}-django{4.0},py310-django{3.2,4.0}
+; https://docs.djangoproject.com/en/2.2/faq/install/
+; https://docs.djangoproject.com/en/5.0/faq/install/
+envlist =
+	py{37,38,39}-django2.2
+	; Django 3.0 / 3.1 not included in PyPI classifiers
+	; py{37,38,39}-django3.0
+	; py{37,38,39}-django3.1
+	py{37,38,39,310}-django3.2
+	py{38,39,310}-django4.0
+	py{38,39,310}-django4.1
+	py{38,39,310,311}-django4.1
+	py{38,39,310,311}-django4.2
+	py{310,311,312}-django{5.0,main}
 
 [testenv]
 commands = {envpython} runtests.py
 deps =
 	django2.2: django>=2.2,<3.0
-	django3.2: django>=3.2,<4.0
+	django3.0: django>=3.0,<3.1
+	django3.1: django>=3.1,<3.2
+	django3.2: django>=3.2,<3.3
 	django4.0: django>=4.0,<4.1
+	django4.1: django>=4.1,<4.2
+	django4.2: django>=4.2,<4.3
+	django5.0: https://github.com/django/django/archive/stable/5.0.x.tar.gz
+	djangomain: https://github.com/django/django/archive/main.tar.gz