0001-forms-add-an-autocomplete-display-mode-to-items-fiel.patch
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 |
- |