Subject: [PATCH django-impersonate] Handle upcoming removal of timezone.utc (Django 5)

From: Hugo Rodger-Brown <[email-masked]>
Date: Wed, 8 Mar 2023 10:58:20 +0000

# HG changeset patch
# User Hugo Rodger-Brown <[email-masked]>
# Date 1678268809 0
# Wed Mar 08 09:46:49 2023 +0000
# Node ID c0c223b5e10524c9cad20de0b11d901277f5b89d
# Parent 76e93d43501e5304d6b27e7f7072e4d8aab15104
Add compat module to handle removal of timzone.utc

In Django 5 the way in which timezones are handled is updated, and a side-effect
of this is that the `utc` value is removed from `django.utils.timezone`. I have
created a new `compat.py` module to handle both scenarios and expose a value
called `UTC` that can be used wherever a tzinfo is required.

At the same time I have moved the `reverse` import code into the same module so
it it centralised.

I have also updated the build matrix to include Python 3.10, 3.11, and Django
4.1, "main".

diff --git a/.hgignore b/.hgignore
--- a/.hgignore
+++ b/.hgignore
@@ -6,10 +6,12 @@
**.pyc
MANIFEST
.idea
+.venv
+*.egg-info
syntax:regexp
^htmlcov$
^env$
syntax: glob
*.komodoproject
-.DS_Store
\ No newline at end of file
+.DS_Store
diff --git a/MANIFEST.in b/MANIFEST.in
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,4 @@
-include BSD-LICENSE
include CHANGELOG
+include LICENSE
include README.rst
recursive-include impersonate/templates *
diff --git a/impersonate/admin.py b/impersonate/admin.py
--- a/impersonate/admin.py
+++ b/impersonate/admin.py
@@ -5,15 +5,11 @@
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__)
diff --git a/impersonate/compat.py b/impersonate/compat.py
new file mode 100644
--- /dev/null
+++ b/impersonate/compat.py
@@ -0,0 +1,15 @@
+try:
+ from django.urls import reverse
+except ImportError:
+ from django.core.urlresolvers import reverse
+
+# timezone.utc removed in Django 5
+try:
+ from django.utils.timezone import utc
+ UTC = utc
+except ImportError:
+ from zoneinfo import ZoneInfo
+ UTC = ZoneInfo("UTC")
+
+
+__all__ = ["reverse", "UTC"]
diff --git a/impersonate/middleware.py b/impersonate/middleware.py
--- a/impersonate/middleware.py
+++ b/impersonate/middleware.py
@@ -3,10 +3,10 @@
from django.http import HttpResponseNotAllowed
from django.shortcuts import redirect, reverse
-from django.utils import timezone
from django.utils.deprecation import MiddlewareMixin
from django.utils.functional import SimpleLazyObject
+from .compat import UTC
from .helpers import User, check_allow_for_uri, check_allow_for_user,
check_read_only
from .settings import settings
@@ -33,11 +33,11 @@
return
start_time = datetime.fromtimestamp(
- request.session['_impersonate_start'], timezone.utc
+ request.session['_impersonate_start'], UTC
)
delta = timedelta(seconds=settings.MAX_DURATION)
- if datetime.now(timezone.utc) - start_time > delta:
+ if datetime.now(UTC) - start_time > delta:
return redirect('impersonate-stop')
new_user_id = request.session['_impersonate']
diff --git a/impersonate/tests.py b/impersonate/tests.py
--- a/impersonate/tests.py
+++ b/impersonate/tests.py
@@ -40,11 +40,7 @@
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
+from .compat import reverse, UTC
User = get_user_model()
django_version_loose = LooseVersion(django.get_version())
@@ -154,7 +150,7 @@
@override_settings(IMPERSONATE={'MAX_DURATION': 3600})
def test_impersonated_request_with_max_duration(self):
self._impersonated_request(
- _impersonate_start=datetime.now(timezone.utc).timestamp()
+ _impersonate_start=datetime.now(UTC).timestamp()
)
@override_settings(IMPERSONATE={'MAX_DURATION': 5, 'REDIRECT_URL': '/foo/'})
@@ -163,12 +159,12 @@
See Issue #67
'''
self._impersonated_request(
- _impersonate_start=datetime.now(timezone.utc).timestamp()
+ _impersonate_start=datetime.now(UTC).timestamp()
)
# new request to see if the redirect to stop
request = self.factory.get('/')
request.user = self.superuser
- past_time = datetime.now(timezone.utc) - timedelta(hours=1)
+ past_time = datetime.now(UTC) - timedelta(hours=1)
request.session = {
'_impersonate': self.user,
'_impersonate_start': past_time.timestamp(),
@@ -207,7 +203,7 @@
request.session = {
'_impersonate': self.user.pk,
'_impersonate_start': (
- datetime.now(timezone.utc) - timedelta(seconds=3601)
+ datetime.now(UTC) - timedelta(seconds=3601)
).timestamp(),
}
response = self.middleware.process_request(request)
diff --git a/impersonate/views.py b/impersonate/views.py
--- a/impersonate/views.py
+++ b/impersonate/views.py
@@ -5,7 +5,6 @@
from django.db.models import Q
from django.http import Http404
from django.shortcuts import get_object_or_404, redirect, render
-from django.utils import timezone
from .decorators import allowed_user_required
from .helpers import (
@@ -14,6 +13,7 @@
)
from .settings import User, settings
from .signals import session_begin, session_end
+from .compat import UTC
logger = logging.getLogger(__name__)
@@ -39,7 +39,7 @@
if check_allow_for_user(request, new_user):
request.session['_impersonate'] = new_user.pk
request.session['_impersonate_start'] = datetime.now(
- tz=timezone.utc
+ tz=UTC
).timestamp()
prev_path = request.META.get('HTTP_REFERER')
if prev_path:
diff --git a/pyproject.toml b/pyproject.toml
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,7 @@
[tool.black]
line-length = 79
skip-string-normalization = true
-target-version = ['py37', 'py38']
+target-version = ['py37', 'py38', 'py39', 'py310', 'py311']
include = '\.pyi?$'
exclude = '''
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -48,11 +48,13 @@
'Framework :: Django :: 2.2',
'Framework :: Django :: 3.2',
'Framework :: Django :: 4.0',
+ 'Framework :: Django :: 4.1',
'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',
'Environment :: Web Environment',
],
)
diff --git a/tox.ini b/tox.ini
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
[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}
+envlist = py{37,38,39}-django{2.2,3.2},py{38,39}-django{4.0,4.1},py310-django{3.2,4.0,4.1},py311-django{3.2,4.0,4.1,main}
[testenv]
commands = {envpython} runtests.py
@@ -8,3 +8,5 @@
django2.2: django>=2.2,<3.0
django3.2: django>=3.2,<4.0
django4.0: django>=4.0,<4.1
+ django4.1: django>=4.1,<4.2
+ djangomain: https://github.com/django/django/archive/main.tar.gz

---

From: Peter Sanchez <[email-masked]>
Date: Mon, 24 Apr 2023 17:00:21 -0600

Thank you for this patch but it doesn't apply cleanly. Seems like most
changes fail to apply. Did you make sure you have the latest version
pulled into your clone?

Thanks,

Peter