kopia lustrzana https://github.com/wagtail/wagtail
Add formal support for customising the form rendering of StructBlocks
The `form_template` attribute was mentioned in passing in the docs, but was missing various things to make it fully useful: - context passed to form_template now includes 'prefix' and 'block_definition' - context for the form is now populated in a separate overrideable `get_form_context` method - full documentation and tests for form_template and get_form_context addedpull/2274/merge
rodzic
9f72e12159
commit
9961455c6a
docs
releases
topics
wagtail
tests/testapp
templates/tests/block_forms
wagtailcore
blocks
tests
|
@ -18,6 +18,7 @@ Changelog
|
|||
* Added a new FloatBlock, DecimalBlock and a RegexBlock (Oktay Altay, Andy Babic)
|
||||
* Wagtail version number is now shown on the settings menu (Chris Rogers)
|
||||
* Added a system check to validate that fields listed in `search_fields` are defined on the model (Josh Schneier)
|
||||
* Added formal APIs for customising the display of StructBlock forms within the page editor (Matt Westcott)
|
||||
* Fix: Email templates and document uploader now support custom `STATICFILES_STORAGE` (Jonny Scholes)
|
||||
* Fix: Removed alignment options (deprecated in HTML and not rendered by Wagtail) from `TableBlock` context menu (Moritz Pfeiffer)
|
||||
* Fix: Fixed incorrect CSS path on ModelAdmin's "choose a parent page" view
|
||||
|
|
|
@ -33,6 +33,7 @@ Minor features
|
|||
* Added a new FloatBlock, DecimalBlock and a RegexBlock (Oktay Altay, Andy Babic)
|
||||
* Wagtail version number is now shown on the settings menu (Chris Rogers)
|
||||
* Added a system check to validate that fields listed in ``search_fields`` are defined on the model (Josh Schneier)
|
||||
* Added formal APIs for customising the display of StructBlock forms within the page editor - see :ref:`custom_editing_interfaces_for_structblock` (Matt Westcott)
|
||||
|
||||
|
||||
Bug fixes
|
||||
|
|
|
@ -111,7 +111,7 @@ FloatBlock
|
|||
A single-line Float input that validates that the value is a valid floating point number. The keyword arguments ``required``, ``max_value`` and ``min_value`` are accepted.
|
||||
|
||||
DecimalBlock
|
||||
~~~~~~~~~~
|
||||
~~~~~~~~~~~~
|
||||
|
||||
``wagtail.wagtailcore.blocks.DecimalBlock``
|
||||
|
||||
|
@ -317,23 +317,7 @@ This defines ``PersonBlock()`` as a block type that can be re-used as many times
|
|||
('person', PersonBlock()),
|
||||
])
|
||||
|
||||
|
||||
To customise the styling of the block as it appears in the page editor, your subclass can specify a ``form_classname`` attribute in ``Meta`` to override the default value of ``struct-block``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class PersonBlock(blocks.StructBlock):
|
||||
first_name = blocks.CharBlock(required=True)
|
||||
surname = blocks.CharBlock(required=True)
|
||||
photo = ImageChooserBlock()
|
||||
biography = blocks.RichTextBlock()
|
||||
|
||||
class Meta:
|
||||
icon = 'user'
|
||||
form_classname = 'person-block struct-block'
|
||||
|
||||
|
||||
You can then provide custom CSS for this block, targeted at the specified classname, by using the ``insert_editor_css`` hook (see :doc:`Hooks </reference/hooks>`). For more extensive customisations that require changes to the HTML markup as well, you can override the ``form_template`` attribute in ``Meta``.
|
||||
Further options are available for customising the display of a ``StructBlock`` within the page editor - see :ref:`custom_editing_interfaces_for_structblock`.
|
||||
|
||||
|
||||
ListBlock
|
||||
|
@ -636,6 +620,65 @@ This is possible because the HTML rendering behaviour of these blocks does not i
|
|||
then writing ``{{ value.guest_speaker }}`` within the EventBlock's template will use the template rendering from ``blocks/speaker.html`` for that field.
|
||||
|
||||
|
||||
.. _custom_editing_interfaces_for_structblock:
|
||||
|
||||
Custom editing interfaces for ``StructBlock``
|
||||
---------------------------------------------
|
||||
|
||||
To customise the styling of a ``StructBlock`` as it appears in the page editor, you can specify a ``form_classname`` attribute (either as a keyword argument to the ``StructBlock`` constructor, or in a subclass's ``Meta``) to override the default value of ``struct-block``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class PersonBlock(blocks.StructBlock):
|
||||
first_name = blocks.CharBlock(required=True)
|
||||
surname = blocks.CharBlock(required=True)
|
||||
photo = ImageChooserBlock()
|
||||
biography = blocks.RichTextBlock()
|
||||
|
||||
class Meta:
|
||||
icon = 'user'
|
||||
form_classname = 'person-block struct-block'
|
||||
|
||||
|
||||
You can then provide custom CSS for this block, targeted at the specified classname, by using the :ref:`insert_editor_css` hook.
|
||||
|
||||
For more extensive customisations that require changes to the HTML markup as well, you can override the ``form_template`` attribute in ``Meta`` to specify your own template path. The following variables are available on this template:
|
||||
|
||||
``children``
|
||||
An ``OrderedDict`` of ``BoundBlock``\s for all of the child blocks making up this ``StructBlock``; typically your template will call ``render_form`` on each of these.
|
||||
|
||||
``help_text``
|
||||
The help text for this block, if specified.
|
||||
|
||||
``classname``
|
||||
The class name passed as ``form_classname`` (defaults to ``struct-block``).
|
||||
|
||||
``block_definition``
|
||||
The ``StructBlock`` instance that defines this block.
|
||||
|
||||
``prefix``
|
||||
The prefix used on form fields for this block instance, guaranteed to be unique across the form.
|
||||
|
||||
To add additional variables, you can override the block's ``get_form_context`` method:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class PersonBlock(blocks.StructBlock):
|
||||
first_name = blocks.CharBlock(required=True)
|
||||
surname = blocks.CharBlock(required=True)
|
||||
photo = ImageChooserBlock()
|
||||
biography = blocks.RichTextBlock()
|
||||
|
||||
def get_form_context(self, value, prefix='', errors=None):
|
||||
context = super(PersonBlock, self).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 block types
|
||||
------------------
|
||||
|
||||
|
|
|
@ -12,9 +12,15 @@ class LinkBlock(blocks.StructBlock):
|
|||
context['classname'] = 'important' if value['title'] == 'Torchbox' else 'normal'
|
||||
return context
|
||||
|
||||
def get_form_context(self, value, prefix='', errors=None):
|
||||
context = super(LinkBlock, self).get_form_context(value, prefix=prefix, errors=errors)
|
||||
context['extra_var'] = "Hello from get_form_context!"
|
||||
return context
|
||||
|
||||
class Meta:
|
||||
icon = "site"
|
||||
template = 'tests/blocks/link_block.html'
|
||||
form_template = 'tests/block_forms/link_block.html'
|
||||
|
||||
|
||||
class SectionBlock(blocks.StructBlock):
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<div class="{{ classname }}" data-prefix="{{ prefix }}">
|
||||
<p>{{ extra_var }}</p>
|
||||
|
||||
<ul class="fields">
|
||||
{% for child in children.values %}
|
||||
<li>{{ child.render_form }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
|
@ -59,7 +59,7 @@ class BaseStructBlock(Block):
|
|||
def media(self):
|
||||
return forms.Media(js=[static('wagtailadmin/js/blocks/struct.js')])
|
||||
|
||||
def render_form(self, value, prefix='', errors=None):
|
||||
def get_form_context(self, value, prefix='', errors=None):
|
||||
if errors:
|
||||
if len(errors) > 1:
|
||||
# We rely on StructBlock.clean throwing a single ValidationError with a specially crafted
|
||||
|
@ -78,11 +78,18 @@ class BaseStructBlock(Block):
|
|||
for name, block in self.child_blocks.items()
|
||||
])
|
||||
|
||||
return render_to_string(self.meta.form_template, {
|
||||
return {
|
||||
'children': bound_child_blocks,
|
||||
'help_text': getattr(self.meta, 'help_text', None),
|
||||
'classname': self.meta.form_classname,
|
||||
})
|
||||
'block_definition': self,
|
||||
'prefix': prefix,
|
||||
}
|
||||
|
||||
def render_form(self, value, prefix='', errors=None):
|
||||
context = self.get_form_context(value, prefix=prefix, errors=errors)
|
||||
|
||||
return render_to_string(self.meta.form_template, context)
|
||||
|
||||
def value_from_datadict(self, data, files, prefix):
|
||||
return StructValue(self, [
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import base64
|
||||
import collections
|
||||
import unittest
|
||||
from decimal import Decimal
|
||||
|
||||
|
@ -12,6 +13,7 @@ from django.test import SimpleTestCase, TestCase
|
|||
from django.utils.html import format_html
|
||||
from django.utils.safestring import SafeData, mark_safe
|
||||
|
||||
from wagtail.tests.testapp.blocks import LinkBlock as CustomLinkBlock
|
||||
from wagtail.tests.testapp.blocks import SectionBlock
|
||||
from wagtail.wagtailcore import blocks
|
||||
from wagtail.wagtailcore.models import Page
|
||||
|
@ -824,6 +826,26 @@ class TestStructBlock(SimpleTestCase):
|
|||
expected = '<div class="rich-text"><b>world</b></div>'
|
||||
self.assertEqual(str(body_bound_block), expected)
|
||||
|
||||
def test_get_form_context(self):
|
||||
class LinkBlock(blocks.StructBlock):
|
||||
title = blocks.CharBlock()
|
||||
link = blocks.URLBlock()
|
||||
|
||||
block = LinkBlock()
|
||||
context = block.get_form_context(block.to_python({
|
||||
'title': "Wagtail site",
|
||||
'link': 'http://www.wagtail.io',
|
||||
}), prefix='mylink')
|
||||
|
||||
self.assertTrue(isinstance(context['children'], collections.OrderedDict))
|
||||
self.assertEqual(len(context['children']), 2)
|
||||
self.assertTrue(isinstance(context['children']['title'], blocks.BoundBlock))
|
||||
self.assertEqual(context['children']['title'].value, "Wagtail site")
|
||||
self.assertTrue(isinstance(context['children']['link'], blocks.BoundBlock))
|
||||
self.assertEqual(context['children']['link'].value, 'http://www.wagtail.io')
|
||||
self.assertEqual(context['block_definition'], block)
|
||||
self.assertEqual(context['prefix'], 'mylink')
|
||||
|
||||
def test_render_form(self):
|
||||
class LinkBlock(blocks.StructBlock):
|
||||
title = blocks.CharBlock()
|
||||
|
@ -1915,10 +1937,16 @@ class TestSystemCheck(TestCase):
|
|||
|
||||
class TestTemplateRendering(TestCase):
|
||||
def test_render_with_custom_context(self):
|
||||
from wagtail.tests.testapp.blocks import LinkBlock
|
||||
|
||||
block = LinkBlock()
|
||||
block = CustomLinkBlock()
|
||||
value = block.to_python({'title': 'Torchbox', 'url': 'http://torchbox.com/'})
|
||||
result = block.render(value)
|
||||
|
||||
self.assertEqual(result, '<a href="http://torchbox.com/" class="important">Torchbox</a>')
|
||||
|
||||
def test_render_with_custom_form_context(self):
|
||||
block = CustomLinkBlock()
|
||||
value = block.to_python({'title': 'Torchbox', 'url': 'http://torchbox.com/'})
|
||||
result = block.render_form(value, prefix='my-link-block')
|
||||
|
||||
self.assertIn('data-prefix="my-link-block"', result)
|
||||
self.assertIn('<p>Hello from get_form_context!</p>', result)
|
||||
|
|
Ładowanie…
Reference in New Issue