0001-blocks-add-option-to-have-a-remove-button-45368.patch
tests/form_pages/test_block.py | ||
---|---|---|
889 | 889 |
assert 'test2.txt' in resp |
890 | 890 | |
891 | 891 | |
892 |
@pytest.mark.parametrize('removed_line', [0, 1, 2]) |
|
893 |
def test_block_repeated_remove_line(pub, blocks_feature, removed_line): |
|
894 |
FormDef.wipe() |
|
895 |
BlockDef.wipe() |
|
896 | ||
897 |
block = BlockDef() |
|
898 |
block.name = 'foobar' |
|
899 |
block.fields = [ |
|
900 |
fields.StringField(id='123', required=True, label='Test', type='string'), |
|
901 |
fields.StringField(id='234', required=True, label='Test2', type='string'), |
|
902 |
] |
|
903 |
block.store() |
|
904 | ||
905 |
formdef = FormDef() |
|
906 |
formdef.name = 'form title' |
|
907 |
formdef.fields = [ |
|
908 |
fields.PageField(id='0', label='1st page', type='page'), |
|
909 |
fields.BlockField(id='1', label='test', type='block:foobar', |
|
910 |
max_items=5, hint='hintblock', remove_button=True), |
|
911 |
fields.PageField(id='2', label='2nd page', type='page'), |
|
912 |
] |
|
913 |
formdef.store() |
|
914 |
formdef.data_class().wipe() |
|
915 | ||
916 |
app = get_app(pub) |
|
917 |
resp = app.get(formdef.get_url()) |
|
918 |
assert resp.text.count('>Test<') == 1 |
|
919 |
resp = resp.form.submit('f1$add_element') |
|
920 |
assert resp.text.count('>Test<') == 2 |
|
921 |
resp = resp.form.submit('f1$add_element') |
|
922 |
assert resp.text.count('>Test<') == 3 |
|
923 | ||
924 |
# fill items on three rows |
|
925 |
resp.form['f1$element0$f123'] = 'foo1' |
|
926 |
resp.form['f1$element0$f234'] = 'bar1' |
|
927 |
resp.form['f1$element1$f123'] = 'foo2' |
|
928 |
resp.form['f1$element1$f234'] = 'bar2' |
|
929 |
resp.form['f1$element2$f123'] = 'foo3' |
|
930 |
resp.form['f1$element2$f234'] = 'bar3' |
|
931 | ||
932 |
resp = resp.form.submit('submit') # -> 2nd page |
|
933 |
resp = resp.form.submit('submit') # -> validation page |
|
934 |
assert 'Check values then click submit.' in resp.text |
|
935 |
assert resp.form['f1$element0$f123'].value == 'foo1' |
|
936 |
assert resp.form['f1$element0$f234'].value == 'bar1' |
|
937 |
assert resp.form['f1$element1$f123'].value == 'foo2' |
|
938 |
assert resp.form['f1$element1$f234'].value == 'bar2' |
|
939 |
assert resp.form['f1$element2$f123'].value == 'foo3' |
|
940 |
assert resp.form['f1$element2$f234'].value == 'bar3' |
|
941 | ||
942 |
resp = resp.form.submit('previous') # -> 2nd page |
|
943 |
resp = resp.form.submit('previous') # -> 1st page |
|
944 |
# simulate javascript removing of block elements from DOM |
|
945 |
resp.form.field_order.remove(('f1$element%s$f123' % removed_line, resp.form.fields['f1$element%s$f123' % removed_line][0])) |
|
946 |
del resp.form.fields['f1$element%s$f123' % removed_line] |
|
947 |
resp.form.field_order.remove(('f1$element%s$f234' % removed_line, resp.form.fields['f1$element%s$f234' % removed_line][0])) |
|
948 |
del resp.form.fields['f1$element%s$f234' % removed_line] |
|
949 | ||
950 |
resp = resp.form.submit('submit') # -> 2nd page |
|
951 |
resp = resp.form.submit('submit') # -> validation page |
|
952 |
values = ['1', '2', '3'] |
|
953 |
del values[removed_line] |
|
954 |
assert resp.form['f1$element0$f123'].value == 'foo%s' % values[0] |
|
955 |
assert resp.form['f1$element0$f234'].value == 'bar%s' % values[0] |
|
956 |
assert resp.form['f1$element1$f123'].value == 'foo%s' % values[1] |
|
957 |
assert resp.form['f1$element1$f234'].value == 'bar%s' % values[1] |
|
958 |
assert 'f1$element2$f123' not in resp.form.fields |
|
959 |
assert 'f1$element2$f234' not in resp.form.fields |
|
960 | ||
961 |
resp = resp.form.submit('submit') # -> submit |
|
962 |
assert len(formdef.data_class().select()[0].data['1']['data']) == 2 |
|
963 | ||
964 | ||
892 | 965 |
@pytest.mark.parametrize('block_name', ['foobar', 'Foo bar']) |
893 | 966 |
def test_block_digest(pub, blocks_feature, block_name): |
894 | 967 |
FormDef.wipe() |
wcs/blocks.py | ||
---|---|---|
192 | 192 | |
193 | 193 | |
194 | 194 |
class BlockSubWidget(CompositeWidget): |
195 |
template_name = 'qommon/forms/widgets/block_sub.html' |
|
196 | ||
195 | 197 |
def __init__(self, name, value=None, *args, **kwargs): |
196 | 198 |
self.block = kwargs.pop('block') |
197 | 199 |
self.readonly = kwargs.get('readonly') |
200 |
self.remove_button = kwargs.pop('remove_button', False) |
|
198 | 201 |
super().__init__(name, value, *args, **kwargs) |
199 | 202 |
for field in self.block.fields: |
200 | 203 |
if 'readonly' in kwargs: |
... | ... | |
240 | 243 | |
241 | 244 | |
242 | 245 |
class BlockWidget(WidgetList): |
246 |
template_name = 'qommon/forms/widgets/block.html' |
|
247 | ||
243 | 248 |
def __init__( |
244 | 249 |
self, name, value=None, title=None, block=None, max_items=None, add_element_label=None, **kwargs |
245 | 250 |
): |
246 | 251 |
self.block = block |
247 | 252 |
self.readonly = kwargs.get('readonly') |
248 | 253 |
self.label_display = kwargs.pop('label_display') or 'normal' |
254 |
self.remove_button = kwargs.pop('remove_button', False) |
|
249 | 255 |
element_values = None |
250 | 256 |
if value: |
251 | 257 |
element_values = value.get('data') |
252 | 258 |
if not max_items: |
253 | 259 |
max_items = 1 |
254 | 260 |
hint = kwargs.pop('hint', None) |
255 |
element_kwargs = {'block': self.block, 'render_br': False} |
|
261 |
element_kwargs = {'block': self.block, 'render_br': False, 'remove_button': self.remove_button}
|
|
256 | 262 |
element_kwargs.update(kwargs) |
257 | 263 |
super().__init__( |
258 | 264 |
name, |
... | ... | |
275 | 281 |
# (maybe this could be moved to WidgetList) |
276 | 282 |
prefix = '%s$element' % self.name |
277 | 283 |
known_prefixes = {x.split('$', 2)[1] for x in request.form.keys() if x.startswith(prefix)} |
278 |
for i in range(len(known_prefixes) - len(self.element_names)): |
|
279 |
self.add_element() |
|
284 |
for prefix in known_prefixes: |
|
285 |
if prefix not in self.element_names: |
|
286 |
self.add_element(element_name=prefix) |
|
280 | 287 |
super()._parse(request) |
281 | 288 |
if self.value: |
282 | 289 |
self.value = {'data': self.value} |
wcs/fields.py | ||
---|---|---|
3155 | 3155 | |
3156 | 3156 |
widget_class = BlockWidget |
3157 | 3157 |
max_items = 1 |
3158 |
extra_attributes = ['block', 'max_items', 'add_element_label', 'label_display'] |
|
3158 |
extra_attributes = ['block', 'max_items', 'add_element_label', 'label_display', 'remove_button']
|
|
3159 | 3159 |
add_element_label = '' |
3160 | 3160 |
label_display = 'normal' |
3161 |
remove_button = False |
|
3161 | 3162 | |
3162 | 3163 |
# cache |
3163 | 3164 |
_block = None |
... | ... | |
3192 | 3193 |
value=self.label_display or 'normal', |
3193 | 3194 |
options=display_options, |
3194 | 3195 |
) |
3196 |
form.add(CheckboxWidget, 'remove_button', title=_('Include remove button'), value=self.remove_button) |
|
3195 | 3197 | |
3196 | 3198 |
def get_admin_attributes(self): |
3197 |
return super().get_admin_attributes() + ['max_items', 'add_element_label', 'label_display'] |
|
3199 |
return super().get_admin_attributes() + [ |
|
3200 |
'max_items', |
|
3201 |
'add_element_label', |
|
3202 |
'label_display', |
|
3203 |
'remove_button', |
|
3204 |
] |
|
3198 | 3205 | |
3199 | 3206 |
def store_display_value(self, data, field_id): |
3200 | 3207 |
value = data.get(field_id) |
wcs/qommon/form.py | ||
---|---|---|
1693 | 1693 |
self.widgets.remove(self.get_widget('add_element')) |
1694 | 1694 |
del self._names['add_element'] |
1695 | 1695 | |
1696 |
def add_element(self, value=None): |
|
1697 |
name = "element%d" % len(self.element_names) |
|
1696 |
def add_element(self, value=None, element_name=None): |
|
1697 |
if element_name: |
|
1698 |
name = element_name |
|
1699 |
else: |
|
1700 |
name = 'element%d' % len(self.element_names) |
|
1698 | 1701 |
self.add(self.element_type, name, value=value, **self.element_kwargs) |
1699 | 1702 |
self.element_names.append(name) |
1700 | 1703 |
wcs/qommon/static/css/dc2/admin.scss | ||
---|---|---|
3 | 3 |
$string-color: str-slice($primary-color + '', 2); |
4 | 4 |
$actions: add, duplicate, edit, remove; |
5 | 5 | |
6 |
@mixin clearfix { |
|
7 |
&::after { |
|
8 |
display: block; |
|
9 |
clear: both; |
|
10 |
content: ""; |
|
11 |
} |
|
12 |
@supports (display: flow-root) { |
|
13 |
display: flow-root; |
|
14 |
&::after { |
|
15 |
content: none; |
|
16 |
} |
|
17 |
} |
|
18 |
} |
|
19 | ||
6 | 20 |
%block { |
7 | 21 |
background: white; |
8 | 22 |
padding: 0.5rem; |
... | ... | |
2111 | 2125 |
padding-bottom: 1ex; |
2112 | 2126 |
} |
2113 | 2127 |
} |
2128 | ||
2129 |
.wcs-block-with-remove-button { |
|
2130 |
.BlockSubWidget { |
|
2131 |
@include clearfix(); |
|
2132 |
position: relative; |
|
2133 |
} |
|
2134 |
.remove-button { |
|
2135 |
position: absolute; |
|
2136 |
right: 0; |
|
2137 |
bottom: 1.5em; |
|
2138 |
margin-right: 0; |
|
2139 |
} |
|
2140 |
} |
wcs/qommon/static/js/qommon.forms.js | ||
---|---|---|
470 | 470 |
const table = elem.querySelector('table'); |
471 | 471 |
new Responsive_table_widget(table); |
472 | 472 |
}); |
473 | ||
474 |
function disable_single_block_remove_button() { |
|
475 |
$('.BlockSubWidget button.remove-button').each(function(i, elem) { |
|
476 |
if ($(this).parents('.BlockWidget').find('.BlockSubWidget').length == 1) { |
|
477 |
$(this).prop('disabled', true); |
|
478 |
} |
|
479 |
}); |
|
480 |
} |
|
481 | ||
482 |
disable_single_block_remove_button(); |
|
483 |
$('.BlockSubWidget button.remove-button').on('click', function() { |
|
484 |
if ($(this).parents('.BlockWidget').find('.BlockSubWidget').length > 1) { |
|
485 |
$(this).parents('.BlockSubWidget').remove(); |
|
486 |
disable_single_block_remove_button(); |
|
487 |
} |
|
488 |
return false; |
|
489 |
}); |
|
473 | 490 |
}); |
wcs/qommon/templates/qommon/forms/widgets/block.html | ||
---|---|---|
1 |
{% extends "qommon/forms/widget.html" %} |
|
2 | ||
3 |
{% block widget-css-classes %}{{ block.super }} {% if widget.remove_button %}wcs-block-with-remove-button{% endif %}{% endblock %} |
wcs/qommon/templates/qommon/forms/widgets/block_sub.html | ||
---|---|---|
1 |
{% extends "qommon/forms/widget.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block widget-content %} |
|
5 |
{% for subwidget in widget.get_widgets %} |
|
6 |
{{ subwidget.render|safe }} |
|
7 |
{% endfor %} |
|
8 |
{% if not widget.readonly and widget.remove_button %}<button class="remove-button" title="{% trans "Remove" %}">-</button>{% endif %} |
|
9 |
{% endblock %} |
|
0 |
- |