backoffice: custom view datasource, return all options (#77302) #320

Merged
lguerin merged 3 commits from wip/77302-customview-datasource-options into main 2023-06-12 16:07:32 +02:00
3 changed files with 153 additions and 51 deletions

View File

@ -1207,7 +1207,7 @@ def test_backoffice_custom_view_boolean_filters(pub):
assert resp.text.count('data-link=') == 5
def test_item_options_in_dynamic_view(pub):
def test_item_options_in_custom_view(pub):
pub.user_class.wipe()
create_superuser(pub)
pub.role_class.wipe()
@ -1223,12 +1223,39 @@ def test_item_options_in_dynamic_view(pub):
fields.StringField(
id='1',
label='1st field',
type='string',
),
fields.ItemField(
id='2',
label='2nd field',
type='item',
items=list('azertyuiopqsdfghjklmwxcvbn')[:16],
display_locations=['validation', 'summary', 'listings'],
display_mode='list',
),
fields.ItemField(
id='3',
label='3rd field',
type='item',
items=list('azertyuiopqsdfghjklmwxcvbn')[:15],
display_locations=['validation', 'summary', 'listings'],
display_mode='list',
),
fields.ItemField(
id='4',
label='4th field',
items=['â', 'b', 'c', 'd'],
type='item',
items=list('azertyuiopqsdfghjklmwxcvbn')[:16],
display_locations=['validation', 'summary', 'listings'],
display_mode='autocomplete',
),
fields.ItemField(
id='5',
label='5th field',
type='item',
items=list('azertyuiopqsdfghjklmwxcvbn')[:15],
display_locations=['validation', 'summary', 'listings'],
display_mode='autocomplete',
),
]
carddef.workflow_roles = {'_editor': role.id}
@ -1241,55 +1268,102 @@ def test_item_options_in_dynamic_view(pub):
carddata = data_class()
carddata.data = {
'1': 'plop%s' % (i % 2),
'2': 'a%s' % (i % 4),
'2_display': 'a%s' % (i % 4),
'3': 'a%s' % (i % 4),
'3_display': 'a%s' % (i % 4),
'4': 'a%s' % (i % 4),
'4_display': 'a%s' % (i % 4),
'5': 'a%s' % (i % 4),
'5_display': 'a%s' % (i % 4),
}
carddata.just_created()
carddata.store()
custom_view = pub.custom_view_class()
custom_view.title = 'custom test view'
custom_view.formdef = carddef
custom_view.visibility = 'datasource'
custom_view.columns = {'list': [{'id': '1'}]}
custom_view.filters = {}
custom_view.store()
datasource_custom_view = pub.custom_view_class()
datasource_custom_view.title = 'custom test view for datasource'
datasource_custom_view.formdef = carddef
datasource_custom_view.visibility = 'datasource'
datasource_custom_view.columns = {'list': [{'id': '1'}]}
datasource_custom_view.filters = {}
datasource_custom_view.store()
any_custom_view = pub.custom_view_class()
any_custom_view.title = 'custom test view for anyone'
any_custom_view.formdef = carddef
any_custom_view.visibility = 'any'
any_custom_view.columns = {'list': [{'id': '1'}]}
any_custom_view.filters = {}
any_custom_view.store()
app = login(get_app(pub))
resp = app.get('/backoffice/data/card-title/custom-test-view/')
# enable both filters
resp = app.get('/backoffice/data/card-title/custom-test-view-for-anyone/')
# enable filters
resp.forms['listing-settings']['filter-1'].checked = True
resp.forms['listing-settings']['filter-2'].checked = True
resp.forms['listing-settings']['filter-3'].checked = True
resp.forms['listing-settings']['filter-4'].checked = True
resp.forms['listing-settings']['filter-5'].checked = True
resp = resp.forms['listing-settings'].submit()
# all used options are listed ({} is "custom value")
assert [x[0] for x in resp.forms['listing-settings']['filter-4-value'].options] == [
# field 2: select - all used options are listed
assert [x[0] for x in resp.forms['listing-settings']['filter-2-value'].options] == [
'',
'a0',
'a1',
'a2',
'a3',
'{}',
]
# plain filter, only used options are listed
resp.forms['listing-settings']['filter-1-value'].value = 'plop0'
resp.forms['listing-settings']['filter-4-value'].value = 'a0'
resp = resp.forms['listing-settings'].submit()
assert [x[0] for x in resp.forms['listing-settings']['filter-4-value'].options] == ['', 'a0', 'a2', '{}']
# template filter, all options are listed
resp.forms['listing-settings']['filter-1-value'].value = '{{ test }}'
resp = resp.forms['listing-settings'].submit()
assert [x[0] for x in resp.forms['listing-settings']['filter-4-value'].options] == [
# field 3: select - all used options are listed
assert [x[0] for x in resp.forms['listing-settings']['filter-3-value'].options] == [
'',
'a0',
'a1',
'a2',
'a3',
'{}',
]
# field 4: select2 - all used options are listed
assert [x[0] for x in resp.forms['listing-settings']['filter-4-value'].options] == ['']
resp2 = app.get(resp.request.path + 'filter-options?filter_field_id=4&_search=')
assert [x['id'] for x in resp2.json['data']] == ['a0', 'a1', 'a2', 'a3']
# field 5: select2 - all used options are listed
assert [x[0] for x in resp.forms['listing-settings']['filter-5-value'].options] == ['']
resp2 = app.get(resp.request.path + 'filter-options?filter_field_id=5&_search=')
assert [x['id'] for x in resp2.json['data']] == ['a0', 'a1', 'a2', 'a3']
resp = app.get('/backoffice/data/card-title/custom-test-view-for-datasource/')
# enable filters
resp.forms['listing-settings']['filter-1'].checked = True
resp.forms['listing-settings']['filter-2'].checked = True
resp.forms['listing-settings']['filter-3'].checked = True
resp.forms['listing-settings']['filter-4'].checked = True
resp.forms['listing-settings']['filter-5'].checked = True
resp = resp.forms['listing-settings'].submit()
# field 2: select2 - all items are listed
assert [x[0] for x in resp.forms['listing-settings']['filter-2-value'].options] == ['', '{}']
resp2 = app.get(resp.request.path + 'filter-options?filter_field_id=2&_search=')
assert [x['id'] for x in resp2.json['data']] == list('azertyuiopqsdfghjklmwxcvbn')[:15] + ['{}']
# field 3: select - all items are listed
assert [x[0] for x in resp.forms['listing-settings']['filter-3-value'].options] == [''] + list(
'azertyuiopqsdfghjklmwxcvbn'
)[:15] + ['{}']
# field 4: select2 - all items are listed
assert [x[0] for x in resp.forms['listing-settings']['filter-4-value'].options] == ['', '{}']
resp2 = app.get(resp.request.path + 'filter-options?filter_field_id=4&_search=')
assert [x['id'] for x in resp2.json['data']] == list('azertyuiopqsdfghjklmwxcvbn')[:15] + ['{}']
# field 5: select - all items are listed
assert [x[0] for x in resp.forms['listing-settings']['filter-5-value'].options] == [''] + list(
'azertyuiopqsdfghjklmwxcvbn'
)[:15] + ['{}']
@pytest.mark.parametrize('user_perms', ['admin', 'category_admin', 'category_not_admin', 'agent'])
def test_backoffice_hidden_data_source_custom_view(pub, user_perms):

View File

@ -382,6 +382,31 @@ def test_backoffice_item_filter(pub):
else:
assert [x['id'] for x in resp2.json['data']] == ['â', 'b', 'd']
# item field in autocomplete mode, check that label is weel displayed in option
CardDef.wipe()
carddef = CardDef()
carddef.name = 'foo'
carddef.fields = [
fields.StringField(id='1', label='Test', type='string', varname='foo'),
]
carddef.digest_templates = {'default': 'card {{ form_var_foo }}'}
carddef.store()
card_ids = {}
for label in ('foo', 'bar', 'baz', 'foo, bar'):
card = carddef.data_class()()
card.data = {'1': label}
card.just_created()
card.store()
card_ids[label] = str(card.id)
formdef.fields[0].display_mode = 'autocomplete'
formdef.fields[0].data_source = {'type': 'carddef:foo', 'value': ''}
formdef.store()
resp.forms['listing-settings']['filter-4-value'].force_value(card_ids['baz'])
resp = resp.forms['listing-settings'].submit()
assert [x[2] for x in resp.forms['listing-settings']['filter-4-value'].options] == ['card baz']
def test_backoffice_item_double_filter(pub):
pub.user_class.wipe()

View File

@ -937,6 +937,8 @@ class FormPage(FormdefDirectoryBase):
criterias=None,
anonymised=False,
):
if self.view and self.view.visibility == 'datasource':
return filter_field.get_options()
# remove potential filter on self
filter_field_id = get_field_id(filter_field)
filtered_criterias = []
@ -981,22 +983,9 @@ class FormPage(FormdefDirectoryBase):
# for item/items fields, get actual option values from database
if not getattr(filter_field, 'block_field', None):
criterias.append(NotNull(sql.get_field_id(filter_field)))
if self.view and self.view.visibility == 'datasource':
# for custom views used as data sources ignore criterias
# that would result in an empty list of options.
# (either "Nothing" or a template string)
options_criterias = []
for criteria in criterias:
if isinstance(criteria, Nothing):
continue
if Template.is_template_string(getattr(criteria, 'value', '')):
continue
options_criterias.append(criteria)
else:
options_criterias = criterias
options = self.formdef.data_class().select_distinct(
[sql.get_field_id(filter_field), '%s_display' % sql.get_field_id(filter_field)],
clause=options_criterias,
clause=criterias,
)
else:
# in case of blocks, this requires digging into the jsonb columns,
@ -1046,7 +1035,7 @@ class FormPage(FormdefDirectoryBase):
term = get_request().form.get('_search')
if term:
options = [x for x in options if term.lower() in x[1].lower()]
options = options[:15]
options = options[:15]
if self.view and self.view.visibility == 'datasource':
options.append(('{}', _('custom value')))
get_response().set_content_type('application/json')
@ -1298,36 +1287,50 @@ class FormPage(FormdefDirectoryBase):
elif filter_field.key in ('item', 'items'):
filter_field.required = False
# Get options from existing formdatas.
# Get options from existing formdatas, except for custom views with visibility "datasource"
# This allows for options that don't appear anymore in the
# data source to be listed (for example because the field
# is using a parametrized URL depending on unavailable
# variables, or simply returning different results now).
is_datasource_customview = self.view and self.view.visibility == 'datasource'
display_mode = 'select'
if filter_field.key == 'item' and filter_field.get_display_mode() == 'autocomplete':
if (
not is_datasource_customview
and filter_field.key == 'item'
and filter_field.get_display_mode() == 'autocomplete'
):
display_mode = 'select2'
if is_datasource_customview:
options = filter_field.get_options()
if len(options) > 15:
display_mode = 'select2'
is_multi_values = filter_field_operator in ['in', 'not_in', 'between']
if display_mode == 'select':
options = self.get_item_filter_options(
filter_field,
selected_filter,
selected_filter_operator,
criterias,
)
options = [(x[0], x[1], x[0]) for x in options]
if not is_datasource_customview:
options = self.get_item_filter_options(
filter_field,
selected_filter,
selected_filter_operator,
criterias,
)
options = [(x[0], x[1], x[0]) for x in options]
options.insert(0, (None, '', ''))
attrs = {'data-refresh-options': str(filter_field.contextual_id)}
else:
options = [(None, '', '')]
if not is_multi_values:
options = [(filter_field_value, filter_field_value or '', filter_field_value or '')]
value_display = filter_field_value or ''
if filter_field_value:
value_display = filter_field.get_display_value(filter_field_value)
options = [(filter_field_value, value_display, filter_field_value or '')]
attrs = {'data-remote-options': str(filter_field.contextual_id)}
get_response().add_javascript(
['jquery.js', '../../i18n.js', 'qommon.forms.js', 'select2.js']
)
get_response().add_css_include('select2.css')
if self.view and self.view.visibility == 'datasource':
if is_datasource_customview:
options.append(('{}', _('custom value'), '{}'))
if filter_field_value and filter_field_value not in [x[0] for x in options]:
options.append((filter_field_value, filter_field_value, filter_field_value))