From 1806b82ca870f0e497b7ea352c6ab12d243cb2f1 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 23 Mar 2015 14:48:11 +0000 Subject: [PATCH 01/36] Made get_willow_image a context manager --- wagtail/wagtailimages/models.py | 51 ++++++++++++---------- wagtail/wagtailimages/tests/test_models.py | 9 ++-- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/wagtail/wagtailimages/models.py b/wagtail/wagtailimages/models.py index 3fa6e390a..c1844f131 100644 --- a/wagtail/wagtailimages/models.py +++ b/wagtail/wagtailimages/models.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import os.path import hashlib import re +from contextlib import contextmanager from six import BytesIO, text_type @@ -84,6 +85,7 @@ class AbstractImage(models.Model, TagSearchable): def __str__(self): return self.title + @contextmanager def get_willow_image(self): try: image_file = self.file.file # triggers a call to self.storage.open, so IOErrors from missing files will be raised at this point @@ -95,7 +97,9 @@ class AbstractImage(models.Model, TagSearchable): image_file.open('rb') image_file.seek(0) - return WillowImage.open(image_file) + yield WillowImage.open(image_file) + + image_file.close() def get_rect(self): return Rect(0, 0, self.width, self.height) @@ -128,27 +132,27 @@ class AbstractImage(models.Model, TagSearchable): self.focal_point_height = None def get_suggested_focal_point(self): - willow = self.get_willow_image() + with self.get_willow_image() as willow: + faces = willow.detect_faces() - faces = willow.detect_faces() - if faces: - # Create a bounding box around all faces - left = min(face[0] for face in faces) - top = min(face[1] for face in faces) - right = max(face[2] for face in faces) - bottom = max(face[3] for face in faces) - focal_point = Rect(left, top, right, bottom) - else: - features = willow.detect_features() - if features: - # Create a bounding box around all features - left = min(feature[0] for feature in features) - top = min(feature[1] for feature in features) - right = max(feature[0] for feature in features) - bottom = max(feature[1] for feature in features) + if faces: + # Create a bounding box around all faces + left = min(face[0] for face in faces) + top = min(face[1] for face in faces) + right = max(face[2] for face in faces) + bottom = max(face[3] for face in faces) focal_point = Rect(left, top, right, bottom) else: - return None + features = willow.detect_features() + if features: + # Create a bounding box around all features + left = min(feature[0] for feature in features) + top = min(feature[1] for feature in features) + right = max(feature[0] for feature in features) + bottom = max(feature[1] for feature in features) + focal_point = Rect(left, top, right, bottom) + else: + return None # Add 20% to width and height and give it a minimum size x, y = focal_point.centroid @@ -299,12 +303,11 @@ class Filter(models.Model): return operations def run(self, image, output): - willow = image.get_willow_image() + with image.get_willow_image() as willow: + for operation in self.operations: + operation.run(willow, image) - for operation in self.operations: - operation.run(willow, image) - - willow.save_as_jpeg(output) + willow.save_as_jpeg(output) return output diff --git a/wagtail/wagtailimages/tests/test_models.py b/wagtail/wagtailimages/tests/test_models.py index fa3434417..9abb3b093 100644 --- a/wagtail/wagtailimages/tests/test_models.py +++ b/wagtail/wagtailimages/tests/test_models.py @@ -219,9 +219,8 @@ class TestGetWillowImage(TestCase): ) def test_willow_image_object_returned(self): - willow_image = self.image.get_willow_image() - - self.assertIsInstance(willow_image, WillowImage) + with self.image.get_willow_image() as willow_image: + self.assertIsInstance(willow_image, WillowImage) def test_with_missing_image(self): # Image id=1 in test fixtures has a missing image file @@ -229,7 +228,9 @@ class TestGetWillowImage(TestCase): # Attempting to get the Willow image for images without files # should raise a SourceImageIOError - self.assertRaises(SourceImageIOError, bad_image.get_willow_image) + with self.assertRaises(SourceImageIOError): + with bad_image.get_willow_image() as willow_image: + self.fail() # Shouldn't get here class TestIssue573(TestCase): From e809176173f57ded1a2bf8f2861e1c0b48cd3cb1 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 23 Mar 2015 17:32:40 +0000 Subject: [PATCH 02/36] Moved core components on level up --- docs/core_components/index.rst | 14 -------------- docs/{core_components => }/form_builder.rst | 0 .../images/feature_detection.rst | 0 docs/{core_components => }/images/index.rst | 0 .../images/using_images_outside_wagtail.rst | 0 docs/index.rst | 15 ++++++++++----- .../pages/advanced_topics/private_pages.rst | 0 .../pages/advanced_topics/queryset_methods.rst | 0 .../pages/advanced_topics/routable_page_mixin.rst | 0 .../pages/creating_pages.rst | 0 docs/{core_components => }/pages/editing_api.rst | 0 docs/{core_components => }/pages/index.rst | 0 .../{core_components => }/pages/model_recipes.rst | 0 docs/{core_components => }/pages/theory.rst | 0 .../pages/writing_templates.rst | 0 docs/{core_components => }/search/backends.rst | 0 docs/{core_components => }/search/index.rst | 0 docs/{core_components => }/search/indexing.rst | 0 docs/{core_components => }/search/searching.rst | 0 docs/{core_components => }/sites.rst | 0 docs/{core_components => }/snippets.rst | 0 21 files changed, 10 insertions(+), 19 deletions(-) delete mode 100644 docs/core_components/index.rst rename docs/{core_components => }/form_builder.rst (100%) rename docs/{core_components => }/images/feature_detection.rst (100%) rename docs/{core_components => }/images/index.rst (100%) rename docs/{core_components => }/images/using_images_outside_wagtail.rst (100%) rename docs/{core_components => }/pages/advanced_topics/private_pages.rst (100%) rename docs/{core_components => }/pages/advanced_topics/queryset_methods.rst (100%) rename docs/{core_components => }/pages/advanced_topics/routable_page_mixin.rst (100%) rename docs/{core_components => }/pages/creating_pages.rst (100%) rename docs/{core_components => }/pages/editing_api.rst (100%) rename docs/{core_components => }/pages/index.rst (100%) rename docs/{core_components => }/pages/model_recipes.rst (100%) rename docs/{core_components => }/pages/theory.rst (100%) rename docs/{core_components => }/pages/writing_templates.rst (100%) rename docs/{core_components => }/search/backends.rst (100%) rename docs/{core_components => }/search/index.rst (100%) rename docs/{core_components => }/search/indexing.rst (100%) rename docs/{core_components => }/search/searching.rst (100%) rename docs/{core_components => }/sites.rst (100%) rename docs/{core_components => }/snippets.rst (100%) diff --git a/docs/core_components/index.rst b/docs/core_components/index.rst deleted file mode 100644 index bd55db7e5..000000000 --- a/docs/core_components/index.rst +++ /dev/null @@ -1,14 +0,0 @@ -Core components -=============== - -.. toctree:: - :maxdepth: 2 - :titlesonly: - - sites - pages/index - images/index - snippets - search/index - form_builder - diff --git a/docs/core_components/form_builder.rst b/docs/form_builder.rst similarity index 100% rename from docs/core_components/form_builder.rst rename to docs/form_builder.rst diff --git a/docs/core_components/images/feature_detection.rst b/docs/images/feature_detection.rst similarity index 100% rename from docs/core_components/images/feature_detection.rst rename to docs/images/feature_detection.rst diff --git a/docs/core_components/images/index.rst b/docs/images/index.rst similarity index 100% rename from docs/core_components/images/index.rst rename to docs/images/index.rst diff --git a/docs/core_components/images/using_images_outside_wagtail.rst b/docs/images/using_images_outside_wagtail.rst similarity index 100% rename from docs/core_components/images/using_images_outside_wagtail.rst rename to docs/images/using_images_outside_wagtail.rst diff --git a/docs/index.rst b/docs/index.rst index 80b30e8db..5fd806007 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -13,10 +13,10 @@ Below are some useful links to help you get started with Wagtail. * **Creating your Wagtail site** - * :doc:`core_components/pages/creating_pages` - * :doc:`Writing templates ` - * :doc:`core_components/images/index` - * :doc:`core_components/search/index` + * :doc:`pages/creating_pages` + * :doc:`Writing templates ` + * :doc:`images/index` + * :doc:`search/index` * :doc:`howto/third_party_tutorials` @@ -33,7 +33,12 @@ Index :titlesonly: getting_started/index - core_components/index + sites + pages/index + images/index + snippets + search/index + form_builder contrib_components/index howto/index reference/index diff --git a/docs/core_components/pages/advanced_topics/private_pages.rst b/docs/pages/advanced_topics/private_pages.rst similarity index 100% rename from docs/core_components/pages/advanced_topics/private_pages.rst rename to docs/pages/advanced_topics/private_pages.rst diff --git a/docs/core_components/pages/advanced_topics/queryset_methods.rst b/docs/pages/advanced_topics/queryset_methods.rst similarity index 100% rename from docs/core_components/pages/advanced_topics/queryset_methods.rst rename to docs/pages/advanced_topics/queryset_methods.rst diff --git a/docs/core_components/pages/advanced_topics/routable_page_mixin.rst b/docs/pages/advanced_topics/routable_page_mixin.rst similarity index 100% rename from docs/core_components/pages/advanced_topics/routable_page_mixin.rst rename to docs/pages/advanced_topics/routable_page_mixin.rst diff --git a/docs/core_components/pages/creating_pages.rst b/docs/pages/creating_pages.rst similarity index 100% rename from docs/core_components/pages/creating_pages.rst rename to docs/pages/creating_pages.rst diff --git a/docs/core_components/pages/editing_api.rst b/docs/pages/editing_api.rst similarity index 100% rename from docs/core_components/pages/editing_api.rst rename to docs/pages/editing_api.rst diff --git a/docs/core_components/pages/index.rst b/docs/pages/index.rst similarity index 100% rename from docs/core_components/pages/index.rst rename to docs/pages/index.rst diff --git a/docs/core_components/pages/model_recipes.rst b/docs/pages/model_recipes.rst similarity index 100% rename from docs/core_components/pages/model_recipes.rst rename to docs/pages/model_recipes.rst diff --git a/docs/core_components/pages/theory.rst b/docs/pages/theory.rst similarity index 100% rename from docs/core_components/pages/theory.rst rename to docs/pages/theory.rst diff --git a/docs/core_components/pages/writing_templates.rst b/docs/pages/writing_templates.rst similarity index 100% rename from docs/core_components/pages/writing_templates.rst rename to docs/pages/writing_templates.rst diff --git a/docs/core_components/search/backends.rst b/docs/search/backends.rst similarity index 100% rename from docs/core_components/search/backends.rst rename to docs/search/backends.rst diff --git a/docs/core_components/search/index.rst b/docs/search/index.rst similarity index 100% rename from docs/core_components/search/index.rst rename to docs/search/index.rst diff --git a/docs/core_components/search/indexing.rst b/docs/search/indexing.rst similarity index 100% rename from docs/core_components/search/indexing.rst rename to docs/search/indexing.rst diff --git a/docs/core_components/search/searching.rst b/docs/search/searching.rst similarity index 100% rename from docs/core_components/search/searching.rst rename to docs/search/searching.rst diff --git a/docs/core_components/sites.rst b/docs/sites.rst similarity index 100% rename from docs/core_components/sites.rst rename to docs/sites.rst diff --git a/docs/core_components/snippets.rst b/docs/snippets.rst similarity index 100% rename from docs/core_components/snippets.rst rename to docs/snippets.rst From aa11d45ece9ae7ddd5db84e78c91c72bbdf2687a Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 23 Mar 2015 17:34:22 +0000 Subject: [PATCH 03/36] Renamed contrib_components to contrib --- docs/{contrib_components => contrib}/frontendcache.rst | 0 docs/{contrib_components => contrib}/index.rst | 0 docs/{contrib_components => contrib}/sitemap_generation.rst | 0 docs/{contrib_components => contrib}/static_site_generation.rst | 0 docs/index.rst | 2 +- 5 files changed, 1 insertion(+), 1 deletion(-) rename docs/{contrib_components => contrib}/frontendcache.rst (100%) rename docs/{contrib_components => contrib}/index.rst (100%) rename docs/{contrib_components => contrib}/sitemap_generation.rst (100%) rename docs/{contrib_components => contrib}/static_site_generation.rst (100%) diff --git a/docs/contrib_components/frontendcache.rst b/docs/contrib/frontendcache.rst similarity index 100% rename from docs/contrib_components/frontendcache.rst rename to docs/contrib/frontendcache.rst diff --git a/docs/contrib_components/index.rst b/docs/contrib/index.rst similarity index 100% rename from docs/contrib_components/index.rst rename to docs/contrib/index.rst diff --git a/docs/contrib_components/sitemap_generation.rst b/docs/contrib/sitemap_generation.rst similarity index 100% rename from docs/contrib_components/sitemap_generation.rst rename to docs/contrib/sitemap_generation.rst diff --git a/docs/contrib_components/static_site_generation.rst b/docs/contrib/static_site_generation.rst similarity index 100% rename from docs/contrib_components/static_site_generation.rst rename to docs/contrib/static_site_generation.rst diff --git a/docs/index.rst b/docs/index.rst index 5fd806007..dc83f2b9f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -39,7 +39,7 @@ Index snippets search/index form_builder - contrib_components/index + contrib/index howto/index reference/index support From f7d123fa7c02816499b6bf375b65758a5743f7a6 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Thu, 26 Mar 2015 11:15:32 +0000 Subject: [PATCH 04/36] Install Django 1.8 from tar archive --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 1f40d9017..d37b20772 100644 --- a/tox.ini +++ b/tox.ini @@ -24,7 +24,7 @@ dj17 = Django>=1.7.1,<1.8 dj18 = - https://github.com/django/django/archive/stable/1.8.x.zip#egg=django + https://github.com/django/django/archive/stable/1.8.x.tar.gz#egg=django py2 = unicodecsv>=0.9.4 From e6ef56e911351392f6172e1e995429065b81792c Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Thu, 26 Mar 2015 12:24:46 +0000 Subject: [PATCH 05/36] Fixed typo in wagtailsites/wagtail_hooks.py Fixes #1074 --- wagtail/wagtailsites/wagtail_hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wagtail/wagtailsites/wagtail_hooks.py b/wagtail/wagtailsites/wagtail_hooks.py index 18e0dbfe2..0b238f7ff 100644 --- a/wagtail/wagtailsites/wagtail_hooks.py +++ b/wagtail/wagtailsites/wagtail_hooks.py @@ -21,4 +21,4 @@ class SitesMenuItem(MenuItem): @hooks.register('register_settings_menu_item') def register_sites_menu_item(): - return MenuItem(_('Sites'), urlresolvers.reverse('wagtailsites_index'), classnames='icon icon-site', order=602) + return SitesMenuItem(_('Sites'), urlresolvers.reverse('wagtailsites_index'), classnames='icon icon-site', order=602) From 81c51e2b13fb3a5bee09489138f148aee8926b82 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Wed, 25 Mar 2015 22:23:48 +0000 Subject: [PATCH 06/36] StreamField developer docs --- docs/core_components/pages/streamfield.rst | 412 +++++++++++++++++++++ docs/pages/index.rst | 1 + 2 files changed, 413 insertions(+) create mode 100644 docs/core_components/pages/streamfield.rst diff --git a/docs/core_components/pages/streamfield.rst b/docs/core_components/pages/streamfield.rst new file mode 100644 index 000000000..cc33a0d75 --- /dev/null +++ b/docs/core_components/pages/streamfield.rst @@ -0,0 +1,412 @@ +Freeform page content using StreamField +======================================= + +Wagtail's content model encourages thinking about the information on a page in a formal, structured way; for example, a page about an event is not just an assortment of headings, paragraphs and images, but has specific fields for dates, location, a list of speakers and so on. This ensures that the template developer has a known, predictable set of fields to work with, and can present that information consistently throughout the site in the most appropriate arrangement. It also allows the information to be re-used and repurposed, such as displaying an "events near you" listing. + +In some cases, though, we would like to give the page author more flexibility to create freeform content, such as blog posts or news stories where the text may be interspersed with subheadings, images, pull quotes and video, and perhaps more specialised content types such as maps and charts (or, for a programming blog, code snippets). The traditional approach to this would be to use a rich text field for the entire page body, with the different content types implemented using a variety of HTML styles. This approach is not recommended; aside from rich text simply not being a very good interface for editing this kind of complex content, it also means that the content becomes an opaque blob of HTML, and we lose the semantic information that indicates what the different block types actually mean. + +StreamField solves the problem of freeform content by expressing it as a sequence of 'blocks' - headings, images, paragraphs and so on - which can be repeated and arranged in any order. Each block is editable using the most suitable interface for that block type - such as a rich text area for paragraphs, or Wagtail's image chooser (along with dropdowns to specify image format, where appropriate) for images. + +StreamField also provides a rich API to define your own block types, ranging from simple collections of sub-blocks (such as a 'person' block consisting of first name, surname and photograph) to completely custom components with their own editing interface. Within the database, the StreamField content is stored as JSON, ensuring that the complete informational content of the field is preserved, rather than just an HTML representation of it. + +Using StreamField +----------------- + +``StreamField`` is a model field that can be defined within your page model like any other field: + +.. code-block:: python + + from django.db import models + + from wagtail.wagtailcore.models import Page + from wagtail.wagtailcore.fields import StreamField + from wagtail.wagtailcore import blocks + from wagtail.wagtailadmin.edit_handlers import FieldPanel, StreamFieldPanel + from wagtail.wagtailimages.blocks import ImageChooserBlock + + class BlogPage(Page): + author = models.CharField(max_length=255) + date = models.DateField("Post date") + body = StreamField([ + ('heading', blocks.CharBlock(classname="full title")), + ('paragraph', blocks.RichTextBlock()), + ('image', ImageChooserBlock()), + ]) + + BlogPage.content_panels = [ + FieldPanel('author'), + FieldPanel('date'), + StreamFieldPanel('body'), + ] + +Note: StreamField is not backwards compatible with other field types such as RichTextField; if you migrate an existing field to StreamField, the existing data will be lost. + +The parameter to ``StreamField`` is a list of (name, block_type) tuples; 'name' is used to identify the block type within templates and the internal JSON representation (and should follow standard Python conventions for variable names: lower-case and underscores, no spaces) and 'block_type' should be a block definition object as described below. (Alternatively, ``StreamField`` can be passed a single ``StreamBlock`` instance - see `Structural block types`_.) + +This defines the set of available block types that can be used within this field. The author of the page is free to use these blocks as many times as desired, in any order. + +Basic block types +----------------- + +All block types accept the following optional keyword arguments: + +``default`` + The default value that a new 'empty' block should receive. + +``label`` + The label to display in the editor interface when referring to this block - defaults to a prettified version of the block name (or, in a context where no name is assigned - such as within a ``ListBlock`` - the empty string). + +``icon`` + The name of the icon to display for this block type in the menu of available block types. For a list of icon names, see the Wagtail style guide, which can be enabled by adding ``wagtail.contrib.wagtailstyleguide`` to your project's ``INSTALLED_APPS``. + +``template`` + The path to a Django template that will be used to render this block on the front end. See `Template rendering`_. + +The basic block types provided by Wagtail are as follows: + +CharBlock +~~~~~~~~~ + +``wagtail.wagtailcore.blocks.CharBlock`` + +A single-line text input. The following keyword arguments are accepted: + +``required`` (default: True) + If true, the field cannot be left blank. + +``max_length``, ``min_length`` + Ensures that the string is at most or at least the given length. + +``help_text`` + Help text to display alongside the field. + +TextBlock +~~~~~~~~~ + +``wagtail.wagtailcore.blocks.TextBlock`` + +A multi-line text input. As with ``CharBlock``, the keyword arguments ``required``, ``max_length``, ``min_length`` and ``help_text`` are accepted. + +URLBlock +~~~~~~~~ + +``wagtail.wagtailcore.blocks.URLBlock`` + +A single-line text input that validates that the string is a valid URL. The keyword arguments ``required``, ``max_length``, ``min_length`` and ``help_text`` are accepted. + +BooleanBlock +~~~~~~~~~~~~ + +``wagtail.wagtailcore.blocks.BooleanBlock`` + +A checkbox. The keyword arguments ``required`` and ``help_text`` are accepted. 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.wagtailcore.blocks.DateBlock`` + +A date picker. The keyword arguments ``required`` and ``help_text`` are accepted. + +TimeBlock +~~~~~~~~~ + +``wagtail.wagtailcore.blocks.TimeBlock`` + +A time picker. The keyword arguments ``required`` and ``help_text`` are accepted. + +DateTimeBlock +~~~~~~~~~~~~~ + +``wagtail.wagtailcore.blocks.DateTimeBlock`` + +A combined date / time picker. The keyword arguments ``required`` and ``help_text`` are accepted. + +RichTextBlock +~~~~~~~~~~~~~ + +``wagtail.wagtailcore.blocks.RichTextBlock`` + +A WYSIWYG editor for creating formatted text including links, bold / italics etc. + +RawHTMLBlock +~~~~~~~~~~~~ + +``wagtail.wagtailcore.blocks.RawHTMLBlock`` + +A text area for entering raw HTML which will be rendered unescaped in the page output. The keyword arguments ``required``, ``max_length``, ``min_length`` and ``help_text`` are accepted. + +.. WARNING:: + When this block is in use, there is nothing to prevent editors from inserting malicious scripts into the page, including scripts that would allow the editor to acquire administrator privileges when another administrator views the page. Do not use this block unless your editors are fully trusted. + +ChoiceBlock +~~~~~~~~~~~ + +``wagtail.wagtailcore.blocks.ChoiceBlock`` + +A dropdown select box for choosing from a list of choices. The following keyword arguments are accepted: + +``choices`` + A list of choices, in any format accepted by Django's ``choices`` parameter for model fields: https://docs.djangoproject.com/en/stable/ref/models/fields/#field-choices + +``required`` (default: True) + If true, the field cannot be left blank. + +``help_text`` + Help text to display alongside the field. + +``ChoiceBlock`` can also be subclassed to produce a reusable block with the same list of choices everywhere it is used. For example, a block definition such as:: + + blocks.ChoiceBlock(choices=[ + ('tea', 'Tea'), + ('coffee', 'Coffee'), + ], icon='cup') + +could be rewritten as a subclass of ChoiceBlock:: + + class DrinksChoiceBlock(blocks.ChoiceBlock): + choices = [ + ('tea', 'Tea'), + ('coffee', 'Coffee'), + ] + + class Meta: + icon = 'cup' + +``StreamField`` definitions can then refer to ``DrinksChoiceBlock()`` in place of the full ``ChoiceBlock`` definition. + +PageChooserBlock +~~~~~~~~~~~~~~~~ + +``wagtail.wagtailcore.blocks.PageChooserBlock`` + +A control for selecting a page object, using Wagtail's page browser. The keyword argument ``required`` is accepted. + +DocumentChooserBlock +~~~~~~~~~~~~~~~~~~~~ + +``wagtail.wagtaildocs.blocks.DocumentChooserBlock`` + +A control to allow the editor to select an existing document object, or upload a new one. The keyword argument ``required`` is accepted. + +ImageChooserBlock +~~~~~~~~~~~~~~~~~ + +``wagtail.wagtailimages.blocks.ImageChooserBlock`` + +A control to allow the editor to select an existing image, or upload a new one. The keyword argument ``required`` is accepted. + +SnippetChooserBlock +~~~~~~~~~~~~~~~~~~~ + +``wagtail.wagtailsnippets.blocks.SnippetChooserBlock`` + +A control to allow the editor to select a snippet object. Requires one positional argument: the snippet class to choose from. The keyword argument ``required`` is accepted. + +EmbedBlock +~~~~~~~~~~ + +``wagtail.wagtailembeds.blocks.EmbedBlock`` + +A field for the editor to enter a URL to a media item (such as a YouTube video) to appear as embedded media on the page. The keyword arguments ``required``, ``max_length``, ``min_length`` and ``help_text`` are accepted. + + +Structural block types +---------------------- + +In addition to the basic block types above, it is possible to define new block types made up of sub-blocks: for example, a 'person' block consisting of sub-blocks for first name, surname and image, or a 'carousel' block consisting of an unlimited number of image blocks. These structures can be nested to any depth, making it possible to have a structure containing a list, or a list of structures. + +StructBlock +~~~~~~~~~~~ + +``wagtail.wagtailcore.blocks.StructBlock`` + +A block consisting of a fixed group of sub-blocks to be displayed together. Takes a list of (name, block_definition) tuples as its first argument:: + + ('person', blocks.StructBlock([ + ('first_name', blocks.CharBlock(required=True)), + ('surname', blocks.CharBlock(required=True)), + ('photo', ImageChooserBlock()), + ('biography', blocks.RichTextBlock()), + ], icon='user')) + + +Alternatively, the list of sub-blocks can be provided in a subclass of StructBlock:: + + class PersonBlock(blocks.StructBlock): + first_name = blocks.CharBlock(required=True) + surname = blocks.CharBlock(required=True) + photo = ImageChooserBlock() + biography = blocks.RichTextBlock() + + class Meta: + icon = 'user' + + +The ``Meta`` class supports the properties ``default``, ``label``, ``icon`` and ``template``; these have the same meanings as when they are passed to the block's constructor. + +This defines ``PersonBlock()`` as a block type that can be re-used as many times as you like within your model definitions:: + + body = StreamField([ + ('heading', blocks.CharBlock(classname="full title")), + ('paragraph', blocks.RichTextBlock()), + ('image', ImageChooserBlock()), + ('person', PersonBlock()), + ]) + + +ListBlock +~~~~~~~~~ + +``wagtail.wagtailcore.blocks.ListBlock`` + +A block consisting of many sub-blocks, all of the same type. The editor can add an unlimited number of sub-blocks, and re-order and delete them. Takes the definition of the sub-block as its first argument:: + + ('ingredients_list', blocks.ListBlock(blocks.CharBlock(label="Ingredient"))) + + +Any block type is valid as the sub-block type, including structural types:: + + ('ingredients_list', blocks.ListBlock(blocks.StructBlock( + ('ingredient', blocks.CharBlock(required=True)), + ('amount', blocks.CharBlock()), + ))) + + +StreamBlock +~~~~~~~~~~~ + +``wagtail.wagtailcore.blocks.StreamBlock`` + +A block consisting of a sequence of sub-blocks of different types, which can be mixed and reordered in any order. Used as the overall mechanism of the StreamField itself, but can also be nested or used within other structural block types. Takes a list of (name, block_definition) tuples as its first argument:: + + ('carousel', blocks.StreamBlock( + [ + ('image', ImageChooserBlock()), + ('quotation', blocks.StructBlock([ + ('text', blocks.TextBlock()), + ('author', blocks.CharBlock), + ])), + ('video', blocks.EmbedBlock()), + ], + icon='cogs' + )) + + +As with StructBlock, the list of sub-blocks can also be provided as a subclass of StreamBlock:: + + class CarouselBlock(blocks.StreamBlock): + image = ImageChooserBlock() + quotation = blocks.StructBlock([ + ('text', blocks.TextBlock()), + ('author', blocks.CharBlock), + ]) + video = blocks.EmbedBlock + + class Meta: + icon='cogs' + + +Since ``StreamField`` accepts an instance of ``StreamBlock`` as a parameter, in place of a list of block types, this makes it possible to re-use a common set block types without repeating definitions:: + + class HomePage(Page): + carousel = StreamField(CarouselBlock()) + + +Template rendering +------------------ + +The simplest way to render the contents of a StreamField into your template is to output it as a variable, like any other field:: + + {{ self.body }} + +This will render each block of the stream in turn, wrapped in a ``
`` element (where ``my_block_name`` is the block name given in the StreamField definition). If you wish to provide your own HTML markup, you can instead iterate over the field's value to access each block in turn:: + +
+ {% for block in self.body %} +
{{ block }}
+ {% endfor %} +
+ + +For more control over the rendering of specific block types, each block object provides ``block_type`` and ``value`` properties:: + +
+ {% for block in self.body %} + {% if block.block_type == 'heading' %} +

{{ block.value }}

+ {% else %} +
+ {{ block }} +
+ {% endif %} + {% endfor %} +
+ + +Each block type provides its own front-end HTML rendering mechanism, and this is used for the output of ``{{ block }}``. For most simple block types, such as CharBlock, this will simply output the field's value, but others will provide their own HTML markup; for example, a ListBlock will output the list of child blocks as a ``