Projet

Général

Profil

0001-forms-add-an-autocomplete-display-mode-to-items-fiel.patch

Frédéric Péters, 19 avril 2022 17:47

Télécharger (8,92 ko)

Voir les différences:

Subject: [PATCH] forms: add an "autocomplete" display mode to items fields
 (#46161)

 tests/form_pages/test_all.py                  | 57 +++++++++++++++++++
 wcs/fields.py                                 | 18 ++++++
 wcs/qommon/form.py                            | 42 +++++++++++++-
 .../qommon/forms/widgets/multiselect.html     | 20 +++++++
 4 files changed, 136 insertions(+), 1 deletion(-)
 create mode 100644 wcs/qommon/templates/qommon/forms/widgets/multiselect.html
tests/form_pages/test_all.py
615 615
    assert 'The form has been recorded' in page.text
616 616

  
617 617

  
618
def test_form_items_autocomplete(pub):
619
    formdef = create_formdef()
620
    formdef.fields = [
621
        fields.ItemsField(
622
            id='0',
623
            label='List of items',
624
            type='items',
625
            required=True,
626
            varname='foo',
627
            display_mode='autocomplete',
628
            items=['Foo', 'Bar', 'Three', 'Four', 'Five', 'Six'],
629
        )
630
    ]
631
    formdef.store()
632
    formdef.data_class().wipe()
633

  
634
    resp = get_app(pub).get('/test/')
635
    assert 'select2.min.js' in resp.text
636
    resp = resp.forms[0].submit('submit')  # but the field is required
637
    assert resp.pyquery('div.error').text() == 'required field'
638
    resp.forms[0]['f0[]'].select_multiple(['Foo', 'Bar'])
639
    resp = resp.forms[0].submit('submit')
640
    assert resp.pyquery('[name="f0[]"] option[selected]').text() == 'Foo Bar'
641
    assert resp.pyquery('#form_f0[readonly]').val() == 'Foo, Bar'
642
    assert 'Check values then click submit.' in resp.text
643
    resp = resp.forms[0].submit('submit').follow()
644
    assert 'The form has been recorded' in resp.text
645
    assert formdef.data_class().count() == 1
646
    data_id = formdef.data_class().select()[0].id
647
    data = formdef.data_class().get(data_id)
648
    assert data.data['0'] == ['Foo', 'Bar']
649
    assert data.data['0_display'] == 'Foo, Bar'
650

  
651
    formdef.data_class().wipe()
652

  
653
    formdef.fields[0].min_choices = 3
654
    formdef.fields[0].max_choices = 4
655
    formdef.store()
656

  
657
    resp = get_app(pub).get('/test/')
658
    resp.forms[0]['f0[]'].select_multiple(['Foo', 'Bar'])
659
    resp = resp.forms[0].submit('submit')
660
    assert resp.pyquery('div.error').text() == 'You must select at least 3 choices.'
661
    assert resp.forms[0]['f0[]'].value == ['Foo', 'Bar']
662
    resp.forms[0]['f0[]'].select_multiple(['Foo', 'Bar', 'Three', 'Four', 'Five'])
663
    resp = resp.forms[0].submit('submit')
664
    assert resp.pyquery('div.error').text() == 'You must select at most 4 choices.'
665
    assert resp.forms[0]['f0[]'].value == ['Foo', 'Bar', 'Three', 'Four', 'Five']
666
    resp.forms[0]['f0[]'].select_multiple(['Foo', 'Bar', 'Three'])
667
    resp = resp.forms[0].submit('submit')  # -> validation
668
    resp = resp.forms[0].submit('submit').follow()
669

  
670
    data_id = formdef.data_class().select()[0].id
671
    data = formdef.data_class().get(data_id)
672
    assert data.data['0'] == ['Foo', 'Bar', 'Three']
673

  
674

  
618 675
def test_form_string_with_invalid_xml_chars(pub):
619 676
    formdef = create_formdef()
620 677
    formdef.fields = [fields.StringField(id='0', label='string')]
wcs/fields.py
54 54
    JsonpSingleSelectWidget,
55 55
    MapMarkerSelectionWidget,
56 56
    MapWidget,
57
    MultiSelectWidget,
57 58
    PasswordEntryWidget,
58 59
    RadiobuttonsWidget,
59 60
    RankedItemsWidget,
......
2297 2298
    data_source = {}
2298 2299
    in_filters = False
2299 2300
    display_disabled_items = False
2301
    display_mode = 'checkboxes'
2300 2302

  
2301 2303
    widget_class = CheckboxesWidget
2302 2304

  
......
2329 2331
        if len(kwargs['options']) > 3:
2330 2332
            kwargs['inline'] = False
2331 2333

  
2334
        if self.display_mode == 'autocomplete':
2335
            self.widget_class = MultiSelectWidget
2336

  
2332 2337
    def fill_admin_form(self, form):
2333 2338
        WidgetField.fill_admin_form(self, form)
2334 2339
        form.add(
......
2338 2343
            value=self.in_filters,
2339 2344
            advanced=True,
2340 2345
        )
2346
        options = [
2347
            ('checkboxes', _('Checkboxes'), 'checkboxes'),
2348
            ('autocomplete', _('Autocomplete'), 'autocomplete'),
2349
        ]
2350
        form.add(
2351
            RadiobuttonsWidget,
2352
            'display_mode',
2353
            title=_('Display Mode'),
2354
            options=options,
2355
            value=self.display_mode,
2356
            extra_css_class='widget-inline-radio',
2357
        )
2341 2358
        self.add_items_fields_admin_form(form)
2342 2359
        form.add(
2343 2360
            IntWidget,
......
2366 2383
    def get_admin_attributes(self):
2367 2384
        return WidgetField.get_admin_attributes(self) + [
2368 2385
            'items',
2386
            'display_mode',
2369 2387
            'min_choices',
2370 2388
            'max_choices',
2371 2389
            'data_source',
wcs/qommon/form.py
58 58
from quixote.form import CheckboxWidget as QuixoteCheckboxWidget
59 59
from quixote.form import FileWidget
60 60
from quixote.form import Form as QuixoteForm
61
from quixote.form import HiddenWidget, IntWidget, PasswordWidget, SelectWidget
61
from quixote.form import HiddenWidget, IntWidget, MultipleSelectWidget, PasswordWidget, SelectWidget
62 62
from quixote.form import StringWidget as QuixoteStringWidget
63 63
from quixote.form import TextWidget as QuixoteTextWidget
64 64
from quixote.form import Widget
......
2486 2486
        return None
2487 2487

  
2488 2488

  
2489
class MultiSelectWidget(MultipleSelectWidget):
2490
    template_name = 'qommon/forms/widgets/multiselect.html'
2491

  
2492
    def __init__(self, name, value=None, **kwargs):
2493
        self.options_with_attributes = kwargs.pop('options_with_attributes', None)
2494
        self.readonly = bool(kwargs.pop('readonly', False))
2495
        self.min_choices = int(kwargs.pop('min_choices', 0) or 0)
2496
        self.max_choices = int(kwargs.pop('max_choices', 0) or 0)
2497
        super().__init__(name, value=value, **kwargs)
2498

  
2499
    def add_media(self):
2500
        if not self.readonly:
2501
            get_response().add_javascript(['select2.js'])
2502

  
2503
    def get_options(self):
2504
        options = self.options_with_attributes or self.options
2505
        for option in options:
2506
            object, description, key = option[:3]
2507
            yield {
2508
                'value': key,
2509
                'label': description,
2510
                'disabled': bool(self.options_with_attributes and option[-1].get('disabled')),
2511
                'selected': self.is_selected(object),
2512
            }
2513

  
2514
    def get_selected_options_labels(self):
2515
        return list(x.get('label') for x in self.get_options() if x.get('selected'))
2516

  
2517
    def _parse(self, request):
2518
        orig_name, self.name = self.name, self.name + '[]'
2519
        try:
2520
            super()._parse(request)
2521
        finally:
2522
            self.name = orig_name
2523
        if self.value and self.min_choices and len(self.value) < self.min_choices:
2524
            self.set_error(_('You must select at least %d choices.') % self.min_choices)
2525
        if self.value and self.max_choices and len(self.value) > self.max_choices:
2526
            self.set_error(_('You must select at most %d choices.') % self.max_choices)
2527

  
2528

  
2489 2529
class WidgetListAsTable(WidgetList):
2490 2530
    def render_content(self):
2491 2531
        r = TemplateIO(html=True)
wcs/qommon/templates/qommon/forms/widgets/multiselect.html
1
{% extends "qommon/forms/widget.html" %}
2
{% block widget-control %}
3
<select
4
    {% if widget.readonly %}
5
    hidden
6
    id="form_{{widget.name}}_hidden" name="{{widget.name}}[]"
7
    {% else %}
8
    id="form_{{widget.name}}" name="{{widget.name}}[]"
9
    data-autocomplete="true"
10
    {% endif %}
11
    {% if widget.required %}data-required="true"{% endif %}
12
    {% for attr in widget.attrs.items %}{% if attr.0 != 'readonly' %}{{attr.0}}="{{attr.1}}" {% endif %}{% endfor %}>
13
  {% for option in widget.get_options %}
14
  <option value="{{ option.value }}" {% if option.disabled %}disabled{% endif %} {% if option.selected %}selected{% endif %}>{{ option.label }}</option>
15
  {% endfor %}
16
</select>
17
{% if widget.readonly %}
18
<input readonly id="form_{{widget.name}}" value="{{ widget.get_selected_options_labels|join:", " }}">
19
{% endif %}
20
{% endblock %}
0
-