0003-backoffice-set-default-view-as-default-47492.patch
tests/backoffice_pages/test_custom_view.py | ||
---|---|---|
13 | 13 |
from wcs.qommon.http_request import HTTPRequest |
14 | 14 |
from wcs.carddef import CardDef |
15 | 15 |
from wcs.formdef import FormDef |
16 |
from wcs.roles import Role |
|
16 | 17 |
from wcs import fields |
17 | 18 | |
18 | 19 |
from utilities import (get_app, login, create_temporary_pub, clean_temporary_pub) |
... | ... | |
291 | 292 |
[('custom-test-view', 'owner'), ('shared-view', 'any'), ('shared-view-2', 'any')]) |
292 | 293 | |
293 | 294 | |
295 |
def test_backoffice_custom_view_is_default(pub): |
|
296 |
create_superuser(pub) |
|
297 | ||
298 |
FormDef.wipe() |
|
299 |
pub.custom_view_class.wipe() |
|
300 |
formdef = FormDef() |
|
301 |
formdef.name = 'form title' |
|
302 |
formdef.fields = [] |
|
303 |
formdef.workflow_roles = {'_receiver': 1} |
|
304 |
formdef.store() |
|
305 | ||
306 |
# private custom view |
|
307 |
agent = pub.user_class(name='agent') |
|
308 |
agent.roles = [formdef.workflow_roles['_receiver']] |
|
309 |
agent.store() |
|
310 |
account = PasswordAccount(id='agent') |
|
311 |
account.set_password('agent') |
|
312 |
account.user_id = agent.id |
|
313 |
account.store() |
|
314 |
app = login(get_app(pub), username='agent', password='agent') |
|
315 |
resp = app.get('/backoffice/management/form-title/') |
|
316 |
resp = resp.forms['listing-settings'].submit() |
|
317 |
resp.forms['save-custom-view']['title'] = 'view 1' |
|
318 |
resp.forms['save-custom-view']['is_default'] = True |
|
319 |
resp = resp.forms['save-custom-view'].submit() |
|
320 | ||
321 |
# other private custom view |
|
322 |
app = login(get_app(pub)) |
|
323 |
resp = app.get('/backoffice/management/form-title/') |
|
324 |
resp = resp.forms['listing-settings'].submit() |
|
325 |
resp.forms['save-custom-view']['title'] = 'view 2' |
|
326 |
resp.forms['save-custom-view']['visibility'] = 'owner' |
|
327 |
resp.forms['save-custom-view']['is_default'] = True |
|
328 |
resp = resp.forms['save-custom-view'].submit() |
|
329 | ||
330 |
# shared custom view |
|
331 |
resp = app.get('/backoffice/management/form-title/') |
|
332 |
resp = resp.forms['listing-settings'].submit() |
|
333 |
resp.forms['save-custom-view']['title'] = 'view 3' |
|
334 |
resp.forms['save-custom-view']['visibility'] = 'any' |
|
335 |
resp.forms['save-custom-view']['is_default'] = True |
|
336 |
resp = resp.forms['save-custom-view'].submit() |
|
337 | ||
338 |
assert pub.custom_view_class.count() == 3 |
|
339 |
assert pub.custom_view_class.get(1).is_default is True # simple user - private |
|
340 |
assert pub.custom_view_class.get(2).is_default is True # super user - private |
|
341 |
assert pub.custom_view_class.get(3).is_default is True # super user - shared |
|
342 | ||
343 |
# not possible to define more than one default private view |
|
344 |
resp = app.get('/backoffice/management/form-title/') |
|
345 |
resp = resp.forms['listing-settings'].submit() |
|
346 |
resp.forms['save-custom-view']['title'] = 'view 4' |
|
347 |
resp.forms['save-custom-view']['visibility'] = 'owner' |
|
348 |
resp.forms['save-custom-view']['is_default'] = True |
|
349 |
resp = resp.forms['save-custom-view'].submit() |
|
350 |
assert pub.custom_view_class.count() == 4 |
|
351 |
assert pub.custom_view_class.get(1).is_default is True # simple user - private |
|
352 |
assert pub.custom_view_class.get(2).is_default is False # super user - private |
|
353 |
assert pub.custom_view_class.get(3).is_default is True # super user - shared |
|
354 |
assert pub.custom_view_class.get(4).is_default is True # super user - private 2 |
|
355 | ||
356 |
# not possible to define more than one default shared view |
|
357 |
resp = app.get('/backoffice/management/form-title/') |
|
358 |
resp = resp.forms['listing-settings'].submit() |
|
359 |
resp.forms['save-custom-view']['title'] = 'view 5' |
|
360 |
resp.forms['save-custom-view']['visibility'] = 'any' |
|
361 |
resp.forms['save-custom-view']['is_default'] = True |
|
362 |
resp = resp.forms['save-custom-view'].submit() |
|
363 |
assert pub.custom_view_class.count() == 5 |
|
364 |
assert pub.custom_view_class.get(1).is_default is True # simple user - private |
|
365 |
assert pub.custom_view_class.get(2).is_default is False # super user - private |
|
366 |
assert pub.custom_view_class.get(3).is_default is False # super user - shared |
|
367 |
assert pub.custom_view_class.get(4).is_default is True # super user - private 2 |
|
368 |
assert pub.custom_view_class.get(5).is_default is True # super user - shared 2 |
|
369 | ||
370 | ||
371 |
def test_backoffice_default_custom_view(pub): |
|
372 |
create_superuser(pub) |
|
373 | ||
374 |
FormDef.wipe() |
|
375 |
pub.custom_view_class.wipe() |
|
376 |
formdef = FormDef() |
|
377 |
formdef.name = 'form title' |
|
378 |
formdef.fields = [ |
|
379 |
fields.ItemField( |
|
380 |
id='1', label='field 1', type='item', |
|
381 |
items=['foo', 'bar', 'baz'], |
|
382 |
display_locations=['validation', 'summary', 'listings']), |
|
383 |
] |
|
384 |
formdef.workflow_roles = {'_receiver': 1} |
|
385 |
formdef.store() |
|
386 | ||
387 |
formdef.data_class().wipe() |
|
388 |
for i in range(3): |
|
389 |
formdata = formdef.data_class()() |
|
390 |
formdata.data = {} |
|
391 |
if i == 0: |
|
392 |
formdata.data['1'] = 'foo' |
|
393 |
formdata.data['1_display'] = 'foo' |
|
394 |
else: |
|
395 |
formdata.data['1'] = 'baz' |
|
396 |
formdata.data['1_display'] = 'baz' |
|
397 |
if i < 2: |
|
398 |
formdata.jump_status('new') |
|
399 |
formdata.store() |
|
400 | ||
401 |
# define a shared defautl view |
|
402 |
app = login(get_app(pub)) |
|
403 |
resp = app.get('/backoffice/management/form-title/') |
|
404 |
# columns |
|
405 |
resp.forms['listing-settings']['user-label'].checked = False |
|
406 |
resp.forms['listing-settings']['1'].checked = False |
|
407 |
# filters |
|
408 |
resp.forms['listing-settings']['filter-1'].checked = True |
|
409 |
resp = resp.forms['listing-settings'].submit() |
|
410 |
resp.forms['listing-settings']['filter-1-value'] = 'baz' |
|
411 |
resp.forms['listing-settings']['filter'] = 'all' |
|
412 |
resp = resp.forms['listing-settings'].submit() |
|
413 |
# view |
|
414 |
resp.forms['save-custom-view']['title'] = 'shared custom test view' |
|
415 |
resp.forms['save-custom-view']['visibility'] = 'any' |
|
416 |
resp.forms['save-custom-view']['is_default'] = True |
|
417 |
resp = resp.forms['save-custom-view'].submit() |
|
418 |
assert resp.location.endswith('/shared-custom-test-view/') |
|
419 |
resp = resp.follow() |
|
420 |
assert resp.text.count('<span>User Label</span>') == 0 |
|
421 |
assert resp.text.count('<span>field 1</span>') == 0 |
|
422 |
assert resp.text.count('<tr') == 3 |
|
423 | ||
424 |
# check that default view is applied |
|
425 |
resp = app.get('/backoffice/management/form-title/') |
|
426 |
assert resp.text.count('<span>User Label</span>') == 0 |
|
427 |
assert resp.text.count('<span>field 1</span>') == 0 |
|
428 |
assert resp.text.count('<tr') == 3 |
|
429 | ||
430 |
# define a user default view |
|
431 |
# columns |
|
432 |
resp.forms['listing-settings']['1'].checked = True |
|
433 |
# filters |
|
434 |
resp.forms['listing-settings']['filter-1'].checked = True |
|
435 |
resp = resp.forms['listing-settings'].submit() |
|
436 |
resp.forms['listing-settings']['filter-1-value'] = 'foo' |
|
437 |
resp.forms['listing-settings']['filter'] = 'all' |
|
438 |
resp = resp.forms['listing-settings'].submit() |
|
439 |
# view |
|
440 |
resp.forms['save-custom-view']['title'] = 'private custom test view' |
|
441 |
resp.forms['save-custom-view']['visibility'] = 'owner' |
|
442 |
resp.forms['save-custom-view']['is_default'] = True |
|
443 | ||
444 |
resp = resp.forms['save-custom-view'].submit() |
|
445 |
assert resp.location.endswith('/user-private-custom-test-view/') |
|
446 |
resp = resp.follow() |
|
447 |
assert resp.text.count('<span>User Label</span>') == 0 |
|
448 |
assert resp.text.count('<span>field 1</span>') == 1 |
|
449 |
assert resp.text.count('<tr') == 2 |
|
450 | ||
451 |
# check that private default view is applied, and not shared default view |
|
452 |
resp = app.get('/backoffice/management/form-title/') |
|
453 |
assert resp.text.count('<span>User Label</span>') == 0 |
|
454 |
assert resp.text.count('<span>field 1</span>') == 1 |
|
455 |
assert resp.text.count('<tr') == 2 |
|
456 | ||
457 | ||
294 | 458 |
def test_backoffice_missing_custom_view(pub): |
295 | 459 |
create_superuser(pub) |
296 | 460 | |
... | ... | |
348 | 512 |
resp = resp.follow() |
349 | 513 | |
350 | 514 | |
515 |
def test_carddata_custom_view_is_default(pub): |
|
516 |
user = create_superuser(pub) |
|
517 |
Role.wipe() |
|
518 |
role = Role(name='foobar') |
|
519 |
role.store() |
|
520 |
user.roles = [role.id] |
|
521 |
user.store() |
|
522 | ||
523 |
CardDef.wipe() |
|
524 |
pub.custom_view_class.wipe() |
|
525 |
carddef = CardDef() |
|
526 |
carddef.name = 'foo' |
|
527 |
carddef.fields = [ |
|
528 |
fields.StringField(id='1', label='Test', type='string', varname='foo'), |
|
529 |
] |
|
530 |
carddef.backoffice_submission_roles = user.roles |
|
531 |
carddef.digest_template = '{{ form_var_foo }}' |
|
532 |
carddef.store() |
|
533 |
carddef.data_class().wipe() |
|
534 | ||
535 |
# datasource custom view |
|
536 |
app = login(get_app(pub)) |
|
537 |
resp = app.get('/backoffice/data/foo/') |
|
538 |
resp = resp.forms['listing-settings'].submit() |
|
539 |
resp.forms['save-custom-view']['title'] = 'view 3' |
|
540 |
resp.forms['save-custom-view']['visibility'] = 'datasource' |
|
541 |
resp.forms['save-custom-view']['is_default'] = True |
|
542 |
resp = resp.forms['save-custom-view'].submit() |
|
543 |
assert pub.custom_view_class.get(1).is_default is False # not for datasource |
|
544 | ||
545 | ||
351 | 546 |
def test_backoffice_custom_view_keep_filters(pub): |
352 | 547 |
create_superuser(pub) |
353 | 548 |
datasource = { |
wcs/api.py | ||
---|---|---|
216 | 216 |
else: |
217 | 217 |
# custom view |
218 | 218 |
for view in self.get_custom_views([Equal('visibility', 'any'), Equal('slug', path[1])]): |
219 |
self.view = view |
|
219 |
self._view = view
|
|
220 | 220 |
path = [mode] |
221 | 221 | |
222 | 222 |
if len(path) >= 2 and path[1] == 'ics': |
223 | 223 |
for view in self.get_custom_views([Equal('visibility', 'any'), Equal('slug', path[0])]): |
224 |
self.view = view |
|
224 |
self._view = view
|
|
225 | 225 |
path = path[1:] |
226 | 226 | |
227 | 227 |
self.is_webhook = False |
wcs/backoffice/management.py | ||
---|---|---|
1027 | 1027 |
_q_exports = ['', 'csv', 'stats', 'xls', 'ods', 'json', 'export', 'map', |
1028 | 1028 |
'geojson', ('filter-options', 'filter_options'), |
1029 | 1029 |
('save-view', 'save_view'), ('delete-view', 'delete_view'),] |
1030 |
view = None |
|
1030 |
_view = None |
|
1031 |
default_view = None |
|
1032 |
use_default_view = False |
|
1031 | 1033 |
admin_permission = 'forms' |
1032 | 1034 |
formdef_class = FormDef |
1033 | 1035 |
search_label = N_('Search in form content') |
... | ... | |
1043 | 1045 |
get_response().breadcrumb.append((component + '/', self.formdef.name)) |
1044 | 1046 |
else: |
1045 | 1047 |
self.formdef = formdef |
1046 |
self.view = view |
|
1048 |
self._view = view
|
|
1047 | 1049 |
if update_breadcrumbs: |
1048 | 1050 |
get_response().breadcrumb.append((view.get_url_slug() + '/', view.title)) |
1051 |
self.set_default_view() |
|
1052 | ||
1053 |
def set_default_view(self): |
|
1054 |
if not get_request(): |
|
1055 |
return |
|
1056 |
custom_views = list(self.get_custom_views()) |
|
1057 | ||
1058 |
# search for first default user custom view |
|
1059 |
for view in custom_views: |
|
1060 |
if view.visibility != 'owner': |
|
1061 |
continue |
|
1062 |
if not view.is_default: |
|
1063 |
continue |
|
1064 |
self.default_view = view |
|
1065 |
return |
|
1066 | ||
1067 |
# default user custom view not found, search in 'any' custom views |
|
1068 |
for view in custom_views: |
|
1069 |
if view.visibility != 'any': |
|
1070 |
continue |
|
1071 |
if not view.is_default: |
|
1072 |
continue |
|
1073 |
self.default_view = view |
|
1074 |
return |
|
1075 | ||
1076 |
@property |
|
1077 |
def view(self): |
|
1078 |
view = self._view |
|
1079 |
if self.use_default_view: |
|
1080 |
view = view or self.default_view |
|
1081 |
return view |
|
1049 | 1082 | |
1050 | 1083 |
def check_access(self, api_name=None): |
1051 | 1084 |
session = get_session() |
... | ... | |
1093 | 1126 |
r += htmltext('<ul id="sidebar-custom-views">') |
1094 | 1127 |
view_type = 'map' if self.view_type == 'map' else '' |
1095 | 1128 |
for view in sorted(views, key=lambda x: getattr(x, 'title')): |
1096 |
if self.view: |
|
1097 |
active = bool(self.view.get_url_slug() == view.get_url_slug()) |
|
1129 |
if self._view:
|
|
1130 |
active = bool(self._view.get_url_slug() == view.get_url_slug())
|
|
1098 | 1131 |
r += htmltext('<li class="active">' if active else '<li>') |
1099 | 1132 |
r += htmltext('<a href="../%s/%s">%s</a>') % (view.get_url_slug(), view_type, view.title) |
1100 | 1133 |
else: |
1101 | 1134 |
r += htmltext('<li><a href="%s/%s">%s</a>') % (view.get_url_slug(), view_type, view.title) |
1102 | 1135 |
if view.visibility == 'datasource': |
1103 | 1136 |
r += htmltext(' <span class="as-data-source">(%s)</span>') % _('for data sources') |
1137 |
elif self.default_view and view.id == self.default_view.id: |
|
1138 |
r += htmltext(' <span class="default-custom-view">(%s)</span>') % _('default') |
|
1104 | 1139 |
r += htmltext('</li>') |
1105 | 1140 |
r += htmltext('</ul>') |
1106 | 1141 |
return r.getvalue() |
... | ... | |
1479 | 1514 |
if isinstance(self.formdef, CardDef) and self.formdef.digest_template: |
1480 | 1515 |
options.append(('datasource', _('as data source'), 'datasource')) |
1481 | 1516 | |
1482 |
form.add(RadiobuttonsWidget, 'visibility', title=_('Visibility'), |
|
1483 |
value=self.view.visibility if self.view else 'owner', |
|
1484 |
options=options) |
|
1517 |
form.add( |
|
1518 |
RadiobuttonsWidget, |
|
1519 |
'visibility', |
|
1520 |
title=_('Visibility'), |
|
1521 |
value=self.view.visibility if self.view else 'owner', |
|
1522 |
options=options, |
|
1523 |
attrs={'data-dynamic-display-parent': 'true'}) |
|
1524 |
form.add( |
|
1525 |
CheckboxWidget, |
|
1526 |
'is_default', |
|
1527 |
title=_('Set as default view'), |
|
1528 |
value=self.view.is_default if self.view else False, |
|
1529 |
attrs={ |
|
1530 |
'data-dynamic-display-child-of': 'visibility', |
|
1531 |
'data-dynamic-display-value-in': 'owner|any', |
|
1532 |
}) |
|
1533 |
else: |
|
1534 |
form.add( |
|
1535 |
CheckboxWidget, |
|
1536 |
'is_default', |
|
1537 |
title=_('Set as default view'), |
|
1538 |
value=self.view.is_default if self.view else False) |
|
1485 | 1539 |
if self.view and (self.view.user_id == get_request().user.id or |
1486 | 1540 |
get_publisher().get_backoffice_root().is_accessible(self.admin_permission)): |
1487 | 1541 |
form.add(CheckboxWidget, 'update', title=_('Update existing view settings'), value=True) |
... | ... | |
1505 | 1559 |
if not custom_view.columns['list']: |
1506 | 1560 |
get_session().message = ('error', _('Views must have at least one column.')) |
1507 | 1561 |
return redirect('.') |
1562 |
if form.get_widget('is_default'): |
|
1563 |
custom_view.is_default = form.get_widget('is_default').parse() |
|
1508 | 1564 |
if form.get_widget('visibility'): |
1509 | 1565 |
custom_view.visibility = form.get_widget('visibility').parse() |
1566 |
if custom_view.visibility == 'datasource': |
|
1567 |
custom_view.is_default = False |
|
1510 | 1568 |
custom_view.store() |
1569 | ||
1570 |
if custom_view.is_default and custom_view.visibility != 'datasource': |
|
1571 |
# need to clean other views to have only one default per owner/any visibility |
|
1572 |
for view in self.get_custom_views(): |
|
1573 |
if view.id == custom_view.id: |
|
1574 |
continue |
|
1575 |
if custom_view.visibility == view.visibility and view.is_default: |
|
1576 |
view.is_default = False |
|
1577 |
view.store() |
|
1578 | ||
1511 | 1579 |
if self.view: |
1512 | 1580 |
return redirect('../' + custom_view.get_url_slug() + '/') |
1513 | 1581 |
else: |
... | ... | |
1788 | 1856 |
if 'job' in get_request().form: |
1789 | 1857 |
return self.job_multi() |
1790 | 1858 | |
1859 |
self.use_default_view = True |
|
1791 | 1860 |
fields = self.get_fields_from_query() |
1792 | 1861 |
selected_filter = self.get_filter_from_query() |
1793 | 1862 |
criterias = self.get_criterias_from_query() |
wcs/custom_views.py | ||
---|---|---|
32 | 32 |
visibility = 'owner' |
33 | 33 |
formdef_type = None |
34 | 34 |
formdef_id = None |
35 |
is_default = False |
|
35 | 36 |
columns = None |
36 | 37 |
filters = None |
37 | 38 |
order_by = None |
... | ... | |
57 | 58 |
self.formdef_type = value.xml_root_node |
58 | 59 | |
59 | 60 |
def match(self, user, formdef): |
60 |
if self.visibility == 'owner' and self.user_id != str(user.id): |
|
61 |
return False |
|
62 | 61 |
if self.formdef_type != formdef.xml_root_node: |
63 | 62 |
return False |
64 | 63 |
if self.formdef_id != str(formdef.id): |
65 | 64 |
return False |
65 |
if self.visibility == 'owner' and self.user_id != str(user.id): |
|
66 |
return False |
|
66 | 67 |
return True |
67 | 68 | |
68 | 69 |
def set_from_qs(self, qs): |
wcs/qommon/static/js/wcs.listing.js | ||
---|---|---|
334 | 334 |
} |
335 | 335 |
] |
336 | 336 |
}); |
337 |
$(document).trigger('wcs:dialog-loaded', $(dialog)); |
|
337 | 338 |
return false; |
338 | 339 |
}); |
339 | 340 | |
... | ... | |
376 | 377 |
prepare_page_links(); |
377 | 378 |
prepare_row_links(); |
378 | 379 |
prepare_column_headers(); |
380 | ||
381 |
$('a[data-base-href]').each(function(idx, elem) { |
|
382 |
$(elem).attr('href', $(elem).data('base-href') + '?' + $('form#listing-settings').serialize()); |
|
383 |
}); |
|
379 | 384 |
}); |
wcs/sql.py | ||
---|---|---|
803 | 803 |
visibility varchar, |
804 | 804 |
formdef_type varchar, |
805 | 805 |
formdef_id varchar, |
806 |
is_default boolean, |
|
806 | 807 |
order_by varchar, |
807 | 808 |
columns jsonb, |
808 | 809 |
filters jsonb |
... | ... | |
814 | 815 | |
815 | 816 |
needed_fields = set([x[0] for x in CustomView._table_static_fields]) |
816 | 817 | |
818 |
# migrations |
|
819 |
if 'is_default' not in existing_fields: |
|
820 |
cur.execute('''ALTER TABLE %s ADD COLUMN is_default boolean DEFAULT FALSE''' % table_name) |
|
821 | ||
817 | 822 |
# delete obsolete fields |
818 | 823 |
for field in (existing_fields - needed_fields): |
819 | 824 |
cur.execute('''ALTER TABLE %s DROP COLUMN %s''' % (table_name, field)) |
... | ... | |
2298 | 2303 |
('visibility', 'varchar'), |
2299 | 2304 |
('formdef_type', 'varchar'), |
2300 | 2305 |
('formdef_id', 'varchar'), |
2306 |
('is_default', 'boolean'), |
|
2301 | 2307 |
('order_by', 'varchar'), |
2302 | 2308 |
('columns', 'jsonb'), |
2303 | 2309 |
('filters', 'jsonb'), |
... | ... | |
2315 | 2321 |
'visibility': self.visibility, |
2316 | 2322 |
'formdef_type': self.formdef_type, |
2317 | 2323 |
'formdef_id': self.formdef_id, |
2324 |
'is_default': self.is_default, |
|
2318 | 2325 |
'order_by': self.order_by, |
2319 | 2326 |
'columns': self.columns, |
2320 | 2327 |
'filters': self.filters, |
... | ... | |
2358 | 2365 |
for field, value in zip(cls._table_static_fields, tuple(row)): |
2359 | 2366 |
if field[1] == 'varchar': |
2360 | 2367 |
setattr(o, field[0], str_encode(value)) |
2361 |
elif field[1] == 'jsonb':
|
|
2368 |
elif field[1] in ('jsonb', 'boolean'):
|
|
2362 | 2369 |
setattr(o, field[0], value) |
2363 | 2370 |
return o |
2364 | 2371 | |
... | ... | |
2817 | 2824 |
# 25: create session_table |
2818 | 2825 |
# 32: add last_update_time column to session table |
2819 | 2826 |
do_session_table() |
2820 |
if sql_level < 37: |
|
2821 |
# 37: create custom_views tabl |
|
2827 |
if sql_level < 43: |
|
2828 |
# 37: create custom_views table |
|
2829 |
# 43: add is_default column to custom_views table |
|
2822 | 2830 |
do_custom_views_table() |
2823 | 2831 |
if sql_level < 30: |
2824 | 2832 |
# 30: actually remove evo.who on anonymised formdatas |
2825 |
- |