0001-blocks-add-option-to-have-a-remove-button-45368.patch
tests/form_pages/test_block.py | ||
---|---|---|
870 | 870 |
assert 'test2.txt' in resp |
871 | 871 | |
872 | 872 | |
873 |
@pytest.mark.parametrize('removed_line', [0, 1, 2]) |
|
874 |
def test_block_repeated_remove_line(pub, blocks_feature, removed_line): |
|
875 |
FormDef.wipe() |
|
876 |
BlockDef.wipe() |
|
877 | ||
878 |
block = BlockDef() |
|
879 |
block.name = 'foobar' |
|
880 |
block.fields = [ |
|
881 |
fields.StringField(id='123', required=True, label='Test', type='string'), |
|
882 |
fields.StringField(id='234', required=True, label='Test2', type='string'), |
|
883 |
] |
|
884 |
block.store() |
|
885 | ||
886 |
formdef = FormDef() |
|
887 |
formdef.name = 'form title' |
|
888 |
formdef.fields = [ |
|
889 |
fields.PageField(id='0', label='1st page', type='page'), |
|
890 |
fields.BlockField(id='1', label='test', type='block:foobar', |
|
891 |
max_items=5, hint='hintblock', remove_button=True), |
|
892 |
fields.PageField(id='2', label='2nd page', type='page'), |
|
893 |
] |
|
894 |
formdef.store() |
|
895 |
formdef.data_class().wipe() |
|
896 | ||
897 |
app = get_app(pub) |
|
898 |
resp = app.get(formdef.get_url()) |
|
899 |
assert resp.text.count('>Test<') == 1 |
|
900 |
resp = resp.form.submit('f1$add_element') |
|
901 |
assert resp.text.count('>Test<') == 2 |
|
902 |
resp = resp.form.submit('f1$add_element') |
|
903 |
assert resp.text.count('>Test<') == 3 |
|
904 | ||
905 |
# fill items on three rows |
|
906 |
resp.form['f1$element0$f123'] = 'foo1' |
|
907 |
resp.form['f1$element0$f234'] = 'bar1' |
|
908 |
resp.form['f1$element1$f123'] = 'foo2' |
|
909 |
resp.form['f1$element1$f234'] = 'bar2' |
|
910 |
resp.form['f1$element2$f123'] = 'foo3' |
|
911 |
resp.form['f1$element2$f234'] = 'bar3' |
|
912 | ||
913 |
resp = resp.form.submit('submit') # -> 2nd page |
|
914 |
resp = resp.form.submit('submit') # -> validation page |
|
915 |
assert 'Check values then click submit.' in resp.text |
|
916 |
assert resp.form['f1$element0$f123'].value == 'foo1' |
|
917 |
assert resp.form['f1$element0$f234'].value == 'bar1' |
|
918 |
assert resp.form['f1$element1$f123'].value == 'foo2' |
|
919 |
assert resp.form['f1$element1$f234'].value == 'bar2' |
|
920 |
assert resp.form['f1$element2$f123'].value == 'foo3' |
|
921 |
assert resp.form['f1$element2$f234'].value == 'bar3' |
|
922 | ||
923 |
resp = resp.form.submit('previous') # -> 2nd page |
|
924 |
resp = resp.form.submit('previous') # -> 1st page |
|
925 |
# simulate javascript removing of block elements from DOM |
|
926 |
resp.form.field_order.remove(('f1$element%s$f123' % removed_line, resp.form.fields['f1$element%s$f123' % removed_line][0])) |
|
927 |
del resp.form.fields['f1$element%s$f123' % removed_line] |
|
928 |
resp.form.field_order.remove(('f1$element%s$f234' % removed_line, resp.form.fields['f1$element%s$f234' % removed_line][0])) |
|
929 |
del resp.form.fields['f1$element%s$f234' % removed_line] |
|
930 | ||
931 |
resp = resp.form.submit('submit') # -> 2nd page |
|
932 |
resp = resp.form.submit('submit') # -> validation page |
|
933 |
values = ['1', '2', '3'] |
|
934 |
del values[removed_line] |
|
935 |
assert resp.form['f1$element0$f123'].value == 'foo%s' % values[0] |
|
936 |
assert resp.form['f1$element0$f234'].value == 'bar%s' % values[0] |
|
937 |
assert resp.form['f1$element1$f123'].value == 'foo%s' % values[1] |
|
938 |
assert resp.form['f1$element1$f234'].value == 'bar%s' % values[1] |
|
939 |
assert 'f1$element2$f123' not in resp.form.fields |
|
940 |
assert 'f1$element2$f234' not in resp.form.fields |
|
941 | ||
942 |
resp = resp.form.submit('submit') # -> submit |
|
943 |
assert len(formdef.data_class().select()[0].data['1']['data']) == 2 |
|
944 | ||
945 | ||
873 | 946 |
@pytest.mark.parametrize('block_name', ['foobar', 'Foo bar']) |
874 | 947 |
def test_block_digest(pub, blocks_feature, block_name): |
875 | 948 |
FormDef.wipe() |
wcs/blocks.py | ||
---|---|---|
191 | 191 | |
192 | 192 | |
193 | 193 |
class BlockSubWidget(CompositeWidget): |
194 |
template_name = 'qommon/forms/widgets/block_sub.html' |
|
195 | ||
194 | 196 |
def __init__(self, name, value=None, *args, **kwargs): |
195 | 197 |
self.block = kwargs.pop('block') |
196 | 198 |
self.readonly = kwargs.get('readonly') |
199 |
self.remove_button = kwargs.pop('remove_button', False) |
|
197 | 200 |
super().__init__(name, value, *args, **kwargs) |
198 | 201 |
for field in self.block.fields: |
199 | 202 |
if 'readonly' in kwargs: |
... | ... | |
239 | 242 | |
240 | 243 | |
241 | 244 |
class BlockWidget(WidgetList): |
245 |
template_name = 'qommon/forms/widgets/block.html' |
|
246 | ||
242 | 247 |
def __init__( |
243 | 248 |
self, name, value=None, title=None, block=None, max_items=None, add_element_label=None, **kwargs |
244 | 249 |
): |
245 | 250 |
self.block = block |
246 | 251 |
self.readonly = kwargs.get('readonly') |
247 | 252 |
self.label_display = kwargs.pop('label_display') or 'normal' |
253 |
self.remove_button = kwargs.pop('remove_button', False) |
|
248 | 254 |
element_values = None |
249 | 255 |
if value: |
250 | 256 |
element_values = value.get('data') |
251 | 257 |
if not max_items: |
252 | 258 |
max_items = 1 |
253 | 259 |
hint = kwargs.pop('hint', None) |
254 |
element_kwargs = {'block': self.block, 'render_br': False} |
|
260 |
element_kwargs = {'block': self.block, 'render_br': False, 'remove_button': self.remove_button}
|
|
255 | 261 |
element_kwargs.update(kwargs) |
256 | 262 |
super().__init__( |
257 | 263 |
name, |
... | ... | |
274 | 280 |
# (maybe this could be moved to WidgetList) |
275 | 281 |
prefix = '%s$element' % self.name |
276 | 282 |
known_prefixes = {x.split('$', 2)[1] for x in request.form.keys() if x.startswith(prefix)} |
277 |
for i in range(len(known_prefixes) - len(self.element_names)): |
|
278 |
self.add_element() |
|
283 |
for prefix in known_prefixes: |
|
284 |
if prefix not in self.element_names: |
|
285 |
self.add_element(element_name=prefix) |
|
279 | 286 |
super()._parse(request) |
280 | 287 |
if self.value: |
281 | 288 |
self.value = {'data': self.value} |
wcs/fields.py | ||
---|---|---|
2763 | 2763 | |
2764 | 2764 |
widget_class = BlockWidget |
2765 | 2765 |
max_items = 1 |
2766 |
extra_attributes = ['block', 'max_items', 'add_element_label', 'label_display'] |
|
2766 |
extra_attributes = ['block', 'max_items', 'add_element_label', 'label_display', 'remove_button']
|
|
2767 | 2767 |
add_element_label = '' |
2768 | 2768 |
label_display = 'normal' |
2769 | 2769 | |
... | ... | |
2796 | 2796 |
form.add( |
2797 | 2797 |
SingleSelectWidget, 'label_display', title=_('Label display'), |
2798 | 2798 |
value=self.label_display or 'normal', options=display_options) |
2799 |
form.add(CheckboxWidget, 'remove_button', title=_('Include remove button')) |
|
2799 | 2800 | |
2800 | 2801 |
def get_admin_attributes(self): |
2801 |
return super().get_admin_attributes() + ['max_items', 'add_element_label', 'label_display'] |
|
2802 |
return super().get_admin_attributes() + [ |
|
2803 |
'max_items', 'add_element_label', 'label_display', 'remove_button'] |
|
2802 | 2804 | |
2803 | 2805 |
def store_display_value(self, data, field_id): |
2804 | 2806 |
value = data.get(field_id) |
wcs/qommon/form.py | ||
---|---|---|
1608 | 1608 |
self.widgets.remove(self.get_widget('add_element')) |
1609 | 1609 |
del self._names['add_element'] |
1610 | 1610 | |
1611 |
def add_element(self, value=None): |
|
1612 |
name = "element%d" % len(self.element_names) |
|
1611 |
def add_element(self, value=None, element_name=None): |
|
1612 |
if element_name: |
|
1613 |
name = element_name |
|
1614 |
else: |
|
1615 |
name = 'element%d' % len(self.element_names) |
|
1613 | 1616 |
self.add(self.element_type, name, value=value, **self.element_kwargs) |
1614 | 1617 |
self.element_names.append(name) |
1615 | 1618 |
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; |
... | ... | |
2104 | 2118 |
padding-bottom: 1ex; |
2105 | 2119 |
} |
2106 | 2120 |
} |
2121 | ||
2122 |
.wcs-block-with-remove-button { |
|
2123 |
.BlockSubWidget { |
|
2124 |
@include clearfix(); |
|
2125 |
position: relative; |
|
2126 |
} |
|
2127 |
.remove-button { |
|
2128 |
position: absolute; |
|
2129 |
right: 0; |
|
2130 |
bottom: 1.5em; |
|
2131 |
margin-right: 0; |
|
2132 |
} |
|
2133 |
} |
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 }} wcs-block-with-remove-button{% 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 |
- |