Skip to content

Commit

Permalink
feat: add Django 4.0.0 compatibility (#2009)
Browse files Browse the repository at this point in the history
  • Loading branch information
tswfi authored Dec 30, 2021
1 parent a2fa5c8 commit babfd09
Show file tree
Hide file tree
Showing 19 changed files with 103 additions and 63 deletions.
12 changes: 6 additions & 6 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ jobs:
- tox-env: "py310-dj32"
python-version: "3.10"
# Django 4.0
# - tox-env: "py38-dj40"
# python-version: "3.8"
# - tox-env: "py39-dj40"
# python-version: "3.9"
# - tox-env: "py310-dj40"
# python-version: "3.10"
- tox-env: "py38-dj40"
python-version: "3.8"
- tox-env: "py39-dj40"
python-version: "3.9"
- tox-env: "py310-dj40"
python-version: "3.10"

steps:
- uses: actions/checkout@v2
Expand Down
4 changes: 2 additions & 2 deletions docs/overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ standard Django environment), with the following dependencies, which
unless noted as optional, should be installed automatically following
the above instructions:

* `Python`_ 3.6 to 3.9
* `Django`_ 2.2 to 3.2
* `Python`_ 3.6 to 3.10
* `Django`_ 2.2 to 4.0
* `django-contrib-comments`_ - for built-in threaded comments
* `Pillow`_ - for image resizing (`Python Imaging Library`_ fork)
* `grappelli-safe`_ - admin skin (`Grappelli`_ fork)
Expand Down
26 changes: 15 additions & 11 deletions mezzanine/accounts/urls.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from django.conf.urls import url
from django.urls import re_path

from mezzanine.accounts import views
from mezzanine.conf import settings
Expand Down Expand Up @@ -28,32 +28,36 @@
_slash = "/" if settings.APPEND_SLASH else ""

urlpatterns = [
url(r"^{}{}$".format(LOGIN_URL.strip("/"), _slash), views.login, name="login"),
url(r"^{}{}$".format(LOGOUT_URL.strip("/"), _slash), views.logout, name="logout"),
url(r"^{}{}$".format(SIGNUP_URL.strip("/"), _slash), views.signup, name="signup"),
url(
re_path(r"^{}{}$".format(LOGIN_URL.strip("/"), _slash), views.login, name="login"),
re_path(
r"^{}{}$".format(LOGOUT_URL.strip("/"), _slash), views.logout, name="logout"
),
re_path(
r"^{}{}$".format(SIGNUP_URL.strip("/"), _slash), views.signup, name="signup"
),
re_path(
r"^{}{}{}$".format(SIGNUP_VERIFY_URL.strip("/"), _verify_pattern, _slash),
views.signup_verify,
name="signup_verify",
),
url(
re_path(
r"^{}{}$".format(PROFILE_UPDATE_URL.strip("/"), _slash),
views.profile_update,
name="profile_update",
),
url(
re_path(
r"^{}{}$".format(PASSWORD_RESET_URL.strip("/"), _slash),
views.password_reset,
name="mezzanine_password_reset",
),
url(
re_path(
r"^{}{}{}$".format(
PASSWORD_RESET_VERIFY_URL.strip("/"), _verify_pattern, _slash
),
views.password_reset_verify,
name="password_reset_verify",
),
url(
re_path(
r"^{}{}$".format(ACCOUNT_URL.strip("/"), _slash),
views.account_redirect,
name="account_redirect",
Expand All @@ -62,12 +66,12 @@

if settings.ACCOUNTS_PROFILE_VIEWS_ENABLED:
urlpatterns += [
url(
re_path(
r"^{}{}$".format(PROFILE_URL.strip("/"), _slash),
views.profile_redirect,
name="profile_redirect",
),
url(
re_path(
r"^{}/(?P<username>.*){}$".format(PROFILE_URL.strip("/"), _slash),
views.profile,
name="profile",
Expand Down
19 changes: 10 additions & 9 deletions mezzanine/boot/lazy_admin.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from django.conf import settings
from django.conf.urls import include, url
from django.conf.urls import include
from django.contrib.admin.sites import AdminSite, AlreadyRegistered, NotRegistered
from django.contrib.admin.sites import site as default_site
from django.contrib.auth import get_user_model
from django.shortcuts import redirect
from django.urls import re_path

from mezzanine.utils.importing import import_dotted_path

Expand Down Expand Up @@ -71,12 +72,12 @@ def urls(self):
# doesn't provide), so that we can target it in the
# ADMIN_MENU_ORDER setting, allowing each view to correctly
# highlight its left-hand admin nav item.
url(
re_path(
r"^media-library/$",
lambda r: redirect("fb_browse"),
name="media-library",
),
url(r"^media-library/", include(fb_urls)),
re_path(r"^media-library/", include(fb_urls)),
]

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

urls += [
url(
re_path(
r"^admin_keywords_submit/$",
admin_keywords_submit,
name="admin_keywords_submit",
),
url(r"^asset_proxy/$", static_proxy, name="static_proxy"),
url(
re_path(r"^asset_proxy/$", static_proxy, name="static_proxy"),
re_path(
r"^displayable_links.js$",
displayable_links_js,
name="displayable_links_js",
Expand All @@ -118,11 +119,11 @@ def urls(self):
from mezzanine.pages.views import admin_page_ordering

urls += [
url(
re_path(
r"^admin_page_ordering/$",
admin_page_ordering,
name="admin_page_ordering",
)
]

return urls + [url(r"", super().urls)]
return urls + [re_path(r"", super().urls)]
4 changes: 2 additions & 2 deletions mezzanine/conf/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django.contrib import admin
from django.contrib.messages import info
from django.http import HttpResponseRedirect
from django.utils.encoding import force_text
from django.utils.encoding import force_str
from django.utils.translation import gettext_lazy as _

from mezzanine.conf import settings
Expand Down Expand Up @@ -53,7 +53,7 @@ def changelist_view(self, request, extra_context=None):
extra_context["settings_form"] = settings_form
extra_context["title"] = "{} {}".format(
_("Change"),
force_text(Setting._meta.verbose_name_plural),
force_str(Setting._meta.verbose_name_plural),
)
return super().changelist_view(request, extra_context)

Expand Down
6 changes: 3 additions & 3 deletions mezzanine/core/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ def __init__(self, *args, **kwargs):

@property
def base_concrete_modeladmin(self):
""" The class inheriting directly from ContentModelAdmin. """
"""The class inheriting directly from ContentModelAdmin."""
candidates = [self.__class__]
while candidates:
candidate = candidates.pop()
Expand Down Expand Up @@ -368,7 +368,7 @@ def change_view(self, request, object_id, **kwargs):
return super().change_view(request, object_id, **kwargs)

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

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

def get_content_models(self):
""" Return all subclasses that are admin registered. """
"""Return all subclasses that are admin registered."""
models = []

for model in self.concrete_model.get_content_models():
Expand Down
8 changes: 6 additions & 2 deletions mezzanine/core/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@
nevercache_token,
)
from mezzanine.utils.conf import middlewares_or_subclasses_installed
from mezzanine.utils.deprecation import MiddlewareMixin, is_authenticated
from mezzanine.utils.deprecation import (
MiddlewareMixin,
get_middleware_request,
is_authenticated,
)
from mezzanine.utils.sites import current_site_id
from mezzanine.utils.urls import next_url

Expand Down Expand Up @@ -207,7 +211,7 @@ def process_response(self, request, response):
# the cookie will be correctly set for the the response
if csrf_middleware_installed():
response.csrf_processing_done = False
csrf_mw = CsrfViewMiddleware()
csrf_mw = CsrfViewMiddleware(get_middleware_request)
csrf_mw.process_response(request, response)
return response

Expand Down
2 changes: 1 addition & 1 deletion mezzanine/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ def get_content_model_name(cls):

@classmethod
def get_content_models(cls):
""" Return all subclasses of the concrete model. """
"""Return all subclasses of the concrete model."""
concrete_model = base_concrete_model(ContentTyped, cls)
return [
m
Expand Down
10 changes: 5 additions & 5 deletions mezzanine/forms/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
from mimetypes import guess_type
from os.path import join

from django.conf.urls import url
from django.contrib import admin
from django.contrib.messages import info
from django.core.files.storage import FileSystemStorage
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import re_path
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ungettext
from django.utils.translation import ngettext

from mezzanine.conf import settings
from mezzanine.core.admin import TabularDynamicInlineAdmin
Expand Down Expand Up @@ -112,12 +112,12 @@ def get_urls(self):
"""
urls = super().get_urls()
extra_urls = [
url(
re_path(
r"^(?P<form_id>\d+)/entries/$",
self.admin_site.admin_view(self.entries_view),
name="form_entries",
),
url(
re_path(
r"^file/(?P<field_entry_id>\d+)/$",
self.admin_site.admin_view(self.file_view),
name="form_file",
Expand Down Expand Up @@ -170,7 +170,7 @@ def entries_view(self, request, form_id):
count = entries.count()
if count > 0:
entries.delete()
message = ungettext(
message = ngettext(
"1 entry deleted", "%(count)s entries deleted", count
)
info(request, message % {"count": count})
Expand Down
4 changes: 2 additions & 2 deletions mezzanine/forms/signals.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from django.dispatch import Signal

form_invalid = Signal(providing_args=["form"])
form_valid = Signal(providing_args=["form", "entry"])
form_invalid = Signal()
form_valid = Signal()
4 changes: 2 additions & 2 deletions mezzanine/galleries/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
from django.db import models
from django.utils.encoding import force_text
from django.utils.encoding import force_str
from django.utils.translation import gettext_lazy as _

from mezzanine.conf import settings
Expand Down Expand Up @@ -151,7 +151,7 @@ def save(self, *args, **kwargs):
file name.
"""
if not self.id and not self.description:
name = force_text(self.file)
name = force_str(self.file)
name = name.rsplit("/", 1)[-1].rsplit(".", 1)[0]
name = name.replace("'", "")
name = "".join(c if c not in punctuation else " " for c in name)
Expand Down
8 changes: 4 additions & 4 deletions mezzanine/generic/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from mezzanine.generic.forms import RatingForm, ThreadedCommentForm
from mezzanine.generic.models import Keyword
from mezzanine.utils.cache import add_cache_bypass
from mezzanine.utils.deprecation import is_authenticated
from mezzanine.utils.deprecation import is_authenticated, request_is_ajax
from mezzanine.utils.importing import import_dotted_path
from mezzanine.utils.views import is_spam, set_cookie

Expand Down Expand Up @@ -87,7 +87,7 @@ def initial_validation(request, prefix):
except (TypeError, ObjectDoesNotExist, LookupError):
redirect_url = "/"
if redirect_url:
if request.is_ajax():
if request_is_ajax(request):
return HttpResponse(dumps({"location": redirect_url}))
else:
return redirect(redirect_url)
Expand Down Expand Up @@ -117,7 +117,7 @@ def comment(request, template="generic/comments.html", extra_context=None):
cookie_value = post_data.get(field, "")
set_cookie(response, cookie_name, cookie_value)
return response
elif request.is_ajax() and form.errors:
elif request_is_ajax(request) and form.errors:
return HttpResponse(dumps({"errors": form.errors}))
# Show errors with stand-alone comment form.
context = {"obj": obj, "posted_comment_form": form}
Expand All @@ -139,7 +139,7 @@ def rating(request):
rating_form = RatingForm(request, obj, post_data)
if rating_form.is_valid():
rating_form.save()
if request.is_ajax():
if request_is_ajax(request):
# Reload the object and return the rating fields as json.
obj = obj.__class__.objects.get(id=obj.id)
rating_name = obj.get_ratingfield_name()
Expand Down
18 changes: 18 additions & 0 deletions mezzanine/utils/deprecation.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,24 @@ class MiddlewareMixin:
pass


def request_is_ajax(request):
"""
request.is_ajax() is deprecated. Check the content_type
Returns true if request CONTENT_TYPE is "application/json"
"""
return request.META.get("CONTENT_TYPE") == "application/json"


def get_middleware_request(request):
"""
Middlewares require get_request in after django4.0
Returns the passed request object
"""
return request


def get_middleware_setting_name():
"""
Returns the name of the middleware setting.
Expand Down
Loading

0 comments on commit babfd09

Please sign in to comment.