在 Wagtail 中隐藏 `***Block`

Hide `***Block` in Wagtail

在 Wagtail 中,我制作了一个带有 ImageChooserBlock 的块,如下所示:

class MyBlock(blocks.StructBlock):
    background = ImageChooserBlock()

现在我想向 ImageChooserBlock 添加一些额外的字段,所以我将它移到了它自己的块中,所以现在它看起来像这样:

class FancyImageChooserBlock(ImageChooserBlock):
    extra = blocks.Charfield()

class MyBlock(blocks.StructBlock):
    background = FancyImageChooserBlock()

我的第一个问题是 extra 字段未包含在内。 (也许是因为该块继承自 ImageChooserBlock?

我的第二个也是最重要的问题是我希望能够将额外的字段隐藏在表单中,但包含在模板呈现中。有人知道如何以及是否可能吗?我不想为此做任何注入 js 或 css 的骇人听闻的事情。必须有一种方法可以使用 BlocksWidgetsforms.HiddenInput 或类似的东西来做到这一点。

我知道我可以在 FancyImageChooserBlockclean 方法中做一些计算来手动设置 extra 的值。这正是我想要做的。

感谢任何帮助,我真的被困在这里了。

关于你的第一个问题,为什么不直接:

class MyBlock(blocks.StructBlock):
    background = ImageChooserBlock()
    extra = blocks.Charfield()

?

ImageBlockChooser 与 'StructBlock' 或 'ListBlock' 或 'StreamBlock' 不同,它们都是 structural block types - 旨在 'look for' 任何子字段你可能会定义。只为 'out of the box' 准备了结构块类型。对于要使用字段的块,需要将其配置为 use/generate 具有这些字段的模板。

就个人而言,我认为有比子类化 ImageChooser 更好的方法来实现您想要的,因为尝试和找到使用 Wagtail 提供的功能的方法通常会更健壮,即使您必须有点和他们一起创作。

但是,如果您仍然想知道如何通过子类化 ImageChooser 来完成(破解)它:

解决方案 1 - 子类化 ImageChooser(这不是我的首选):

#the chooser block class:
class MyImageChooserBlock(ImageChooserBlock):

  @cached_property
  def widget(self):
    from .mywidgetsfolder import MyAdminImageChooser
    return MyAdminImageChooser

from wagtail.admin.widgets import AdminChooser
from wagtail.images import get_image_model

#the chooser admin class...
class MyAdminImageChooser(AdminChooser):

  """the only difference between this class and AdminImageChooser
  is that this one provides a different template value to 
  render in the render_to_string method and the addition of certain 
  variables to the attrs dictionary. You could probably get away 
  with subclassing AdminImageChooser instead of AdminChooser and just 
  overriding the render_html method, but for some reason that seemed 
  to give me a duplicate 'choose image' button and it didn't seem 
  essential to fix it to demonstrate this principle"""

  choose_one_text = _('Choose an image')
  choose_another_text = _('Change image')
  link_to_chosen_text = _('Edit this image')

  def __init__(self, **kwargs):
    super().__init__(**kwargs)
    self.image_model = get_image_model()

  def render_html(self, name, value, attrs):
    instance, value = self.get_instance_and_id(self.image_model, 
    value)
    attrs['extra_hidden_fields'] = ('extra_1', 'extra_2')
    original_field_html = super().render_html(name, value, attrs)
    return render_to_string("my-widgets-folder/my_image_chooser.html", {
        'widget': self,
        'original_field_html': original_field_html,
        'attrs': attrs,
        'value': value,
        'image': instance,
    })

  def render_js_init(self, id_, name, value):
    return "createImageChooser({0});".format(json.dumps(id_))

#my-widgets-folder/my_image_chooser.html template:
{% extends "wagtailadmin/widgets/chooser.html" %}
{% load wagtailimages_tags %}
{% block chooser_class %}image-chooser{% endblock %}
{% block chosen_state_view %}
  {% for a in attrs.extra_hidden_fields %}
    <input type="hidden", name={{a}}, value="">
  {% endfor %}
  <div class="preview-image">
    {% if image %}
        {% image image max-300x300 class="show-transparency" %}
    {% else %}
        <img>
    {% endif %}
 </div>
{% endblock %}
{% block edit_chosen_item_url %}{% if image %}{% url 'wagtailimages:edit' image.id %}{% endif %}{% endblock %}

解决方案 2 - 使用自定义结构块和组元值:

  • 此解决方案使用自定义结构块来隐藏字段和标签。我现在推荐这种方法,因为它使用 wagtail 提供的自定义功能供您使用。尽管文档没有提到以这种方式使用组元,但组元已记录在案并且应该可以依赖(如果需要,它可能很容易被其他细节替换)。
  • 对于遇到此答案的任何人,我建议在使用它之前检查 wagtail 文档,因为该项目的开发似乎很快,如果他们提供 'built in' 方式,我一点也不会感到惊讶即将生成隐藏字段。
  • 在我自己的测试中,我没有得到 OP 在他的评论中提到的缩进。只要结构本身是顶级元素,默认情况下所有子元素都左对齐 - 因此它们与结构块之外的任何字段对齐。
  • 当然你可以创建基本类型的自定义块(比如自定义 CharBlock)并将小部件 kwarg 指定为 forms.HiddenInput 但你仍然需要处理标签 - 并且传递类名 kwarg 仅适用它到输入而不是标签等。使用自定义基本块意味着永远保留它们或 providing deconstruct methods to avoid migration trouble。这避免了所有这些问题。
  • 当然,任何这些都可以通过一些 JS/css 轻松实现,但这是假设我们想要一个仅 html 的解决方案。

    class StructWithHiddenFields(StructBlock):
      classMeta:
        form_template = "blocks/admin/struct_with_hidden_fields.html"
    
    """Obviously you'd want to copy the template from the wagtail one 
    for StructBlocks (wagtailadmin/block_forms/struct.html) to ensure 
    similar behaviour and then add a bit of logic for the hiding.  
    This might look like this:"""  
    
    #blocks/admin/struct_with_hidden_fields.html template:
    
    <div class="{{ classname }}">
      {% if help_text %}
       <div class="sequence-member__help help"><span class="icon- 
         help-inverse" aria-hidden="true"></span>{{ help_text }} 
       </div>
      {% endif %}
    
      <ul class="fields">
        {% for child in children.values %}
          {% if child.block.meta.group != "hidden-input" %}
            <li{% if child.block.required %} class="required"{% endif %}>
            {% if child.block.label %}
                <label{% if child.id_for_label %} for="{{ child.id_for_label }}"{% endif %}>{{ child.block.label }}:</label>
            {% endif %}
            {{ child.render_form }}
            </li>
         {% endif %}
        {% endfor %}
      </ul>
      {% for child in children.values %}
        {% if child.block.meta.group == "hidden-input" %}
           <input type="hidden" id="{{ prefix }}-{{child.block.label}}" name="{{ prefix }}-{{child.block.label}}" value="{{child.block.value}}">
        {% endif %}
      {% endfor %}
    </div>
    
    #Usage:
    
    class MySpecificBlockWithHiddenFields(StructWithHiddenFields):
      normal_field = CharBlock(required=False)
      hidden_field = IntegerBlock(required=False, group="hidden-input")
    

模型中的甜蜜解

这个答案并不是问题的真正答案,而是在尝试向背景上的 ImageChooser 添加一些字段时更好的选择。从 Wagtail 文档中了解到,有一个叫做 Custom Image Model

的东西

因此,我没有尝试在 Block“层”上添加字段,而是在 Model 上添加了它们。对我来说,代码看起来像这样:

class ImageModel(AbstractImage):
    extra = models.CharField(max_length=255, blank=True, null=True)

    admin_form_fields = Image.admin_form_fields # So that in the image edit page, the fields are shown

    def save(self, **kwargs):
        self.clean_extra()
        return super(ImageModel, self).save(**kwargs)

    def clean_extra(self):
        if something():
            extra = 'This gets added as an attribute of image'

# Needed for the rendition relation
class ImageRenditionModel(AbstractRendition):
    image = models.ForeignKey(ImageModel, related_name='renditions')

    class Meta:
        unique_together = (
            ('image', 'filter_spec', 'focal_point_key'),
        )

此外,WAGTAILIMAGES_IMAGE_MODEL 必须指向您自己的模型,更多信息您可以在文档中找到。

块中的一个非常 hacky 的解决方案

还有另一种方法可以做到这一点,无需使用额外的 html 或额外的模型,但它非常 hacky 并且 disapproved.

class FancyImageChooserBlock(ImageChooserBlock):

    def clean_extra(self, value):
        if something():
            value['extra'] = 'This will get rendered in template'
        return value

    # for rendering in preview
    def clean(self, value):
        value = super(FancyImageChooserBlock, self).clean(value)
        value = self.clean_extra(value)
        return value

    # for rendering the live view
    def to_python(self, value):
        value = super(FancyImageChooserBlock, self).to_python(value)
        value = self.clean_extra(value)
        return value

这样您在添加额外值时就不需要额外的 html, css or js 了。 disencouraged 的原因是因为它占用了大量的 UX 性能并且还覆盖 to_python 函数和 clean 函数以注入额外的变量是 非常,非常hacky和肮脏。但它确实有效,所以如果您不介意设计标准或性能,独自工作并且没有其他人会看到您的代码,您可以使用它。

所以不要..