From 0e3428264685f8670829bebddf38383932a5b19c Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Fri, 30 Oct 2015 23:00:04 +0000 Subject: [PATCH] Add section about BoundBlocks and values --- docs/topics/streamfield.rst | 124 ++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/docs/topics/streamfield.rst b/docs/topics/streamfield.rst index 69f57940a..705ba1d68 100644 --- a/docs/topics/streamfield.rst +++ b/docs/topics/streamfield.rst @@ -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 + +

{{ value }}

+ +This gives us a block that behaves as an ordinary text field, but wraps its output in ``

`` 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

...

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 + +
+ {{ value.heading }} + - {{ value.description }} +
+ +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 ``

`` 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 ``

`` rather than ``

``, you could write: + +.. code-block:: html+django + + {% for block in page.body %} + {% if block.block_type == 'heading' %} +

{{ block.value }}

+ {% 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 ``

`` tags included): + +.. code-block:: html+django + +
+ {{ value.bound_block.heading }} + {{ value.description }} +
+ +However, in this case it's probably more readable to make the ``

`` tag explicit in the EventBlock's template: + +.. code-block:: html+django + +
+

{{ value.heading }}

+ {{ value.description }} +
+ +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 ------------------