backoffice: make it possible to submit forms from the backoffice (#7052)
This commit is contained in:
parent
cf43ef783c
commit
d1884d84f7
|
@ -460,11 +460,11 @@ def test_form_roles():
|
|||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/forms/1/')
|
||||
resp = resp.click('change', href='roles')
|
||||
resp = resp.click('change', href='roles', index=0)
|
||||
resp = resp.forms[0].submit('cancel')
|
||||
|
||||
resp = app.get('/backoffice/forms/1/')
|
||||
resp = resp.click('change', href='roles')
|
||||
resp = resp.click('change', href='roles', index=0)
|
||||
resp.forms[0]['roles$element0'].value = role.name
|
||||
resp = resp.forms[0].submit('submit')
|
||||
assert FormDef.get(1).roles == [role.id]
|
||||
|
@ -827,7 +827,7 @@ def test_form_legacy_int_id():
|
|||
assert resp.forms[0]['workflow_id'].value
|
||||
|
||||
resp = app.get('/backoffice/forms/1/')
|
||||
resp = resp.click('change', href='roles')
|
||||
resp = resp.click('change', href='roles', index=0)
|
||||
assert resp.forms[0]['roles$element0'].value == 'Logged Users'
|
||||
assert resp.forms[0]['roles$element1'].value == 'ZAB'
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ def create_user(pub, is_admin=False):
|
|||
if pub.user_class.select(lambda x: x.name == 'admin'):
|
||||
user1 = pub.user_class.select(lambda x: x.name == 'admin')[0]
|
||||
user1.is_admin = is_admin
|
||||
user1.roles = [x.id for x in Role.select() if x.name == 'foobar']
|
||||
user1.store()
|
||||
return user1
|
||||
user1 = pub.user_class(name='admin')
|
||||
|
@ -453,3 +454,105 @@ def test_global_statisticspub(pub):
|
|||
resp.forms[0]['end'] = '2014-12-31'
|
||||
resp = resp.forms[0].submit()
|
||||
assert 'Total count: 20' in resp.body
|
||||
|
||||
def test_backoffice_submission(pub):
|
||||
user = create_user(pub)
|
||||
create_environment(pub)
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/')
|
||||
assert 'Submission' in resp.body
|
||||
|
||||
resp = resp.click('Submission', index=0)
|
||||
formdef = FormDef.select()[0]
|
||||
assert not formdef.url_name in resp.body
|
||||
|
||||
formdef.backoffice_submission_roles = user.roles[:]
|
||||
formdef.store()
|
||||
resp = app.get('/backoffice/submission/')
|
||||
assert formdef.url_name in resp.body
|
||||
|
||||
resp = resp.click(formdef.name)
|
||||
resp.form['f1'] = 'test submission'
|
||||
resp.form['f2'] = 'baz'
|
||||
resp.form['f3'] = 'C'
|
||||
resp = resp.form.submit('submit')
|
||||
assert 'Check values then click submit.' in resp.body
|
||||
|
||||
# going back to first page, to check
|
||||
resp = resp.form.submit('previous')
|
||||
assert resp.form['f1'].value == 'test submission'
|
||||
resp = resp.form.submit('submit')
|
||||
|
||||
# final submit
|
||||
resp = resp.form.submit('submit')
|
||||
|
||||
formdata_no = resp.location.split('/')[-2]
|
||||
data_class = formdef.data_class()
|
||||
assert data_class.get(formdata_no).data['1'] == 'test submission'
|
||||
assert data_class.get(formdata_no).data['2'] == 'baz'
|
||||
assert data_class.get(formdata_no).status == 'wf-new'
|
||||
assert data_class.get(formdata_no).user is None
|
||||
|
||||
resp = resp.follow() # get to the formdata page
|
||||
|
||||
formdata_count = data_class.count()
|
||||
|
||||
# test submission when agent is not receiver
|
||||
formdef.workflow_roles = {}
|
||||
formdef.store()
|
||||
resp = app.get('/backoffice/submission/')
|
||||
resp = resp.click(formdef.name)
|
||||
resp.form['f1'] = 'test submission'
|
||||
resp.form['f2'] = 'baz'
|
||||
resp.form['f3'] = 'C'
|
||||
resp = resp.form.submit('submit') # to validation screen
|
||||
resp = resp.form.submit('submit') # final submit
|
||||
assert resp.location == 'http://example.net/backoffice/submission/'
|
||||
resp = resp.follow() # should go back to submission screen
|
||||
|
||||
assert data_class.count() == formdata_count + 1
|
||||
|
||||
def test_backoffice_submission_tracking_code(pub):
|
||||
user = create_user(pub)
|
||||
create_environment(pub)
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/')
|
||||
assert 'Submission' in resp.body
|
||||
|
||||
resp = resp.click('Submission', index=0)
|
||||
formdef = FormDef.select()[0]
|
||||
assert not formdef.url_name in resp.body
|
||||
|
||||
formdef.enable_tracking_codes = True
|
||||
formdef.backoffice_submission_roles = user.roles[:]
|
||||
formdef.store()
|
||||
data_class = formdef.data_class()
|
||||
data_class.wipe()
|
||||
resp = app.get('/backoffice/submission/')
|
||||
assert formdef.url_name in resp.body
|
||||
|
||||
resp = resp.click(formdef.name)
|
||||
resp.form['f1'] = 'test submission'
|
||||
resp.form['f2'] = 'baz'
|
||||
resp.form['f3'] = 'C'
|
||||
resp = resp.form.submit('submit')
|
||||
assert 'Check values then click submit.' in resp.body
|
||||
|
||||
# stop here, don't validate, let user finish it.
|
||||
assert data_class.count() == 1
|
||||
formdata_no = data_class.select()[0].id
|
||||
tracking_code = data_class.select()[0].tracking_code
|
||||
|
||||
assert data_class.get(formdata_no).data['1'] == 'test submission'
|
||||
assert data_class.get(formdata_no).data['2'] == 'baz'
|
||||
assert data_class.get(formdata_no).status == 'draft'
|
||||
assert data_class.get(formdata_no).user is None
|
||||
|
||||
resp = get_app(pub).get('/code/%s/load' % data_class.select()[0].tracking_code)
|
||||
resp = resp.follow()
|
||||
assert resp.location.startswith('http://example.net/form-title/?mt=')
|
||||
resp = resp.follow()
|
||||
assert 'Check values then click submit.' in resp.body
|
||||
assert 'test submission' in resp.body
|
||||
|
|
|
@ -175,7 +175,8 @@ class FormDefPage(Directory):
|
|||
('workflow-status-remapping', 'workflow_status_remapping'),
|
||||
'roles', 'title', 'options', ('acl-read', 'acl_read'),
|
||||
'overwrite', 'qrcode', 'information',
|
||||
('public-url', 'public_url')]
|
||||
('public-url', 'public_url'),
|
||||
('backoffice-submission-roles', 'backoffice_submission_roles'),]
|
||||
|
||||
def __init__(self, component):
|
||||
try:
|
||||
|
@ -299,24 +300,10 @@ class FormDefPage(Directory):
|
|||
r += htmltext('</ul>')
|
||||
r += htmltext('</li>')
|
||||
|
||||
r += htmltext('<li>%s ') % _('User Roles:')
|
||||
if self.formdef.roles:
|
||||
roles = []
|
||||
for x in self.formdef.roles:
|
||||
if x == logged_users_role().id:
|
||||
roles.append(logged_users_role().name)
|
||||
else:
|
||||
try:
|
||||
roles.append(htmltext('<a href="../../roles/%s/">%s</a>') % (x, Role.get(x).name))
|
||||
except KeyError:
|
||||
# removed role ?
|
||||
roles.append(htmltext('<em>%s</em>') % _('Unknown role (%s)') % role_id)
|
||||
r += htmltext(', ').join(roles)
|
||||
else:
|
||||
r += '-'
|
||||
r += ' '
|
||||
r += htmltext('(<a href="roles" rel="popup">%s</a>)') % _('change')
|
||||
r += htmltext('</li>')
|
||||
r += self._display_roles(_('User Roles:'), 'roles', 'roles')
|
||||
r += self._display_roles(_('Backoffice Submission Roles:'),
|
||||
'backoffice_submission_roles',
|
||||
'backoffice-submission-roles')
|
||||
|
||||
r += htmltext('<li>%s ') % _('Read Access:')
|
||||
r += '%s' % {'none': _('None'),
|
||||
|
@ -368,6 +355,29 @@ class FormDefPage(Directory):
|
|||
r += htmltext('</div>')
|
||||
return r.getvalue()
|
||||
|
||||
def _display_roles(self, title, attribute, change_url):
|
||||
r = TemplateIO(html=True)
|
||||
r += htmltext('<li>%s ') % title
|
||||
if getattr(self.formdef, attribute):
|
||||
roles = []
|
||||
for x in getattr(self.formdef, attribute):
|
||||
if x == logged_users_role().id:
|
||||
roles.append(logged_users_role().name)
|
||||
else:
|
||||
try:
|
||||
roles.append(htmltext('<a href="../../roles/%s/">%s</a>') % (x, Role.get(x).name))
|
||||
except KeyError:
|
||||
# removed role ?
|
||||
roles.append(htmltext('<em>%s</em>') % _('Unknown role (%s)') % role_id)
|
||||
r += htmltext(', ').join(roles)
|
||||
else:
|
||||
r += '-'
|
||||
r += ' '
|
||||
r += htmltext('(<a href="%s" rel="popup">%s</a>)') % (
|
||||
change_url, _('change'))
|
||||
r += htmltext('</li>')
|
||||
return r.getvalue()
|
||||
|
||||
def get_sidebar(self):
|
||||
r = TemplateIO(html=True)
|
||||
r += htmltext('<ul id="sidebar-actions">')
|
||||
|
@ -483,6 +493,14 @@ class FormDefPage(Directory):
|
|||
attribute='roles',
|
||||
description=_('Select the roles that can access this form.'))
|
||||
|
||||
def backoffice_submission_roles(self):
|
||||
return self._roles_selection(
|
||||
title=_('Backoffice Submission Roles'),
|
||||
attribute='backoffice_submission_roles',
|
||||
include_logged_users_role=False,
|
||||
description=_('Select the roles that will be allowed to '
|
||||
'fill out forms of this kind in the backoffice.'))
|
||||
|
||||
def title(self):
|
||||
form = Form(enctype='multipart/form-data')
|
||||
form.add(StringWidget, 'name', title=_('Form Title'), required=True,
|
||||
|
|
|
@ -39,6 +39,7 @@ import wcs.admin.settings
|
|||
import wcs.admin.users
|
||||
import wcs.admin.workflows
|
||||
|
||||
from . import submission
|
||||
from . import management
|
||||
|
||||
|
||||
|
@ -54,9 +55,11 @@ class RootDirectory(BackofficeRootDirectory):
|
|||
users = wcs.admin.users.UsersDirectory()
|
||||
workflows = wcs.admin.workflows.WorkflowsDirectory()
|
||||
management = management.ManagementDirectory()
|
||||
submission = submission.SubmissionDirectory()
|
||||
|
||||
menu_items = [
|
||||
('management/', N_('Management')),
|
||||
('submission/', N_('Submission')),
|
||||
('forms/', N_('Forms Workshop')),
|
||||
('workflows/', N_('Workflows Workshop')),
|
||||
('users/', N_('Users')),
|
||||
|
@ -104,7 +107,7 @@ class RootDirectory(BackofficeRootDirectory):
|
|||
|
||||
# for some subdirectories, the user needs to be part of a role allowed
|
||||
# to go in the backoffice
|
||||
if subdirectory in ('management',):
|
||||
if subdirectory in ('management', 'submission'):
|
||||
return get_request().user.can_go_in_backoffice()
|
||||
|
||||
# for the other directories, an extra level is required, the user needs
|
||||
|
@ -272,6 +275,6 @@ class RootDirectory(BackofficeRootDirectory):
|
|||
'slug': slug,
|
||||
'url': backoffice_url + k})
|
||||
if slug in ('home', 'forms', 'workflows', 'users', 'roles',
|
||||
'categories', 'settings', 'management'):
|
||||
'categories', 'settings', 'management', 'submission'):
|
||||
menu_items[-1]['icon'] = k.strip('/')
|
||||
return menu_items
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
# w.c.s. - web application for online forms
|
||||
# Copyright (C) 2005-2015 Entr'ouvert
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from quixote import get_publisher, get_request, get_response, get_session, redirect
|
||||
from quixote.directory import Directory
|
||||
from quixote.html import TemplateIO, htmltext
|
||||
|
||||
from qommon.backoffice.menu import html_top
|
||||
from qommon import errors
|
||||
|
||||
from wcs.formdef import FormDef
|
||||
from wcs.categories import Category
|
||||
from wcs.forms.root import FormPage as PublicFormFillPage
|
||||
|
||||
|
||||
class FormFillPage(PublicFormFillPage):
|
||||
def html_top(self, *args, **kwargs):
|
||||
return html_top('submission', *args, **kwargs)
|
||||
|
||||
def check_role(self):
|
||||
if not self.formdef.backoffice_submission_roles:
|
||||
raise errors.AccessUnauthorizedError()
|
||||
for role in get_request().user.roles or []:
|
||||
if role in self.formdef.backoffice_submission_roles:
|
||||
break
|
||||
else:
|
||||
raise errors.AccessUnauthorizedError()
|
||||
|
||||
def get_sidebar(self, data):
|
||||
r = TemplateIO(html=True)
|
||||
|
||||
if self.formdef.enable_tracking_codes:
|
||||
draft_formdata_id = data.get('draft_formdata_id')
|
||||
r += htmltext('<h3>%s</h3>') % _('Tracking Code')
|
||||
tracking_code = None
|
||||
if draft_formdata_id:
|
||||
formdata = self.formdef.data_class().get(draft_formdata_id)
|
||||
if formdata.tracking_code:
|
||||
tracking_code = formdata.tracking_code
|
||||
if tracking_code:
|
||||
r += htmltext('<p>%s</p>') % tracking_code
|
||||
else:
|
||||
r += htmltext('<p>-</p>')
|
||||
|
||||
return r.getvalue()
|
||||
|
||||
def form_side(self, step_no, page_no=0, log_detail=None, data=None, editing=None):
|
||||
r = TemplateIO(html=True)
|
||||
get_response().filter['sidebar'] = self.get_sidebar(data)
|
||||
r += htmltext('<div id="side">')
|
||||
r += self.step(step_no, page_no, log_detail, data=data, editing=editing)
|
||||
r += htmltext('</div> <!-- #side -->')
|
||||
return r.getvalue()
|
||||
|
||||
def submitted(self, form, *args):
|
||||
filled = self.formdef.data_class()()
|
||||
filled.just_created()
|
||||
filled.data = self.formdef.get_data(form)
|
||||
filled.store()
|
||||
|
||||
self.keep_tracking_code(filled)
|
||||
get_session().remove_magictoken(get_request().form.get('magictoken'))
|
||||
|
||||
url = filled.perform_workflow()
|
||||
if not url:
|
||||
url = filled.get_url(backoffice=True)
|
||||
|
||||
if not self.formdef.is_of_concern_for_user(self.user, filled):
|
||||
# if the agent is not allowed to see the submitted formdef,
|
||||
# redirect to the submission homepage
|
||||
return redirect(get_publisher().get_backoffice_url() + '/submission/')
|
||||
|
||||
return redirect(url)
|
||||
|
||||
def save_draft(self, data, page_no):
|
||||
formdata = super(FormFillPage, self).save_draft(data, page_no)
|
||||
formdata.user_id = None
|
||||
formdata.store()
|
||||
return formdata
|
||||
|
||||
|
||||
class SubmissionDirectory(Directory):
|
||||
_q_exports = ['']
|
||||
|
||||
def _q_index(self):
|
||||
get_response().breadcrumb.append(('submission/', _('Submission')))
|
||||
html_top('submission', _('Submission'))
|
||||
user = get_request().user
|
||||
|
||||
list_forms = []
|
||||
for formdef in FormDef.select(order_by='name', ignore_errors=True):
|
||||
if formdef.is_disabled():
|
||||
continue
|
||||
if not formdef.backoffice_submission_roles:
|
||||
continue
|
||||
for role in user.roles or []:
|
||||
if role in formdef.backoffice_submission_roles:
|
||||
break
|
||||
else:
|
||||
continue
|
||||
list_forms.append(formdef)
|
||||
|
||||
r = TemplateIO(html=True)
|
||||
|
||||
cats = Category.select()
|
||||
Category.sort_by_position(cats)
|
||||
one = False
|
||||
for c in cats:
|
||||
l2 = [x for x in list_forms if str(x.category_id) == str(c.id)]
|
||||
if l2:
|
||||
r += self.form_list(l2, title=c.name)
|
||||
one = True
|
||||
|
||||
l2 = [x for x in list_forms if not x.category]
|
||||
if l2:
|
||||
if one:
|
||||
title = _('Misc')
|
||||
else:
|
||||
title = None
|
||||
r += self.form_list(l2, title=title)
|
||||
|
||||
return r.getvalue()
|
||||
|
||||
def form_list(self, formdefs, title=None):
|
||||
r = TemplateIO(html=True)
|
||||
r += htmltext('<div class="bo-block">')
|
||||
if title:
|
||||
r += htmltext('<h2>%s</h2>') % title
|
||||
|
||||
r += htmltext('<ul class="biglist">')
|
||||
for formdef in formdefs:
|
||||
r += htmltext('<li>')
|
||||
r += htmltext('<strong class="label"><a href="%s/">%s</a></strong>') % (
|
||||
formdef.url_name, formdef.name)
|
||||
r += htmltext('</li>')
|
||||
r += htmltext('</ul>')
|
||||
r += htmltext('</div>')
|
||||
return r.getvalue()
|
||||
|
||||
def _q_lookup(self, component):
|
||||
get_response().breadcrumb.append(('submission/', _('Submission')))
|
||||
return FormFillPage(component)
|
|
@ -67,6 +67,7 @@ class FormDef(StorableObject):
|
|||
workflow_options = None
|
||||
workflow_roles = None
|
||||
roles = None
|
||||
backoffice_submission_roles = None
|
||||
discussion = False
|
||||
confirmation = True
|
||||
detailed_emails = True
|
||||
|
|
|
@ -222,6 +222,9 @@ class FormPage(Directory):
|
|||
self.user = get_request().user
|
||||
get_response().breadcrumb.append( (component + '/', self.formdef.name) )
|
||||
|
||||
def html_top(self, *args, **kwargs):
|
||||
html_top(*args, **kwargs)
|
||||
|
||||
def get_substitution_variables(self):
|
||||
return self.substvars
|
||||
|
||||
|
@ -375,6 +378,11 @@ class FormPage(Directory):
|
|||
v = data[k]
|
||||
elif field.prefill:
|
||||
v = field.get_prefill_value()
|
||||
if get_request().get_path().startswith('/backoffice/') and (
|
||||
field.prefill and field.prefill.get('type') in ('user', 'geoloc')):
|
||||
# turn off prefilling from user and geolocation
|
||||
# attributes if the form is filled from the backoffice
|
||||
v = None
|
||||
if v:
|
||||
prefilled = True
|
||||
form.get_widget('f%s' % k).set_message(
|
||||
|
@ -395,7 +403,7 @@ class FormPage(Directory):
|
|||
if not one:
|
||||
req.form = {}
|
||||
|
||||
html_top(self.formdef.name)
|
||||
self.html_top(self.formdef.name)
|
||||
r += self.form_side(0, page_no, log_detail=log_detail, data=data, editing=editing)
|
||||
|
||||
form.add_hidden('step', '0')
|
||||
|
@ -505,7 +513,7 @@ class FormPage(Directory):
|
|||
if data.has_key('page_no'):
|
||||
page_no = int(data['page_no'])
|
||||
del data['page_no']
|
||||
if page_no == -1:
|
||||
if page_no == -1 or page_no >= self.page_number:
|
||||
req = get_request()
|
||||
for k, v in data.items():
|
||||
req.form['f%s' % k] = v
|
||||
|
@ -799,6 +807,9 @@ class FormPage(Directory):
|
|||
except AttributeError:
|
||||
filled.user_id = get_request().user.id
|
||||
|
||||
if get_request().get_path().startswith('/backoffice/'):
|
||||
filled.user_id = None
|
||||
|
||||
if self.formdef.only_allow_one:
|
||||
# this is already checked in _q_index but it's done a second time
|
||||
# just before a new form is to be stored.
|
||||
|
@ -819,7 +830,10 @@ class FormPage(Directory):
|
|||
get_session().mark_anonymous_formdata(filled)
|
||||
|
||||
if not url:
|
||||
url = filled.get_url()
|
||||
if get_request().get_path().startswith('/backoffice/'):
|
||||
url = filled.get_url(backoffice=True)
|
||||
else:
|
||||
url = filled.get_url()
|
||||
return redirect(url)
|
||||
|
||||
def keep_tracking_code(self, formdata):
|
||||
|
@ -869,7 +883,7 @@ class FormPage(Directory):
|
|||
return get_session().get_tempfile_content(t).get_file_pointer().read()
|
||||
|
||||
def validating(self, data):
|
||||
html_top(self.formdef.name)
|
||||
self.html_top(self.formdef.name)
|
||||
r = TemplateIO(html=True)
|
||||
r += htmltext('<div class="form-validation">')
|
||||
r += self.form_side(step_no=1, data=data)
|
||||
|
@ -892,7 +906,7 @@ class FormPage(Directory):
|
|||
return r.getvalue()
|
||||
|
||||
def error(self, msg):
|
||||
html_top(self.formdef.name)
|
||||
self.html_top(self.formdef.name)
|
||||
homepage = get_publisher().get_root_url()
|
||||
r = TemplateIO(html=True)
|
||||
r += htmltext('<div class="errornotice">%s</div>') % msg
|
||||
|
@ -903,7 +917,7 @@ class FormPage(Directory):
|
|||
if not self.formdef.is_user_allowed_read(get_request().user):
|
||||
raise errors.AccessForbiddenError()
|
||||
get_response().breadcrumb.append( ('listing', _('Listing')) )
|
||||
html_top('%s - %s' % (_('Listing'), self.formdef.name))
|
||||
self.html_top('%s - %s' % (_('Listing'), self.formdef.name))
|
||||
r = TemplateIO(html=True)
|
||||
|
||||
fields = []
|
||||
|
|
|
@ -854,6 +854,9 @@ li.zone-management a:hover { background-image: url(icons/management.large-hover.
|
|||
li.zone-home a { background-image: url(icons/home.large.png); }
|
||||
li.zone-home a:hover { background-image: url(icons/home.large-hover.png); }
|
||||
|
||||
li.zone-submission a { background-image: url(icons/submission.large.png); }
|
||||
li.zone-submission a:hover { background-image: url(icons/submission.large-hover.png); }
|
||||
|
||||
ul.apps li.zone-no-icon a {
|
||||
height: 29px;
|
||||
padding-top: 24px;
|
||||
|
|
Loading…
Reference in New Issue