Skip to content

Commit babfd09

Browse files
authored
feat: add Django 4.0.0 compatibility (#2009)
1 parent a2fa5c8 commit babfd09

File tree

19 files changed

+103
-63
lines changed

19 files changed

+103
-63
lines changed

.github/workflows/main.yml

+6-6
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,12 @@ jobs:
5353
- tox-env: "py310-dj32"
5454
python-version: "3.10"
5555
# Django 4.0
56-
# - tox-env: "py38-dj40"
57-
# python-version: "3.8"
58-
# - tox-env: "py39-dj40"
59-
# python-version: "3.9"
60-
# - tox-env: "py310-dj40"
61-
# python-version: "3.10"
56+
- tox-env: "py38-dj40"
57+
python-version: "3.8"
58+
- tox-env: "py39-dj40"
59+
python-version: "3.9"
60+
- tox-env: "py310-dj40"
61+
python-version: "3.10"
6262

6363
steps:
6464
- uses: actions/checkout@v2

docs/overview.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ standard Django environment), with the following dependencies, which
4545
unless noted as optional, should be installed automatically following
4646
the above instructions:
4747

48-
* `Python`_ 3.6 to 3.9
49-
* `Django`_ 2.2 to 3.2
48+
* `Python`_ 3.6 to 3.10
49+
* `Django`_ 2.2 to 4.0
5050
* `django-contrib-comments`_ - for built-in threaded comments
5151
* `Pillow`_ - for image resizing (`Python Imaging Library`_ fork)
5252
* `grappelli-safe`_ - admin skin (`Grappelli`_ fork)

mezzanine/accounts/urls.py

+15-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from django.conf.urls import url
1+
from django.urls import re_path
22

33
from mezzanine.accounts import views
44
from mezzanine.conf import settings
@@ -28,32 +28,36 @@
2828
_slash = "/" if settings.APPEND_SLASH else ""
2929

3030
urlpatterns = [
31-
url(r"^{}{}$".format(LOGIN_URL.strip("/"), _slash), views.login, name="login"),
32-
url(r"^{}{}$".format(LOGOUT_URL.strip("/"), _slash), views.logout, name="logout"),
33-
url(r"^{}{}$".format(SIGNUP_URL.strip("/"), _slash), views.signup, name="signup"),
34-
url(
31+
re_path(r"^{}{}$".format(LOGIN_URL.strip("/"), _slash), views.login, name="login"),
32+
re_path(
33+
r"^{}{}$".format(LOGOUT_URL.strip("/"), _slash), views.logout, name="logout"
34+
),
35+
re_path(
36+
r"^{}{}$".format(SIGNUP_URL.strip("/"), _slash), views.signup, name="signup"
37+
),
38+
re_path(
3539
r"^{}{}{}$".format(SIGNUP_VERIFY_URL.strip("/"), _verify_pattern, _slash),
3640
views.signup_verify,
3741
name="signup_verify",
3842
),
39-
url(
43+
re_path(
4044
r"^{}{}$".format(PROFILE_UPDATE_URL.strip("/"), _slash),
4145
views.profile_update,
4246
name="profile_update",
4347
),
44-
url(
48+
re_path(
4549
r"^{}{}$".format(PASSWORD_RESET_URL.strip("/"), _slash),
4650
views.password_reset,
4751
name="mezzanine_password_reset",
4852
),
49-
url(
53+
re_path(
5054
r"^{}{}{}$".format(
5155
PASSWORD_RESET_VERIFY_URL.strip("/"), _verify_pattern, _slash
5256
),
5357
views.password_reset_verify,
5458
name="password_reset_verify",
5559
),
56-
url(
60+
re_path(
5761
r"^{}{}$".format(ACCOUNT_URL.strip("/"), _slash),
5862
views.account_redirect,
5963
name="account_redirect",
@@ -62,12 +66,12 @@
6266

6367
if settings.ACCOUNTS_PROFILE_VIEWS_ENABLED:
6468
urlpatterns += [
65-
url(
69+
re_path(
6670
r"^{}{}$".format(PROFILE_URL.strip("/"), _slash),
6771
views.profile_redirect,
6872
name="profile_redirect",
6973
),
70-
url(
74+
re_path(
7175
r"^{}/(?P<username>.*){}$".format(PROFILE_URL.strip("/"), _slash),
7276
views.profile,
7377
name="profile",

mezzanine/boot/lazy_admin.py

+10-9
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from django.conf import settings
2-
from django.conf.urls import include, url
2+
from django.conf.urls import include
33
from django.contrib.admin.sites import AdminSite, AlreadyRegistered, NotRegistered
44
from django.contrib.admin.sites import site as default_site
55
from django.contrib.auth import get_user_model
66
from django.shortcuts import redirect
7+
from django.urls import re_path
78

89
from mezzanine.utils.importing import import_dotted_path
910

@@ -71,12 +72,12 @@ def urls(self):
7172
# doesn't provide), so that we can target it in the
7273
# ADMIN_MENU_ORDER setting, allowing each view to correctly
7374
# highlight its left-hand admin nav item.
74-
url(
75+
re_path(
7576
r"^media-library/$",
7677
lambda r: redirect("fb_browse"),
7778
name="media-library",
7879
),
79-
url(r"^media-library/", include(fb_urls)),
80+
re_path(r"^media-library/", include(fb_urls)),
8081
]
8182

8283
# Give the urlpattern for the user password change view an
@@ -88,7 +89,7 @@ def urls(self):
8889
if user_change_password:
8990
bits = (User._meta.app_label, User._meta.object_name.lower())
9091
urls += [
91-
url(
92+
re_path(
9293
r"^%s/%s/(\d+)/password/$" % bits,
9394
self.admin_view(user_change_password),
9495
name="user_change_password",
@@ -102,13 +103,13 @@ def urls(self):
102103
from mezzanine.generic.views import admin_keywords_submit
103104

104105
urls += [
105-
url(
106+
re_path(
106107
r"^admin_keywords_submit/$",
107108
admin_keywords_submit,
108109
name="admin_keywords_submit",
109110
),
110-
url(r"^asset_proxy/$", static_proxy, name="static_proxy"),
111-
url(
111+
re_path(r"^asset_proxy/$", static_proxy, name="static_proxy"),
112+
re_path(
112113
r"^displayable_links.js$",
113114
displayable_links_js,
114115
name="displayable_links_js",
@@ -118,11 +119,11 @@ def urls(self):
118119
from mezzanine.pages.views import admin_page_ordering
119120

120121
urls += [
121-
url(
122+
re_path(
122123
r"^admin_page_ordering/$",
123124
admin_page_ordering,
124125
name="admin_page_ordering",
125126
)
126127
]
127128

128-
return urls + [url(r"", super().urls)]
129+
return urls + [re_path(r"", super().urls)]

mezzanine/conf/admin.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from django.contrib import admin
44
from django.contrib.messages import info
55
from django.http import HttpResponseRedirect
6-
from django.utils.encoding import force_text
6+
from django.utils.encoding import force_str
77
from django.utils.translation import gettext_lazy as _
88

99
from mezzanine.conf import settings
@@ -53,7 +53,7 @@ def changelist_view(self, request, extra_context=None):
5353
extra_context["settings_form"] = settings_form
5454
extra_context["title"] = "{} {}".format(
5555
_("Change"),
56-
force_text(Setting._meta.verbose_name_plural),
56+
force_str(Setting._meta.verbose_name_plural),
5757
)
5858
return super().changelist_view(request, extra_context)
5959

mezzanine/core/admin.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ def __init__(self, *args, **kwargs):
335335

336336
@property
337337
def base_concrete_modeladmin(self):
338-
""" The class inheriting directly from ContentModelAdmin. """
338+
"""The class inheriting directly from ContentModelAdmin."""
339339
candidates = [self.__class__]
340340
while candidates:
341341
candidate = candidates.pop()
@@ -368,7 +368,7 @@ def change_view(self, request, object_id, **kwargs):
368368
return super().change_view(request, object_id, **kwargs)
369369

370370
def changelist_view(self, request, extra_context=None):
371-
""" Redirect to the changelist view for subclasses. """
371+
"""Redirect to the changelist view for subclasses."""
372372
if self.model is not self.concrete_model:
373373
return HttpResponseRedirect(admin_url(self.concrete_model, "changelist"))
374374

@@ -378,7 +378,7 @@ def changelist_view(self, request, extra_context=None):
378378
return super().changelist_view(request, extra_context)
379379

380380
def get_content_models(self):
381-
""" Return all subclasses that are admin registered. """
381+
"""Return all subclasses that are admin registered."""
382382
models = []
383383

384384
for model in self.concrete_model.get_content_models():

mezzanine/core/middleware.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@
3333
nevercache_token,
3434
)
3535
from mezzanine.utils.conf import middlewares_or_subclasses_installed
36-
from mezzanine.utils.deprecation import MiddlewareMixin, is_authenticated
36+
from mezzanine.utils.deprecation import (
37+
MiddlewareMixin,
38+
get_middleware_request,
39+
is_authenticated,
40+
)
3741
from mezzanine.utils.sites import current_site_id
3842
from mezzanine.utils.urls import next_url
3943

@@ -207,7 +211,7 @@ def process_response(self, request, response):
207211
# the cookie will be correctly set for the the response
208212
if csrf_middleware_installed():
209213
response.csrf_processing_done = False
210-
csrf_mw = CsrfViewMiddleware()
214+
csrf_mw = CsrfViewMiddleware(get_middleware_request)
211215
csrf_mw.process_response(request, response)
212216
return response
213217

mezzanine/core/models.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,7 @@ def get_content_model_name(cls):
578578

579579
@classmethod
580580
def get_content_models(cls):
581-
""" Return all subclasses of the concrete model. """
581+
"""Return all subclasses of the concrete model."""
582582
concrete_model = base_concrete_model(ContentTyped, cls)
583583
return [
584584
m

mezzanine/forms/admin.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
from mimetypes import guess_type
66
from os.path import join
77

8-
from django.conf.urls import url
98
from django.contrib import admin
109
from django.contrib.messages import info
1110
from django.core.files.storage import FileSystemStorage
1211
from django.http import HttpResponse, HttpResponseRedirect
1312
from django.shortcuts import get_object_or_404, render
13+
from django.urls import re_path
1414
from django.utils.translation import gettext_lazy as _
15-
from django.utils.translation import ungettext
15+
from django.utils.translation import ngettext
1616

1717
from mezzanine.conf import settings
1818
from mezzanine.core.admin import TabularDynamicInlineAdmin
@@ -112,12 +112,12 @@ def get_urls(self):
112112
"""
113113
urls = super().get_urls()
114114
extra_urls = [
115-
url(
115+
re_path(
116116
r"^(?P<form_id>\d+)/entries/$",
117117
self.admin_site.admin_view(self.entries_view),
118118
name="form_entries",
119119
),
120-
url(
120+
re_path(
121121
r"^file/(?P<field_entry_id>\d+)/$",
122122
self.admin_site.admin_view(self.file_view),
123123
name="form_file",
@@ -170,7 +170,7 @@ def entries_view(self, request, form_id):
170170
count = entries.count()
171171
if count > 0:
172172
entries.delete()
173-
message = ungettext(
173+
message = ngettext(
174174
"1 entry deleted", "%(count)s entries deleted", count
175175
)
176176
info(request, message % {"count": count})

mezzanine/forms/signals.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
from django.dispatch import Signal
22

3-
form_invalid = Signal(providing_args=["form"])
4-
form_valid = Signal(providing_args=["form", "entry"])
3+
form_invalid = Signal()
4+
form_valid = Signal()

mezzanine/galleries/models.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from django.core.files.base import ContentFile
88
from django.core.files.storage import default_storage
99
from django.db import models
10-
from django.utils.encoding import force_text
10+
from django.utils.encoding import force_str
1111
from django.utils.translation import gettext_lazy as _
1212

1313
from mezzanine.conf import settings
@@ -151,7 +151,7 @@ def save(self, *args, **kwargs):
151151
file name.
152152
"""
153153
if not self.id and not self.description:
154-
name = force_text(self.file)
154+
name = force_str(self.file)
155155
name = name.rsplit("/", 1)[-1].rsplit(".", 1)[0]
156156
name = name.replace("'", "")
157157
name = "".join(c if c not in punctuation else " " for c in name)

mezzanine/generic/views.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from mezzanine.generic.forms import RatingForm, ThreadedCommentForm
1616
from mezzanine.generic.models import Keyword
1717
from mezzanine.utils.cache import add_cache_bypass
18-
from mezzanine.utils.deprecation import is_authenticated
18+
from mezzanine.utils.deprecation import is_authenticated, request_is_ajax
1919
from mezzanine.utils.importing import import_dotted_path
2020
from mezzanine.utils.views import is_spam, set_cookie
2121

@@ -87,7 +87,7 @@ def initial_validation(request, prefix):
8787
except (TypeError, ObjectDoesNotExist, LookupError):
8888
redirect_url = "/"
8989
if redirect_url:
90-
if request.is_ajax():
90+
if request_is_ajax(request):
9191
return HttpResponse(dumps({"location": redirect_url}))
9292
else:
9393
return redirect(redirect_url)
@@ -117,7 +117,7 @@ def comment(request, template="generic/comments.html", extra_context=None):
117117
cookie_value = post_data.get(field, "")
118118
set_cookie(response, cookie_name, cookie_value)
119119
return response
120-
elif request.is_ajax() and form.errors:
120+
elif request_is_ajax(request) and form.errors:
121121
return HttpResponse(dumps({"errors": form.errors}))
122122
# Show errors with stand-alone comment form.
123123
context = {"obj": obj, "posted_comment_form": form}
@@ -139,7 +139,7 @@ def rating(request):
139139
rating_form = RatingForm(request, obj, post_data)
140140
if rating_form.is_valid():
141141
rating_form.save()
142-
if request.is_ajax():
142+
if request_is_ajax(request):
143143
# Reload the object and return the rating fields as json.
144144
obj = obj.__class__.objects.get(id=obj.id)
145145
rating_name = obj.get_ratingfield_name()

mezzanine/utils/deprecation.py

+18
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,24 @@ class MiddlewareMixin:
1616
pass
1717

1818

19+
def request_is_ajax(request):
20+
"""
21+
request.is_ajax() is deprecated. Check the content_type
22+
23+
Returns true if request CONTENT_TYPE is "application/json"
24+
"""
25+
return request.META.get("CONTENT_TYPE") == "application/json"
26+
27+
28+
def get_middleware_request(request):
29+
"""
30+
Middlewares require get_request in after django4.0
31+
32+
Returns the passed request object
33+
"""
34+
return request
35+
36+
1937
def get_middleware_setting_name():
2038
"""
2139
Returns the name of the middleware setting.

0 commit comments

Comments
 (0)