0
0
mirror of https://github.com/wagtail/wagtail.git synced 2024-11-24 10:58:52 +01:00

Remove use_json_field parameter from StreamField

This commit is contained in:
Matt Westcott 2023-10-30 14:28:43 +00:00
parent df44cf7998
commit c49ec1bb06
8 changed files with 45 additions and 74 deletions

View File

@ -24,7 +24,7 @@ class BlogPage(Page):
# ...
('heading', HeadingBlock()),
# ...
], use_json_field=True)
])
```
```html+django

View File

@ -59,7 +59,6 @@ class Migration(migrations.Migration):
name="body",
field=wagtail.fields.StreamField(
[("rich_text", wagtail.blocks.RichTextBlock())],
use_json_field=True,
),
),
]
@ -200,7 +199,6 @@ class Migration(migrations.Migration):
name="body",
field=wagtail.fields.StreamField(
[("rich_text", wagtail.blocks.RichTextBlock())],
use_json_field=True,
),
),
]

View File

@ -9,12 +9,11 @@ This document details the block types provided by Wagtail for use in [StreamFiel
```
```{eval-rst}
.. class:: wagtail.fields.StreamField(blocks, use_json_field=None, blank=False, min_num=None, max_num=None, block_counts=None, collapsed=False)
.. class:: wagtail.fields.StreamField(blocks, blank=False, min_num=None, max_num=None, block_counts=None, collapsed=False)
A model field for representing long-form content as a sequence of content blocks of various types. See :ref:`streamfield_topic`.
:param blocks: A list of block types, passed as either a list of ``(name, block_definition)`` tuples or a ``StreamBlock`` instance.
:param use_json_field: Must be set to ``True``. This causes the field to use :class:`~django.db.models.JSONField` as its internal type, allowing the use of ``JSONField`` lookups and transforms.
:param blank: When false (the default), at least one block must be provided for the field to be considered valid.
:param min_num: Minimum number of sub-blocks that the stream must have.
:param max_num: Maximum number of sub-blocks that the stream may have.
@ -30,9 +29,15 @@ body = StreamField([
], block_counts={
'heading': {'min_num': 1},
'image': {'max_num': 5},
}, use_json_field=True)
})
```
```{versionchanged} 6.0
The `use_json_field` argument is no longer required.
```
## Block options
All block definitions accept the following optional keyword arguments:
@ -414,7 +419,7 @@ All block definitions accept the following optional keyword arguments:
('photo', ImageChooserBlock(required=False)),
('biography', blocks.RichTextBlock()),
], icon='user')),
], use_json_field=True)
])
Alternatively, StructBlock can be subclassed to specify a reusable set of sub-blocks:
@ -443,7 +448,7 @@ All block definitions accept the following optional keyword arguments:
('paragraph', blocks.RichTextBlock()),
('image', ImageChooserBlock()),
('person', PersonBlock()),
], use_json_field=True)
])
The following additional options are available as either keyword arguments or Meta class attributes:
@ -466,7 +471,7 @@ All block definitions accept the following optional keyword arguments:
body = StreamField([
# ...
('ingredients_list', blocks.ListBlock(blocks.CharBlock(label="Ingredient"))),
], use_json_field=True)
])
@ -480,7 +485,7 @@ All block definitions accept the following optional keyword arguments:
('ingredient', blocks.CharBlock()),
('amount', blocks.CharBlock(required=False)),
]))),
], use_json_field=True)
])
The following additional options are available as either keyword arguments or Meta class attributes:
@ -512,7 +517,7 @@ All block definitions accept the following optional keyword arguments:
],
icon='cogs'
)),
], use_json_field=True)
])
As with StructBlock, the list of sub-blocks can also be provided as a subclass of StreamBlock:
@ -537,7 +542,6 @@ All block definitions accept the following optional keyword arguments:
class HomePage(Page):
carousel = StreamField(
CarouselBlock(max_num=10, block_counts={'video': {'max_num': 2}}),
use_json_field=True
)
``StreamBlock`` accepts the following additional options as either keyword arguments or ``Meta`` properties:
@ -559,7 +563,7 @@ All block definitions accept the following optional keyword arguments:
('hashtag', blocks.CharBlock()),
('post_date', blocks.DateBlock()),
], form_classname='event-promotions')),
], use_json_field=True)
])
.. code-block:: python
:emphasize-lines: 6

View File

@ -28,7 +28,7 @@ class BlogPage(Page):
('heading', blocks.CharBlock(form_classname="title")),
('paragraph', blocks.RichTextBlock()),
('image', ImageChooserBlock()),
], use_json_field=True)
])
content_panels = Page.content_panels + [
FieldPanel('author'),
@ -124,7 +124,7 @@ body = StreamField([
('heading', blocks.CharBlock(form_classname="title")),
('paragraph', blocks.RichTextBlock()),
('image', ImageChooserBlock()),
], use_json_field=True)
])
```
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:
@ -165,7 +165,7 @@ body = StreamField([
('heading', blocks.CharBlock(form_classname="title")),
('paragraph', blocks.RichTextBlock()),
('image', ImageChooserBlock()),
], use_json_field=True)
])
```
### Block icons
@ -185,7 +185,7 @@ body = StreamField([
('heading', blocks.CharBlock(form_classname="title")),
('paragraph', blocks.RichTextBlock()),
('image', ImageChooserBlock()),
], use_json_field=True)
])
```
```{code-block} python
@ -215,7 +215,7 @@ body = StreamField([
('heading', blocks.CharBlock(form_classname="title")),
('paragraph', blocks.RichTextBlock()),
('image', ImageChooserBlock()),
], use_json_field=True)
])
```
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:
@ -251,7 +251,7 @@ body = StreamField([
('heading', blocks.CharBlock(form_classname="title")),
('paragraph', blocks.RichTextBlock()),
('image', ImageChooserBlock()),
], use_json_field=True)
])
```
`StreamBlock` can also be subclassed in the same way as `StructBlock`, with the child blocks being specified as attributes on the class:
@ -275,7 +275,7 @@ class CommonContentBlock(blocks.StreamBlock):
class BlogPage(Page):
body = StreamField(CommonContentBlock(), use_json_field=True)
body = StreamField(CommonContentBlock())
```
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.
@ -309,7 +309,7 @@ body = StreamField([
('heading', blocks.CharBlock(form_classname="title")),
('paragraph', blocks.RichTextBlock()),
('image', ImageChooserBlock()),
], min_num=2, max_num=5, use_json_field=True)
], min_num=2, max_num=5)
```
Or equivalently:
@ -334,7 +334,7 @@ body = StreamField([
('image', ImageChooserBlock()),
], block_counts={
'heading': {'min_num': 1, 'max_num': 3},
}, use_json_field=True)
})
```
Or equivalently:

View File

@ -1,6 +1,5 @@
import json
from django.core.exceptions import ImproperlyConfigured
from django.core.serializers.json import DjangoJSONEncoder
from django.core.validators import MaxLengthValidator
from django.db import models
@ -83,7 +82,10 @@ class Creator:
class StreamField(models.Field):
def __init__(self, block_types, use_json_field=None, **kwargs):
def __init__(self, block_types, use_json_field=True, **kwargs):
# use_json_field no longer has any effect but is recognised to support historical
# migrations
# extract kwargs that are to be passed on to the block, not handled by super
block_opts = {}
for arg in ["min_num", "max_num", "block_counts", "collapsed"]:
@ -97,8 +99,6 @@ class StreamField(models.Field):
super().__init__(**kwargs)
self.use_json_field = use_json_field
if isinstance(block_types, Block):
# use the passed block as the top-level block
self.stream_block = block_types
@ -115,13 +115,6 @@ class StreamField(models.Field):
def json_field(self):
return models.JSONField(encoder=DjangoJSONEncoder)
def _check_json_field(self):
if self.use_json_field is not True:
# RemovedInWagtail60Warning - make use_json_field optional and default to True
raise ImproperlyConfigured(
"StreamField must explicitly set use_json_field=True"
)
def get_internal_type(self):
return "JSONField"
@ -135,7 +128,6 @@ class StreamField(models.Field):
name, path, _, kwargs = super().deconstruct()
block_types = list(self.stream_block.child_blocks.items())
args = [block_types]
kwargs["use_json_field"] = self.use_json_field
return name, path, args, kwargs
def to_python(self, value):
@ -273,11 +265,6 @@ class StreamField(models.Field):
def contribute_to_class(self, cls, name, **kwargs):
super().contribute_to_class(cls, name, **kwargs)
# Output error on missing use_json_field=True argument, unless this is a fake model
# for a migration
if cls.__module__ != "__fake__":
self._check_json_field()
# Add Creator descriptor to allow the field to be set from a list or a
# JSON string.
setattr(cls, self.name, Creator(self))

View File

@ -42,8 +42,8 @@ class BaseStreamBlock(StreamBlock):
class SampleModel(models.Model):
content = StreamField(BaseStreamBlock(), use_json_field=True)
content = StreamField(BaseStreamBlock())
class SamplePage(Page):
content = StreamField(BaseStreamBlock(), use_json_field=True)
content = StreamField(BaseStreamBlock())

View File

@ -1241,7 +1241,6 @@ class VariousOnDeleteModel(models.Model):
("image", ImageChooserBlock()),
("document", DocumentChooserBlock()),
],
use_json_field=True,
)
rich_text = RichTextField(blank=True)
@ -1451,7 +1450,6 @@ class JSONStreamModel(models.Model):
("rich_text", RichTextBlock()),
("image", ImageChooserBlock()),
],
use_json_field=True,
)
@ -1464,7 +1462,6 @@ class JSONMinMaxCountStreamModel(models.Model):
],
min_num=2,
max_num=5,
use_json_field=True,
)
@ -1480,7 +1477,6 @@ class JSONBlockCountsStreamModel(models.Model):
"rich_text": {"max_num": 1},
"image": {"min_num": 1, "max_num": 1},
},
use_json_field=True,
)
@ -1530,7 +1526,6 @@ class StreamPage(Page):
ListBlock(CharBlock()),
),
],
use_json_field=True,
)
api_fields = ("body",)
@ -1551,7 +1546,6 @@ class DefaultStreamPage(Page):
("image", ImageChooserBlock()),
],
default="",
use_json_field=True,
)
content_panels = [
@ -1789,7 +1783,6 @@ class DefaultRichBlockFieldPage(Page):
[
("rich_text", RichTextBlock()),
],
use_json_field=True,
)
content_panels = Page.content_panels + [FieldPanel("body")]
@ -1809,7 +1802,6 @@ class CustomRichBlockFieldPage(Page):
[
("rich_text", RichTextBlock(editor="custom")),
],
use_json_field=True,
)
content_panels = [
@ -1855,7 +1847,6 @@ class InlineStreamPageSection(Orderable):
("rich_text", RichTextBlock()),
("image", ImageChooserBlock()),
],
use_json_field=True,
)
panels = [FieldPanel("body")]
@ -1868,7 +1859,7 @@ class InlineStreamPage(Page):
class TableBlockStreamPage(Page):
table = StreamField([("table", TableBlock())], use_json_field=True)
table = StreamField([("table", TableBlock())])
content_panels = [FieldPanel("table")]
@ -1911,15 +1902,15 @@ class AlwaysShowInMenusPage(Page):
# test for AddField migrations on StreamFields using various default values
class AddedStreamFieldWithoutDefaultPage(Page):
body = StreamField([("title", CharBlock())], use_json_field=True)
body = StreamField([("title", CharBlock())])
class AddedStreamFieldWithEmptyStringDefaultPage(Page):
body = StreamField([("title", CharBlock())], default="", use_json_field=True)
body = StreamField([("title", CharBlock())], default="")
class AddedStreamFieldWithEmptyListDefaultPage(Page):
body = StreamField([("title", CharBlock())], default=[], use_json_field=True)
body = StreamField([("title", CharBlock())], default=[])
class SecretPage(Page):
@ -2047,7 +2038,6 @@ class DeadlyStreamPage(Page):
[
("title", DeadlyCharBlock()),
],
use_json_field=True,
)
content_panels = Page.content_panels + [
FieldPanel("body"),

View File

@ -198,7 +198,6 @@ class TestSystemCheck(TestCase):
("heading", blocks.CharBlock()),
("rich text", blocks.RichTextBlock()),
],
use_json_field=True,
)
errors = InvalidStreamModel.check()
@ -308,7 +307,6 @@ class TestRequiredStreamField(TestCase):
field = StreamField(
[("paragraph", blocks.CharBlock())],
blank=False,
use_json_field=True,
)
self.assertTrue(field.stream_block.required)
with self.assertRaises(StreamBlockValidationError):
@ -321,7 +319,7 @@ class TestRequiredStreamField(TestCase):
required = False
# passing a block instance
field = StreamField(MyStreamBlock(), blank=False, use_json_field=True)
field = StreamField(MyStreamBlock(), blank=False)
self.assertTrue(field.stream_block.required)
with self.assertRaises(StreamBlockValidationError):
field.stream_block.clean([])
@ -329,21 +327,20 @@ class TestRequiredStreamField(TestCase):
field = StreamField(
MyStreamBlock(required=False),
blank=False,
use_json_field=True,
)
self.assertTrue(field.stream_block.required)
with self.assertRaises(StreamBlockValidationError):
field.stream_block.clean([])
# passing a block class
field = StreamField(MyStreamBlock, blank=False, use_json_field=True)
field = StreamField(MyStreamBlock, blank=False)
self.assertTrue(field.stream_block.required)
with self.assertRaises(StreamBlockValidationError):
field.stream_block.clean([])
def test_blank_false_is_implied_by_default(self):
# passing a block list
field = StreamField([("paragraph", blocks.CharBlock())], use_json_field=True)
field = StreamField([("paragraph", blocks.CharBlock())])
self.assertTrue(field.stream_block.required)
with self.assertRaises(StreamBlockValidationError):
field.stream_block.clean([])
@ -355,18 +352,18 @@ class TestRequiredStreamField(TestCase):
required = False
# passing a block instance
field = StreamField(MyStreamBlock(), use_json_field=True)
field = StreamField(MyStreamBlock())
self.assertTrue(field.stream_block.required)
with self.assertRaises(StreamBlockValidationError):
field.stream_block.clean([])
field = StreamField(MyStreamBlock(required=False), use_json_field=True)
field = StreamField(MyStreamBlock(required=False))
self.assertTrue(field.stream_block.required)
with self.assertRaises(StreamBlockValidationError):
field.stream_block.clean([])
# passing a block class
field = StreamField(MyStreamBlock, use_json_field=True)
field = StreamField(MyStreamBlock)
self.assertTrue(field.stream_block.required)
with self.assertRaises(StreamBlockValidationError):
field.stream_block.clean([])
@ -376,7 +373,6 @@ class TestRequiredStreamField(TestCase):
field = StreamField(
[("paragraph", blocks.CharBlock())],
blank=True,
use_json_field=True,
)
self.assertFalse(field.stream_block.required)
field.stream_block.clean([]) # no validation error on empty stream
@ -388,18 +384,16 @@ class TestRequiredStreamField(TestCase):
required = True
# passing a block instance
field = StreamField(MyStreamBlock(), blank=True, use_json_field=True)
field = StreamField(MyStreamBlock(), blank=True)
self.assertFalse(field.stream_block.required)
field.stream_block.clean([]) # no validation error on empty stream
field = StreamField(
MyStreamBlock(required=True), blank=True, use_json_field=True
)
field = StreamField(MyStreamBlock(required=True), blank=True)
self.assertFalse(field.stream_block.required)
field.stream_block.clean([]) # no validation error on empty stream
# passing a block class
field = StreamField(MyStreamBlock, blank=True, use_json_field=True)
field = StreamField(MyStreamBlock, blank=True)
self.assertFalse(field.stream_block.required)
field.stream_block.clean([]) # no validation error on empty stream
@ -536,7 +530,7 @@ class TestStreamFieldCountValidation(TestCase):
block_counts = {"heading": {"max_num": 1}}
# args being picked up from the class definition
field = StreamField(TestStreamBlock, use_json_field=True)
field = StreamField(TestStreamBlock)
self.assertEqual(field.stream_block.meta.min_num, 2)
self.assertEqual(field.stream_block.meta.max_num, 5)
self.assertEqual(field.stream_block.meta.block_counts["heading"]["max_num"], 1)
@ -547,7 +541,6 @@ class TestStreamFieldCountValidation(TestCase):
min_num=3,
max_num=6,
block_counts={"heading": {"max_num": 2}},
use_json_field=True,
)
self.assertEqual(field.stream_block.meta.min_num, 3)
self.assertEqual(field.stream_block.meta.max_num, 6)
@ -559,7 +552,6 @@ class TestStreamFieldCountValidation(TestCase):
min_num=None,
max_num=None,
block_counts=None,
use_json_field=True,
)
self.assertIsNone(field.stream_block.meta.min_num)
self.assertIsNone(field.stream_block.meta.max_num)
@ -575,7 +567,7 @@ class TestJSONStreamField(TestCase):
)
def test_internal_type(self):
json = StreamField([("paragraph", blocks.CharBlock())], use_json_field=True)
json = StreamField([("paragraph", blocks.CharBlock())])
self.assertEqual(json.get_internal_type(), "JSONField")
def test_json_body_equals_to_text_body(self):