From c5513d098a0e463769e655090b3cbacced9f90d3 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Thu, 12 Jan 2023 21:17:37 +0000 Subject: [PATCH] Implement mechanism to pass the 'multiple' URL parameter (and any other specified ones) on links / form actions within generic chooser modal --- docs/reference/viewsets.md | 1 + .../wagtailadmin/generic/chooser/results.html | 2 +- wagtail/admin/views/generic/chooser.py | 68 +++++++++++++++++-- wagtail/admin/viewsets/chooser.py | 7 ++ 4 files changed, 70 insertions(+), 8 deletions(-) diff --git a/docs/reference/viewsets.md b/docs/reference/viewsets.md index 6040abb14d..9c9a1606d1 100644 --- a/docs/reference/viewsets.md +++ b/docs/reference/viewsets.md @@ -53,6 +53,7 @@ Viewsets are Wagtail's mechanism for defining a group of related admin views wit .. autoattribute:: choose_another_text .. autoattribute:: edit_item_text .. autoattribute:: per_page + .. autoattribute:: preserve_url_parameters .. autoattribute:: choose_view_class .. autoattribute:: choose_results_view_class .. autoattribute:: chosen_view_class diff --git a/wagtail/admin/templates/wagtailadmin/generic/chooser/results.html b/wagtail/admin/templates/wagtailadmin/generic/chooser/results.html index ea69439ad6..31c01157d0 100644 --- a/wagtail/admin/templates/wagtailadmin/generic/chooser/results.html +++ b/wagtail/admin/templates/wagtailadmin/generic/chooser/results.html @@ -18,7 +18,7 @@ {% block results_listing %} {% component table %} {% endblock %} - {% include "wagtailadmin/shared/pagination_nav.html" with items=results linkurl=results_url %} + {% include "wagtailadmin/shared/pagination_nav.html" with items=results linkurl=results_pagination_url %} {% else %} {% if is_searching %} {% block no_search_results_message %} diff --git a/wagtail/admin/views/generic/chooser.py b/wagtail/admin/views/generic/chooser.py index 03b7681e0b..66ff35d5af 100644 --- a/wagtail/admin/views/generic/chooser.py +++ b/wagtail/admin/views/generic/chooser.py @@ -1,5 +1,8 @@ +import re +import urllib.parse + from django.conf import settings -from django.contrib.admin.utils import unquote +from django.contrib.admin.utils import quote, unquote from django.core.exceptions import ( ImproperlyConfigured, ObjectDoesNotExist, @@ -66,7 +69,46 @@ class ModelLookupMixin: return resolve_model_string(self.model) -class BaseChooseView(ModalPageFurnitureMixin, ModelLookupMixin, ContextMixin, View): +class PreserveURLParametersMixin: + """ + Adds support for passing designated URL parameters from the current request when constructing URLs + for links / form actions. + """ + + preserve_url_parameters = ["multiple"] + + @cached_property + def _preserved_param_string(self): + params = {} + for param in self.preserve_url_parameters: + try: + params[param] = self.request.GET[param] + except KeyError: + pass + + return urllib.parse.urlencode(params) + + def append_preserved_url_parameters(self, url): + """ + Given a base URL (which might already include URL parameters), append any URL parameters + from the preserve_url_parameters list that are present in the current request URL + """ + if self._preserved_param_string: + if "?" in url: + url += "&" + self._preserved_param_string + else: + url += "?" + self._preserved_param_string + + return url + + +class BaseChooseView( + ModalPageFurnitureMixin, + ModelLookupMixin, + PreserveURLParametersMixin, + ContextMixin, + View, +): """ Provides common functionality for views that present a (possibly searchable / filterable) list of objects to choose from @@ -136,7 +178,7 @@ class BaseChooseView(ModalPageFurnitureMixin, ModelLookupMixin, ContextMixin, Vi return objects def get_results_url(self): - return reverse(self.results_url_name) + return self.append_preserved_url_parameters(reverse(self.results_url_name)) @property def columns(self): @@ -145,7 +187,11 @@ class BaseChooseView(ModalPageFurnitureMixin, ModelLookupMixin, ContextMixin, Vi "title", label=_("Title"), accessor=str, - url_name=self.chosen_url_name, + get_url=( + lambda obj: self.append_preserved_url_parameters( + reverse(self.chosen_url_name, args=(quote(obj.pk),)) + ) + ), link_attrs={"data-chooser-modal-choice": True}, ), ] @@ -167,11 +213,19 @@ class BaseChooseView(ModalPageFurnitureMixin, ModelLookupMixin, ContextMixin, Vi def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) + + results_url = self.get_results_url() + + # For result pagination links, we need a version of results_url with parameters removed, + # so that the pagination include can append its own parameters via the {% querystring %} template tag + results_pagination_url = re.sub(r"\?.*$", "", results_url) + context.update( { "results": self.results, "table": self.table, - "results_url": self.get_results_url(), + "results_url": results_url, + "results_pagination_url": results_pagination_url, "is_searching": self.filter_form.is_searching, "is_filtering_by_collection": self.filter_form.is_filtering_by_collection, "search_query": self.filter_form.search_query, @@ -184,7 +238,7 @@ class BaseChooseView(ModalPageFurnitureMixin, ModelLookupMixin, ContextMixin, Vi raise NotImplementedError() -class CreationFormMixin(ModelLookupMixin): +class CreationFormMixin(ModelLookupMixin, PreserveURLParametersMixin): """ Provides a form class for creating new objects """ @@ -246,7 +300,7 @@ class CreationFormMixin(ModelLookupMixin): "%r must provide a create_url_name attribute or a get_create_url method" % type(self) ) - return reverse(self.create_url_name) + return self.append_preserved_url_parameters(reverse(self.create_url_name)) def get_creation_form_context_data(self, form): return { diff --git a/wagtail/admin/viewsets/chooser.py b/wagtail/admin/viewsets/chooser.py index 3110b0adee..ebdc79cb2e 100644 --- a/wagtail/admin/viewsets/chooser.py +++ b/wagtail/admin/viewsets/chooser.py @@ -31,6 +31,10 @@ class ChooserViewSet(ViewSet): per_page = 10 #: Number of results to show per page + #: A list of URL query parameters that should be passed on unmodified as part of any links or + #: form submissions within the chooser modal workflow. + preserve_url_parameters = ["multiple"] + #: The view class to use for the overall chooser modal; must be a subclass of ``wagtail.admin.views.generic.chooser.ChooseView``. choose_view_class = chooser_views.ChooseView @@ -101,6 +105,7 @@ class ChooserViewSet(ViewSet): create_action_label=self.create_action_label, create_action_clicked_label=self.create_action_clicked_label, permission_policy=self.permission_policy, + preserve_url_parameters=self.preserve_url_parameters, ) @property @@ -116,6 +121,7 @@ class ChooserViewSet(ViewSet): create_action_label=self.create_action_label, create_action_clicked_label=self.create_action_clicked_label, permission_policy=self.permission_policy, + preserve_url_parameters=self.preserve_url_parameters, ) @property @@ -141,6 +147,7 @@ class ChooserViewSet(ViewSet): create_action_label=self.create_action_label, create_action_clicked_label=self.create_action_clicked_label, permission_policy=self.permission_policy, + preserve_url_parameters=self.preserve_url_parameters, ) @cached_property