Fix BaseDeleteView subclassing for Django 4.0

As of Django 4.0 BaseDeleteView has switched to a new implementation based on FormMixin where custom deletion logic now lives in form_valid:
https://docs.djangoproject.com/en/4.0/releases/4.0/#deleteview-changes

Here we copy the Django 4.0 implementation for use on older Django versions to keep everything consistent, and provide a delete_action method for views that override wagtail.views.generic.models.DeleteView further (e.g. the workflow deactivation views).
pull/7568/head
Matt Westcott 2022-01-05 19:33:31 +00:00 zatwierdzone przez Matt Westcott
rodzic 44963d87d0
commit daa46b216c
4 zmienionych plików z 57 dodań i 19 usunięć

Wyświetl plik

@ -60,3 +60,7 @@ should be rewritten as:
```python
self.assertEqual(list(page.body[0].value), ['hello', 'goodbye'])
```
### `wagtail.admin.views.generic.DeleteView` follows Django 4.0 conventions
The internal (undocumented) class-based view `wagtail.admin.views.generic.DeleteView` has been updated to align with [Django 4.0's `DeleteView` implementation](https://docs.djangoproject.com/en/4.0/releases/4.0/#deleteview-changes), which uses `FormMixin` to handle POST requests. Any custom deletion logic in `delete()` handlers should be moved to `form_valid()`.

Wyświetl plik

@ -1,10 +1,15 @@
from django import VERSION as DJANGO_VERSION
from django.db import transaction
from django.forms import Form
from django.http import HttpResponseRedirect
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy
from django.views.generic.edit import BaseCreateView, BaseDeleteView, BaseUpdateView
from django.views.generic.detail import BaseDetailView
from django.views.generic.edit import BaseCreateView
from django.views.generic.edit import BaseDeleteView as DjangoBaseDeleteView
from django.views.generic.edit import BaseUpdateView, DeletionMixin, FormMixin
from django.views.generic.list import BaseListView
from wagtail.admin import messages
@ -15,6 +20,39 @@ from .base import WagtailAdminTemplateMixin
from .permissions import PermissionCheckedMixin
if DJANGO_VERSION >= (4, 0):
BaseDeleteView = DjangoBaseDeleteView
else:
# As of Django 4.0 BaseDeleteView has switched to a new implementation based on FormMixin
# where custom deletion logic now lives in form_valid:
# https://docs.djangoproject.com/en/4.0/releases/4.0/#deleteview-changes
# Here we define BaseDeleteView to match the Django 4.0 implementation to keep it consistent
# across all versions.
class BaseDeleteView(DeletionMixin, FormMixin, BaseDetailView):
"""
Base view for deleting an object.
Using this base class requires subclassing to provide a response mixin.
"""
form_class = Form
def post(self, request, *args, **kwargs):
# Set self.object before the usual form processing flow.
# Inlined because having DeletionMixin as the first base, for
# get_success_url(), makes leveraging super() with ProcessFormView
# overly complex.
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
success_url = self.get_success_url()
self.object.delete()
return HttpResponseRedirect(success_url)
class IndexView(PermissionCheckedMixin, WagtailAdminTemplateMixin, BaseListView):
model = None
index_url_name = None
@ -248,11 +286,13 @@ class DeleteView(PermissionCheckedMixin, WagtailAdminTemplateMixin, BaseDeleteVi
return None
return self.success_message.format(self.object)
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
success_url = self.get_success_url()
def delete_action(self):
with transaction.atomic():
log(instance=self.object, action='wagtail.delete')
self.object.delete()
messages.success(request, self.get_success_message())
def form_valid(self, form):
success_url = self.get_success_url()
self.delete_action()
messages.success(self.request, self.get_success_message())
return HttpResponseRedirect(success_url)

Wyświetl plik

@ -231,11 +231,8 @@ class Disable(DeleteView):
}
return context
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
self.object.deactivate(user=request.user)
messages.success(request, self.get_success_message())
return redirect(reverse(self.index_url_name))
def delete_action(self):
self.object.deactivate(user=self.request.user)
def usage(request, pk):
@ -460,11 +457,8 @@ class DisableTask(DeleteView):
def get_edit_url(self):
return reverse(self.edit_url_name, args=(self.kwargs['pk'],))
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
self.object.deactivate(user=request.user)
messages.success(request, self.get_success_message())
return redirect(reverse(self.index_url_name))
def delete_action(self):
self.object.deactivate(user=self.request.user)
@require_POST

Wyświetl plik

@ -85,12 +85,12 @@ class DeleteView(generic.DeleteView):
context['can_delete'] = self.can_delete(object)
return context
def delete(self, request, *args, **kwargs):
def form_valid(self, form):
if self.can_delete(self.get_object()):
return super().delete(request, *args, **kwargs)
return super().form_valid(form)
else:
messages.error(request, self.cannot_delete_message)
return super().get(request)
messages.error(self.request, self.cannot_delete_message)
return super().get(self.request)
class LocaleViewSet(ModelViewSet):