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
Matt Westcott 2023-01-31 15:49:09 +00:00
rodzic 619f395980
commit 5c653002a2
4 zmienionych plików z 199 dodań i 213 usunięć

Wyświetl plik

@ -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()

Wyświetl plik

@ -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,
),
),
]

Wyświetl plik

@ -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"),

Wyświetl plik

@ -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(