Projet

Général

Profil

0001-blocks-add-option-to-have-a-remove-button-45368.patch

Frédéric Péters, 02 février 2021 12:39

Télécharger (11,2 ko)

Voir les différences:

Subject: [PATCH] blocks: add option to have a remove button (#45368)

 tests/form_pages/test_block.py                | 73 +++++++++++++++++++
 wcs/blocks.py                                 | 13 +++-
 wcs/fields.py                                 |  6 +-
 wcs/qommon/form.py                            |  7 +-
 wcs/qommon/static/css/dc2/admin.scss          | 27 +++++++
 wcs/qommon/static/js/qommon.forms.js          | 17 +++++
 .../templates/qommon/forms/widgets/block.html |  3 +
 .../qommon/forms/widgets/block_sub.html       |  9 +++
 8 files changed, 148 insertions(+), 7 deletions(-)
 create mode 100644 wcs/qommon/templates/qommon/forms/widgets/block.html
 create mode 100644 wcs/qommon/templates/qommon/forms/widgets/block_sub.html
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
-