backoffice: make form management sidebar items configurable (#75957) #978

Merged
fpeters merged 1 commits from wip/75957-management-sidebar-content into main 2024-03-15 07:22:33 +01:00
7 changed files with 190 additions and 43 deletions

View File

@ -241,13 +241,26 @@ def test_forms_edit_management(pub, formdef):
# Misc management
assert_option_display(resp, 'Management', 'Default')
resp = resp.click('Management', href='options/management')
assert resp.forms[0]['include_download_all_button'].checked is False
resp.forms[0]['include_download_all_button'].checked = True
assert resp.forms[0]['management_sidebar_items$elementgeneral'].checked is True
assert resp.forms[0]['management_sidebar_items$elementdownload-files'].checked is False
resp.forms[0]['management_sidebar_items$elementdownload-files'].checked = True
resp = resp.forms[0].submit()
assert resp.location == 'http://example.net/backoffice/forms/1/'
resp = resp.follow()
assert_option_display(resp, 'Management', 'Custom')
assert FormDef.get(1).include_download_all_button is True
assert 'general' in FormDef.get(1).management_sidebar_items
assert 'download-files' in FormDef.get(1).management_sidebar_items
resp = resp.click('Management', href='options/management')
resp.forms[0]['management_sidebar_items$elementgeneral'].checked = False
resp = resp.forms[0].submit().follow()
assert 'general' not in FormDef.get(1).management_sidebar_items
resp = resp.click('Management', href='options/management')
resp.forms[0]['management_sidebar_items$elementgeneral'].checked = True
resp.forms[0]['management_sidebar_items$elementdownload-files'].checked = False
resp = resp.forms[0].submit().follow()
assert FormDef.get(1).management_sidebar_items == {'__default__'}
def test_forms_edit_tracking_code(pub, formdef):

View File

@ -2142,7 +2142,7 @@ def test_backoffice_download_as_zip(pub):
app = login(get_app(pub))
resp = app.get('/backoffice/management/form-title/%s/' % number31.id)
assert 'Download all files as .zip' not in resp
formdef.include_download_all_button = True
formdef.management_sidebar_items.add('download-files')
formdef.store()
resp = app.get('/backoffice/management/form-title/%s/' % number31.id)
resp = resp.click('Download all files as .zip')
@ -2222,6 +2222,48 @@ def test_backoffice_geolocation_info(pub):
assert 'data-init-lat="48.83' in resp.text
def test_backoffice_sidebar_elements(pub):
user = create_user(pub)
FormDef.wipe()
formdef = FormDef()
formdef.name = 'form title'
formdef.geolocations = {'base': 'Geolocation'}
formdef.store()
formdata = formdef.data_class()()
formdata.geolocations = {'base': {'lat': 48.83, 'lon': 2.32}}
formdata.user_id = user.id
formdata.just_created()
formdata.store()
other_formdata = formdef.data_class()()
other_formdata.just_created()
other_formdata.just_created()
other_formdata.store()
app = login(get_app(pub))
resp = app.get(formdata.get_backoffice_url())
assert [x.text for x in resp.pyquery('#sidebar .extra-context h3')] == [
'General Information',
'Associated User',
'Geolocation',
]
assert len(resp.pyquery('[data-async-url$="/user-pending-forms"]')) == 1
formdef.management_sidebar_items = ['general', 'pending-forms']
formdef.store()
resp = app.get(formdata.get_backoffice_url())
assert [x.text for x in resp.pyquery('#sidebar .extra-context h3')] == ['General Information']
assert len(resp.pyquery('[data-async-url$="/user-pending-forms"]')) == 1
formdef.management_sidebar_items = ['geolocation']
formdef.store()
resp = app.get(formdata.get_backoffice_url())
assert [x.text for x in resp.pyquery('#sidebar .extra-context h3')] == ['Geolocation']
assert len(resp.pyquery('[data-async-url$="/user-pending-forms"]')) == 0
def test_backoffice_info_text(pub):
create_user(pub)
create_environment(pub)

View File

@ -1077,3 +1077,12 @@ def test_tracking_code_attributes(pub):
assert f2.enable_tracking_codes == formdef.enable_tracking_codes
assert f2.tracking_code_verify_fields == formdef.tracking_code_verify_fields
assert f2.confirmation == formdef.confirmation
def test_management_sidebar_items(pub):
formdef = FormDef()
formdef.name = 'Foo'
formdef.url_name = 'foo'
formdef.management_sidebar_items = {'general', 'pending-forms'}
f2 = assert_xml_import_export_works(formdef)
assert f2.management_sidebar_items == {'general', 'pending-forms'}

View File

@ -332,10 +332,12 @@ class OptionsDirectory(Directory):
def management(self):
form = Form(enctype='multipart/form-data')
form.add(
CheckboxWidget,
'include_download_all_button',
title=_('Include button to download all files'),
value=self.formdef.include_download_all_button,
CheckboxesWidget,
'management_sidebar_items',
title=_('Sidebar elements'),
options=[(x[0], x[1], x[0]) for x in self.formdef.get_management_sidebar_available_items()],
value=self.formdef.get_management_sidebar_items(),
inline=False,
)
form.add(
CheckboxWidget,
@ -494,6 +496,7 @@ class OptionsDirectory(Directory):
'submission_lateral_template',
'drafts_lifespan',
'user_support',
'management_sidebar_items',
]
for attr in attrs:
widget = form.get_widget(attr)
@ -503,6 +506,10 @@ class OptionsDirectory(Directory):
if has_error:
continue
new_value = widget.parse()
if attr == 'management_sidebar_items':
new_value = set(new_value)
if new_value == self.formdef.__class__.management_sidebar_items:
new_value = {'__default__'}
if attr == 'digest_template':
if self.formdef.default_digest_template != new_value:
self.changed = True
@ -777,7 +784,10 @@ class FormDefPage(Directory, TempfileDirectoryMixin):
'options/management',
_('Management'),
_('Custom')
if (self.formdef.skip_from_360_view or self.formdef.include_download_all_button)
if (
self.formdef.skip_from_360_view
or self.formdef.management_sidebar_items != {'__default__'}
)
else _('Default'),
),
'tracking_code': self.add_option_line(

View File

@ -3411,6 +3411,7 @@ class FormBackOfficeStatusPage(FormStatusPage):
def get_extra_context_bar(self, parent=None):
formdata = self.filled
management_sidebar_items = self.formdef.get_management_sidebar_items()
r = TemplateIO(html=True)
@ -3418,7 +3419,6 @@ class FormBackOfficeStatusPage(FormStatusPage):
r += htmltext('<p><a class="button" id="back-to-listing" href="..">%s</a></p>') % _(
'Back to Listing'
)
r += htmltext('<div class="extra-context">')
if (
formdata.backoffice_submission
and formdata.submission_agent_id == str(get_request().user.id)
@ -3427,9 +3427,13 @@ class FormBackOfficeStatusPage(FormStatusPage):
):
# keep displaying tracking code to submission agent for 30
# minutes after submission
r += htmltext('<div class="extra-context">')
r += htmltext('<h3>%s</h3>') % _('Tracking Code')
r += htmltext('<p>%s</p>') % formdata.tracking_code
r += htmltext('</div>')
if not formdata.is_draft() and 'general' in management_sidebar_items:
r += htmltext('<div class="extra-context sidebar-general-information">')
r += htmltext('<h3>%s</h3>') % _('General Information')
r += htmltext('<p>')
tm = misc.localstrftime(formdata.receipt_time)
@ -3481,43 +3485,54 @@ class FormBackOfficeStatusPage(FormStatusPage):
'date': formdata.anonymised.strftime(misc.date_format())
}
r += htmltext('</div>')
r += htmltext('</div>') # .extra-context
if formdata.formdef.include_download_all_button:
has_attached_files = False
for value in (formdata.data or {}).values():
if isinstance(value, PicklableUpload):
has_attached_files = True
if isinstance(value, dict) and isinstance(value.get('data'), list):
# block fields
for subvalue in value.get('data'):
for subvalue_elem in subvalue.values():
if isinstance(subvalue_elem, PicklableUpload):
has_attached_files = True
break
if has_attached_files:
break
if not formdata.is_draft() and 'download-files' in management_sidebar_items:
has_attached_files = False
for value in (formdata.data or {}).values():
if isinstance(value, PicklableUpload):
has_attached_files = True
if isinstance(value, dict) and isinstance(value.get('data'), list):
# block fields
for subvalue in value.get('data'):
for subvalue_elem in subvalue.values():
if isinstance(subvalue_elem, PicklableUpload):
has_attached_files = True
break
if has_attached_files:
r += htmltext('<p><a class="button" href="download-as-zip">%s</a></p>') % _(
'Download all files as .zip'
)
break
r += htmltext('</div>')
if has_attached_files:
r += htmltext('<div class="extra-context sidebar-download-files">')
r += htmltext('<p><a class="button" href="download-as-zip">%s</a></p>') % _(
'Download all files as .zip'
)
r += htmltext('</div>')
r += self.get_extra_submission_context_bar()
r += self.get_extra_submission_channel_bar()
r += self.get_extra_submission_user_id_bar(parent=parent)
r += self.get_extra_geolocation_bar()
if formdata.formdef.lateral_template:
if 'submission-context' in management_sidebar_items:
r += self.get_extra_submission_context_bar()
r += self.get_extra_submission_channel_bar()
if 'user' in management_sidebar_items:
r += self.get_extra_submission_user_id_bar(parent=parent)
if 'geolocation' in management_sidebar_items:
r += self.get_extra_geolocation_bar()
if 'custom-template' in management_sidebar_items and formdata.formdef.lateral_template:
r += htmltext('<div data-async-url="%slateral-block"></div>' % formdata.get_url(backoffice=True))
if not isinstance(formdata.formdef, CardDef) and formdata.user_id:
if (
'pending-forms' in management_sidebar_items
and not isinstance(formdata.formdef, CardDef)
and formdata.user_id
):
r += htmltext(
'<div data-async-url="%suser-pending-forms"></div>' % formdata.get_url(backoffice=True)
)
if not formdata.is_draft() and self.can_go_in_inspector():
r += htmltext('<div class="extra-context">')
r += htmltext('<div class="extra-context sidebar-data-inspector">')
r += htmltext('<p><a href="%sinspect">' % formdata.get_url(backoffice=True))
r += htmltext('%s</a></p>') % _('Data Inspector')
r += htmltext('</div>')
@ -3529,7 +3544,7 @@ class FormBackOfficeStatusPage(FormStatusPage):
r = TemplateIO(html=True)
if formdata.submission_context or formdata.submission_channel:
extra_context = formdata.submission_context or {}
r += htmltext('<div class="extra-context">')
r += htmltext('<div class="extra-context sidebar-submission-context">')
if extra_context.get('orig_formdef_id'):
object_type = extra_context.get('orig_object_type', 'formdef')
if object_type == 'formdef':
@ -3572,7 +3587,7 @@ class FormBackOfficeStatusPage(FormStatusPage):
r += htmltext('<p>%s</p>') % extra_context.get('comments')
if extra_context.get('summary_url'):
r += htmltext('<div data-content-url="%s"></div>' % (extra_context.get('summary_url')))
r += htmltext('</div>')
r += htmltext('</div>') # closes .extra-context from get_extra_submission_context_bar
return r.getvalue()
@ -3580,7 +3595,7 @@ class FormBackOfficeStatusPage(FormStatusPage):
formdata = self.filled
r = TemplateIO(html=True)
if formdata and formdata.user_id and formdata.get_user():
r += htmltext('<div class="extra-context">')
r += htmltext('<div class="extra-context sidebar--user">')
r += htmltext('<h3>%s</h3>') % _('Associated User')
users_cfg = get_cfg('users', {})
sidebar_user_template = users_cfg.get('sidebar_template')
@ -3617,7 +3632,7 @@ class FormBackOfficeStatusPage(FormStatusPage):
formdata = self.filled
r = TemplateIO(html=True)
if formdata.formdef.geolocations and formdata.geolocations:
r += htmltext('<div class="geolocations">')
r += htmltext('<div class="extra-context geolocations sidebar-geolocations">')
for geoloc_key in formdata.formdef.geolocations:
if geoloc_key not in formdata.geolocations:
continue
@ -3676,7 +3691,7 @@ class FormBackOfficeStatusPage(FormStatusPage):
self.filled.related_user_forms = formdatas
if formdatas:
r += htmltext('<div class="user-pending-forms">')
r += htmltext('<div class="extra-context user-pending-forms">')
r += htmltext('<h3>%s</h3>') % _('User Pending Forms')
categories = {}
formdata_by_category = {}

View File

@ -174,6 +174,14 @@ class FormDef(StorableObject):
expiration_date = None
has_captcha = False
skip_from_360_view = False
management_sidebar_items = {
'general',
'submission-context',
'user',
'geolocation',
'custom-template',
'pending-forms',
}
include_download_all_button = False
appearance_keywords = None
digest_templates = None
@ -221,7 +229,6 @@ class FormDef(StorableObject):
'enable_tracking_codes',
'confirmation',
'always_advertise',
'include_download_all_button',
'has_captcha',
'skip_from_360_view',
]
@ -231,6 +238,7 @@ class FormDef(StorableObject):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields = []
self.management_sidebar_items = {'__default__'}
def __eq__(self, other):
return bool(
@ -267,6 +275,12 @@ class FormDef(StorableObject):
changed = True
break
if self.include_download_all_button: # 2023-12-30
self.management_sidebar_items = self.__class__.management_sidebar_items.copy()
self.management_sidebar_items.add('download-files')
self.include_download_all_button = False
changed = True
for f in self.fields or []:
changed |= f.migrate()
@ -287,6 +301,29 @@ class FormDef(StorableObject):
sql.clean_global_views(conn, cur)
cur.close()
def get_management_sidebar_available_items(self):
return [
('general', _('General Information')),
('download-files', _('Button to download all files')),
('submission-context', _('Submission context')),
('user', _('Associated User')),
('geolocation', _('Geolocation')),
('custom-template', _('Custom template')),
('pending-forms', _('User Pending Forms')),
]
def management_sidebar_items_labels(self):
# return ordered labels
management_sidebar_items = self.get_management_sidebar_items()
for key, label in self.get_management_sidebar_available_items():
if key in management_sidebar_items:
yield label
def get_management_sidebar_items(self):
if self.management_sidebar_items == {'__default__'}:
return self.__class__.management_sidebar_items
return self.management_sidebar_items or []
@property
def data_class_name(self):
return '_wcs_%s' % self.url_name.title()
@ -1078,6 +1115,9 @@ class FormDef(StorableObject):
if self.required_authentication_contexts:
root['required_authentication_contexts'] = self.required_authentication_contexts[:]
if self.management_sidebar_items:
root['management_sidebar_items'] = sorted(self.management_sidebar_items)
if isinstance(self, CardDef):
all_carddefs = CardDef.select(ignore_errors=True)
all_carddefs = [c for c in all_carddefs if c]
@ -1245,6 +1285,9 @@ class FormDef(StorableObject):
str(x) for x in value.get('required_authentication_contexts')
]
if value.get('management_sidebar_items'):
formdef.management_sidebar_items = {str(x) for x in value.get('management_sidebar_items')}
return formdef
def export_to_xml(self, include_id=False):
@ -1394,6 +1437,11 @@ class FormDef(StorableObject):
for auth_context in self.required_authentication_contexts:
ET.SubElement(element, 'method').text = force_str(auth_context)
if self.management_sidebar_items:
element = ET.SubElement(root, 'management_sidebar_items')
for item in sorted(self.management_sidebar_items):
ET.SubElement(element, 'item').text = force_str(item)
if self.digest_templates:
digest_templates = ET.SubElement(root, 'digest_templates')
for key, value in self.digest_templates.items():
@ -1593,6 +1641,12 @@ class FormDef(StorableObject):
for child in node:
formdef.required_authentication_contexts.append(str(child.text))
if tree.find('management_sidebar_items') is not None:
node = tree.find('management_sidebar_items')
formdef.management_sidebar_items = set()
for child in node:
formdef.management_sidebar_items.add(str(child.text))
if tree.find('digest_templates') is not None:
digest_templates_node = tree.find('digest_templates')
formdef.digest_templates = {}

View File

@ -56,7 +56,11 @@
{% if formdef.roles %}
<li><span class="parameter">{% trans "Display to unlogged users" %}{% trans ":" %}</span> {{ formdef.always_advertise|yesno }}</li>
{% endif %}
<li><span class="parameter">{% trans "Include button to download all files" %}{% trans ":" %}</span> {{ formdef.include_download_all_button|yesno }}</li>
<li><span class="parameter">{% trans "Management sidebar elements" %}{% trans ":" %}</span>
<ul>
{% for label in formdef.management_sidebar_items_labels %} <li>{{ label }}</li>{% endfor %}
</ul>
</li>
<li><span class="parameter">{% trans "Skip from per user view" %}{% trans ":" %}</span> {{ formdef.skip_from_360_view|yesno }}</li>
<li><span class="parameter">{% trans "Tracking codes" %}{% trans ":" %}</span> {{ formdef.enable_tracking_codes|yesno }}</li>
{% if formdef.enable_tracking_codes %}