限制Wagtail Steamfield中特定DocumentChooserBlock()块的文件类型

2024-10-02 20:42:14 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图将特定DocumentChooserBlock的查询结果限制在wagtail流字段块中

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

是否有任何可能的方法来实现我在这里试图实现的目标


Tags: 方法类型目标页面wagtail文件类型hooksstreamfield
2条回答

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

正如您所指出的,^{} hook可以限制这些模式中显示的结果,但只能访问正在查看的页面的请求对象,而不能访问用于触发该模式的小部件

有一种方法可以获得一些有限的所需功能,但它不适用于搜索结果,也不会将任何附加上载限制为该文件类型

步骤1-创建自定义DocumentChooserBlock

  • 这个block类扩展了DocumentChooserBlock,并有一个自定义的__init__方法,该方法提取kwargaccept并将其分配给小部件属性
  • Django小部件都有accept ^{}的功能,这些都是在呈现的HTML元素上输出的,我们的自定义块将我们想要的值分配给小部件,以便其他方法可以访问它
  • 此块的使用方式与任何其他块相同,但将使用accept doc_block = SpecificDocumentChooserBlock(accept="svg,md") # uses accept kwarg的kwarg
  • 您可以通过查看DOM(浏览器中的inspect元素)来确认这一点,在“选择文档”之后,将有一个类似<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上,请参见document-chooser块div上的data-chooser-url
  • 此数据属性由名为Telepath的系统生成
  • 需要理解的主要部分是,有一个类用于告诉浏览器基于小部件渲染什么,默认情况下,它不会传递小部件属性
  • 下面的代码应该添加到wagtail_hooks.py,因为我们无论如何都需要该文件,而且我们知道它在运行时只运行一次
  • widget.render_html(是关键部分,我们正在使用**语法来解压任何widget.attrs值(其中一个是自定义块设置的accept项)

hooks.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-覆盖文档选择器的管理模板

  • 请查看Customising admin templates的文档,因为您可能需要为INSTALLED_APPS添加更多的应用程序来完成此步骤
  • 创建一个新文件myapp/templates/wagtaildocs/widgets/document_chooser.htmltemplates之后的部分在这里非常重要,因为我们想要覆盖和扩展这个精确的模板
  • 在模板中,我们将扩展原始块并覆盖块chooser_attributes,因为这是添加选择器模式触发器使用的data-chooser-url的部分
  • 重要提示:在这里重新启动dev服务器,因为您已经添加了一个新的模板覆盖
  • 完成后,在浏览器中检查包含“选择文档”按钮的元素,您应该能够看到容器元素现在有一个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\u 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并对其进行解析以生成一组不同的文档结果

摇尾钩.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,所以一定要注意摇摆代码的变化

使用^{}提供了更多定制选择器模式工作方式的能力

步骤1-安装wagtail-generic-chooser

  • 运行:pip install wagtail-generic-chooser
  • 然后将generic_chooser添加到项目的INSTALLED_APPS

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

  • 类似于setting up a Chooser view set上的文档说明
  • 确保我们可以通过创建扩展ModelChooserMixin的自定义类来处理accept参数,这意味着在搜索时仍会传递参数
  • 添加对acceptURL参数的处理,以有条件地筛选返回值
  • 设置一个扩展ModelChooserViewSet的类,该类将处理模式中Document列表的显示

基本/视图.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
  • Setting up of a model based Widget类似,创建一个扩展AdminChooser的类
  • __init__方法中,我们拉出acceptkwarg,以便使用它生成自定义URL参数
  • 重写get_edit_item_url方法,该方法允许单击所选的文档进行编辑
  • 覆盖“get\u choose\u modal\u urlto append the URL query param (note: I could not getreverse`working here而不产生更多的争论)

base/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钩子中注册选择器视图集

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

底座/摇尾钩.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
  • 这个块可以通过调用类似于任何其他块的方法来使用,但使用kwargacceptdoc_block = RestrictedDocumentChooserBlock(accept="svg,md")

基本/块.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)

相关问题 更多 >