streamfield.rst 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252
  1. .. _streamfield:
  2. Freeform page content using StreamField
  3. =======================================
  4. StreamField provides a content editing model suitable for pages that do not follow a fixed structure -- such as blog posts or news stories -- where the text may be interspersed with subheadings, images, pull quotes and video. It's also suitable for more specialised content types, such as maps and charts (or, for a programming blog, code snippets). In this model, these different content types are represented as a sequence of 'blocks', which can be repeated and arranged in any order.
  5. For further background on StreamField, and why you would use it instead of a rich text field for the article body, see the blog post `Rich text fields and faster horses <https://torchbox.com/blog/rich-text-fields-and-faster-horses/>`__.
  6. StreamField also offers 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 full informational content of the field is preserved, rather than just an HTML representation of it.
  7. Using StreamField
  8. -----------------
  9. ``StreamField`` is a model field that can be defined within your page model like any other field:
  10. .. code-block:: python
  11. from django.db import models
  12. from wagtail.core.models import Page
  13. from wagtail.core.fields import StreamField
  14. from wagtail.core import blocks
  15. from wagtail.admin.edit_handlers import FieldPanel, StreamFieldPanel
  16. from wagtail.images.blocks import ImageChooserBlock
  17. class BlogPage(Page):
  18. author = models.CharField(max_length=255)
  19. date = models.DateField("Post date")
  20. body = StreamField([
  21. ('heading', blocks.CharBlock(form_classname="full title")),
  22. ('paragraph', blocks.RichTextBlock()),
  23. ('image', ImageChooserBlock()),
  24. ])
  25. content_panels = Page.content_panels + [
  26. FieldPanel('author'),
  27. FieldPanel('date'),
  28. StreamFieldPanel('body'),
  29. ]
  30. Note: StreamField is not backwards compatible with other field types such as RichTextField. If you need to migrate an existing field to StreamField, refer to :ref:`streamfield_migrating_richtext`.
  31. 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`_.)
  32. 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.
  33. ``StreamField`` also accepts an optional keyword argument ``blank``, defaulting to false; when this is false, at least one block must be provided for the field to be considered valid.
  34. Basic block types
  35. -----------------
  36. All block types accept the following optional keyword arguments:
  37. ``default``
  38. The default value that a new 'empty' block should receive.
  39. ``label``
  40. 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).
  41. ``icon``
  42. 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.styleguide`` to your project's ``INSTALLED_APPS``.
  43. ``template``
  44. The path to a Django template that will be used to render this block on the front end. See `Template rendering`_.
  45. ``group``
  46. The group used to categorize this block, i.e. any blocks with the same group name will be shown together in the editor interface with the group name as a heading.
  47. The basic block types provided by Wagtail are as follows:
  48. CharBlock
  49. ~~~~~~~~~
  50. ``wagtail.core.blocks.CharBlock``
  51. A single-line text input. The following keyword arguments are accepted:
  52. ``required`` (default: True)
  53. If true, the field cannot be left blank.
  54. ``max_length``, ``min_length``
  55. Ensures that the string is at most or at least the given length.
  56. ``help_text``
  57. Help text to display alongside the field.
  58. ``validators``
  59. A list of validation functions for the field (see `Django Validators <https://docs.djangoproject.com/en/stable/ref/validators/>`__).
  60. ``form_classname``
  61. A value to add to the form field's ``class`` attribute when rendered on the page editing form.
  62. .. versionchanged:: 2.11
  63. The ``class`` attribute was previously set via the keyword argument ``classname``.
  64. TextBlock
  65. ~~~~~~~~~
  66. ``wagtail.core.blocks.TextBlock``
  67. A multi-line text input. As with ``CharBlock``, the keyword arguments ``required`` (default: True), ``max_length``, ``min_length``, ``help_text``, ``validators`` and ``form_classname`` are accepted.
  68. EmailBlock
  69. ~~~~~~~~~~
  70. ``wagtail.core.blocks.EmailBlock``
  71. A single-line email input that validates that the email is a valid Email Address. The keyword arguments ``required`` (default: True), ``help_text``, ``validators`` and ``form_classname`` are accepted.
  72. For an example of ``EmailBlock`` in use, see :ref:`streamfield_personblock_example`
  73. IntegerBlock
  74. ~~~~~~~~~~~~
  75. ``wagtail.core.blocks.IntegerBlock``
  76. A single-line integer input that validates that the integer is a valid whole number. The keyword arguments ``required`` (default: True), ``max_value``, ``min_value``, ``help_text``, ``validators`` and ``form_classname`` are accepted.
  77. For an example of ``IntegerBlock`` in use, see :ref:`streamfield_personblock_example`
  78. FloatBlock
  79. ~~~~~~~~~~
  80. ``wagtail.core.blocks.FloatBlock``
  81. A single-line Float input that validates that the value is a valid floating point number. The keyword arguments ``required`` (default: True), ``max_value``, ``min_value``, ``validators`` and ``form_classname`` are accepted.
  82. DecimalBlock
  83. ~~~~~~~~~~~~
  84. ``wagtail.core.blocks.DecimalBlock``
  85. A single-line decimal input that validates that the value is a valid decimal number. The keyword arguments ``required`` (default: True), ``help_text``, ``max_value``, ``min_value``, ``max_digits``, ``decimal_places``, ``validators`` and ``form_classname`` are accepted.
  86. For an example of ``DecimalBlock`` in use, see :ref:`streamfield_personblock_example`
  87. RegexBlock
  88. ~~~~~~~~~~
  89. ``wagtail.core.blocks.RegexBlock``
  90. A single-line text input that validates a string against a regex expression. The regular expression used for validation must be supplied as the first argument, or as the keyword argument ``regex``. To customise the message text used to indicate a validation error, pass a dictionary as the keyword argument ``error_messages`` containing either or both of the keys ``required`` (for the message shown on an empty value) or ``invalid`` (for the message shown on a non-matching value):
  91. .. code-block:: python
  92. blocks.RegexBlock(regex=r'^[0-9]{3}$', error_messages={
  93. 'invalid': "Not a valid library card number."
  94. })
  95. The keyword arguments ``regex``, ``error_messages``, ``help_text``, ``required`` (default: True), ``max_length``, ``min_length``, ``validators`` and ``form_classname`` are accepted.
  96. URLBlock
  97. ~~~~~~~~
  98. ``wagtail.core.blocks.URLBlock``
  99. A single-line text input that validates that the string is a valid URL. The keyword arguments ``required`` (default: True), ``max_length``, ``min_length``, ``help_text``, ``validators`` and ``form_classname`` are accepted.
  100. BooleanBlock
  101. ~~~~~~~~~~~~
  102. ``wagtail.core.blocks.BooleanBlock``
  103. A checkbox. The keyword arguments ``required``, ``help_text`` and ``form_classname`` 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``.
  104. DateBlock
  105. ~~~~~~~~~
  106. ``wagtail.core.blocks.DateBlock``
  107. A date picker. The keyword arguments ``required`` (default: True), ``help_text``, ``validators``, ``form_classname`` and ``format`` are accepted.
  108. ``format`` (default: None)
  109. Date format. This must be one of the recognised formats listed in the `DATE_INPUT_FORMATS <https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-DATE_INPUT_FORMATS>`_ setting. If not specified Wagtail will use ``WAGTAIL_DATE_FORMAT`` setting with fallback to '%Y-%m-%d'.
  110. TimeBlock
  111. ~~~~~~~~~
  112. ``wagtail.core.blocks.TimeBlock``
  113. A time picker. The keyword arguments ``required`` (default: True), ``help_text``, ``validators`` and ``form_classname`` are accepted.
  114. DateTimeBlock
  115. ~~~~~~~~~~~~~
  116. ``wagtail.core.blocks.DateTimeBlock``
  117. A combined date / time picker. The keyword arguments ``required`` (default: True), ``help_text``, ``format``, ``validators`` and ``form_classname`` are accepted.
  118. ``format`` (default: None)
  119. Date format. This must be one of the recognised formats listed in the `DATETIME_INPUT_FORMATS <https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-DATETIME_INPUT_FORMATS>`_ setting. If not specified Wagtail will use ``WAGTAIL_DATETIME_FORMAT`` setting with fallback to '%Y-%m-%d %H:%M'.
  120. RichTextBlock
  121. ~~~~~~~~~~~~~
  122. ``wagtail.core.blocks.RichTextBlock``
  123. A WYSIWYG editor for creating formatted text including links, bold / italics etc. The keyword arguments ``required`` (default: True), ``help_text``, ``validators``, ``form_classname``, ``editor`` and ``features`` are accepted.
  124. ``editor`` (default: ``default``)
  125. The rich text editor to be used (see :ref:`WAGTAILADMIN_RICH_TEXT_EDITORS`).
  126. ``features`` (default: None)
  127. Specify the set of features allowed (see :ref:`rich_text_features`).
  128. RawHTMLBlock
  129. ~~~~~~~~~~~~
  130. ``wagtail.core.blocks.RawHTMLBlock``
  131. A text area for entering raw HTML which will be rendered unescaped in the page output. The keyword arguments ``required`` (default: True), ``max_length``, ``min_length``, ``help_text``, ``validators`` and ``form_classname`` are accepted.
  132. .. WARNING::
  133. 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.
  134. BlockQuoteBlock
  135. ~~~~~~~~~~~~~~~
  136. ``wagtail.core.blocks.BlockQuoteBlock``
  137. A text field, the contents of which will be wrapped in an HTML `<blockquote>` tag pair. The keyword arguments ``required`` (default: True), ``max_length``, ``min_length``, ``help_text``, ``validators`` and ``form_classname`` are accepted.
  138. ChoiceBlock
  139. ~~~~~~~~~~~
  140. ``wagtail.core.blocks.ChoiceBlock``
  141. A dropdown select box for choosing from a list of choices. The following keyword arguments are accepted:
  142. ``choices``
  143. A list of choices, in any format accepted by Django's :attr:`~django.db.models.Field.choices` parameter for model fields, or a callable returning such a list.
  144. ``required`` (default: True)
  145. If true, the field cannot be left blank.
  146. ``help_text``
  147. Help text to display alongside the field.
  148. ``validators``
  149. A list of validation functions for the field (see `Django Validators <https://docs.djangoproject.com/en/stable/ref/validators/>`__).
  150. ``form_classname``
  151. A value to add to the form field's ``class`` attribute when rendered on the page editing form.
  152. ``widget``
  153. The form widget to render the field with (see `Django Widgets <https://docs.djangoproject.com/en/stable/ref/forms/widgets/>`__).
  154. ``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:
  155. .. code-block:: python
  156. blocks.ChoiceBlock(choices=[
  157. ('tea', 'Tea'),
  158. ('coffee', 'Coffee'),
  159. ], icon='cup')
  160. could be rewritten as a subclass of ChoiceBlock:
  161. .. code-block:: python
  162. class DrinksChoiceBlock(blocks.ChoiceBlock):
  163. choices = [
  164. ('tea', 'Tea'),
  165. ('coffee', 'Coffee'),
  166. ]
  167. class Meta:
  168. icon = 'cup'
  169. ``StreamField`` definitions can then refer to ``DrinksChoiceBlock()`` in place of the full ``ChoiceBlock`` definition. Note that this only works when ``choices`` is a fixed list, not a callable.
  170. .. _streamfield_multiplechoiceblock:
  171. MultipleChoiceBlock
  172. ~~~~~~~~~~~~~~~~~~~
  173. ``wagtail.core.blocks.MultipleChoiceBlock``
  174. A multiple select box for choosing from a list of choices. The following keyword arguments are accepted:
  175. ``choices``
  176. A list of choices, in any format accepted by Django's :attr:`~django.db.models.Field.choices` parameter for model fields, or a callable returning such a list.
  177. ``required`` (default: True)
  178. If true, the field cannot be left blank.
  179. ``help_text``
  180. Help text to display alongside the field.
  181. ``validators``
  182. A list of validation functions for the field (see `Django Validators <https://docs.djangoproject.com/en/stable/ref/validators/>`__).
  183. ``form_classname``
  184. A value to add to the form field's ``class`` attribute when rendered on the page editing form.
  185. ``widget``
  186. The form widget to render the field with (see `Django Widgets <https://docs.djangoproject.com/en/stable/ref/forms/widgets/>`__).
  187. PageChooserBlock
  188. ~~~~~~~~~~~~~~~~
  189. ``wagtail.core.blocks.PageChooserBlock``
  190. A control for selecting a page object, using Wagtail's page browser. The following keyword arguments are accepted:
  191. ``required`` (default: True)
  192. If true, the field cannot be left blank.
  193. ``page_type`` (default: Page)
  194. Restrict choices to one or more specific page types. Accepts a page model class, model name (as a string), or a list or tuple of these.
  195. ``can_choose_root`` (default: False)
  196. If true, the editor can choose the tree root as a page. Normally this would be undesirable, since the tree root is never a usable page, but in some specialised cases it may be appropriate. For example, a block providing a feed of related articles could use a PageChooserBlock to select which subsection of the site articles will be taken from, with the root corresponding to 'everywhere'.
  197. DocumentChooserBlock
  198. ~~~~~~~~~~~~~~~~~~~~
  199. ``wagtail.documents.blocks.DocumentChooserBlock``
  200. A control to allow the editor to select an existing document object, or upload a new one. The keyword argument ``required`` (default: True) is accepted.
  201. ImageChooserBlock
  202. ~~~~~~~~~~~~~~~~~
  203. ``wagtail.images.blocks.ImageChooserBlock``
  204. A control to allow the editor to select an existing image, or upload a new one. The keyword argument ``required`` (default: True) is accepted.
  205. SnippetChooserBlock
  206. ~~~~~~~~~~~~~~~~~~~
  207. ``wagtail.snippets.blocks.SnippetChooserBlock``
  208. 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`` (default: True) is accepted.
  209. EmbedBlock
  210. ~~~~~~~~~~
  211. ``wagtail.embeds.blocks.EmbedBlock``
  212. 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`` (default: True), ``max_length``, ``min_length`` and ``help_text`` are accepted.
  213. .. _streamfield_staticblock:
  214. StaticBlock
  215. ~~~~~~~~~~~
  216. ``wagtail.core.blocks.StaticBlock``
  217. A block which doesn't have any fields, thus passes no particular values to its template during rendering. This can be useful if you need the editor to be able to insert some content which is always the same or doesn't need to be configured within the page editor, such as an address, embed code from third-party services, or more complex pieces of code if the template uses template tags.
  218. By default, some default text (which contains the ``label`` keyword argument if you pass it) will be displayed in the editor interface, so that the block doesn't look empty. But you can also customise it entirely by passing a text string as the ``admin_text`` keyword argument instead:
  219. .. code-block:: python
  220. blocks.StaticBlock(
  221. admin_text='Latest posts: no configuration needed.',
  222. # or admin_text=mark_safe('<b>Latest posts</b>: no configuration needed.'),
  223. template='latest_posts.html')
  224. ``StaticBlock`` can also be subclassed to produce a reusable block with the same configuration everywhere it is used:
  225. .. code-block:: python
  226. class LatestPostsStaticBlock(blocks.StaticBlock):
  227. class Meta:
  228. icon = 'user'
  229. label = 'Latest posts'
  230. admin_text = '{label}: configured elsewhere'.format(label=label)
  231. template = 'latest_posts.html'
  232. Structural block types
  233. ----------------------
  234. 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.
  235. StructBlock
  236. ~~~~~~~~~~~
  237. ``wagtail.core.blocks.StructBlock``
  238. 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:
  239. .. code-block:: python
  240. ('person', blocks.StructBlock([
  241. ('first_name', blocks.CharBlock()),
  242. ('surname', blocks.CharBlock()),
  243. ('photo', ImageChooserBlock(required=False)),
  244. ('biography', blocks.RichTextBlock()),
  245. ], icon='user'))
  246. Alternatively, the list of sub-blocks can be provided in a subclass of StructBlock:
  247. .. code-block:: python
  248. class PersonBlock(blocks.StructBlock):
  249. first_name = blocks.CharBlock()
  250. surname = blocks.CharBlock()
  251. photo = ImageChooserBlock(required=False)
  252. biography = blocks.RichTextBlock()
  253. class Meta:
  254. icon = 'user'
  255. The ``Meta`` class supports the properties ``default``, ``label``, ``icon`` and ``template``, which have the same meanings as when they are passed to the block's constructor.
  256. This defines ``PersonBlock()`` as a block type that can be re-used as many times as you like within your model definitions:
  257. .. code-block:: python
  258. body = StreamField([
  259. ('heading', blocks.CharBlock(form_classname="full title")),
  260. ('paragraph', blocks.RichTextBlock()),
  261. ('image', ImageChooserBlock()),
  262. ('person', PersonBlock()),
  263. ])
  264. Further options are available for customising the display of a ``StructBlock`` within the page editor - see :ref:`custom_editing_interfaces_for_structblock`.
  265. You can also customise how the value of a ``StructBlock`` is prepared for using in templates - see :ref:`custom_value_class_for_structblock`.
  266. ListBlock
  267. ~~~~~~~~~
  268. ``wagtail.core.blocks.ListBlock``
  269. 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:
  270. .. code-block:: python
  271. ('ingredients_list', blocks.ListBlock(blocks.CharBlock(label="Ingredient")))
  272. Any block type is valid as the sub-block type, including structural types:
  273. .. code-block:: python
  274. ('ingredients_list', blocks.ListBlock(blocks.StructBlock([
  275. ('ingredient', blocks.CharBlock()),
  276. ('amount', blocks.CharBlock(required=False)),
  277. ])))
  278. To customise the class name of a ``ListBlock`` as it appears in the page editor, you can specify a ``form_classname`` attribute as a keyword argument to the ``ListBlock`` constructor:
  279. .. code-block:: python
  280. :emphasize-lines: 4
  281. ('ingredients_list', blocks.ListBlock(blocks.StructBlock([
  282. ('ingredient', blocks.CharBlock()),
  283. ('amount', blocks.CharBlock(required=False)),
  284. ]), form_classname='ingredients-list'))
  285. Alternatively, you can add ``form_classname`` in a subclass's ``Meta``:
  286. .. code-block:: python
  287. :emphasize-lines: 6
  288. class IngredientsListBlock(blocks.ListBlock):
  289. ingredient = blocks.CharBlock()
  290. amount = blocks.CharBlock(required=False)
  291. class Meta:
  292. form_classname = 'ingredients-list'
  293. StreamBlock
  294. ~~~~~~~~~~~
  295. ``wagtail.core.blocks.StreamBlock``
  296. A block consisting of a sequence of sub-blocks of different types, which can be mixed and reordered at will. 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:
  297. .. code-block:: python
  298. ('carousel', blocks.StreamBlock(
  299. [
  300. ('image', ImageChooserBlock()),
  301. ('quotation', blocks.StructBlock([
  302. ('text', blocks.TextBlock()),
  303. ('author', blocks.CharBlock()),
  304. ])),
  305. ('video', EmbedBlock()),
  306. ],
  307. icon='cogs'
  308. ))
  309. As with StructBlock, the list of sub-blocks can also be provided as a subclass of StreamBlock:
  310. .. code-block:: python
  311. class CarouselBlock(blocks.StreamBlock):
  312. image = ImageChooserBlock()
  313. quotation = blocks.StructBlock([
  314. ('text', blocks.TextBlock()),
  315. ('author', blocks.CharBlock()),
  316. ])
  317. video = EmbedBlock()
  318. class Meta:
  319. icon='cogs'
  320. 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 of block types without repeating definitions:
  321. .. code-block:: python
  322. class HomePage(Page):
  323. carousel = StreamField(CarouselBlock(max_num=10, block_counts={'video': {'max_num': 2}}))
  324. ``StreamBlock`` accepts the following options as either keyword arguments or ``Meta`` properties:
  325. ``required`` (default: True)
  326. If true, at least one sub-block must be supplied. This is ignored when using the ``StreamBlock`` as the top-level block of a StreamField; in this case the StreamField's ``blank`` property is respected instead.
  327. ``min_num``
  328. Minimum number of sub-blocks that the stream must have.
  329. ``max_num``
  330. Maximum number of sub-blocks that the stream may have.
  331. ``block_counts``
  332. Specifies the minimum and maximum number of each block type, as a dictionary mapping block names to dicts with (optional) ``min_num`` and ``max_num`` fields.
  333. ``form_classname``
  334. Customise the class name added to a ``StreamBlock`` form in the page editor.
  335. .. code-block:: python
  336. :emphasize-lines: 4
  337. ('event_promotions', blocks.StreamBlock([
  338. ('hashtag', blocks.CharBlock()),
  339. ('post_date', blocks.DateBlock()),
  340. ], form_classname='event-promotions'))
  341. .. code-block:: python
  342. :emphasize-lines: 6
  343. class EventPromotionsBlock(blocks.StreamBlock):
  344. hashtag = blocks.CharBlock()
  345. post_date = blocks.DateBlock()
  346. class Meta:
  347. form_classname = 'event-promotions'
  348. .. _streamfield_personblock_example:
  349. Example: ``PersonBlock``
  350. ------------------------
  351. This example demonstrates how the basic block types introduced above can be combined into a more complex block type based on ``StructBlock``:
  352. .. code-block:: python
  353. from wagtail.core import blocks
  354. class PersonBlock(blocks.StructBlock):
  355. name = blocks.CharBlock()
  356. height = blocks.DecimalBlock()
  357. age = blocks.IntegerBlock()
  358. email = blocks.EmailBlock()
  359. class Meta:
  360. template = 'blocks/person_block.html'
  361. .. _streamfield_template_rendering:
  362. Template rendering
  363. ------------------
  364. StreamField provides an HTML representation for the stream content as a whole, as well as for each individual block. To include this HTML into your page, use the ``{% include_block %}`` tag:
  365. .. code-block:: html+django
  366. {% load wagtailcore_tags %}
  367. ...
  368. {% include_block page.body %}
  369. In the default rendering, each block of the stream is wrapped in a ``<div class="block-my_block_name">`` 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, and invoke ``{% include_block %}`` on each block in turn:
  370. .. code-block:: html+django
  371. {% load wagtailcore_tags %}
  372. ...
  373. <article>
  374. {% for block in page.body %}
  375. <section>{% include_block block %}</section>
  376. {% endfor %}
  377. </article>
  378. For more control over the rendering of specific block types, each block object provides ``block_type`` and ``value`` properties:
  379. .. code-block:: html+django
  380. {% load wagtailcore_tags %}
  381. ...
  382. <article>
  383. {% for block in page.body %}
  384. {% if block.block_type == 'heading' %}
  385. <h1>{{ block.value }}</h1>
  386. {% else %}
  387. <section class="block-{{ block.block_type }}">
  388. {% include_block block %}
  389. </section>
  390. {% endif %}
  391. {% endfor %}
  392. </article>
  393. By default, each block is rendered using simple, minimal HTML markup, or no markup at all. For example, a CharBlock value is rendered as plain text, while a ListBlock outputs its child blocks in a `<ul>` wrapper. To override this with your own custom HTML rendering, you can pass a ``template`` argument to the block, giving the filename of a template file to be rendered. This is particularly useful for custom block types derived from StructBlock:
  394. .. code-block:: python
  395. ('person', blocks.StructBlock(
  396. [
  397. ('first_name', blocks.CharBlock()),
  398. ('surname', blocks.CharBlock()),
  399. ('photo', ImageChooserBlock(required=False)),
  400. ('biography', blocks.RichTextBlock()),
  401. ],
  402. template='myapp/blocks/person.html',
  403. icon='user'
  404. ))
  405. Or, when defined as a subclass of StructBlock:
  406. .. code-block:: python
  407. class PersonBlock(blocks.StructBlock):
  408. first_name = blocks.CharBlock()
  409. surname = blocks.CharBlock()
  410. photo = ImageChooserBlock(required=False)
  411. biography = blocks.RichTextBlock()
  412. class Meta:
  413. template = 'myapp/blocks/person.html'
  414. icon = 'user'
  415. Within the template, the block value is accessible as the variable ``value``:
  416. .. code-block:: html+django
  417. {% load wagtailimages_tags %}
  418. <div class="person">
  419. {% image value.photo width-400 %}
  420. <h2>{{ value.first_name }} {{ value.surname }}</h2>
  421. {{ value.biography }}
  422. </div>
  423. Since ``first_name``, ``surname``, ``photo`` and ``biography`` are defined as blocks in their own right, this could also be written as:
  424. .. code-block:: html+django
  425. {% load wagtailcore_tags wagtailimages_tags %}
  426. <div class="person">
  427. {% image value.photo width-400 %}
  428. <h2>{% include_block value.first_name %} {% include_block value.surname %}</h2>
  429. {% include_block value.biography %}
  430. </div>
  431. Writing ``{{ my_block }}`` is roughly equivalent to ``{% include_block my_block %}``, but the short form is more restrictive, as it does not pass variables from the calling template such as ``request`` or ``page``; for this reason, it is recommended that you only use it for simple values that do not render HTML of their own. For example, if our PersonBlock used the template:
  432. .. code-block:: html+django
  433. {% load wagtailimages_tags %}
  434. <div class="person">
  435. {% image value.photo width-400 %}
  436. <h2>{{ value.first_name }} {{ value.surname }}</h2>
  437. {% if request.user.is_authenticated %}
  438. <a href="#">Contact this person</a>
  439. {% endif %}
  440. {{ value.biography }}
  441. </div>
  442. then the ``request.user.is_authenticated`` test would not work correctly when rendering the block through a ``{{ ... }}`` tag:
  443. .. code-block:: html+django
  444. {# Incorrect: #}
  445. {% for block in page.body %}
  446. {% if block.block_type == 'person' %}
  447. <div>
  448. {{ block }}
  449. </div>
  450. {% endif %}
  451. {% endfor %}
  452. {# Correct: #}
  453. {% for block in page.body %}
  454. {% if block.block_type == 'person' %}
  455. <div>
  456. {% include_block block %}
  457. </div>
  458. {% endif %}
  459. {% endfor %}
  460. Like Django's ``{% include %}`` tag, ``{% include_block %}`` also allows passing additional variables to the included template, through the syntax ``{% include_block my_block with foo="bar" %}``:
  461. .. code-block:: html+django
  462. {# In page template: #}
  463. {% for block in page.body %}
  464. {% if block.block_type == 'person' %}
  465. {% include_block block with classname="important" %}
  466. {% endif %}
  467. {% endfor %}
  468. {# In PersonBlock template: #}
  469. <div class="{{ classname }}">
  470. ...
  471. </div>
  472. The syntax ``{% include_block my_block with foo="bar" only %}`` is also supported, to specify that no variables from the parent template other than ``foo`` will be passed to the child template.
  473. .. _streamfield_get_context:
  474. As well as passing variables from the parent template, block subclasses can pass additional template variables of their own by overriding the ``get_context`` method:
  475. .. code-block:: python
  476. import datetime
  477. class EventBlock(blocks.StructBlock):
  478. title = blocks.CharBlock()
  479. date = blocks.DateBlock()
  480. def get_context(self, value, parent_context=None):
  481. context = super().get_context(value, parent_context=parent_context)
  482. context['is_happening_today'] = (value['date'] == datetime.date.today())
  483. return context
  484. class Meta:
  485. template = 'myapp/blocks/event.html'
  486. In this example, the variable ``is_happening_today`` will be made available within the block template. The ``parent_context`` keyword argument is available when the block is rendered through an ``{% include_block %}`` tag, and is a dict of variables passed from the calling template.
  487. BoundBlocks and values
  488. ----------------------
  489. All block types, not just StructBlock, accept a ``template`` parameter to determine how they will be rendered on a page. However, for blocks that handle basic Python data types, such as ``CharBlock`` and ``IntegerBlock``, there are some limitations on where the template will take effect, since those built-in types (``str``, ``int`` and so on) cannot be 'taught' about their template rendering. As an example of this, consider the following block definition:
  490. .. code-block:: python
  491. class HeadingBlock(blocks.CharBlock):
  492. class Meta:
  493. template = 'blocks/heading.html'
  494. where ``blocks/heading.html`` consists of:
  495. .. code-block:: html+django
  496. <h1>{{ value }}</h1>
  497. This gives us a block that behaves as an ordinary text field, but wraps its output in ``<h1>`` tags whenever it is rendered:
  498. .. code-block:: python
  499. class BlogPage(Page):
  500. body = StreamField([
  501. # ...
  502. ('heading', HeadingBlock()),
  503. # ...
  504. ])
  505. .. code-block:: html+django
  506. {% load wagtailcore_tags %}
  507. {% for block in page.body %}
  508. {% if block.block_type == 'heading' %}
  509. {% include_block block %} {# This block will output its own <h1>...</h1> tags. #}
  510. {% endif %}
  511. {% endfor %}
  512. This kind of arrangement - a value that supposedly represents a plain text string, but has its own custom HTML representation when output on a template - would normally be a very messy thing to achieve in Python, but it works here because the items you get when iterating over a StreamField are not actually the 'native' values of the blocks. Instead, each item is returned as an instance of ``BoundBlock`` - an object that represents the pairing of a value and its block definition. By keeping track of the block definition, a ``BoundBlock`` always knows which template to render. To get to the underlying value - in this case, the text content of the heading - you would need to access ``block.value``. Indeed, if you were to output ``{% include_block block.value %}`` on the page, you would find that it renders as plain text, without the ``<h1>`` tags.
  513. (More precisely, the items returned when iterating over a StreamField are instances of a class ``StreamChild``, which provides the ``block_type`` property as well as ``value``.)
  514. Experienced Django developers may find it helpful to compare this to the ``BoundField`` class in Django's forms framework, which represents the pairing of a form field value with its corresponding form field definition, and therefore knows how to render the value as an HTML form field.
  515. Most of the time, you won't need to worry about these internal details; Wagtail will use the template rendering wherever you would expect it to. However, there are certain cases where the illusion isn't quite complete - namely, when accessing children of a ``ListBlock`` or ``StructBlock``. In these cases, there is no ``BoundBlock`` wrapper, and so the item cannot be relied upon to know its own template rendering. For example, consider the following setup, where our ``HeadingBlock`` is a child of a StructBlock:
  516. .. code-block:: python
  517. class EventBlock(blocks.StructBlock):
  518. heading = HeadingBlock()
  519. description = blocks.TextBlock()
  520. # ...
  521. class Meta:
  522. template = 'blocks/event.html'
  523. In ``blocks/event.html``:
  524. .. code-block:: html+django
  525. {% load wagtailcore_tags %}
  526. <div class="event {% if value.heading == 'Party!' %}lots-of-balloons{% endif %}">
  527. {% include_block value.heading %}
  528. - {% include_block value.description %}
  529. </div>
  530. In this case, ``value.heading`` returns the plain string value rather than a ``BoundBlock``; this is necessary because otherwise the comparison in ``{% if value.heading == 'Party!' %}`` would never succeed. This in turn means that ``{% include_block value.heading %}`` renders as the plain string, without the ``<h1>`` tags. To get the HTML rendering, you need to explicitly access the ``BoundBlock`` instance through ``value.bound_blocks.heading``:
  531. .. code-block:: html+django
  532. {% load wagtailcore_tags %}
  533. <div class="event {% if value.heading == 'Party!' %}lots-of-balloons{% endif %}">
  534. {% include_block value.bound_blocks.heading %}
  535. - {% include_block value.description %}
  536. </div>
  537. In practice, it would probably be more natural and readable to make the ``<h1>`` tag explicit in the EventBlock's template:
  538. .. code-block:: html+django
  539. {% load wagtailcore_tags %}
  540. <div class="event {% if value.heading == 'Party!' %}lots-of-balloons{% endif %}">
  541. <h1>{{ value.heading }}</h1>
  542. - {% include_block value.description %}
  543. </div>
  544. This limitation does not apply to StructBlock and StreamBlock values as children of a StructBlock, because Wagtail implements these as complex objects that know their own template rendering, even when not wrapped in a ``BoundBlock``. For example, if a StructBlock is nested in another StructBlock, as in:
  545. .. code-block:: python
  546. class EventBlock(blocks.StructBlock):
  547. heading = HeadingBlock()
  548. description = blocks.TextBlock()
  549. guest_speaker = blocks.StructBlock([
  550. ('first_name', blocks.CharBlock()),
  551. ('surname', blocks.CharBlock()),
  552. ('photo', ImageChooserBlock()),
  553. ], template='blocks/speaker.html')
  554. then ``{% include_block value.guest_speaker %}`` within the EventBlock's template will pick up the template rendering from ``blocks/speaker.html`` as intended.
  555. In summary, interactions between BoundBlocks and plain values work according to the following rules:
  556. 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.
  557. 2. If you have a BoundBlock instance, you can access the plain value as ``block.value``.
  558. 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``.
  559. 4. The value of a ListBlock is a plain Python list; iterating over it returns plain child values.
  560. 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.
  561. .. _custom_editing_interfaces_for_structblock:
  562. Custom editing interfaces for ``StructBlock``
  563. ---------------------------------------------
  564. 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``:
  565. .. code-block:: python
  566. class PersonBlock(blocks.StructBlock):
  567. first_name = blocks.CharBlock()
  568. surname = blocks.CharBlock()
  569. photo = ImageChooserBlock(required=False)
  570. biography = blocks.RichTextBlock()
  571. class Meta:
  572. icon = 'user'
  573. form_classname = 'person-block struct-block'
  574. You can then provide custom CSS for this block, targeted at the specified classname, by using the :ref:`insert_editor_css` hook.
  575. .. Note::
  576. Wagtail's editor styling has some built in styling for the ``struct-block`` class and other related elements. If you specify a value for ``form_classname``, it will overwrite the classes that are already applied to ``StructBlock``, so you must remember to specify the ``struct-block`` as well.
  577. 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:
  578. ``children``
  579. 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.
  580. ``help_text``
  581. The help text for this block, if specified.
  582. ``classname``
  583. The class name passed as ``form_classname`` (defaults to ``struct-block``).
  584. ``block_definition``
  585. The ``StructBlock`` instance that defines this block.
  586. ``prefix``
  587. The prefix used on form fields for this block instance, guaranteed to be unique across the form.
  588. To add additional variables, you can override the block's ``get_form_context`` method:
  589. .. code-block:: python
  590. class PersonBlock(blocks.StructBlock):
  591. first_name = blocks.CharBlock()
  592. surname = blocks.CharBlock()
  593. photo = ImageChooserBlock(required=False)
  594. biography = blocks.RichTextBlock()
  595. def get_form_context(self, value, prefix='', errors=None):
  596. context = super().get_form_context(value, prefix=prefix, errors=errors)
  597. context['suggested_first_names'] = ['John', 'Paul', 'George', 'Ringo']
  598. return context
  599. class Meta:
  600. icon = 'user'
  601. form_template = 'myapp/block_forms/person.html'
  602. .. _custom_value_class_for_structblock:
  603. Custom value class for ``StructBlock``
  604. --------------------------------------
  605. To customise the methods available for a ``StructBlock`` value, you can specify a ``value_class`` attribute (either as a keyword argument to the ``StructBlock`` constructor, or in a subclass's ``Meta``) to override how the value is prepared.
  606. This ``value_class`` must be a subclass of ``StructValue``, any additional methods can access the value from sub-blocks via the block key on ``self`` (e.g. ``self.get('my_block')``).
  607. Example:
  608. .. code-block:: python
  609. from wagtail.core.models import Page
  610. from wagtail.core.blocks import (
  611. CharBlock, PageChooserBlock, StructValue, StructBlock, TextBlock, URLBlock)
  612. class LinkStructValue(StructValue):
  613. def url(self):
  614. external_url = self.get('external_url')
  615. page = self.get('page')
  616. if external_url:
  617. return external_url
  618. elif page:
  619. return page.url
  620. class QuickLinkBlock(StructBlock):
  621. text = CharBlock(label="link text", required=True)
  622. page = PageChooserBlock(label="page", required=False)
  623. external_url = URLBlock(label="external URL", required=False)
  624. class Meta:
  625. icon = 'site'
  626. value_class = LinkStructValue
  627. class MyPage(Page):
  628. quick_links = StreamField([('links', QuickLinkBlock())], blank=True)
  629. quotations = StreamField([('quote', StructBlock([
  630. ('quote', TextBlock(required=True)),
  631. ('page', PageChooserBlock(required=False)),
  632. ('external_url', URLBlock(required=False)),
  633. ], icon='openquote', value_class=LinkStructValue))], blank=True)
  634. content_panels = Page.content_panels + [
  635. StreamFieldPanel('quick_links'),
  636. StreamFieldPanel('quotations'),
  637. ]
  638. Your extended value class methods will be available in your template:
  639. .. code-block:: html+django
  640. {% load wagtailcore_tags %}
  641. <ul>
  642. {% for link in page.quick_links %}
  643. <li><a href="{{ link.value.url }}">{{ link.value.text }}</a></li>
  644. {% endfor %}
  645. </ul>
  646. <div>
  647. {% for quotation in page.quotations %}
  648. <blockquote cite="{{ quotation.value.url }}">
  649. {{ quotation.value.quote }}
  650. </blockquote>
  651. {% endfor %}
  652. </div>
  653. .. _modifying_streamfield_data:
  654. Modifying StreamField data
  655. --------------------------
  656. A StreamField's value behaves as a list, and blocks can be inserted, overwritten and deleted before saving the instance back to the database. A new item can be written to the list as a tuple of *(block_type, value)* - when read back, it will be returned as a ``BoundBlock`` object.
  657. .. code-block:: python
  658. # Replace the first block with a new block of type 'heading'
  659. my_page.body[0] = ('heading', "My story")
  660. # Delete the last block
  661. del my_page.body[-1]
  662. # Append a block to the stream
  663. my_page.body.append(('paragraph', "<p>And they all lived happily ever after.</p>"))
  664. # Save the updated data back to the database
  665. my_page.save()
  666. .. versionadded:: 2.12
  667. In earlier versions, a StreamField value could be replaced by assigning a new list of *(block_type, value)* tuples, but not modified in-place.
  668. Custom block types
  669. ------------------
  670. If you need to implement a custom UI, or handle a datatype that is not provided by Wagtail's built-in block types (and cannot be built up as a structure of existing fields), it is possible to define your own custom block types. For further guidance, refer to the source code of Wagtail's built-in block classes.
  671. For block types that simply wrap an existing Django form field, Wagtail provides an abstract class ``wagtail.core.blocks.FieldBlock`` as a helper. Subclasses just need to set a ``field`` property that returns the form field object:
  672. .. code-block:: python
  673. class IPAddressBlock(FieldBlock):
  674. def __init__(self, required=True, help_text=None, **kwargs):
  675. self.field = forms.GenericIPAddressField(required=required, help_text=help_text)
  676. super().__init__(**kwargs)
  677. Migrations
  678. ----------
  679. StreamField definitions within migrations
  680. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  681. As with any model field in Django, any changes to a model definition that affect a StreamField will result in a migration file that contains a 'frozen' copy of that field definition. Since a StreamField definition is more complex than a typical model field, there is an increased likelihood of definitions from your project being imported into the migration -- which would cause problems later on if those definitions are moved or deleted.
  682. To mitigate this, StructBlock, StreamBlock and ChoiceBlock implement additional logic to ensure that any subclasses of these blocks are deconstructed to plain instances of StructBlock, StreamBlock and ChoiceBlock -- in this way, the migrations avoid having any references to your custom class definitions. This is possible because these block types provide a standard pattern for inheritance, and know how to reconstruct the block definition for any subclass that follows that pattern.
  683. If you subclass any other block class, such as ``FieldBlock``, you will need to either keep that class definition in place for the lifetime of your project, or implement a :ref:`custom deconstruct method <django:custom-deconstruct-method>` that expresses your block entirely in terms of classes that are guaranteed to remain in place. Similarly, if you customise a StructBlock, StreamBlock or ChoiceBlock subclass to the point where it can no longer be expressed as an instance of the basic block type -- for example, if you add extra arguments to the constructor -- you will need to provide your own ``deconstruct`` method.
  684. .. _streamfield_migrating_richtext:
  685. Migrating RichTextFields to StreamField
  686. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  687. If you change an existing RichTextField to a StreamField, the database migration will complete with no errors, since both fields use a text column within the database. However, StreamField uses a JSON representation for its data, so the existing text requires an extra conversion step in order to become accessible again. For this to work, the StreamField needs to include a RichTextBlock as one of the available block types. (When updating the model, don't forget to change ``FieldPanel`` to ``StreamFieldPanel`` too.) Create the migration as normal using ``./manage.py makemigrations``, then edit it as follows (in this example, the 'body' field of the ``demo.BlogPage`` model is being converted to a StreamField with a RichTextBlock named ``rich_text``):
  688. .. code-block:: python
  689. # -*- coding: utf-8 -*-
  690. from django.db import models, migrations
  691. from wagtail.core.rich_text import RichText
  692. def convert_to_streamfield(apps, schema_editor):
  693. BlogPage = apps.get_model("demo", "BlogPage")
  694. for page in BlogPage.objects.all():
  695. if page.body.raw_text and not page.body:
  696. page.body = [('rich_text', RichText(page.body.raw_text))]
  697. page.save()
  698. def convert_to_richtext(apps, schema_editor):
  699. BlogPage = apps.get_model("demo", "BlogPage")
  700. for page in BlogPage.objects.all():
  701. if page.body.raw_text is None:
  702. raw_text = ''.join([
  703. child.value.source for child in page.body
  704. if child.block_type == 'rich_text'
  705. ])
  706. page.body = raw_text
  707. page.save()
  708. class Migration(migrations.Migration):
  709. dependencies = [
  710. # leave the dependency line from the generated migration intact!
  711. ('demo', '0001_initial'),
  712. ]
  713. operations = [
  714. # leave the generated AlterField intact!
  715. migrations.AlterField(
  716. model_name='BlogPage',
  717. name='body',
  718. field=wagtail.core.fields.StreamField([('rich_text', wagtail.core.blocks.RichTextBlock())]),
  719. ),
  720. migrations.RunPython(
  721. convert_to_streamfield,
  722. convert_to_richtext,
  723. ),
  724. ]
  725. Note that the above migration will work on published Page objects only. If you also need to migrate draft pages and page revisions, then edit the migration as in the following example instead:
  726. .. code-block:: python
  727. # -*- coding: utf-8 -*-
  728. import json
  729. from django.core.serializers.json import DjangoJSONEncoder
  730. from django.db import migrations, models
  731. from wagtail.core.rich_text import RichText
  732. def page_to_streamfield(page):
  733. changed = False
  734. if page.body.raw_text and not page.body:
  735. page.body = [('rich_text', {'rich_text': RichText(page.body.raw_text)})]
  736. changed = True
  737. return page, changed
  738. def pagerevision_to_streamfield(revision_data):
  739. changed = False
  740. body = revision_data.get('body')
  741. if body:
  742. try:
  743. json.loads(body)
  744. except ValueError:
  745. revision_data['body'] = json.dumps(
  746. [{
  747. "value": {"rich_text": body},
  748. "type": "rich_text"
  749. }],
  750. cls=DjangoJSONEncoder)
  751. changed = True
  752. else:
  753. # It's already valid JSON. Leave it.
  754. pass
  755. return revision_data, changed
  756. def page_to_richtext(page):
  757. changed = False
  758. if page.body.raw_text is None:
  759. raw_text = ''.join([
  760. child.value['rich_text'].source for child in page.body
  761. if child.block_type == 'rich_text'
  762. ])
  763. page.body = raw_text
  764. changed = True
  765. return page, changed
  766. def pagerevision_to_richtext(revision_data):
  767. changed = False
  768. body = revision_data.get('body', 'definitely non-JSON string')
  769. if body:
  770. try:
  771. body_data = json.loads(body)
  772. except ValueError:
  773. # It's not apparently a StreamField. Leave it.
  774. pass
  775. else:
  776. raw_text = ''.join([
  777. child['value']['rich_text'] for child in body_data
  778. if child['type'] == 'rich_text'
  779. ])
  780. revision_data['body'] = raw_text
  781. changed = True
  782. return revision_data, changed
  783. def convert(apps, schema_editor, page_converter, pagerevision_converter):
  784. BlogPage = apps.get_model("demo", "BlogPage")
  785. for page in BlogPage.objects.all():
  786. page, changed = page_converter(page)
  787. if changed:
  788. page.save()
  789. for revision in page.revisions.all():
  790. revision_data = json.loads(revision.content_json)
  791. revision_data, changed = pagerevision_converter(revision_data)
  792. if changed:
  793. revision.content_json = json.dumps(revision_data, cls=DjangoJSONEncoder)
  794. revision.save()
  795. def convert_to_streamfield(apps, schema_editor):
  796. return convert(apps, schema_editor, page_to_streamfield, pagerevision_to_streamfield)
  797. def convert_to_richtext(apps, schema_editor):
  798. return convert(apps, schema_editor, page_to_richtext, pagerevision_to_richtext)
  799. class Migration(migrations.Migration):
  800. dependencies = [
  801. # leave the dependency line from the generated migration intact!
  802. ('demo', '0001_initial'),
  803. ]
  804. operations = [
  805. # leave the generated AlterField intact!
  806. migrations.AlterField(
  807. model_name='BlogPage',
  808. name='body',
  809. field=wagtail.core.fields.StreamField([('rich_text', wagtail.core.blocks.RichTextBlock())]),
  810. ),
  811. migrations.RunPython(
  812. convert_to_streamfield,
  813. convert_to_richtext,
  814. ),
  815. ]