kopia lustrzana https://github.com/wagtail/wagtail
Implement a ListValue type for ListBlocks
rodzic
93229cfc14
commit
4a848bfb4e
|
@ -32,3 +32,17 @@
|
|||
### Removed support for Python 3.6
|
||||
|
||||
Python 3.6 is no longer supported as of this release; please upgrade to Python 3.7 or above before upgrading Wagtail.
|
||||
|
||||
### StreamField ListBlock now returns `ListValue` rather than a list instance
|
||||
|
||||
The data type returned as the value of a ListBlock is now a custom class, `ListValue`, rather than a Python `list` object. This change allows it to provide a `bound_blocks` property that exposes the list items as [`BoundBlock` objects](../advanced_topics/boundblocks_and_values) rather than plain values. `ListValue` objects are mutable sequences that behave similarly to lists, and so all code that iterates over them, accesses individual elements, or manipulates them should continue to work. However, code that specifically expects a `list` object (e.g. using `isinstance` or testing for equality against a list) may need to be updated. For example, a unit test that tests the value of a `ListBlock` as follows:
|
||||
|
||||
```python
|
||||
self.assertEqual(page.body[0].value, ['hello', 'goodbye'])
|
||||
```
|
||||
|
||||
should be rewritten as:
|
||||
|
||||
```python
|
||||
self.assertEqual(list(page.body[0].value), ['hello', 'goodbye'])
|
||||
```
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import itertools
|
||||
from collections.abc import MutableSequence
|
||||
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
|
@ -48,6 +49,32 @@ class ListBlockValidationErrorAdapter(Adapter):
|
|||
register(ListBlockValidationErrorAdapter(), ListBlockValidationError)
|
||||
|
||||
|
||||
class ListValue(MutableSequence):
|
||||
def __init__(self, values=None):
|
||||
if values is None:
|
||||
self.values = []
|
||||
else:
|
||||
self.values = values
|
||||
|
||||
def __getitem__(self, i):
|
||||
return self.values[i]
|
||||
|
||||
def __setitem__(self, i, val):
|
||||
self.values[i] = val
|
||||
|
||||
def __delitem__(self, i):
|
||||
del self.values[i]
|
||||
|
||||
def __len__(self):
|
||||
return len(self.values)
|
||||
|
||||
def insert(self, i, item):
|
||||
self.values.insert(i, item)
|
||||
|
||||
def __repr__(self):
|
||||
return "<ListValue: %r>" % (self.values, )
|
||||
|
||||
|
||||
class ListBlock(Block):
|
||||
|
||||
def __init__(self, child_block, **kwargs):
|
||||
|
@ -65,7 +92,7 @@ class ListBlock(Block):
|
|||
|
||||
def get_default(self):
|
||||
# wrap with list() so that each invocation of get_default returns a distinct instance
|
||||
return list(self.meta.default)
|
||||
return ListValue(values=list(self.meta.default))
|
||||
|
||||
def value_from_datadict(self, data, files, prefix):
|
||||
count = int(data['%s-count' % prefix])
|
||||
|
@ -111,11 +138,11 @@ class ListBlock(Block):
|
|||
if any(errors) or non_block_errors:
|
||||
raise ListBlockValidationError(block_errors=errors, non_block_errors=non_block_errors)
|
||||
|
||||
return result
|
||||
return ListValue(values=result)
|
||||
|
||||
def to_python(self, value):
|
||||
# 'value' is a list of child block values; use bulk_to_python to convert them all in one go
|
||||
return self.child_block.bulk_to_python(value)
|
||||
return ListValue(values=self.child_block.bulk_to_python(value))
|
||||
|
||||
def bulk_to_python(self, values):
|
||||
# 'values' is a list of lists of child block values; concatenate them into one list so that
|
||||
|
@ -128,7 +155,7 @@ class ListBlock(Block):
|
|||
result = []
|
||||
offset = 0
|
||||
for sublist_len in lengths:
|
||||
result.append(converted_values[offset:offset + sublist_len])
|
||||
result.append(ListValue(values=converted_values[offset:offset + sublist_len]))
|
||||
offset += sublist_len
|
||||
|
||||
return result
|
||||
|
|
|
@ -1823,9 +1823,9 @@ class TestStructBlock(SimpleTestCase):
|
|||
])
|
||||
|
||||
shopping_lists[0]['items'].append('cake')
|
||||
self.assertEqual(shopping_lists[0]['items'], ['chocolate', 'cake'])
|
||||
self.assertEqual(list(shopping_lists[0]['items']), ['chocolate', 'cake'])
|
||||
# shopping_lists[1] should not be updated
|
||||
self.assertEqual(shopping_lists[1]['items'], ['chocolate'])
|
||||
self.assertEqual(list(shopping_lists[1]['items']), ['chocolate'])
|
||||
|
||||
def test_clean(self):
|
||||
block = blocks.StructBlock([
|
||||
|
@ -2272,7 +2272,7 @@ class TestListBlock(WagtailTestUtils, SimpleTestCase):
|
|||
def test_can_specify_default(self):
|
||||
block = blocks.ListBlock(blocks.CharBlock(), default=['peas', 'beans', 'carrots'])
|
||||
|
||||
self.assertEqual(block.get_default(), ['peas', 'beans', 'carrots'])
|
||||
self.assertEqual(list(block.get_default()), ['peas', 'beans', 'carrots'])
|
||||
|
||||
def test_default_default(self):
|
||||
"""
|
||||
|
@ -2281,7 +2281,7 @@ class TestListBlock(WagtailTestUtils, SimpleTestCase):
|
|||
"""
|
||||
block = blocks.ListBlock(blocks.CharBlock(default='chocolate'))
|
||||
|
||||
self.assertEqual(block.get_default(), ['chocolate'])
|
||||
self.assertEqual(list(block.get_default()), ['chocolate'])
|
||||
|
||||
block.set_name('test_shoppinglistblock')
|
||||
js_args = ListBlockAdapter().js_args(block)
|
||||
|
@ -2303,9 +2303,9 @@ class TestListBlock(WagtailTestUtils, SimpleTestCase):
|
|||
asda_shopping = block.to_python({'shop': 'Asda'}) # 'items' will default to ['chocolate'], but a distinct instance
|
||||
|
||||
tesco_shopping['items'].append('cake')
|
||||
self.assertEqual(tesco_shopping['items'], ['chocolate', 'cake'])
|
||||
self.assertEqual(list(tesco_shopping['items']), ['chocolate', 'cake'])
|
||||
# asda_shopping should not be modified
|
||||
self.assertEqual(asda_shopping['items'], ['chocolate'])
|
||||
self.assertEqual(list(asda_shopping['items']), ['chocolate'])
|
||||
|
||||
def test_adapt_with_classname_via_kwarg(self):
|
||||
"""form_classname from kwargs to be used as an additional class when rendering list block"""
|
||||
|
@ -2409,8 +2409,10 @@ class TestListBlockWithFixtures(TestCase):
|
|||
|
||||
with self.assertNumQueries(1):
|
||||
result = block.bulk_to_python([[4, 5], [], [2]])
|
||||
# result will be a list of ListValues - convert to lists for equality check
|
||||
clean_result = [list(val) for val in result]
|
||||
|
||||
self.assertEqual(result, [
|
||||
self.assertEqual(clean_result, [
|
||||
[Page.objects.get(id=4), Page.objects.get(id=5)],
|
||||
[],
|
||||
[Page.objects.get(id=2)],
|
||||
|
|
Ładowanie…
Reference in New Issue