general: keep submission context additional details (#8306)

This commit is contained in:
Frédéric Péters 2015-09-19 18:08:43 +02:00
parent f0e88b1cc3
commit 558c4df65e
10 changed files with 129 additions and 13 deletions

View File

@ -34,7 +34,8 @@ différentes pages définies dans le formulaire (mode flux).
La complétion d'un formulaire se fait par une requête <code>POST</code> à
l'adresse <code>/api/formdefs/<var>slug</var>/submit</code>, le contenu de
la requête doit être un dictionnaire contenant obligatoirement un attribut
<code>data</code> et optionnellement un attribut <code>meta</code>.
<code>data</code> et optionnellement un attribut <code>meta</code> et un
attribut <code>context</code>.
</p>
<p>
@ -62,6 +63,13 @@ différentes pages définies dans le formulaire (mode flux).
</tr>
</table>
<p>
L'attribut <code>context</code> est également optionnel et contient une
série de renseignements supplémentaires sur le contexte de l'envoi du
formulaire. Les attributs reconnus sont <code>channel</code>,
<code>thumbnail_url</code>, <code>user_id</code> et <code>comments</code>.
</p>
<p>
L'exemple suivant complète un formulaire d'inscription à une newsletter, qui
demanderait trois champs, prénom, nom et adresse électronique.

View File

@ -314,6 +314,13 @@ def test_formdef_submit(local_user):
resp = get_app(pub).post_json(url, {'meta': {'draft': True}, 'data': {}})
assert data_class.get(resp.json['data']['id']).status == 'draft'
resp = get_app(pub).post_json(url, {'meta': {'backoffice-submission': True}, 'data': {},
'context': {'channel': 'mail'} })
assert data_class.get(resp.json['data']['id']).status == 'wf-new'
assert data_class.get(resp.json['data']['id']).backoffice_submission is True
assert data_class.get(resp.json['data']['id']).user_id is None
assert data_class.get(resp.json['data']['id']).submission_context == {'channel': 'mail'}
data_class.wipe()
def test_categories():

View File

@ -395,6 +395,37 @@ def test_backoffice_handling(pub):
assert FormDef.get_by_urlname('form-title').data_class().get(number31).status == 'wf-accepted'
assert 'HELLO WORLD' in resp.body
def test_backoffice_submission_context(pub):
user = create_user(pub)
create_environment(pub)
form_class = FormDef.get_by_urlname('form-title').data_class()
number31 = [x for x in form_class.select() if x.data['1'] == 'FOO BAR 30'][0]
app = login(get_app(pub))
resp = app.get('/backoffice/management/form-title/')
assert re.findall('<tbody.*\/tbody>', resp.body, re.DOTALL)[0].count('<tr') == 17
# click on a formdata
resp = resp.click(href='%s/' % number31.id)
assert (' with the number %s.' % number31.id) in resp.body
# check there's nothing in the sidebar
assert not 'Channel' in resp.body
number31.submission_context = {
'channel': 'mail',
'thumbnail_url': 'http://www.example.com/thumbnail.png',
'user_id': user.id,
'comments': 'test_backoffice_submission_context',
}
number31.store()
resp = app.get('/backoffice/management/form-title/')
resp = resp.click(href='%s/' % number31.id)
assert 'Channel' in resp.body
assert 'http://www.example.com/thumbnail.png' in resp.body
assert 'Associated User' in resp.body
assert 'test_backoffice_submission_context' in resp.body
def test_backoffice_info_text(pub):
create_user(pub)
create_environment(pub)

View File

@ -204,6 +204,8 @@ class ApiFormdefDirectory(Directory):
formdata.backoffice_submission = True
else:
formdata.user_id = user.id
if json_input.get('context'):
formdata.submission_context = json_input['context']
formdata.store()
if self.formdef.enable_tracking_codes:
code = get_publisher().tracking_code_class()

View File

@ -1079,8 +1079,39 @@ class FormBackOfficeStatusPage(FormStatusPage):
return html_top('management', title)
def _q_index(self):
get_response().filter['sidebar'] = self.get_sidebar()
return self.status()
def get_sidebar(self):
return self.get_extra_context_bar()
def get_extra_context_bar(self):
formdata = self.filled
if not formdata.submission_context:
return ''
r = TemplateIO(html=True)
extra_context = formdata.submission_context or {}
r += htmltext('<div class="extra-context">')
if extra_context.get('channel'):
channel_labels = {
'mail': _('Mail'),
}
r += htmltext('<h3>%s</h3>') % '%s: %s' % (
_('Channel'), channel_labels.get(extra_context.get('channel'), '?'))
if extra_context.get('thumbnail_url'):
r += htmltext('<p class="thumbnail"><img src="%s" alt=""/></p>'
) % extra_context.get('thumbnail_url')
if extra_context.get('user_id'):
r += htmltext('<h3>%s</h3>') % _('Associated User')
r += htmltext('<p>%s</p>') % get_publisher().user_class.get(
extra_context.get('user_id')).display_name
if extra_context.get('comments'):
r += htmltext('<h3>%s</h3>') % _('Comments')
r += htmltext('<p>%s</p>') % extra_context.get('comments')
r += htmltext('</div>')
return r.getvalue()
class FakeField(object):
def __init__(self, id, type_, label):

View File

@ -25,6 +25,8 @@ from wcs.formdef import FormDef
from wcs.categories import Category
from wcs.forms.root import FormPage as PublicFormFillPage
from .management import FormBackOfficeStatusPage
class FormFillPage(PublicFormFillPage):
def html_top(self, *args, **kwargs):
@ -42,19 +44,21 @@ class FormFillPage(PublicFormFillPage):
def get_sidebar(self, data):
r = TemplateIO(html=True)
formdata = None
draft_formdata_id = data.get('draft_formdata_id')
if draft_formdata_id:
formdata = self.formdef.data_class().get(draft_formdata_id)
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
if formdata and formdata.tracking_code:
tracking_code = formdata.tracking_code
r += htmltext('<p>%s</p>') % formdata.tracking_code
else:
r += htmltext('<p>-</p>')
if formdata and formdata.submission_context:
r += FormBackOfficeStatusPage(self.formdef, formdata).get_extra_context_bar()
return r.getvalue()
def form_side(self, step_no, page_no=0, log_detail=None, data=None, editing=None):
@ -72,6 +76,13 @@ class FormFillPage(PublicFormFillPage):
filled.backoffice_submission = True
filled.store()
magictoken = get_request().form.get('magictoken')
form_data = get_session().get_by_magictoken(magictoken, {})
draft_formdata_id = form_data.get('draft_formdata_id')
if draft_formdata_id:
old_draft_formdata = self.formdef.data_class().get(draft_formdata_id)
filled.submission_context = old_draft_formdata.submission_context
self.keep_tracking_code(filled)
get_session().remove_magictoken(get_request().form.get('magictoken'))

View File

@ -158,6 +158,7 @@ class FormData(StorableObject):
editable_by = None
tracking_code = None
backoffice_submission = False
submission_context = None
workflow_data = None
workflow_roles = None

View File

@ -509,7 +509,10 @@ class FormPage(Directory):
if get_request().form.has_key('mt'):
magictoken = get_request().form['mt']
data = session.get_by_magictoken(magictoken, {})
session.remove_magictoken(magictoken)
if not get_request().is_in_backoffice():
# don't remove magictoken as the backoffice agent may get
# the page reloaded.
session.remove_magictoken(magictoken)
if data:
# create a new one since the other has been exposed in a url
magictoken = randbytes(8)

View File

@ -1079,6 +1079,15 @@ div.admin-permissions tbody tr:nth-child(even) {
background: #eee;
}
div.extra-context p.thumbnail {
text-align: center;
}
div.extra-context p.thumbnail img {
box-shadow: 0 0 3px black;
transform: rotate(2deg);
}
@media print {
div#sidebar {
display: none;

View File

@ -308,7 +308,8 @@ def do_formdef_tables(formdef, conn=None, cur=None, rebuild_views=False, rebuild
'status', 'workflow_data', 'id_display', 'fts', 'page_no',
'anonymised', 'workflow_roles', 'workflow_roles_array',
'concerned_roles_array', 'tracking_code',
'actions_roles_array', 'backoffice_submission'])
'actions_roles_array', 'backoffice_submission',
'submission_context'])
# migrations
if not 'fts' in existing_fields:
@ -337,6 +338,9 @@ def do_formdef_tables(formdef, conn=None, cur=None, rebuild_views=False, rebuild
if not 'backoffice_submission' in existing_fields:
cur.execute('''ALTER TABLE %s ADD COLUMN backoffice_submission boolean''' % table_name)
if not 'submission_context' in existing_fields:
cur.execute('''ALTER TABLE %s ADD COLUMN submission_context bytea''' % table_name)
# add new fields
for field in formdef.fields:
assert field.id is not None
@ -927,6 +931,7 @@ class SqlFormData(SqlMixin, wcs.formdata.FormData):
('actions_roles_array', 'text[]'),
('tracking_code', 'varchar'),
('backoffice_submission', 'boolean'),
('submission_context', 'bytea'),
]
def __init__(self, id=None):
@ -1019,6 +1024,7 @@ class SqlFormData(SqlMixin, wcs.formdata.FormData):
'anonymised': self.anonymised,
'tracking_code': self.tracking_code,
'backoffice_submission': self.backoffice_submission,
'submission_context': self.submission_context,
}
if self.receipt_time:
sql_dict['receipt_time'] = datetime.datetime.fromtimestamp(time.mktime(self.receipt_time)),
@ -1030,6 +1036,10 @@ class SqlFormData(SqlMixin, wcs.formdata.FormData):
else:
sql_dict['workflow_roles'] = None
sql_dict['workflow_roles_array'] = None
if self.submission_context:
sql_dict['submission_context'] = bytearray(cPickle.dumps(self.submission_context))
else:
sql_dict['submission_context'] = None
sql_dict['concerned_roles_array'] = [str(x) for x in self.concerned_roles if x]
sql_dict['actions_roles_array'] = [str(x) for x in self.actions_roles if x]
@ -1134,6 +1144,8 @@ class SqlFormData(SqlMixin, wcs.formdata.FormData):
o.workflow_data = cPickle.loads(str(o.workflow_data))
if o.workflow_roles:
o.workflow_roles = cPickle.loads(str(o.workflow_roles))
if o.submission_context:
o.submission_context = cPickle.loads(str(o.submission_context))
o.data = cls._row2obdata(row, cls._formdef)
return o
_row2ob = classmethod(_row2ob)
@ -1659,7 +1671,7 @@ def get_yearly_totals(period_start=None, period_end=None, criterias=None):
return result
SQL_LEVEL = 7
SQL_LEVEL = 8
def migrate_global_views(conn, cur):
cur.execute('''SELECT COUNT(*) FROM information_schema.tables
@ -1708,8 +1720,9 @@ def migrate():
migrate_views(conn, cur)
for formdef in FormDef.select():
formdef.data_class().rebuild_security()
if sql_level < 7:
if sql_level < 8:
# 7: add backoffice_submission to tables and views
# 8: add submission_context to tables
migrate_views(conn, cur)
cur.execute('''UPDATE wcs_meta SET value = %s WHERE key = %s''', (