.. _streamfield: 使用 StreamField 实现页面灵活定制 ======================================= StreamField 适合构建无固定结构的编辑模型,例如博客或新闻,这些页面正文经常是小标题、图片、引用及图像交替重复出现。也可以用于更特别的一些内容,例如图表、程序代码片段等。 在这种类型中,不同内容类型是以顺序'块(block)'的形式展示,但展示过程并无规律。 关于 StreamField 的更多背景需求,以及在文章主体中为什么应该使用它代替富文本编辑, 请参考博客 `Rich text fields and faster horses `__. StreamField 也提供了丰富的 API 接口给开发者定制块类型,从简单子块集成 (例如一个 'person' 块包含姓、名及图片) 到复杂的拥有自身编辑界面的组件。 在数据库中,StreamField 的内容存贮为 JSON 字符串, 确保字段信息被保留,而不仅是 HTML 的展示内容。 使用 StreamField ----------------- ``StreamField`` 是模型的字段类型,定义模型时同使用其它字段类型的方法一致: .. code-block:: python from django.db import models from wagtail.core.models import Page from wagtail.core.fields import StreamField from wagtail.core import blocks from wagtail.admin.edit_handlers import FieldPanel, StreamFieldPanel from wagtail.images.blocks import ImageChooserBlock class BlogPage(Page): author = models.CharField(max_length=255) date = models.DateField("Post date") body = StreamField([ ('heading', blocks.CharBlock(form_classname="full title")), ('paragraph', blocks.RichTextBlock()), ('image', ImageChooserBlock()), ]) content_panels = Page.content_panels + [ FieldPanel('author'), FieldPanel('date'), StreamFieldPanel('body'), ] 注意: StreamField 并不向后兼容其它字段类型,例如 RichTextField 类型。如果需要改变模型的字段类型为 StreamField, 请参考 :ref:`streamfield_migrating_richtext`。 ``StreamField`` 是 ``(name, block_type)`` 元组的列表。 'name' 用于在模型及 JSON 串中识别一个块 (命名应遵循 Python 变更命名规范: 使用小写字母及下划线,不能有空格) 'block_type' 应是块定义对象,参见如下说明。(有时, ``StreamField`` 也可以使用一个 ``StreamBlock`` 实例做为参数 - 参考 `Structural block types`_ 。) 这个列表定义了编辑字段时可以使用的块类型,页面作者可使用这个块任意多次,顺序也是根据需要任意设置(这里体现了'流'布局)。 ``StreamField`` 也有可选关键字参数 ``blank``, 默认为 false; 当设为 false 时,至少应该入一个块内容。 基本块类型 ----------------- 所有块类型接受以下关键字参数: ``default`` 缺省值,新建空'块'时默认值。 ``label`` 提示标识,在编辑界面中引用块时的提示信息。缺省是块名可读版本。 ``icon`` 图标,在编辑菜单中标识块的图标。可用的图标名,参考 Wagtail 样式指南,可以增加 ``wagtail.contrib.styleguide`` 到项目配置的 ``INSTALLED_APPS`` 设置中。 ``template`` 模板文件,指定渲染这个块内容所使用的模板文件。参考 `Template rendering`_ 。 ``group`` 分组,说明块所在分类分组,同类分组的块将在编辑界面上集中显示在组名下面。 Wagtail 提供基本块类型有: CharBlock ~~~~~~~~~ ``wagtail.core.blocks.CharBlock`` 单行文本输入。可跟如下关键字参数: ``required`` (缺省: True) 设定为真时,字段必须输入。 ``max_length``, ``min_length`` 设定输入字符串最短和最长长度。 ``help_text`` 在字段输入旁边的帮助提示内容。 ``validators`` 字段校验器函数列表 (参考 `Django Validators `__)。 ``form_classname`` 定义在页面编辑表单输出时增加的表单样式( ``class`` )属性。 .. versionchanged:: 2.11 ``class`` 属性之前通过关键字参数 ``classname`` 设置。 TextBlock ~~~~~~~~~ ``wagtail.core.blocks.TextBlock`` 多行文本输入。 参数可使用 ``CharBlock`` 中的 ``required`` (缺省: True), ``max_length``, ``min_length``, ``help_text``, ``validators`` 和 ``form_classname`` 。 EmailBlock ~~~~~~~~~~ ``wagtail.core.blocks.EmailBlock`` 单行邮件输入,使用邮件地址校验器保证邮件地址输入正确。可使用关键字参数 ``required`` (缺省: True), ``help_text``, ``validators`` 和 ``form_classname`` 。 使用 ``EmailBlock`` 的例子请参考 :ref:`streamfield_personblock_example` IntegerBlock ~~~~~~~~~~~~ ``wagtail.core.blocks.IntegerBlock`` 单行整数输入,使用校验器保证输入值为整数。 可使用关键字参数 ``required`` (缺省: True), ``max_value``, ``min_value``, ``help_text``, ``validators`` 和 ``form_classname`` 。 使用 ``IntegerBlock`` 的例子请参考 :ref:`streamfield_personblock_example` FloatBlock ~~~~~~~~~~ ``wagtail.core.blocks.FloatBlock`` 单行浮点数输入,使用校验器保证输入值为浮点数。 可使用关键字参数 ``required`` (缺省: True), ``max_value``, ``min_value``, ``validators`` 和 ``form_classname`` 。 DecimalBlock ~~~~~~~~~~~~ ``wagtail.core.blocks.DecimalBlock`` 单行十进制数字,使用校验器保证输入值为十进制数字。 可使用关键字参数 ``required`` (缺省: True), ``help_text``, ``max_value``, ``min_value``, ``max_digits``, ``decimal_places``, ``validators`` 和 ``form_classname`` 。 使用 ``DecimalBlock`` 的例子请参考 :ref:`streamfield_personblock_example` RegexBlock ~~~~~~~~~~ ``wagtail.core.blocks.RegexBlock`` 单行文件输入,要求输入内容符合正则表达式的要求。正则表达式必需是第一个参数,或者使用关键字参数 ``regex`` 指定。校验错误时,提示信息可以传递一个字典给关键字 ``error_messages``,字典的键名可以是 ``required`` (当前输入内容不能为空时) 或 ``invalid`` (当输入值不满足正则表达式要求时): .. code-block:: python blocks.RegexBlock(regex=r'^[0-9]{3}$', error_messages={ 'invalid': "Not a valid library card number." }) 可使用关键字参数 ``regex``, ``error_messages``, ``help_text``, ``required`` (缺省: True), ``max_length``, ``min_length``, ``validators`` 和 ``form_classname`` 。 URLBlock ~~~~~~~~ ``wagtail.core.blocks.URLBlock`` 单行文本输入,要求输入内容是一个有效的 URL. 可使用关键字参数 ``required`` (缺省: True), ``max_length``, ``min_length``, ``help_text``, ``validators`` 和 ``form_classname`` 。 BooleanBlock ~~~~~~~~~~~~ ``wagtail.core.blocks.BooleanBlock`` 复选框输入。可使用关键字参数 ``required``, ``help_text`` 和 ``form_classname`` 。 As with Django's ``BooleanField``, a value of ``required=True`` (the default) indicates that the checkbox must be ticked in order to proceed. For a checkbox that can be ticked or unticked, you must explicitly pass in ``required=False``. DateBlock ~~~~~~~~~ ``wagtail.core.blocks.DateBlock`` 日期选择器输入。可使用关键字参数 ``required`` (缺省: True), ``help_text``, ``validators``, ``form_classname`` 和 ``format`` 。 ``format`` (缺省: None) 日期格式。格式定义应满足 `DATE_INPUT_FORMATS `_ 设置。 如果没有设置,则 Wagtail 将使用 ``WAGTAIL_DATE_FORMAT`` 设置格式,都未设定的情况下使用 '%Y-%m-%d'。 TimeBlock ~~~~~~~~~ ``wagtail.core.blocks.TimeBlock`` 时间选择器输入。 可使用关键字参数 ``required`` (缺省: True), ``help_text``, ``validators`` 和 ``form_classname`` 。 DateTimeBlock ~~~~~~~~~~~~~ ``wagtail.core.blocks.DateTimeBlock`` 日期时间选择器输入。 可使用关键字参数 ``required`` (缺省: True), ``help_text``, ``format``, ``validators`` 和 ``form_classname`` 。 ``format`` (default: None) 日期时间格式。格式定义应满足 `DATETIME_INPUT_FORMATS `_ 设置。 如果没有设置,则 Wagtail 将使用 ``WAGTAIL_DATETIME_FORMAT`` 设置格式,都未设定的情况下使用 '%Y-%m-%d %H:%M'。 RichTextBlock ~~~~~~~~~~~~~ ``wagtail.core.blocks.RichTextBlock`` 富文本编辑器输入,可生成包括链接、粗/斜体等格式化正文。 可使用关键字参数 ``required`` (缺省: True), ``help_text``, ``validators``, ``form_classname``, ``editor`` 和 ``features`` 。 ``editor`` (缺省: ``default``) 使用的富文本编辑器 (参考 :ref:`WAGTAILADMIN_RICH_TEXT_EDITORS`)。 ``features`` (缺省: None) 设定编辑器的参数 (参考 :ref:`rich_text_features`)。 RawHTMLBlock ~~~~~~~~~~~~ ``wagtail.core.blocks.RawHTMLBlock`` 多行文本编辑器输入。输入的内容为 HTML 文本,在页面输出时可以不转义输出。 可使用关键字参数 ``required`` (缺省: True), ``max_length``, ``min_length``, ``help_text``, ``validators`` 和 ``form_classname`` 。 .. WARNING:: 使用这个编辑器是输入的 HTML 中可以使用程序脚本, Wagtail 并不做安全性判断,脚本有可能会获得管理员权限,因此只有编辑人员是完全可信的人员才能考虑使用这个选项。 BlockQuoteBlock ~~~~~~~~~~~~~~~ ``wagtail.core.blocks.BlockQuoteBlock`` 文本输入,输入内容会包含在 HTML `
` 标签对里面。 可使用关键字参数 ``required`` (缺省: True), ``max_length``, ``min_length``, ``help_text``, ``validators`` 和 ``form_classname`` 。 ChoiceBlock ~~~~~~~~~~~ ``wagtail.core.blocks.ChoiceBlock`` 下拉选择框,可使用关键字参数 ``choices`` 选项列表,以 Django :attr:`~django.db.models.Field.choices` 可接受的格式,或者一个可调用的方法返回结果满足格式要求。 ``required`` (缺省: True) 为 true 时, 字段选择不能是空白。 ``help_text`` 在字段输入旁边的帮助提示内容。 ``validators`` 字段校验器函数列表 (参考 `Django Validators `__)。 ``form_classname`` 定义在页面编辑表单输出时增加的表单样式( ``class`` )属性。 ``widget`` 渲染表单字段时使用的组件 (参考 `Django Widgets `__)。 ``ChoiceBlock`` 也可以定义为一个子类,以方便在多个地方引用。例如,如下定义的块: .. code-block:: python blocks.ChoiceBlock(choices=[ ('tea', 'Tea'), ('coffee', 'Coffee'), ], icon='cup') 也可以重写成 ChoiceBlock 的子类: .. code-block:: python class DrinksChoiceBlock(blocks.ChoiceBlock): choices = [ ('tea', 'Tea'), ('coffee', 'Coffee'), ] class Meta: icon = 'cup' ``StreamField`` 定义``ChoiceBlock`` 时使用 ``DrinksChoiceBlock()`` 即可。 注意,这只在 ``choices`` 是固定列表时有效,不能是可调用的方法。 .. _streamfield_multiplechoiceblock: MultipleChoiceBlock ~~~~~~~~~~~~~~~~~~~ ``wagtail.core.blocks.MultipleChoiceBlock`` 多项选择框,可使用关键字参数有: ``choices`` 选项列表,以 Django :attr:`~django.db.models.Field.choices` 可接受的格式,或者一个可调用的方法返回结果满足格式要求。 ``required`` (缺省: True) 为 true 时, 字段选择不能是空白。 ``help_text`` 在字段输入旁边的帮助提示内容。 ``validators`` 字段校验器函数列表 (参考 `Django Validators `__)。 ``form_classname`` 定义在页面编辑表单输出时增加的表单样式( ``class`` )属性。 ``widget`` 渲染表单字段时使用的组件 (参考 `Django Widgets `__)。 PageChooserBlock ~~~~~~~~~~~~~~~~ ``wagtail.core.blocks.PageChooserBlock`` 页面选择组件,使用 Wagtail 浏览器进行页面选择。可使用关键字参数有: ``required`` (缺省: True) 为 true 时, 不能不选择页面。 ``page_type`` (缺省: Page) 设定选择指定类型的页面。可以是一个模型类、模型名(字符串),或者是它们的列表。 ``can_choose_root`` (缺省: False) 可选择根页面。一个情况下,根页面是从不使用的,但在一些特定的场景是有意义的。例如使用 PageChooserBlock 提供一个子页面集合的起点,使用根结点就表示可以是'任可页面'。 DocumentChooserBlock ~~~~~~~~~~~~~~~~~~~~ ``wagtail.documents.blocks.DocumentChooserBlock`` 文档选择组件,使用 Wagtail 文档浏览器进行文档选择,可以在选择时上传文档。可使用关键字参数只有 ``required`` (缺省: True)。 ImageChooserBlock ~~~~~~~~~~~~~~~~~ ``wagtail.images.blocks.ImageChooserBlock`` 图片选择组件,使用 Wagtail 图片浏览器进行图片选择,可以在选择时上传图片。可使用关键字参数只有 ``required`` (缺省: True)。 SnippetChooserBlock ~~~~~~~~~~~~~~~~~~~ ``wagtail.snippets.blocks.SnippetChooserBlock`` 片段选择组件,用于选择一个片段,需要一个位置参数:片段类,表示片段从哪个类中取。可使用关键字参数只有 ``required`` (缺省: True)。 EmbedBlock ~~~~~~~~~~ ``wagtail.embeds.blocks.EmbedBlock`` 内嵌对象编辑器,输入一个需要嵌入页面的多媒体对象的 URL (例如,一个云视频)。可使用关键字参数 ``required`` (缺省: True), ``max_length``, ``min_length`` 和 ``help_text`` 。 .. _streamfield_staticblock: StaticBlock ~~~~~~~~~~~ ``wagtail.core.blocks.StaticBlock`` 不包括任何字段内容,只是给模板传递一个特殊值。这在只想传递一个静态不需要编辑数值给编辑器时有用,例如一个地址、一个第三方的服务参数或一段较复杂代码的模板。 默认情况下, 缺省文本 (包含 ``label`` 位置关键字传入的内容) 将显示在编辑界面上,看起来块不是空的。也可以使用 ``admin_text`` 位置关键字来定义显示的内容: .. code-block:: python blocks.StaticBlock( admin_text='Latest posts: no configuration needed.', # or admin_text=mark_safe('Latest posts: no configuration needed.'), template='latest_posts.html') ``StaticBlock`` 也可定义成子类,以便在多处引用: .. code-block:: python class LatestPostsStaticBlock(blocks.StaticBlock): class Meta: icon = 'user' label = 'Latest posts' admin_text = '{label}: configured elsewhere'.format(label=label) template = 'latest_posts.html' Structural block types ---------------------- 除了上述基础块类型外,还可以定义结构化的块类型。新的类型由子块构成: 例如 'person' 块可以由姓、名、头像构成,'轮播图' 由不定数量的图片块构成。结构块可以嵌套,深度不限,还可以使用包含列表的块以及块的列表。 StructBlock ~~~~~~~~~~~ ``wagtail.core.blocks.StructBlock`` 结构块由一组固定顺序的子块组成。使用 ``(name, block_definition)`` 元组列表做为其第一个参数: .. code-block:: python ('person', blocks.StructBlock([ ('first_name', blocks.CharBlock()), ('surname', blocks.CharBlock()), ('photo', ImageChooserBlock(required=False)), ('biography', blocks.RichTextBlock()), ], icon='user')) 另外也可以采用基于 StructBlock 子类的方法来实现上面功能: .. code-block:: python class PersonBlock(blocks.StructBlock): first_name = blocks.CharBlock() surname = blocks.CharBlock() photo = ImageChooserBlock(required=False) biography = blocks.RichTextBlock() class Meta: icon = 'user' ``Meta`` 类支持属性包括 ``default``, ``label``, ``icon`` 和 ``template``, 和传递给块构造函数的含义相同。 这里定义 ``PersonBlock()`` 做为块类型,可以在多个模型定义中进行复用。 .. code-block:: python body = StreamField([ ('heading', blocks.CharBlock(form_classname="full title")), ('paragraph', blocks.RichTextBlock()), ('image', ImageChooserBlock()), ('person', PersonBlock()), ]) 定制 ``StructBlock`` 在页面编辑器的更多显示选项请参考 :ref:`custom_editing_interfaces_for_structblock` 。 在模板中使用 ``StructBlock`` 值的方法参考 :ref:`custom_value_class_for_structblock` 。 ListBlock ~~~~~~~~~ ``wagtail.core.blocks.ListBlock`` 列表块定义许多类型相同的子块。编辑器可以添加不同数量的子块,可以执行重新排序和删除子块的操作。将子块做为第一个参数来构造这个类实例: .. code-block:: python ('ingredients_list', blocks.ListBlock(blocks.CharBlock(label="Ingredient"))) 包括结构块在内的任何类型的块都可以做为列表块的子块。 .. code-block:: python ('ingredients_list', blocks.ListBlock(blocks.StructBlock([ ('ingredient', blocks.CharBlock()), ('amount', blocks.CharBlock(required=False)), ]))) 如果要改变 ``ListBlock`` 在编辑界面中显示风格,使用 ``form_classname`` 关键字参数来传递值给 ``ListBlock`` 构造函数: .. code-block:: python :emphasize-lines: 4 ('ingredients_list', blocks.ListBlock(blocks.StructBlock([ ('ingredient', blocks.CharBlock()), ('amount', blocks.CharBlock(required=False)), ]), form_classname='ingredients-list')) 实现中也可以将 ``form_classname`` 增加到 ``Meta`` 子类中: .. code-block:: python :emphasize-lines: 6 class IngredientsListBlock(blocks.ListBlock): ingredient = blocks.CharBlock() amount = blocks.CharBlock(required=False) class Meta: form_classname = 'ingredients-list' StreamBlock ~~~~~~~~~~~ ``wagtail.core.blocks.StreamBlock`` 流块由一系列不同类型子块构成,可以混合顺序使用,编辑时可重排显示顺序。 可以使用 StreamField 的全部机制,同时可以嵌入其它结构化的块类型中。使用 ``(name, block_definition)`` 元组列表做为第一个参数: .. code-block:: python ('carousel', blocks.StreamBlock( [ ('image', ImageChooserBlock()), ('quotation', blocks.StructBlock([ ('text', blocks.TextBlock()), ('author', blocks.CharBlock()), ])), ('video', EmbedBlock()), ], icon='cogs' )) 同 StructBlock 结合使用时, 子块列表也可以实现成 StreamBlock 的子类: .. code-block:: python class CarouselBlock(blocks.StreamBlock): image = ImageChooserBlock() quotation = blocks.StructBlock([ ('text', blocks.TextBlock()), ('author', blocks.CharBlock()), ]) video = EmbedBlock() class Meta: icon='cogs' 由于 ``StreamField`` 接受 ``StreamBlock`` 做为参数,在使用块类型列表的地方都可以使用 ``StreamBlock``,这样可重用一套块类型集: .. code-block:: python class HomePage(Page): carousel = StreamField(CarouselBlock(max_num=10, block_counts={'video': {'max_num': 2}})) ``StreamBlock`` 使用如下关键字参数或 ``Meta`` 类属性: ``required`` (缺省: True) 为 true 时至少编辑时至少应提供一个子块。这在使用 ``StreamBlock`` 做为 StreamField 最顶级块时忽略,在这种情况下使用 StreamField 字段的 ``blank`` 属性定义值。 ``min_num`` 字段中包含最少子区块数量。 ``max_num`` 字段中包含最多子区块数量。 ``block_counts`` 设定每种子类型块最少、最多数量,值为一个字典,字典里块名又映射到一个包含 ``min_num`` 和 ``max_num`` 字段的字典. ``form_classname`` 定义编辑器中 ``StreamBlock`` 使用的风格样式名。 .. code-block:: python :emphasize-lines: 4 ('event_promotions', blocks.StreamBlock([ ('hashtag', blocks.CharBlock()), ('post_date', blocks.DateBlock()), ], form_classname='event-promotions')) .. code-block:: python :emphasize-lines: 6 class EventPromotionsBlock(blocks.StreamBlock): hashtag = blocks.CharBlock() post_date = blocks.DateBlock() class Meta: form_classname = 'event-promotions' .. _streamfield_personblock_example: Example: ``PersonBlock`` ------------------------ 下面例子展示了如何基于 ``StructBlock`` 使用基础块类型生成复杂块类型 : .. code-block:: python from wagtail.core import blocks class PersonBlock(blocks.StructBlock): name = blocks.CharBlock() height = blocks.DecimalBlock() age = blocks.IntegerBlock() email = blocks.EmailBlock() class Meta: template = 'blocks/person_block.html' .. _streamfield_template_rendering: 模板渲染 ------------------ StreamField 提供了块的 HTML 显示方法,可以做为一个整体输出,也可以分块输出。 使用 ``{% include_block %}`` 标签将块 HTML 渲染结果插入到页面中: .. code-block:: html+django {% load wagtailcore_tags %} ... {% include_block page.body %} 在缺省渲染过程中,流字段中的每个块都包含在 ``
`` 元素中 (这里 ``my_block_name`` 是 StreamField 中定义的块名)。 如果需要使用自定义的 HTML 标记,可能遍历字段中各个值,然后对每个块使用 ``{% include_block %}`` 进行处理: .. code-block:: html+django {% load wagtailcore_tags %} ...
{% for block in page.body %}
{% include_block block %}
{% endfor %}
如需进一步控制特定块的渲染方法,每个块都提供了 ``block_type`` 和 ``value`` 属性,可以判断这些属性然后分别进行处理: .. code-block:: html+django {% load wagtailcore_tags %} ...
{% for block in page.body %} {% if block.block_type == 'heading' %}

{{ block.value }}

{% else %}
{% include_block block %}
{% endif %} {% endfor %}
默认情况下,每个块使用最简单的 HTML 标签修饰,或者根本不用。例如 CharBlock 值直接渲染成普通广西,ListBlock 输出子块时使用 `
    ` 修饰。 在定制这个 HTML 渲染过程,可以通过 ``template`` 参数将渲染模板文件名传递给块。这对基于 StructBlock 的块特别有用: .. code-block:: python ('person', blocks.StructBlock( [ ('first_name', blocks.CharBlock()), ('surname', blocks.CharBlock()), ('photo', ImageChooserBlock(required=False)), ('biography', blocks.RichTextBlock()), ], template='myapp/blocks/person.html', icon='user' )) 或者在定义 StructBlock 子类时: .. code-block:: python class PersonBlock(blocks.StructBlock): first_name = blocks.CharBlock() surname = blocks.CharBlock() photo = ImageChooserBlock(required=False) biography = blocks.RichTextBlock() class Meta: template = 'myapp/blocks/person.html' icon = 'user' 在模板中,块值通过变量 ``value`` 取出: .. code-block:: html+django {% load wagtailimages_tags %}
    {% image value.photo width-400 %}

    {{ value.first_name }} {{ value.surname }}

    {{ value.biography }}
    由于 ``first_name``, ``surname``, ``photo`` 和 ``biography`` 以各自方式定义,也可以写成: .. code-block:: html+django {% load wagtailcore_tags wagtailimages_tags %}
    {% image value.photo width-400 %}

    {% include_block value.first_name %} {% include_block value.surname %}

    {% include_block value.biography %}
    使用 ``{{ my_block }}`` 与 ``{% include_block my_block %}`` 语句是同样的意思,但限制更多,模板中的 ``request`` 或 ``page`` 没有绑定; 因此建议只对不生成 HTML 的简单值进行处理,例如 PersonBlock 使用的模板: .. code-block:: html+django {% load wagtailimages_tags %}
    {% image value.photo width-400 %}

    {{ value.first_name }} {{ value.surname }}

    {% if request.user.is_authenticated %} Contact this person {% endif %} {{ value.biography }}
    这里 ``request.user.is_authenticated`` 在通过 ``{{ ... }}`` 渲染时测试不正确: .. code-block:: html+django {# Incorrect: #} {% for block in page.body %} {% if block.block_type == 'person' %}
    {{ block }}
    {% endif %} {% endfor %} {# Correct: #} {% for block in page.body %} {% if block.block_type == 'person' %}
    {% include_block block %}
    {% endif %} {% endfor %} 和 Django 的 ``{% include %}`` 标签一样, ``{% include_block %}`` 允许使用类似于 ``{% include_block my_block with foo="bar" %}`` 的语法传递额外的变量给模板: .. code-block:: html+django {# In page template: #} {% for block in page.body %} {% if block.block_type == 'person' %} {% include_block block with classname="important" %} {% endif %} {% endfor %} {# In PersonBlock template: #}
    ...
    ``{% include_block my_block with foo="bar" only %}`` 的语法格式也是支持的,这里设置父模板中的变量只有 ``foo`` 传递给子模板。 .. _streamfield_get_context: 除了使用父模板传来变量,子类中也可以重写 ``get_context`` 方法来增加模板上下文的变量: .. code-block:: python import datetime class EventBlock(blocks.StructBlock): title = blocks.CharBlock() date = blocks.DateBlock() def get_context(self, value, parent_context=None): context = super().get_context(value, parent_context=parent_context) context['is_happening_today'] = (value['date'] == datetime.date.today()) return context class Meta: template = 'myapp/blocks/event.html' 在这个例子中,在模板中 ``is_happening_today`` 变更也是可以使用的。 当块通过 ``{% include_block %}`` 标签渲染模板时,关键字参数 ``parent_context`` 是可用的,其中存放者父模板中的上下文变量字典。 BoundBlock 与 value ---------------------- 全部块类型,不只是 StructBlock,都接受 ``template`` 参数来决定在页面上显示时如何渲染。但是负责处理 Python 基础数据类型的块,例如 ``CharBlock`` 和 ``IntegerBlock`` 在模板中应用时有一些限制, 这是因为这些 Python 内置类型(如 ``str``, ``int`` 等等) 并不能 '告知' 是否用在模板渲染中。可能通过一个例子来了解下这种情况: .. code-block:: python class HeadingBlock(blocks.CharBlock): class Meta: template = 'blocks/heading.html' 这里 ``blocks/heading.html`` 内容如下: .. code-block:: html+django

    {{ value }}

    这样就定义了一个类似文本字段的块,但输出时使用 ``

    `` 标签进行渲染: .. code-block:: python class BlogPage(Page): body = StreamField([ # ... ('heading', HeadingBlock()), # ... ]) .. code-block:: html+django {% load wagtailcore_tags %} {% for block in page.body %} {% if block.block_type == 'heading' %} {% include_block block %} {# This block will output its own

    ...

    tags. #} {% endif %} {% endfor %} 这种设计设想是值是一个普通的字符串,但在模板 include_block 引用时输出了带

    标签的字符串。这在 Python 中使用非常容易造成困扰,但在这个地方可以完成预想的工作, 原因是 block 是通过遍历 StreamField 获得的条目,并不是块的本身的值。遍历时获得的值是 ``BoundBlock`` 类的实例,包括了值和块的定义内容。跟踪一下块定义,``BoundBlock`` 是知道使用哪个模板渲染输出结果的。要获取本身编辑时输入的值,应使用 ``block.value`` 变量。所在在模板页面中使用 ``{% include_block block.value %}`` 就得到正常输入字符串,没有 ``

    `` 标签。 (严格说来,遍历 StreamField 字段获得的条目是 ``StreamChild`` 实例,包括 ``block_type`` 属性和 ``value`` 属性。) 有 Django 开发经验的开发者可以参考 Django Form 框架中的 ``BoundField`` 类来理解 ``BoundBlock``,``BoundField`` 也是包含了表单字段以及在表单上如何将值渲染到 HTML 表单的定义。 正常情况下,开发时不需要担心这些内部细节,Wagtail 使用模板时会处理这些问题。但在复杂的设计中,例如访问 ``ListBlock`` 或 ``StructBlock`` 中的块时,这种情况下没有 ``BoundBlock`` 这层处理, 所以其中的条目不知道是在做模板渲染。下面例子,``HeadingBlock`` 是一个 StructBlock 的子项: .. code-block:: python class EventBlock(blocks.StructBlock): heading = HeadingBlock() description = blocks.TextBlock() # ... class Meta: template = 'blocks/event.html' ``blocks/event.html`` 文件内容: .. code-block:: html+django {% load wagtailcore_tags %}
    {% include_block value.heading %} - {% include_block value.description %}
    这种情况下,``value.heading`` 返回的是一个原始输入的字符串内容,而不是 ``BoundBlock``,这是必须的,否则像 ``{% if value.heading == 'Party!' %}`` 之类比较不可能为真。 这意味着 ``{% include_block value.heading %}`` 将渲染输出为普通字符串,没有 ``

    `` 标签。要生成带 ``

    `` 标签的模板,要使用 ``value.bound_blocks.heading`` 的形式 来明确指明使用 ``BoundBlock`` 实例的模板格式输出。 .. code-block:: html+django {% load wagtailcore_tags %}
    {% include_block value.bound_blocks.heading %} - {% include_block value.description %}
    实际上对这种复合块结构的输出,为了更具可读性,最好是相关的 HTML 标签编写在顶层复合块模板中。例如,要输出 ``

    `` 标签,可以在 EventBlock 的模板中定义: .. code-block:: html+django {% load wagtailcore_tags %}

    {{ value.heading }}

    - {% include_block value.description %}
    这种限制不会影响 StructBlock 及 StreamBlock 做为 StructBlock 子结点的值, 因为 Wagtail 在实现这些复合块时知道它们渲染的模板,与放不放在 ``BoundBlock`` 中无关。 例如一个结构块嵌入另一个结构块中: .. code-block:: python class EventBlock(blocks.StructBlock): heading = HeadingBlock() description = blocks.TextBlock() guest_speaker = blocks.StructBlock([ ('first_name', blocks.CharBlock()), ('surname', blocks.CharBlock()), ('photo', ImageChooserBlock()), ], template='blocks/speaker.html') 这样 EventBlock 模板中使用 ``{% include_block value.guest_speaker %}`` 会知道使用 ``blocks/speaker.html`` 渲染输出。 总结下来,使用 BoundBlocks 与普通值应参考如下准则: 1. 在遍历 StreamField 或 StreamBlock (例如 ``{% for block in page.body %}``)时,将得到一系列 BoundBlocks 条目。 2. 对于 BoundBlock 对象实例, 使用 ``block.value`` 获取原始值。 3. 访问 StructBlock (例如 ``value.heading``) 子项返回原始值, 而要使用相关的 BoundBlock 时, 使用 ``value.bound_blocks.heading`` 格式。 4. ListBlock 是一个普通的 Python 列表; 遍历时返回原始值,而不是 BoundBlocks 条目。 5. StructBlock 和 StreamBlock 值是一至知道自已渲染模板的, 即使不在遍历的 BoundBlock 条目中。 .. _custom_editing_interfaces_for_structblock: 定制 ``StructBlock`` 编辑界面 --------------------------------------------- 要改变 ``StructBlock`` 在页面编辑器上显示的方式,可以使用 ``form_classname`` 指定 CSS 风格 (通过 ``StructBlock`` 构造函数的关键字参数,或 ``Meta`` 子类的属性), 设置后会用指定值覆盖缺省的 ``struct-block``: .. code-block:: python class PersonBlock(blocks.StructBlock): first_name = blocks.CharBlock() surname = blocks.CharBlock() photo = ImageChooserBlock(required=False) biography = blocks.RichTextBlock() class Meta: icon = 'user' form_classname = 'person-block struct-block' 这个就可以为这个块设置特定的 CSS 样式 ``person-block``,参考 :ref:`insert_editor_css` 勾子说明。 .. Note:: Wagtail 编辑器已经内置了 ``struct-block`` 类以及其它相关元素的 CSS 样式定义。使用 ``form_classname`` 设定 ``StructBlock`` 时记得加上 ``struct-block`` 以简化定义内容。 如果要更进一步改变 HTML 标签,可以重写 ``Meta`` 子类的 ``form_template`` 属性,设定新的模板文件。模板中可以使用如下变量: ``children`` 组成 ``StructBlock`` 所有子块的 ``OrderedDict``,字典内容是包含子块的``BoundBlock``。通常在模板中使用 ``render_form`` 来渲染他们。 ``help_text`` 块帮助文本。 ``classname`` CCS 的样式类名,保存 ``form_classname`` 的值(缺省是 ``struct-block``)。 ``block_definition`` 这个块的 ``StructBlock`` 类实例。 ``prefix`` 块实例中表单的前缀,以保证各个表单中的标识是唯一的。 要增加其它的变量,可以重写块的 ``get_form_context`` 方法: .. code-block:: python class PersonBlock(blocks.StructBlock): first_name = blocks.CharBlock() surname = blocks.CharBlock() photo = ImageChooserBlock(required=False) biography = blocks.RichTextBlock() def get_form_context(self, value, prefix='', errors=None): context = super().get_form_context(value, prefix=prefix, errors=errors) context['suggested_first_names'] = ['John', 'Paul', 'George', 'Ringo'] return context class Meta: icon = 'user' form_template = 'myapp/block_forms/person.html' .. _custom_value_class_for_structblock: 定制 ``StructBlock`` 的 value_class -------------------------------------- 要改变获取 ``StructBlock`` 值的方法,可以设置 ``value_class`` 属性 (通过 ``StructBlock`` 构造函数的关键字参数,或 ``Meta`` 子类的属性) 来重新定义获值的过程。 ``value_class`` 必须是 ``StructValue`` 子类,任何附加的方法要取得子块原始值通过方法的 ``self`` 参数 (例如 ``self.get('my_block')``)。 例: .. code-block:: python from wagtail.core.models import Page from wagtail.core.blocks import ( CharBlock, PageChooserBlock, StructValue, StructBlock, TextBlock, URLBlock) class LinkStructValue(StructValue): def url(self): external_url = self.get('external_url') page = self.get('page') if external_url: return external_url elif page: return page.url class QuickLinkBlock(StructBlock): text = CharBlock(label="link text", required=True) page = PageChooserBlock(label="page", required=False) external_url = URLBlock(label="external URL", required=False) class Meta: icon = 'site' value_class = LinkStructValue class MyPage(Page): quick_links = StreamField([('links', QuickLinkBlock())], blank=True) quotations = StreamField([('quote', StructBlock([ ('quote', TextBlock(required=True)), ('page', PageChooserBlock(required=False)), ('external_url', URLBlock(required=False)), ], icon='openquote', value_class=LinkStructValue))], blank=True) content_panels = Page.content_panels + [ StreamFieldPanel('quick_links'), StreamFieldPanel('quotations'), ] 在模板中使用改变获取值的方法: .. code-block:: html+django {% load wagtailcore_tags %}
    {% for quotation in page.quotations %}
    {{ quotation.value.quote }}
    {% endfor %}
    定制块类型 ------------------ 在需要定义用户界面 UI,或处理不是 Wagtail 自带的块类型时(不能通过已有字段类型结构组合成时), 可以通过定制块类型来达成目标。 更深入的了解,可以参考 Wagtail 内置各种块类的源代码。 Wagtail 提供了一个 ``wagtail.core.blocks.FieldBlock`` 基类来 Django 中已存在字段类型的封装。子类只需定义一个 ``field`` 属性返回表单字段对象: .. code-block:: python class IPAddressBlock(FieldBlock): def __init__(self, required=True, help_text=None, **kwargs): self.field = forms.GenericIPAddressField(required=required, help_text=help_text) super().__init__(**kwargs) 迁移 ---------- 迁移文件中定义的 StreamField 字段 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 同 Django 的模型字段一样, 任何影响 StreamField 的模型定义的改变,将生成迁移文件包括变更字段的定义。 由于 StreamField 比通常模型复杂,这将导致项目迁移文件导入 更多信息,这会导致在移动或删除时的一些问题。 要迁移的话,StructBlock, StreamBlock 及 ChoiceBlock 实现额外逻辑以确保这些块的子类能解构成 StructBlock, StreamBlock 及 ChoiceBlock 实例,这样避免对定义类 的引用。这样做是可能是,因为块类型定义了继承的标准模式,也知道根据这些模式重构子类对应块。 如果定义了其它块类的子类,例如 ``FieldBlock``,需要在项目的生命周期中保持那个类定义,并实现 :ref:`定制析构方法 ` 说明块在类中 表述方法。同样定制一个 StructBlock、StreamBlock 或 ChoiceBlock 的子类解析到不能再分解的基础块类型。如果在构造器中增加了额外的参数,也需要提供自已的 ``deconstruct`` 方法。 .. _streamfield_migrating_richtext: 将 RichTextFields 改成 StreamField ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 如果将即存的 RichTextField 改成 StreamField,数据库迁移完成时不会报错,因为在数据库中都使用文本字段。但 StreamField 是使用 JSON 表示数据, 所以已有的数据需要做特殊的转换才能正确取出并展示。StreamField 需要至少包含一个 RichTextBlock 块类型。(当变更模型时,不要忘记记将 ``FieldPanel`` 改成 ``StreamFieldPanel``) 生成迁移文件使用 ``./manage.py makemigrations``,然后编辑文件成如下形式(这个例子中,``demo.BlogPage`` 模型的 'body' 字段将转换成 StreamField 字段中 RichTextBlock 类型块 ``rich_text``): .. code-block:: python # -*- coding: utf-8 -*- from django.db import models, migrations from wagtail.core.rich_text import RichText def convert_to_streamfield(apps, schema_editor): BlogPage = apps.get_model("demo", "BlogPage") for page in BlogPage.objects.all(): if page.body.raw_text and not page.body: page.body = [('rich_text', RichText(page.body.raw_text))] page.save() def convert_to_richtext(apps, schema_editor): BlogPage = apps.get_model("demo", "BlogPage") for page in BlogPage.objects.all(): if page.body.raw_text is None: raw_text = ''.join([ child.value.source for child in page.body if child.block_type == 'rich_text' ]) page.body = raw_text page.save() class Migration(migrations.Migration): dependencies = [ # leave the dependency line from the generated migration intact! ('demo', '0001_initial'), ] operations = [ # leave the generated AlterField intact! migrations.AlterField( model_name='BlogPage', name='body', field=wagtail.core.fields.StreamField([('rich_text', wagtail.core.blocks.RichTextBlock())]), ), migrations.RunPython( convert_to_streamfield, convert_to_richtext, ), ] 注意,上述迁移仅对已发布的页面对象生效。如果也想迁移草稿及以前的版本,请使用如下代码: .. code-block:: python # -*- coding: utf-8 -*- import json from django.core.serializers.json import DjangoJSONEncoder from django.db import migrations, models from wagtail.core.rich_text import RichText def page_to_streamfield(page): changed = False if page.body.raw_text and not page.body: page.body = [('rich_text', {'rich_text': RichText(page.body.raw_text)})] changed = True return page, changed def pagerevision_to_streamfield(revision_data): changed = False body = revision_data.get('body') if body: try: json.loads(body) except ValueError: revision_data['body'] = json.dumps( [{ "value": {"rich_text": body}, "type": "rich_text" }], cls=DjangoJSONEncoder) changed = True else: # It's already valid JSON. Leave it. pass return revision_data, changed def page_to_richtext(page): changed = False if page.body.raw_text is None: raw_text = ''.join([ child.value['rich_text'].source for child in page.body if child.block_type == 'rich_text' ]) page.body = raw_text changed = True return page, changed def pagerevision_to_richtext(revision_data): changed = False body = revision_data.get('body', 'definitely non-JSON string') if body: try: body_data = json.loads(body) except ValueError: # It's not apparently a StreamField. Leave it. pass else: raw_text = ''.join([ child['value']['rich_text'] for child in body_data if child['type'] == 'rich_text' ]) revision_data['body'] = raw_text changed = True return revision_data, changed def convert(apps, schema_editor, page_converter, pagerevision_converter): BlogPage = apps.get_model("demo", "BlogPage") for page in BlogPage.objects.all(): page, changed = page_converter(page) if changed: page.save() for revision in page.revisions.all(): revision_data = json.loads(revision.content_json) revision_data, changed = pagerevision_converter(revision_data) if changed: revision.content_json = json.dumps(revision_data, cls=DjangoJSONEncoder) revision.save() def convert_to_streamfield(apps, schema_editor): return convert(apps, schema_editor, page_to_streamfield, pagerevision_to_streamfield) def convert_to_richtext(apps, schema_editor): return convert(apps, schema_editor, page_to_richtext, pagerevision_to_richtext) class Migration(migrations.Migration): dependencies = [ # leave the dependency line from the generated migration intact! ('demo', '0001_initial'), ] operations = [ # leave the generated AlterField intact! migrations.AlterField( model_name='BlogPage', name='body', field=wagtail.core.fields.StreamField([('rich_text', wagtail.core.blocks.RichTextBlock())]), ), migrations.RunPython( convert_to_streamfield, convert_to_richtext, ), ]