Add a mechanism for preserving the raw text value of StreamFields when they fail to parse as JSON

pull/1386/head
Matt Westcott 2015-06-08 19:09:23 +01:00
rodzic 6479eb80ef
commit 31f45a8a63
2 zmienionych plików z 33 dodań i 9 usunięć

Wyświetl plik

@ -260,7 +260,7 @@ class StreamValue(collections.Sequence):
"""
return self.block.name
def __init__(self, stream_block, stream_data, is_lazy=False):
def __init__(self, stream_block, stream_data, is_lazy=False, raw_text=None):
"""
Construct a StreamValue linked to the given StreamBlock,
with child values given in stream_data.
@ -273,11 +273,18 @@ class StreamValue(collections.Sequence):
Passing is_lazy=False means that stream_data consists of immediately usable
native values. In this mode, stream_data is a list of (type_name, value)
tuples.
raw_text exists solely as a way of representing StreamField content that is
not valid JSON; this may legitimately occur if an existing text field is
migrated to a StreamField. In this situation we return a blank StreamValue
with the raw text accessible under the `raw_text` attribute, so that migration
code can be rewritten to convert it as desired.
"""
self.is_lazy = is_lazy
self.stream_block = stream_block # the StreamBlock object that handles this value
self.stream_data = stream_data # a list of (type_name, value) tuples
self._bound_blocks = {} # populated lazily from stream_data as we access items through __getitem__
self.raw_text = raw_text
def __getitem__(self, i):
if i not in self._bound_blocks:

Wyświetl plik

@ -5,7 +5,7 @@ import json
from django.db import models
from django import forms
from django.core.serializers.json import DjangoJSONEncoder
from django.utils.six import with_metaclass
from django.utils.six import with_metaclass, string_types
from wagtail.wagtailcore.rich_text import DbWhitelister, expand_db_html
from wagtail.utils.widgets import WidgetWithScript
@ -69,17 +69,16 @@ class StreamField(with_metaclass(models.SubfieldBase, models.Field)):
return StreamValue(self.stream_block, [])
elif isinstance(value, StreamValue):
return value
else: # assume string
elif isinstance(value, string_types):
try:
unpacked_value = json.loads(value)
except ValueError:
# value is not valid JSON; most likely, this field was previously a
# rich text field before being migrated to StreamField, and the data
# was left intact in the migration. Return an empty stream instead.
# TODO: keep this raw text data around as a property of the StreamValue
# so that it can be retrieved in data migrations
return StreamValue(self.stream_block, [])
# was left intact in the migration. Return an empty stream instead
# (but keep the raw text available as an attribute, so that it can be
# used to migrate that data to StreamField)
return StreamValue(self.stream_block, [], raw_text=value)
if unpacked_value is None:
# we get here if value is the literal string 'null'. This should probably
@ -88,9 +87,27 @@ class StreamField(with_metaclass(models.SubfieldBase, models.Field)):
return StreamValue(self.stream_block, [])
return self.stream_block.to_python(unpacked_value)
else:
# See if it looks like the standard non-smart representation of a
# StreamField value: a list of (block_name, value) tuples
try:
[None for (x, y) in value]
except (TypeError, ValueError):
# Give up trying to make sense of the value
raise TypeError("Cannot handle %r (type %r) as a value of StreamField" % (value, type(value)))
# Test succeeded, so return as a StreamValue-ified version of that value
return StreamValue(self.stream_block, value)
def get_prep_value(self, value):
return json.dumps(self.stream_block.get_prep_value(value), cls=DjangoJSONEncoder)
if isinstance(value, StreamValue) and not(value) and value.raw_text is not None:
# An empty StreamValue with a nonempty raw_text attribute should have that
# raw_text attribute written back to the db. (This is probably only useful
# for reverse migrations that convert StreamField data back into plain text
# fields.)
return value.raw_text
else:
return json.dumps(self.stream_block.get_prep_value(value), cls=DjangoJSONEncoder)
def formfield(self, **kwargs):
"""