kopia lustrzana https://github.com/wagtail/wagtail
Remove support for use_json_field=False StreamFields
use_json_field=False (outside of migrations) now throws an ImproperlyConfigured exception. In Wagtail 6.0 this will be changed to default to True, and user code can remove it - we cannot do this in 5.0 because this would mean that site owners who have not already generated the migrations to change legacy StreamFields to use_json_field=True will be unable to do so (since makemigrations will see no changes).pull/10060/head
rodzic
619f395980
commit
5c653002a2
|
@ -1,6 +1,6 @@
|
|||
import json
|
||||
import warnings
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.core.validators import MaxLengthValidator
|
||||
from django.db import models
|
||||
|
@ -13,7 +13,6 @@ from wagtail.rich_text import (
|
|||
extract_references_from_rich_text,
|
||||
get_text_for_indexing,
|
||||
)
|
||||
from wagtail.utils.deprecation import RemovedInWagtail50Warning
|
||||
|
||||
|
||||
class RichTextField(models.TextField):
|
||||
|
@ -117,25 +116,20 @@ class StreamField(models.Field):
|
|||
return models.JSONField(encoder=DjangoJSONEncoder)
|
||||
|
||||
def _check_json_field(self):
|
||||
if type(self.use_json_field) is not bool:
|
||||
warnings.warn(
|
||||
f"StreamField must explicitly set use_json_field argument to True/False instead of {self.use_json_field}.",
|
||||
RemovedInWagtail50Warning,
|
||||
stacklevel=3,
|
||||
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" if self.use_json_field else "TextField"
|
||||
return "JSONField"
|
||||
|
||||
def get_lookup(self, lookup_name):
|
||||
if self.use_json_field:
|
||||
return self.json_field.get_lookup(lookup_name)
|
||||
return super().get_lookup(lookup_name)
|
||||
return self.json_field.get_lookup(lookup_name)
|
||||
|
||||
def get_transform(self, lookup_name):
|
||||
if self.use_json_field:
|
||||
return self.json_field.get_transform(lookup_name)
|
||||
return super().get_transform(lookup_name)
|
||||
return self.json_field.get_transform(lookup_name)
|
||||
|
||||
def deconstruct(self):
|
||||
name, path, _, kwargs = super().deconstruct()
|
||||
|
@ -167,12 +161,7 @@ class StreamField(models.Field):
|
|||
return StreamValue(self.stream_block, [])
|
||||
|
||||
return self.stream_block.to_python(unpacked_value)
|
||||
elif (
|
||||
self.use_json_field
|
||||
and value
|
||||
and isinstance(value, list)
|
||||
and isinstance(value[0], dict)
|
||||
):
|
||||
elif value and isinstance(value, list) and isinstance(value[0], dict):
|
||||
# The value is already unpacked since JSONField-based StreamField should
|
||||
# accept deserialised values (no need to call json.dumps() first).
|
||||
# In addition, the value is not a list of (block_name, value) tuples
|
||||
|
@ -204,9 +193,8 @@ class StreamField(models.Field):
|
|||
# for reverse migrations that convert StreamField data back into plain text
|
||||
# fields.)
|
||||
return value.raw_text
|
||||
elif isinstance(value, StreamValue) or not self.use_json_field:
|
||||
elif isinstance(value, StreamValue):
|
||||
# StreamValue instances must be prepared first.
|
||||
# Before use_json_field was implemented, this is also the value used in queries.
|
||||
return json.dumps(
|
||||
self.stream_block.get_prep_value(value), cls=DjangoJSONEncoder
|
||||
)
|
||||
|
@ -217,7 +205,7 @@ class StreamField(models.Field):
|
|||
return self.json_field.get_prep_value(value)
|
||||
|
||||
def get_db_prep_value(self, value, connection, prepared=False):
|
||||
if self.use_json_field and not isinstance(value, StreamValue):
|
||||
if not isinstance(value, StreamValue):
|
||||
# When querying with JSONField features, the rhs might not be a StreamValue.
|
||||
# As of Django 4.2, JSONField value serialisation is handled in
|
||||
# get_db_prep_value instead of get_prep_value.
|
||||
|
@ -225,7 +213,7 @@ class StreamField(models.Field):
|
|||
return super().get_db_prep_value(value, connection, prepared)
|
||||
|
||||
def from_db_value(self, value, expression, connection):
|
||||
if self.use_json_field and isinstance(expression, KeyTransform):
|
||||
if isinstance(expression, KeyTransform):
|
||||
# This could happen when using JSONField key transforms,
|
||||
# e.g. Page.object.values('body__0').
|
||||
try:
|
||||
|
@ -266,7 +254,7 @@ class StreamField(models.Field):
|
|||
def contribute_to_class(self, cls, name, **kwargs):
|
||||
super().contribute_to_class(cls, name, **kwargs)
|
||||
|
||||
# Output deprecation warning on missing use_json_field argument, unless this is a fake model
|
||||
# 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()
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
# Generated by Django 4.1.5 on 2023-01-31 15:35
|
||||
|
||||
from django.db import migrations
|
||||
import wagtail.blocks
|
||||
import wagtail.contrib.table_block.blocks
|
||||
import wagtail.fields
|
||||
import wagtail.images.blocks
|
||||
import wagtail.test.testapp.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("tests", "0018_alter_streampage_body"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name="BlockCountsStreamModel",
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name="MinMaxCountStreamModel",
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name="StreamModel",
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="addedstreamfieldwithemptylistdefaultpage",
|
||||
name="body",
|
||||
field=wagtail.fields.StreamField(
|
||||
[("title", wagtail.blocks.CharBlock())], default=[], use_json_field=True
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="addedstreamfieldwithemptystringdefaultpage",
|
||||
name="body",
|
||||
field=wagtail.fields.StreamField(
|
||||
[("title", wagtail.blocks.CharBlock())], default="", use_json_field=True
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="addedstreamfieldwithoutdefaultpage",
|
||||
name="body",
|
||||
field=wagtail.fields.StreamField(
|
||||
[("title", wagtail.blocks.CharBlock())], use_json_field=True
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="customrichblockfieldpage",
|
||||
name="body",
|
||||
field=wagtail.fields.StreamField(
|
||||
[("rich_text", wagtail.blocks.RichTextBlock(editor="custom"))],
|
||||
use_json_field=True,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="deadlystreampage",
|
||||
name="body",
|
||||
field=wagtail.fields.StreamField(
|
||||
[("title", wagtail.test.testapp.models.DeadlyCharBlock())],
|
||||
use_json_field=True,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="defaultrichblockfieldpage",
|
||||
name="body",
|
||||
field=wagtail.fields.StreamField(
|
||||
[("rich_text", wagtail.blocks.RichTextBlock())], use_json_field=True
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="defaultstreampage",
|
||||
name="body",
|
||||
field=wagtail.fields.StreamField(
|
||||
[
|
||||
("text", wagtail.blocks.CharBlock()),
|
||||
("rich_text", wagtail.blocks.RichTextBlock()),
|
||||
("image", wagtail.images.blocks.ImageChooserBlock()),
|
||||
],
|
||||
default="",
|
||||
use_json_field=True,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="inlinestreampagesection",
|
||||
name="body",
|
||||
field=wagtail.fields.StreamField(
|
||||
[
|
||||
("text", wagtail.blocks.CharBlock()),
|
||||
("rich_text", wagtail.blocks.RichTextBlock()),
|
||||
("image", wagtail.images.blocks.ImageChooserBlock()),
|
||||
],
|
||||
use_json_field=True,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="streampage",
|
||||
name="body",
|
||||
field=wagtail.fields.StreamField(
|
||||
[
|
||||
("text", wagtail.blocks.CharBlock()),
|
||||
("rich_text", wagtail.blocks.RichTextBlock()),
|
||||
("image", wagtail.test.testapp.models.ExtendedImageChooserBlock()),
|
||||
(
|
||||
"product",
|
||||
wagtail.blocks.StructBlock(
|
||||
[
|
||||
("name", wagtail.blocks.CharBlock()),
|
||||
("price", wagtail.blocks.CharBlock()),
|
||||
]
|
||||
),
|
||||
),
|
||||
("raw_html", wagtail.blocks.RawHTMLBlock()),
|
||||
(
|
||||
"books",
|
||||
wagtail.blocks.StreamBlock(
|
||||
[
|
||||
("title", wagtail.blocks.CharBlock()),
|
||||
("author", wagtail.blocks.CharBlock()),
|
||||
]
|
||||
),
|
||||
),
|
||||
(
|
||||
"title_list",
|
||||
wagtail.blocks.ListBlock(wagtail.blocks.CharBlock()),
|
||||
),
|
||||
],
|
||||
use_json_field=True,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="tableblockstreampage",
|
||||
name="table",
|
||||
field=wagtail.fields.StreamField(
|
||||
[("table", wagtail.contrib.table_block.blocks.TableBlock())],
|
||||
use_json_field=True,
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1327,17 +1327,6 @@ class CustomDocumentWithAuthor(AbstractDocument):
|
|||
admin_form_fields = Document.admin_form_fields + ("author",)
|
||||
|
||||
|
||||
class StreamModel(models.Model):
|
||||
body = StreamField(
|
||||
[
|
||||
("text", CharBlock()),
|
||||
("rich_text", RichTextBlock()),
|
||||
("image", ImageChooserBlock()),
|
||||
],
|
||||
use_json_field=False,
|
||||
)
|
||||
|
||||
|
||||
class JSONStreamModel(models.Model):
|
||||
body = StreamField(
|
||||
[
|
||||
|
@ -1349,19 +1338,6 @@ class JSONStreamModel(models.Model):
|
|||
)
|
||||
|
||||
|
||||
class MinMaxCountStreamModel(models.Model):
|
||||
body = StreamField(
|
||||
[
|
||||
("text", CharBlock()),
|
||||
("rich_text", RichTextBlock()),
|
||||
("image", ImageChooserBlock()),
|
||||
],
|
||||
min_num=2,
|
||||
max_num=5,
|
||||
use_json_field=False,
|
||||
)
|
||||
|
||||
|
||||
class JSONMinMaxCountStreamModel(models.Model):
|
||||
body = StreamField(
|
||||
[
|
||||
|
@ -1375,22 +1351,6 @@ class JSONMinMaxCountStreamModel(models.Model):
|
|||
)
|
||||
|
||||
|
||||
class BlockCountsStreamModel(models.Model):
|
||||
body = StreamField(
|
||||
[
|
||||
("text", CharBlock()),
|
||||
("rich_text", RichTextBlock()),
|
||||
("image", ImageChooserBlock()),
|
||||
],
|
||||
block_counts={
|
||||
"text": {"min_num": 1},
|
||||
"rich_text": {"max_num": 1},
|
||||
"image": {"min_num": 1, "max_num": 1},
|
||||
},
|
||||
use_json_field=False,
|
||||
)
|
||||
|
||||
|
||||
class JSONBlockCountsStreamModel(models.Model):
|
||||
body = StreamField(
|
||||
[
|
||||
|
@ -1453,7 +1413,7 @@ class StreamPage(Page):
|
|||
ListBlock(CharBlock()),
|
||||
),
|
||||
],
|
||||
use_json_field=False,
|
||||
use_json_field=True,
|
||||
)
|
||||
|
||||
api_fields = ("body",)
|
||||
|
@ -1474,7 +1434,7 @@ class DefaultStreamPage(Page):
|
|||
("image", ImageChooserBlock()),
|
||||
],
|
||||
default="",
|
||||
use_json_field=False,
|
||||
use_json_field=True,
|
||||
)
|
||||
|
||||
content_panels = [
|
||||
|
@ -1708,7 +1668,7 @@ class DefaultRichBlockFieldPage(Page):
|
|||
[
|
||||
("rich_text", RichTextBlock()),
|
||||
],
|
||||
use_json_field=False,
|
||||
use_json_field=True,
|
||||
)
|
||||
|
||||
content_panels = Page.content_panels + [FieldPanel("body")]
|
||||
|
@ -1728,7 +1688,7 @@ class CustomRichBlockFieldPage(Page):
|
|||
[
|
||||
("rich_text", RichTextBlock(editor="custom")),
|
||||
],
|
||||
use_json_field=False,
|
||||
use_json_field=True,
|
||||
)
|
||||
|
||||
content_panels = [
|
||||
|
@ -1774,7 +1734,7 @@ class InlineStreamPageSection(Orderable):
|
|||
("rich_text", RichTextBlock()),
|
||||
("image", ImageChooserBlock()),
|
||||
],
|
||||
use_json_field=False,
|
||||
use_json_field=True,
|
||||
)
|
||||
panels = [FieldPanel("body")]
|
||||
|
||||
|
@ -1787,7 +1747,7 @@ class InlineStreamPage(Page):
|
|||
|
||||
|
||||
class TableBlockStreamPage(Page):
|
||||
table = StreamField([("table", TableBlock())], use_json_field=False)
|
||||
table = StreamField([("table", TableBlock())], use_json_field=True)
|
||||
|
||||
content_panels = [FieldPanel("table")]
|
||||
|
||||
|
@ -1830,15 +1790,15 @@ class AlwaysShowInMenusPage(Page):
|
|||
|
||||
# test for AddField migrations on StreamFields using various default values
|
||||
class AddedStreamFieldWithoutDefaultPage(Page):
|
||||
body = StreamField([("title", CharBlock())], use_json_field=False)
|
||||
body = StreamField([("title", CharBlock())], use_json_field=True)
|
||||
|
||||
|
||||
class AddedStreamFieldWithEmptyStringDefaultPage(Page):
|
||||
body = StreamField([("title", CharBlock())], default="", use_json_field=False)
|
||||
body = StreamField([("title", CharBlock())], default="", use_json_field=True)
|
||||
|
||||
|
||||
class AddedStreamFieldWithEmptyListDefaultPage(Page):
|
||||
body = StreamField([("title", CharBlock())], default=[], use_json_field=False)
|
||||
body = StreamField([("title", CharBlock())], default=[], use_json_field=True)
|
||||
|
||||
|
||||
class SecretPage(Page):
|
||||
|
@ -1966,7 +1926,7 @@ class DeadlyStreamPage(Page):
|
|||
[
|
||||
("title", DeadlyCharBlock()),
|
||||
],
|
||||
use_json_field=False,
|
||||
use_json_field=True,
|
||||
)
|
||||
content_panels = Page.content_panels + [
|
||||
FieldPanel("body"),
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# -*- coding: utf-8 -*
|
||||
import json
|
||||
from unittest import skip
|
||||
|
||||
from django.apps import apps
|
||||
from django.db import connection, models
|
||||
|
@ -16,18 +15,14 @@ from wagtail.images.tests.utils import get_test_image_file
|
|||
from wagtail.rich_text import RichText
|
||||
from wagtail.signal_handlers import disable_reference_index_auto_update
|
||||
from wagtail.test.testapp.models import (
|
||||
BlockCountsStreamModel,
|
||||
JSONBlockCountsStreamModel,
|
||||
JSONMinMaxCountStreamModel,
|
||||
JSONStreamModel,
|
||||
MinMaxCountStreamModel,
|
||||
StreamModel,
|
||||
)
|
||||
from wagtail.utils.deprecation import RemovedInWagtail50Warning
|
||||
|
||||
|
||||
class TestLazyStreamField(TestCase):
|
||||
model = StreamModel
|
||||
model = JSONStreamModel
|
||||
|
||||
def setUp(self):
|
||||
self.image = Image.objects.create(
|
||||
|
@ -183,13 +178,7 @@ class TestLazyStreamField(TestCase):
|
|||
instance.save()
|
||||
|
||||
|
||||
class TestJSONLazyStreamField(TestLazyStreamField):
|
||||
model = JSONStreamModel
|
||||
|
||||
|
||||
class TestSystemCheck(TestCase):
|
||||
use_json_field = False
|
||||
|
||||
def tearDown(self):
|
||||
# unregister InvalidStreamModel from the overall model registry
|
||||
# so that it doesn't break tests elsewhere
|
||||
|
@ -207,7 +196,7 @@ class TestSystemCheck(TestCase):
|
|||
("heading", blocks.CharBlock()),
|
||||
("rich text", blocks.RichTextBlock()),
|
||||
],
|
||||
use_json_field=self.use_json_field,
|
||||
use_json_field=True,
|
||||
)
|
||||
|
||||
errors = InvalidStreamModel.check()
|
||||
|
@ -217,57 +206,18 @@ class TestSystemCheck(TestCase):
|
|||
self.assertEqual(errors[0].obj, InvalidStreamModel._meta.get_field("body"))
|
||||
|
||||
|
||||
class TestJSONSystemCheck(TestSystemCheck):
|
||||
use_json_field = True
|
||||
|
||||
|
||||
class TestUseJsonFieldWarning(TestCase):
|
||||
def tearDown(self):
|
||||
# unregister DeprecatedStreamModel from the overall model registry
|
||||
# so that it doesn't break tests elsewhere
|
||||
for package in ("wagtailcore", "wagtail.tests"):
|
||||
try:
|
||||
del apps.all_models[package]["deprecatedstreammodel"]
|
||||
except KeyError:
|
||||
pass
|
||||
apps.clear_cache()
|
||||
|
||||
def test_system_check_validates_block(self):
|
||||
message = "StreamField must explicitly set use_json_field argument to True/False instead of None."
|
||||
with self.assertWarnsMessage(RemovedInWagtail50Warning, message):
|
||||
|
||||
class DeprecatedStreamModel(models.Model):
|
||||
body = StreamField(
|
||||
[
|
||||
("heading", blocks.CharBlock()),
|
||||
("rich text", blocks.RichTextBlock()),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
class TestStreamValueAccess(TestCase):
|
||||
model = StreamModel
|
||||
|
||||
def setUp(self):
|
||||
self.json_body = self.model.objects.create(
|
||||
self.json_body = JSONStreamModel.objects.create(
|
||||
body=json.dumps([{"type": "text", "value": "foo"}])
|
||||
)
|
||||
|
||||
def test_can_read_non_json_content(self):
|
||||
"""StreamField columns should handle non-JSON database content gracefully"""
|
||||
nonjson_body = self.model.objects.create(body="<h1>hello world</h1>")
|
||||
self.assertIsInstance(nonjson_body.body, StreamValue)
|
||||
# the main list-like content of the StreamValue should be blank
|
||||
self.assertFalse(nonjson_body.body)
|
||||
# the unparsed text content should be available in raw_text
|
||||
self.assertEqual(nonjson_body.body.raw_text, "<h1>hello world</h1>")
|
||||
|
||||
def test_can_assign_as_list(self):
|
||||
self.json_body.body = [("rich_text", RichText("<h2>hello world</h2>"))]
|
||||
self.json_body.save()
|
||||
|
||||
# the body should now be a stream consisting of a single rich_text block
|
||||
fetched_body = self.model.objects.get(id=self.json_body.id).body
|
||||
fetched_body = JSONStreamModel.objects.get(id=self.json_body.id).body
|
||||
self.assertIsInstance(fetched_body, StreamValue)
|
||||
self.assertEqual(len(fetched_body), 1)
|
||||
self.assertIsInstance(fetched_body[0].value, RichText)
|
||||
|
@ -277,7 +227,7 @@ class TestStreamValueAccess(TestCase):
|
|||
self.json_body.body.append(("text", "bar"))
|
||||
self.json_body.save()
|
||||
|
||||
fetched_body = self.model.objects.get(id=self.json_body.id).body
|
||||
fetched_body = JSONStreamModel.objects.get(id=self.json_body.id).body
|
||||
self.assertIsInstance(fetched_body, StreamValue)
|
||||
self.assertEqual(len(fetched_body), 2)
|
||||
self.assertEqual(fetched_body[0].block_type, "text")
|
||||
|
@ -286,16 +236,8 @@ class TestStreamValueAccess(TestCase):
|
|||
self.assertEqual(fetched_body[1].value, "bar")
|
||||
|
||||
|
||||
class TestJSONStreamValueAccess(TestStreamValueAccess):
|
||||
model = JSONStreamModel
|
||||
|
||||
@skip("JSONField-based StreamField does not support storing non-json content.")
|
||||
def test_can_read_non_json_content(self):
|
||||
pass
|
||||
|
||||
|
||||
class TestStreamFieldRenderingBase(TestCase):
|
||||
model = StreamModel
|
||||
model = JSONStreamModel
|
||||
|
||||
def setUp(self):
|
||||
self.image = Image.objects.create(
|
||||
|
@ -336,10 +278,6 @@ class TestStreamFieldRendering(TestStreamFieldRenderingBase):
|
|||
self.assertIsInstance(rendered, SafeString)
|
||||
|
||||
|
||||
class TestJSONStreamFieldRendering(TestStreamFieldRendering):
|
||||
model = JSONStreamModel
|
||||
|
||||
|
||||
class TestStreamFieldDjangoRendering(TestStreamFieldRenderingBase):
|
||||
def render(self, string, context):
|
||||
return Template(string).render(Context(context))
|
||||
|
@ -349,10 +287,6 @@ class TestStreamFieldDjangoRendering(TestStreamFieldRenderingBase):
|
|||
self.assertHTMLEqual(rendered, self.expected)
|
||||
|
||||
|
||||
class TestJSONStreamFieldDjangoRendering(TestStreamFieldDjangoRendering):
|
||||
model = JSONStreamModel
|
||||
|
||||
|
||||
class TestStreamFieldJinjaRendering(TestStreamFieldRenderingBase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
@ -366,19 +300,13 @@ class TestStreamFieldJinjaRendering(TestStreamFieldRenderingBase):
|
|||
self.assertHTMLEqual(rendered, self.expected)
|
||||
|
||||
|
||||
class TestJSONStreamFieldJinjaRendering(TestStreamFieldJinjaRendering):
|
||||
model = JSONStreamModel
|
||||
|
||||
|
||||
class TestRequiredStreamField(TestCase):
|
||||
use_json_field = False
|
||||
|
||||
def test_non_blank_field_is_required(self):
|
||||
# passing a block list
|
||||
field = StreamField(
|
||||
[("paragraph", blocks.CharBlock())],
|
||||
blank=False,
|
||||
use_json_field=self.use_json_field,
|
||||
use_json_field=True,
|
||||
)
|
||||
self.assertTrue(field.stream_block.required)
|
||||
with self.assertRaises(StreamBlockValidationError):
|
||||
|
@ -391,9 +319,7 @@ class TestRequiredStreamField(TestCase):
|
|||
required = False
|
||||
|
||||
# passing a block instance
|
||||
field = StreamField(
|
||||
MyStreamBlock(), blank=False, use_json_field=self.use_json_field
|
||||
)
|
||||
field = StreamField(MyStreamBlock(), blank=False, use_json_field=True)
|
||||
self.assertTrue(field.stream_block.required)
|
||||
with self.assertRaises(StreamBlockValidationError):
|
||||
field.stream_block.clean([])
|
||||
|
@ -401,25 +327,21 @@ class TestRequiredStreamField(TestCase):
|
|||
field = StreamField(
|
||||
MyStreamBlock(required=False),
|
||||
blank=False,
|
||||
use_json_field=self.use_json_field,
|
||||
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=self.use_json_field
|
||||
)
|
||||
field = StreamField(MyStreamBlock, blank=False, use_json_field=True)
|
||||
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=self.use_json_field
|
||||
)
|
||||
field = StreamField([("paragraph", blocks.CharBlock())], use_json_field=True)
|
||||
self.assertTrue(field.stream_block.required)
|
||||
with self.assertRaises(StreamBlockValidationError):
|
||||
field.stream_block.clean([])
|
||||
|
@ -431,20 +353,18 @@ class TestRequiredStreamField(TestCase):
|
|||
required = False
|
||||
|
||||
# passing a block instance
|
||||
field = StreamField(MyStreamBlock(), use_json_field=self.use_json_field)
|
||||
field = StreamField(MyStreamBlock(), use_json_field=True)
|
||||
self.assertTrue(field.stream_block.required)
|
||||
with self.assertRaises(StreamBlockValidationError):
|
||||
field.stream_block.clean([])
|
||||
|
||||
field = StreamField(
|
||||
MyStreamBlock(required=False), use_json_field=self.use_json_field
|
||||
)
|
||||
field = StreamField(MyStreamBlock(required=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, use_json_field=self.use_json_field)
|
||||
field = StreamField(MyStreamBlock, use_json_field=True)
|
||||
self.assertTrue(field.stream_block.required)
|
||||
with self.assertRaises(StreamBlockValidationError):
|
||||
field.stream_block.clean([])
|
||||
|
@ -454,7 +374,7 @@ class TestRequiredStreamField(TestCase):
|
|||
field = StreamField(
|
||||
[("paragraph", blocks.CharBlock())],
|
||||
blank=True,
|
||||
use_json_field=self.use_json_field,
|
||||
use_json_field=True,
|
||||
)
|
||||
self.assertFalse(field.stream_block.required)
|
||||
field.stream_block.clean([]) # no validation error on empty stream
|
||||
|
@ -466,35 +386,23 @@ class TestRequiredStreamField(TestCase):
|
|||
required = True
|
||||
|
||||
# passing a block instance
|
||||
field = StreamField(
|
||||
MyStreamBlock(), blank=True, use_json_field=self.use_json_field
|
||||
)
|
||||
field = StreamField(MyStreamBlock(), blank=True, use_json_field=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=self.use_json_field
|
||||
MyStreamBlock(required=True), blank=True, use_json_field=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=self.use_json_field
|
||||
)
|
||||
field = StreamField(MyStreamBlock, blank=True, use_json_field=True)
|
||||
self.assertFalse(field.stream_block.required)
|
||||
field.stream_block.clean([]) # no validation error on empty stream
|
||||
|
||||
|
||||
class TestJSONRequiredStreamField(TestRequiredStreamField):
|
||||
use_json_field = True
|
||||
|
||||
|
||||
class TestStreamFieldCountValidation(TestCase):
|
||||
min_max_count_model = MinMaxCountStreamModel
|
||||
block_counts_model = BlockCountsStreamModel
|
||||
use_json_field = False
|
||||
|
||||
def setUp(self):
|
||||
self.image = Image.objects.create(
|
||||
title="Test image", file=get_test_image_file()
|
||||
|
@ -505,14 +413,14 @@ class TestStreamFieldCountValidation(TestCase):
|
|||
self.text_body = {"type": "text", "value": "Hello, World!"}
|
||||
|
||||
def test_minmax_pass_to_block(self):
|
||||
instance = self.min_max_count_model.objects.create(body=json.dumps([]))
|
||||
instance = JSONMinMaxCountStreamModel.objects.create(body=json.dumps([]))
|
||||
internal_block = instance.body.stream_block
|
||||
|
||||
self.assertEqual(internal_block.meta.min_num, 2)
|
||||
self.assertEqual(internal_block.meta.max_num, 5)
|
||||
|
||||
def test_counts_pass_to_block(self):
|
||||
instance = self.block_counts_model.objects.create(body=json.dumps([]))
|
||||
instance = JSONBlockCountsStreamModel.objects.create(body=json.dumps([]))
|
||||
block_counts = instance.body.stream_block.meta.block_counts
|
||||
|
||||
self.assertEqual(block_counts.get("text"), {"min_num": 1})
|
||||
|
@ -522,7 +430,7 @@ class TestStreamFieldCountValidation(TestCase):
|
|||
def test_minimum_count(self):
|
||||
# Single block should fail validation
|
||||
body = [self.rich_text_body]
|
||||
instance = self.min_max_count_model.objects.create(body=json.dumps(body))
|
||||
instance = JSONMinMaxCountStreamModel.objects.create(body=json.dumps(body))
|
||||
with self.assertRaises(StreamBlockValidationError) as catcher:
|
||||
instance.body.stream_block.clean(instance.body)
|
||||
self.assertEqual(
|
||||
|
@ -531,18 +439,18 @@ class TestStreamFieldCountValidation(TestCase):
|
|||
|
||||
# 2 blocks okay
|
||||
body = [self.rich_text_body, self.text_body]
|
||||
instance = self.min_max_count_model.objects.create(body=json.dumps(body))
|
||||
instance = JSONMinMaxCountStreamModel.objects.create(body=json.dumps(body))
|
||||
self.assertTrue(instance.body.stream_block.clean(instance.body))
|
||||
|
||||
def test_maximum_count(self):
|
||||
# 5 blocks okay
|
||||
body = [self.rich_text_body] * 5
|
||||
instance = self.min_max_count_model.objects.create(body=json.dumps(body))
|
||||
instance = JSONMinMaxCountStreamModel.objects.create(body=json.dumps(body))
|
||||
self.assertTrue(instance.body.stream_block.clean(instance.body))
|
||||
|
||||
# 6 blocks should fail validation
|
||||
body = [self.rich_text_body, self.text_body] * 3
|
||||
instance = self.min_max_count_model.objects.create(body=json.dumps(body))
|
||||
instance = JSONMinMaxCountStreamModel.objects.create(body=json.dumps(body))
|
||||
with self.assertRaises(StreamBlockValidationError) as catcher:
|
||||
instance.body.stream_block.clean(instance.body)
|
||||
self.assertEqual(
|
||||
|
@ -550,10 +458,10 @@ class TestStreamFieldCountValidation(TestCase):
|
|||
)
|
||||
|
||||
def test_block_counts_minimums(self):
|
||||
instance = self.block_counts_model.objects.create(body=json.dumps([]))
|
||||
instance = JSONBlockCountsStreamModel.objects.create(body=json.dumps([]))
|
||||
|
||||
# Zero blocks should fail validation (requires one text, one image)
|
||||
instance = self.block_counts_model.objects.create(body=json.dumps([]))
|
||||
instance = JSONBlockCountsStreamModel.objects.create(body=json.dumps([]))
|
||||
with self.assertRaises(StreamBlockValidationError) as catcher:
|
||||
instance.body.stream_block.clean(instance.body)
|
||||
errors = list(catcher.exception.params["__all__"])
|
||||
|
@ -564,7 +472,7 @@ class TestStreamFieldCountValidation(TestCase):
|
|||
|
||||
# One plain text should fail validation
|
||||
body = [self.text_body]
|
||||
instance = self.block_counts_model.objects.create(body=json.dumps(body))
|
||||
instance = JSONBlockCountsStreamModel.objects.create(body=json.dumps(body))
|
||||
with self.assertRaises(StreamBlockValidationError) as catcher:
|
||||
instance.body.stream_block.clean(instance.body)
|
||||
self.assertEqual(
|
||||
|
@ -574,15 +482,15 @@ class TestStreamFieldCountValidation(TestCase):
|
|||
|
||||
# One text, one image should be okay
|
||||
body = [self.text_body, self.image_body]
|
||||
instance = self.block_counts_model.objects.create(body=json.dumps(body))
|
||||
instance = JSONBlockCountsStreamModel.objects.create(body=json.dumps(body))
|
||||
self.assertTrue(instance.body.stream_block.clean(instance.body))
|
||||
|
||||
def test_block_counts_maximums(self):
|
||||
instance = self.block_counts_model.objects.create(body=json.dumps([]))
|
||||
instance = JSONBlockCountsStreamModel.objects.create(body=json.dumps([]))
|
||||
|
||||
# Base is one text, one image
|
||||
body = [self.text_body, self.image_body]
|
||||
instance = self.block_counts_model.objects.create(body=json.dumps(body))
|
||||
instance = JSONBlockCountsStreamModel.objects.create(body=json.dumps(body))
|
||||
self.assertTrue(instance.body.stream_block.clean(instance.body))
|
||||
|
||||
# Two rich text should error
|
||||
|
@ -592,14 +500,14 @@ class TestStreamFieldCountValidation(TestCase):
|
|||
self.rich_text_body,
|
||||
self.rich_text_body,
|
||||
]
|
||||
instance = self.block_counts_model.objects.create(body=json.dumps(body))
|
||||
instance = JSONBlockCountsStreamModel.objects.create(body=json.dumps(body))
|
||||
|
||||
with self.assertRaises(StreamBlockValidationError):
|
||||
instance.body.stream_block.clean(instance.body)
|
||||
|
||||
# Two images should error
|
||||
body = [self.text_body, self.image_body, self.image_body]
|
||||
instance = self.block_counts_model.objects.create(body=json.dumps(body))
|
||||
instance = JSONBlockCountsStreamModel.objects.create(body=json.dumps(body))
|
||||
|
||||
with self.assertRaises(StreamBlockValidationError) as catcher:
|
||||
instance.body.stream_block.clean(instance.body)
|
||||
|
@ -610,7 +518,7 @@ class TestStreamFieldCountValidation(TestCase):
|
|||
|
||||
# One text, one rich, one image should be okay
|
||||
body = [self.text_body, self.image_body, self.rich_text_body]
|
||||
instance = self.block_counts_model.objects.create(body=json.dumps(body))
|
||||
instance = JSONBlockCountsStreamModel.objects.create(body=json.dumps(body))
|
||||
self.assertTrue(instance.body.stream_block.clean(instance.body))
|
||||
|
||||
def test_streamfield_count_argument_precedence(self):
|
||||
|
@ -624,7 +532,7 @@ class TestStreamFieldCountValidation(TestCase):
|
|||
block_counts = {"heading": {"max_num": 1}}
|
||||
|
||||
# args being picked up from the class definition
|
||||
field = StreamField(TestStreamBlock, use_json_field=self.use_json_field)
|
||||
field = StreamField(TestStreamBlock, use_json_field=True)
|
||||
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)
|
||||
|
@ -635,7 +543,7 @@ class TestStreamFieldCountValidation(TestCase):
|
|||
min_num=3,
|
||||
max_num=6,
|
||||
block_counts={"heading": {"max_num": 2}},
|
||||
use_json_field=self.use_json_field,
|
||||
use_json_field=True,
|
||||
)
|
||||
self.assertEqual(field.stream_block.meta.min_num, 3)
|
||||
self.assertEqual(field.stream_block.meta.max_num, 6)
|
||||
|
@ -647,19 +555,13 @@ class TestStreamFieldCountValidation(TestCase):
|
|||
min_num=None,
|
||||
max_num=None,
|
||||
block_counts=None,
|
||||
use_json_field=self.use_json_field,
|
||||
use_json_field=True,
|
||||
)
|
||||
self.assertIsNone(field.stream_block.meta.min_num)
|
||||
self.assertIsNone(field.stream_block.meta.max_num)
|
||||
self.assertIsNone(field.stream_block.meta.block_counts)
|
||||
|
||||
|
||||
class TestJSONStreamFieldCountValidation(TestStreamFieldCountValidation):
|
||||
min_max_count_model = JSONMinMaxCountStreamModel
|
||||
block_counts_model = JSONBlockCountsStreamModel
|
||||
use_json_field = True
|
||||
|
||||
|
||||
class TestJSONStreamField(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
@ -669,14 +571,11 @@ class TestJSONStreamField(TestCase):
|
|||
)
|
||||
|
||||
def test_internal_type(self):
|
||||
text = StreamField([("paragraph", blocks.CharBlock())], use_json_field=False)
|
||||
json = StreamField([("paragraph", blocks.CharBlock())], use_json_field=True)
|
||||
|
||||
self.assertEqual(text.get_internal_type(), "TextField")
|
||||
self.assertEqual(json.get_internal_type(), "JSONField")
|
||||
|
||||
def test_json_body_equals_to_text_body(self):
|
||||
instance_text = StreamModel.objects.create(
|
||||
instance_text = JSONStreamModel.objects.create(
|
||||
body=json.dumps([{"type": "text", "value": "foo"}]),
|
||||
)
|
||||
self.assertEqual(
|
||||
|
|
Ładowanie…
Reference in New Issue