From 5cbe531f79c4282b4c5dae8f3e87cec3aab9274e Mon Sep 17 00:00:00 2001 From: Daniel Kirkham Date: Mon, 8 Jan 2024 18:56:47 +1100 Subject: [PATCH 1/8] Migrate core code from Wagtail ModelAdmin to Snippets --- wagtailmenus/conf/defaults.py | 4 +- wagtailmenus/copyable_snippetviewset.py | 82 +++++++++ wagtailmenus/forms.py | 21 +++ wagtailmenus/modeladmin.py | 220 ++++++++++++++---------- wagtailmenus/models/menus.py | 9 + wagtailmenus/views.py | 187 -------------------- wagtailmenus/wagtail_hooks.py | 10 +- 7 files changed, 246 insertions(+), 287 deletions(-) create mode 100644 wagtailmenus/copyable_snippetviewset.py delete mode 100644 wagtailmenus/views.py diff --git a/wagtailmenus/conf/defaults.py b/wagtailmenus/conf/defaults.py index ef6c4231..bb6fd438 100644 --- a/wagtailmenus/conf/defaults.py +++ b/wagtailmenus/conf/defaults.py @@ -9,7 +9,7 @@ FLAT_MENUS_EDITABLE_IN_WAGTAILADMIN = True -FLAT_MENUS_MODELADMIN_CLASS = 'wagtailmenus.modeladmin.FlatMenuAdmin' +FLAT_MENUS_ADMIN_CLASS = 'wagtailmenus.modeladmin.FlatMenuAdmin' FLAT_MENUS_HANDLE_CHOICES = None @@ -17,7 +17,7 @@ MAIN_MENUS_EDITABLE_IN_WAGTAILADMIN = True -MAIN_MENUS_MODELADMIN_CLASS = 'wagtailmenus.modeladmin.MainMenuAdmin' +MAIN_MENUS_ADMIN_CLASS = 'wagtailmenus.modeladmin.MainMenuAdmin' USE_CONDENSEDINLINEPANEL = True diff --git a/wagtailmenus/copyable_snippetviewset.py b/wagtailmenus/copyable_snippetviewset.py new file mode 100644 index 00000000..b10451b8 --- /dev/null +++ b/wagtailmenus/copyable_snippetviewset.py @@ -0,0 +1,82 @@ +from django.contrib.admin.utils import quote +from django.urls import path, reverse +from django.utils.translation import gettext_lazy as _ + +from wagtail.admin.widgets.button import ListingButton +from wagtail.snippets.views.snippets import CreateView, IndexView, SnippetViewSet + +""" +Extension to SnippetViewSet which enables Snippet copying +Can be removed once Wagtail supports this function natively, +with some small refactoring in FlatMenuAdmin. +""" + +class CopyView(CreateView): + view_name = "copy" + template_name = "wagtailsnippets/snippets/create.html" + error_message = _("The snippet could not be copied due to errors.") + + def run_before_hook(self): + return self.run_hook("before_copy_snippet", self.request, self.model) + + def run_after_hook(self): + return self.run_hook("after_copy_snippet", self.request, self.object) + + def _get_initial_form_instance(self): + return self.get_object() + + +class CopyableSnippetIndexView(IndexView): + copy_url_name = None + + def get_copy_url(self, instance): + return reverse(self.copy_url_name, args=(quote(instance.pk),)) + + def get_list_more_buttons(self, instance): + r = super().get_list_more_buttons(instance) + r.append(ListingButton( + _("Copy"), + url=self.get_copy_url(instance), + icon_name="copy", + attrs={ "aria-label": _("Copy '%(title)s'") % {"title": str(instance)} }, + priority=15, + )) + return r + + +class CopyableSnippetViewSet(SnippetViewSet): + #: The view class to use for the copy view; must be a subclass of ``wagtail.snippet.views.snippets.CopyView``. + copy_view_class = CopyView + + #: The template to use for the edit view. + copy_template_name = "" + + @property + def copy_view(self): + return self.copy_view_class.as_view( + model=self.model, + template_name=self.get_copy_template(), + header_icon=self.icon, + permission_policy=self.permission_policy, + panel=self._edit_handler, + form_class=self.get_form_class(), + index_url_name=self.get_url_name("list"), + add_url_name=self.get_url_name("add"), + edit_url_name=self.get_url_name("edit"), + preview_url_name=self.get_url_name("preview_on_add"), + ) + + def get_common_view_kwargs(self, **kwargs): + view_kwargs = super().get_common_view_kwargs(**kwargs) + view_kwargs["copy_url_name"] = self.get_url_name("copy") + return view_kwargs + + def get_copy_template(self): + return self.copy_template_name or self.get_templates("copy") + + def get_urlpatterns(self): + urls = super().get_urlpatterns() + + return urls + [ + path("copy//", self.copy_view, name="copy") + ] diff --git a/wagtailmenus/forms.py b/wagtailmenus/forms.py index 720a9ffa..48f4a634 100644 --- a/wagtailmenus/forms.py +++ b/wagtailmenus/forms.py @@ -1,6 +1,10 @@ from django import forms +from django.contrib.admin.utils import quote +from django.urls import reverse from django.utils.translation import gettext_lazy as _ + from wagtail.admin.forms import WagtailAdminModelForm, WagtailAdminPageForm +from wagtail.models import Site from wagtailmenus.conf import settings @@ -22,3 +26,20 @@ def __init__(self, *args, **kwargs): "By default, this will be used as the link text when appearing " "in menus." ) + + +class SiteSwitchForm(forms.Form): + site = forms.ChoiceField(choices=[]) + + class Media: + js = [ + 'wagtailmenus/js/site-switcher.js', + ] + + def __init__(self, current_site, edit_url_name, **kwargs): + initial = {'site': reverse(edit_url_name, args=(quote(current_site.pk),))} + super().__init__(initial=initial, **kwargs) + sites = [] + for site in Site.objects.all(): + sites.append((reverse(edit_url_name, args=(quote(site.pk),)), site)) + self.fields['site'].choices = sites diff --git a/wagtailmenus/modeladmin.py b/wagtailmenus/modeladmin.py index c04abb0b..e3c31feb 100644 --- a/wagtailmenus/modeladmin.py +++ b/wagtailmenus/modeladmin.py @@ -1,105 +1,143 @@ -from django.contrib.admin.utils import quote -from django.urls import re_path -from django.utils.safestring import mark_safe +from django.contrib.admin.utils import unquote, quote +from django.shortcuts import get_object_or_404, redirect +from django.urls import reverse +from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ -try: - from wagtail_modeladmin.helpers import ButtonHelper - from wagtail_modeladmin.options import ModelAdmin -except ModuleNotFoundError: - from wagtail.contrib.modeladmin.helpers import ButtonHelper - from wagtail.contrib.modeladmin.options import ModelAdmin - -from wagtailmenus import views -from wagtailmenus.conf import settings +from wagtail.admin.panels import TabbedInterface, ObjectList +from wagtail.models import Site +from wagtail.snippets.views.snippets import CreateView, EditView, IndexView, SnippetViewSet -class MainMenuAdmin(ModelAdmin): +from wagtailmenus import panels +from wagtailmenus.conf import settings +from wagtailmenus.forms import SiteSwitchForm + +from .copyable_snippetviewset import CopyableSnippetIndexView, CopyableSnippetViewSet + + +class MainMenuIndexView(IndexView): + def dispatch(self, request, *args, **kwargs): + site = Site.find_for_request(request) + return redirect(self.get_edit_url(site)) + + +class MainMenuEditView(EditView): + def setup(self, request, *args, **kwargs): + self.site = get_object_or_404(Site, id=unquote(kwargs['pk'])) + + super().setup(request, *args, **kwargs) + + self.site_switcher = None + if Site.objects.count() > 1: + self.site_switcher = SiteSwitchForm(self.site, self.edit_url_name) + + def get_object(self, queryset=None): + self.object = self.model.get_for_site(self.site) + self.object.save() + self.live_object = self.object + return self.object + + def get_edit_url(self): + return reverse(self.edit_url_name, args=(quote(self.site.pk),)) + + @property + def media(self): + if self.site_switcher: + return self.site_switcher.media + return [] + + def dispatch(self, request, *args, **kwargs): + self.site_switcher = None + if Site.objects.count() > 1: + self.site_switcher = SiteSwitchForm(self.site, self.edit_url_name) + site_from_get = request.GET.get('site', None) + if site_from_get and site_from_get != str(self.site.pk): + return redirect(reverse(self.edit_url_name, args=(quote(site_from_get),))) + + return super().dispatch(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'action_url': "?", + 'site': self.site, + 'site_switcher': self.site_switcher, + }) + return context + + def form_valid(self, form): + super().form_valid(form) + return redirect(self.get_edit_url()) + + +class MainMenuAdmin(SnippetViewSet): model = settings.models.MAIN_MENU_MODEL menu_label = _('Main menu') - menu_icon = settings.MAINMENU_MENU_ICON - index_view_class = views.MainMenuIndexView - edit_view_class = views.MainMenuEditView + icon = settings.MAINMENU_MENU_ICON + url_prefix = "wagtailmenus/mainmenu" add_to_settings_menu = True - def get_admin_urls_for_registration(self): - return ( - re_path(self.url_helper.get_action_url_pattern('index'), - self.index_view, - name=self.url_helper.get_action_url_name('index')), - re_path(self.url_helper.get_action_url_pattern('edit'), - self.edit_view, - name=self.url_helper.get_action_url_name('edit')), - ) - - -class FlatMenuButtonHelper(ButtonHelper): - - def copy_button(self, pk, classnames_add=[], classnames_exclude=[]): - cn = self.finalise_classname(classnames_add, classnames_exclude) - return { - 'url': self.url_helper.get_action_url('copy', quote(pk)), - 'label': _('Copy'), - 'classname': cn, - 'title': _('Copy this %(model_name)s') % { - 'model_name': self.verbose_name, - }, - } - - def get_buttons_for_obj(self, obj, exclude=[], classnames_add=[], - classnames_exclude=[]): - ph = self.permission_helper - usr = self.request.user - pk = quote(getattr(obj, self.opts.pk.attname)) - btns = super().get_buttons_for_obj( - obj, exclude, classnames_add, classnames_exclude) - if('copy' not in exclude and ph.user_can_create(usr)): - btns.append( - self.copy_button(pk, classnames_add, classnames_exclude) - ) - return btns - - -class FlatMenuAdmin(ModelAdmin): - model = settings.models.FLAT_MENU_MODEL - menu_label = _('Flat menus') - menu_icon = settings.FLATMENU_MENU_ICON - button_helper_class = FlatMenuButtonHelper - ordering = ('-site__is_default_site', 'site__hostname', 'handle') - create_view_class = views.FlatMenuCreateView - edit_view_class = views.FlatMenuEditView - add_to_settings_menu = True + index_view_class = MainMenuIndexView - def copy_view(self, request, instance_pk): - kwargs = {'model_admin': self, 'instance_pk': instance_pk} - return views.FlatMenuCopyView.as_view(**kwargs)(request) - - def get_admin_urls_for_registration(self): - urls = super().get_admin_urls_for_registration() - urls += ( - re_path(self.url_helper.get_action_url_pattern('copy'), - self.copy_view, - name=self.url_helper.get_action_url_name('copy')), - ) - return urls - - def get_list_filter(self, request): - if self.is_multisite_listing(request): - return ('site', 'handle') - return () + edit_view_class = MainMenuEditView + edit_template_name = "wagtailmenus/mainmenu_edit.html" + error_message = _("The menu could not be saved due to errors.") + + edit_handler = TabbedInterface([ + ObjectList(panels.main_menu_content_panels, heading=_("Content")), + ObjectList(panels.menu_settings_panels, heading=_("Settings"), + classname="settings"), + ]) - def get_list_display(self, request): - if self.is_multisite_listing(request): + +class FlatMenuIndexView(CopyableSnippetIndexView): + @property + def list_display(self): + if self.is_multisite_listing: return ('title', 'handle_formatted', 'site', 'items') return ('title', 'handle_formatted', 'items') - def handle_formatted(self, obj): - return mark_safe('%s' % obj.handle) - handle_formatted.short_description = _('handle') - handle_formatted.admin_order_field = 'handle' + @list_display.setter + def list_display(self, _): + pass + + @property + def list_filter(self): + if self.is_multisite_listing: + return ('site', 'handle') + return () + + @list_filter.setter + def list_filter(self, _): + pass + + @cached_property + def is_multisite_listing(self): + return self.get_base_queryset().values('site').distinct().count() > 1 + + +class FlatMenuCreateView(CreateView): + error_message = _("The flat menu could not be saved due to errors") + + +class FlatMenuAdmin(CopyableSnippetViewSet): + model = settings.models.FLAT_MENU_MODEL + menu_label = _('Flat menus') + icon = settings.FLATMENU_MENU_ICON + url_prefix = "wagtailmenus/flatmenu" + add_to_settings_menu = True + + index_view_class = FlatMenuIndexView + index_template_name = "wagtailmenus/flatmenu_index.html" + list_display = ('title', 'handle_formatted', 'site', 'items') + list_filter = ('site', 'handle') + ordering = ('-site__is_default_site', 'site__hostname', 'handle') - def is_multisite_listing(self, request): - return self.get_queryset(request).values('site').distinct().count() > 1 + add_view_class = FlatMenuCreateView + error_message = _("The flat menu could not be saved due to errors.") - def items(self, obj): - return obj.get_menu_items_manager().count() - items.short_description = _('no. of items') + edit_handler = TabbedInterface([ + ObjectList(panels.flat_menu_content_panels, heading=_("Content")), + ObjectList(panels.menu_settings_panels, heading=_("Settings"), + classname="settings"), + ]) diff --git a/wagtailmenus/models/menus.py b/wagtailmenus/models/menus.py index 792c2352..505dcb45 100644 --- a/wagtailmenus/models/menus.py +++ b/wagtailmenus/models/menus.py @@ -1179,6 +1179,15 @@ class Meta: verbose_name = _("flat menu") verbose_name_plural = _("flat menus") + def handle_formatted(self): + return mark_safe(f'{self.handle}') + handle_formatted.short_description = _('handle') + handle_formatted.admin_order_field = 'handle' + + def items(self): + return self.get_menu_items_manager().count() + items.short_description = _('no. of items') + @classmethod def render_from_tag( cls, context, handle, fall_back_to_default_site_menus=True, diff --git a/wagtailmenus/views.py b/wagtailmenus/views.py deleted file mode 100644 index 453cbbe4..00000000 --- a/wagtailmenus/views.py +++ /dev/null @@ -1,187 +0,0 @@ -from copy import copy - -from django import forms -from django.contrib.admin.utils import quote, unquote -from django.core.exceptions import PermissionDenied -from django.shortcuts import get_object_or_404, redirect -from django.utils.text import capfirst -from django.utils.translation import gettext as _ -from wagtail.admin import messages -from wagtail.admin.panels import ObjectList, TabbedInterface -try: - from wagtail_modeladmin.views import (CreateView, EditView, - ModelFormView, WMABaseView) -except ModuleNotFoundError: - from wagtail.contrib.modeladmin.views import (CreateView, EditView, - ModelFormView, WMABaseView) -from wagtail.models import Site - -from wagtailmenus.conf import settings - - -class SiteSwitchForm(forms.Form): - site = forms.ChoiceField(choices=[]) - - class Media: - js = [ - 'wagtailmenus/js/site-switcher.js', - ] - - def __init__(self, current_site, url_helper, **kwargs): - initial = {'site': url_helper.get_action_url('edit', current_site.pk)} - super().__init__(initial=initial, **kwargs) - sites = [] - for site in Site.objects.all(): - sites.append((url_helper.get_action_url('edit', site.pk), site)) - self.fields['site'].choices = sites - - -class MainMenuIndexView(WMABaseView): - - def dispatch(self, request, *args, **kwargs): - site = Site.find_for_request(request) - return redirect( - self.model_admin.url_helper.get_action_url('edit', site.pk)) - - -class MenuTabbedInterfaceMixin: - - def get_edit_handler(self): - if hasattr(self.model, 'edit_handler'): - edit_handler = self.model.edit_handler - elif hasattr(self.model, 'panels'): - edit_handler = ObjectList(self.model.panels) - else: - edit_handler = TabbedInterface([ - ObjectList(self.model.content_panels, heading=_("Content")), - ObjectList(self.model.settings_panels, heading=_("Settings"), - classname="settings"), - ]) - return edit_handler.bind_to_model(self.model) - - def form_invalid(self, form): - # TODO: This override is only required for Wagtail<2.1 - messages.validation_error( - self.request, self.get_error_message(), form - ) - return self.render_to_response(self.get_context_data()) - - -class MainMenuEditView(MenuTabbedInterfaceMixin, ModelFormView): - page_title = _('Editing') - instance_pk = None - instance = None - - def __init__(self, model_admin, instance_pk): - super().__init__(model_admin) - self.instance_pk = unquote(instance_pk) - self.pk_safe = quote(self.instance_pk) - self.site = get_object_or_404(Site, id=self.instance_pk) - self.instance = self.model.get_for_site(self.site) - self.instance.save() - - @property - def media(self): - media = super().media - if self.site_switcher: - media += self.site_switcher.media - return media - - @property - def edit_url(self): - return self.url_helper.get_action_url('edit', self.instance_pk) - - def get_meta_title(self): - return _('Editing %(model_name)s') % { - 'model_name': self.opts.verbose_name - } - - def get_page_subtitle(self): - return capfirst(self.opts.verbose_name) - - def dispatch(self, request, *args, **kwargs): - user = request.user - if not self.permission_helper.user_can_edit_obj(user, self.instance): - raise PermissionDenied - self.site_switcher = None - if Site.objects.count() > 1: - url_helper = self.model_admin.url_helper - self.site_switcher = SiteSwitchForm(self.site, url_helper) - site_from_get = request.GET.get('site', None) - if site_from_get and site_from_get != self.instance_pk: - return redirect( - url_helper.get_action_url('edit', site_from_get)) - return super().dispatch(request, *args, **kwargs) - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context.update({ - 'site': self.site, - 'site_switcher': self.site_switcher, - }) - return context - - def form_valid(self, form): - form.save() - messages.success(self.request, _("Main menu updated successfully.")) - return redirect(self.edit_url) - - def get_error_message(self): - return _("The menu could not be saved due to errors.") - - def get_template_names(self): - return ['wagtailmenus/mainmenu_edit.html'] - - -class FlatMenuCreateView(MenuTabbedInterfaceMixin, CreateView): - pass - - -class FlatMenuEditView(MenuTabbedInterfaceMixin, EditView): - pass - - -class FlatMenuCopyView(FlatMenuEditView): - page_title = _('Copying') - - @property - def copy_url(self): - return self.url_helper.get_action_url('copy', self.pk_quoted) - - def get_meta_title(self): - return _('Copying %(model_name)s') % { - 'model_name': self.opts.verbose_name, - } - - def check_action_permitted(self, user): - return self.permission_helper.user_can_create(user) - - def get_form_kwargs(self): - kwargs = super().get_form_kwargs() - """ - When the form is posted, don't pass an instance to the form. It should - create a new one out of the posted data. We also need to nullify any - IDs posted for inline menu items, so that new instances of those are - created too. - """ - if self.request.method == 'POST': - data = copy(self.request.POST) - i = 0 - while(data.get('%s-%s-id' % ( - settings.FLAT_MENU_ITEMS_RELATED_NAME, i - ))): - data['%s-%s-id' % ( - settings.FLAT_MENU_ITEMS_RELATED_NAME, i - )] = None - i += 1 - kwargs.update({ - 'data': data, - 'instance': self.model() - }) - return kwargs - - def get_success_message(self, instance): - return _("Flat menu '{instance}' created.").format(instance=instance) - - def get_template_names(self): - return ['wagtailmenus/flatmenu_copy.html'] diff --git a/wagtailmenus/wagtail_hooks.py b/wagtailmenus/wagtail_hooks.py index 675f0bc6..749bc187 100644 --- a/wagtailmenus/wagtail_hooks.py +++ b/wagtailmenus/wagtail_hooks.py @@ -1,18 +1,14 @@ from wagtail import hooks -try: - from wagtail_modeladmin.options import modeladmin_register -except ModuleNotFoundError: - from wagtail.contrib.modeladmin.options import modeladmin_register +from wagtail.snippets.models import register_snippet from wagtailmenus.conf import settings from wagtailmenus.utils.misc import derive_section_root if settings.MAIN_MENUS_EDITABLE_IN_WAGTAILADMIN: - modeladmin_register(settings.objects.MAIN_MENUS_MODELADMIN_CLASS) - + register_snippet(settings.objects.MAIN_MENUS_ADMIN_CLASS) if settings.FLAT_MENUS_EDITABLE_IN_WAGTAILADMIN: - modeladmin_register(settings.objects.FLAT_MENUS_MODELADMIN_CLASS) + register_snippet(settings.objects.FLAT_MENUS_ADMIN_CLASS) @hooks.register('before_serve_page') From 7446b09ea4e1fbd22475808ce521e32f838650b3 Mon Sep 17 00:00:00 2001 From: Daniel Kirkham Date: Mon, 8 Jan 2024 18:58:19 +1100 Subject: [PATCH 2/8] Template changes --- .../wagtailmenus/flatmenu_index.html | 3 + .../templates/wagtailmenus/mainmenu_edit.html | 57 +++++++++++++++---- .../wagtailsnippets/snippets/copy.html | 55 ++++++++++++++++++ 3 files changed, 105 insertions(+), 10 deletions(-) create mode 100644 wagtailmenus/templates/wagtailmenus/flatmenu_index.html create mode 100644 wagtailmenus/templates/wagtailsnippets/snippets/copy.html diff --git a/wagtailmenus/templates/wagtailmenus/flatmenu_index.html b/wagtailmenus/templates/wagtailmenus/flatmenu_index.html new file mode 100644 index 00000000..120cee85 --- /dev/null +++ b/wagtailmenus/templates/wagtailmenus/flatmenu_index.html @@ -0,0 +1,3 @@ +{% extends "wagtailsnippets/snippets/index.html" %} +{% load i18n %} +{% block titletag %}{% blocktrans trimmed with snippet_type_name_plural=model_opts.verbose_name_plural|capfirst %}{{ snippet_type_name_plural }}{% endblocktrans %}{% endblock %} diff --git a/wagtailmenus/templates/wagtailmenus/mainmenu_edit.html b/wagtailmenus/templates/wagtailmenus/mainmenu_edit.html index 404eae34..cccaa7a1 100644 --- a/wagtailmenus/templates/wagtailmenus/mainmenu_edit.html +++ b/wagtailmenus/templates/wagtailmenus/mainmenu_edit.html @@ -1,20 +1,57 @@ -{% extends "modeladmin/create.html" %} +{% extends "wagtailadmin/base.html" %} +{% load wagtailadmin_tags i18n %} +{% block titletag %}{% blocktrans trimmed with snippet_type_name=model_opts.verbose_name %}Editing {{ snippet_type_name }} - {{ object }}{% endblocktrans %}{% endblock %} +{% block bodyclass %}{% if locked_for_user %}content-locked{% endif %}{% endblock %} +{% block content %} + {% include 'wagtailadmin/shared/headers/slim_header.html' %} + + {% include "wagtailmenus/includes/header_with_site_switcher.html" with title=object icon=header_icon merged=1 %} + +
+ +
+
+ {% csrf_token %} +
+ {{ panel.render_form_content }} +
+
+ +
+
+
+
+{% endblock %} {% block extra_css %} {{ block.super }} - {{ edit_handler.form.media.css }} + {{ media.css }} {{ view.media.css }} {% endblock %} - {% block extra_js %} + {{ block.super }} {% include "wagtailadmin/pages/_editor_js.html" %} - {{ edit_handler.form.media.js }} - {{ edit_handler.html_declarations }} + {{ media.js }} {{ view.media.js }} -{% endblock %} -{% block header %} - {% include "wagtailmenus/includes/header_with_site_switcher.html" with title=view.page_title subtitle=view.get_page_subtitle icon=view.header_icon %} -{% endblock %} + -{% block form_action %}?{% endblock %} + {% if workflow_enabled %} + {% include "wagtailadmin/shared/_workflow_init.html" %} + {% endif %} +{% endblock %} diff --git a/wagtailmenus/templates/wagtailsnippets/snippets/copy.html b/wagtailmenus/templates/wagtailsnippets/snippets/copy.html new file mode 100644 index 00000000..14c7cb2c --- /dev/null +++ b/wagtailmenus/templates/wagtailsnippets/snippets/copy.html @@ -0,0 +1,55 @@ +{% extends "wagtailadmin/base.html" %} +{% load wagtailadmin_tags i18n %} +{% block titletag %}{% blocktrans trimmed with snippet_type_name=model_opts.verbose_name %}Copying {{ snippet_type_name }} - {{ object }}{% endblocktrans %}{% endblock %} +{% block bodyclass %}{% if locked_for_user %}content-locked{% endif %}{% endblock %} +{% block content %} + {% include 'wagtailadmin/shared/headers/slim_header.html' %} + + {% include "wagtailadmin/shared/header.html" with title=object icon=header_icon merged=1 only %} + +
+ +
+
+ {% csrf_token %} +
+ {{ panel.render_form_content }} +
+
+ +
+
+
+
+{% endblock %} + +{% block extra_css %} + {{ block.super }} + {{ media.css }} +{% endblock %} +{% block extra_js %} + {{ block.super }} + {% include "wagtailadmin/pages/_editor_js.html" %} + {{ media.js }} + + + + {% if workflow_enabled %} + {% include "wagtailadmin/shared/_workflow_init.html" %} + {% endif %} +{% endblock %} From 44f41e31d531746e0f102a30e27998df04bb061d Mon Sep 17 00:00:00 2001 From: Daniel Kirkham Date: Mon, 8 Jan 2024 19:00:35 +1100 Subject: [PATCH 3/8] Test changes --- .../conf/tests/test_modeladmin_disable_settings.py | 8 ++------ wagtailmenus/tests/test_backend.py | 11 ++++++----- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/wagtailmenus/conf/tests/test_modeladmin_disable_settings.py b/wagtailmenus/conf/tests/test_modeladmin_disable_settings.py index 891c4482..f511ebbe 100644 --- a/wagtailmenus/conf/tests/test_modeladmin_disable_settings.py +++ b/wagtailmenus/conf/tests/test_modeladmin_disable_settings.py @@ -6,12 +6,8 @@ from wagtailmenus import wagtail_hooks from wagtailmenus.modeladmin import FlatMenuAdmin, MainMenuAdmin -try: - from wagtail_modeladmin.options import modeladmin_register - modeladmin_register_str = 'wagtail_modeladmin.options.modeladmin_register' -except ModuleNotFoundError: - from wagtail.contrib.modeladmin.options import modeladmin_register - modeladmin_register_str = 'wagtail.contrib.modeladmin.options.modeladmin_register' +modeladmin_register_str = 'wagtail.snippets.models.register_snippet' + class TestDisablingFlatMenusInWagtailCMS(TestCase): """ diff --git a/wagtailmenus/tests/test_backend.py b/wagtailmenus/tests/test_backend.py index 02a72991..f4f8b5bd 100644 --- a/wagtailmenus/tests/test_backend.py +++ b/wagtailmenus/tests/test_backend.py @@ -1,4 +1,5 @@ from collections import OrderedDict +import re from django.contrib.auth import get_user_model from django.contrib.auth.models import Group @@ -45,7 +46,7 @@ def test_copy_footer_menu(self): response = form.submit().follow() assert len(response.context['object_list']) == 4 - assert '
' in response + assert '
' in response # Let's just compare the two menu with the old one site_two_footer_menu = FlatMenu.get_for_site('footer', site_two) @@ -84,7 +85,7 @@ def test_main_menu_save_success(self): form = edit_view.forms[next(reversed(OrderedDict(edit_view.forms)))] response = form.submit().follow() - assert 'Main menu updated successfully.' in response + assert re.search(r"Main menu [^<>]+ updated", response.content.decode()) class LinkPageCMSTest(TestCase): @@ -230,9 +231,9 @@ def test_flatmenu_list_multisite(self): # filters response = self.client.get('/admin/wagtailmenus/flatmenu/') self.assertEqual(response.status_code, 200) - self.assertContains( - response, '') - self.assertContains(response, '
') + assert re.search( + r"Sort by [^<>]+Site[^<>]+ in ascending order", response.content.decode()) + self.assertContains(response, 'data-field data-contentpath="site"') def test_flatmenu_edit(self): response = self.client.get( From d8d8db6fa7679603055ea8e85d58822373bbb8cb Mon Sep 17 00:00:00 2001 From: Daniel Kirkham Date: Mon, 8 Jan 2024 19:02:35 +1100 Subject: [PATCH 4/8] Installation and configuration changes --- docs/source/installation.rst | 3 --- setup.py | 7 ++----- tox.ini | 14 ++++++-------- wagtailmenus/settings/base.py | 4 ---- 4 files changed, 8 insertions(+), 20 deletions(-) diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 7ecd9486..3a2981ed 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -9,7 +9,6 @@ Installing wagtailmenus .. code-block:: console pip install wagtailmenus - pip install wagtail_modeladmin # if Wagtail >= 5.1 2. Add ``wagtailmenus`` and ``modeladmin`` to the ``INSTALLED_APPS`` setting in your project settings: @@ -19,8 +18,6 @@ Installing wagtailmenus INSTALLED_APPS = [ ... - 'wagtail_modeladmin', # if Wagtail >=5.1; Don't repeat if it's there already - 'wagtail.contrib.modeladmin', # if Wagtail <5.1; Don't repeat if it's there already 'wagtailmenus', ] diff --git a/setup.py b/setup.py index a1ff4988..22c45d40 100644 --- a/setup.py +++ b/setup.py @@ -14,6 +14,7 @@ # Essential dependencies requires = [ + 'wagtail>=5.2', 'django-cogwheels==0.3', ] @@ -24,8 +25,7 @@ ] development_extras = [ - 'wagtail>=2.15', - 'wagtail_modeladmin>=1.0', + 'wagtail>=5.2', 'django-debug-toolbar', 'django-extensions', 'ipdb', @@ -71,9 +71,6 @@ 'Framework :: Django :: 3.2', 'Framework :: Django :: 4.1', 'Framework :: Django :: 4.2', - 'Framework :: Wagtail :: 2', - 'Framework :: Wagtail :: 3', - 'Framework :: Wagtail :: 4', 'Framework :: Wagtail :: 5', 'Topic :: Internet :: WWW/HTTP', "Topic :: Internet :: WWW/HTTP :: Dynamic Content", diff --git a/tox.ini b/tox.ini index 2b482fa7..19652b84 100644 --- a/tox.ini +++ b/tox.ini @@ -3,9 +3,11 @@ skipsdist = True usedevelop = True envlist = - py{38,39,310}-dj{32,41}-wt{41,42,50} - py{311,312}-dj41-wt{41,42,50} - py{38,39,310,311,312}-dj{41,42}-wt{51,52}-wma{10} + wt42-dj41-py310 + wt52-dj32-py{38,39,310} + wt52-dj41-py{38,39,310,311} + wt52-dj42-py{38,39,310,311,312} + wt52-dj50-py{310,311,312} [gh-actions] python = @@ -23,12 +25,8 @@ commands = coverage run --source=wagtailmenus runtests.py deps = coverage - wma10: wagtail_modeladmin>=1.0,<1.1 dj32: Django>=3.2,<4.0 dj41: Django>=4.1,<4.2 dj42: Django>=4.2,<4.3 - wt41: wagtail>=4.1,<4.2 - wt42: wagtail>=4.2,<5.0 - wt50: wagtail>=5.0,<5.1 - wt51: wagtail>=5.1,<5.2 + dj50: Django>=5.0,<5.1 wt52: wagtail>=5.2,<5.3 diff --git a/wagtailmenus/settings/base.py b/wagtailmenus/settings/base.py index 45a7a9f8..9fbbda1a 100644 --- a/wagtailmenus/settings/base.py +++ b/wagtailmenus/settings/base.py @@ -39,10 +39,6 @@ 'wagtail.contrib.settings', ] -if wagtail.VERSION >= (5, 1): - INSTALLED_APPS += ['wagtail_modeladmin'] -else: - INSTALLED_APPS += ['wagtail.contrib.modeladmin'] PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) STATIC_ROOT = os.path.join(PROJECT_ROOT, 'staticfiles') From a0dc0d98d4f1dd52f4dfbefa86bbe0e6d7e528e6 Mon Sep 17 00:00:00 2001 From: Daniel Kirkham Date: Mon, 8 Jan 2024 19:03:33 +1100 Subject: [PATCH 5/8] Updated high-level documentation --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 093e6ead..2d800713 100644 --- a/README.rst +++ b/README.rst @@ -31,8 +31,8 @@ wagtailmenus is an extension for Torchbox's `Wagtail CMS = 4.1 -- Django versions 3.2, 4.1, and 4.2 +- Wagtail versions >= 5.2 +- Django versions 3.2, 4.2 and >= 5.0 - Python versions 3.8 to 3.12 .. image:: https://raw.githubusercontent.com/jazzband/wagtailmenus/master/docs/source/_static/images/repeating-item.png From 1b0f3f641b6a2340a1021a085d72ae3d03939341 Mon Sep 17 00:00:00 2001 From: Daniel Kirkham Date: Mon, 8 Jan 2024 19:03:49 +1100 Subject: [PATCH 6/8] Updated contibutors --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 8ac08b16..20119124 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -34,6 +34,7 @@ This is a [Jazzband](https://jazzband.co/) project. By contributing you agree to * Gabriel Augendre (Crocmagnon) * Bojan Mihelac (bmihelac) * Ben Froelich-Leon (benfroelich) +* Daniel Kirkham (dkirkham) ## Translators From 1ed0240223af899d84463cd8d3f84c4a078cc342 Mon Sep 17 00:00:00 2001 From: Daniel Kirkham Date: Sun, 18 Feb 2024 22:22:57 +1100 Subject: [PATCH 7/8] Add Wagtail 6.0 compatibility --- wagtailmenus/modeladmin.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/wagtailmenus/modeladmin.py b/wagtailmenus/modeladmin.py index e3c31feb..88f2886e 100644 --- a/wagtailmenus/modeladmin.py +++ b/wagtailmenus/modeladmin.py @@ -12,7 +12,14 @@ from wagtailmenus.conf import settings from wagtailmenus.forms import SiteSwitchForm -from .copyable_snippetviewset import CopyableSnippetIndexView, CopyableSnippetViewSet +# Wagtail 6 introduced Snippet copy. +# CopyableSnippetViewSet provides the equivalent in Wagtail 5.2 +from wagtail import VERSION as WAGTAIL_VERSION +if WAGTAIL_VERSION >= (6, 0): + CopyableSnippetIndexView = IndexView + CopyableSnippetViewSet = SnippetViewSet +else: + from .copyable_snippetviewset import CopyableSnippetIndexView, CopyableSnippetViewSet class MainMenuIndexView(IndexView): From 06cc1040475377071c4e7b2fdf10a8d5235423a4 Mon Sep 17 00:00:00 2001 From: Daniel Kirkham Date: Sun, 18 Feb 2024 22:24:56 +1100 Subject: [PATCH 8/8] Extend tests to Wagtail 6.0 --- tox.ini | 3 +++ wagtailmenus/tests/test_backend.py | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 19652b84..9e355c65 100644 --- a/tox.ini +++ b/tox.ini @@ -8,6 +8,8 @@ envlist = wt52-dj41-py{38,39,310,311} wt52-dj42-py{38,39,310,311,312} wt52-dj50-py{310,311,312} + wt60-dj42-py{38,39,310,311,312} + wt60-dj50-py{310,311,312} [gh-actions] python = @@ -30,3 +32,4 @@ deps = dj42: Django>=4.2,<4.3 dj50: Django>=5.0,<5.1 wt52: wagtail>=5.2,<5.3 + wt60: wagtail>=6.0,<6.1 diff --git a/wagtailmenus/tests/test_backend.py b/wagtailmenus/tests/test_backend.py index f4f8b5bd..13847821 100644 --- a/wagtailmenus/tests/test_backend.py +++ b/wagtailmenus/tests/test_backend.py @@ -46,7 +46,8 @@ def test_copy_footer_menu(self): response = form.submit().follow() assert len(response.context['object_list']) == 4 - assert '
' in response + # Check for Wagtail 5.2 or >=6.0 response + assert '
' in response or '