from django.db.models import ForeignKey from django.urls import path from django.utils.functional import cached_property from django.utils.translation import gettext as _ from wagtail.admin.forms.models import register_form_field_override from wagtail.admin.views.generic import chooser as chooser_views from wagtail.admin.widgets.chooser import BaseChooser from wagtail.blocks import ChooserBlock from wagtail.telepath import register as register_telepath_adapter from .base import ViewSet class ChooserViewSet(ViewSet): """ A viewset that creates a chooser modal interface for choosing model instances. """ model = None icon = "snippet" #: The icon to use in the header of the chooser modal, and on the chooser widget choose_one_text = _( "Choose" ) #: Label for the 'choose' button in the chooser widget when choosing an initial item page_title = None #: Title text for the chooser modal (defaults to the same as ``choose_one_text``)` choose_another_text = _( "Choose another" ) #: Label for the 'choose' button in the chooser widget, when an item has already been chosen edit_item_text = _("Edit") #: Label for the 'edit' button in the chooser widget per_page = ViewSet.UNDEFINED #: 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"] #: A list of URL query parameters that, if present in the url, should be applied as filters to the queryset. #: (These should also be listed in `preserve_url_parameters`.) url_filter_parameters = [] #: 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 #: The view class used to render just the results panel within the chooser modal; must be a subclass of ``wagtail.admin.views.generic.chooser.ChooseResultsView``. choose_results_view_class = chooser_views.ChooseResultsView #: The view class used after an item has been chosen; must be a subclass of ``wagtail.admin.views.generic.chooser.ChosenView``. chosen_view_class = chooser_views.ChosenView #: The view class used after multiple items have been chosen; must be a subclass of ``wagtail.admin.views.generic.chooser.ChosenMultipleView``. chosen_multiple_view_class = chooser_views.ChosenMultipleView #: The view class used to handle submissions of the 'create' form; must be a subclass of ``wagtail.admin.views.generic.chooser.CreateView``. create_view_class = chooser_views.CreateView #: The base Widget class that the chooser widget will be derived from. base_widget_class = BaseChooser #: The adapter class used to map the widget class to its JavaScript implementation - see :ref:`streamfield_widget_api`. #: Only required if the chooser uses custom JavaScript code. widget_telepath_adapter_class = None #: The base ChooserBlock class that the StreamField chooser block will be derived from. base_block_class = ChooserBlock #: Defaults to True; if False, the chooser widget will not automatically be registered for use in admin forms. register_widget = True #: Form class to use for the form in the "Create" tab of the modal. creation_form_class = None #: List of model fields that should be included in the creation form, if creation_form_class is not specified. form_fields = None #: List of model fields that should be excluded from the creation form, if creation_form_class. #: If none of ``creation_form_class``, ``form_fields`` or ``exclude_form_fields`` are specified, the "Create" tab will be omitted. exclude_form_fields = None search_tab_label = _("Search") #: Label for the 'search' tab in the chooser modal create_action_label = _( "Create" ) #: Label for the submit button on the 'create' form create_action_clicked_label = None #: Alternative text to display on the submit button after it has been clicked creation_tab_label = None #: Label for the 'create' tab in the chooser modal (defaults to the same as create_action_label) permission_policy = None def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if self.page_title is None: self.page_title = self.choose_one_text def get_common_view_kwargs(self, **kwargs): return super().get_common_view_kwargs( **{ "model": self.model, "permission_policy": self.permission_policy, "preserve_url_parameters": self.preserve_url_parameters, "url_filter_parameters": self.url_filter_parameters, "create_action_label": self.create_action_label, "create_action_clicked_label": self.create_action_clicked_label, "creation_form_class": self.creation_form_class, "form_fields": self.form_fields, "exclude_form_fields": self.exclude_form_fields, "chosen_url_name": self.get_url_name("chosen"), "chosen_multiple_url_name": self.get_url_name("chosen_multiple"), "results_url_name": self.get_url_name("choose_results"), "create_url_name": self.get_url_name("create"), "per_page": self.per_page, **kwargs, } ) @property def choose_view(self): view_class = self.inject_view_methods( self.choose_view_class, ["get_object_list"] ) return self.construct_view( view_class, icon=self.icon, page_title=self.page_title, search_tab_label=self.search_tab_label, creation_tab_label=self.creation_tab_label, ) @property def choose_results_view(self): view_class = self.inject_view_methods( self.choose_results_view_class, ["get_object_list"] ) return self.construct_view(view_class) @property def chosen_view(self): return self.construct_view(self.chosen_view_class) @property def chosen_multiple_view(self): return self.construct_view(self.chosen_multiple_view_class) @property def create_view(self): return self.construct_view(self.create_view_class) @cached_property def model_name(self): if isinstance(self.model, str): return self.model.split(".")[-1] else: return self.model.__name__ @cached_property def widget_class(self): """ Returns the form widget class for this chooser. """ if self.model is None: widget_class_name = "ChooserWidget" else: if isinstance(self.model, str): model_name = self.model.split(".")[-1] else: model_name = self.model.__name__ widget_class_name = "%sChooserWidget" % model_name return type( widget_class_name, (self.base_widget_class,), { "model": self.model, "choose_one_text": self.choose_one_text, "choose_another_text": self.choose_another_text, "link_to_chosen_text": self.edit_item_text, "chooser_modal_url_name": self.get_url_name("choose"), "icon": self.icon, }, ) def get_block_class(self, name=None, module_path=None): """ Returns a StreamField ChooserBlock class using this chooser. :param name: Name to give to the class; defaults to the model name with "ChooserBlock" appended :param module_path: The dotted path of the module where the class can be imported from; used when deconstructing the block definition for migration files. """ meta = type( "Meta", (self.base_block_class._meta_class,), { "icon": self.icon, }, ) cls = type( name or "%sChooserBlock" % self.model_name, (self.base_block_class,), { "target_model": self.model, "widget": self.widget_class(), "Meta": meta, }, ) if module_path: cls.__module__ = module_path return cls def get_urlpatterns(self): return super().get_urlpatterns() + [ path("", self.choose_view, name="choose"), path("results/", self.choose_results_view, name="choose_results"), path("chosen//", self.chosen_view, name="chosen"), path("chosen-multiple/", self.chosen_multiple_view, name="chosen_multiple"), path("create/", self.create_view, name="create"), ] def on_register(self): if self.model and self.register_widget: register_form_field_override( ForeignKey, to=self.model, override={"widget": self.widget_class} ) if self.widget_telepath_adapter_class: adapter = self.widget_telepath_adapter_class() register_telepath_adapter(adapter, self.widget_class)