Projet

Général

Profil

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

Frédéric Péters, 05 février 2021 15:43

Télécharger (11,3 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                                 | 11 ++-
 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, 153 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
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
-