wcs/wcs/forms/root.py

1137 lines
44 KiB
Python

# 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 json
import time
import re
from StringIO import StringIO
import sys
import urllib2
try:
import qrcode
except ImportError:
qrcode = None
from quixote import get_publisher, get_request, get_response, get_session, redirect
from quixote.directory import Directory, AccessControlled
from quixote.util import randbytes
from quixote.form.widget import *
from quixote.html import TemplateIO, htmltext
from qommon import errors, get_cfg
from qommon import misc, get_logger
from qommon import template
from qommon.form import *
from qommon import tokens
from qommon import ezt
from wcs.anonylink import AnonymityLink
from wcs.categories import Category
from wcs.formdef import FormDef
from wcs.formdata import FormData
from wcs.roles import logged_users_role
from wcs.workflows import Workflow, EditableWorkflowStatusItem
from wcs.api import get_user_from_api_query_string
from qommon.admin.texts import TextsDirectory
from backoffice import FormDefUI
def html_top(title = None):
template.html_top(title = title, default_org = _('Forms'))
def get_user_forms(formdef):
"""Return forms data for the current user
formdef - the formdef from which we want form datas
"""
session = get_session()
user = session.get_user()
user_forms = []
if user:
if user.anonymous:
anonylinks = AnonymityLink.select(
lambda x: x.name_identifier == session.name_identifier and
x.formdata_type == 'form')
for anonylink in anonylinks:
if anonylink.formdata_def_id != formdef.id:
continue
user_forms.append(formdef.data_class().get(anonylink.formdata_id))
else:
user_forms.extend(formdef.data_class().get_with_indexed_value('user_id', user.id))
try:
user_forms.extend(formdef.data_class().get_with_indexed_value('user_hash', user.hash))
except AttributeError:
pass
return user_forms
from wcs.forms.common import FormStatusPage
class TokenDirectory(Directory):
_q_exports = ['']
def __init__(self, formdef, token):
self.formdef = formdef
self.token = token
def _q_index(self):
if self.token.type != 'form-invite':
raise errors.TraversalError()
if self.token.formdef_id != self.formdef.id:
raise errors.TraversalError()
try:
self.token.remove_self()
except OSError:
# race condition, and the token already got removed (??!)
self.token.type = None
get_session().set_user(self.token.user_id)
return redirect(self.formdef.get_url())
class TokensDirectory(Directory):
def __init__(self, formdef):
self.formdef = formdef
def _q_lookup(self, component):
try:
token = tokens.Token.get(component)
except KeyError:
raise errors.TraversalError()
return TokenDirectory(self.formdef, token)
class FormPage(Directory):
_q_exports = ['', 'listing', 'tempfile', 'tokens', 'schema', 'tryauth',
'auth', 'qrcode']
steps = [N_("Filling"), N_("Validating"), N_("Receipt")]
def __init__(self, component):
try:
self.formdef = FormDef.get_by_urlname(component)
except KeyError:
raise errors.TraversalError()
self.substvars = {}
get_publisher().substitutions.feed(self)
get_publisher().substitutions.feed(self.formdef)
self.tokens = TokensDirectory(self.formdef)
self.page_number = len([
x for x in self.formdef.fields[1:] if x.type == 'page']) + 1
self.user = get_request().user
get_response().breadcrumb.append( (component + '/', self.formdef.name) )
def get_substitution_variables(self):
return self.substvars
def schema(self):
get_response().set_content_type('application/json')
return self.formdef.export_to_json()
def check_role(self):
if self.formdef.roles:
if not self.user:
raise errors.AccessUnauthorizedError()
if logged_users_role().id not in self.formdef.roles and not (
self.user and self.user.is_admin):
if self.user:
user_roles = set(self.user.roles)
else:
user_roles = set([])
other_roles = (self.formdef.roles or [])
if self.formdef.workflow_roles:
other_roles.extend(self.formdef.workflow_roles.keys())
if not user_roles.intersection(other_roles):
raise errors.AccessForbiddenError()
def step(self, no, page_no = 0, log_detail = None, data = None, editing = None):
r = TemplateIO(html=True)
r += htmltext('<div id="steps"><ol>')
steps = self.steps[:]
if not self.formdef.confirmation:
del steps[1]
self.substvars['current_step'] = steps[no]
if no == 0:
self.substvars['current_page_no'] = str(page_no + 1)
if log_detail:
get_logger().info('form %s - step %s (%s)' % (self.formdef.name, steps[no], log_detail))
else:
get_logger().info('form %s - step %s' % (self.formdef.name, steps[no]))
for i, l in enumerate(steps):
classes = ['step-%d' % i]
if no == i:
classes.append('current')
elif no < i:
classes.append('step-after')
elif no > i:
classes.append('step-before')
if i+1 == len(steps):
classes.append('last')
if i == 0:
classes.append('first')
r += htmltext('<li class="%s">') % ' '.join(classes)
r += htmltext('<span class="marker">%d</span> <span class="label">%s</span>') % (i+1, _(l))
if i == 0 and (no == i or True) and self.page_number > 1:
r += htmltext('<ul>')
t = 0
for field in self.formdef.fields:
if field.type != 'page':
continue
classes = []
if t == page_no and no == i:
classes.append('current')
t += 1
if not field.is_visible(data, self.formdef):
continue
r += htmltext('<li class="%s"><span>%s</span></li>') % (' '.join(classes), field.label)
r += htmltext('</ul>')
r += htmltext('</li>')
if editing:
break
r += htmltext('</ol></div>')
return r.getvalue()
def page(self, page_no, page_change = True, log_detail = None, editing = None):
r = TemplateIO(html=True)
displayed_fields = []
session = get_session()
if page_no > 0:
magictoken = get_request().form['magictoken']
self.feed_current_data(magictoken)
form = self.formdef.create_form(page_no, displayed_fields)
if page_no == 0 and not get_request().form.has_key('magictoken'):
magictoken = randbytes(8)
else:
magictoken = get_request().form['magictoken']
form.add_hidden('magictoken', magictoken)
data = session.get_by_magictoken(magictoken, {})
if page_no == 0 and get_request().form.has_key('cancelurl'):
cancelurl = get_request().form['cancelurl']
session.add_magictoken(magictoken, {'__cancelurl': cancelurl})
if editing and page_no == self.page_number - 1:
form.add_submit('submit', _('Save Changes'))
elif not self.formdef.confirmation and page_no == self.page_number - 1:
form.add_submit('submit', _('Submit'))
else:
form.add_submit('submit', _('Next'))
if page_no > 0:
form.add_submit('previous', _('Previous'))
if page_change:
# on page change, we fake a GET request so the form is not altered
# with errors from the previous submit; if the page was already
# visited, we restore values; otherwise we set req.form as empty.
req = get_request()
req.environ['REQUEST_METHOD'] = 'GET'
one = False
for field in displayed_fields:
k = field.id
v = None
prefilled = False
if data.has_key(k):
v = data[k]
elif field.prefill:
v = field.get_prefill_value()
if v:
prefilled = True
form.get_widget('f%s' % k).set_message(
_('Value has been automatically prefilled.'))
form.get_widget('f%s' % k).prefilled = True
form.get_widget('f%s' % k).prefill_attributes = field.get_prefill_attributes()
if not prefilled and form.get_widget('f%s' % k):
form.get_widget('f%s' % k).clear_error()
if v is not None:
if not isinstance(v, str) and field.convert_value_to_str:
v = field.convert_value_to_str(v)
form.get_widget('f%s' % k).set_value(v)
req.form['f%s' % k] = v
one = True
if not one:
req.form = {}
html_top(self.formdef.name)
r += self.step(0, page_no, log_detail, data = data, editing = editing)
form.add_hidden('step', '0')
form.add_hidden('page', page_no)
form.add_submit('cancel', _('Cancel'), css_class = 'cancel')
if session.user and self.formdef.allow_drafts and not editing:
if get_request().form.has_key('mt'):
form.add_submit('removedraft', _('Remove Draft'), css_class = 'remove-draft')
form.add_submit('savedraft', _('Save As Draft'), css_class = 'save-draft')
r += form.render()
return r.getvalue()
def feed_current_data(self, magictoken):
# create a fake FormData to feed variables
formdata = FormData()
formdata._formdef = self.formdef
formdata.user = get_request().user
formdata.data = get_session().get_by_magictoken(magictoken, {})
formdata.status = str('')
get_publisher().substitutions.feed(formdata)
def _q_index(self, log_detail = None, editing = None):
self.check_role()
if self.formdef.is_disabled():
if self.formdef.disabled_redirection:
if re.search(r'\[.*\]', self.formdef.disabled_redirection):
try:
variables = get_publisher().substitutions.get_context_variables()
redirect_url = template.process_template(self.formdef.disabled_redirection, variables)
return redirect(redirect_url)
except ezt.EZTException:
pass
return redirect(self.formdef.disabled_redirection)
else:
raise errors.AccessForbiddenError()
session = get_session()
existing_formdata = None
if editing:
existing_formdata = editing.data
if not get_request().form:
# on the initial visit editing the form (i.e. not after
# clicking for previous or next page), we need to load the
# existing data into the session
editing.feed_session()
token = randbytes(8)
get_request().form['magictoken'] = token
session.add_magictoken(token, editing.data)
elif self.formdef.only_allow_one:
user_forms = get_user_forms(self.formdef)
if [x for x in user_forms if not x.is_draft()]:
return redirect('%s/' % user_forms[0].id)
form = Form()
form.add_hidden('step', '-1')
form.add_hidden('page', '-1')
form.add_hidden('magictoken', '-1')
form.add_submit('cancel')
if self.formdef.allow_drafts:
form.add_submit('removedraft')
form.add_submit('savedraft')
if not form.is_submitted():
if get_request().form.has_key('mt'):
magictoken = get_request().form['mt']
data = session.get_by_magictoken(magictoken, {})
if data:
# create a new one since the other has been exposed in a url
magictoken = randbytes(8)
session.add_magictoken(magictoken, data)
get_request().form['magictoken'] = magictoken
if data.has_key('page_no'):
page_no = int(data['page_no'])
del data['page_no']
if page_no == -1:
req = get_request()
for k, v in data.items():
req.form['f%s' % k] = v
return self.validating(data)
else:
page_no = 0
return self.page(page_no, True)
return self.page(0, editing = editing)
if form.get_submit() == 'cancel':
get_logger().info('form %s - cancel' % (self.formdef.name))
if editing:
return redirect('.')
try:
magictoken = form.get_widget('magictoken').parse()
cancelurl = session.get_by_magictoken(magictoken, {}).get('__cancelurl')
if cancelurl:
return redirect(cancelurl)
except KeyError:
pass
return redirect(get_publisher().get_root_url())
try:
step = int(form.get_widget('step').parse())
except TypeError:
step = 0
if step == 0:
try:
page_no = int(form.get_widget('page').parse())
except TypeError:
page_no = -1
try:
magictoken = form.get_widget('magictoken').parse()
except KeyError:
magictoken = randbytes(8)
self.feed_current_data(magictoken)
form = self.formdef.create_form(page_no)
form.add_submit('previous')
if self.formdef.allow_drafts:
form.add_submit('removedraft')
form.add_submit('savedraft')
form.add_submit('submit')
if page_no > 0 and form.get_submit() == 'previous':
return self.previous_page(page_no, magictoken, editing = editing)
if self.formdef.allow_drafts and form.get_submit() == 'removedraft':
self.remove_draft()
return redirect(get_publisher().get_root_url())
if self.formdef.allow_drafts and form.get_submit() == 'savedraft':
form_data = session.get_by_magictoken(magictoken, {})
data = self.formdef.get_data(form)
form_data.update(data)
self.save_draft(form_data, page_no)
return redirect(get_publisher().get_root_url())
# form.get_submit() returns the name of the clicked button, and
# it will return True if the form has been submitted, but not
# by clicking on a submit widget; for example if an "add row"
# button is clicked.
if form.has_errors() or form.get_submit() is True:
if page_no == -1:
# this situation shouldn't arise (that likely means the
# page hidden field had an error in its submission), in
# that case we just fall back to the first page.
page_no = 0
return self.page(page_no, page_change = False, editing = editing)
form_data = session.get_by_magictoken(magictoken, {})
data = self.formdef.get_data(form)
form_data.update(data)
session.add_magictoken(magictoken, form_data)
while True:
page_no = int(page_no) + 1
try:
next_page = self.formdef.get_page(page_no)
except IndexError:
break
if next_page.is_visible(form_data, self.formdef):
break
if page_no == self.page_number:
# last page has been submitted
req = get_request()
for field in self.formdef.fields:
k = field.id
if form_data.has_key(k):
v = form_data[k]
if field.convert_value_to_str:
v = field.convert_value_to_str(v)
req.form['f%s' % k] = v
if editing:
form = self.formdef.create_view_form(form_data, use_tokens = False)
return self.submitted_existing(form, editing)
if self.formdef.confirmation:
return self.validating(form_data)
else:
step = 1 # so it will flow to submit
# kind of restore state
form = Form()
form.add_hidden('step', '-1')
form.add_hidden('page', '-1')
form.add_hidden('magictoken', '-1')
form.add_submit('cancel')
if self.formdef.allow_drafts:
form.add_submit('removedraft')
form.add_submit('savedraft')
else:
return self.page(page_no, editing = editing)
if step == 1:
form.add_submit('previous')
magictoken = form.get_widget('magictoken').parse()
if form.get_submit() == 'previous':
return self.previous_page(self.page_number, magictoken, editing = editing)
magictoken = form.get_widget('magictoken').parse()
form_data = session.get_by_magictoken(magictoken, {})
data = self.formdef.get_data(form)
form_data.update(data)
session.add_magictoken(magictoken, form_data)
step = 2 # so it will flow to submit
form = Form()
form.add_hidden('step', '-1')
form.add_hidden('page', '-1')
form.add_hidden('magictoken', '-1')
form.add_submit('cancel')
if step == 2:
form.add_submit('previous')
magictoken = form.get_widget('magictoken').parse()
self.feed_current_data(magictoken)
form_data = session.get_by_magictoken(magictoken, {})
if form.get_submit() == 'previous':
return self.previous_page(self.page_number, magictoken, editing = editing)
if self.formdef.allow_drafts and form.get_submit() == 'removedraft':
self.remove_draft()
return redirect(get_publisher().get_root_url())
if self.formdef.allow_drafts and form.get_submit() == 'savedraft':
self.save_draft(form_data, page_no = -1)
return redirect(get_publisher().get_root_url())
# so it gets FakeFileWidget in preview mode
form = self.formdef.create_view_form(form_data,
use_tokens = self.formdef.confirmation)
if form.has_errors():
# the only possible error here is a token error if the form is
# submitted a second time
return template.error_page(_('This form has already been submitted.'))
return self.submitted(form, existing_formdata)
def previous_page(self, page_no, magictoken, editing = None):
session = get_session()
form_data = session.get_by_magictoken(magictoken, {})
while True:
page_no = int(page_no) - 1
try:
previous_page = self.formdef.get_page(page_no)
except IndexError:
break
if not previous_page.condition:
break
if previous_page.is_visible(form_data, self.formdef):
break
return self.page(page_no, page_change = True, editing = editing)
def remove_draft(self):
magictoken = get_request().form.get('magictoken')
if magictoken:
form_data = get_session().get_by_magictoken(magictoken, {})
if form_data.get('draft_formdata_id'):
self.formdef.data_class().remove_object(form_data.get('draft_formdata_id'))
def save_draft(self, data, page_no):
filled = self.formdef.data_class()()
filled.data = data
filled.status = 'draft'
filled.page_no = page_no
filled.receipt_time = time.localtime()
session = get_session()
if session and session.user and not str(session.user).startswith('anonymous-'):
filled.user_id = session.user
filled.store()
magictoken = get_request().form.get('magictoken')
if magictoken:
form_data = session.get_by_magictoken(magictoken, {})
if form_data.get('draft_formdata_id'):
filled.remove_object(form_data.get('draft_formdata_id'))
get_logger().info('form %s - saving draft (id: %s)' % (self.formdef.name, filled.id))
def submitted(self, form, existing_formdata = None):
if existing_formdata: # modifying
filled = existing_formdata
filled.last_modification_time = time.localtime()
# XXX: what about status?
else:
filled = self.formdef.data_class()()
filled.just_created()
filled.data = self.formdef.get_data(form)
session = get_session()
if session and session.user and not str(session.user).startswith('anonymous-'):
try:
filled.user_hash = get_request().user.hash
except AttributeError:
filled.user_id = get_request().user.id
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.
user_forms = get_user_forms(self.formdef)
if [x for x in user_forms if not x.is_draft()]:
return redirect('%s/' % user_forms[0].id)
filled.store()
magictoken = get_request().form.get('magictoken')
if magictoken:
form_data = session.get_by_magictoken(magictoken, {})
if form_data.get('draft_formdata_id'):
filled.remove_object(form_data.get('draft_formdata_id'))
if not filled.user_id and existing_formdata is None:
a = AnonymityLink()
a.formdata_type = 'form'
a.formdata_def_id = self.formdef.id
a.formdata_id = filled.id
if session.name_identifier:
a.name_identifier = session.name_identifier
# XXX nothing with anonylink.key ?
a.store()
get_logger().info('form %s - done (id: %s)' % (self.formdef.name, filled.id))
url = None
if existing_formdata is None:
url = filled.perform_workflow()
if not filled.user_id:
get_session().mark_anonymous_formdata(filled)
if not url:
url = filled.get_url()
return redirect(url)
def submitted_existing(self, form, editing):
old_data = editing.data
editing.data = self.formdef.get_data(form)
editing.store()
return redirect('.')
def tempfile(self):
self.check_role()
if not self.formdef.acl_read == 'all' and (
self.user and not self.user.id == get_session().user):
self.check_receiver()
try:
t = get_request().form['t']
tempfile = get_session().tempfiles[t]
except KeyError:
raise errors.TraversalError()
response = get_response()
if tempfile['content_type']:
response.set_content_type(tempfile['content_type'])
else:
response.set_content_type('application/octet-stream')
if tempfile['charset']:
response.set_charset(tempfile['charset'])
return get_session().get_tempfile_content(t).get_file_pointer().read()
def validating(self, data):
html_top(self.formdef.name)
r = TemplateIO(html=True)
r += htmltext('<div class="form-validation">')
r += self.step(1, data = data)
r += TextsDirectory.get_html_text('check-before-submit')
form = self.formdef.create_view_form(data)
token_widget = form.get_widget(form.TOKEN_NAME)
token_widget._parsed = True
form.add_submit('previous', _('Previous'))
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'), css_class = 'cancel')
session = get_session()
if session.user and self.formdef.allow_drafts:
if get_request().form.has_key('mt'):
form.add_submit('removedraft', _('Remove Draft'), css_class = 'remove-draft')
form.add_submit('savedraft', _('Save As Draft'), css_class = 'save-draft')
form.add_hidden('step', '2')
magictoken = get_request().form['magictoken']
form.add_hidden('magictoken', magictoken)
r += form.render()
r += htmltext('</div>')
return r.getvalue()
def error(self, msg):
html_top(self.formdef.name)
homepage = get_publisher().get_root_url()
r = TemplateIO(html=True)
r += htmltext('<div class="errornotice">%s</div>') % msg
r += htmltext('<a href="%s">%s</a>') % (homepage, _('Back Home'))
return r.getvalue()
def listing(self):
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))
r = TemplateIO(html=True)
fields = []
for field in self.formdef.fields:
if hasattr(field, str('get_view_value')) and field.in_listing:
fields.append(field)
r += FormDefUI(self.formdef).listing(fields, 'all', include_form=True)
r += htmltext('<p><a href="..">%s</a></p>') % _('Back')
return r.getvalue()
def tryauth(self):
# this URL tries to log the user in before displaying the form; this
# won't do anything for local logins but will use a passive SAML
# request when configured to use an external identity provider.
form_url = self.formdef.get_url()
if self.user:
return redirect(form_url)
ident_methods = get_cfg('identification', {}).get('methods', ['idp'])
if not 'idp' in ident_methods:
# when configured with local logins and not logged in, display the
# form.
return redirect(form_url)
login_url = '/login/?ReturnUrl=%s&IsPassive=true' % urllib2.quote(form_url)
return redirect(login_url)
def auth(self):
# this URL logs the user in before displaying the form.
form_url = self.formdef.get_url()
if self.user:
return redirect(form_url)
login_url = '/login/?ReturnUrl=%s' % urllib2.quote(form_url)
return redirect(login_url)
def qrcode(self):
img = qrcode.make(self.formdef.get_url())
s = StringIO()
img.save(s)
if get_request().get_query() == 'download':
get_response().set_header('content-disposition',
'attachment; filename=qrcode-%s.png' % self.formdef.url_name)
get_response().set_content_type('image/png')
return s.getvalue()
def _q_lookup(self, component):
try:
filled = self.formdef.data_class().get(component)
except KeyError:
raise errors.TraversalError()
if not filled.is_draft():
return PublicFormStatusPage(self.formdef, filled)
if get_request().user is None:
raise errors.AccessUnauthorizedError()
session = get_session()
if str(session.user) != str(filled.user_id):
raise errors.AccessForbiddenError()
magictoken = randbytes(8)
filled.feed_session()
form_data = filled.data
form_data['draft_formdata_id'] = filled.id
form_data['page_no'] = filled.page_no
session.add_magictoken(magictoken, form_data)
return redirect('./?mt=%s' % magictoken)
class RootDirectory(AccessControlled, Directory):
_q_exports = ['', 'json', 'categories']
category = None
def __init__(self, category = None):
self.category = category
get_publisher().substitutions.feed(category)
def _q_access(self):
if self.category:
response = get_response()
response.breadcrumb.append( ('%s/' % self.category.url_name, self.category.name ) )
def get_list_of_forms(self, formdefs, user):
list_forms = []
advertised_forms = []
for formdef in formdefs:
if formdef.roles:
if not user:
if formdef.always_advertise:
advertised_forms.append(formdef)
continue
if logged_users_role().id not in formdef.roles:
for q in user.roles or []:
if q in formdef.roles:
break
else:
if formdef.always_advertise:
advertised_forms.append(formdef)
continue
list_forms.append(formdef)
return list_forms, advertised_forms
def _q_index(self):
if get_request().get_header(str('Accept'), '') == 'application/json':
return self.json()
template.html_top(default_org = _('Forms'))
r = TemplateIO(html=True)
session = get_session()
request = get_request()
user = request.user
if not self.category:
get_logger().info('home page')
if user:
message = TextsDirectory.get_html_text('welcome-logged')
else:
message = TextsDirectory.get_html_text('welcome-unlogged')
if message:
r += htmltext('<div id="welcome-message">')
r += message
r += htmltext('</div>')
if self.category:
formdefs = FormDef.select(lambda x: (
x.category_id == self.category.id and (not x.is_disabled() or x.disabled_redirection)),
order_by = 'name')
else:
formdefs = FormDef.select(lambda x: not x.is_disabled() or x.disabled_redirection, order_by = 'name',
ignore_errors=True)
list_forms, advertised_forms = self.get_list_of_forms(formdefs, user)
if formdefs and not list_forms and not advertised_forms:
# there is forms, but none can be displayed
raise errors.AccessUnauthorizedError()
user_forms = []
if user:
for formdef in list_forms:
if not formdef.is_disabled():
user_forms.extend(get_user_forms(formdef))
user_forms = [x for x in user_forms if x.formdef.is_user_allowed_read(user, x)]
user_forms.sort(lambda x,y: cmp(x.receipt_time, y.receipt_time))
if self.category:
r += self.form_list(list_forms, category = self.category,
session = session, user_forms = user_forms,
advertised_forms = advertised_forms)
else:
cats = Category.select()
Category.sort_by_position(cats)
one = False
for c in cats:
l2 = [x for x in list_forms if x.category_id == c.id]
l2_advertise = [x for x in advertised_forms if x.category_id == c.id]
if l2 or l2_advertise:
r += self.form_list(l2, category = c,
session = session, user_forms = user_forms,
advertised_forms = l2_advertise)
one = True
l2 = [x for x in list_forms if not x.category]
l2_advertise = [x for x in advertised_forms if not x.category]
if l2 or l2_advertise:
if one:
title = _('Misc')
else:
title = None
r += self.form_list(l2, title = title,
session = session, user_forms = user_forms,
advertised_forms = l2_advertise)
root_url = get_publisher().get_root_url()
if user:
r += self.user_forms(user_forms)
if not self.category:
r += htmltext('<p id="logout">')
if user.can_go_in_backoffice():
r += htmltext('<a href="%sbackoffice/">%s</a> - ') % (root_url, _('Back Office'))
if user.anonymous:
if not get_cfg('misc', {}).get('do-not-token', False):
r += htmltext('<a href="%stoken">%s</a> - ') % (root_url, _('Enter Identification Token'))
if get_cfg('saml_identities', {}).get('creation', 'admin') != 'admin':
r += htmltext('<a href="%sregister">%s</a> - ') % (root_url, _('Register'))
r += htmltext('<a href="%slogout">%s</a></p>') % (root_url, _('Logout'))
elif get_cfg('sp') or get_cfg('identification', {}).get('methods'):
if not self.category:
r += htmltext('<p id="login"><a href="%slogin">%s</a>') % (root_url, _('Login'))
identities_cfg = get_cfg('identities', {})
if identities_cfg.get('creation') in ('self', 'moderated'):
r += htmltext(' - <a href="%sregister">%s</a>') % (root_url, _('Register'))
r += htmltext('</p>')
return r.getvalue()
def user_forms(self, user_forms):
r = TemplateIO(html=True)
draft = [x for x in user_forms if x.is_draft()]
if draft:
r += htmltext('<h2 id="drafts">%s</h2>') % _('Your Current Drafts')
r += htmltext('<ul>')
for f in draft:
r += htmltext('<li><a href="%s/%s">%s</a>, %s</li>') % (
f.formdef.url_name, f.id, f.formdef.name,
misc.localstrftime(f.receipt_time))
r += htmltext('</ul>')
# with workflows
workflows = [Workflow.get_default_workflow()] + Workflow.select(order_by = 'name')
for workflow in workflows:
# XXX: seperate endpoints from non-endpoints
for status in workflow.possible_status:
fms = [x for x in user_forms if \
not x.formdef.private_status_and_history and \
x.formdef.workflow.id == workflow.id and \
(x.get_visible_status() == status)]
if not fms:
continue
r += htmltext('<h2>%s</h2>') % _('Your forms with status "%s"') % status.name
r += htmltext('<ul>')
for f in fms:
r += htmltext('<li><a href="%s/%s/">%s</a>, %s</li>') % (
f.formdef.url_name, f.id, f.formdef.name,
misc.localstrftime(f.receipt_time))
r += htmltext('</ul>')
return r.getvalue()
def form_list(self, list, category = None, title = None,
session = None, user_forms = None, advertised_forms = []):
r = TemplateIO(html=True)
if title:
r += htmltext('<div class="category" id="category-%s">') % misc.simplify(title)
r += htmltext('<h2>%s</h2>') % title
elif category:
r += htmltext('<div class="category" id="category-%s">') % category.url_name
r += htmltext('<h2>%s</h2>') % category.name
else:
r += htmltext('<div class="category" id="category-misc">')
formdefs_data = None
if self.category:
url_prefix = ''
r += self.category.get_description_html_text()
elif category:
url_prefix = '%s/' % category.url_name
else:
url_prefix = ''
r += htmltext('<ul class="catforms">')
for formdef in list:
if formdef.only_allow_one and user_forms:
if formdefs_data is None:
formdefs_data = [x.formdef.id for x in user_forms
if x.formdef.only_allow_one and not x.is_draft()]
if formdefs_data and formdef.id in formdefs_data:
# form has already been completed
r += htmltext('<li>%s (%s, <a href="%s%s/">%s</a>)') % (
formdef.name, _('already completed'),
url_prefix, formdef.url_name, _('review'))
else:
r += htmltext('<li><a href="%s%s/">%s</a>') % (url_prefix, formdef.url_name, formdef.name)
if formdef.acl_read == 'all':
r += htmltext(' <a class="listing" href="%s%s/listing">%s</a>') % (
url_prefix, formdef.url_name, _('(listing)'))
r += htmltext('</li>')
for formdef in advertised_forms:
r += htmltext('<li class="required-authentication">')
r += htmltext('<a href="%s%s/">%s</a>') % (url_prefix, formdef.url_name, formdef.name)
r += htmltext('<span> (%s)</span></li>') % _('authentication required')
r += htmltext('</ul>')
r += htmltext('</div>')
return r.getvalue()
def json(self):
user = get_user_from_api_query_string() or get_request().user
list_forms = []
if self.category:
formdefs = FormDef.select(lambda x: (
x.category_id == self.category.id and (not x.is_disabled() or x.disabled_redirection)),
order_by = 'name')
else:
formdefs = FormDef.select(lambda x: not x.is_disabled() or x.disabled_redirection,
order_by='name',
ignore_errors=True)
charset = get_publisher().site_charset
for formdef in formdefs:
authentication_required = False
if formdef.roles and not (user and user.is_admin):
if not user:
if not formdef.always_advertise:
continue
authentication_required = True
elif logged_users_role().id not in formdef.roles:
for q in user.roles or []:
if q in formdef.roles:
break
else:
if not formdef.always_advertise:
continue
authentication_required = True
formdict = {'title': unicode(formdef.name, charset),
'slug': formdef.url_name,
'url': formdef.get_url(),
'authentication_required': authentication_required}
# we include the count of submitted forms so it's possible to sort
# them by popularity
formdict['count'] = formdef.data_class().count()
if formdef.category:
formdict['category'] = unicode(formdef.category.name, charset)
formdict['category_position'] = (formdef.category.position or 0)
else:
formdict['category_position'] = sys.maxint
list_forms.append(formdict)
list_forms.sort(lambda x, y: cmp(x['category_position'], y['category_position']))
for formdict in list_forms:
del formdict['category_position']
get_response().set_content_type('application/json')
return json.dumps(list_forms)
def get_categories(self, user):
result = []
formdefs = FormDef.select(
lambda x: not x.is_disabled() or x.disabled_redirection,
order_by='name', ignore_errors=True)
list_forms, advertised_forms = self.get_list_of_forms(formdefs, user)
list_forms = list_forms + advertised_forms
cats = Category.select()
Category.sort_by_position(cats)
for c in cats:
if [x for x in list_forms if x.category_id == c.id]:
result.append(c)
return result
def categories(self):
if self.category:
raise errors.TraversalError()
if get_request().is_json():
return self.categories_json()
template.html_top(_('Categories'))
r = TemplateIO(html=True)
user = get_request().user
for category in self.get_categories(user):
r += htmltext('<h2>%s</h2>') % category.name
r += category.get_description_html_text()
r += htmltext('<p><a href="%s/">%s</a></p>') % (category.url_name, _('All forms'))
return r.getvalue()
def categories_json(self):
user = get_user_from_api_query_string() or get_request().user
list_categories = []
charset = get_publisher().site_charset
categories = self.get_categories(user)
Category.sort_by_position(categories)
for category in categories:
d = {}
d['title'] = unicode(category.name, charset)
d['url'] = category.get_url()
if category.description:
d['description'] = unicode(category.description, charset)
list_categories.append(d)
get_response().set_content_type('application/json')
return json.dumps({'data': list_categories})
def _q_lookup(self, component):
return FormPage(component)
class PublicFormStatusPage(FormStatusPage):
_q_exports = ['', 'download', 'status', 'wfedit']
def __init__(self, *args, **kwargs):
FormStatusPage.__init__(self, *args, **kwargs)
if self.filled.anonymised:
if get_session() and get_session().is_anonymous_submitter(self.filled):
return
raise errors.TraversalError()
def status(self):
return redirect('%sbackoffice/%s/%s/' % (
get_publisher().get_root_url(),
self.formdef.url_name,
str(self.filled.id)))
def form_status_buttons(self):
if not get_response().iframe_mode:
r = TemplateIO(html=True)
r += htmltext('<div class="back-home-button">')
r += htmltext('<a href="%s">%s</a>') % (get_publisher().get_root_url(), _('Back Home'))
r += htmltext('</div>')
return r.getvalue()
def wfedit(self):
wf_status = self.filled.get_status()
for item in wf_status.items:
if not isinstance(item, EditableWorkflowStatusItem):
continue
if item.check_auth(self.filled, get_request().user):
f = FormPage(self.formdef.url_name)
get_response().breadcrumb = get_response().breadcrumb[:-1]
get_response().breadcrumb.append( ('wfedit', _('Edit')) )
return f._q_index(editing = self.filled)
raise errors.AccessForbiddenError()
TextsDirectory.register('welcome-logged',
N_('Welcome text on home page for logged users'))
TextsDirectory.register('welcome-unlogged',
N_('Welcome text on home page for unlogged users'))
TextsDirectory.register('form-recorded',
N_('Message when a form has been recorded'),
hint = N_('Available variables: date, number'),
category = N_('Forms'),
default = N_('The form has been recorded on [date] with the number [number].'))
TextsDirectory.register('form-recorded-allow-one',
N_('Message when a form has been recorded, and the form is set to only allow one per user'),
hint = N_('Available variable: date'),
category = N_('Forms'),
default = N_('The form has been recorded on [date].'))
TextsDirectory.register('check-before-submit',
N_('Message when a form is displayed before validation'),
category = N_('Forms'),
default = N_('Check values then click submit.'))