streamfield.rst 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721
  1. .. _streamfield:
  2. How to use StreamField for mixed content
  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.models import Page
  13. from wagtail.fields import StreamField
  14. from wagtail import blocks
  15. from wagtail.admin.panels import FieldPanel
  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. ], use_json_field=True)
  25. content_panels = Page.content_panels + [
  26. FieldPanel('author'),
  27. FieldPanel('date'),
  28. FieldPanel('body'),
  29. ]
  30. In this example, the body field of ``BlogPage`` is defined as a ``StreamField`` where authors can compose content from three different block types: headings, paragraphs, and images, which can be used and repeated in any order. The block types available to authors are defined as a list of ``(name, block_type)`` tuples: 'name' is used to identify the block type within templates, and should follow the standard Python conventions for variable names: lower-case and underscores, no spaces.
  31. You can find the complete list of available block types in the :ref:`StreamField block reference <streamfield_block_reference>`.
  32. .. note::
  33. StreamField is not a direct replacement for other field types such as RichTextField. If you need to migrate an existing field to StreamField, refer to :ref:`streamfield_migrating_richtext`.
  34. .. versionchanged:: 3.0
  35. The ``use_json_field=True`` argument was added. This indicates that the database's native JSONField support should be used for this field, and is a temporary measure to assist in migrating StreamFields created on earlier Wagtail versions; it will become the default in a future release.
  36. .. _streamfield_template_rendering:
  37. Template rendering
  38. ------------------
  39. 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:
  40. .. code-block:: html+django
  41. {% load wagtailcore_tags %}
  42. ...
  43. {% include_block page.body %}
  44. 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:
  45. .. code-block:: html+django
  46. {% load wagtailcore_tags %}
  47. ...
  48. <article>
  49. {% for block in page.body %}
  50. <section>{% include_block block %}</section>
  51. {% endfor %}
  52. </article>
  53. For more control over the rendering of specific block types, each block object provides ``block_type`` and ``value`` properties:
  54. .. code-block:: html+django
  55. {% load wagtailcore_tags %}
  56. ...
  57. <article>
  58. {% for block in page.body %}
  59. {% if block.block_type == 'heading' %}
  60. <h1>{{ block.value }}</h1>
  61. {% else %}
  62. <section class="block-{{ block.block_type }}">
  63. {% include_block block %}
  64. </section>
  65. {% endif %}
  66. {% endfor %}
  67. </article>
  68. Combining blocks
  69. ----------------
  70. In addition to using the built-in block types directly within StreamField, it's possible to construct new block types by combining sub-blocks in various ways. Examples of this could include:
  71. * An "image with caption" block consisting of an image chooser and a text field
  72. * A "related links" section, where an author can provide any number of links to other pages
  73. * A slideshow block, where each slide may be an image, text or video, arranged in any order
  74. Once a new block type has been built up in this way, you can use it anywhere where a built-in block type would be used - including using it as a component for yet another block type. For example, you could define an image gallery block where each item is an "image with caption" block.
  75. StructBlock
  76. ~~~~~~~~~~~
  77. ``StructBlock`` allows you to group several 'child' blocks together to be presented as a single block. The child blocks are passed to ``StructBlock`` as a list of ``(name, block_type)`` tuples:
  78. .. code-block:: python
  79. :emphasize-lines: 2-7
  80. body = StreamField([
  81. ('person', blocks.StructBlock([
  82. ('first_name', blocks.CharBlock()),
  83. ('surname', blocks.CharBlock()),
  84. ('photo', ImageChooserBlock(required=False)),
  85. ('biography', blocks.RichTextBlock()),
  86. ])),
  87. ('heading', blocks.CharBlock(form_classname="full title")),
  88. ('paragraph', blocks.RichTextBlock()),
  89. ('image', ImageChooserBlock()),
  90. ], use_json_field=True)
  91. When reading back the content of a StreamField (such as when rendering a template), the value of a StructBlock is a dict-like object with keys corresponding to the block names given in the definition:
  92. .. code-block:: html+django
  93. <article>
  94. {% for block in page.body %}
  95. {% if block.block_type == 'person' %}
  96. <div class="person">
  97. {% image block.value.photo width-400 %}
  98. <h2>{{ block.value.first_name }} {{ block.value.surname }}</h2>
  99. {{ block.value.biography }}
  100. </div>
  101. {% else %}
  102. (rendering for other block types)
  103. {% endif %}
  104. {% endfor %}
  105. </article>
  106. Subclassing ``StructBlock``
  107. ~~~~~~~~~~~~~~~~~~~~~~~~~~~
  108. Placing a StructBlock's list of child blocks inside a ``StreamField`` definition can often be hard to read, and makes it difficult for the same block to be reused in multiple places. As an alternative, ``StructBlock`` can be subclassed, with the child blocks defined as attributes on the subclass. The 'person' block in the above example could be rewritten as:
  109. .. code-block:: python
  110. class PersonBlock(blocks.StructBlock):
  111. first_name = blocks.CharBlock()
  112. surname = blocks.CharBlock()
  113. photo = ImageChooserBlock(required=False)
  114. biography = blocks.RichTextBlock()
  115. ``PersonBlock`` can then be used in a ``StreamField`` definition in the same way as the built-in block types:
  116. .. code-block:: python
  117. body = StreamField([
  118. ('person', PersonBlock()),
  119. ('heading', blocks.CharBlock(form_classname="full title")),
  120. ('paragraph', blocks.RichTextBlock()),
  121. ('image', ImageChooserBlock()),
  122. ], use_json_field=True)
  123. Block icons
  124. ~~~~~~~~~~~
  125. In the menu that content authors use to add new blocks to a StreamField, each block type has an associated icon. For StructBlock and other structural block types, a placeholder icon is used, since the purpose of these blocks is specific to your project. To set a custom icon, pass the option ``icon`` as either a keyword argument to ``StructBlock``, or an attribute on a ``Meta`` class:
  126. .. code-block:: python
  127. :emphasize-lines: 7
  128. body = StreamField([
  129. ('person', blocks.StructBlock([
  130. ('first_name', blocks.CharBlock()),
  131. ('surname', blocks.CharBlock()),
  132. ('photo', ImageChooserBlock(required=False)),
  133. ('biography', blocks.RichTextBlock()),
  134. ], icon='user')),
  135. ('heading', blocks.CharBlock(form_classname="full title")),
  136. ('paragraph', blocks.RichTextBlock()),
  137. ('image', ImageChooserBlock()),
  138. ], use_json_field=True)
  139. .. code-block:: python
  140. :emphasize-lines: 7-8
  141. class PersonBlock(blocks.StructBlock):
  142. first_name = blocks.CharBlock()
  143. surname = blocks.CharBlock()
  144. photo = ImageChooserBlock(required=False)
  145. biography = blocks.RichTextBlock()
  146. class Meta:
  147. icon = 'user'
  148. For a list of the recognised icon identifiers, see the :ref:`styleguide`.
  149. ListBlock
  150. ~~~~~~~~~
  151. ``ListBlock`` defines a repeating block, allowing content authors to insert as many instances of a particular block type as they like. For example, a 'gallery' block consisting of multiple images can be defined as follows:
  152. .. code-block:: python
  153. :emphasize-lines: 2
  154. body = StreamField([
  155. ('gallery', blocks.ListBlock(ImageChooserBlock())),
  156. ('heading', blocks.CharBlock(form_classname="full title")),
  157. ('paragraph', blocks.RichTextBlock()),
  158. ('image', ImageChooserBlock()),
  159. ], use_json_field=True)
  160. When reading back the content of a StreamField (such as when rendering a template), the value of a ListBlock is a list of child values:
  161. .. code-block:: html+django
  162. <article>
  163. {% for block in page.body %}
  164. {% if block.block_type == 'gallery' %}
  165. <ul class="gallery">
  166. {% for img in block.value %}
  167. <li>{% image img width-400 %}</li>
  168. {% endfor %}
  169. </ul>
  170. {% else %}
  171. (rendering for other block types)
  172. {% endif %}
  173. {% endfor %}
  174. </article>
  175. StreamBlock
  176. ~~~~~~~~~~~
  177. ``StreamBlock`` defines a set of child block types that can be mixed and repeated in any sequence, via the same mechanism as StreamField itself. For example, a carousel that supports both image and video slides could be defined as follows:
  178. .. code-block:: python
  179. :emphasize-lines: 2-5
  180. body = StreamField([
  181. ('carousel', blocks.StreamBlock([
  182. ('image', ImageChooserBlock()),
  183. ('video', EmbedBlock()),
  184. ])),
  185. ('heading', blocks.CharBlock(form_classname="full title")),
  186. ('paragraph', blocks.RichTextBlock()),
  187. ('image', ImageChooserBlock()),
  188. ], use_json_field=True)
  189. ``StreamBlock`` can also be subclassed in the same way as ``StructBlock``, with the child blocks being specified as attributes on the class:
  190. .. code-block:: python
  191. class CarouselBlock(blocks.StreamBlock):
  192. image = ImageChooserBlock()
  193. video = EmbedBlock()
  194. class Meta:
  195. icon = 'image'
  196. A StreamBlock subclass defined in this way can also be passed to a ``StreamField`` definition, instead of passing a list of block types. This allows setting up a common set of block types to be used on multiple page types:
  197. .. code-block:: python
  198. class CommonContentBlock(blocks.StreamBlock):
  199. heading = blocks.CharBlock(form_classname="full title")
  200. paragraph = blocks.RichTextBlock()
  201. image = ImageChooserBlock()
  202. class BlogPage(Page):
  203. body = StreamField(CommonContentBlock(), use_json_field=True)
  204. When reading back the content of a StreamField, the value of a StreamBlock is a sequence of block objects with ``block_type`` and ``value`` properties, just like the top-level value of the StreamField itself.
  205. .. code-block:: html+django
  206. <article>
  207. {% for block in page.body %}
  208. {% if block.block_type == 'carousel' %}
  209. <ul class="carousel">
  210. {% for slide in block.value %}
  211. {% if slide.block_type == 'image' %}
  212. <li class="image">{% image slide.value width-200 %}</li>
  213. {% else %}
  214. <li class="video">{% include_block slide %}</li>
  215. {% endif %}
  216. {% endfor %}
  217. </ul>
  218. {% else %}
  219. (rendering for other block types)
  220. {% endif %}
  221. {% endfor %}
  222. </article>
  223. Limiting block counts
  224. ~~~~~~~~~~~~~~~~~~~~~
  225. By default, a StreamField can contain an unlimited number of blocks. The ``min_num`` and ``max_num`` options on ``StreamField`` or ``StreamBlock`` allow you to set a minimum or maximum number of blocks:
  226. .. code-block:: python
  227. body = StreamField([
  228. ('heading', blocks.CharBlock(form_classname="full title")),
  229. ('paragraph', blocks.RichTextBlock()),
  230. ('image', ImageChooserBlock()),
  231. ], min_num=2, max_num=5, use_json_field=True)
  232. Or equivalently:
  233. .. code-block:: python
  234. class CommonContentBlock(blocks.StreamBlock):
  235. heading = blocks.CharBlock(form_classname="full title")
  236. paragraph = blocks.RichTextBlock()
  237. image = ImageChooserBlock()
  238. class Meta:
  239. min_num = 2
  240. max_num = 5
  241. The ``block_counts`` option can be used to set a minimum or maximum count for specific block types. This accepts a dict, mapping block names to a dict containing either or both of ``min_num`` and ``max_num``. For example, to permit between 1 and 3 'heading' blocks:
  242. .. code-block:: python
  243. body = StreamField([
  244. ('heading', blocks.CharBlock(form_classname="full title")),
  245. ('paragraph', blocks.RichTextBlock()),
  246. ('image', ImageChooserBlock()),
  247. ], block_counts={
  248. 'heading': {'min_num': 1, 'max_num': 3},
  249. }, use_json_field=True)
  250. Or equivalently:
  251. .. code-block:: python
  252. class CommonContentBlock(blocks.StreamBlock):
  253. heading = blocks.CharBlock(form_classname="full title")
  254. paragraph = blocks.RichTextBlock()
  255. image = ImageChooserBlock()
  256. class Meta:
  257. block_counts = {
  258. 'heading': {'min_num': 1, 'max_num': 3},
  259. }
  260. .. _streamfield_per_block_templates:
  261. Per-block templates
  262. -------------------
  263. 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:
  264. .. code-block:: python
  265. ('person', blocks.StructBlock(
  266. [
  267. ('first_name', blocks.CharBlock()),
  268. ('surname', blocks.CharBlock()),
  269. ('photo', ImageChooserBlock(required=False)),
  270. ('biography', blocks.RichTextBlock()),
  271. ],
  272. template='myapp/blocks/person.html',
  273. icon='user'
  274. ))
  275. Or, when defined as a subclass of StructBlock:
  276. .. code-block:: python
  277. class PersonBlock(blocks.StructBlock):
  278. first_name = blocks.CharBlock()
  279. surname = blocks.CharBlock()
  280. photo = ImageChooserBlock(required=False)
  281. biography = blocks.RichTextBlock()
  282. class Meta:
  283. template = 'myapp/blocks/person.html'
  284. icon = 'user'
  285. Within the template, the block value is accessible as the variable ``value``:
  286. .. code-block:: html+django
  287. {% load wagtailimages_tags %}
  288. <div class="person">
  289. {% image value.photo width-400 %}
  290. <h2>{{ value.first_name }} {{ value.surname }}</h2>
  291. {{ value.biography }}
  292. </div>
  293. Since ``first_name``, ``surname``, ``photo`` and ``biography`` are defined as blocks in their own right, this could also be written as:
  294. .. code-block:: html+django
  295. {% load wagtailcore_tags wagtailimages_tags %}
  296. <div class="person">
  297. {% image value.photo width-400 %}
  298. <h2>{% include_block value.first_name %} {% include_block value.surname %}</h2>
  299. {% include_block value.biography %}
  300. </div>
  301. 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:
  302. .. code-block:: html+django
  303. {% load wagtailimages_tags %}
  304. <div class="person">
  305. {% image value.photo width-400 %}
  306. <h2>{{ value.first_name }} {{ value.surname }}</h2>
  307. {% if request.user.is_authenticated %}
  308. <a href="#">Contact this person</a>
  309. {% endif %}
  310. {{ value.biography }}
  311. </div>
  312. then the ``request.user.is_authenticated`` test would not work correctly when rendering the block through a ``{{ ... }}`` tag:
  313. .. code-block:: html+django
  314. {# Incorrect: #}
  315. {% for block in page.body %}
  316. {% if block.block_type == 'person' %}
  317. <div>
  318. {{ block }}
  319. </div>
  320. {% endif %}
  321. {% endfor %}
  322. {# Correct: #}
  323. {% for block in page.body %}
  324. {% if block.block_type == 'person' %}
  325. <div>
  326. {% include_block block %}
  327. </div>
  328. {% endif %}
  329. {% endfor %}
  330. 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" %}``:
  331. .. code-block:: html+django
  332. {# In page template: #}
  333. {% for block in page.body %}
  334. {% if block.block_type == 'person' %}
  335. {% include_block block with classname="important" %}
  336. {% endif %}
  337. {% endfor %}
  338. {# In PersonBlock template: #}
  339. <div class="{{ classname }}">
  340. ...
  341. </div>
  342. 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.
  343. .. _streamfield_get_context:
  344. 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:
  345. .. code-block:: python
  346. import datetime
  347. class EventBlock(blocks.StructBlock):
  348. title = blocks.CharBlock()
  349. date = blocks.DateBlock()
  350. def get_context(self, value, parent_context=None):
  351. context = super().get_context(value, parent_context=parent_context)
  352. context['is_happening_today'] = (value['date'] == datetime.date.today())
  353. return context
  354. class Meta:
  355. template = 'myapp/blocks/event.html'
  356. 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.
  357. All block types, not just ``StructBlock``, support the ``template`` property. 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. For further details, see :ref:`boundblocks_and_values`.
  358. Customisations
  359. --------------
  360. All block types implement a common API for rendering their front-end and form representations, and storing and retrieving values to and from the database. By subclassing the various block classes and overriding these methods, all kinds of customisations are possible, from modifying the layout of StructBlock form fields to implementing completely new ways of combining blocks. For further details, see :ref:`custom_streamfield_blocks`.
  361. .. _modifying_streamfield_data:
  362. Modifying StreamField data
  363. --------------------------
  364. 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.
  365. .. code-block:: python
  366. # Replace the first block with a new block of type 'heading'
  367. my_page.body[0] = ('heading', "My story")
  368. # Delete the last block
  369. del my_page.body[-1]
  370. # Append a rich text block to the stream
  371. from wagtail.rich_text import RichText
  372. my_page.body.append(('paragraph', RichText("<p>And they all lived happily ever after.</p>")))
  373. # Save the updated data back to the database
  374. my_page.save()
  375. .. _streamfield_migrating_richtext:
  376. Migrating RichTextFields to StreamField
  377. ---------------------------------------
  378. 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. 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``):
  379. .. note::
  380. This migration cannot be used if the StreamField has the ``use_json_field`` argument set to ``True``. To migrate, set the ``use_json_field`` argument to ``False`` first, migrate the data, then set it back to ``True``.
  381. .. code-block:: python
  382. # -*- coding: utf-8 -*-
  383. from django.db import models, migrations
  384. from wagtail.rich_text import RichText
  385. def convert_to_streamfield(apps, schema_editor):
  386. BlogPage = apps.get_model("demo", "BlogPage")
  387. for page in BlogPage.objects.all():
  388. if page.body.raw_text and not page.body:
  389. page.body = [('rich_text', RichText(page.body.raw_text))]
  390. page.save()
  391. def convert_to_richtext(apps, schema_editor):
  392. BlogPage = apps.get_model("demo", "BlogPage")
  393. for page in BlogPage.objects.all():
  394. if page.body.raw_text is None:
  395. raw_text = ''.join([
  396. child.value.source for child in page.body
  397. if child.block_type == 'rich_text'
  398. ])
  399. page.body = raw_text
  400. page.save()
  401. class Migration(migrations.Migration):
  402. dependencies = [
  403. # leave the dependency line from the generated migration intact!
  404. ('demo', '0001_initial'),
  405. ]
  406. operations = [
  407. # leave the generated AlterField intact!
  408. migrations.AlterField(
  409. model_name='BlogPage',
  410. name='body',
  411. field=wagtail.fields.StreamField([('rich_text', wagtail.blocks.RichTextBlock())]),
  412. ),
  413. migrations.RunPython(
  414. convert_to_streamfield,
  415. convert_to_richtext,
  416. ),
  417. ]
  418. 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:
  419. .. code-block:: python
  420. # -*- coding: utf-8 -*-
  421. import json
  422. from django.core.serializers.json import DjangoJSONEncoder
  423. from django.db import migrations, models
  424. from wagtail.rich_text import RichText
  425. def page_to_streamfield(page):
  426. changed = False
  427. if page.body.raw_text and not page.body:
  428. page.body = [('rich_text', {'rich_text': RichText(page.body.raw_text)})]
  429. changed = True
  430. return page, changed
  431. def pagerevision_to_streamfield(revision_data):
  432. changed = False
  433. body = revision_data.get('body')
  434. if body:
  435. try:
  436. json.loads(body)
  437. except ValueError:
  438. revision_data['body'] = json.dumps(
  439. [{
  440. "value": {"rich_text": body},
  441. "type": "rich_text"
  442. }],
  443. cls=DjangoJSONEncoder)
  444. changed = True
  445. else:
  446. # It's already valid JSON. Leave it.
  447. pass
  448. return revision_data, changed
  449. def page_to_richtext(page):
  450. changed = False
  451. if page.body.raw_text is None:
  452. raw_text = ''.join([
  453. child.value['rich_text'].source for child in page.body
  454. if child.block_type == 'rich_text'
  455. ])
  456. page.body = raw_text
  457. changed = True
  458. return page, changed
  459. def pagerevision_to_richtext(revision_data):
  460. changed = False
  461. body = revision_data.get('body', 'definitely non-JSON string')
  462. if body:
  463. try:
  464. body_data = json.loads(body)
  465. except ValueError:
  466. # It's not apparently a StreamField. Leave it.
  467. pass
  468. else:
  469. raw_text = ''.join([
  470. child['value']['rich_text'] for child in body_data
  471. if child['type'] == 'rich_text'
  472. ])
  473. revision_data['body'] = raw_text
  474. changed = True
  475. return revision_data, changed
  476. def convert(apps, schema_editor, page_converter, pagerevision_converter):
  477. BlogPage = apps.get_model("demo", "BlogPage")
  478. for page in BlogPage.objects.all():
  479. page, changed = page_converter(page)
  480. if changed:
  481. page.save()
  482. for revision in page.revisions.all():
  483. revision_data = revision.content
  484. revision_data, changed = pagerevision_converter(revision_data)
  485. if changed:
  486. revision.content = revision_data
  487. revision.save()
  488. def convert_to_streamfield(apps, schema_editor):
  489. return convert(apps, schema_editor, page_to_streamfield, pagerevision_to_streamfield)
  490. def convert_to_richtext(apps, schema_editor):
  491. return convert(apps, schema_editor, page_to_richtext, pagerevision_to_richtext)
  492. class Migration(migrations.Migration):
  493. dependencies = [
  494. # leave the dependency line from the generated migration intact!
  495. ('demo', '0001_initial'),
  496. ]
  497. operations = [
  498. # leave the generated AlterField intact!
  499. migrations.AlterField(
  500. model_name='BlogPage',
  501. name='body',
  502. field=wagtail.fields.StreamField([('rich_text', wagtail.blocks.RichTextBlock())]),
  503. ),
  504. migrations.RunPython(
  505. convert_to_streamfield,
  506. convert_to_richtext,
  507. ),
  508. ]