From f0890d999cd1a7d489c9cc759bfb24adb50f4e1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= Date: Tue, 13 Oct 2020 15:10:49 +0200 Subject: [PATCH] backoffice: set default view as default (#47492) --- tests/backoffice_pages/test_custom_view.py | 182 +++++++++++++++++++++ tests/test_api.py | 3 + wcs/api.py | 4 +- wcs/backoffice/management.py | 83 +++++++++- wcs/custom_views.py | 5 +- wcs/qommon/static/js/wcs.listing.js | 5 + wcs/sql.py | 14 +- 7 files changed, 282 insertions(+), 14 deletions(-) diff --git a/tests/backoffice_pages/test_custom_view.py b/tests/backoffice_pages/test_custom_view.py index 34f97818c..fbbb61a6a 100644 --- a/tests/backoffice_pages/test_custom_view.py +++ b/tests/backoffice_pages/test_custom_view.py @@ -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('User Label') == 0 + assert resp.text.count('field 1') == 0 + assert resp.text.count('User Label') == 0 + assert resp.text.count('field 1') == 0 + assert resp.text.count('User Label') == 0 + assert resp.text.count('field 1') == 1 + assert resp.text.count('User Label') == 0 + assert resp.text.count('field 1') == 1 + assert resp.text.count('= 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 diff --git a/wcs/backoffice/management.py b/wcs/backoffice/management.py index c048421a3..8dbb2726c 100644 --- a/wcs/backoffice/management.py +++ b/wcs/backoffice/management.py @@ -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('') 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() diff --git a/wcs/custom_views.py b/wcs/custom_views.py index 26de1fbe8..7ae95e72a 100644 --- a/wcs/custom_views.py +++ b/wcs/custom_views.py @@ -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): diff --git a/wcs/qommon/static/js/wcs.listing.js b/wcs/qommon/static/js/wcs.listing.js index f99843824..28cb8f416 100644 --- a/wcs/qommon/static/js/wcs.listing.js +++ b/wcs/qommon/static/js/wcs.listing.js @@ -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()); + }); }); diff --git a/wcs/sql.py b/wcs/sql.py index aa326b179..85ccd9861 100644 --- a/wcs/sql.py +++ b/wcs/sql.py @@ -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