在Wagtail中隐藏`*** Block`

时间:2018-03-28 08:32:59

标签: python django wagtail

在Wagtail中,我在其中创建了一个ImageChooserBlock的块,如下所示:

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

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

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

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

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

我的第二个也是最重要的问题是我希望能够在表单中隐藏额外字段,但包含在模板渲染中。有人知道如何以及如果可能吗?我不想做任何为此注入js或css的hacky东西。必须有一种方法可以使用BlocksWidgetsforms.HiddenInput或类似的方式执行此操作。

我知道我可以在clean的{​​{1}}方法中进行一些计算,以手动设置FancyImageChooserBlock的值。这正是我想要做的。

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

3 个答案:

答案 0 :(得分:2)

ImageBlockChooser不喜欢' StructBlock'或者' ListBlock'或者' StreamBlock'这些都是structural block types - 这是为了寻找'您可能定义的任何子字段。只为结构块类型准备了开箱即用的#39;。对于要使用字段的块,需要将其配置为使用/生成具有这些字段的模板。

就我个人而言,我认为有更好的方法来实现你想要的东西而不是子类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 - 使用自定义结构块和组元值:

  • 此解决方案使用自定义结构块来隐藏字段和标签。我现在推荐这种方法,因为它使用wigtail提供的自定义功能供您使用。虽然文档没有提到以这种方式使用组元,但是组元文档已被记录并且应该可以依赖(如果需要,它可能很容易被另一个细节替换)。
  • 对于在这个问题上发生任何事情的人,我建议在使用之前检查一下令人讨厌的文档,因为项目的开发似乎很快,如果他们提供了一个&#39;那么我就不会感到惊讶。内置&#39;很快就会产生隐藏字段的方式。
  • 在我自己的测试中,我没有在评论中得到OP提到的缩进。只要struct本身是顶级元素,所有子节点都默认保持对齐 - 所以它们与struct block之外的任何字段对齐。
  • 当然你可以创建基本类型的自定义块(比如一个自定义的CharBlock)并将widget kwarg指定为forms.HiddenInput但你仍然有标签要处理 - 并且传递一个classname 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")
    

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

模型中的甜解

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

的东西

因此,我没有尝试在Block&#34;图层&#34;上添加字段,而是将其添加到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 被拒绝

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 的原因是因为它占用了很多用户体验的性能,并且还覆盖了to_python函数和clean函数来注入额外的变量非常,非常hacky和脏可能。但是它有效,所以如果你不介意设计标准或性能,单独工作,没有其他人永远看不到你的代码,你可以使用它。

所以不要......