483 lines
17 KiB
Plaintext
483 lines
17 KiB
Plaintext
# w.c.s. - web application for online forms
|
|
# Copyright (C) 2005-2010 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 base64
|
|
import mimetypes
|
|
import json
|
|
|
|
from quixote import get_publisher, get_request, get_response, get_session, redirect
|
|
from quixote.directory import Directory, AccessControlled
|
|
|
|
from wcs import formdata
|
|
|
|
from wcs.fields import WidgetField
|
|
|
|
from qommon import template
|
|
from qommon import get_logger
|
|
from qommon.form import *
|
|
from wcs.anonylink import AnonymityLink
|
|
from qommon.strftime import strftime
|
|
|
|
from qommon.admin.texts import TextsDirectory
|
|
|
|
from qommon import errors
|
|
|
|
class FormStatusPage(Directory):
|
|
_q_exports = ['', 'download']
|
|
_q_extra_exports = []
|
|
|
|
def html_top [html] (self, title = None):
|
|
template.html_top(title = title, default_org = _('Forms'))
|
|
|
|
def __init__(self, formdef, filled, register_workflow_subdirs=True):
|
|
get_publisher().substitutions.feed(filled)
|
|
self.formdef = formdef
|
|
self.filled = filled
|
|
for q in self._q_extra_exports:
|
|
if not q in self._q_exports:
|
|
self._q_exports.append(q)
|
|
|
|
if self.formdef.workflow and register_workflow_subdirs:
|
|
for name, directory in self.filled.get_workflow_subdirectories():
|
|
self._q_exports.append(name)
|
|
setattr(self, name, directory)
|
|
|
|
def check_auth(self):
|
|
session = get_session()
|
|
mine = False
|
|
user = get_request().user
|
|
if user:
|
|
if user.anonymous:
|
|
anonylink = AnonymityLink.select(
|
|
lambda x: x.name_identifier == session.name_identifier and
|
|
x.formdata_type == 'form' and
|
|
x.formdata_def_id == self.formdef.id)
|
|
if len(anonylink) == 1:
|
|
mine = True
|
|
elif self.filled.is_submitter(user):
|
|
mine = True
|
|
|
|
if not self.filled.formdef.is_user_allowed_read(user, self.filled):
|
|
if not user:
|
|
raise errors.AccessUnauthorizedError()
|
|
raise errors.AccessForbiddenError()
|
|
return mine
|
|
|
|
def _q_index [html] (self):
|
|
mine = self.check_auth()
|
|
if get_request().get_header(str('Accept')) == 'application/json':
|
|
return self.export_to_json()
|
|
get_logger().info('form %s - id: %s - view' % (self.formdef.name, self.filled.id))
|
|
|
|
if self.formdef.only_allow_one:
|
|
self.html_top(self.formdef.name)
|
|
else:
|
|
self.html_top('%s - %s' % (self.formdef.name, self.filled.id))
|
|
if self.filled.receipt_time is not None:
|
|
tm = misc.localstrftime(self.filled.receipt_time)
|
|
else:
|
|
tm = '???'
|
|
'<div id="receipt-intro">'
|
|
if self.formdef.only_allow_one:
|
|
TextsDirectory.get_html_text('form-recorded-allow-one', vars={'date': tm})
|
|
else:
|
|
TextsDirectory.get_html_text('form-recorded',
|
|
vars={'date': tm, 'number': self.filled.id})
|
|
if mine:
|
|
handling_role = self.filled.get_handling_role()
|
|
if handling_role and handling_role.details:
|
|
endpoint_status = self.formdef.workflow.get_endpoint_status()
|
|
'<p>'
|
|
if self.filled.status in endpoint_status:
|
|
_('Your case is handled by:')
|
|
else:
|
|
_('Your case has been handled by:')
|
|
'</p>'
|
|
'<p id="receiver">'
|
|
htmltext(handling_role.details.replace(str('\n'), str('<br />')))
|
|
'</p>'
|
|
'</div>'
|
|
|
|
self.receipt()
|
|
if self.formdef.workflow:
|
|
htmltext(self.filled.display_workflow_message())
|
|
|
|
self.history()
|
|
|
|
form = None
|
|
|
|
session = get_session()
|
|
user = get_request().user
|
|
form = self.filled.get_workflow_form(user)
|
|
if form:
|
|
form.add_captcha()
|
|
|
|
if form and form.is_submitted():
|
|
if not form.has_errors():
|
|
url = self.submit(form, comment_only = True)
|
|
if not form.has_errors():
|
|
if url is None:
|
|
url = get_request().get_url()
|
|
response = get_response()
|
|
response.set_status(303)
|
|
response.headers[str('location')] = url
|
|
response.content_type = 'text/plain'
|
|
return "Your browser should redirect you"
|
|
|
|
if form:
|
|
form.render()
|
|
|
|
self.form_status_buttons()
|
|
|
|
def export_to_json(self):
|
|
data = {}
|
|
if self.filled.receipt_time is not None:
|
|
data['receipt_time'] = strftime('%Y-%m-%dT%H:%M:%S', self.filled.receipt_time)
|
|
|
|
try:
|
|
user = get_publisher().user_class.get(self.filled.user_id)
|
|
except KeyError:
|
|
user = None
|
|
|
|
# this is custom code so it is possible to mark forms as anonyms, this
|
|
# is done through the VoteAnonymity field, this is very specific but
|
|
# isn't generalised yet into an useful extension mechanism, as it's not
|
|
# clear at the moment what could be useful.
|
|
for f in self.formdef.fields:
|
|
if f.key == 'vote-anonymity':
|
|
user = None
|
|
break
|
|
|
|
if user:
|
|
data['user'] = {'id': user.id, 'name': user.display_name}
|
|
|
|
if self.formdef.signing:
|
|
if self.filled.signature:
|
|
data['signature'] = {'status': 'valid',
|
|
'issuer': self.filled.signature['issuer'],
|
|
'subject': self.filled.signature['subject']}
|
|
else:
|
|
data['signature'] = {'status': 'missing'}
|
|
|
|
data['fields'] = {}
|
|
for f in self.formdef.fields:
|
|
if not f.varname:
|
|
continue
|
|
if not self.filled.data.has_key(f.id):
|
|
continue
|
|
|
|
if f.store_display_value and ('%s_display' % f.id) in self.filled.data:
|
|
value = self.filled.data['%s_display' % f.id]
|
|
else:
|
|
value = self.filled.data[f.id]
|
|
|
|
if value is None:
|
|
value = ''
|
|
|
|
if not hasattr(f, str('get_view_value')):
|
|
continue
|
|
|
|
data['fields'][f.varname] = unicode(value)
|
|
|
|
|
|
wf_status = self.filled.get_visible_status()
|
|
if wf_status:
|
|
data['workflow'] = {}
|
|
data['workflow']['status'] = {'id': wf_status.id, 'name': wf_status.name}
|
|
|
|
get_response().set_content_type('application/json')
|
|
return json.dumps(data)
|
|
|
|
|
|
def form_status_buttons [html] (self):
|
|
if not get_response().iframe_mode:
|
|
'<div class="back-home-button">'
|
|
'<a href="%s">%s</a>' % (get_publisher().get_root_url(), _('Back Home'))
|
|
'</div>'
|
|
|
|
def history [html] (self):
|
|
if not self.filled.evolution:
|
|
return
|
|
if not self.formdef.is_user_allowed_read_status_and_history(get_request().user, self.filled):
|
|
return
|
|
'<div class="bo-block">'
|
|
'<h2>%s</h2>' % _('Log')
|
|
'<dl id="evolutions">'
|
|
hidden = False
|
|
for evo in self.filled.evolution:
|
|
if evo.status:
|
|
wf_status = self.filled.get_status(evo.status)
|
|
if wf_status and not wf_status.is_visible(self.filled, get_request().user):
|
|
hidden = True
|
|
else:
|
|
hidden = False
|
|
if hidden:
|
|
continue
|
|
|
|
"<dt>%s" % misc.localstrftime(evo.time)
|
|
if evo.who:
|
|
evo_author = None
|
|
if evo.who == '_submitter':
|
|
evo_author = _('Original Submitter')
|
|
if self.filled.is_submitter(get_request().user):
|
|
evo_author = _('Yourself')
|
|
elif self.filled.user_id:
|
|
try:
|
|
evo_author = _('Original Submitter (%s)') % \
|
|
get_publisher().user_class.get(self.filled.user_id).display_name
|
|
except KeyError:
|
|
pass
|
|
else:
|
|
try:
|
|
evo_author = evo.get_author_name()
|
|
except KeyError:
|
|
pass
|
|
if evo_author:
|
|
' <span class="user">%s</span>' % evo_author
|
|
'</dt>'
|
|
'<dd>'
|
|
if evo.status:
|
|
'<span class="status">%s</span> ' % self.filled.get_status_label(evo.status)
|
|
if evo.comment:
|
|
if evo.comment.startswith(str('#pre')):
|
|
'<div class="comment"><pre>%s</pre></div>' % evo.comment[4:]
|
|
else:
|
|
'<div class="comment">'
|
|
'<p>'
|
|
htmltext('<p>') + htmltext('\n').join(
|
|
[(x or htmltext('</p><p>')) for x in evo.comment.splitlines()])
|
|
'</p>'
|
|
'</div>'
|
|
for t in evo.display_parts():
|
|
t
|
|
'</dd>'
|
|
'</dl>'
|
|
'</div>' # bo-block
|
|
|
|
def check_receiver(self):
|
|
session = get_session()
|
|
if not session or not session.user:
|
|
raise errors.AccessUnauthorizedError()
|
|
user = get_request().user
|
|
if self.filled.formdef is None:
|
|
raise errors.AccessForbiddenError()
|
|
if not self.filled.formdef.is_user_allowed_read(user, self.filled):
|
|
raise errors.AccessForbiddenError()
|
|
return user
|
|
|
|
def receipt [html] (self,
|
|
always_include_user = False,
|
|
show_signature = True,
|
|
show_status = True,
|
|
form_url = ''):
|
|
user = get_request().user
|
|
if not always_include_user and get_request().user and \
|
|
get_request().user.id == self.filled.user_id:
|
|
user = None
|
|
else:
|
|
try:
|
|
user = get_publisher().user_class.get(self.filled.user_id)
|
|
except KeyError:
|
|
user = None
|
|
|
|
# this is custom code so it is possible to mark forms as anonyms, this
|
|
# is done through the VoteAnonymity field, this is very specific but
|
|
# isn't generalised yet into an useful extension mechanism, as it's not
|
|
# clear at the moment what could be useful.
|
|
for f in self.formdef.fields:
|
|
if f.key == 'vote-anonymity':
|
|
user = None
|
|
break
|
|
|
|
'<div class="dataview">'
|
|
if self.formdef.signing and not self.filled.signature:
|
|
'<div class="errornotice">%s</div>' % \
|
|
_("Warning form not signed")
|
|
if user:
|
|
'<p><span class="label">%s</span>' % _('User name')
|
|
'<span class="value">%s</span></p>' % user.display_name
|
|
|
|
if self.filled.signature:
|
|
'<span class="label">%s</span>' % _("The form is electronically signed")
|
|
if show_signature:
|
|
'<div class="signok">'
|
|
'<span class="label">%s</span>' % _("Signature")
|
|
issuer = self.filled.signature["issuer"]
|
|
subject = self.filled.signature["subject"]
|
|
if issuer:
|
|
'<span class="value">%s %s</span>' % (_('Issuer:'), issuer)
|
|
if subject:
|
|
'<span class="value">%s %s</span>' % (_('Subject:'), subject)
|
|
'</div>'
|
|
on_page = False
|
|
on_disabled_page = False
|
|
for f in self.formdef.fields:
|
|
|
|
if f.type == 'page':
|
|
on_disabled_page = False
|
|
if not f.is_visible(self.filled.data, self.formdef):
|
|
on_disabled_page = True
|
|
|
|
form_field = False
|
|
for f1 in self.formdef.fields[self.formdef.fields.index(f)+1:]:
|
|
if f1.key == 'page':
|
|
break
|
|
if isinstance(f1, WidgetField):
|
|
form_field = True
|
|
break
|
|
if form_field is False:
|
|
on_disabled_page = True
|
|
|
|
if on_disabled_page:
|
|
continue
|
|
|
|
|
|
if f.type == 'page':
|
|
if on_page:
|
|
'</div>'
|
|
'<div class="page">'
|
|
'<h3>%s</h3>' % f.label
|
|
on_page = True
|
|
continue
|
|
|
|
if not self.filled.data.has_key(f.id):
|
|
continue
|
|
|
|
if f.store_display_value and ('%s_display' % f.id) in self.filled.data:
|
|
value = self.filled.data['%s_display' % f.id]
|
|
else:
|
|
value = self.filled.data[f.id]
|
|
|
|
if value is None or value == '':
|
|
continue
|
|
|
|
if not hasattr(f, str('get_view_value')):
|
|
continue
|
|
|
|
'<p><span class="label">%s</span> ' % f.label
|
|
'<span class="value">'
|
|
s = f.get_view_value(value)
|
|
s = s.replace(str('[download]'), str('%sdownload' % form_url))
|
|
s
|
|
'</span></p>'
|
|
|
|
|
|
if on_page:
|
|
'</div>'
|
|
|
|
if show_status and self.formdef.is_user_allowed_read_status_and_history(
|
|
get_request().user, self.filled):
|
|
wf_status = self.filled.get_visible_status()
|
|
if wf_status:
|
|
'<p><span class="label">%s</span> ' % _('Status')
|
|
'<span class="value">%s</span></p>' % wf_status.name
|
|
|
|
'</div>'
|
|
|
|
|
|
def status [html] (self):
|
|
user = self.check_receiver()
|
|
form = None
|
|
|
|
try:
|
|
form = self.filled.get_workflow_form(user)
|
|
except:
|
|
# XXX: probably because there are mixed forms, with and without
|
|
# workflows
|
|
form = Form()
|
|
|
|
if form and form.is_submitted():
|
|
url = self.submit(form)
|
|
if url is None:
|
|
url = get_request().get_url()
|
|
response = get_response()
|
|
response.set_status(303)
|
|
response.headers[str('location')] = url
|
|
response.content_type = 'text/plain'
|
|
return "Your browser should redirect you"
|
|
|
|
get_logger().info('form %s - id: %s - view status' % (self.formdef.name, self.filled.id))
|
|
self.html_top('%s - %s' % (self.formdef.name, self.filled.id))
|
|
if self.filled.receipt_time is not None:
|
|
tm = misc.localstrftime(self.filled.receipt_time)
|
|
else:
|
|
tm = '???'
|
|
"<p>"
|
|
_('The form has been recorded on %(date)s with the number %(number)s.') % {
|
|
'date': tm, 'number': self.filled.id}
|
|
"</p>"
|
|
|
|
self.receipt(always_include_user = True)
|
|
|
|
if self.formdef.workflow:
|
|
htmltext(self.filled.display_workflow_message())
|
|
|
|
self.history()
|
|
|
|
if form:
|
|
form.render()
|
|
|
|
'<a href="..">%s</a>' % _('Back to Listing')
|
|
|
|
|
|
def submit(self, form, comment_only = False):
|
|
status = None
|
|
current_status = self.filled.status
|
|
user = get_request().user
|
|
next_url = self.filled.handle_workflow_form(user, form)
|
|
if next_url:
|
|
return next_url
|
|
if form.has_errors():
|
|
return
|
|
if current_status != self.filled.status:
|
|
get_logger().info('form %s - id: %s - status -> %s' % (
|
|
self.formdef.name, self.filled.id, self.filled.status))
|
|
|
|
def download(self):
|
|
if not self.filled.formdef.is_user_allowed_read(get_request().user, self.filled):
|
|
self.check_receiver()
|
|
try:
|
|
fn = get_request().form['f']
|
|
f = self.filled.data[fn]
|
|
except (KeyError, ValueError):
|
|
raise errors.TraversalError()
|
|
|
|
file = self.filled.data[fn]
|
|
if not hasattr(file, 'content_type'):
|
|
raise errors.TraversalError()
|
|
|
|
response = get_response()
|
|
if file.content_type:
|
|
response.set_content_type(file.content_type)
|
|
else:
|
|
response.set_content_type('application/octet-stream')
|
|
if file.charset:
|
|
response.set_charset(file.charset)
|
|
if file.base_filename:
|
|
if file.content_type.startswith('image/'):
|
|
response.set_header(
|
|
'content-disposition', 'inline; filename="%s"' % file.base_filename)
|
|
else:
|
|
response.set_header(
|
|
'content-disposition', 'attachment; filename="%s"' % file.base_filename)
|
|
|
|
return file.get_file_pointer().read()
|
|
|
|
def _q_traverse(self, path):
|
|
get_response().breadcrumb.append(
|
|
(str(self.filled.id) + '/',str(self.filled.id)))
|
|
return super(FormStatusPage, self)._q_traverse(path)
|