1

我正在尝试限制 wagtail 流字段块内特定 DocumentChooserBlock 的查询结果。

我已经知道您可以使用hooks将 DocumentChooser 的文件类型限制为页面类型,但我想避免在页面范围内限制可能的文件类型,以防其他 StreamField 块需要它们。

有没有可能的方法来实现我在这里想要实现的目标?

4

2 回答 2

0

Usingwagtail-generic-chooser提供了更多自定义选择器模式工作方式的能力。

第 1 步 - 安装wagtail-generic-chooser

  • 跑:pip install wagtail-generic-chooser
  • 然后添加generic_chooser到您项目的INSTALLED_APPS.

第 2 步 - 设置选择器视图集

  • 类似于设置选择器视图集的文档说明
  • 确保我们可以accept通过创建一个扩展的自定义类来处理参数ModelChooserMixin,这意味着在搜索时参数仍然会被传递。
  • 添加对acceptURL 参数的处理以有条件地过滤返回的值。
  • 设置一个扩展类,ModelChooserViewSet它将处理Document模式中列表的显示。

基础/views.py

from django.db.models import Q

from generic_chooser.views import ModelChooserMixin, ModelChooserViewSet

from wagtail.documents.models import Document


class RestrictedDocumentChooserMixin(ModelChooserMixin):
    # preserve this URL parameter on pagination / search
    preserve_url_parameters = [
        "accept",
    ]

    def get_unfiltered_object_list(self):
        objects = super().get_unfiltered_object_list()

        accept = self.request.GET.get("accept")
        print("get_unfiltered_object_list", accept)

        if accept:
            accepted_files = accept.split(",")

            queries = [Q(file__iendswith=f".{value}") for value in accepted_files]

            query = queries.pop()
            for item in queries:
                query |= item

            objects = objects.filter(query)
        return objects


class RestrictedDocumentChooserViewSet(ModelChooserViewSet):
    chooser_mixin_class = RestrictedDocumentChooserMixin

    icon = "doc"
    model = Document
    page_title = "Choose a document"
    per_page = 10
    order_by = "title"
    fields = ["title", "file"]

第 3 步 - 创建选择器小部件

  • 此小部件不是 ,Block但将用作 的基础,Block也可用于FieldPanel.
  • 与基于模型的 Widget 的设置类似,创建一个扩展AdminChooser.
  • 在该__init__方法中,我们提取了acceptkwarg,以便我们可以使用它来生成自定义 URL 参数。
  • 覆盖get_edit_item_url允许单击所选文档以对其进行编辑的方法。
  • 覆盖此处工作的“get_choose_modal_url reverse” to append the URL query param (note: I could not get,而不会产生更多争论)。

基础/models.py

from django.contrib.admin.utils import quote
from django.urls import reverse

from generic_chooser.widgets import AdminChooser

from wagtail.documents.models import Document


class RestrictedDocumentChooser(AdminChooser):
    def __init__(self, **kwargs):

        self.accept = kwargs.pop("accept")

        super().__init__(**kwargs)

    choose_one_text = "Choose a Document"
    choose_another_text = "Choose another document"
    link_to_chosen_text = "Edit this document"
    model = Document
    choose_modal_url_name = "restricted_document_chooser:choose"

    def get_choose_modal_url(self):
        url = super().get_choose_modal_url()
        return url + "?accept=%s" % self.accept

    def get_edit_item_url(self, item):
        return reverse("wagtaildocs:edit", args=[item.id])

第 4 步 - 在 Wagtail Hooks 中注册选择器视图集

  • 不需要在construct_document_chooser_queryset这里使用,而是使用钩子register_admin_viewset并注册RestrictedDocumentChooserViewSet.

基地/wagtail_hooks.py

from wagtail.core import hooks
from .views import RestrictedDocumentChooserViewSet

# ... other hooks etc

@hooks.register("register_admin_viewset")
def register_restricted_document_chooser_viewset():
    return RestrictedDocumentChooserViewSet(
        "restricted_document_chooser", url_prefix="restricted-document-chooser"
    )

第 5 步 - 设置和使用自定义Block

  • 此类扩展ChooserBlock并包装了RestrictedDocumentChooser已创建的小部件。
  • __init__同一个 kwargaccept上被拉出并传递给RestrictedDocumentChooser创建时。
  • 这个块可以通过调用它来使用,类似于任何其他块,不过使用 kwarg acceptdoc_block = RestrictedDocumentChooserBlock(accept="svg,md")

基础/blocks.py

from django.utils.functional import cached_property
from wagtail.images.blocks import ChooserBlock

# ...

class RestrictedDocumentChooserBlock(ChooserBlock):
    def __init__(self, **kwargs):
        self.accept = kwargs.pop("accept")
        super().__init__(**kwargs)

    @cached_property
    def target_model(self):
        from wagtail.documents.models import Document

        return Document

    @cached_property
    def widget(self):
        from .widgets import RestrictedDocumentChooser

        return RestrictedDocumentChooser(accept=self.accept)

    def get_form_state(self, value):
        return self.widget.get_value_data(value)

于 2021-09-05T03:13:15.103 回答
0

Wagtail 的选择器模态系统与普通的 Django 小部件(用于呈现字段的 html 内容的类)有点不同,小部件本身主要呈现一个按钮“选择文档”,然后该按钮触发一个模态,它是一个单独的视图和模板.

正如您所指出的,construct_document_chooser_queryset钩子可以限制这些模式中显示的结果,但只能访问正在查看的页面的请求对象,而不是用于触发该模式的 Widget。

有一种方法可以获得一些有限的所需功能,但它不适用于搜索结果,并且不会限制对该文件类型的任何其他上传。

第 1 步 - 创建自定义DocumentChooserBlock

  • 这个块类扩展了DocumentChooserBlock并有一个自定义__init__方法,该方法拉出一个 kwargaccept并将其分配给小部件 attrs。
  • Django Widgets 都有接受attrs的能力,这些都在渲染的 HTML 元素上输出,我们的自定义 Block 将我们想要的值分配给小部件,以便其他方法可以访问它。
  • 这个块可以像任何其他块一样使用,但会使用一个接受的 kwarg'doc_block = SpecificDocumentChooserBlock(accept="svg,md") # uses accept kwarg
  • 您可以通过查看 DOM(在浏览器中检查元素)来确认这是否有效,就在“选择文档”之后,将有一个隐藏的属性,类似于<input type="hidden" name="body-2-value" accept="svg,md" id="body-2-value" value="">

块.py

from wagtail.documents.blocks import DocumentChooserBlock


class SpecificDocumentChooserBlock(DocumentChooserBlock):
    """
    Existing DocumentChooserBlock with the ability to add widget attrs based on the
    accept kwarg, anything on self.widget.attrs will be added to the hidden
    input field (so be careful what key is used).
    """

    def __init__(self, accept=None, **kwargs):
        super().__init__(**kwargs)

        self.widget.attrs["accept"] = accept


第 2 步 - 确保将小部件属性传递给模式触发器

  • 不幸的是,用于查询 URL 的数据不在上面的 HTML 输入元素上,而是在容器 div 上,请参阅块 divdata-chooser-url上的。document-chooser
  • 此数据属性由名为Telepath的系统生成。
  • 要理解的主要部分是有一个 Class 用于告诉浏览器根据 Widget 呈现什么,默认情况下,它不会传递 Widget 属性。
  • 下面的代码应该添加到 中wagtail_hooks.py,因为无论如何我们都需要该文件,而且我们知道它只会在运行时运行一次。
  • 该行widget.render_html(是关键部分,我们正在使用**语法来解压缩任何 widget.attrs 值(一个将是accept我们自定义块设置的项目)。

钩子.py

from wagtail.core.telepath import register as telepath_register
from wagtail.documents.widgets import AdminDocumentChooser, DocumentChooserAdapter

class CustomDocumentChooserAdapter(DocumentChooserAdapter):
    def js_args(self, widget):
        return [
            widget.render_html(
                # this line is changed, allocate any widget.attrs to the attrs passed to render_html
                "__NAME__",
                None,
                attrs={**widget.attrs, "id": "__ID__"},
            ),
            widget.id_for_label("__ID__"),
        ]


telepath_register(CustomDocumentChooserAdapter(), AdminDocumentChooser)

第 3 步 - 覆盖文档选择器的管理模板

  • 请查看自定义管理模板的文档,因为您可能需要INSTALLED_APPS为此步骤添加更多应用程序。
  • 创建一个新文件myapp/templates/wagtaildocs/widgets/document_chooser.html,后面的部分在templates这里很关键,因为我们要覆盖和扩展这个确切的模板。
  • 在模板中,我们将扩展原始并覆盖块chooser_attributes,因为这是添加data-chooser-url选择器模态触发器使用的块。
  • 重要提示:在此处重新启动您的开发服务器,因为您添加了一个新的模板覆盖。
  • 完成后,在您的浏览器中检查包含“选择文档”按钮的元素,您应该能够看到容器元素现在有一个data-chooser-url添加到 URL 的查询字符串<div id="body-2-value-chooser" class="chooser document-chooser blank" data-chooser-url="/admin/documents/chooser/?accept=svg,md">

myapp/templates/wagtaildocs/widgets/document_chooser.html

{% extends "wagtaildocs/widgets/document_chooser.html" %}
{% comment %}
  This template overrides the Wagtail default chooser field, this is not the modal but
  the button / selected value shown in the page editor.
  chooser_attributes are the attributes that are used by the modal trigger, we will
  override the 'data-chooser-url' value with a url param
{% endcomment %}

{% block chooser_attributes %}data-chooser-url="{% url "wagtaildocs:chooser" %}{% if attrs.accept %}?accept={{ attrs.accept }}{% endif %}"{% endblock %}

第 4 步 - 处理accept查询字符串参数

  • 使用construct_document_chooser_queryset现在,可以拉入 GET 参数accept并对其进行解析以生成一组不同的文档结果。

wagtail_hooks.py

@hooks.register("construct_document_chooser_queryset")
def show_accepted_documents_only(documents, request):
    accept = request.GET.get("accept")

    if accept:
        accepted_files = accept.split(",")

        queries = [Q(file__iendswith=f".{value}") for value in accepted_files]

        query = queries.pop()
        for item in queries:
            query |= item

        documents = documents.filter(query)

    return documents

注意事项

  • 此解决方案不会阻止用户仅在模式中上传特定文件,但您可以探索使用一些 CSS 隐藏该选项卡(块接受类名道具)。
  • 当用户在模式中搜索时,不幸的是,它不会尊重以这种方式设置的 URL。
  • 该解决方案在各种版本中可能很脆弱,尤其是CustomDocumentChooserAdapter因此请务必密切关注 Wagtail 代码更改。
于 2021-09-03T13:03:27.703 回答