backoffice: set default view as default (#47492)

This commit is contained in:
Lauréline Guérin 2020-10-13 15:10:49 +02:00
parent 5bb8b78512
commit f0890d999c
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
7 changed files with 282 additions and 14 deletions

View File

@ -13,6 +13,7 @@ from wcs.qommon.ident.password_accounts import PasswordAccount
from wcs.qommon.http_request import HTTPRequest
from wcs.carddef import CardDef
from wcs.formdef import FormDef
from wcs.roles import Role
from wcs import fields
from utilities import (get_app, login, create_temporary_pub, clean_temporary_pub)
@ -290,6 +291,156 @@ def test_backoffice_custom_view_visibility(pub):
[('custom-test-view', 'owner'), ('shared-view', 'any'), ('shared-view-2', 'any')])
def test_backoffice_custom_view_is_default(pub):
create_superuser(pub)
FormDef.wipe()
pub.custom_view_class.wipe()
formdef = FormDef()
formdef.name = 'form title'
formdef.fields = []
formdef.workflow_roles = {'_receiver': 1}
formdef.store()
# private custom view
agent = pub.user_class(name='agent')
agent.roles = [formdef.workflow_roles['_receiver']]
agent.store()
account = PasswordAccount(id='agent')
account.set_password('agent')
account.user_id = agent.id
account.store()
app = login(get_app(pub), username='agent', password='agent')
resp = app.get('/backoffice/management/form-title/')
resp = resp.forms['listing-settings'].submit()
resp.forms['save-custom-view']['title'] = 'view 1'
resp.forms['save-custom-view']['is_default'] = True
resp = resp.forms['save-custom-view'].submit()
# other private custom view
app = login(get_app(pub))
resp = app.get('/backoffice/management/form-title/')
resp = resp.forms['listing-settings'].submit()
resp.forms['save-custom-view']['title'] = 'view 2'
resp.forms['save-custom-view']['visibility'] = 'owner'
resp.forms['save-custom-view']['is_default'] = True
resp = resp.forms['save-custom-view'].submit()
# shared custom view
resp = app.get('/backoffice/management/form-title/')
resp = resp.forms['listing-settings'].submit()
resp.forms['save-custom-view']['title'] = 'view 3'
resp.forms['save-custom-view']['visibility'] = 'any'
resp.forms['save-custom-view']['is_default'] = True
resp = resp.forms['save-custom-view'].submit()
assert pub.custom_view_class.count() == 3
assert pub.custom_view_class.get(1).is_default is True # simple user - private
assert pub.custom_view_class.get(2).is_default is True # super user - private
assert pub.custom_view_class.get(3).is_default is True # super user - shared
# not possible to define more than one default private view
resp = app.get('/backoffice/management/form-title/')
resp = resp.forms['listing-settings'].submit()
resp.forms['save-custom-view']['title'] = 'view 4'
resp.forms['save-custom-view']['visibility'] = 'owner'
resp.forms['save-custom-view']['is_default'] = True
resp = resp.forms['save-custom-view'].submit()
assert pub.custom_view_class.count() == 4
assert pub.custom_view_class.get(1).is_default is True # simple user - private
assert pub.custom_view_class.get(2).is_default is False # super user - private
assert pub.custom_view_class.get(3).is_default is True # super user - shared
assert pub.custom_view_class.get(4).is_default is True # super user - private 2
# not possible to define more than one default shared view
resp = app.get('/backoffice/management/form-title/')
resp = resp.forms['listing-settings'].submit()
resp.forms['save-custom-view']['title'] = 'view 5'
resp.forms['save-custom-view']['visibility'] = 'any'
resp.forms['save-custom-view']['is_default'] = True
resp = resp.forms['save-custom-view'].submit()
assert pub.custom_view_class.count() == 5
assert pub.custom_view_class.get(1).is_default is True # simple user - private
assert pub.custom_view_class.get(2).is_default is False # super user - private
assert pub.custom_view_class.get(3).is_default is False # super user - shared
assert pub.custom_view_class.get(4).is_default is True # super user - private 2
assert pub.custom_view_class.get(5).is_default is True # super user - shared 2
def test_backoffice_default_custom_view(pub):
user = create_superuser(pub)
FormDef.wipe()
pub.custom_view_class.wipe()
formdef = FormDef()
formdef.name = 'form title'
formdef.fields = [
fields.ItemField(
id='1', label='field 1', type='item',
items=['foo', 'bar', 'baz'],
display_locations=['validation', 'summary', 'listings']),
]
formdef.workflow_roles = {'_receiver': 1}
formdef.store()
formdef.data_class().wipe()
for i in range(3):
formdata = formdef.data_class()()
formdata.data = {}
if i == 0:
formdata.data['1'] = 'foo'
formdata.data['1_display'] = 'foo'
else:
formdata.data['1'] = 'baz'
formdata.data['1_display'] = 'baz'
if i < 2:
formdata.jump_status('new')
formdata.store()
app = login(get_app(pub))
# define a shared defautl view
custom_view = pub.custom_view_class()
custom_view.title = 'shared custom test view'
custom_view.formdef = formdef
custom_view.visibility = 'any'
custom_view.columns = {'list': [{'id': 'id'}]}
custom_view.filters = {'filter-1-value': 'baz', 'filter': 'all', 'filter-1': 'on', 'filter-status': 'on'}
custom_view.user = user
custom_view.is_default = True
custom_view.store()
resp = app.get('/backoffice/management/form-title/shared-custom-test-view/')
assert resp.text.count('<span>User Label</span>') == 0
assert resp.text.count('<span>field 1</span>') == 0
assert resp.text.count('<tr') == 3
# check that default view is applied
resp = app.get('/backoffice/management/form-title/')
assert resp.text.count('<span>User Label</span>') == 0
assert resp.text.count('<span>field 1</span>') == 0
assert resp.text.count('<tr') == 3
# define a user default view
custom_view = pub.custom_view_class()
custom_view.title = 'private custom test view'
custom_view.formdef = formdef
custom_view.visibility = 'owner'
custom_view.columns = {'list': [{'id': '1'}]}
custom_view.filters = {'filter-1-value': 'foo', 'filter': 'all', 'filter-1': 'on', 'filter-status': 'on'}
custom_view.user = user
custom_view.is_default = True
custom_view.store()
resp = app.get('/backoffice/management/form-title/user-private-custom-test-view/')
assert resp.text.count('<span>User Label</span>') == 0
assert resp.text.count('<span>field 1</span>') == 1
assert resp.text.count('<tr') == 2
# check that private default view is applied, and not shared default view
resp = app.get('/backoffice/management/form-title/')
assert resp.text.count('<span>User Label</span>') == 0
assert resp.text.count('<span>field 1</span>') == 1
assert resp.text.count('<tr') == 2
def test_backoffice_missing_custom_view(pub):
create_superuser(pub)
@ -336,6 +487,37 @@ def test_carddata_custom_view(pub, studio):
resp = resp.follow()
def test_carddata_custom_view_is_default(pub):
user = create_superuser(pub)
Role.wipe()
role = Role(name='foobar')
role.store()
user.roles = [role.id]
user.store()
CardDef.wipe()
pub.custom_view_class.wipe()
carddef = CardDef()
carddef.name = 'foo'
carddef.fields = [
fields.StringField(id='1', label='Test', type='string', varname='foo'),
]
carddef.backoffice_submission_roles = user.roles
carddef.digest_template = '{{ form_var_foo }}'
carddef.store()
carddef.data_class().wipe()
# datasource custom view
app = login(get_app(pub))
resp = app.get('/backoffice/data/foo/')
resp = resp.forms['listing-settings'].submit()
resp.forms['save-custom-view']['title'] = 'view 3'
resp.forms['save-custom-view']['visibility'] = 'datasource'
resp.forms['save-custom-view']['is_default'] = True
resp = resp.forms['save-custom-view'].submit()
assert pub.custom_view_class.get(1).is_default is False # not for datasource
def test_backoffice_custom_view_keep_filters(pub):
user = create_superuser(pub)

View File

@ -2191,6 +2191,7 @@ def test_api_list_formdata_custom_view(pub, local_user):
resp = get_app(pub).get(sign_uri('/api/forms/test/list', user=local_user))
assert len(resp.json) == 30
pub.custom_view_class.wipe()
custom_view = pub.custom_view_class()
custom_view.title = 'custom view'
custom_view.formdef = formdef
@ -2239,6 +2240,7 @@ def test_api_ods_formdata_custom_view(pub, local_user):
ods_sheet = ET.parse(zipf.open('content.xml'))
assert len(ods_sheet.findall('.//{%s}table-row' % ods.NS['table'])) == 11
pub.custom_view_class.wipe()
custom_view = pub.custom_view_class()
custom_view.title = 'custom view'
custom_view.formdef = formdef
@ -2289,6 +2291,7 @@ def test_api_geojson_formdata_custom_view(pub, local_user):
resp = get_app(pub).get(sign_uri('/api/forms/test/geojson', user=local_user))
assert len(resp.json['features']) == 10
pub.custom_view_class.wipe()
custom_view = pub.custom_view_class()
custom_view.title = 'custom view'
custom_view.formdef = formdef

View File

@ -216,12 +216,12 @@ class ApiFormPage(BackofficeFormPage):
else:
# custom view
for view in self.get_custom_views([Equal('visibility', 'any'), Equal('slug', path[1])]):
self.view = view
self._view = view
path = [mode]
if len(path) >= 2 and path[1] == 'ics':
for view in self.get_custom_views([Equal('visibility', 'any'), Equal('slug', path[0])]):
self.view = view
self._view = view
path = path[1:]
self.is_webhook = False

View File

@ -1027,7 +1027,9 @@ class FormPage(Directory):
_q_exports = ['', 'csv', 'stats', 'xls', 'ods', 'json', 'export', 'map',
'geojson', ('filter-options', 'filter_options'),
('save-view', 'save_view'), ('delete-view', 'delete_view'),]
view = None
_view = None
default_view = None
use_default_view = False
admin_permission = 'forms'
formdef_class = FormDef
search_label = N_('Search in form content')
@ -1043,9 +1045,40 @@ class FormPage(Directory):
get_response().breadcrumb.append((component + '/', self.formdef.name))
else:
self.formdef = formdef
self.view = view
self._view = view
if update_breadcrumbs:
get_response().breadcrumb.append((view.get_url_slug() + '/', view.title))
self.set_default_view()
def set_default_view(self):
if not get_request():
return
custom_views = list(self.get_custom_views())
# search for first default user custom view
for view in custom_views:
if view.visibility != 'owner':
continue
if not view.is_default:
continue
self.default_view = view
return
# default user custom view not found, search in 'any' custom views
for view in custom_views:
if view.visibility != 'any':
continue
if not view.is_default:
continue
self.default_view = view
return
@property
def view(self):
view = self._view
if self.use_default_view:
view = view or self.default_view
return view
def check_access(self, api_name=None):
session = get_session()
@ -1093,14 +1126,16 @@ class FormPage(Directory):
r += htmltext('<ul id="sidebar-custom-views">')
view_type = 'map' if self.view_type == 'map' else ''
for view in sorted(views, key=lambda x: getattr(x, 'title')):
if self.view:
active = bool(self.view.get_url_slug() == view.get_url_slug())
if self._view:
active = bool(self._view.get_url_slug() == view.get_url_slug())
r += htmltext('<li class="active">' if active else '<li>')
r += htmltext('<a href="../%s/%s">%s</a>') % (view.get_url_slug(), view_type, view.title)
else:
r += htmltext('<li><a href="%s/%s">%s</a>') % (view.get_url_slug(), view_type, view.title)
if view.visibility == 'datasource':
r += htmltext(' <span class="as-data-source">(%s)</span>') % _('for data sources')
elif self.default_view and view.id == self.default_view.id:
r += htmltext(' <span class="default-custom-view">(%s)</span>') % _('default')
r += htmltext('</li>')
r += htmltext('</ul>')
return r.getvalue()
@ -1479,9 +1514,28 @@ class FormPage(Directory):
if isinstance(self.formdef, CardDef) and self.formdef.digest_template:
options.append(('datasource', _('as data source'), 'datasource'))
form.add(RadiobuttonsWidget, 'visibility', title=_('Visibility'),
value=self.view.visibility if self.view else 'owner',
options=options)
form.add(
RadiobuttonsWidget,
'visibility',
title=_('Visibility'),
value=self.view.visibility if self.view else 'owner',
options=options,
attrs={'data-dynamic-display-parent': 'true'})
form.add(
CheckboxWidget,
'is_default',
title=_('Set as default view'),
value=self.view.is_default if self.view else False,
attrs={
'data-dynamic-display-child-of': 'visibility',
'data-dynamic-display-value-in': 'owner|any',
})
else:
form.add(
CheckboxWidget,
'is_default',
title=_('Set as default view'),
value=self.view.is_default if self.view else False)
if self.view and (self.view.user_id == get_request().user.id or
get_publisher().get_backoffice_root().is_accessible(self.admin_permission)):
form.add(CheckboxWidget, 'update', title=_('Update existing view settings'), value=True)
@ -1505,9 +1559,23 @@ class FormPage(Directory):
if not custom_view.columns['list']:
get_session().message = ('error', _('Views must have at least one column.'))
return redirect('.')
if form.get_widget('is_default'):
custom_view.is_default = form.get_widget('is_default').parse()
if form.get_widget('visibility'):
custom_view.visibility = form.get_widget('visibility').parse()
if custom_view.visibility == 'datasource':
custom_view.is_default = False
custom_view.store()
if custom_view.is_default and custom_view.visibility != 'datasource':
# need to clean other views to have only one default per owner/any visibility
for view in self.get_custom_views():
if view.id == custom_view.id:
continue
if custom_view.visibility == view.visibility and view.is_default:
view.is_default = False
view.store()
if self.view:
return redirect('../' + custom_view.get_url_slug() + '/')
else:
@ -1788,6 +1856,7 @@ class FormPage(Directory):
if 'job' in get_request().form:
return self.job_multi()
self.use_default_view = True
fields = self.get_fields_from_query()
selected_filter = self.get_filter_from_query()
criterias = self.get_criterias_from_query()

View File

@ -32,6 +32,7 @@ class CustomView(StorableObject):
visibility = 'owner'
formdef_type = None
formdef_id = None
is_default = False
columns = None
filters = None
order_by = None
@ -57,12 +58,12 @@ class CustomView(StorableObject):
self.formdef_type = value.xml_root_node
def match(self, user, formdef):
if self.visibility == 'owner' and self.user_id != str(user.id):
return False
if self.formdef_type != formdef.xml_root_node:
return False
if self.formdef_id != str(formdef.id):
return False
if self.visibility == 'owner' and self.user_id != str(user.id):
return False
return True
def set_from_qs(self, qs):

View File

@ -334,6 +334,7 @@ $(function() {
}
]
});
$(document).trigger('wcs:dialog-loaded', $(dialog));
return false;
});
@ -376,4 +377,8 @@ $(function() {
prepare_page_links();
prepare_row_links();
prepare_column_headers();
$('a[data-base-href]').each(function(idx, elem) {
$(elem).attr('href', $(elem).data('base-href') + '?' + $('form#listing-settings').serialize());
});
});

View File

@ -803,6 +803,7 @@ def do_custom_views_table():
visibility varchar,
formdef_type varchar,
formdef_id varchar,
is_default boolean,
order_by varchar,
columns jsonb,
filters jsonb
@ -814,6 +815,10 @@ def do_custom_views_table():
needed_fields = set([x[0] for x in CustomView._table_static_fields])
# migrations
if 'is_default' not in existing_fields:
cur.execute('''ALTER TABLE %s ADD COLUMN is_default boolean DEFAULT FALSE''' % table_name)
# delete obsolete fields
for field in (existing_fields - needed_fields):
cur.execute('''ALTER TABLE %s DROP COLUMN %s''' % (table_name, field))
@ -2298,6 +2303,7 @@ class CustomView(SqlMixin, wcs.custom_views.CustomView):
('visibility', 'varchar'),
('formdef_type', 'varchar'),
('formdef_id', 'varchar'),
('is_default', 'boolean'),
('order_by', 'varchar'),
('columns', 'jsonb'),
('filters', 'jsonb'),
@ -2315,6 +2321,7 @@ class CustomView(SqlMixin, wcs.custom_views.CustomView):
'visibility': self.visibility,
'formdef_type': self.formdef_type,
'formdef_id': self.formdef_id,
'is_default': self.is_default,
'order_by': self.order_by,
'columns': self.columns,
'filters': self.filters,
@ -2358,7 +2365,7 @@ class CustomView(SqlMixin, wcs.custom_views.CustomView):
for field, value in zip(cls._table_static_fields, tuple(row)):
if field[1] == 'varchar':
setattr(o, field[0], str_encode(value))
elif field[1] == 'jsonb':
elif field[1] in ('jsonb', 'boolean'):
setattr(o, field[0], value)
return o
@ -2817,8 +2824,9 @@ def migrate():
# 25: create session_table
# 32: add last_update_time column to session table
do_session_table()
if sql_level < 37:
# 37: create custom_views tabl
if sql_level < 43:
# 37: create custom_views table
# 43: add is_default column to custom_views table
do_custom_views_table()
if sql_level < 30:
# 30: actually remove evo.who on anonymised formdatas