在 Wagtail Steamfield 中限制特定 DocumentChooserBlock() 块的文件类型
Limiting file types for a specific DocumentChooserBlock() Block in Wagtail Steamfield
我正在尝试限制 wagtail 流字段块内特定 DocumentChooserBlock 的查询结果。
我已经知道您可以使用 hooks 限制页面类型的 DocumentChooser 的文件类型,但我想避免在页面范围内限制可能的文件类型,以防其他 StreamField 块需要它们。
是否有任何可能的方法来实现我在这里想要实现的目标?
Wagtail 的 Chooser Modal 系统与普通的 Django 小部件(Class 用于呈现 html 字段内容)的工作方式略有不同,小部件本身主要呈现一个按钮 'Choose Document'然后该按钮触发一个模式,它是一个单独的视图和模板。
如您所述,construct_document_chooser_queryset
hook 可以限制这些模式中显示的结果,但只能访问正在查看的页面的请求对象,而不是用于触发该模式的 Widget。
有一种方法可以获得一些有限的所需功能,但它不适用于搜索结果,并且不会限制对该文件类型的任何其他上传。
第 1 步 - 创建自定义 DocumentChooserBlock
- 这个块 Class 扩展了
DocumentChooserBlock
并且有一个自定义的 __init__
方法可以提取一个 kwarg accept
并将其分配给小部件属性。
- Django Widgets 都具有 accept
attrs
的能力,这些都在呈现的 HTML 元素上输出,我们的自定义 Block 将我们想要的值分配给小部件,以便其他方法可以访问它.
- 这个块可以像任何其他块一样使用,但会使用 accept'
doc_block = SpecificDocumentChooserBlock(accept="svg,md") # uses accept kwarg
- 您可以通过查看 DOM(在浏览器中检查元素)来确认这是有效的,就在 'Choose a Document' 之后,会有一个隐藏属性,类似于
<input type="hidden" name="body-2-value" accept="svg,md" id="body-2-value" value="">
blocks.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 上,参见
data-chooser-url
document-chooser
区块 div.
- 此数据属性由名为 Telepath 的系统生成。
- 要理解的主要部分是有一个 Class 用于告诉浏览器根据 Widget 呈现什么,默认情况下,这不会传递 widget 属性。
- 应将下面的代码添加到
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.html
,templates
之后的部分在这里很关键,因为我们要覆盖和扩展这个确切的模板。
- 在模板中,我们将扩展原始内容并覆盖块
chooser_attributes
,因为这是添加 Chooser Modal 触发器使用的 data-chooser-url
的内容。
- 重要提示:在此处重新启动您的开发服务器,因为您添加了新的模板覆盖。
- 完成后,在浏览器中检查包含 'Choose a Document' 按钮的元素,您应该能够看到容器元素现在有一个
data-chooser-url
,并在 [=140= 中添加了一个查询字符串] <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 代码更改。
使用 wagtail-generic-chooser
提供更多自定义选择器模式工作方式的能力。
第 1 步 - 安装 wagtail-generic-chooser
- 运行:
pip install wagtail-generic-chooser
- 然后将
generic_chooser
添加到您项目的 INSTALLED_APPS
。
第 2 步 - 设置选择器视图集
- 类似于 setting up a Chooser view set
上的文档说明
- 确保我们可以通过创建扩展
ModelChooserMixin
的自定义 class 来处理 accept
参数,这意味着搜索时仍会传递该参数。
- 添加
accept
URL 参数的处理以有条件地过滤返回值。
- 设置一个扩展
ModelChooserViewSet
的 class,它将处理 Document
列表在模式中的显示。
base/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
。
- 类似于 Setting up of a model based Widget 创建一个扩展
AdminChooser
的 class。
- 在
__init__
方法中,我们取出 accept
kwarg,以便我们可以使用它来生成自定义 URL 参数。
- 重写
get_edit_item_url
方法,该方法允许单击 选定的 文档进行编辑。
- 覆盖在这里工作的``get_choose_modal_url
to append the URL query param (note: I could not get
reverse`,没有更多的争论。
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 Hooks 中注册选择器视图集
- 这里不需要使用
construct_document_chooser_queryset
,而是使用钩子register_admin_viewset
并注册RestrictedDocumentChooserViewSet
。
base/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
- 此 class 扩展
ChooserBlock
并包装已创建的 RestrictedDocumentChooser
小部件。
- 在
__init__
上,相同的 kwarg accept
被拉出并在创建时传递给 RestrictedDocumentChooser
。
- 这个块可以通过调用它类似于任何其他块来使用,但是使用 kwarg
accept
。 doc_block = RestrictedDocumentChooserBlock(accept="svg,md")
base/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)
我正在尝试限制 wagtail 流字段块内特定 DocumentChooserBlock 的查询结果。
我已经知道您可以使用 hooks 限制页面类型的 DocumentChooser 的文件类型,但我想避免在页面范围内限制可能的文件类型,以防其他 StreamField 块需要它们。
是否有任何可能的方法来实现我在这里想要实现的目标?
Wagtail 的 Chooser Modal 系统与普通的 Django 小部件(Class 用于呈现 html 字段内容)的工作方式略有不同,小部件本身主要呈现一个按钮 'Choose Document'然后该按钮触发一个模式,它是一个单独的视图和模板。
如您所述,construct_document_chooser_queryset
hook 可以限制这些模式中显示的结果,但只能访问正在查看的页面的请求对象,而不是用于触发该模式的 Widget。
有一种方法可以获得一些有限的所需功能,但它不适用于搜索结果,并且不会限制对该文件类型的任何其他上传。
第 1 步 - 创建自定义 DocumentChooserBlock
- 这个块 Class 扩展了
DocumentChooserBlock
并且有一个自定义的__init__
方法可以提取一个 kwargaccept
并将其分配给小部件属性。 - Django Widgets 都具有 accept
attrs
的能力,这些都在呈现的 HTML 元素上输出,我们的自定义 Block 将我们想要的值分配给小部件,以便其他方法可以访问它. - 这个块可以像任何其他块一样使用,但会使用 accept'
doc_block = SpecificDocumentChooserBlock(accept="svg,md") # uses accept kwarg
- 您可以通过查看 DOM(在浏览器中检查元素)来确认这是有效的,就在 'Choose a Document' 之后,会有一个隐藏属性,类似于
<input type="hidden" name="body-2-value" accept="svg,md" id="body-2-value" value="">
blocks.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 上,参见
data-chooser-url
document-chooser
区块 div. - 此数据属性由名为 Telepath 的系统生成。
- 要理解的主要部分是有一个 Class 用于告诉浏览器根据 Widget 呈现什么,默认情况下,这不会传递 widget 属性。
- 应将下面的代码添加到
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.html
,templates
之后的部分在这里很关键,因为我们要覆盖和扩展这个确切的模板。 - 在模板中,我们将扩展原始内容并覆盖块
chooser_attributes
,因为这是添加 Chooser Modal 触发器使用的data-chooser-url
的内容。 - 重要提示:在此处重新启动您的开发服务器,因为您添加了新的模板覆盖。
- 完成后,在浏览器中检查包含 'Choose a Document' 按钮的元素,您应该能够看到容器元素现在有一个
data-chooser-url
,并在 [=140= 中添加了一个查询字符串]<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 代码更改。
使用 wagtail-generic-chooser
提供更多自定义选择器模式工作方式的能力。
第 1 步 - 安装 wagtail-generic-chooser
- 运行:
pip install wagtail-generic-chooser
- 然后将
generic_chooser
添加到您项目的INSTALLED_APPS
。
第 2 步 - 设置选择器视图集
- 类似于 setting up a Chooser view set 上的文档说明
- 确保我们可以通过创建扩展
ModelChooserMixin
的自定义 class 来处理accept
参数,这意味着搜索时仍会传递该参数。 - 添加
accept
URL 参数的处理以有条件地过滤返回值。 - 设置一个扩展
ModelChooserViewSet
的 class,它将处理Document
列表在模式中的显示。
base/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
。 - 类似于 Setting up of a model based Widget 创建一个扩展
AdminChooser
的 class。 - 在
__init__
方法中,我们取出accept
kwarg,以便我们可以使用它来生成自定义 URL 参数。 - 重写
get_edit_item_url
方法,该方法允许单击 选定的 文档进行编辑。 - 覆盖在这里工作的``get_choose_modal_url
to append the URL query param (note: I could not get
reverse`,没有更多的争论。
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 Hooks 中注册选择器视图集
- 这里不需要使用
construct_document_chooser_queryset
,而是使用钩子register_admin_viewset
并注册RestrictedDocumentChooserViewSet
。
base/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
- 此 class 扩展
ChooserBlock
并包装已创建的RestrictedDocumentChooser
小部件。 - 在
__init__
上,相同的 kwargaccept
被拉出并在创建时传递给RestrictedDocumentChooser
。 - 这个块可以通过调用它类似于任何其他块来使用,但是使用 kwarg
accept
。doc_block = RestrictedDocumentChooserBlock(accept="svg,md")
base/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)