kopia lustrzana https://github.com/wagtail/wagtail
Allow StreamBlock to pick up blank, min_num, max_num and block_counts kwargs from StreamField
rodzic
f03c11f1d1
commit
643bbfc600
|
@ -47,6 +47,12 @@ class Block(metaclass=BaseBlock):
|
||||||
classname = None
|
classname = None
|
||||||
group = ''
|
group = ''
|
||||||
|
|
||||||
|
# Attributes of Meta which can legally be modified after the block has been instantiated.
|
||||||
|
# Used to implement __eq__. label is not included here, despite it technically being mutable via
|
||||||
|
# set_name, since its value must originate from either the constructor arguments or set_name,
|
||||||
|
# both of which are captured by the equality test, so checking label as well would be redundant.
|
||||||
|
MUTABLE_META_ATTRIBUTES = []
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Setting a 'dependencies' list serves as a shortcut for the common case where a complex block type
|
Setting a 'dependencies' list serves as a shortcut for the common case where a complex block type
|
||||||
(such as struct, list or stream) relies on one or more inner block objects, and needs to ensure that
|
(such as struct, list or stream) relies on one or more inner block objects, and needs to ensure that
|
||||||
|
@ -121,6 +127,16 @@ class Block(metaclass=BaseBlock):
|
||||||
if not self.meta.label:
|
if not self.meta.label:
|
||||||
self.label = capfirst(force_str(name).replace('_', ' '))
|
self.label = capfirst(force_str(name).replace('_', ' '))
|
||||||
|
|
||||||
|
def set_meta_options(self, options):
|
||||||
|
"""
|
||||||
|
Called when this block is used as the top-level block of a StreamField, to pass on any options
|
||||||
|
from the StreamField constructor that ought to be handled by the block, e.g.
|
||||||
|
body = StreamField(SomeStreamBlock(), max_num=5)
|
||||||
|
"""
|
||||||
|
# Ignore all options here; block types that are allowed at the top level (i.e. currently just
|
||||||
|
# StreamBlock) and recognise these options will override this method
|
||||||
|
pass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def media(self):
|
def media(self):
|
||||||
return forms.Media()
|
return forms.Media()
|
||||||
|
@ -395,9 +411,9 @@ class Block(metaclass=BaseBlock):
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
"""
|
"""
|
||||||
Implement equality on block objects so that two blocks with matching definitions are considered
|
Implement equality on block objects so that two blocks with matching definitions are considered
|
||||||
equal. (Block objects are intended to be immutable with the exception of set_name(), so here
|
equal. Block objects are intended to be immutable with the exception of set_name() and any meta
|
||||||
'matching definitions' means that both the 'name' property and the constructor args/kwargs - as
|
attributes identified in MUTABLE_META_ATTRIBUTES, so checking these along with the result of
|
||||||
captured in _constructor_args - are equal on both blocks.)
|
deconstruct (which captures the constructor arguments) is sufficient to identify (valid) differences.
|
||||||
|
|
||||||
This was originally necessary as a workaround for https://code.djangoproject.com/ticket/24340
|
This was originally necessary as a workaround for https://code.djangoproject.com/ticket/24340
|
||||||
in Django <1.9; the deep_deconstruct function used to detect changes for migrations did not
|
in Django <1.9; the deep_deconstruct function used to detect changes for migrations did not
|
||||||
|
@ -439,7 +455,14 @@ class Block(metaclass=BaseBlock):
|
||||||
# the migration, rather than leaving the migration vulnerable to future changes to FooBlock / BarBlock
|
# the migration, rather than leaving the migration vulnerable to future changes to FooBlock / BarBlock
|
||||||
# in models.py.
|
# in models.py.
|
||||||
|
|
||||||
return (self.name == other.name) and (self.deconstruct() == other.deconstruct())
|
return (
|
||||||
|
self.name == other.name
|
||||||
|
and self.deconstruct() == other.deconstruct()
|
||||||
|
and all(
|
||||||
|
getattr(self.meta, attr, None) == getattr(other.meta, attr, None)
|
||||||
|
for attr in self.MUTABLE_META_ATTRIBUTES
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BoundBlock:
|
class BoundBlock:
|
||||||
|
|
|
@ -51,6 +51,11 @@ class BaseStreamBlock(Block):
|
||||||
|
|
||||||
self.dependencies = self.child_blocks.values()
|
self.dependencies = self.child_blocks.values()
|
||||||
|
|
||||||
|
def set_meta_options(self, opts):
|
||||||
|
for attr in ['required', 'min_num', 'max_num', 'block_counts']:
|
||||||
|
if attr in opts:
|
||||||
|
setattr(self.meta, attr, opts[attr])
|
||||||
|
|
||||||
def get_default(self):
|
def get_default(self):
|
||||||
"""
|
"""
|
||||||
Default values set on a StreamBlock should be a list of (type_name, value) tuples -
|
Default values set on a StreamBlock should be a list of (type_name, value) tuples -
|
||||||
|
@ -381,6 +386,8 @@ class BaseStreamBlock(Block):
|
||||||
max_num = None
|
max_num = None
|
||||||
block_counts = {}
|
block_counts = {}
|
||||||
|
|
||||||
|
MUTABLE_META_ATTRIBUTES = ['required', 'min_num', 'max_num', 'block_counts']
|
||||||
|
|
||||||
|
|
||||||
class StreamBlock(BaseStreamBlock, metaclass=DeclarativeSubBlocksMetaclass):
|
class StreamBlock(BaseStreamBlock, metaclass=DeclarativeSubBlocksMetaclass):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -51,13 +51,31 @@ class Creator:
|
||||||
|
|
||||||
class StreamField(models.Field):
|
class StreamField(models.Field):
|
||||||
def __init__(self, block_types, **kwargs):
|
def __init__(self, block_types, **kwargs):
|
||||||
|
|
||||||
|
# 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']:
|
||||||
|
if arg in kwargs:
|
||||||
|
block_opts[arg] = kwargs.pop(arg)
|
||||||
|
|
||||||
|
# for a top-level block, the 'blank' kwarg (defaulting to False) always overrides the
|
||||||
|
# block's own 'required' meta attribute, even if not passed explicitly; this ensures
|
||||||
|
# that the field and block have consistent definitions
|
||||||
|
block_opts['required'] = not kwargs.get('blank', False)
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
if isinstance(block_types, Block):
|
if isinstance(block_types, Block):
|
||||||
|
# use the passed block as the top-level block
|
||||||
self.stream_block = block_types
|
self.stream_block = block_types
|
||||||
elif isinstance(block_types, type):
|
elif isinstance(block_types, type):
|
||||||
self.stream_block = block_types(required=not self.blank)
|
# block passed as a class - instantiate it
|
||||||
|
self.stream_block = block_types()
|
||||||
else:
|
else:
|
||||||
self.stream_block = StreamBlock(block_types, required=not self.blank)
|
# construct a top-level StreamBlock from the list of block types
|
||||||
|
self.stream_block = StreamBlock(block_types)
|
||||||
|
|
||||||
|
self.stream_block.set_meta_options(block_opts)
|
||||||
|
|
||||||
def get_internal_type(self):
|
def get_internal_type(self):
|
||||||
return 'TextField'
|
return 'TextField'
|
||||||
|
|
Ładowanie…
Reference in New Issue