wcs/wcs/backoffice/submission.py

409 lines
17 KiB
Python

# 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/>.
import time
from django.utils.safestring import mark_safe
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 import _
from ..qommon.backoffice.menu import html_top
from ..qommon.form import *
from ..qommon import errors, misc
from wcs.formdata import FormData
from wcs.formdef import FormDef
from wcs.categories import Category
from wcs.forms.root import FormPage as PublicFormFillPage
class RemoveDraftDirectory(Directory):
def __init__(self, parent_directory):
self.parent_directory = parent_directory
self.formdef = parent_directory.formdef
def _q_lookup(self, component):
try:
formdata = self.formdef.data_class().get(component)
except KeyError:
raise errors.TraversalError()
if not formdata.is_draft():
raise errors.AccessForbiddenError()
if not formdata.backoffice_submission:
raise errors.AccessForbiddenError()
self.parent_directory.check_role()
if self.parent_directory.edit_mode:
raise errors.AccessForbiddenError()
self.parent_directory.html_top(title=_('Remove'))
form = Form(enctype='multipart/form-data')
form.widgets.append(HtmlWidget('<p>%s</p>' % _(
'You are about to irrevocably delete this form.')))
form.add_submit('delete', _('Delete'))
form.add_submit('cancel', _('Cancel'))
if form.get_widget('cancel').parse():
return redirect('../..')
if not form.is_submitted() or form.has_errors():
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % (_('Deleting Form'))
r += form.render()
return r.getvalue()
if formdata.tracking_code:
get_publisher().tracking_code_class.remove_object(formdata.tracking_code)
formdata.remove_self()
return redirect('../..')
class FormFillPage(PublicFormFillPage):
_q_exports = ['', 'tempfile', 'autosave', 'code',
('remove', 'remove_draft'), 'live']
filling_templates = ['wcs/formdata_filling.html']
validation_templates = ['wcs/formdata_validation.html']
steps_templates = ['wcs/formdata_steps.html']
def __init__(self, *args, **kwargs):
super(FormFillPage, self).__init__(*args, **kwargs)
self.selected_submission_channel = None
self.remove_draft = RemoveDraftDirectory(self)
def _q_index(self, *args, **kwargs):
# if NameID, return URL or submission channel are in query string,
# create a new draft with these parameters, and redirect to it
submission_channel = get_request().form.get('channel')
name_id = get_request().form.get('NameID')
return_url = get_request().form.get('ReturnURL')
if name_id or submission_channel or return_url:
formdata = self.formdef.data_class()()
formdata.data = {}
formdata.backoffice_submission = True
formdata.submission_channel = submission_channel or ''
formdata.submission_context = {'agent_id': get_request().user.id}
formdata.status = 'draft'
formdata.receipt_time = time.localtime()
if name_id:
users = list(get_publisher().user_class.get_users_with_name_identifier(name_id))
if users:
formdata.user_id = users[0].id
if return_url:
formdata.submission_context['return_url'] = return_url
formdata.store()
self.set_tracking_code(formdata)
return redirect('%s' % formdata.id)
self.selected_submission_channel = get_request().form.get('submission_channel') or ''
return super(FormFillPage, self)._q_index(*args, **kwargs)
def html_top(self, *args, **kwargs):
return html_top('submission', *args, **kwargs)
def check_authentication_context(self):
pass
def check_role(self):
if self.edit_mode:
return True
if not self.formdef.backoffice_submission_roles:
raise errors.AccessUnauthorizedError()
for role in get_request().user.get_roles():
if role in self.formdef.backoffice_submission_roles:
break
else:
raise errors.AccessUnauthorizedError()
def get_sidebar(self, data):
r = TemplateIO(html=True)
formdata = None
if self.edit_mode:
formdata = self.edited_data
else:
draft_formdata_id = data.get('draft_formdata_id')
if draft_formdata_id:
try:
formdata = self.formdef.data_class().get(draft_formdata_id)
except KeyError: # it may not exist
pass
if self.formdef.enable_tracking_codes and not self.edit_mode:
r += htmltext('<h3>%s</h3>') % _('Tracking Code')
if formdata and formdata.tracking_code:
r += htmltext('<p>%s</p>') % formdata.tracking_code
else:
r += htmltext('<p>-</p>')
welco_url = get_publisher().get_site_option('welco_url', 'options')
if formdata and formdata.submission_context:
from .management import FormBackOfficeStatusPage
r += FormBackOfficeStatusPage(self.formdef, formdata).get_extra_context_bar()
elif not welco_url and not self.edit_mode:
r += htmltext('<div class="submit-channel-selection" style="display: none;">')
r += htmltext('<h3>%s</h3>') % _('Channel')
r += htmltext('<select>')
for channel_key, channel_label in [('', '-')] + list(FormData.get_submission_channels().items()):
selected = ''
if self.selected_submission_channel == channel_key:
selected = 'selected="selected"'
r += htmltext('<option value="%s" %s>' % (channel_key, selected))
r += htmltext('%s</option>') % channel_label
r += htmltext('</select>')
r += htmltext('</div>')
return r.getvalue()
def create_view_form(self, *args, **kwargs):
form = super(FormFillPage, self).create_view_form(*args, **kwargs)
welco_url = get_publisher().get_site_option('welco_url', 'options')
if not welco_url:
form.add_hidden('submission_channel', self.selected_submission_channel)
return form
def create_form(self, *args, **kwargs):
form = super(FormFillPage, self).create_form(*args, **kwargs)
form.attrs['data-live-url'] = self.formdef.get_backoffice_submission_url() + 'live'
welco_url = get_publisher().get_site_option('welco_url', 'options')
if not welco_url:
form.add_hidden('submission_channel', self.selected_submission_channel)
return form
def form_side(self, step_no, page, data=None, magictoken=None):
r = TemplateIO(html=True)
get_response().filter['sidebar'] = self.get_sidebar(data)
r += htmltext('<div id="appbar">')
r += htmltext('<h2>%s</h2>') % self.formdef.name
if not self.edit_mode:
draft_formdata_id = data.get('draft_formdata_id')
if draft_formdata_id:
r += htmltext('<a rel="popup" href="remove/%s">%s</a>') % (draft_formdata_id, _('Delete this form'))
r += htmltext('</div>')
r += htmltext('<div id="side">')
r += self.step(step_no, page)
r += htmltext('</div> <!-- #side -->')
return mark_safe(str(r.getvalue()))
def submitted(self, form, *args):
filled = self.get_current_draft() or self.formdef.data_class()()
if filled.id and filled.status != 'draft':
get_session().message = ('error', _('This form has already been submitted.'))
return redirect(get_publisher().get_backoffice_url() + '/submission/')
filled.just_created()
filled.data = self.formdef.get_data(form)
filled.backoffice_submission = True
if not filled.submission_context:
filled.submission_context = {}
if self.selected_submission_channel:
filled.submission_channel = self.selected_submission_channel
filled.submission_context['agent_id'] = get_request().user.id
filled.store()
self.set_tracking_code(filled)
get_session().remove_magictoken(get_request().form.get('magictoken'))
self.clean_submission_context()
url = filled.perform_workflow()
if url:
pass # always redirect to an URL the workflow returned
elif 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 defined return URL or to the submission
# homepage
if filled.submission_context.get('return_url'):
url = filled.submission_context['return_url']
else:
url = get_publisher().get_backoffice_url() + '/submission/'
else:
url = filled.get_url(backoffice=True)
return redirect(url)
def cancelled(self):
url = get_publisher().get_backoffice_url() + '/submission/'
formdata = self.get_current_draft() or self.formdef.data_class()()
if formdata.submission_context and formdata.submission_context.get('return_url'):
url = formdata.submission_context.get('return_url')
if formdata.id:
formdata.remove_self()
return redirect(url)
def save_draft(self, data, page_no):
formdata = super(FormFillPage, self).save_draft(data, page_no)
formdata.backoffice_submission = True
if not formdata.submission_context:
formdata.submission_context = {}
formdata.submission_context['agent_id'] = get_request().user.id
formdata.store()
return formdata
class SubmissionDirectory(Directory):
_q_exports = ['', 'count']
def is_accessible(self, user):
if not user.can_go_in_backoffice():
return False
# check user has at least one role set for backoffice submission
for role_id in (user.roles or []):
ids = FormDef.get_ids_with_indexed_value('backoffice_submission_roles', role_id)
if ids:
return True
return False
def get_submittable_formdefs(self):
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.get_roles():
if role in formdef.backoffice_submission_roles:
break
else:
continue
list_forms.append(formdef)
return list_forms
def _q_index(self):
get_response().breadcrumb.append(('submission/', _('Submission')))
html_top('submission', _('Submission'))
list_forms = self.get_submittable_formdefs()
cats = Category.select()
Category.sort_by_position(cats)
for cat in cats:
cat.formdefs = [x for x in list_forms if str(x.category_id) == str(cat.id)]
misc_cat = Category(name=_('Misc'))
misc_cat.formdefs = [x for x in list_forms if not x.category]
cats.append(misc_cat)
welco_url = get_publisher().get_site_option('welco_url', 'options')
r = TemplateIO(html=True)
r += get_session().display_message()
modes = ['empty', 'create', 'existing']
if welco_url:
modes.remove('create')
empty = True
for mode in modes:
list_content = TemplateIO()
for cat in cats:
if not cat.formdefs:
continue
list_content += self.form_list(cat.formdefs, title=cat.name, mode=mode)
if not list_content.getvalue().strip():
continue
empty = False
r += htmltext('<h2>%s</h2>') % {
'create': _('New submission'),
'existing': _('Running submission'),
'empty': _('Submission to complete'),
}.get(mode)
r += htmltext('<ul class="biglist">')
r += htmltext(list_content.getvalue())
r += htmltext('</ul>')
if empty and welco_url:
return redirect(welco_url)
return r.getvalue()
def form_list(self, formdefs, title=None, mode='create'):
r = TemplateIO(html=True)
if mode != 'create':
skip = True
for formdef in formdefs:
if not hasattr(formdef, '_formdatas'):
data_class = formdef.data_class()
formdata_ids = data_class.get_ids_with_indexed_value('status', 'draft')
formdef._formdatas = [x for x in data_class.get_ids(formdata_ids)
if x.backoffice_submission is True]
skip &= not(bool(formdef._formdatas))
if skip:
return
first = True
for formdef in formdefs:
if mode != 'create':
formdatas = formdef._formdatas[:]
if mode == 'empty':
formdatas = [x for x in formdatas if x.has_empty_data()]
elif mode == 'existing':
formdatas = [x for x in formdatas if not x.has_empty_data()]
if not formdatas:
continue
if first and title:
r += htmltext('<li><h3>%s</h3></li>') % title
first = False
r += htmltext('<li>')
if mode == 'create':
r += htmltext('<strong class="label"><a href="%s/">%s</a></strong>') % (
formdef.url_name, formdef.name)
else:
r += htmltext('<strong class="label"><a class="fake">%s</a></strong>') % formdef.name
r += htmltext('</li>')
if mode == 'create':
continue
for formdata in formdatas:
r += htmltext('<li class="smallitem">')
label = ''
if formdata.submission_channel:
label = '%s ' % formdata.get_submission_channel_label()
label += _('#%(id)s, %(time)s') % {'id': formdata.id,
'time': misc.localstrftime(formdata.receipt_time)}
if formdata.submission_context and 'agent_id' in formdata.submission_context:
agent_user = get_publisher().user_class.get(
formdata.submission_context['agent_id'], ignore_errors=True)
if agent_user:
label += ' (%s)' % agent_user.display_name
r += htmltext('<a href="%s/%s">%s</a>') % (
formdef.url_name, formdata.id, label)
r += htmltext('</li>')
return r.getvalue()
def count(self):
formdefs = self.get_submittable_formdefs()
count = 0
mode = get_request().form.get('mode')
for formdef in formdefs:
if not hasattr(formdef, '_formdatas'):
data_class = formdef.data_class()
formdata_ids = data_class.get_ids_with_indexed_value('status', 'draft')
formdatas = [x for x in data_class.get_ids(formdata_ids)
if x.backoffice_submission is True]
if mode == 'empty':
formdatas = [x for x in formdatas if x.has_empty_data()]
elif mode == 'existing':
formdatas = [x for x in formdatas if not x.has_empty_data()]
count += len(formdatas)
return misc.json_response({'count': count})
def _q_lookup(self, component):
get_response().breadcrumb.append(('submission/', _('Submission')))
return FormFillPage(component)