Add section about BoundBlocks and values

pull/1889/merge
Matt Westcott 2015-10-30 23:00:04 +00:00
rodzic 2c765a7462
commit 0e34282646
1 zmienionych plików z 124 dodań i 0 usunięć

Wyświetl plik

@ -471,6 +471,130 @@ To pass additional context variables to the template, block subclasses can overr
In this example, the variable ``is_happening_today`` will be made available within the block template.
BoundBlocks and values
----------------------
As you've seen above, it's possible to assign a particular template rendering to a block. This can be done on any block type, not just StructBlocks - however, there are some extra details to be aware of. Consider the following block definition:
.. code-block:: python
class HeadingBlock(blocks.CharBlock):
class Meta:
template = 'blocks/heading.html'
where blocks/heading.html consists of:
.. code-block:: html+django
<h1>{{ value }}</h1>
This gives us a block that behaves as an ordinary text field, but wraps its output in ``<h1>`` tags whenever it is rendered:
.. code-block:: python
class BlogPage(Page):
body = StreamField([
# ...
'heading': HeadingBlock(),
# ...
])
.. code-block:: html+django
{% for block in page.body %}
{% if block.block_type == 'heading' %}
{{ block }} {# this block will output its own <h1>...</h1> tags #}
{% endif %}
{% endfor %}
This is a powerful feature, but it involves some complexity behind the scenes to make it work. Effectively, HeadingBlock has a double identity - logically it represents a plain Python string value, but in circumstances such as this it needs to yield a 'magic' object that knows its own custom HTML representation. This 'magic' object is an instance of ``BoundBlock`` - an object that represents the pairing of a value and its block definition. (Django developers may recognise this as the same principle behind ``BoundField`` in Django's forms framework.)
Most of the time, you won't need to worry about whether you're dealing with a plain value or a BoundBlock; you can trust Wagtail to do the right thing. However, there are certain cases where the distinction becomes important. For example, consider the following setup:
.. code-block:: python
class EventBlock(blocks.StructBlock):
heading = HeadingBlock()
description = blocks.TextBlock()
# ...
class Meta:
template = 'blocks/event.html'
where blocks/event.html is:
.. code-block:: html+django
<div class="event {% if value.heading == 'Party!' %}lots-of-balloons{% endif %}">
{{ value.heading }}
- {{ value.description }}
</div>
In this case, ``value.heading`` returns the plain string value; if this weren't the case, the comparison in ``{% if value.heading == 'Party!' %}`` would never succeed. This in turn means that ``{{ value.heading }}`` renders as the plain string, without the ``<h1>`` tags.
Interactions between BoundBlocks and plain values work according to the following rules:
1. When iterating over the value of a StreamField or StreamBlock (as in ``{% for block in page.body %}``), you will get back a sequence of BoundBlocks.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This means that ``{{ block }}`` will always render using the block's own template, if one is supplied. More specifically, these ``block`` objects will be instances of StreamChild, which additionally provides the ``block_type`` property.
2. If you have a BoundBlock instance, you can access the plain value as ``block.value``.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For example, if you had a particular page template where you wanted HeadingBlock to display as ``<h2>`` rather than ``<h1>``, you could write:
.. code-block:: html+django
{% for block in page.body %}
{% if block.block_type == 'heading' %}
<h2>{{ block.value }}</h2>
{% endif %}
{% endfor %}
3. Accessing a child of a StructBlock (as in ``value.heading``) will return a plain value; to retrieve the BoundBlock instead, use ``value.bound_blocks.heading``.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This ensures that template tags such as ``{% if value.heading == 'Party!' %}`` and ``{% image value.photo fill-320x200 %}`` work as expected. The event template above could be rewritten as follows to access the HeadingBlock content as a BoundBlock and use its own HTML representation (with ``<h1>`` tags included):
.. code-block:: html+django
<div class="event {% if value.heading == 'Party!' %}lots-of-balloons{% endif %}">
{{ value.bound_block.heading }}
{{ value.description }}
</div>
However, in this case it's probably more readable to make the ``<h1>`` tag explicit in the EventBlock's template:
.. code-block:: html+django
<div class="event {% if value.heading == 'Party!' %}lots-of-balloons{% endif %}">
<h1>{{ value.heading }}</h1>
{{ value.description }}
</div>
4. The value of a ListBlock is a plain Python list; iterating over it returns plain child values.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5. StructBlock and StreamBlock values always know how to render their own templates, even if you only have the plain value rather than the BoundBlock.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is possible because the HTML rendering behaviour of these blocks does not interfere with their main role as a container for data - there's no "double identity" as there is for blocks like CharBlock. For example, if a StructBlock is nested in another StructBlock, as in:
.. 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')
then writing ``{{ value.guest_speaker }}`` within the EventBlock's template will use the template rendering from blocks/speaker.html for that field.
Custom block types
------------------