# -*- coding: utf-8 -*-
import datetime
import json
import os
import re
import shutil
import time
import hashlib
import random
import xml.etree.ElementTree as ET
import zipfile

import mock
import pytest

try:
    import xlwt
except ImportError:
    xlwt = None

from django.utils import six
from django.utils.six import StringIO, BytesIO
from django.utils.six.moves.urllib import parse as urllib
from django.utils.six.moves.urllib import parse as urlparse

from quixote import cleanup, get_publisher
from wcs.qommon import ods
from wcs.api_utils import sign_url
from wcs.qommon import errors, sessions
from wcs.qommon.form import PicklableUpload
from wcs.qommon.ident.password_accounts import PasswordAccount
from wcs.qommon.http_request import HTTPRequest
from wcs.roles import Role
from wcs.workflows import (Workflow, CommentableWorkflowStatusItem,
        ChoiceWorkflowStatusItem, EditableWorkflowStatusItem,
        DisplayMessageWorkflowStatusItem,
        JumpOnSubmitWorkflowStatusItem, WorkflowCriticalityLevel,
        WorkflowBackofficeFieldsFormDef)
from wcs.wf.backoffice_fields import SetBackofficeFieldsWorkflowStatusItem
from wcs.wf.dispatch import DispatchWorkflowStatusItem
from wcs.wf.form import FormWorkflowStatusItem, WorkflowFormFieldsFormDef
from wcs.wf.jump import JumpWorkflowStatusItem
from wcs.wf.wscall import WebserviceCallStatusItem
from wcs.wf.redirect_to_url import RedirectToUrlWorkflowStatusItem
from wcs.wf.register_comment import RegisterCommenterWorkflowStatusItem
from wcs.wf.resubmit import ResubmitWorkflowStatusItem
from wcs.carddef import CardDef
from wcs.categories import Category
from wcs.formdef import FormDef
from wcs.logged_errors import LoggedError
from wcs import fields
from wcs.wscalls import NamedWsCall

from utilities import (get_app, login, create_temporary_pub,
        clean_temporary_pub)


def pytest_generate_tests(metafunc):
    if 'pub' in metafunc.fixturenames:
        metafunc.parametrize('pub', ['pickle', 'sql', 'pickle-templates'], indirect=True)

@pytest.fixture
def pub(request, emails):
    pub = create_temporary_pub(
            sql_mode=bool('sql' in request.param),
            templates_mode=bool('templates' in request.param)
            )

    req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
    pub.set_app_dir(req)
    pub.cfg['identification'] = {'methods': ['password']}
    pub.cfg['language'] = {'language': 'en'}
    pub.write_cfg()
    fd = open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w')
    fd.write('''
[api-secrets]
coucou = 1234
''')
    fd.close()

    return pub

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')
    user1.email = 'admin@localhost'
    user1.is_admin = is_admin
    user1.store()

    account1 = PasswordAccount(id='admin')
    account1.set_password('admin')
    account1.user_id = user1.id
    account1.store()

    Role.wipe()
    role = Role(name='foobar')
    role.store()

    user1.roles = [role.id]
    user1.store()

    return user1

def create_superuser(pub): return create_user(pub, is_admin=True)

def create_environment(pub, set_receiver=True):
    pub.session_manager.session_class.wipe()
    Workflow.wipe()
    Category.wipe()
    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    if set_receiver:
        formdef.workflow_roles = {'_receiver': 1}

    datasource = {'type': 'formula',
            'value': repr([('A', 'aa'), ('B', 'bb'), ('C', 'cc')])}

    formdef.fields = [
            fields.StringField(id='1', label='1st field', type='string',
                display_locations=['validation', 'summary', 'listings']),
            fields.ItemField(id='2', label='2nd field', type='item',
                items=['foo', 'bar', 'baz'],
                display_locations=['validation', 'summary', 'listings']),
            fields.ItemField(id='3', label='3rd field', type='item',
                data_source=datasource, varname='foo'),
        ]

    formdef.store()
    formdef.data_class().wipe()
    for i in range(50):
        formdata = formdef.data_class()()
        formdata.just_created()
        formdata.receipt_time = datetime.datetime(2015, 1, 1).timetuple()
        formdata.data = {'1': 'FOO BAR %d' % i}
        if i%4 == 0:
            formdata.data['2'] = 'foo'
            formdata.data['2_display'] = 'foo'
            formdata.data['3'] = 'A'
            formdata.data['3_display'] = 'aa'
        elif i%4 == 1:
            formdata.data['2'] = 'bar'
            formdata.data['2_display'] = 'bar'
            formdata.data['3'] = 'B'
            formdata.data['3_display'] = 'bb'
        else:
            formdata.data['2'] = 'baz'
            formdata.data['2_display'] = 'baz'
            formdata.data['3'] = 'C'
            formdata.data['3_display'] = 'cc'
        if i%3 == 0:
            formdata.jump_status('new')
        else:
            formdata.jump_status('finished')
        formdata.store()

    formdata = formdef.data_class()()
    formdata.data = {'1': 'XXX', '2': 'foo', '2_display': 'foo'}
    formdata.status = 'draft'
    formdata.store()

    formdef = FormDef()
    if set_receiver:
        formdef.workflow_roles = {'_receiver': 1}
    formdef.name = 'other form'
    formdef.fields = []
    formdef.store()
    formdef.data_class().wipe()
    for i in range(20):
        formdata = formdef.data_class()()
        formdata.just_created()
        formdata.receipt_time = datetime.datetime(2014, 1, 1).timetuple()
        formdata.jump_status('new')
        formdata.store()

def teardown_module(module):
    clean_temporary_pub()

def test_backoffice_unlogged(pub):
    create_superuser(pub)
    resp = get_app(pub).get('/backoffice/', status=302)
    assert resp.location == 'http://example.net/login/?next=http%3A%2F%2Fexample.net%2Fbackoffice%2F'

def test_backoffice_home(pub):
    create_superuser(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/')
    assert 'Management' in resp.text
    assert 'Forms Workshop' in resp.text
    assert 'Workflows Workshop' in resp.text

def test_backoffice_role_user(pub):
    create_user(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/')
    assert 'Management' in resp.text
    assert not 'Forms Workshop' in resp.text
    assert not 'Workflows Workshop' in resp.text

    pub.cfg['admin-permissions'] = {'forms': [x.id for x in Role.select()]}
    pub.write_cfg()
    resp = app.get('/backoffice/')
    assert 'Management' in resp.text
    assert 'Forms Workshop' in resp.text
    assert not 'Workflows Workshop' in resp.text

    pub.cfg['admin-permissions'] = {'workflows': [x.id for x in Role.select()]}
    pub.write_cfg()
    resp = app.get('/backoffice/')
    assert 'Management' in resp.text
    assert not 'Forms Workshop' in resp.text
    assert 'Workflows Workshop' in resp.text

    # check role id int->str migration
    pub.cfg['admin-permissions'] = {'workflows': [int(x.id) for x in Role.select()]}
    pub.write_cfg()
    resp = app.get('/backoffice/')
    assert 'Management' in resp.text
    assert not 'Forms Workshop' in resp.text
    assert 'Workflows Workshop' in resp.text

def test_backoffice_forms(pub):
    create_superuser(pub)
    create_environment(pub, set_receiver=False)

    # 1st time with user not handling those forms
    app = login(get_app(pub))
    resp = app.get('/backoffice/')
    resp = resp.click('Management', index=0)
    resp = resp.follow()
    assert not 'Forms in your care' in resp.text
    assert re.findall('Other Forms.*form-title', resp.text)

    # 2nd time with user set as receiver of the forms
    create_environment(pub, set_receiver=True)
    app = login(get_app(pub))
    resp = app.get('/backoffice/')
    resp = resp.click('Management', index=0)
    resp = resp.follow()
    assert 'Forms in your care' in resp.text
    assert '17 open on 50' in resp.text

    # disable form, make sure it's still displayed
    formdef = FormDef.get_by_urlname('form-title')
    formdef.disabled = True
    formdef.store()
    resp = app.get('/backoffice/management/forms')
    assert 'form-title' in resp.text
    assert '17 open on 50' in resp.text

    formdef.disabled = False
    formdef.store()

    # add an extra status to workflow and move a few formdatas to it, they
    # should then be marked as open but not waiting for actions.
    workflow = Workflow.get_default_workflow()
    workflow.id = '2'
    st1 = workflow.add_status('Status1')
    jump = JumpWorkflowStatusItem()
    jump.id = '_jump'
    jump.timeout = 86400
    jump.status = 'finished'
    st1.items.append(jump)
    jump.parent = st1
    workflow.store()

    formdef = FormDef.get_by_urlname('form-title')
    formdef.workflow = workflow
    formdef.store()

    for i, formdata in enumerate(formdef.data_class().select(order_by='id')):
        if formdata.status == 'wf-new' and i % 2:
            formdata.status = 'wf-%s' % st1.id
            formdata.store()

    resp = app.get('/backoffice/')
    resp = resp.click('Management', index=0)
    resp = resp.follow()
    assert 'Forms in your care' in resp.text
    assert '9 open on 50' in resp.text

def test_backoffice_listing(pub):
    create_superuser(pub)
    create_environment(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/')
    assert resp.text.count('data-link') == 17

    # check status filter <select>
    resp = app.get('/backoffice/management/form-title/')
    resp.forms[0]['filter'] = 'all'
    resp = resp.forms[0].submit()
    if pub.is_using_postgresql():
        assert resp.text.count('data-link') == 20
    else:
        # not using sql -> no pagination
        assert resp.text.count('data-link') == 50

    # check status filter <select>
    resp = app.get('/backoffice/management/form-title/')
    resp.forms[0]['filter'] = 'done'
    resp = resp.forms[0].submit()
    if pub.is_using_postgresql():
        assert resp.text.count('data-link') == 20
        resp = resp.click('Next Page')
        assert resp.text.count('data-link') == 13
    else:
        assert resp.text.count('data-link') == 33

    # add an extra status to workflow and move a few formdatas to it, they
    # should then be marked as open but not waiting for actions.
    workflow = Workflow.get_default_workflow()
    workflow.id = '2'
    st1 = workflow.add_status('Status1')
    st1.id = 'plop'
    jump = JumpWorkflowStatusItem()
    jump.id = '_jump'
    jump.timeout = 86400
    jump.status = 'finished'
    st1.items.append(jump)
    jump.parent = st1
    workflow.store()

    formdef = FormDef.get_by_urlname('form-title')
    formdef.workflow = workflow
    formdef.store()

    for i, formdata in enumerate(formdef.data_class().select(order_by='id')):
        if formdata.status == 'wf-new' and i % 2:
            formdata.status = 'wf-%s' % st1.id
            formdata.store()

    resp = app.get('/backoffice/management/form-title/')
    assert resp.text.count('data-link') == 9
    resp.forms[0]['filter'] = 'pending'
    resp = resp.forms[0].submit()
    assert resp.text.count('data-link') == 17

    # check status forced as endpoints are not part of the "actionable" list.
    workflow = Workflow.get_default_workflow()
    workflow.id = '3'
    st1 = workflow.add_status('Status1')
    st1.id = 'plop'
    st1.forced_endpoint = False
    again = ChoiceWorkflowStatusItem()
    again.id = '_again'
    again.label = 'Again'
    again.by = ['_receiver']
    again.status = st1.id
    st1.items.append(again)
    again.parent = st1
    workflow.store()

    formdef = FormDef.get_by_urlname('form-title')
    formdef.workflow = workflow
    formdef.store()
    formdef.data_class().rebuild_security()

    for i, formdata in enumerate(formdef.data_class().select(order_by='id')):
        if formdata.status == 'wf-new' and i % 2:
            formdata.status = 'wf-%s' % st1.id
            formdata.store()

    resp = app.get('/backoffice/management/form-title/')
    assert resp.text.count('data-link') == 17
    resp.forms[0]['filter'] = 'pending'
    resp = resp.forms[0].submit()
    assert resp.text.count('data-link') == 17

    # mark status as an endpoint
    st1.forced_endpoint = True
    workflow.store()
    formdef.data_class().rebuild_security()

    resp = app.get('/backoffice/management/form-title/')
    assert resp.text.count('data-link') == 9
    resp.forms[0]['filter'] = 'pending'
    resp = resp.forms[0].submit()
    assert resp.text.count('data-link') == 9

def test_backoffice_listing_pagination(pub):
    if not pub.is_using_postgresql():
        pytest.skip('this requires SQL')
        return
    create_superuser(pub)
    create_environment(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/')
    assert resp.text.count('data-link') == 17

    resp = app.get('/backoffice/management/form-title/?limit=5')
    assert resp.text.count('data-link') == 5
    assert '<div id="page-links">' in resp.text

    resp = resp.click(re.compile('^2$')) # second page
    assert resp.text.count('data-link') == 5
    assert resp.form['offset'].value == '5'

    resp = resp.click(re.compile('^3$')) # third page
    assert resp.text.count('data-link') == 5
    assert resp.form['offset'].value == '10'

    resp = resp.click(re.compile('^4$')) # fourth page
    assert resp.text.count('data-link') == 2
    assert resp.form['offset'].value == '15'

    with pytest.raises(IndexError): # no fifth page
        resp = resp.click(re.compile('^5$'))

    resp = resp.click(re.compile('^10$')) # per page: 10
    assert resp.text.count('data-link') == 10

    resp = resp.click(re.compile('^20$')) # per page: 20
    assert resp.text.count('data-link') == 17

    # try an overbound offset
    resp = app.get('/backoffice/management/form-title/?limit=5&offset=30')
    resp = resp.follow()
    assert resp.form['offset'].value == '0'

def test_backoffice_listing_order(pub):
    if not pub.is_using_postgresql():
        pytest.skip('this requires SQL')
        return
    create_superuser(pub)
    create_environment(pub)
    formdef = FormDef.get_by_urlname('form-title')
    ids = []
    for i, formdata in enumerate(formdef.data_class().select(order_by='id')):
        if formdata.status in ('wf-new', 'wf-accepted'):
            ids.append(formdata.id)
            formdata.receipt_time = datetime.datetime(2015, 1, 1, 10, i).timetuple()
            # ordered with odd-numbered ids then even-numbered ids
            formdata.evolution[-1].time = datetime.datetime(2015, 2, 1, 10 + i % 2, i).timetuple()
            formdata.store()

    inversed_receipt_time_order = list(reversed([str(x) for x in sorted(ids)]))
    last_update_time_order = [str(x) for x in sorted(ids, key=lambda x: int(x) if int(x) % 2 else int(x)+1000)]

    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/')
    assert resp.text.count('data-link') == 17
    ids = [x.strip('/') for x in re.findall(r'data-link="(.*?)"', resp.text)]
    assert ids == inversed_receipt_time_order

    resp = app.get('/backoffice/management/form-title/?order_by=receipt_time')
    assert resp.text.count('data-link') == 17
    ids = [x.strip('/') for x in re.findall(r'data-link="(.*?)"', resp.text)]
    assert ids == list(reversed(inversed_receipt_time_order))

    resp = app.get('/backoffice/management/form-title/?order_by=last_update_time')
    assert resp.text.count('data-link') == 17
    ids = [x.strip('/') for x in re.findall(r'data-link="(.*?)"', resp.text)]
    assert ids == last_update_time_order

    resp = app.get('/backoffice/management/form-title/?order_by=-last_update_time')
    assert resp.text.count('data-link') == 17
    ids = [x.strip('/') for x in re.findall(r'data-link="(.*?)"', resp.text)]
    assert ids == list(reversed(last_update_time_order))

    if not pub.site_options.has_section('options'):
        pub.site_options.add_section('options')
        pub.site_options.set('options', 'default-sort-order', '-last_update_time')
        fd = open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w')
        pub.site_options.write(fd)
        fd.close()

    resp = app.get('/backoffice/management/form-title/')
    assert resp.text.count('data-link') == 17
    ids = [x.strip('/') for x in re.findall(r'data-link="(.*?)"', resp.text)]
    assert ids == list(reversed(last_update_time_order))

def test_backoffice_legacy_urls(pub):
    create_superuser(pub)
    create_environment(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/form-title/')
    assert resp.location == 'http://example.net/backoffice/management/form-title/'
    resp = app.get('/backoffice/form-title/1/')
    assert resp.location == 'http://example.net/backoffice/management/form-title/1/'
    resp = app.get('/backoffice/form-title/listing/?bla')
    assert resp.location == 'http://example.net/backoffice/management/form-title/listing/?bla'
    resp = app.get('/backoffice/form-title/listing/foo?bla')
    assert resp.location == 'http://example.net/backoffice/management/form-title/listing/foo?bla'
    resp = app.get('/backoffice/not-form-title/', status=404)

def test_backoffice_columns(pub):
    create_superuser(pub)
    create_environment(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/')
    assert resp.text.count('</th>') == 8 # six columns
    resp.forms[0]['1'].checked = False
    assert not 'submission_channel' in resp.forms[0].fields
    assert 'last_update_time' in resp.forms[0].fields
    resp = resp.forms[0].submit()
    assert resp.text.count('</th>') == 7 # fixe columns
    assert resp.text.count('data-link') == 17 # 17 rows
    assert resp.text.count('FOO BAR') == 0 # no field 1 column

def test_backoffice_channel_column(pub):
    if not pub.site_options.has_section('variables'):
        pub.site_options.add_section('variables')
    pub.site_options.set('variables', 'welco_url', 'xxx')
    fd = open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w')
    pub.site_options.write(fd)
    fd.close()

    create_superuser(pub)
    create_environment(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/')
    assert resp.text.count('</th>') == 8 # six columns
    resp.forms[0]['submission_channel'].checked = True
    resp = resp.forms[0].submit()
    assert resp.text.count('</th>') == 9 # seven columns
    assert resp.text.count('data-link') == 17 # 17 rows
    assert resp.text.count('<td>Web</td>') == 17

def test_backoffice_submission_agent_column(pub):
    user = create_user(pub)
    create_environment(pub)

    agent = pub.user_class(name='agent')
    agent.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/')
    assert not 'submission_agent' in resp.form.fields

    formdef = FormDef.get_by_urlname('form-title')
    formdef.backoffice_submission_roles = user.roles
    formdef.store()
    resp = app.get('/backoffice/management/form-title/')
    assert resp.text.count('</th>') == 8 # six columns
    resp.form['submission_agent'].checked = True
    resp = resp.form.submit()
    assert resp.text.count('</th>') == 9 # seven columns
    assert resp.text.count('data-link') == 17 # 17 rows
    assert not '>agent<' in resp.text
    resp = resp.click('Export as CSV File')
    assert len(resp.text.splitlines()) == 18 # 17 + header line
    assert not ',agent,' in resp.text

    for formdata in formdef.data_class().select():
        formdata.submission_context = {'agent_id': agent.id}
        formdata.store()

    resp = app.get('/backoffice/management/form-title/')
    resp.form['submission_agent'].checked = True
    resp = resp.form.submit()
    assert resp.text.count('>agent<') == 17

    resp = resp.click('Export as CSV File')
    assert len(resp.text.splitlines()) == 18 # 17 + header line
    assert resp.text.count(',agent,') == 17

def test_backoffice_image_column(pub):
    create_superuser(pub)
    create_environment(pub)
    formdef = FormDef.get_by_urlname('form-title')
    formdef.fields.append(fields.FileField(id='4', label='file field', type='file',
                display_locations=['validation', 'summary', 'listings']))
    formdef.store()

    upload = PicklableUpload('test.jpeg', 'image/jpeg')
    jpg = open(os.path.join(os.path.dirname(__file__), 'image-with-gps-data.jpeg'), 'rb').read()
    upload.receive([jpg])
    for formdata in formdef.data_class().select(lambda x: x.status == 'wf-new'):
        formdata.data['4'] = upload
        formdata.store()
    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/')
    assert 'download?f=4&thumbnail=1' not in resp.text

def test_backoffice_filter(pub):
    create_superuser(pub)
    create_environment(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/')
    assert resp.forms[0]['filter-status'].checked == True
    resp.forms[0]['filter-status'].checked = False
    resp.forms[0]['filter-2'].checked = True
    resp = resp.forms[0].submit()
    assert '<select name="filter">' not in resp.text

    resp.forms[0]['filter-2-value'] = 'baz'
    resp = resp.forms[0].submit()
    assert resp.text.count('<td>baz</td>') == 8
    assert resp.text.count('<td>foo</td>') == 0
    assert resp.text.count('<td>bar</td>') == 0

    resp.forms[0]['filter-start'].checked = True
    resp = resp.forms[0].submit()
    resp.forms[0]['filter-start-value'] = datetime.datetime(2015, 2, 1).strftime('%Y-%m-%d')
    resp = resp.forms[0].submit()
    assert resp.text.count('<td>baz</td>') == 0
    resp.forms[0]['filter-start-value'] = datetime.datetime(2014, 2, 1).strftime('%Y-%m-%d')
    resp = resp.forms[0].submit()
    assert resp.text.count('<td>baz</td>') == 8

    # check there's no crash on invalid date values
    resp.forms[0]['filter-start-value'] = 'whatever'
    resp = resp.forms[0].submit()
    assert resp.text.count('<td>baz</td>') == 8

    # check two-digit years are handled correctly
    resp.forms[0]['filter-start-value'] = datetime.datetime(2014, 2, 1).strftime('%y-%m-%d')
    resp = resp.forms[0].submit()
    assert resp.text.count('<td>baz</td>') == 8

    # check it's also ok for end filter
    resp.forms[0]['filter-end'].checked = True
    resp = resp.forms[0].submit()
    resp.forms[0]['filter-end-value'] = datetime.datetime(2014, 2, 2).strftime('%y-%m-%d')
    resp = resp.forms[0].submit()
    assert resp.text.count('<td>baz</td>') == 0

def test_backoffice_default_filter(pub):
    create_superuser(pub)
    create_environment(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/')
    assert not 'filter-2-value' in resp.form.fields

    formdef = FormDef.get_by_urlname('form-title')
    formdef.fields[1].in_filters = True
    formdef.store()
    resp = app.get('/backoffice/management/form-title/')
    assert 'filter-2-value' in resp.form.fields

    # same check for items field
    formdef.fields.append(
            fields.ItemsField(id='4', label='4th field', type='items', items=['foo', 'bar', 'baz']))
    formdef.store()
    resp = app.get('/backoffice/management/form-title/')
    assert not 'filter-4-value' in resp.form.fields

    formdef.fields[-1].in_filters = True
    formdef.store()
    resp = app.get('/backoffice/management/form-title/')
    assert 'filter-4-value' in resp.form.fields

def test_backoffice_bool_filter(pub):
    create_superuser(pub)
    create_environment(pub)
    formdef = FormDef.get_by_urlname('form-title')
    formdef.fields.append(fields.BoolField(id='4', label='4th field',
        type='bool',
        display_locations=['validation', 'summary', 'listings']))
    formdef.store()

    for i, formdata in enumerate(formdef.data_class().select()):
        formdata.data['4'] = bool(i%2)
        formdata.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/')
    resp.form['filter-4'].checked = True
    resp = resp.form.submit()

    assert resp.form['filter-4-value'].value == ''

    resp.form['filter-4-value'].value = 'true'
    resp = resp.form.submit()
    assert resp.text.count('<td>Yes</td>') > 0
    assert resp.text.count('<td>No</td>') == 0

    resp.form['filter-4-value'].value = 'false'
    resp = resp.form.submit()
    assert resp.text.count('<td>Yes</td>') == 0
    assert resp.text.count('<td>No</td>') > 0

def test_backoffice_items_filter(pub):
    create_superuser(pub)
    create_environment(pub)
    formdef = FormDef.get_by_urlname('form-title')
    formdef.fields.append(fields.ItemsField(id='4', label='4th field', type='items',
        items=['a', 'b', 'c', 'd'],
        display_locations=['validation', 'summary', 'listings']))
    formdef.store()

    for i, formdata in enumerate(formdef.data_class().select()):
        if i%4 == 0:
            formdata.data['4'] = ['a', 'b']
            formdata.data['4_display'] = 'a, b'
        elif i%4 == 1:
            formdata.data['4'] = ['b', 'd']
            formdata.data['4_display'] = 'b, d'
        elif i%4 == 2:
            formdata.data['4'] = ['a']
            formdata.data['4_display'] = 'a'
        formdata.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/')
    resp.form['filter-4'].checked = True
    resp = resp.form.submit()

    assert resp.form['filter-4-value'].value == ''

    resp.form['filter-4-value'].value = 'a'
    resp = resp.form.submit()
    assert resp.text.count('<td>a, b</td>') > 0
    assert resp.text.count('<td>a</td>') > 0
    assert resp.text.count('<td>b, d</td>') == 0

    resp.form['filter-4-value'].value = 'b'
    resp = resp.form.submit()
    assert resp.text.count('<td>a, b</td>') > 0
    assert resp.text.count('<td>a</td>') == 0
    assert resp.text.count('<td>b, d</td>') > 0

    resp.form['filter-4-value'].value = 'c'
    resp = resp.form.submit()
    assert resp.text.count('<td>a, b</td>') == 0
    assert resp.text.count('<td>a</td>') == 0
    assert resp.text.count('<td>b, d</td>') == 0
    assert resp.text.count('data-link') == 0 # no rows

def test_backoffice_csv(pub):
    create_superuser(pub)
    create_environment(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/')
    resp = resp.click('Export as CSV File')
    assert resp.headers['content-type'].startswith('text/')
    assert len(resp.text.splitlines()) == 18 # 17 + header line
    assert len(resp.text.splitlines()[0].split(',')) == 7

    formdef = FormDef.get_by_urlname('form-title')
    formdef.fields[-1].display_locations = ['validation', 'summary', 'listings']
    formdef.store()
    resp = app.get('/backoffice/management/form-title/')
    resp = resp.click('Export as CSV File')
    assert len(resp.text.splitlines()[0].split(',')) == 9

    # check item fields with datasources get two columns (id & text)
    assert resp.text.splitlines()[0].split(',')[6] == '3rd field'
    assert resp.text.splitlines()[0].split(',')[7] == '' # 3rd field, continue
    assert resp.text.splitlines()[1].split(',')[6] == 'A'
    assert resp.text.splitlines()[1].split(',')[7] == 'aa'

    resp = app.get('/backoffice/management/form-title/')
    resp.forms[0]['filter'] = 'all'
    resp = resp.forms[0].submit()
    resp_csv = resp.click('Export as CSV File')
    assert len(resp_csv.text.splitlines()) == 51

    # test status filter
    resp.forms[0]['filter'] = 'pending'
    resp.forms[0]['filter-2'].checked = True
    resp = resp.forms[0].submit()
    resp.forms[0]['filter-2-value'] = 'baz'
    resp = resp.forms[0].submit()
    resp_csv = resp.click('Export as CSV File')
    assert len(resp_csv.text.splitlines()) == 9

    # test criteria filters
    resp.forms[0]['filter-start'].checked = True
    resp = resp.forms[0].submit()
    resp.forms[0]['filter-start-value'] = datetime.datetime(2015, 2, 1).strftime('%Y-%m-%d')
    resp = resp.forms[0].submit()
    resp_csv = resp.click('Export as CSV File')
    assert len(resp_csv.text.splitlines()) == 1

    resp.forms[0]['filter-start-value'] = datetime.datetime(2014, 2, 1).strftime('%Y-%m-%d')
    resp = resp.forms[0].submit()
    resp_csv = resp.click('Export as CSV File')
    assert len(resp_csv.text.splitlines()) == 9
    assert 'Created' in resp_csv.text.splitlines()[0]

    # test column selection
    resp.form['time'].checked = False
    resp = resp.forms[0].submit()
    resp_csv = resp.click('Export as CSV File')
    assert 'Created' not in resp_csv.text.splitlines()[0]

def test_backoffice_export_long_listings(pub):
    create_superuser(pub)
    create_environment(pub)
    formdef = FormDef.get_by_urlname('form-title')
    for i in range(100):
        formdata = formdef.data_class()()
        formdata.just_created()
        formdata.receipt_time = datetime.datetime(2015, 1, 1).timetuple()
        formdata.data = {'1': 'BAZ BAZ %d' % i}
        formdata.jump_status('new')
        formdata.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/')
    resp = resp.click('Export as CSV File')
    assert resp.location.startswith('http://example.net/backoffice/management/form-title/export?job=')
    resp = resp.follow()
    assert 'completed' in resp.text
    resp = resp.click('Download Export')
    resp_lines = resp.text.splitlines()
    assert resp_lines[0] == 'Number,Created,Last Modified,User Label,1st field,2nd field,Status'
    assert len(resp_lines) == 118
    assert resp_lines[1].split(',')[1].startswith(
            time.strftime('%Y-%m-%d', formdata.receipt_time))
    assert resp_lines[1].split(',')[2].startswith(
            time.strftime('%Y-%m-%d', formdata.last_update_time))

    resp = app.get('/backoffice/management/form-title/')
    resp = resp.click('Export a Spreadsheet')
    assert resp.location.startswith('http://example.net/backoffice/management/form-title/export?job=')
    job_id = urlparse.parse_qs(urlparse.urlparse(resp.location).query)['job'][0]
    resp = resp.follow()
    assert 'completed' in resp.text
    resp = resp.click('Download Export')
    assert resp.content_type == 'application/vnd.oasis.opendocument.spreadsheet'

    # check afterjob ajax call
    status_resp = app.get('/afterjobs/' + job_id)
    assert status_resp.text == 'completed|completed'

    # check error handling
    app.get('/afterjobs/whatever', status=404)

def test_backoffice_csv_export_channel(pub):
    if not pub.site_options.has_section('variables'):
        pub.site_options.add_section('variables')
    pub.site_options.set('variables', 'welco_url', 'xxx')
    fd = open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w')
    pub.site_options.write(fd)
    fd.close()

    create_superuser(pub)
    create_environment(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/')
    resp_csv = resp.click('Export as CSV File')
    assert 'Channel' not in resp_csv.text.splitlines()[0]

    # add submission channel column
    resp.form['submission_channel'].checked = True
    resp = resp.forms[0].submit()
    resp_csv = resp.click('Export as CSV File')
    assert resp_csv.text.splitlines()[0].split(',')[1] == 'Channel'
    assert resp_csv.text.splitlines()[1].split(',')[1] == 'Web'

def test_backoffice_csv_export_anonymised(pub):
    fd = open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w')
    pub.site_options.write(fd)
    fd.close()

    create_superuser(pub)
    create_environment(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/')
    resp_csv = resp.click('Export as CSV File')
    assert resp_csv.text.splitlines()[0].split(',')[-1] != 'Anonymised'

    # add anonymised column
    resp.form['anonymised'].checked = True
    resp = resp.forms[0].submit()
    resp_csv = resp.click('Export as CSV File')
    assert resp_csv.text.splitlines()[0].split(',')[-1] == 'Anonymised'
    assert resp_csv.text.splitlines()[1].split(',')[-1] == 'No'

def test_backoffice_ods(pub):
    create_superuser(pub)
    create_environment(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/')
    resp = resp.click('Export a Spreadsheet')
    assert resp.headers['content-type'] == 'application/vnd.oasis.opendocument.spreadsheet'
    assert 'filename=form-title.ods' in resp.headers['content-disposition']
    assert resp.body[:2] == b'PK' # ods has a zip container

    formdef = FormDef.get_by_urlname('form-title')
    formdef.fields.append(fields.FileField(id='4', label='file field', type='file',
        display_locations=['validation', 'summary', 'listings']))
    formdef.fields.append(fields.DateField(id='5', label='date field', type='date',
        display_locations=['validation', 'summary', 'listings']))
    formdef.fields.append(fields.StringField(id='6', label='number field', type='string',
        display_locations=['validation', 'summary', 'listings']))
    formdef.fields.append(fields.StringField(id='7', label='phone field', type='string',
        display_locations=['validation', 'summary', 'listings']))
    formdef.fields.append(fields.DateField(id='8', label='very old field', type='date',
        display_locations=['validation', 'summary', 'listings']))
    formdef.fields.append(fields.StringField(id='9', label='string field', type='string',
        display_locations=['validation', 'summary', 'listings']))
    formdef.store()

    formdata = formdef.data_class().select(lambda x: x.status == 'wf-new')[0]
    formdata.data['4'] = PicklableUpload('/foo/bar', content_type='text/plain')
    formdata.data['4'].receive([b'hello world'])
    formdata.data['5'] = time.strptime('2015-05-12', '%Y-%m-%d')
    formdata.data['6'] = '12345'
    formdata.data['7'] = '0102030405'
    formdata.data['8'] = time.strptime('1871-03-18', '%Y-%m-%d')
    formdata.data['9'] = 'plop\npl\x1dop'  # with control characters
    formdata.store()

    resp = app.get('/backoffice/management/form-title/')
    resp = resp.click('Export a Spreadsheet')
    assert resp.headers['content-type'] == 'application/vnd.oasis.opendocument.spreadsheet'
    assert 'filename=form-title.ods' in resp.headers['content-disposition']
    assert resp.body[:2] == b'PK' # ods has a zip container

    zipf = zipfile.ZipFile(BytesIO(resp.body))
    ods_sheet = ET.parse(zipf.open('content.xml'))
    # check the ods contains a link to the document
    elem = ods_sheet.findall('.//{%s}a' % ods.NS['text'])[0]
    assert elem.attrib['{%s}href' % ods.NS['xlink']] == 'http://example.net/backoffice/management/form-title/%s/files/4/bar' % formdata.id
    resp = app.get(elem.attrib['{%s}href' % ods.NS['xlink']])
    assert resp.text == 'hello world'

    all_texts = [x.text for x in ods_sheet.findall('.//{%s}table-row//{%s}p' % (ods.NS['table'], ods.NS['text']))]
    created_column = all_texts.index('Created')
    date_column = all_texts.index('date field')
    number_column = all_texts.index('number field')
    phone_column = all_texts.index('phone field')
    old_column = all_texts.index('very old field')
    string_column = all_texts.index('string field')

    for row in ods_sheet.findall('.//{%s}table-row' % ods.NS['table']):
        if row.findall('.//{%s}table-cell/{%s}p' % (
                ods.NS['table'], ods.NS['text']))[0].text == formdata.get_display_id():
            break
    else:
        assert False, 'failed to find data row'

    assert row.findall('.//{%s}table-cell' % ods.NS['table'])[created_column].attrib[
            '{%s}value-type' % ods.NS['office']] == 'date'
    assert row.findall('.//{%s}table-cell' % ods.NS['table'])[created_column].attrib[
            '{%s}style-name' % ods.NS['table']] == 'DateTime'
    assert row.findall('.//{%s}table-cell' % ods.NS['table'])[date_column].attrib[
            '{%s}value-type' % ods.NS['office']] == 'date'
    assert row.findall('.//{%s}table-cell' % ods.NS['table'])[date_column].attrib[
            '{%s}style-name' % ods.NS['table']] == 'Date'
    assert row.findall('.//{%s}table-cell' % ods.NS['table'])[number_column].attrib[
            '{%s}value-type' % ods.NS['office']] == 'float'
    assert row.findall('.//{%s}table-cell' % ods.NS['table'])[number_column].attrib[
            '{%s}value' % ods.NS['office']] == '12345'
    assert row.findall('.//{%s}table-cell' % ods.NS['table'])[phone_column].attrib[
            '{%s}value-type' % ods.NS['office']] == 'string'
    assert row.findall('.//{%s}table-cell' % ods.NS['table'])[old_column].attrib[
            '{%s}value-type' % ods.NS['office']] == 'date'
    assert row.findall('.//{%s}table-cell' % ods.NS['table'])[old_column].attrib[
            '{%s}date-value' % ods.NS['office']] == '1871-03-18'
    assert row.findall('.//{%s}table-cell' % ods.NS['table'])[
            string_column].find('{%s}p' % ods.NS['text']).text == 'plop\nplop'

@pytest.mark.skipif('xlwt is None')
def test_backoffice_xls(pub):
    create_superuser(pub)
    create_environment(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/')
    assert not 'Excel Export' in resp.text

    if not pub.site_options.has_section('options'):
        pub.site_options.add_section('options')
        pub.site_options.set('options', 'legacy-excel-export', 'true')
        fd = open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w')
        pub.site_options.write(fd)
        fd.close()

    resp = app.get('/backoffice/management/form-title/')
    resp = resp.click('Excel Export')
    assert resp.headers['content-type'].startswith('application/vnd.ms-excel')

def test_backoffice_statistics(pub):
    create_superuser(pub)
    create_environment(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/')
    resp = resp.click('Statistics')
    assert 'Total number of records: 50' in resp.text
    assert 'New: 17' in resp.text
    assert 'Finished: 33' in resp.text
    assert re.findall('foo.*26.*bar.*26.*bar.*48', resp.text) # percentages
    assert 'Resolution time' in resp.text
    assert 'To Status &quot;New&quot;' in resp.text
    assert 'To Status &quot;Finished&quot;' in resp.text
    assert not '<h2>Filters</h2>' in resp.text

    resp.forms[0]['filter-end-value'] = '2013-01-01'
    resp = resp.forms[0].submit()
    assert 'Total number of records: 0' in resp.text
    assert '<h2>Filters</h2>' in resp.text
    assert 'End: 2013-01-01' in resp.text

def test_backoffice_multi_actions(pub):
    create_superuser(pub)
    create_environment(pub)
    formdef = FormDef.get_by_urlname('form-title')

    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/')
    assert not 'id="multi-actions"' in resp.text

    workflow = Workflow.get_default_workflow()
    workflow.id = '2'
    action = workflow.add_global_action('FOOBAR')
    jump = action.append_item('jump')
    jump.status = 'finished'
    trigger = action.triggers[0]
    trigger.roles = ['whatever']

    workflow.store()
    formdef.workflow_id = workflow.id
    formdef.store()

    resp = app.get('/backoffice/management/form-title/')
    assert not 'id="multi-actions"' in resp.text

    trigger.roles = [x.id for x in Role.select() if x.name == 'foobar']
    workflow.store()

    resp = app.get('/backoffice/management/form-title/')
    assert 'id="multi-actions"' in resp.text
    ids = []
    for checkbox in resp.forms[0].fields['select[]'][1:6]:
        ids.append(checkbox._value)
        checkbox.checked = True
    resp = resp.forms[0].submit('button-action-1')
    assert '?job=' in resp.location
    resp = resp.follow()
    assert 'Executing task &quot;FOOBAR&quot; on forms' in resp.text
    assert '>completed<' in resp.text
    for id in ids:
        assert formdef.data_class().get(id).status == 'wf-finished'

    draft_ids = [x.id for x in formdef.data_class().select() if x.status == 'draft']
    resp = app.get('/backoffice/management/form-title/')
    assert resp.forms[0].fields['select[]'][0]._value == '_all'
    resp.forms[0].fields['select[]'][0].checked = True
    resp = resp.forms[0].submit('button-action-1')
    for formdata in formdef.data_class().select():
        if formdata.id in draft_ids:
            assert formdata.status == 'draft'
        else:
            assert formdata.status == 'wf-finished'

    for formdata in formdef.data_class().select():
        if formdata.status != 'draft':
            formdata.jump_status('new')
            formdata.store()

    # action for other role
    action2 = workflow.add_global_action('OTHER ACTION')
    jump = action2.append_item('jump')
    jump.status = 'accepted'
    trigger = action2.triggers[0]
    trigger.roles = ['whatever']
    workflow.store()
    resp = app.get('/backoffice/management/form-title/')
    assert 'id="multi-actions"' in resp.text
    assert not 'OTHER ACTION' in resp.text

    # action for function
    trigger.roles = ['_foobar']
    workflow.store()

    resp = app.get('/backoffice/management/form-title/')
    assert 'id="multi-actions"' in resp.text
    assert 'OTHER ACTION' not in resp.text

    workflow.roles['_foobar'] = 'Foobar'
    workflow.store()

    resp = app.get('/backoffice/management/form-title/')
    assert 'id="multi-actions"' in resp.text
    assert 'OTHER ACTION' in resp.text

    # alter some formdata to simulate dispatch action
    stable_ids = []
    for checkbox in resp.forms[0].fields['select[]'][1:6]:
        formdata = formdef.data_class().get(checkbox._value)
        formdata.workflow_roles = {'_foobar': formdef.workflow_roles['_receiver']}
        formdata.store()
        stable_ids.append(formdata.id)

    resp = app.get('/backoffice/management/form-title/')
    assert 'OTHER ACTION' in resp.text

    resp.forms[0].fields['select[]'][0].checked = True  # _all
    resp = resp.forms[0].submit('button-action-2')
    assert '?job=' in resp.location
    resp = resp.follow()
    assert 'Executing task &quot;OTHER ACTION&quot; on forms' in resp.text
    # check only dispatched formdata have been moved by global action executed
    # on all formdatas
    for formdata in formdef.data_class().select():
        if formdata.id in draft_ids:
            assert formdata.status == 'draft'
        elif formdata.id in stable_ids:
            assert formdata.status == 'wf-accepted'
        else:
            assert formdata.status != 'wf-accepted'

def test_backoffice_statistics_status_filter(pub):
    create_superuser(pub)
    create_environment(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/')
    resp = resp.click('Statistics')
    assert 'filter' not in resp.forms[0].fields # status is not displayed by default
    assert not '<h2>Filters</h2>' in resp.text

    # add 'status' as a filter
    resp.forms[0]['filter-status'].checked = True
    resp = resp.forms[0].submit()
    assert 'filter' in resp.forms[0].fields
    assert not '<h2>Filters</h2>' in resp.text

    assert resp.forms[0]['filter'].value == 'all'
    resp.forms[0]['filter'].value = 'pending'
    resp = resp.forms[0].submit()
    assert 'Total number of records: 17' in resp.text
    assert '<h2>Filters</h2>' in resp.text
    assert 'Status: Pending' in resp.text

    resp.forms[0]['filter'].value = 'done'
    resp = resp.forms[0].submit()
    assert 'Total number of records: 33' in resp.text
    assert '<h2>Filters</h2>' in resp.text
    assert 'Status: Done' in resp.text

    resp.forms[0]['filter'].value = 'rejected'
    resp = resp.forms[0].submit()
    assert 'Total number of records: 0' in resp.text
    assert '<h2>Filters</h2>' in resp.text
    assert 'Status: Rejected' in resp.text

    resp.forms[0]['filter'].value = 'all'
    resp = resp.forms[0].submit()
    assert 'Total number of records: 50' in resp.text

def test_backoffice_statistics_status_select(pub):
    create_superuser(pub)
    create_environment(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/')
    resp = resp.click('Statistics')
    assert not 'filter-2-value' in resp.form.fields

    resp.forms[0]['filter-2'].checked = True
    resp = resp.forms[0].submit()
    resp.forms[0]['filter-2-value'].value = 'bar'
    resp = resp.forms[0].submit()
    assert 'Total number of records: 13' in resp.text

    resp.forms[0]['filter-2-value'].value = 'baz'
    resp = resp.forms[0].submit()
    assert 'Total number of records: 24' in resp.text

    resp.forms[0]['filter-2-value'].value = 'foo'
    resp = resp.forms[0].submit()
    assert 'Total number of records: 13' in resp.text
    assert '<h2>Filters</h2>' in resp.text
    assert '2nd field: foo' in resp.text

    # check it's also possible to get back to the complete list
    resp.forms[0]['filter-2-value'].value = ''
    resp = resp.forms[0].submit()
    assert 'Total number of records: 50' in resp.text

    # check it also works with item fields with a data source
    resp = app.get('/backoffice/management/form-title/')
    resp = resp.click('Statistics')
    resp.forms[0]['filter-3'].checked = True
    resp = resp.forms[0].submit()
    resp.forms[0]['filter-3-value'].value = 'A'
    resp = resp.forms[0].submit()
    assert 'Total number of records: 13' in resp.text
    assert '<h2>Filters</h2>' in resp.text
    assert '3rd field: aa' in resp.text

    # set field to be displayed by default in filters
    formdef = FormDef.get_by_urlname('form-title')
    formdef.fields[1].in_filters = True
    formdef.store()
    resp = app.get('/backoffice/management/form-title/')
    resp = resp.click('Statistics')
    assert 'filter-2-value' in resp.form.fields

def test_backoffice_map(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.text, re.DOTALL)[0].count('<tr') == 17

    # check there's no link to map the sidebar
    assert not 'Plot on a Map' in resp.text

    formdef = FormDef.get_by_urlname('form-title')
    formdef.geolocations = {'base': 'Geolocafoobar'}
    formdef.store()
    number31.geolocations = {'base': {'lat': 48.83, 'lon': 2.32}}
    number31.store()

    resp = app.get('/backoffice/management/form-title/')
    assert 'Plot on a Map' in resp.text
    resp = resp.click('Plot on a Map')
    assert 'data-geojson-url' in resp.text
    assert 'tiles.entrouvert.org/' in resp.text

    if not pub.site_options.has_section('options'):
        pub.site_options.add_section('options')

    pub.site_options.set('options', 'map-tile-urltemplate', 'https://{s}.tile.example.net/{z}/{x}/{y}.png')
    fd = open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w')
    pub.site_options.write(fd)
    fd.close()

    resp = app.get('/backoffice/management/form-title/')
    resp = resp.click('Plot on a Map')
    assert 'tile.example.net/' in resp.text

def test_backoffice_geojson(pub):
    user = create_user(pub)
    create_environment(pub)
    formdef = FormDef.get_by_urlname('form-title')
    formdef.fields.append(fields.MapField(id='4', label='4th field', type='map'))
    formdef.fields.append(fields.MapField(id='5', label='5th field', type='string'))
    form_class = formdef.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/geojson', status=404)

    formdef = FormDef.get_by_urlname('form-title')
    formdef.geolocations = {'base': 'Geolocafoobar'}
    formdef.store()
    number31 = formdef.data_class().get(number31.id)
    number31.geolocations = {'base': {'lat': 48.83, 'lon': 2.32}}
    number31.store()

    resp = app.get('/backoffice/management/form-title/geojson?1=on&4=on&5=on')
    assert len(resp.json['features']) == 1
    assert resp.json['features'][0]['geometry']['coordinates'] == [2.32, 48.83]
    assert 'status_colour' in resp.json['features'][0]['properties']
    assert resp.json['features'][0]['properties']['status_name'] == 'New'
    assert resp.json['features'][0]['properties']['status_colour'] == '#66FF00'
    assert resp.json['features'][0]['properties']['view_label'] == 'View'
    assert 'display_fields' in resp.json['features'][0]['properties']
    assert len(resp.json['features'][0]['properties']['display_fields']) == 1

    resp = app.get('/backoffice/management/form-title/geojson?filter=pending&filter-status=on')
    assert len(resp.json['features']) == 1

    resp = app.get('/backoffice/management/form-title/geojson?filter=done&filter-status=on')
    assert len(resp.json['features']) == 0


def test_backoffice_handling(pub):
    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.text, re.DOTALL)[0].count('<tr') == 17

    # check sidebar links are ok
    assert 'Statistics' in resp.text
    assert 'Export' in resp.text
    app.get('/backoffice/management/form-title/stats', status=200)
    app.get('/backoffice/management/form-title/csv', status=200)
    app.get('/backoffice/management/form-title/ods', status=200)
    app.get('/backoffice/management/form-title/json', status=200)

    # click on a formdata
    resp = resp.click(href='%s/' % number31.id)
    assert (' with the number %s.' % number31.get_display_id()) in resp.text
    resp.forms[0]['comment'] = 'HELLO WORLD'
    resp = resp.forms[0].submit('button_accept')
    resp = resp.follow()
    assert FormDef.get_by_urlname('form-title').data_class().get(number31.id).status == 'wf-accepted'
    assert 'HELLO WORLD' in resp.text

def test_backoffice_handling_global_action(pub):
    create_user(pub)
    create_environment(pub)

    formdef = FormDef()
    formdef.name = 'test global action'
    formdef.fields = []

    workflow = Workflow.get_default_workflow()
    workflow.id = '2'
    action = workflow.add_global_action('FOOBAR')
    register_comment = action.append_item('register-comment')
    register_comment.comment = 'HELLO WORLD GLOBAL ACTION'
    jump = action.append_item('jump')
    jump.status = 'finished'
    trigger = action.triggers[0]
    trigger.roles = [x.id for x in Role.select() if x.name == 'foobar']

    workflow.store()
    formdef.workflow_id = workflow.id
    formdef.workflow_roles = {'_receiver': 1}
    formdef.store()

    formdata = formdef.data_class()()
    formdata.just_created()
    formdata.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/management/%s/%s/' % (formdef.url_name, formdata.id))
    assert 'button-action-1' in resp.form.fields
    resp = resp.form.submit('button-action-1')

    resp = app.get('/backoffice/management/%s/%s/' % (formdef.url_name, formdata.id))
    assert 'HELLO WORLD GLOBAL ACTION' in resp.text
    assert formdef.data_class().get(formdata.id).status == 'wf-finished'

def test_backoffice_global_remove_action(pub):
    create_user(pub)
    create_environment(pub)

    formdef = FormDef()
    formdef.name = 'test global remove'
    formdef.fields = []

    workflow = Workflow.get_default_workflow()
    workflow.id = '2'
    action = workflow.add_global_action('FOOBAR')
    remove = action.append_item('remove')
    trigger = action.triggers[0]
    trigger.roles = [x.id for x in Role.select() if x.name == 'foobar']

    workflow.store()
    formdef.workflow_id = workflow.id
    formdef.workflow_roles = {'_receiver': 1}
    formdef.store()

    formdata = formdef.data_class()()
    formdata.just_created()
    formdata.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/management/%s/%s/' % (formdef.url_name, formdata.id))
    assert 'remove' in resp.text
    assert 'button-action-1' in resp.form.fields
    resp = resp.form.submit('button-action-1')
    resp = resp.follow()
    assert resp.request.url == 'http://example.net/backoffice/management/test-global-remove/'
    assert 'The form has been deleted.' in resp.text

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.text, re.DOTALL)[0].count('<tr') == 17

    # click on a formdata
    resp = resp.click(href='%s/' % number31.id)
    assert (' with the number %s.' % number31.get_display_id()) in resp.text

    # check there's nothing in the sidebar
    assert not 'Channel' in resp.text

    number31.submission_channel = 'mail'
    number31.user_id = user.id
    number31.submission_context = {
            'mail_url': 'http://www.example.com/test.pdf',
            'thumbnail_url': 'http://www.example.com/thumbnail.png',
            'comments': 'test_backoffice_submission_context',
            'summary_url': 'http://www.example.com/summary',
            'agent_id': user.id,
    }
    number31.store()
    resp = app.get('/backoffice/management/form-title/')
    resp = resp.click(href='%s/' % number31.id)
    assert 'Channel' in resp.text
    assert 'http://www.example.com/thumbnail.png' in resp.text
    assert 'http://www.example.com/test.pdf' in resp.text
    assert 'Associated User' in resp.text
    assert 'test_backoffice_submission_context' in resp.text
    assert 'http://www.example.com/summary' in resp.text
    assert 'by %s' % user.get_display_name() in resp.text

def test_backoffice_geolocation_info(pub):
    user = create_user(pub)
    create_environment(pub)
    formdef = FormDef.get_by_urlname('form-title')
    formdef.geolocations = {'base': 'Geolocafoobar'}
    formdef.store()
    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.text, re.DOTALL)[0].count('<tr') == 17

    # click on a formdata
    resp = resp.click(href='%s/' % number31.id)
    assert (' with the number %s.' % number31.get_display_id()) in resp.text

    # check there's nothing in the sidebar
    assert not 'Geolocation' in resp.text

    number31.geolocations = {'base': {'lat': 48.83, 'lon': 2.32}}
    number31.store()
    resp = app.get('/backoffice/management/form-title/')
    resp = resp.click(href='%s/' % number31.id)
    assert 'Geolocafoobar' in resp.text
    assert 'class="qommon-map"' in resp.text
    assert 'data-init-lng="2.32"' in resp.text
    assert 'data-init-lat="48.83' in resp.text

def test_backoffice_info_text(pub):
    create_user(pub)
    create_environment(pub)
    formdef = FormDef.get_by_urlname('form-title')
    form_class = formdef.data_class()

    number31 = [x for x in form_class.select() if x.data['1'] == 'FOO BAR 30'][0]

    # attach a custom workflow
    workflow = Workflow(name='info texts')
    st1 = workflow.add_status('Status1', number31.status.split('-')[1])

    commentable = CommentableWorkflowStatusItem()
    commentable.id = '_commentable'
    commentable.by = ['_submitter', '_receiver']
    commentable.button_label = 'CLICK ME!'
    st1.items.append(commentable)
    commentable.parent = st1

    commentable2 = CommentableWorkflowStatusItem()
    commentable2.id = '_commentable2'
    commentable2.by = ['_submitter']
    commentable2.button_label = 'CLICK ME2!'
    st1.items.append(commentable2)
    commentable2.parent = st1

    workflow.store()

    formdef.workflow_id = workflow.id
    formdef.store()

    app = login(get_app(pub))

    resp = app.get('/backoffice/management/form-title/%s/' % number31.id)
    assert (' with the number %s.' % number31.get_display_id()) in resp.text
    assert 'CLICK ME!' in resp.text
    assert not 'CLICK ME2!' in resp.text
    assert not 'backoffice-description' in resp.text

    # add an info text to the status
    st1.backoffice_info_text = '<p>Foo</p>'
    workflow.store()
    resp = app.get('/backoffice/management/form-title/%s/' % number31.id)
    assert 'backoffice-description' in resp.text
    assert '<p>Foo</p>' in resp.text

    # add an info text to the button
    commentable.backoffice_info_text = '<p>Bar</p>'
    workflow.store()
    resp = app.get('/backoffice/management/form-title/%s/' % number31.id)
    assert 'backoffice-description' in resp.text
    assert '<p>Foo</p>' in resp.text
    assert '<p>Bar</p>' in resp.text

    # info text is not visible if form is locked
    second_user = pub.user_class(name='foobar')
    second_user.roles = Role.keys()
    second_user.store()
    account = PasswordAccount(id='foobar')
    account.set_password('foobar')
    account.user_id = second_user.id
    account.store()
    app2 = login(get_app(pub), username='foobar', password='foobar')
    resp = app2.get('/backoffice/management/form-title/%s/' % number31.id)
    assert 'Be warned forms of this user are also being looked' in resp.text
    assert not 'backoffice-description' in resp.text
    assert not 'CLICK ME!' in resp.text
    assert not 'CLICK ME2!' in resp.text

    # remove info text from the status
    st1.backoffice_info_text = None
    workflow.store()
    resp = app.get('/backoffice/management/form-title/%s/' % number31.id)
    assert 'backoffice-description' in resp.text
    assert not '<p>Foo</p>' in resp.text
    assert '<p>Bar</p>' in resp.text

    # add info text to second button
    commentable2.backoffice_info_text = '<p>Baz</p>'
    workflow.store()
    resp = app.get('/backoffice/management/form-title/%s/' % number31.id)
    assert 'backoffice-description' in resp.text
    assert not '<p>Foo</p>' in resp.text
    assert '<p>Bar</p>' in resp.text
    assert not '<p>Baz</p>' in resp.text

    # remove info text from first button
    commentable.backoffice_info_text = None
    workflow.store()
    resp = app.get('/backoffice/management/form-title/%s/' % number31.id)
    assert not 'backoffice-description' in resp.text

def test_backoffice_handling_post_dispatch(pub):
    # check a formdata that has been dispatched to another role is accessible
    # by an user with that role.
    user1 = create_user(pub)
    role = Role(name='foobaz')
    role.store()
    user1.roles = [role.id]
    user1.store()
    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))

    # check there's no access at the moment
    resp = app.get('/backoffice/management/').follow()
    assert not 'form-title/' in resp.text
    resp = app.get('/backoffice/management/form-title/', status=403)
    resp = app.get('/backoffice/management/form-title/%s/' % number31.id, status=403)

    # emulate a dispatch (setting formdata.workflow_roles), receiver of that
    # formdata is now the local role we gave to the user.
    formdata31 = form_class.get(number31.id)
    formdata31.workflow_roles = {'_receiver': role.id}
    formdata31.store()

    # check listing is accessible, with a single item
    resp = app.get('/backoffice/management/').follow()
    assert 'form-title/' in resp.text
    resp = app.get('/backoffice/management/form-title/', status=200)
    assert re.findall('<tbody.*\/tbody>', resp.text, re.DOTALL)[0].count('<tr') == 1

    # check statistics and exports are also available
    assert 'Statistics' in resp.text
    assert 'Export' in resp.text
    app.get('/backoffice/management/form-title/stats', status=200)
    app.get('/backoffice/management/form-title/csv', status=200)
    app.get('/backoffice/management/form-title/ods', status=200)
    app.get('/backoffice/management/form-title/json', status=200)

    # check formdata is accessible, and that it's possible to perform an action
    # on it.
    resp = resp.click(href='%s/' % number31.id)
    assert (' with the number %s.' % number31.get_display_id()) in resp.text
    resp.forms[0]['comment'] = 'HELLO WORLD'
    resp = resp.forms[0].submit('button_accept')
    resp = resp.follow()
    assert FormDef.get_by_urlname('form-title').data_class().get(number31.id).status == 'wf-accepted'
    assert 'HELLO WORLD' in resp.text

def test_global_statisticspub(pub):
    create_superuser(pub)
    create_environment(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/')
    resp = resp.click('Global statistics')
    assert 'Total count: 70' in resp.text

    resp.forms[0]['start'] = '2014-01-01'
    resp.forms[0]['end'] = '2014-12-31'
    resp = resp.forms[0].submit()
    assert 'Total count: 20' in resp.text

def test_backoffice_submission(pub):
    user = create_user(pub)
    create_environment(pub)

    app = login(get_app(pub))
    resp = app.get('/backoffice/')
    assert not 'Submission' in resp.text
    app.get('/backoffice/submission/', status=403)

    formdef = FormDef.get_by_urlname('form-title')
    formdef.backoffice_submission_roles = user.roles[:]
    formdef.store()
    resp = app.get('/backoffice/')
    assert 'Submission' in resp.text
    resp = app.get('/backoffice/submission/')
    assert formdef.url_name in resp.text

    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.text

    # 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
    assert data_class.get(formdata_no).backoffice_submission is True

    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

    # test redirection on cancel
    resp = app.get('/backoffice/submission/')
    assert formdef.url_name in resp.text

    resp = resp.click(formdef.name)
    resp.form['f1'] = 'test submission'
    resp = resp.form.submit('cancel')
    assert resp.location == 'http://example.net/backoffice/submission/'

    # test submission when agent is not receiver but there's a redirect action
    # in the workflow.
    formdef = FormDef.get_by_urlname('form-title')
    wf = Workflow(name='dispatch')
    st1 = wf.add_status('Status1')
    item = RedirectToUrlWorkflowStatusItem()
    item.id = '_redirect'
    item.url = 'http://www.example.org/'
    st1.items.append(item)
    item.parent = st1
    wf.store()
    formdef.workflow_id = wf.id
    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://www.example.org/'


def test_backoffice_submission_with_tracking_code(pub):
    user = create_user(pub)
    create_environment(pub)

    formdef = FormDef.get_by_urlname('form-title')
    formdef.backoffice_submission_roles = user.roles[:]
    formdef.enable_tracking_codes = True
    formdef.store()

    app = login(get_app(pub))
    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')
    assert 'Check values then click submit.' in resp.text
    # final submit
    validation_resp_body = resp.text
    resp = resp.form.submit('submit')

    formdata_no = resp.location.split('/')[-2]
    data_class = formdef.data_class()
    formdata = data_class.get(formdata_no)
    assert formdata.tracking_code in validation_resp_body

    formdata_location = resp.location
    resp = resp.follow() # get to the formdata page
    # check tracking code is still displayed in formdata page
    assert 'test submission' in resp.text
    assert formdata.tracking_code in resp.text

    # check access by different user
    formdata.submission_context = {'agent_id': '10000'}
    formdata.store()
    resp = app.get(formdata_location)
    assert 'test submission' in resp.text
    assert not formdata.tracking_code in resp.text

    # restore user
    formdata.submission_context = {'agent_id': user.id}
    formdata.store()
    resp = app.get(formdata_location)
    assert formdata.tracking_code in resp.text

    # check access at a later time
    formdata.receipt_time = time.localtime(time.time() - 3600)
    formdata.store()
    resp = app.get(formdata_location)
    assert not formdata.tracking_code in resp.text

def test_backoffice_submission_welco(pub, welco_url):
    user = create_user(pub)
    create_environment(pub)

    app = login(get_app(pub))
    resp = app.get('/backoffice/')
    assert not 'Submission' in resp.text
    app.get('/backoffice/submission/', status=403)

    formdef = FormDef.get_by_urlname('form-title')
    formdef.backoffice_submission_roles = user.roles[:]
    formdef.store()

    # if it's empty, redirect to welco
    resp = app.get('/backoffice/')
    assert 'Submission' in resp.text
    resp = app.get('/backoffice/submission/')
    assert resp.location == 'http://welco.example.net'

    # if there are pending submissions, display them
    formdata = formdef.data_class()()
    formdata.data = {}
    formdata.status = 'draft'
    formdata.backoffice_submission = True
    formdata.submission_context = {'agent_id': user.id}
    formdata.store()

    resp = app.get('/backoffice/')
    assert 'Submission' in resp.text
    resp = app.get('/backoffice/submission/')
    assert 'Submission to complete' in resp.text
    # check agent name is displayed next to pending submission
    assert '(%s)' % user.display_name in resp.text

def test_backoffice_submission_initiated_from_welco(pub, welco_url):
    user = create_user(pub)
    create_environment(pub)

    app = login(get_app(pub))

    formdef = FormDef.get_by_urlname('form-title')
    formdef.fields = [
        fields.PageField(id='0', label='1st PAGE', type='page',
            condition={'type': 'python', 'value': 'form_submission_channel != "counter" and is_in_backoffice'}),
        fields.StringField(id='1', label='Field on 1st page', type='string'),
        fields.PageField(id='2', label='2nd PAGE', type='page'),
        fields.StringField(id='3', label='Field on 2nd page', type='string'),
    ]
    formdef.backoffice_submission_roles = user.roles[:]
    formdef.store()

    def post_formdata():
        signed_url = sign_url('http://example.net/api/formdefs/form-title/submit' +
                '?format=json&orig=coucou&email=%s' % urllib.quote(user.email), '1234')
        url = signed_url[len('http://example.net'):]
        resp = get_app(pub).post_json(url, {
                'meta': {
                    'draft': True,
                    'backoffice-submission': True,
                },
                'data': {},
                'context': {
                    'channel': 'counter'
                }
        })
        return resp.json['data']['id']

    resp = app.get('/backoffice/management/form-title/%s/' % post_formdata())
    resp = resp.follow()  # -> /backoffice/submission/form-title/XXX
    resp = resp.follow()  # -> /backoffice/submission/form-title/?mt=XYZ

    # second page should be shown
    pq = resp.pyquery.remove_namespaces()
    assert pq('#steps li.current .label').text() == '2nd PAGE'
    assert 'Field on 2nd page' in resp.text  # and in fields

    # reverse condition
    formdef.fields[0].condition['value'] = 'form_submission_channel == "counter" and is_in_backoffice'
    formdef.store()

    resp = app.get('/backoffice/management/form-title/%s/' % post_formdata())
    resp = resp.follow()  # -> /backoffice/submission/form-title/XXX
    resp = resp.follow()  # -> /backoffice/submission/form-title/?mt=XYZ

    pq = resp.pyquery.remove_namespaces()
    assert pq('#steps li.current .label').text() == '1st PAGE'
    assert 'Field on 1st page' in resp.text  # and in fields

def test_backoffice_submission_with_return_url(pub):
    user = create_user(pub)
    create_environment(pub)

    formdef = FormDef.get_by_urlname('form-title')
    formdef.backoffice_submission_roles = user.roles[:]
    formdef.enable_tracking_codes = True
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/submission/form-title/?ReturnURL=https://example.org')
    resp = resp.follow().follow()
    resp = resp.form.submit('cancel')
    assert resp.location == 'https://example.org'

    resp = app.get('/backoffice/submission/form-title/?ReturnURL=https://example.org')
    resp = resp.follow().follow()
    resp.form['f1'] = 'test submission'
    resp.form['f2'] = 'baz'
    resp.form['f3'] = 'C'
    resp = resp.form.submit('submit')  # -> to validation
    resp = resp.form.submit('submit')  # -> to submit
    assert resp.location.startswith('http://example.net/backoffice/management/form-title/')

    # test submission when agent is not receiver
    formdef.workflow_roles = {}
    formdef.store()
    resp = app.get('/backoffice/submission/form-title/?ReturnURL=https://example.org')
    resp = resp.follow().follow()
    resp.form['f1'] = 'test submission'
    resp.form['f2'] = 'baz'
    resp.form['f3'] = 'C'
    resp = resp.form.submit('submit')  # -> to validation
    resp = resp.form.submit('submit')  # -> to submit
    assert resp.location == 'https://example.org'

def test_backoffice_parallel_submission(pub):
    user = create_user(pub)
    create_environment(pub)

    app = login(get_app(pub))
    resp = app.get('/backoffice/')
    app.get('/backoffice/submission/', status=403)

    formdef = FormDef.get_by_urlname('form-title')
    formdef.backoffice_submission_roles = user.roles[:]
    formdef.enable_tracking_codes = True
    formdef.store()

    formdata = formdef.data_class()()
    formdata.data = {}
    formdata.status = 'draft'
    formdata.backoffice_submission = True
    formdata.submission_context = {'agent_id': user.id}
    formdata.store()

    resp = app.get('/backoffice/submission/')
    assert 'Submission to complete' in resp.text
    resp1 = app.get('/backoffice/submission/form-title/%s' % formdata.id)
    resp1 = resp1.follow()
    resp2 = app.get('/backoffice/submission/form-title/%s' % formdata.id)
    resp2 = resp2.follow()
    resp3 = app.get('/backoffice/submission/form-title/%s' % formdata.id)
    resp3 = resp3.follow()

    resp1.form['f1'] = 'foo'
    resp1.form['f2'] = 'bar'
    resp1.form['f3'] = 'C'
    resp1 = resp1.form.submit('submit') # to validation page

    # also move the second form to the validation page
    resp2.form['f1'] = 'bar'
    resp2.form['f2'] = 'bar'
    resp2.form['f3'] = 'C'
    resp2 = resp2.form.submit('submit') # to validation page

    resp1 = resp1.form.submit('submit') # final validation
    resp1 = resp1.follow()

    resp2 = resp2.form.submit('submit') # final validation
    assert resp2.status_code == 302
    resp2 = resp2.follow()
    assert 'This form has already been submitted.' in resp2.text

    # do the third form from the start
    resp3.form['f1'] = 'baz'
    resp3.form['f2'] = 'bar'
    resp3.form['f3'] = 'C'

    resp_autosave = app.post('/backoffice/submission/form-title/autosave',
            params=resp3.form.submit_fields())
    assert resp_autosave.json['result'] == 'error'
    assert resp_autosave.json['reason'] == 'form has already been submitted'

    resp3 = resp3.form.submit('submit') # to validation page
    assert resp3.status_code == 302
    resp3 = resp3.follow()
    assert 'This form has already been submitted.' in resp3.text

    assert formdef.data_class().get(formdata.id).data['1'] == 'foo'

    # try again, very late.
    resp4 = app.get('/backoffice/submission/form-title/%s' % formdata.id)
    resp4 = resp4.follow()
    assert 'This form has already been submitted.' in resp4.text

def test_backoffice_submission_dispatch(pub):
    user = create_user(pub)
    create_environment(pub)

    wf = Workflow(name='dispatch')
    st1 = wf.add_status('Status1')
    dispatch = DispatchWorkflowStatusItem()
    dispatch.id = '_dispatch'
    dispatch.role_key = '_receiver'
    dispatch.role_id = '2'
    st1.items.append(dispatch)
    dispatch.parent = st1
    wf.store()

    formdef = FormDef.get_by_urlname('form-title')
    formdef.workflow_id = wf.id
    formdef.backoffice_submission_roles = user.roles[:]
    formdef.store()

    app = login(get_app(pub))
    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
    # should go to the formdata because the formdef is defined as is
    assert resp.location.startswith('http://example.net/backoffice/management/form-title/')

    # remove function from formdef
    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
    # should NOT go to the formdata
    assert resp.location == 'http://example.net/backoffice/submission/'

    # if there's no function but the dispatch sets the right function, should
    # go to the formdata screen
    dispatch.role_id = '1'
    wf.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
    # should go to the formdata because the formdata was dispatched to the
    # right role
    assert resp.location.startswith('http://example.net/backoffice/management/form-title/')

def test_backoffice_submission_tracking_code(pub):
    user = create_user(pub)
    create_environment(pub)

    app = login(get_app(pub))

    formdef = FormDef.get_by_urlname('form-title')
    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.text

    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.text

    # 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.text
    assert 'test submission' in resp.text

def test_backoffice_submission_drafts(pub):
    user = create_user(pub)
    create_environment(pub)

    app = login(get_app(pub))

    formdef = FormDef.get_by_urlname('form-title')
    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.text

    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.text

    assert data_class.count() == 1
    formdata = data_class.select()[0]
    formdata_no = formdata.id
    tracking_code = data_class.select()[0].tracking_code

    # stop here, go back to index
    resp = app.get('/backoffice/submission/')
    assert '%s/%s' % (formdef.url_name, formdata_no) in resp.text
    assert '>#%s' % formdata_no in resp.text
    formdata.submission_channel = 'mail'
    formdata.store()
    resp = app.get('/backoffice/submission/')
    assert '>Mail #%s' % formdata_no in resp.text

    # check it can also be accessed using its final URL
    resp2 = app.get('/backoffice/management/%s/%s/' % (formdef.url_name, formdata_no))
    assert resp2.location == 'http://example.net/backoffice/submission/%s/%s' % (
            formdef.url_name, formdata_no)

    resp = resp.click('#%s' % formdata_no)
    resp = resp.follow()
    assert tracking_code in resp.text
    resp = resp.form.submit('previous')
    assert resp.form['f1'].value == 'test submission'

    resp = resp.form.submit('submit')
    assert "Check values then click submit." in resp.text
    resp = resp.form.submit('submit')
    # check it kept the same id
    assert resp.location == 'http://example.net/backoffice/management/form-title/%s/' % formdata_no

def test_backoffice_submission_remove_drafts(pub):
    user = create_user(pub)
    create_environment(pub)

    app = login(get_app(pub))

    formdef = FormDef.get_by_urlname('form-title')
    formdef.enable_tracking_codes = True
    formdef.backoffice_submission_roles = user.roles[:]
    formdef.store()
    data_class = formdef.data_class()
    data_class.wipe()
    pub.tracking_code_class.wipe()
    resp = app.get('/backoffice/submission/')
    assert formdef.url_name in resp.text

    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.text

    assert data_class.count() == 1
    formdata = data_class.select()[0]
    formdata_no = formdata.id
    tracking_code = data_class.select()[0].tracking_code

    # stop here, go back to the index
    resp = app.get('/backoffice/submission/')
    resp = resp.click('#%s' % formdata_no)
    resp = resp.follow()

    # and try to delete the form (but cancel)
    resp = resp.click('Delete this form')
    resp = resp.form.submit('cancel')
    assert resp.location == 'http://example.net/backoffice/submission/'
    assert data_class.count() == 1
    assert pub.tracking_code_class().count() == 1

    # and this time for real
    resp = app.get('/backoffice/submission/')
    resp = resp.click('#%s' % formdata_no)
    resp = resp.follow()
    resp = resp.click('Delete this form')
    resp = resp.form.submit('delete')
    assert resp.location == 'http://example.net/backoffice/submission/'
    assert data_class.count() == 0
    assert pub.tracking_code_class().count() == 0

    # check it's not possible to delete an actual formdata
    formdata = data_class()
    formdata.store()
    resp = app.get('/backoffice/submission/form-title/remove/%s' % formdata.id,
            status=403)

def test_backoffice_submission_live_condition(pub):
    user = create_user(pub)
    create_environment(pub)

    formdef = FormDef.get_by_urlname('form-title')
    formdef.backoffice_submission_roles = user.roles[:]
    formdef.store()
    formdef.fields = [
        fields.StringField(type='string', id='1', label='Bar', size='40',
            required=True, varname='bar'),
        fields.StringField(type='string', id='2', label='Foo', size='40',
            required=True, varname='foo',
            condition={'type': 'django', 'value': 'form_var_bar == "bye"'}),
    ]
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/submission/form-title/')
    assert 'f1' in resp.form.fields
    assert 'f2' in resp.form.fields
    assert resp.html.find('div', {'data-field-id': '1'}).attrs['data-live-source'] == 'true'
    assert resp.html.find('div', {'data-field-id': '2'}).attrs.get('style') == 'display: none'
    resp.form['f1'] = 'hello'
    live_url = resp.html.find('form').attrs['data-live-url']
    assert '/backoffice/' in live_url
    live_resp = app.post(live_url, params=resp.form.submit_fields())
    assert live_resp.json['result']['1']['visible']
    assert not live_resp.json['result']['2']['visible']
    resp.form['f1'] = 'bye'
    live_resp = app.post(live_url, params=resp.form.submit_fields())
    assert live_resp.json['result']['1']['visible']
    assert live_resp.json['result']['2']['visible']
    resp.form['f1'] = 'hello'
    resp = resp.form.submit('submit')
    assert 'Check values then click submit.' in resp.text
    assert 'name="f1"' in resp.text
    assert 'name="f2"' not in resp.text
    resp = resp.form.submit('submit')
    resp = resp.follow()
    assert '<span class="label">Bar</span>' in resp.text
    assert '<span class="label">Foo</span>' not in resp.text

def test_backoffice_submission_conditional_jump_based_on_bo_field(pub):
    user = create_superuser(pub)
    Workflow.wipe()
    workflow = Workflow(name='form-title')
    workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
    workflow.backoffice_fields_formdef.fields = [
        fields.StringField(
            id='bo0', varname='foo_bovar', type='string', label='bo variable'),
    ]

    st1 = workflow.add_status('Status1', 'st1')
    st2 = workflow.add_status('Status2', 'st2')

    setbo = SetBackofficeFieldsWorkflowStatusItem()
    setbo.parent = st1
    setbo.fields = [{'field_id': 'bo0', 'value': 'go'}]
    st1.items.append(setbo)

    jump = JumpWorkflowStatusItem()
    jump.condition = {'type': 'django', 'value': "form_var_foo_bovar == 'go'"}
    jump.status = 'st2'
    st1.items.append(jump)
    jump.parent = st1

    workflow.store()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form-title'
    formdef.fields = [
        fields.TextField(id='1', varname='foo', type='text', label='fo variable'),
        fields.TextField(id='2', varname='var3', type='text', label='n/a',
                         condition={'type': 'django', 'value': 'True'}),
      ]
    formdef.workflow_id = workflow.id
    formdef.backoffice_submission_roles = user.roles[:]
    formdef.store()
    formdef.data_class().wipe()

    # check jump condition being True
    app = login(get_app(pub))
    resp = app.get('/backoffice/submission/form-title/')
    resp.form['f1'] = 'foo'
    resp.form['f2'] = 'bar'
    resp = resp.form.submit('submit')  # -> confirmation page
    resp = resp.form.submit('submit').follow()

    assert formdef.data_class().count() == 1
    assert formdef.data_class().select()[0].status == 'wf-st2'

    # check jump condition being False
    setbo.fields = [{'field_id': 'bo0', 'value': 'nogo'}]
    workflow.store()
    formdef.data_class().wipe()
    resp = app.get('/backoffice/submission/form-title/')
    resp.form['f1'] = 'foo'
    resp.form['f2'] = 'bar'
    resp = resp.form.submit('submit')  # -> confirmation page
    resp = resp.form.submit('submit').follow()

    assert formdef.data_class().count() == 1
    assert formdef.data_class().select()[0].status == 'wf-st1'

def test_backoffice_submission_sections(pub):
    user = create_user(pub)
    create_environment(pub)

    app = login(get_app(pub))

    formdef = FormDef.get_by_urlname('form-title')
    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 not 'Submission to complete' in resp.text
    assert not 'Running submission' in resp.text

    formdata = data_class()
    formdata.data = {}
    formdata.status = 'draft'
    formdata.backoffice_submission = True
    formdata.receipt_time = datetime.datetime(2015, 1, 1).timetuple()
    formdata.store()

    resp = app.get('/backoffice/submission/')
    assert 'Submission to complete' in resp.text
    assert not 'Running submission' in resp.text
    assert '>#%s' % formdata.id in resp.text

    formdata.data = {'1': 'xxx'}
    formdata.store()
    resp = app.get('/backoffice/submission/')
    assert not 'Submission to complete' in resp.text
    assert 'Running submission' in resp.text
    assert '>#%s' % formdata.id in resp.text

def test_backoffice_submission_prefill_user(pub):
    user = create_user(pub)
    create_environment(pub)
    other_user = pub.user_class(name='other user')
    other_user.email = 'other@example.net'
    other_user.store()

    formdef = FormDef.get_by_urlname('form-title')
    formdef.fields[0].prefill = {'type': 'user', 'value': 'email'}
    formdef.backoffice_submission_roles = user.roles[:]
    formdef.store()

    formdata = formdef.data_class()()
    formdata.backoffice_submission = True
    formdata.status = 'draft'
    formdata.data = {}
    formdata.submission_channel = 'mail'
    formdata.user_id = other_user.id
    formdata.submission_context = {}
    formdata.store()

    formdata2 = formdef.data_class()()
    formdata2.backoffice_submission = True
    formdata2.status = 'draft'
    formdata2.data = {}
    formdata.submission_channel = 'mail'
    formdata.user_id = None
    formdata2.submission_context = {}
    formdata2.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/submission/form-title/')
    assert resp.form['f1'].value == ''

    # restore a draft
    resp = app.get('/backoffice/submission/form-title/%s' % formdata.id)
    resp = resp.follow()
    # and check it got prefilled with the user from context
    assert resp.form['f1'].value == 'other@example.net'

    # restore another, without user id
    resp = app.get('/backoffice/submission/form-title/%s' % formdata2.id)
    resp = resp.follow()
    # and check it was not prefilled
    assert resp.form['f1'].value == ''

def test_backoffice_submission_prefill_user_via_formula(pub):
    user = create_user(pub)
    create_environment(pub)
    other_user = pub.user_class(name='other user')
    other_user.email = 'other@example.net'
    other_user.store()

    formdef = FormDef.get_by_urlname('form-title')
    formdef.fields[0].prefill = {'type': 'formula', 'value': 'form_user_email'}
    formdef.backoffice_submission_roles = user.roles[:]
    formdef.store()

    formdata = formdef.data_class()()
    formdata.backoffice_submission = True
    formdata.status = 'draft'
    formdata.data = {}
    formdata.submission_channel = 'mail'
    formdata.user_id = other_user.id
    formdata.submission_context = {}
    formdata.store()

    formdata2 = formdef.data_class()()
    formdata2.backoffice_submission = True
    formdata2.status = 'draft'
    formdata2.data = {}
    formdata.submission_channel = 'mail'
    formdata.user_id = None
    formdata2.submission_context = {}
    formdata2.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/submission/form-title/')
    assert resp.form['f1'].value == ''

    # restore a draft
    resp = app.get('/backoffice/submission/form-title/%s' % formdata.id)
    resp = resp.follow()
    # and check it got prefilled with the user from context
    assert resp.form['f1'].value == 'other@example.net'

    # restore another, without user id
    resp = app.get('/backoffice/submission/form-title/%s' % formdata2.id)
    resp = resp.follow()
    # and check it was not prefilled
    assert resp.form['f1'].value == ''

def test_backoffice_submission_prefill_user_multiple_pages(pub):
    user = create_user(pub)
    create_environment(pub)
    other_user = pub.user_class(name='other user')
    other_user.email = 'other@example.net'
    other_user.store()

    for enable_tracking_code in (False, True):
        formdef = FormDef.get_by_urlname('form-title')
        formdef.fields = [
            fields.PageField(id='0', label='1st page', type='page'),
            fields.StringField(id='1', label='1st field', type='string', required=False),
            fields.PageField(id='4', label='2nd page', type='page'),
            fields.StringField(id='5', label='field on 2nd page',
                prefill = {'type': 'user', 'value': 'email'}),
        ]
        formdef.backoffice_submission_roles = user.roles[:]
        formdef.enable_tracking_codes = enable_tracking_code
        formdef.store()

        formdef.data_class().wipe()

        formdata = formdef.data_class()()
        formdata.backoffice_submission = True
        formdata.status = 'draft'
        formdata.data = {}
        formdata.submission_channel = 'mail'
        formdata.user_id = other_user.id
        formdata.submission_context = {}
        formdata.store()

        formdata2 = formdef.data_class()()
        formdata2.backoffice_submission = True
        formdata2.status = 'draft'
        formdata2.data = {}
        formdata.submission_channel = 'mail'
        formdata.user_id = None
        formdata2.submission_context = {}
        formdata2.store()

        app = login(get_app(pub))
        resp = app.get('/backoffice/submission/form-title/')
        resp = resp.form.submit('submit')
        assert resp.form['f5'].value == ''

        # restore a draft
        resp = app.get('/backoffice/submission/form-title/%s' % formdata.id)
        resp = resp.follow()
        resp = resp.form.submit('submit')
        # and check it got prefilled with the user from context
        assert resp.form['f5'].value == 'other@example.net'

        # restore another, without user id
        resp = app.get('/backoffice/submission/form-title/%s' % formdata2.id)
        resp = resp.follow()
        resp = resp.form.submit('submit')
        # and check it was not prefilled
        assert resp.form['f5'].value == ''

    # continue with additional tests when drafts are enabled, using autosave

    # restore a draft
    formdata.page_no = 0
    formdata.user_id = other_user.id
    formdata.store()
    resp = app.get('/backoffice/submission/form-title/%s' % formdata.id)
    resp = resp.follow()
    resp.form['f1'] = 'Hello'
    resp_autosave = app.post('/backoffice/submission/form-title/autosave', params=resp.form.submit_fields())
    assert formdef.data_class().get(formdata.id).user_id == str(other_user.id)
    assert formdef.data_class().get(formdata.id).data['1'] == 'Hello'

    resp = resp.form.submit('submit')
    # and check it got prefilled with the user from context
    assert resp.form['f5'].value == 'other@example.net'

    assert formdef.data_class().get(formdata.id).user_id == str(other_user.id)
    assert formdef.data_class().get(formdata.id).data['1'] == 'Hello'

    formdef.data_class().wipe()
    resp = app.get('/backoffice/submission/form-title/')
    resp.form['f1'] = 'Hello'
    resp_autosave = app.post('/backoffice/submission/form-title/autosave', params=resp.form.submit_fields())
    assert formdef.data_class().count() == 1
    assert formdef.data_class().select()[0].user_id is None
    resp = resp.form.submit('submit')
    assert formdef.data_class().count() == 1
    assert formdef.data_class().select()[0].user_id is None

def test_backoffice_submission_multiple_page_restore_on_validation(pub):
    user = create_user(pub)
    create_environment(pub)
    other_user = pub.user_class(name='other user')
    other_user.email = 'other@example.net'
    other_user.store()

    formdef = FormDef.get_by_urlname('form-title')
    formdef.fields = [
        fields.PageField(id='0', label='1st page', type='page'),
        fields.StringField(id='1', label='1st field', type='string', required=False),
        fields.PageField(id='2', label='2nd page', type='page',
            condition={'type': 'python', 'value': 'False'}),
        fields.PageField(id='3', label='3rd page', type='page'),
        fields.StringField(id='5', label='field on 3rd page'),
    ]
    formdef.backoffice_submission_roles = user.roles[:]
    formdef.enable_tracking_codes = True
    formdef.store()

    formdef.data_class().wipe()

    app = login(get_app(pub))
    resp = app.get('/backoffice/submission/form-title/')
    resp.form['f1'] = 'foo'
    resp = resp.form.submit('submit')
    resp.form['f5'] = 'bar'
    resp = resp.form.submit('submit')
    assert 'Check values then click submit.' in resp.text

    assert formdef.data_class().count() == 1
    formdata = formdef.data_class().select()[0]
    # restore draft
    resp = app.get('/backoffice/submission/')
    resp = resp.click(href='form-title/%s' % formdata.id)
    resp = resp.follow()
    assert 'Check values then click submit.' in resp.text

def test_backoffice_submission_substitution_vars(pub):
    user = create_user(pub)
    create_environment(pub)

    formdef = FormDef.get_by_urlname('form-title')
    formdef.fields = [
        fields.PageField(id='0', label='1st page', type='page'),
        fields.StringField(id='1', label='1st field', type='string',
            required=False, varname='foobar'),
        fields.ItemField(id='10', label='2nd field', type='item',
                items=['foo', 'bar', 'baz'], varname='foobar2'),
        fields.PageField(id='4', label='2nd page', type='page'),
        fields.CommentField(id='5', label='X[form_var_foobar]Y[form_var_foobar2_raw]Z', type='comment'),
    ]
    formdef.backoffice_submission_roles = user.roles[:]
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/submission/form-title/')
    resp.form['f1'].value = 'PLOP'
    resp.form['f10'].value = 'bar'
    resp = resp.form.submit('submit')
    assert 'XPLOPYbarZ' in resp.text

    # django-templated comment
    formdef.fields[4] = fields.CommentField(id='5',
            label='dj-{{ form_var_foobar }}-an-{{ form_var_foobar2_raw}}-go', type='comment')
    formdef.store()
    formdef.data_class().wipe()
    resp = app.get('/backoffice/submission/form-title/')
    resp.form['f1'].value = 'foo'
    resp.form['f10'].value = 'bar'
    resp = resp.form.submit('submit')
    assert 'dj-foo-an-bar-go' in resp.text

    formdef.data_class().wipe()

    # same but starting from a draft, as if it was initiated by welco
    formdata = formdef.data_class()()
    formdata.data = {}
    formdata.status = 'draft'
    formdata.backoffice_submission = True
    formdata.store()

    resp = app.get('/backoffice/submission/form-title/%s' % formdata.id)
    resp = resp.follow()
    resp.form['f1'].value = 'PLOP'
    resp.form['f10'].value = 'bar'
    resp = resp.form.submit('submit')
    assert 'dj-PLOP-an-bar-go' in resp.text

def test_backoffice_submission_manual_channel(pub):
    user = create_user(pub)
    create_environment(pub)

    app = login(get_app(pub))
    resp = app.get('/backoffice/')
    assert not 'Submission' in resp.text
    app.get('/backoffice/submission/', status=403)

    formdef = FormDef.get_by_urlname('form-title')
    formdef.backoffice_submission_roles = user.roles[:]
    formdef.store()
    resp = app.get('/backoffice/')
    assert 'Submission' in resp.text
    resp = app.get('/backoffice/submission/')
    assert formdef.url_name in resp.text

    resp = resp.click(formdef.name)
    assert resp.form['submission_channel'].attrs['type'] == 'hidden'
    resp.form['submission_channel'] = 'mail'
    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.text

    # 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
    assert data_class.get(formdata_no).backoffice_submission is True
    assert data_class.get(formdata_no).submission_channel == 'mail'

def test_backoffice_submission_no_manual_channel_with_welco(pub, welco_url):
    user = create_user(pub)
    create_environment(pub)

    formdef = FormDef.get_by_urlname('form-title')
    formdef.backoffice_submission_roles = user.roles[:]
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/submission/%s/' % formdef.url_name)
    assert 'submission_channel' not in resp.form.fields

def test_backoffice_submission_with_nameid_and_channel(pub, local_user):
    user = create_user(pub)
    create_environment(pub)

    formdef = FormDef.get_by_urlname('form-title')
    formdef.fields[0].prefill = {'type': 'formula', 'value': 'form_user_email'}
    formdef.backoffice_submission_roles = user.roles[:]
    formdef.enable_tracking_codes = True
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/submission/form-title/?NameID=%s&channel=mail' % local_user.name_identifiers[0])
    assert resp.location.startswith('http://example.net/backoffice/submission/form-title/')

    formdata_no = resp.location.split('/')[-1]
    formdata = formdef.data_class().get(formdata_no)
    assert formdata.user_id == str(local_user.id)
    assert formdata.submission_channel == 'mail'
    assert formdata.status == 'draft'

    resp = resp.follow()  # redirect to created draft
    resp = resp.follow()  # redirect to ?mt=

    assert resp.form['f1'].value == local_user.email  # prefill with form_user_email
    resp.form['f2'] = 'baz'
    resp.form['f3'] = 'C'
    resp = resp.form.submit('submit')
    assert 'Check values then click submit.' in resp.text
    # final submit
    resp = resp.form.submit('submit')

    formdata_no = resp.location.split('/')[-2]
    data_class = formdef.data_class()
    formdata = data_class.get(formdata_no)
    assert formdata.user_id == str(local_user.id)
    assert formdata.submission_channel == 'mail'
    assert formdata.status == 'wf-new'

def test_backoffice_wscall_failure_display(http_requests, pub):
    user = create_user(pub)
    create_environment(pub)
    formdef = FormDef.get_by_urlname('form-title')
    form_class = formdef.data_class()

    number31 = [x for x in form_class.select() if x.data['1'] == 'FOO BAR 30'][0]

    # attach a custom workflow
    workflow = Workflow(name='wscall')
    st1 = workflow.add_status('Status1', number31.status.split('-')[1])

    wscall = WebserviceCallStatusItem()
    wscall.id = '_wscall'
    wscall.varname = 'xxx'
    wscall.url = 'http://remote.example.net/xml'
    wscall.action_on_bad_data = ':stop'
    wscall.record_errors = True
    st1.items.append(wscall)
    wscall.parent = st1

    again = ChoiceWorkflowStatusItem()
    again.id = '_again'
    again.label = 'Again'
    again.by = ['_receiver']
    again.status = st1.id
    st1.items.append(again)
    again.parent = st1

    workflow.store()

    formdef.workflow_id = workflow.id
    formdef.store()

    app = login(get_app(pub))

    resp = app.get('/backoffice/management/form-title/%s/' % number31.id)
    assert (' with the number %s.' % number31.get_display_id()) in resp.text
    assert 'Again' in resp.text
    resp = resp.forms[0].submit('button_again')
    resp = resp.follow()
    assert 'Error during webservice call' in resp.text

    number31.user_id = user.id  # change ownership to stay in frontoffice
    number31.store()
    # the failure message shouldn't be displayed in the frontoffice
    resp = app.get('/form-title/%s/' % number31.id)
    assert (' with the number %s.' % number31.get_display_id()) in resp.text
    assert not 'Error during webservice call' in resp.text

def test_backoffice_wscall_error_email(http_requests, pub, emails):
    pub.cfg['debug'] = {'error_email': 'errors@localhost.invalid'}
    pub.write_cfg()

    user = create_user(pub)
    create_environment(pub)
    formdef = FormDef.get_by_urlname('form-title')
    form_class = formdef.data_class()

    number31 = [x for x in form_class.select() if x.data['1'] == 'FOO BAR 30'][0]

    # attach a custom workflow
    workflow = Workflow(name='wscall')
    st1 = workflow.add_status('Status1', number31.status.split('-')[1])

    wscall = WebserviceCallStatusItem()
    wscall.id = '_wscall'
    wscall.varname = 'xxx'
    wscall.url = 'http://remote.example.net/xml'
    wscall.action_on_bad_data = ':stop'
    wscall.record_errors = True
    st1.items.append(wscall)
    wscall.parent = st1

    again = ChoiceWorkflowStatusItem()
    again.id = '_again'
    again.label = 'Again'
    again.by = ['_receiver']
    again.status = st1.id
    st1.items.append(again)
    again.parent = st1

    workflow.store()

    formdef.workflow_id = workflow.id
    formdef.store()

    app = login(get_app(pub))

    resp = app.get('/backoffice/management/form-title/%s/' % number31.id)
    assert (' with the number %s.' % number31.get_display_id()) in resp.text
    assert 'Again' in resp.text
    resp = resp.forms[0].submit('button_again')
    resp = resp.follow()
    assert 'Error during webservice call' in resp.text

    # check email box
    if six.PY2:
        error_email = emails.get('[ERROR] [WSCALL] ValueError: No JSON object could be decoded')
    else:
        error_email = emails.get('[ERROR] [WSCALL] json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)')
    assert '/form-title/%s/' % number31.id in error_email['payload']
    assert error_email['msg']['References']

def test_backoffice_wscall_attachment(http_requests, pub):
    create_user(pub)
    create_environment(pub)
    formdef = FormDef.get_by_urlname('form-title')
    form_class = formdef.data_class()

    number31 = [x for x in form_class.select() if x.data['1'] == 'FOO BAR 30'][0]

    # attach a custom workflow
    workflow = Workflow(name='wscall')
    workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
    workflow.backoffice_fields_formdef.fields = [
        fields.FileField(id='bo1', label='bo field 1', type='file'),
    ]

    st1 = workflow.add_status('Status1', number31.status.split('-')[1])

    wscall = WebserviceCallStatusItem()
    wscall.id = '_wscall'
    wscall.varname = 'xxx'
    wscall.response_type = 'attachment'
    wscall.backoffice_filefield_id = 'bo1'
    wscall.url = 'http://remote.example.net/xml'
    wscall.action_on_bad_data = ':stop'
    wscall.record_errors = True
    st1.items.append(wscall)
    wscall.parent = st1

    again = ChoiceWorkflowStatusItem()
    again.id = '_again'
    again.label = 'Again'
    again.by = ['_receiver']
    again.status = st1.id
    st1.items.append(again)
    again.parent = st1

    workflow.store()

    formdef.workflow_id = workflow.id
    formdef.store()

    app = login(get_app(pub))

    resp = app.get('/backoffice/management/form-title/%s/' % number31.id)
    assert (' with the number %s.' % number31.get_display_id()) in resp.text
    assert 'Again' in resp.text
    resp = resp.forms[0].submit('button_again')
    resp = resp.follow()

    # get the two generated files from backoffice: in backoffice fields
    # (wscall.backoffice_filefield_id), and in history
    for index in (0, 1):
        resp = app.get('/backoffice/management/form-title/%s/' % number31.id)
        resp = resp.click('xxx.xml', index=index)
        assert resp.location.endswith('/xxx.xml')
        resp = resp.follow()
        assert resp.content_type == 'text/xml'
        assert resp.text == '<?xml version="1.0"><foo/>'

    formdata = formdef.data_class().get(number31.id)
    assert formdata.evolution[-1].parts[0].orig_filename == 'xxx.xml'
    assert formdata.evolution[-1].parts[0].content_type == 'text/xml'
    assert formdata.get_substitution_variables()['attachments'].xxx.filename == 'xxx.xml'
    resp = app.get(formdata.get_substitution_variables()['attachments'].xxx.url)
    resp = resp.follow()
    assert resp.text == '<?xml version="1.0"><foo/>'

def test_backoffice_wfedit(pub):
    user = create_user(pub)
    create_environment(pub)
    formdef = FormDef.get_by_urlname('form-title')
    form_class = formdef.data_class()

    number31 = [x for x in form_class.select() if x.data['1'] == 'FOO BAR 30'][0]
    number31.submission_context = {
            'mail_url': 'http://www.example.com/test.pdf',
    }
    number31.store()

    # attach a custom workflow
    workflow = Workflow(name='wfedit')
    st1 = workflow.add_status('Status1', number31.status.split('-')[1])

    wfedit = EditableWorkflowStatusItem()
    wfedit.id = '_wfedit'
    wfedit.by = [user.roles[0]]
    st1.items.append(wfedit)
    wfedit.parent = st1
    workflow.store()

    formdef.workflow_id = workflow.id
    formdef.store()

    app = login(get_app(pub))

    resp = app.get('/backoffice/management/form-title/%s/' % number31.id)
    assert (' with the number %s.' % number31.get_display_id()) in resp.text
    resp = resp.form.submit('button_wfedit')
    resp = resp.follow()
    assert 'http://www.example.com/test.pdf' in resp.text # make sure sidebar has details
    assert not 'Tracking Code' in resp.text # make sure it doesn't display a tracking code
    assert resp.form['f1'].value == number31.data['1']
    assert resp.form['f2'].value == number31.data['2']
    assert resp.form['f3'].value == number31.data['3']
    assert 'value="Save Changes"' in resp.text
    resp.form['f2'].value = 'bar'
    resp = resp.form.submit('submit')
    resp = resp.follow()
    assert form_class().get(number31.id).data['2'] == 'bar'
    number31.store()

def test_backoffice_wfedit_submission(pub):
    user = create_user(pub)
    create_environment(pub)
    formdef = FormDef.get_by_urlname('form-title')
    formdef.backoffice_submission_roles = user.roles[:]
    formdef.enable_tracking_codes = True

    formdef.fields.insert(0, fields.PageField(id='0', label='1st page', type='page'))
    formdef.fields.append(fields.PageField(id='4', label='2nd page', type='page'))

    formdef.store()
    form_class = formdef.data_class()

    number31 = [x for x in form_class.select() if x.data['1'] == 'FOO BAR 30'][0]
    number31.backoffice_submission = True
    number31.store()
    formdata_count = form_class.count()

    # attach a custom workflow
    workflow = Workflow(name='wfedit')
    st1 = workflow.add_status('Status1', number31.status.split('-')[1])

    wfedit = EditableWorkflowStatusItem()
    wfedit.id = '_wfedit'
    wfedit.by = [user.roles[0]]
    st1.items.append(wfedit)
    wfedit.parent = st1
    workflow.store()

    formdef.workflow_id = workflow.id
    formdef.store()

    app = login(get_app(pub))

    resp = app.get('/backoffice/management/form-title/%s/' % number31.id)
    assert (' with the number %s.' % number31.get_display_id()) in resp.text
    resp = resp.form.submit('button_wfedit')
    resp = resp.follow()
    assert resp.form['f1'].value == number31.data['1']
    assert resp.form['f2'].value == number31.data['2']
    assert resp.form['f3'].value == number31.data['3']
    resp.form['f2'].value = 'bar'
    resp = resp.form.submit('submit')
    assert 'value="Save Changes"' in resp.text
    resp = resp.form.submit('submit')
    resp = resp.follow()
    assert form_class().get(number31.id).data['2'] == 'bar'
    number31.store()
    assert formdata_count == form_class.count()

def test_backoffice_wfedit_and_backoffice_fields(pub):
    user = create_user(pub)
    create_environment(pub)
    formdef = FormDef.get_by_urlname('form-title')
    form_class = formdef.data_class()

    number31 = [x for x in form_class.select() if x.data['1'] == 'FOO BAR 30'][0]

    # attach a custom workflow
    workflow = Workflow(name='wfedit')
    workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
    workflow.backoffice_fields_formdef.fields = [
        fields.StringField(id='bo1', label='1st backoffice field',
            type='string', varname='backoffice_blah', required=False),
    ]

    st1 = workflow.add_status('Status1', number31.status.split('-')[1])

    wfedit = EditableWorkflowStatusItem()
    wfedit.id = '_wfedit'
    wfedit.by = [user.roles[0]]
    st1.items.append(wfedit)
    wfedit.parent = st1
    workflow.store()

    formdef.workflow_id = workflow.id
    formdef.store()

    number31 = form_class().get(number31.id)
    number31.data['bo1'] = 'plop'
    number31.store()

    app = login(get_app(pub))

    resp = app.get('/backoffice/management/form-title/%s/' % number31.id)
    resp = resp.form.submit('button_wfedit')
    resp = resp.follow()
    resp.form['f2'].value = 'bar'
    resp = resp.form.submit('submit')
    resp = resp.follow()
    assert form_class().get(number31.id).data['bo1'] == 'plop'

def test_backoffice_wfedit_and_data_source_with_user_info(pub):
    user = create_user(pub)
    create_environment(pub)
    formdef = FormDef.get_by_urlname('form-title')

    formdef.fields[2].data_source = {'type': 'json',
            'value': 'https://www.example.invalid/?name_id={% firstof form_user_display_name "XXX" %}'
            }
    formdef.store()

    form_class = formdef.data_class()

    number31 = [x for x in form_class.select() if x.data['1'] == 'FOO BAR 30'][0]

    # attach a custom workflow
    workflow = Workflow(name='wfedit')
    st1 = workflow.add_status('Status1', number31.status.split('-')[1])

    wfedit = EditableWorkflowStatusItem()
    wfedit.id = '_wfedit'
    wfedit.by = [user.roles[0]]
    st1.items.append(wfedit)
    wfedit.parent = st1
    workflow.store()

    formdef.workflow_id = workflow.id
    formdef.store()

    number31 = form_class().get(number31.id)
    number31.user_id = user.id
    number31.store()

    app = login(get_app(pub))

    with mock.patch('wcs.qommon.misc.urlopen') as urlopen:
        data = {'data': [{'id': 'A', 'text': 'hello'}, {'id': 'B', 'text': 'world'}]}
        def side_effect(url, *args):
            assert '?name_id=admin' in url
            return StringIO(json.dumps(data))

        urlopen.side_effect = side_effect

        resp = app.get('/backoffice/management/form-title/%s/' % number31.id)
        resp = resp.form.submit('button_wfedit')
        resp = resp.follow()
        assert urlopen.call_count == 1
        resp.form['f3'].value = 'A'
        resp = resp.form.submit('submit')
        assert urlopen.call_count == 2
        resp = resp.follow()

def test_global_listing(pub):
    if not pub.is_using_postgresql():
        pytest.skip('this requires SQL')
        return

    create_user(pub)
    create_environment(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/management/').follow()
    assert 'Global View' in resp.text
    resp = resp.click('Global View')
    assert resp.text[resp.text.index('<tbody'):].count('<tr') == 20
    assert not 'Map View' in resp.text

    resp = app.get('/backoffice/management/listing?limit=500')
    assert resp.text[resp.text.index('<tbody'):].count('<tr') == 37 # 17 formdef1 + 20 formdef2

    resp = app.get('/backoffice/management/listing?offset=20&limit=20')
    assert resp.text[resp.text.index('<tbody'):].count('<tr') == 17

    # try an overbound offset
    resp = app.get('/backoffice/management/listing?offset=40&limit=20')
    resp = resp.follow()
    assert resp.forms['listing-settings']['offset'].value == '0'

    resp = app.get('/backoffice/management/listing')
    resp.forms['listing-settings']['end'] = '2014-02-01'
    resp = resp.forms['listing-settings'].submit()

    assert resp.text[resp.text.index('<tbody'):].count('<tr') == 20
    assert 'http://example.net/backoffice/management/other-form/' in resp.text
    assert not 'http://example.net/backoffice/management/form-title/' in resp.text

    formdef = FormDef.get_by_urlname('form-title')
    last_update_time = formdef.data_class().select(lambda x: not x.is_draft())[0].last_update_time
    # check created and last modified columns
    assert '>2014-01-01 00:00<' in resp.text
    assert time.strftime('>%Y-%m-%d', last_update_time) in resp.text

    # check digest is included
    formdata = formdef.data_class().get(
            re.findall(r'data-link="(.*?)"', app.get('/backoffice/management/listing').text)[0].split('/')[-2])
    formdata.formdef.digest_template = 'digest of number <{{form_number}}>'
    formdata.store()
    assert formdata.get(formdata.id).digest
    resp = app.get('/backoffice/management/listing')
    assert formdata.get_url(backoffice=True) in resp.text
    assert 'digest of number &lt;%s&gt;' % formdata.id_display in resp.text

    # check a Channel column is added when welco is available
    assert not 'Channel' in resp.text

    if not pub.site_options.has_section('variables'):
        pub.site_options.add_section('variables')
    pub.site_options.set('variables', 'welco_url', 'xxx')
    fd = open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w')
    pub.site_options.write(fd)
    fd.close()

    resp = app.get('/backoffice/management/listing?limit=500')
    formdata = formdef.data_class().select(lambda x: x.status == 'wf-new')[0]
    formdata.submission_channel = 'mail'
    formdata.store()
    assert 'Channel' in resp.text
    assert '>Web<' in resp.text
    resp.forms['listing-settings']['submission_channel'] = 'web'
    resp = resp.forms['listing-settings'].submit()
    assert resp.text[resp.text.index('<tbody'):].count('<tr') == 36
    resp.forms['listing-settings']['submission_channel'] = 'mail'
    resp = resp.forms['listing-settings'].submit()
    assert resp.text[resp.text.index('<tbody'):].count('<tr') == 1

    resp = app.get('/backoffice/management/listing?limit=500')
    resp.forms['listing-settings']['q'] = 'foo'
    resp = resp.forms['listing-settings'].submit()
    assert resp.text[resp.text.index('<tbody'):].count('<tr') == 17

    resp = app.get('/backoffice/management/listing?limit=500')
    resp.forms['listing-settings']['status'] = 'waiting'
    resp = resp.forms['listing-settings'].submit()
    assert resp.text[resp.text.index('<tbody'):].count('<tr') == 37
    resp.forms['listing-settings']['status'] = 'open'
    resp = resp.forms['listing-settings'].submit()
    assert resp.text[resp.text.index('<tbody'):].count('<tr') == 37
    resp.forms['listing-settings']['status'] = 'all'
    resp = resp.forms['listing-settings'].submit()
    assert resp.text[resp.text.index('<tbody'):].count('<tr') == 70
    resp.forms['listing-settings']['status'] = 'done'
    resp = resp.forms['listing-settings'].submit()
    assert resp.text[resp.text.index('<tbody'):].count('<tr') == 33

    # change role handling a formdef, make sure they do not appear anylonger in
    # the all/done views.
    role = Role(name='whatever')
    role.store()
    formdef = FormDef.get_by_urlname('form-title')
    formdef.workflow_roles = {'_receiver': role.id}
    formdef.store()
    formdef.data_class().rebuild_security()

    resp = app.get('/backoffice/management/listing?limit=500')
    resp.forms['listing-settings']['status'] = 'waiting'
    resp = resp.forms['listing-settings'].submit()
    assert resp.text[resp.text.index('<tbody'):].count('<tr') == 20
    assert not 'form-title' in resp.text
    resp.forms['listing-settings']['status'] = 'open'
    resp = resp.forms['listing-settings'].submit()
    assert resp.text[resp.text.index('<tbody'):].count('<tr') == 20
    assert not 'form-title' in resp.text
    resp.forms['listing-settings']['status'] = 'all'
    resp = resp.forms['listing-settings'].submit()
    assert resp.text[resp.text.index('<tbody'):].count('<tr') == 20
    assert not 'form-title' in resp.text
    resp.forms['listing-settings']['status'] = 'done'
    resp = resp.forms['listing-settings'].submit()
    assert resp.text[resp.text.index('<tbody'):].count('<tr') == 0
    assert not 'form-title' in resp.text

def test_global_listing_user_label(pub):
    if not pub.is_using_postgresql():
        pytest.skip('this requires SQL')
        return

    create_user(pub)
    FormDef.wipe()

    from wcs.admin.settings import UserFieldsFormDef
    user_formdef = UserFieldsFormDef(pub)
    user_formdef.fields.append(fields.StringField(id='3', label='first_name', type='string'))
    user_formdef.fields.append(fields.StringField(id='4', label='last_name', type='string'))
    user_formdef.store()
    pub.cfg['users']['field_name'] = ['3', '4']
    pub.write_cfg()

    formdef = FormDef()
    formdef.name = 'foobar'
    formdef.url_name = 'foobar'
    formdef.workflow_roles = {'_receiver': 1}
    formdef.fields = [
        fields.StringField(id='1', label='first_name',
        prefill={'type': 'user', 'value': '3'}),
        fields.StringField(id='2', label='last_name',
        prefill={'type': 'user', 'value': '4'}),
    ]
    formdef.store()

    formdata = formdef.data_class()()
    formdata.data = {'1': 'blah', '2': 'xxx'}
    formdata.just_created()
    formdata.store()
    formdata.jump_status('new')

    app = login(get_app(pub))
    resp = app.get('/backoffice/management/').follow()
    resp = resp.click('Global View')
    assert '<td class="cell-user">blah xxx</td>' in resp.text

def test_management_views_with_no_formdefs(pub):
    if not pub.is_using_postgresql():
        pytest.skip('this requires SQL')
        return

    create_user(pub)
    create_environment(pub)
    FormDef.wipe()

    from wcs.sql import get_connection_and_cursor, drop_global_views
    conn, cur = get_connection_and_cursor()
    drop_global_views(conn, cur)
    conn.commit()
    cur.close()

    app = login(get_app(pub))
    resp = app.get('/backoffice/management/forms')
    assert 'This site is currently empty.' in resp.text
    resp = app.get('/backoffice/management/listing')
    assert 'This site is currently empty.' in resp.text

def test_category_in_global_listing(pub):
    if not pub.is_using_postgresql():
        pytest.skip('this requires SQL')
        return

    user = create_user(pub)
    create_environment(pub)

    app = login(get_app(pub))
    resp = app.get('/backoffice/management/listing?limit=500')
    assert not 'category_id' in resp.forms['listing-settings'].fields

    cat1 = Category(name='cat1')
    cat1.store()
    formdef = FormDef.get_by_urlname('form-title')
    formdef.category_id = cat1.id
    formdef.store()

    cat2 = Category(name='cat2')
    cat2.store()
    formdef = FormDef.get_by_urlname('other-form')
    formdef.category_id = cat2.id
    formdef.store()

    resp = app.get('/backoffice/management/listing?limit=500')
    assert 'category_id' in resp.forms['listing-settings'].fields
    assert 'management/other-form/' in resp.text
    assert 'management/form-title/' in resp.text

    resp.forms['listing-settings']['category_id'] = cat1.id
    resp = resp.forms['listing-settings'].submit()
    assert not 'management/other-form/' in resp.text
    assert 'management/form-title/' in resp.text

    resp.forms['listing-settings']['category_id'] = cat2.id
    resp = resp.forms['listing-settings'].submit()
    assert 'management/other-form/' in resp.text
    assert not 'management/form-title/' in resp.text

def test_datetime_in_global_listing(pub):
    if not pub.is_using_postgresql():
        pytest.skip('this requires SQL')
        return

    user = create_user(pub)
    create_environment(pub)

    app = login(get_app(pub))
    resp = app.get('/backoffice/management/listing?limit=500')
    resp.forms['listing-settings']['end'] = '01/01/2010'
    resp = resp.forms['listing-settings'].submit()
    assert resp.text[resp.text.index('<tbody'):].count('<tr') == 0

    resp = app.get('/backoffice/management/listing?limit=500')
    resp.forms['listing-settings']['start'] = '01/01/2010'
    resp = resp.forms['listing-settings'].submit()
    assert resp.text[resp.text.index('<tbody'):].count('<tr') == 37

    resp.forms['listing-settings']['start'] = '01/01/2016'
    resp = resp.forms['listing-settings'].submit()
    assert resp.text[resp.text.index('<tbody'):].count('<tr') == 0

    resp.forms['listing-settings']['start'] = '01/01/16'
    resp = resp.forms['listing-settings'].submit()
    assert resp.text[resp.text.index('<tbody'):].count('<tr') == 0

    resp.forms['listing-settings']['start'] = '01/01/10'
    resp = resp.forms['listing-settings'].submit()
    assert resp.text[resp.text.index('<tbody'):].count('<tr') == 37

    resp.forms['listing-settings']['end'] = '01/01/10'
    resp = resp.forms['listing-settings'].submit()
    assert resp.text[resp.text.index('<tbody'):].count('<tr') == 0

    # check invalid values are simply ignored
    resp.forms['listing-settings']['end'] = 'whatever'
    resp = resp.forms['listing-settings'].submit()
    assert resp.text[resp.text.index('<tbody'):].count('<tr') == 37

def test_global_listing_geojson(pub):
    if not pub.is_using_postgresql():
        pytest.skip('this requires SQL')
        return

    create_user(pub)
    create_environment(pub)

    formdef = FormDef.get_by_urlname('form-title')
    formdef.geolocations = {'base': 'Geolocafoobar'}
    formdef.store()
    for formdata in formdef.data_class().select():
        formdata.geolocations = {'base': {'lat': 48.83, 'lon': 2.32}}
        formdata.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/management/geojson')
    assert len(resp.json['features']) == 17
    assert resp.json['features'][0]['geometry']['coordinates'] == [2.32, 48.83]
    for feature in resp.json['features']:
        assert feature['properties']['status_colour'] == '#66FF00'
        assert feature['properties']['view_label'] == 'View'
        assert feature['properties']['status_name'] == 'New'
        assert feature['properties']['display_fields']
        assert feature['properties']['display_fields'][0]['label'] == 'Name'
        assert feature['properties']['display_fields'][0]['value'].startswith('form title #')

    resp = app.get('/backoffice/management/geojson?q=aa')
    assert len(resp.json['features']) == 5
    resp = app.get('/backoffice/management/geojson?q=bb')
    assert len(resp.json['features']) == 4
    resp = app.get('/backoffice/management/geojson?q=cc')
    assert len(resp.json['features']) == 8

def test_global_map(pub):
    if not pub.is_using_postgresql():
        pytest.skip('this requires SQL')
        return

    create_user(pub)
    create_environment(pub)

    formdef = FormDef.get_by_urlname('form-title')
    formdef.geolocations = {'base': 'Geolocafoobar'}
    formdef.store()
    for formdata in formdef.data_class().select():
        formdata.geolocations = {'base': {'lat': 48.83, 'lon': 2.32}}
        formdata.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/management/listing')
    assert 'Map View' in resp.text
    resp = app.get('/backoffice/management/forms')
    assert 'Map View' in resp.text
    resp = resp.click('Map View')
    assert re.findall(r'data-geojson-url="(.*?)"', resp.text) == ['http://example.net/backoffice/management/geojson?']

    resp = app.get('/backoffice/management/map?q=test')
    assert re.findall(r'data-geojson-url="(.*?)"', resp.text) == ['http://example.net/backoffice/management/geojson?q=test']

def test_formdata_lookup(pub):
    create_user(pub)
    create_environment(pub, set_receiver=False)

    formdef = FormDef.get_by_urlname('form-title')
    formdef.enable_tracking_codes = True
    formdef.store()
    formdata, formdata2 = formdef.data_class().select(order_by='id')[:2]
    code = pub.tracking_code_class()
    code.formdata = formdata

    app = login(get_app(pub))
    resp = app.get('/backoffice/management/').follow()
    assert 'id="lookup-box"' in resp.text
    resp.forms[0]['query'] = formdata.tracking_code
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/management/form-title/%s/' % formdata.id
    resp = resp.follow()
    assert 'The form has been recorded' in resp.text
    assert 'This form has been accessed via its tracking code' in resp.text

    # check there's no access to other formdata
    app.get('http://example.net/backoffice/management/form-title/%s/' % formdata2.id, status=403)

    resp = app.get('/backoffice/management/').follow()
    resp.forms[0]['query'] = 'AAAAAAAA'
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/management/'
    resp = resp.follow().follow()
    assert 'No such tracking code or identifier.' in resp.text

    # check looking up a formdata id
    resp.form['query'] = formdata.get_display_id()
    resp = resp.form.submit()
    assert resp.location == 'http://example.net/backoffice/management/form-title/%s/' % formdata.id

    if pub.is_using_postgresql():
        # check looking up on a custom display_id
        formdata.id_display = '999999'
        formdata.store()
        assert formdata.get_display_id() == '999999'
        resp = app.get('/backoffice/management/').follow()
        resp.form['query'] = formdata.get_display_id()
        resp = resp.form.submit()
        assert resp.location == 'http://example.net/backoffice/management/form-title/%s/' % formdata.id

        # try it from the global listing
        resp = app.get('/backoffice/management/listing')
        assert 'id="lookup-box"' in resp.text
        resp.forms[0]['query'] = formdata.tracking_code
        resp = resp.forms[0].submit()
        assert resp.location == 'http://example.net/backoffice/management/form-title/%s/' % formdata.id
        resp = resp.follow()
        assert 'The form has been recorded' in resp.text

        resp = app.get('/backoffice/management/listing')
        resp.forms[0]['query'] = 'AAAAAAAA'
        resp = resp.forms[0].submit()
        assert resp.location == 'http://example.net/backoffice/management/listing'
        resp = resp.follow()
        assert 'No such tracking code or identifier.' in resp.text

def test_backoffice_sidebar_user_context(pub):
    if not pub.is_using_postgresql():
        pytest.skip('this requires SQL')
        return

    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.text, re.DOTALL)[0].count('<tr') == 17

    # click on a formdata
    resp = resp.click(href='%s/' % number31.id)
    assert (' with the number %s.' % number31.get_display_id()) in resp.text

    # check there's nothing in the sidebar
    assert not '/user-pending-forms' in resp.text

    number31.formdef.digest_template = 'digest of number {{form_number}}'
    number31.user_id = user.id
    number31.store()
    resp = app.get('/backoffice/management/form-title/%s/' % number31.id)
    assert '/user-pending-forms' in resp.text
    user_pending_form_url = re.findall('data-async-url="(.*/user-pending-forms)"', resp.text)[0]
    partial_resp = app.get(user_pending_form_url)
    assert not number31.get_url(backoffice=True) in partial_resp.text
    assert number31.digest in partial_resp.text
    assert '<span class="formname">%s</span>' % number31.formdef.name in partial_resp.text

    # another item with status = new
    number34 = [x for x in form_class.select() if x.data['1'] == 'FOO BAR 33'][0]
    number34.user_id = user.id
    number34.store()

    resp = app.get('/backoffice/management/form-title/%s/' % number31.id)
    assert '/user-pending-forms' in resp.text
    user_pending_form_url = re.findall('data-async-url="(.*/user-pending-forms)"', resp.text)[0]
    partial_resp = app.get(user_pending_form_url)
    assert not number31.get_url(backoffice=True) in partial_resp.text
    assert number34.get_url(backoffice=True) in partial_resp.text

    cat1 = Category(name='cat1')
    cat1.store()

    formdef = FormDef.get_by_urlname('other-form')
    formdef.category_id = cat1.id
    formdef.store()
    other_formdata = formdef.data_class().select()[0]
    other_formdata.user_id = user.id
    other_formdata.store()

    resp = app.get('/backoffice/management/form-title/%s/' % number31.id)
    assert '/user-pending-forms' in resp.text
    user_pending_form_url = re.findall('data-async-url="(.*/user-pending-forms)"', resp.text)[0]
    partial_resp = app.get(user_pending_form_url)
    assert number34.get_url(backoffice=True) in partial_resp.text
    assert other_formdata.get_url(backoffice=True) in partial_resp.text
    # categories are displayed, and current formdata category is on top
    assert '>cat1<' in partial_resp.text
    assert '>Misc<' in partial_resp.text
    assert partial_resp.text.index('>Misc<') < partial_resp.text.index('>cat1<')

def test_count_open(pub):
    if not pub.is_using_postgresql():
        pytest.skip('this requires SQL')
        return
    create_user(pub)

    FormDef.wipe()
    resp = login(get_app(pub)).get('/backoffice/management/count')
    assert resp.json['count'] == 0

    create_environment(pub)
    resp = login(get_app(pub)).get('/backoffice/management/count')
    assert resp.json['count'] == 37

    formdef = FormDef.get_by_urlname('form-title')
    formdef.workflow_roles = {'_receiver': 2} # role the user doesn't have
    formdef.store()
    formdef.data_class().rebuild_security()
    resp = login(get_app(pub)).get('/backoffice/management/count')
    assert resp.json['count'] == 20

    formdef = FormDef.get_by_urlname('form-title')
    formdef.workflow_roles = {'_receiver': 2, '_foobar': 1}
    formdef.store()
    formdef.data_class().rebuild_security()
    resp = login(get_app(pub)).get('/backoffice/management/count')
    assert resp.json['count'] == 20
    resp = login(get_app(pub)).get('/backoffice/management/count?waiting=yes')
    assert resp.json['count'] == 20

    formdef = FormDef.get_by_urlname('form-title')
    workflow = Workflow.get_default_workflow()
    workflow.roles['_foobar'] = 'Foobar'
    workflow.id = '2'
    workflow.store()
    formdef.workflow_id = workflow.id
    formdef.workflow_roles = {'_receiver': 2, '_foobar': '1'}
    formdef.store()
    formdef.data_class().rebuild_security()
    resp = login(get_app(pub)).get('/backoffice/management/count?waiting=no')
    assert resp.json['count'] == 37
    resp = login(get_app(pub)).get('/backoffice/management/count?waiting=yes')
    assert resp.json['count'] == 20
    resp = login(get_app(pub)).get('/backoffice/management/count')
    assert resp.json['count'] == 20

    # check the callback parameter is ignored, that we still get the default
    # criterias when it's set.
    resp = login(get_app(pub)).get('/backoffice/management/count?callback=toto')
    assert "20" in resp.text

def test_count_backoffice_drafts(pub):
    user = create_user(pub)
    create_environment(pub)

    formdef = FormDef.get_by_urlname('form-title')
    formdef.backoffice_submission_roles = user.roles[:]
    formdef.store()

    resp = login(get_app(pub)).get('/backoffice/submission/count')
    assert resp.json['count'] == 0

    formdata1, formdata2, formdata3 = formdef.data_class().select()[:3]
    for formdata in (formdata1, formdata2, formdata3):
        formdata.status = 'draft'
        formdata.store()

    resp = login(get_app(pub)).get('/backoffice/submission/count')
    assert resp.json['count'] == 0

    for formdata in (formdata1, formdata2, formdata3):
        formdata.backoffice_submission = True
        formdata.store()

    resp = login(get_app(pub)).get('/backoffice/submission/count')
    assert resp.json['count'] == 3

    formdata1.data = {}
    formdata1.store()

    resp = login(get_app(pub)).get('/backoffice/submission/count?mode=empty')
    assert resp.json['count'] == 1
    resp = login(get_app(pub)).get('/backoffice/submission/count?mode=existing')
    assert resp.json['count'] == 2

def test_menu_json(pub):
    user = create_user(pub)
    create_environment(pub)
    resp = login(get_app(pub)).get('/backoffice/menu.json')
    menu_json_str = resp.text
    assert len(resp.json) == 1
    assert resp.json[0]['slug'] == 'management'
    assert resp.headers['content-type'] == 'application/json'

    resp = login(get_app(pub)).get('/backoffice/menu.json?jsonpCallback=foo')
    assert resp.text == 'foo(%s);' % menu_json_str
    assert resp.headers['content-type'] == 'application/javascript'

def test_per_user_view(pub):
    if not pub.is_using_postgresql():
        pytest.skip('this requires SQL')
        return

    user = create_user(pub)
    create_environment(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/management/').follow()
    assert 'Per User View' not in resp.text

    if not pub.site_options.has_section('options'):
        pub.site_options.add_section('options')
        pub.site_options.set('options', 'per-user-view', 'true')
        fd = open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w')
        pub.site_options.write(fd)
        fd.close()

    resp = app.get('/backoffice/management/').follow()
    assert 'Per User View' in resp.text
    resp = resp.click(' User View')
    assert 'Use the search field on the right' in resp.text

    resp.form['q'] = 'admin'
    resp = resp.form.submit()

    assert resp.text.count('<tr') == 2 # header + user

    form_class = FormDef.get_by_urlname('form-title').data_class()
    to_match = []
    for formdata in form_class.select():
        if formdata.data['1'] in ('FOO BAR 30', 'FOO BAR 33'):
            formdata.user_id = user.id
            formdata.store()
            to_match.append('/management/form-title/%s/' % formdata.id)

    resp = app.get('/backoffice/management/users/%s/' % user.id)
    for item in to_match:
        assert item in resp.text
    count_li = resp.text.count('<li')

    # check list items are displayed, without links, if we cannot view them.
    formdef = FormDef.get_by_urlname('form-title')
    formdef.workflow_roles['_receiver'] = 'XXX'
    formdef.store()
    formdef.data_class().rebuild_security()

    resp = app.get('/backoffice/management/users/%s/' % user.id)
    for item in to_match:
        assert not item in resp.text # not linked
    assert resp.text.count('<li') == count_li

    # mark formdef so it's not listed in per-user view
    formdef.skip_from_360_view = True
    formdef.store()

    # check formdatas are no longer part of the page
    resp = app.get('/backoffice/management/users/%s/' % user.id)
    assert resp.text.count('<li') == (count_li - 2)

def test_per_user_view_tracking_code(pub, emails, sms_mocking):
    if not pub.is_using_postgresql():
        pytest.skip('this requires SQL')
        return

    user = create_user(pub)
    create_environment(pub)
    app = login(get_app(pub))

    formdef = FormDef.get_by_urlname('form-title')
    form_class = formdef.data_class()
    number31 = [x for x in form_class.select() if x.data['1'] == 'FOO BAR 30'][0]
    number31.user_id = user.id
    number31.store()

    resp = app.get('/backoffice/management/users/%s/' % user.id)
    assert not 'Send tracking code' in resp.text

    formdef.enable_tracking_codes = True
    formdef.store()
    user_view_resp = app.get('/backoffice/management/users/%s/' % user.id)
    assert 'Send tracking code' in user_view_resp.text

    pub.cfg['sms'] = {'mode': 'none'}
    pub.write_cfg()
    resp = user_view_resp.click('Send tracking code')
    assert not 'sms' in resp.form.fields
    assert resp.form['email'].value == user.email

    resp = resp.form.submit()
    resp = resp.follow()
    assert emails.count() == 1
    assert emails.get('Tracking Code reminder')['email_rcpt'] == [user.email]
    assert form_class.get(number31.id).tracking_code in emails.get('Tracking Code reminder')['payload']

    pub.cfg['sms'] = {'mode': 'xx'}
    pub.write_cfg()
    resp = user_view_resp.click('Send tracking code', index=0)
    resp.form['method'].value = 'SMS'
    resp.form['sms'].value = '0123456789'
    resp = resp.form.submit()
    resp = resp.follow()
    assert sms_mocking.sms[-1]['destinations'] == ['0123456789']
    assert form_class.get(number31.id).tracking_code in sms_mocking.sms[-1]['text']

def test_backoffice_resume_folded(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))

    # first access: summary is not folded
    resp = app.get('/backoffice/management/form-title/%s/' % number31.id)
    assert '<div class="section foldable" id="summary">' in resp.text
    # do something: summary is folded
    resp = resp.form.submit('button_commentable')
    resp = resp.follow()
    assert '<div class="section foldable folded" id="summary">' in resp.text

def test_backoffice_backoffice_submission_in_listings(pub):
    create_superuser(pub)
    create_environment(pub)

    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/')
    first_link = re.findall('data-link="(\d+)/?"', resp.text)[0]
    assert not 'backoffice-submission' in resp.text

    formdata = FormDef.get_by_urlname('form-title').data_class().get(first_link)
    formdata.backoffice_submission = True
    formdata.store()

    resp = app.get('/backoffice/management/form-title/')
    assert 'backoffice-submission' in resp.text

def test_backoffice_backoffice_submission_in_global_listing(pub):
    if not pub.is_using_postgresql():
        pytest.skip('this requires SQL')
        return
    create_superuser(pub)
    create_environment(pub)

    app = login(get_app(pub))
    resp = app.get('/backoffice/management/listing?limit=100')
    assert not 'backoffice-submission' in resp.text

    formdef = FormDef.get_by_urlname('form-title')
    formdata = formdef.data_class().get(
            re.findall(r'data-link="(.*?)"', app.get('/backoffice/management/listing').text)[0].split('/')[-2])
    formdata.backoffice_submission = True
    formdata.store()

    resp = app.get('/backoffice/management/listing?limit=100')
    assert 'backoffice-submission' in resp.text

def test_backoffice_advisory_lock(pub):
    create_superuser(pub)
    create_environment(pub)

    second_user = pub.user_class(name='foobar')
    second_user.roles = Role.keys()
    second_user.store()
    account = PasswordAccount(id='foobar')
    account.set_password('foobar')
    account.user_id = second_user.id
    account.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/')
    first_link = re.findall('data-link="(\d+/)?"', resp.text)[0]
    assert not 'advisory-lock' in resp.text

    app2 = login(get_app(pub), username='foobar', password='foobar')
    resp = app2.get('/backoffice/management/form-title/')
    assert not 'advisory-lock' in resp.text

    resp = app.get('/backoffice/management/form-title/' + first_link)
    resp = app2.get('/backoffice/management/form-title/')
    assert 'advisory-lock' in resp.text
    resp = app.get('/backoffice/management/form-title/')
    assert not 'advisory-lock' in resp.text

    if pub.is_using_postgresql():
        # check global view
        resp = app2.get('/backoffice/management/listing?limit=100')
        assert 'advisory-lock' in resp.text
        resp = app.get('/backoffice/management/listing?limit=100')
        assert not 'advisory-lock' in resp.text

    resp = app.get('/backoffice/management/form-title/' + first_link)
    assert not 'Be warned forms of this user are also being looked' in resp.text
    assert 'button_commentable' in resp.text
    assert len(resp.forms)
    resp = app2.get('/backoffice/management/form-title/' + first_link)
    assert 'Be warned forms of this user are also being looked' in resp.text
    assert not 'button_commentable' in resp.text
    assert len(resp.forms) == 0
    # revisit with first user, no change
    resp = app.get('/backoffice/management/form-title/' + first_link)
    assert not 'Be warned forms of this user are also being looked' in resp.text
    assert 'button_commentable' in resp.text
    # back to second
    resp = app2.get('/backoffice/management/form-title/' + first_link)
    assert 'Be warned forms of this user are also being looked' in resp.text
    assert not 'button_commentable' in resp.text
    resp = resp.click('(unlock actions)')
    resp = resp.follow()
    assert 'Be warned forms of this user are also being looked' in resp.text
    assert 'button_commentable' in resp.text
    assert not '(unlock actions)' in resp.text
    assert len(resp.forms)

    # submit action form
    resp.form['comment'] = 'HELLO'
    resp = resp.form.submit('button_commentable')
    # locks are reset after an action
    assert not 'advisory-lock' in app2.get('/backoffice/management/form-title/')
    assert not 'advisory-lock' in app.get('/backoffice/management/form-title/')
    # but as the current user is redirected to the form, a lock will be
    # acquired (unless the user didn't have actions anymore, but it's not the
    # case here)
    resp = resp.follow()
    assert not 'advisory-lock' in app2.get('/backoffice/management/form-title/')
    assert 'advisory-lock' in app.get('/backoffice/management/form-title/')

def test_backoffice_advisory_lock_related_formdatas(pub):
    if not pub.is_using_postgresql():
        pytest.skip('this requires SQL')
        return
    pub.session_manager.session_class.wipe()
    user = create_superuser(pub)
    create_environment(pub)

    formdef = FormDef.get_by_urlname('form-title')
    formdatas = formdef.data_class().select(lambda x: x.status == 'wf-new')

    second_user = pub.user_class(name='foobar')
    second_user.roles = Role.keys()
    second_user.store()
    account = PasswordAccount(id='foobar')
    account.set_password('foobar')
    account.user_id = second_user.id
    account.store()

    third_user = pub.user_class(name='user')
    third_user.store()

    for formdata in formdatas[:2]:
        formdata.user_id = third_user.id
        formdata.store()

    second_formdef = FormDef.get_by_urlname('other-form')
    second_formdef.workflow_roles = {}
    second_formdef.store()
    other_formdatas = second_formdef.data_class().select(lambda x: x.status == 'wf-new')
    other_formdatas[0].user_id = third_user.id
    other_formdatas[0].store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/%s/' % formdatas[0].id)
    assert not 'Be warned forms of this user are also being looked' in resp.text
    app.get(re.findall('data-async-url="(.*/user-pending-forms)"', resp.text)[0])

    app2 = login(get_app(pub), username='foobar', password='foobar')
    resp2 = app2.get('/backoffice/management/form-title/%s/' % formdatas[0].id)
    assert 'Be warned forms of this user are also being looked' in resp2.text
    app2.get(re.findall('data-async-url="(.*/user-pending-forms)"', resp.text)[0])

    # another by the same user
    resp2 = app2.get('/backoffice/management/form-title/%s/' % formdatas[1].id)
    assert 'Be warned forms of this user are also being looked' in resp2.text
    app2.get(re.findall('data-async-url="(.*/user-pending-forms)"', resp.text)[0])

    # another by another user
    resp2 = app2.get('/backoffice/management/form-title/%s/' % formdatas[3].id)
    assert not 'Be warned forms of this user are also being looked' in resp2.text
    app2.get(re.findall('data-async-url="(.*/user-pending-forms)"', resp.text)[0])

    # check another formdef is only marked as visited if the user has potential
    # actions on it.
    session = pub.session_manager.session_class.select(lambda x: x.user == user.id)[0]
    second_formdef.workflow_roles = {'_receiver': 1}
    second_formdef.store()
    other_formdata = second_formdef.data_class().get(other_formdatas[0].id)
    other_formdata.store() # update concerned_roles

    assert not 'formdata-other-form-%d' % other_formdata.id in session.visiting_objects.keys()
    session.visiting_objects = {}
    session.store()

    resp = app.get('/backoffice/management/form-title/%s/' % formdatas[0].id)
    app.get(re.findall('data-async-url="(.*/user-pending-forms)"', resp.text)[0])
    session = pub.session_manager.session_class.select(lambda x: x.user == user.id)[0]
    assert 'formdata-other-form-%d' % other_formdata.id in session.visiting_objects.keys()

def test_backoffice_resubmit(pub):
    user = create_user(pub)
    create_environment(pub)

    wf = Workflow(name='resubmit')
    st1 = wf.add_status('Status1')
    st2 = wf.add_status('Status2')

    resubmit = ResubmitWorkflowStatusItem()
    resubmit.id = '_resubmit'
    resubmit.by = [user.roles[0]]
    st1.items.append(resubmit)
    resubmit.parent = st1

    jump = JumpOnSubmitWorkflowStatusItem()
    jump.id = '_jump'
    jump.status = st2.id
    st1.items.append(jump)
    jump.parent = st1

    register_comment = RegisterCommenterWorkflowStatusItem()
    register_comment.id = '_register'
    register_comment.comment = '<p><a href="[resubmit_formdata_backoffice_url]">resubmitted</a></p>'
    st2.items.append(register_comment)
    register_comment.parent = st2

    wf.store()

    formdef = FormDef.get_by_urlname('form-title')
    formdef.fields[0].varname = 'foo'
    formdef.workflow_id = wf.id
    formdef.store()

    formdef2 = FormDef()
    formdef2.name = 'form title bis'
    formdef2.backoffice_submission_roles = user.roles[:]
    formdef2.fields = [
        fields.StringField(id='2', label='1st field', type='string', varname='foo')
    ]
    formdef2.store()
    formdef2.data_class().wipe()

    formdata = formdef.data_class()()
    formdata.just_created()
    formdata.data= {'1': 'XXX'}
    formdata.store()

    app = login(get_app(pub))
    resp = app.get(formdata.get_url(backoffice=True))
    resp.form['resubmit'].value = formdef2.id
    resp = resp.form.submit('button_resubmit')
    resp = resp.follow()
    assert 'resubmitted' in resp.text
    assert formdef2.data_class().select()[0].status == 'draft'
    assert formdef2.data_class().select()[0].data == {'2': 'XXX'}
    resp = resp.click('resubmitted')
    resp = resp.follow()
    resp = resp.follow()
    assert resp.form['f2'].value == 'XXX'
    assert 'Original form' in resp.text
    assert formdata.get_url(backoffice=True) in resp.text

def test_backoffice_workflow_display_form(pub):
    user = create_user(pub)
    create_environment(pub)

    wf = Workflow.get_default_workflow()
    wf.id = '2'
    wf.store()
    wf = Workflow.get(wf.id)
    status = wf.get_status('new')
    status.items = []
    display_form = FormWorkflowStatusItem()
    display_form.id = '_display_form'
    display_form.by = [user.roles[0]]
    display_form.varname = 'blah'
    display_form.formdef = WorkflowFormFieldsFormDef(item=display_form)
    display_form.formdef.fields.append(
            fields.StringField(
                id='1', label='Test', varname='str', type='string', required=True))
    display_form.formdef.fields.append(
            # mimick special case: https://dev.entrouvert.org/issues/14691
            # item field displayed as radio buttons, with prefill of a value
            # that doesn't exist.
            fields.ItemField(
                id='2', label='Test2', type='item',
                prefill={'type': 'string', 'value': ''},
                display_mode='radio',
                varname='radio',
                items=['a', 'b', 'c'], required=True))
    status.items.append(display_form)
    display_form.parent = status

    jump = JumpOnSubmitWorkflowStatusItem()
    jump.id = '_jump'
    jump.status = 'accepted'
    status.items.append(jump)
    jump.parent = status

    wf.store()
    formdef = FormDef.get_by_urlname('form-title')
    formdef.workflow_id = wf.id
    formdef.store()

    for formdata in formdef.data_class().select():
        if formdata.status == 'wf-new':
            break
    app = login(get_app(pub))
    resp = app.get(formdata.get_url(backoffice=True))
    assert 'f1' in resp.form.fields
    resp.form['f1'] = 'blah'
    # don't fill required radio button
    resp = resp.form.submit('submit')
    assert formdef.data_class().get(formdata.id).status == 'wf-new'
    assert 'There were errors processing your form.' in resp.text
    resp.form['f2'] = 'c'
    resp = resp.form.submit('submit')
    assert formdef.data_class().get(formdata.id).status == 'wf-accepted'
    assert formdef.data_class().get(formdata.id).workflow_data == {
            'blah_var_str': 'blah', 'blah_var_radio': 'c', 'blah_var_radio_raw': 'c'}

def test_backoffice_workflow_form_with_conditions(pub):
    user = create_user(pub)
    create_environment(pub)

    wf = Workflow.get_default_workflow()
    wf.id = '2'
    wf.store()
    wf = Workflow.get(wf.id)
    status = wf.get_status('new')
    status.items = []
    display_form = FormWorkflowStatusItem()
    display_form.id = '_display_form'
    display_form.by = [user.roles[0]]
    display_form.varname = 'blah'
    display_form.formdef = WorkflowFormFieldsFormDef(item=display_form)
    display_form.formdef.fields = [
            fields.StringField(id='1', label='Test', varname='str', type='string', required=True),
            fields.StringField(id='2', label='Test2', varname='str2', type='string', required=True),
    ]
    status.items.append(display_form)
    display_form.parent = status

    wf.store()
    formdef = FormDef.get_by_urlname('form-title')
    formdef.workflow_id = wf.id
    formdef.fields[0].varname = 'plop'
    formdef.store()

    for formdata in formdef.data_class().select():
        if formdata.status == 'wf-new':
            break
    app = login(get_app(pub))
    resp = app.get(formdata.get_url(backoffice=True))
    assert 'f1' in resp.form.fields
    assert 'f2' in resp.form.fields

    # check with static condition
    display_form.formdef.fields = [
            fields.StringField(id='1', label='Test', varname='str', type='string', required=True),
            fields.StringField(id='2', label='Test2', varname='str2',
                type='string', required=True,
                condition={'type': 'django', 'value': '0'}),
    ]
    wf.store()

    resp = login(get_app(pub)).get(formdata.get_url(backoffice=True))
    assert 'f1' in resp.form.fields
    assert 'f2' not in resp.form.fields

    # check condition based on formdata
    display_form.formdef.fields = [
            fields.StringField(id='1', label='Test', varname='str', type='string', required=True),
            fields.StringField(id='2', label='Test2', varname='str2',
                type='string', required=True,
                condition={'type': 'django', 'value': 'form_var_plop'}),
    ]
    wf.store()

    resp = login(get_app(pub)).get(formdata.get_url(backoffice=True))
    assert 'f1' in resp.form.fields
    assert 'f2' in resp.form.fields

    display_form.formdef.fields = [
            fields.StringField(id='1', label='Test', varname='str', type='string', required=True),
            fields.StringField(id='2', label='Test2', varname='str2',
                type='string', required=True,
                condition={'type': 'django', 'value': 'form_var_plop != "xxx"'}),
    ]
    wf.store()

    resp = login(get_app(pub)).get(formdata.get_url(backoffice=True))
    assert 'f1' in resp.form.fields
    assert 'f2' in resp.form.fields

    # check with live conditions
    display_form.formdef.fields = [
            fields.StringField(id='1', label='Test', varname='str', type='string', required=True),
            fields.StringField(id='2', label='Test2', varname='str2',
                type='string', required=True,
                condition={'type': 'django', 'value': 'blah_var_str == "xxx"'}),
    ]
    wf.store()

    resp = login(get_app(pub)).get(formdata.get_url(backoffice=True))
    assert 'f1' in resp.form.fields
    assert 'f2' in resp.form.fields
    assert resp.html.find('div', {'data-field-id': '1'}).attrs['data-live-source'] == 'true'
    assert resp.html.find('div', {'data-field-id': '2'}).attrs.get('style') == 'display: none'
    live_url = resp.html.find('form').attrs['data-live-url']
    assert '/backoffice/' in live_url
    resp.form['f1'] = ''
    live_resp = app.post(live_url, params=resp.form.submit_fields())
    assert live_resp.json['result']['1']['visible']
    assert not live_resp.json['result']['2']['visible']

    resp.form['f1'] = 'xxx'
    live_resp = app.post(live_url, params=resp.form.submit_fields())
    assert live_resp.json['result']['1']['visible']
    assert live_resp.json['result']['2']['visible']

    # check submit doesn't work
    resp = resp.form.submit('submit')
    assert 'There were errors processing your form.' in resp.text

    resp.form['f1'] = 'xxx2'
    live_resp = app.post(live_url, params=resp.form.submit_fields())
    assert live_resp.json['result']['1']['visible']
    assert not live_resp.json['result']['2']['visible']

    # check submit does work when second field is hidden
    resp = resp.form.submit('submit').follow()

    assert formdef.data_class().get(formdata.id).workflow_data == {'blah_var_str': 'xxx2', 'blah_var_str2': None}

def test_backoffice_workflow_form_with_live_data_source(pub):
    user = create_user(pub)
    create_environment(pub)

    wf = Workflow.get_default_workflow()
    wf.id = '2'
    wf.store()
    wf = Workflow.get(wf.id)
    status = wf.get_status('new')
    status.items = []
    display_form = FormWorkflowStatusItem()
    display_form.id = '_display_form'
    display_form.by = [user.roles[0]]
    display_form.varname = 'blah'
    display_form.formdef = WorkflowFormFieldsFormDef(item=display_form)
    display_form.formdef.fields = [
            fields.StringField(id='1', label='Test', varname='str', type='string', required=True),
            fields.ItemField(id='2', label='Test2', varname='str2', type='item', required=True,
                data_source={'type': 'json', 'value': 'https://www.example.invalid/{{ blah_var_str }}'}),
    ]
    status.items.append(display_form)
    display_form.parent = status

    wf.store()
    formdef = FormDef.get_by_urlname('form-title')
    formdef.workflow_id = wf.id
    formdef.fields[0].varname = 'plop'
    formdef.store()

    for formdata in formdef.data_class().select():
        if formdata.status == 'wf-new':
            break

    app = get_app(pub)

    with mock.patch('wcs.qommon.misc.urlopen') as urlopen:
        data1 = {'data': [{'id': 'A', 'text': 'hello'}, {'id': 'B', 'text': 'world'}]}
        data2 = {'data': [{'id': 'C', 'text': 'hello'}, {'id': 'D', 'text': 'world'}]}
        def side_effect(url, *args):
            if 'toto' not in url:
                return StringIO(json.dumps(data1))
            else:
                return StringIO(json.dumps(data2))
        urlopen.side_effect = side_effect

        resp = login(app).get(formdata.get_url(backoffice=True))
        assert 'f1' in resp.form.fields
        assert 'f2' in resp.form.fields
        assert resp.form.fields['f2'][0].options == [(u'A', False, u'hello'), (u'B', False, u'world')]

        live_url = resp.html.find('form').attrs['data-live-url']
        resp.form['f1'] = 'toto'
        live_resp = app.post(live_url + '?modified_field_id=1', params=resp.form.submit_fields())
        assert live_resp.json['result']['2']['items'] == [{u'text': u'hello', u'id': u'C'}, {u'text': u'world', u'id': u'D'}]

def test_backoffice_criticality_in_formdef_listing(pub):
    if not pub.is_using_postgresql():
        pytest.skip('this requires SQL')
        return
    user = create_user(pub)
    create_environment(pub)

    wf = Workflow.get_default_workflow()
    wf.id = '2'
    wf.criticality_levels = [
        WorkflowCriticalityLevel(name='green'),
        WorkflowCriticalityLevel(name='yellow'),
        WorkflowCriticalityLevel(name='red'),
    ]
    wf.store()
    formdef = FormDef.get_by_urlname('form-title')
    formdef.workflow_id = wf.id
    formdef.store()

    formdata1, formdata2, formdata3, formdata4 = [
            x for x in formdef.data_class().select() if x.status == 'wf-new'][:4]

    formdata1.set_criticality_level(1)
    formdata1.store()
    formdata1_str = '>%s<' % formdata1.get_display_id()
    formdata2.set_criticality_level(2)
    formdata2.store()
    formdata2_str = '>%s<' % formdata2.get_display_id()
    formdata3.set_criticality_level(2)
    formdata3.store()
    formdata3_str = '>%s<' % formdata3.get_display_id()
    formdata4_str = '>%s<' % formdata4.get_display_id()

    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/?order_by=-criticality_level&limit=100')
    assert resp.text.index(formdata1_str) > resp.text.index(formdata2_str)
    assert resp.text.index(formdata1_str) > resp.text.index(formdata3_str)
    assert resp.text.index(formdata1_str) < resp.text.index(formdata4_str)

    resp = app.get('/backoffice/management/form-title/?order_by=criticality_level&limit=100')
    assert resp.text.index(formdata1_str) < resp.text.index(formdata2_str)
    assert resp.text.index(formdata1_str) < resp.text.index(formdata3_str)
    assert resp.text.index(formdata1_str) > resp.text.index(formdata4_str)

def test_backoffice_criticality_in_global_listing(pub):
    if not pub.is_using_postgresql():
        pytest.skip('this requires SQL')
        return

    user = create_user(pub)
    create_environment(pub)

    wf = Workflow.get_default_workflow()
    wf.id = '2'
    wf.criticality_levels = [
        WorkflowCriticalityLevel(name='green'),
        WorkflowCriticalityLevel(name='yellow'),
        WorkflowCriticalityLevel(name='red'),
    ]
    wf.store()
    formdef = FormDef.get_by_urlname('form-title')
    formdef.workflow_id = wf.id
    formdef.store()

    formdata1, formdata2, formdata3, formdata4 = [
            x for x in formdef.data_class().select() if x.status == 'wf-new'][:4]

    formdata1.set_criticality_level(1)
    formdata1.store()
    formdata1_str = '>%s<' % formdata1.get_display_id()
    formdata2.set_criticality_level(2)
    formdata2.store()
    formdata2_str = '>%s<' % formdata2.get_display_id()
    formdata3.set_criticality_level(2)
    formdata3.store()
    formdata3_str = '>%s<' % formdata3.get_display_id()
    formdata4_str = '>%s<' % formdata4.get_display_id()

    app = login(get_app(pub))
    resp = app.get('/backoffice/management/listing?order_by=-criticality_level&limit=100')
    assert resp.text.index(formdata1_str) > resp.text.index(formdata2_str)
    assert resp.text.index(formdata1_str) > resp.text.index(formdata3_str)
    assert resp.text.index(formdata1_str) < resp.text.index(formdata4_str)

    resp = app.get('/backoffice/management/listing?order_by=criticality_level&limit=100')
    assert resp.text.index(formdata1_str) < resp.text.index(formdata2_str)
    assert resp.text.index(formdata1_str) < resp.text.index(formdata3_str)
    assert resp.text.index(formdata1_str) > resp.text.index(formdata4_str)

def test_backoffice_criticality_formdata_view(pub):
    user = create_user(pub)
    create_environment(pub)

    wf = Workflow.get_default_workflow()
    wf.id = '2'
    wf.criticality_levels = [
        WorkflowCriticalityLevel(name='green'),
        WorkflowCriticalityLevel(name='yellow'),
        WorkflowCriticalityLevel(name='red'),
    ]
    wf.store()
    formdef = FormDef.get_by_urlname('form-title')
    formdef.workflow_id = wf.id
    formdef.store()

    formdef = FormDef.get_by_urlname('form-title')
    formdata = [x for x in formdef.data_class().select() if x.status == 'wf-new'][0]

    formdata.set_criticality_level(1)
    formdata.store()

    app = login(get_app(pub))
    resp = app.get(formdata.get_url(backoffice=True))
    assert 'Criticality Level: yellow' in resp.text

    formdata.set_criticality_level(2)
    formdata.store()
    resp = app.get(formdata.get_url(backoffice=True))
    assert 'Criticality Level: red' in resp.text


class IHateUnicode(object):
    def __unicode__(self):
        raise Exception('HATE!!')

    def __repr__(self):
        return 'ok'

@pytest.fixture
def local_user():
    get_publisher().user_class.wipe()
    user = get_publisher().user_class()
    user.name = 'Jean Darmette'
    user.email = 'jean.darmette@triffouilis.fr'
    user.name_identifiers = ['0123456789']
    user.store()
    return user

def test_inspect_page(pub, local_user):
    create_user(pub)
    create_environment(pub)

    formdef = FormDef.get_by_urlname('form-title')
    formdata = [x for x in formdef.data_class().select() if x.status == 'wf-new'][0]
    # temper with field 3 structured values

    formdata.data['3_structured'] = {
        'unicode': u'uné',
        'str_but_non_utf8': b'\xed\xa0\x00',  # not actually supposed to happen
        'non_unicode_convertible': IHateUnicode(),
        'very_long_string': '0' * 100000,
    }
    formdata.user_id = local_user.id
    formdata.store()

    from wcs.admin.settings import UserFieldsFormDef
    user_formdef = UserFieldsFormDef(pub)
    user_formdef.fields.append(fields.StringField(id='_first_name', label='name', type='string'))
    user_formdef.fields.append(fields.StringField(id='3', label='test', type='string'))
    user_formdef.store()
    local_user.form_data = {'_first_name': 'toto', '3': 'nono'}
    local_user.set_attributes_from_formdata(local_user.form_data)
    local_user.store()

    resp = login(get_app(pub)).get(formdata.get_url(backoffice=True), status=200)
    assert 'Data Inspector' not in resp.text
    resp = login(get_app(pub)).get('%sinspect' % formdata.get_url(backoffice=True), status=403)

    create_user(pub, is_admin=True)
    resp = login(get_app(pub)).get(formdata.get_url(backoffice=True), status=200)
    resp = resp.click('Data Inspector')

    assert '0' * 1000 in resp.text
    assert len(resp.text) < 100000
    pq = resp.pyquery.remove_namespaces()
    assert (pq('[title="form_var_foo_unicode"]')
            .parents('li').children('div.value span')
            .text() == u'uné')
    assert (pq('[title="form_var_foo_non_unicode_convertible"]')
            .parents('li').children('div.value span')
            .text().startswith('ok '))
    if six.PY2:
        assert (pq('[title="form_var_foo_str_but_non_utf8"]')
                .parents('li').children('div.value span')
                .text() == '\'\\xed\\xa0\\x00\'')
    else:
        assert (pq('[title="form_var_foo_str_but_non_utf8"]')
                .parents('li').children('div.value span')
                .text() == "b'\\xed\\xa0\\x00' (<class 'bytes'>)")

    # don't show «unusable» variables
    assert 'form_f1' not in resp.text
    assert 'form_field_' not in resp.text
    assert 'form_user_field_' not in resp.text
    assert 'form_user_f3' not in resp.text
    assert 'form_user_f_' not in resp.text

    # check functions
    assert re.findall('Recipient.*foobar', resp.text)

    role = Role(name='plop')
    role.store()
    formdata.workflow_roles = {'_receiver': role.id}
    formdata.store()
    resp = login(get_app(pub)).get('%sinspect' % formdata.get_url(backoffice=True), status=200)
    assert re.findall('Recipient.*plop', resp.text)

    workflow = Workflow.get_default_workflow()
    workflow.id = None
    workflow.roles.update({'_plop': 'New Function'})
    workflow.store()
    formdef.workflow_id = workflow.id
    formdef.store()
    resp = login(get_app(pub)).get('%sinspect' % formdata.get_url(backoffice=True), status=200)
    assert re.findall('New Function.*unset', resp.text)

    formdata.workflow_roles = {'_receiver': role.id, '_plop': '123'}
    formdata.store()
    resp = login(get_app(pub)).get('%sinspect' % formdata.get_url(backoffice=True), status=200)
    assert re.findall('New Function.*(deleted)', resp.text)

    # test tools
    app = login(get_app(pub))
    resp = app.get('%sinspect' % formdata.get_url(backoffice=True), status=200)
    assert "Test tool" in resp.text

    resp.form['test_mode'] = 'python-condition'

    resp.form['python-condition'] = 'len(form_name) == 10'  # "form title"
    resp = resp.form.submit()
    assert 'Condition result' in resp.text
    assert 'result-true' in resp.text
    assert '<code>True</code>' in resp.text
    resp.form['python-condition'] = 'len(form_name) == 5'
    resp = resp.form.submit()
    assert 'Condition result' in resp.text
    assert 'result-false' in resp.text
    assert '<code>False</code>' in resp.text
    resp.form['python-condition'] = 'form_do_not_exist == 3'
    resp = resp.form.submit()
    assert 'Condition result' not in resp.text
    assert 'Failed to evaluate condition' in resp.text
    assert 'NameError' in resp.text

    resp.form['test_mode'] = 'django-condition'
    resp.form['django-condition'] = 'form_name|length == 10'
    resp = resp.form.submit()
    assert 'Condition result' in resp.text
    assert 'result-true' in resp.text
    resp.form['django-condition'] = 'form_name|length == 5'
    resp = resp.form.submit()
    assert 'Condition result' in resp.text
    assert 'result-false' in resp.text
    resp.form['django-condition'] = 'foo bar'
    resp = resp.form.submit()
    assert 'Condition result' not in resp.text
    assert 'TemplateSyntaxError: Unused' in resp.text

    resp.form['django-condition'] = 'form_name|length == 5'
    ajax_resp = app.post('%sinspect-tool' % formdata.get_url(backoffice=True), params=resp.form.submit_fields())
    assert ajax_resp.text.startswith('<div class="test-tool-result')

    resp.form['test_mode'] = 'template'
    resp.form['template'] = '{{ form_name }}'
    resp = resp.form.submit()
    assert 'Template rendering' in resp.text
    assert '<div class="test-tool-result-plain">form title</div>' in resp.text
    assert 'HTML Source' not in resp.text
    resp.form['template'] = '<p>{{ form_name }}</p>'
    resp = resp.form.submit()
    assert 'Template rendering' in resp.text
    assert '<p>form title</p>' in resp.text
    assert 'HTML Source' in resp.text
    resp.form['template'] = '{% for x in 1 %}ok{% endfor %}'
    resp = resp.form.submit()
    assert 'Failed to evaluate template' in resp.text
    assert 'TypeError' in resp.text
    resp.form['template'] = '{% for x in 1 %}ok{% end %}'
    resp = resp.form.submit()
    assert 'syntax error' in resp.text
    assert 'Invalid block tag' in resp.text
    resp.form['template'] = ''

    resp.form['test_mode'] = 'html_template'
    resp.form['html_template'] = '<p>{{ form_name }}</p>'
    resp = resp.form.submit()
    resp = resp.form.submit()
    assert 'Template rendering' in resp.text
    assert '<p>form title</p>' in resp.text
    assert 'HTML Source' in resp.text
    resp.form['html_template'] = '{% for x in 1 %}ok{% endfor %}'
    resp = resp.form.submit()
    assert 'Failed to evaluate HTML template' in resp.text
    assert 'TypeError' in resp.text
    resp.form['html_template'] = '{% for x in 1 %}ok{% end %}'
    resp = resp.form.submit()
    assert 'syntax error' in resp.text
    assert 'Invalid block tag' in resp.text

def test_workflow_jump_previous(pub):
    user = create_user(pub)
    create_environment(pub)

    wf = Workflow(name='jump around')
    #       North
    #     /      \
    # West <----> East
    #   |          |
    #   |         autojump
    #    |         |
    #     \       /
    #       South

    st1 = wf.add_status('North')
    st1.id = 'north'
    st2 = wf.add_status('West')
    st2.id = 'west'
    st3 = wf.add_status('East')
    st3.id = 'east'
    st4 = wf.add_status('Autojump')
    st4.id = 'autojump'
    st5 = wf.add_status('South')
    st5.id = 'south'

    button_by_id = {}

    def add_jump(label, src, dst_id):
        jump = ChoiceWorkflowStatusItem()
        jump.id = str(random.random())
        jump.label = label
        jump.by = ['logged-users']
        jump.status = dst_id
        src.items.append(jump)
        jump.parent = src
        if dst_id != '_previous':
            jump.set_marker_on_status = True
        button_by_id[label] = 'button%s' % jump.id
        return jump

    add_jump('Go West', st1, st2.id)
    add_jump('Go East', st1, st3.id)
    add_jump('Go South', st2, st5.id)
    add_jump('Go Autojump', st3, st4.id)
    add_jump('Go Back', st5, '_previous')

    add_jump('Jump West', st3, st2.id)
    add_jump('Jump East', st2, st3.id)

    jump = JumpWorkflowStatusItem()
    jump.id = '_auto-jump'
    jump.status = st5.id
    st4.items.append(jump)
    jump.parent = st4

    wf.store()

    formdef = FormDef.get_by_urlname('form-title')
    formdef.data_class().wipe()
    formdef.workflow = wf
    formdef.store()

    formdata = formdef.data_class()()
    formdata.data = {}
    formdata.just_created()
    formdata.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/%s/' % formdata.id)

    # jump around using buttons
    resp = resp.form.submit(button_by_id['Go West']).follow()  # push (north)
    assert formdef.data_class().get(formdata.id).status == 'wf-%s' % st2.id
    resp = resp.form.submit(button_by_id['Go South']).follow() # push (north, west)
    assert formdef.data_class().get(formdata.id).status == 'wf-%s' % st5.id
    resp = resp.form.submit(button_by_id['Go Back']).follow()  # pop (north)
    assert formdef.data_class().get(formdata.id).status == 'wf-%s' % st2.id
    resp = resp.form.submit(button_by_id['Go South']).follow() # push (north, west)
    assert formdef.data_class().get(formdata.id).status == 'wf-%s' % st5.id
    resp = resp.form.submit(button_by_id['Go Back']).follow()  # pop (north)
    assert formdef.data_class().get(formdata.id).status == 'wf-%s' % st2.id
    resp = resp.form.submit(button_by_id['Jump East']).follow() # push (north, west)
    assert formdef.data_class().get(formdata.id).status == 'wf-%s' % st3.id
    resp = resp.form.submit(button_by_id['Go Autojump']).follow() # push (north, west, east)
    assert formdef.data_class().get(formdata.id).status == 'wf-%s' % st5.id

    # check markers are displayed in /inspect page
    user.is_admin = True
    user.store()
    resp2 = app.get('/backoffice/management/form-title/%s/inspect' % formdata.id)
    assert 'Markers Stack' in resp2.text
    assert '<span class="status">East</span>' in resp2.text
    assert '<span class="status">West</span>' in resp2.text
    assert '<span class="status">North</span>' in resp2.text
    assert resp2.text.find('<span class="status">East</span>') < resp2.text.find('<span class="status">West</span>')
    assert resp2.text.find('<span class="status">West</span>') < resp2.text.find('<span class="status">North</span>')

    resp = resp.form.submit(button_by_id['Go Back']).follow() # pop (north, west)
    assert formdef.data_class().get(formdata.id).status == 'wf-%s' % st3.id

    # and do a last jump using the API
    formdata = formdef.data_class().get(formdata.id)
    formdata.jump_status('_previous') # pop (north)
    assert formdata.status == 'wf-%s' % st2.id

    formdata = formdef.data_class().get(formdata.id)
    formdata.jump_status('_previous') # pop ()
    assert formdata.status == 'wf-%s' % st1.id

def test_workflow_jump_previous_on_submit(pub):
    user = create_user(pub)
    create_environment(pub)

    wf = Workflow(name='blah')
    st1 = wf.add_status('North')
    st1.id = 'north'
    st2 = wf.add_status('South')
    st2.id = 'south'

    commentable = CommentableWorkflowStatusItem()
    commentable.id = '_commentable'
    commentable.by = ['_submitter', '_receiver']
    commentable.button_label = 'CLICK ME!'

    st1.items.append(commentable)
    commentable.parent = st1

    jump = JumpOnSubmitWorkflowStatusItem()
    jump.id = '_jump'
    jump.status = st2.id
    st1.items.append(jump)
    jump.parent = st1
    jump.set_marker_on_status = True

    back = ChoiceWorkflowStatusItem()
    back.id = '_back'
    back.label = 'Back'
    back.by = ['_receiver']
    back.status = '_previous'
    st2.items.append(back)
    back.parent = st2

    wf.store()

    formdef = FormDef.get_by_urlname('form-title')
    formdef.data_class().wipe()
    formdef.workflow = wf
    formdef.store()

    formdata = formdef.data_class()()
    formdata.data = {}
    formdata.just_created()
    formdata.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/%s/' % formdata.id)
    resp.form['comment'] = 'HELLO WORLD'
    resp = resp.form.submit('button_commentable')
    resp = resp.follow()

    assert formdef.data_class().get(formdata.id).status == 'wf-south'
    assert formdef.data_class().get(formdata.id).workflow_data['_markers_stack']
    resp = resp.form.submit('button_back')
    resp = resp.follow()

    assert formdef.data_class().get(formdata.id).status == 'wf-north'
    assert not formdef.data_class().get(formdata.id).workflow_data['_markers_stack']

def test_workflow_jump_previous_auto(pub):
    user = create_user(pub)
    create_environment(pub)

    wf = Workflow(name='blah')
    st1 = wf.add_status('North')
    st1.id = 'north'
    st2 = wf.add_status('South')
    st2.id = 'south'

    jump = JumpWorkflowStatusItem()
    jump.id = '_auto-jump'
    jump.set_marker_on_status = True
    jump.status = st2.id
    st1.items.append(jump)
    jump.parent = st1

    back = ChoiceWorkflowStatusItem()
    back.id = '_back'
    back.label = 'Back'
    back.by = ['_receiver']
    back.status = '_previous'
    st2.items.append(back)
    back.parent = st2

    wf.store()

    formdef = FormDef.get_by_urlname('form-title')
    formdef.data_class().wipe()
    formdef.workflow = wf
    formdef.store()

    formdata = formdef.data_class()()
    formdata.data = {}
    formdata.just_created()
    formdata.store()
    formdata.perform_workflow()
    assert formdata.status == 'wf-south'
    assert formdata.workflow_data['_markers_stack'] == [{'status_id': 'north'}]

    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/%s/' % formdata.id)
    resp = resp.form.submit('button_back')
    resp = resp.follow()

    # jumped and got back to north, then re-jump to south again (auto-jump)
    formdata = formdef.data_class().get(formdata.id)
    statuses = [evo.status for evo in formdata.evolution]
    assert statuses == ['wf-north', 'wf-south', 'wf-north', 'wf-south']
    # formdata went through north->south auto-jump again, status and marker are still here
    assert formdata.status == 'wf-south'
    assert formdata.workflow_data['_markers_stack'] == [{'status_id': 'north'}]

def test_backoffice_fields(pub):
    user = create_user(pub)
    create_environment(pub)

    wf = Workflow(name='bo fields')
    wf.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(wf)
    wf.backoffice_fields_formdef.fields = [
        fields.StringField(id='bo1', label='1st backoffice field',
            type='string', varname='backoffice_blah', required=False),
    ]
    st1 = wf.add_status('Status1')
    wf.store()

    formdef = FormDef.get_by_urlname('form-title')
    formdef.workflow_id = wf.id
    formdef.store()

    formdata = formdef.data_class()()
    formdata.data = {}
    formdata.just_created()
    formdata.store()

    app = login(get_app(pub))
    resp = app.get(formdata.get_url(backoffice=True))
    assert not 'Backoffice Data' in resp.text
    assert not '1st backoffice field' in resp.text

    formdata.data = {'bo1': 'HELLO WORLD'}
    formdata.store()
    resp = app.get(formdata.get_url(backoffice=True))
    assert 'Backoffice Data' in resp.text
    assert '1st backoffice field' in resp.text
    assert 'HELLO WORLD' in resp.text

    wf.backoffice_fields_formdef.fields = [
        fields.StringField(id='bo1', label='1st backoffice field',
            type='string', varname='backoffice_blah', required=True),
    ]
    wf.store()

    formdata = formdef.data_class()()
    formdata.data = {}
    formdata.just_created()
    formdata.store()

    app = login(get_app(pub))
    resp = app.get(formdata.get_url(backoffice=True))
    assert 'Backoffice Data' in resp.text
    assert 'Not set' in resp.text

def test_backoffice_logged_errors(pub):
    Workflow.wipe()
    workflow = Workflow.get_default_workflow()
    workflow.id = '12'
    jump = JumpWorkflowStatusItem()
    jump.id = '_jump'
    jump.status = 'rejected'
    jump.condition = {'type': 'python', 'value': '1//0'}  # ZeroDivisionError
    st1 = workflow.possible_status[0]
    st1.items.insert(0, jump)
    jump.parent = st1
    workflow.store()

    FormDef.wipe()
    formdef = FormDef()
    formdef.id = '34'
    formdef.workflow = workflow
    formdef.name = 'test'
    formdef.confirmation = False
    formdef.fields = []
    formdef.store()

    LoggedError.wipe()

    create_superuser(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/%s/' % formdef.id)
    assert not 'ZeroDivisionError' in resp.text
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    assert not 'ZeroDivisionError' in resp.text

    app = get_app(pub)
    resp = app.get('/test/')
    resp = resp.form.submit('submit').follow()
    resp = resp.form.submit('submit')
    assert LoggedError.count() == 1

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/%s/' % formdef.id)
    assert 'Failed to evaluate condition' in resp.text
    assert 'error ZeroDivisionError' in resp.text
    resp = resp.click('1 error')

    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp2 = resp.click('1 error')
    assert 'Failed to evaluate condition' in resp2.text
    assert 'error ZeroDivisionError' in resp2.text
    resp = resp2.click('Failed to evaluate condition')
    assert 'ZeroDivisionError: integer division or modulo by zero' in resp.text
    assert 'Python Expression: <code>1//0</code>' in resp.text
    assert not 'Acked' in resp.text
    resp = resp.click('Ack').follow()
    assert 'Acked' in resp.text
    assert LoggedError.select()[0].acked is True
    resp = resp.click('Delete').follow()
    assert LoggedError.count() == 0

    pub.cfg.update({'debug': {'error_email': None}})
    pub.write_cfg()

    app = get_app(pub)
    resp = app.get('/test/')
    resp = resp.form.submit('submit').follow()
    resp = resp.form.submit('submit')
    assert LoggedError.count() == 1

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    assert 'Failed to evaluate condition' in resp2.text
    assert 'error ZeroDivisionError' in resp2.text
    resp2 = resp.click('1 error')
    resp = resp2.click('Failed to evaluate condition')
    assert 'href="http://example.net/backoffice/management/test/' in resp.text

    # very long error string (check it creates a viable tech_id)
    jump.condition = {'type': 'python', 'value': 'x'*500}  # NameError + very long string
    workflow.store()

    app = get_app(pub)
    resp = app.get('/test/')
    resp = resp.form.submit('submit').follow()
    resp = resp.form.submit('submit')
    assert LoggedError.count() == 2

    # remove formdef
    FormDef.wipe()
    resp = resp2.click('Failed to evaluate condition')
    assert not 'href="http://example.net/backoffice/management/test/' in resp.text

def test_backoffice_formdata_named_wscall(http_requests, pub):
    user = create_user(pub)
    create_environment(pub)

    NamedWsCall.wipe()

    wscall = NamedWsCall()
    wscall.name = 'Hello world'
    wscall.request = {'url': 'http://remote.example.net/json'}
    wscall.store()
    assert wscall.slug == 'hello_world'

    formdef = FormDef()
    formdef.name = 'test'
    formdef.backoffice_submission_roles = user.roles[:]
    formdef.fields = [
        fields.CommentField(id='7', label='X[webservice.hello_world.foo]Y', type='comment'),
    ]
    formdef.store()
    formdef.data_class().wipe()

    app = login(get_app(pub))

    resp = app.get('/backoffice/submission/test/')
    assert resp.html.find('div', {'data-field-id': '7'}).text.strip() == 'XbarY'

    # check with publisher variable in named webservice call
    if not pub.site_options.has_section('variables'):
        pub.site_options.add_section('variables')
    pub.site_options.set('variables', 'example_url', 'http://remote.example.net/')
    with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
        pub.site_options.write(fd)

    wscall = NamedWsCall()
    wscall.name = 'Hello world'
    wscall.request = {'url': '[example_url]json'}
    wscall.store()

    resp = app.get('/backoffice/submission/test/')
    assert resp.html.find('div', {'data-field-id': '7'}).text.strip() == 'XbarY'

    # django-templated URL
    wscall.request = {'url': '{{ example_url }}json'}
    wscall.store()
    resp = app.get('/backoffice/submission/test/')
    assert resp.html.find('div', {'data-field-id': '7'}).text.strip() == 'XbarY'

    # webservice call in django template
    formdef.fields = [
        fields.CommentField(id='7', label='dja-{{ webservice.hello_world.foo}}-ngo', type='comment'),
    ]
    formdef.store()
    formdef.data_class().wipe()
    resp = app.get('/backoffice/submission/test/')
    assert resp.html.find('div', {'data-field-id': '7'}).text.strip() == 'dja-bar-ngo'

def test_backoffice_session_var(pub):
    open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w').write('''[options]
query_string_allowed_vars = foo,bar
''')

    user = create_user(pub)
    create_environment(pub)

    formdef = FormDef()
    formdef.name = 'test'
    formdef.backoffice_submission_roles = user.roles[:]
    formdef.fields = [
        fields.CommentField(id='7', label='X[session_var_foo]Y', type='comment'),
    ]
    formdef.store()
    formdef.data_class().wipe()

    app = login(get_app(pub))

    resp = app.get('/backoffice/submission/test/?session_var_foo=bar')
    assert resp.location.endswith('/backoffice/submission/test/')
    resp = resp.follow()
    assert resp.html.find('div', {'data-field-id': '7'}).text.strip() == 'XbarY'

    # django template
    formdef.fields = [
        fields.CommentField(id='7', label='d{{ session_var_foo }}o', type='comment'),
    ]
    formdef.store()
    formdef.data_class().wipe()
    resp = app.get('/backoffice/submission/test/?session_var_foo=jang')
    assert resp.location.endswith('/backoffice/submission/test/')
    resp = resp.follow()
    assert resp.html.find('div', {'data-field-id': '7'}).text.strip() == 'django'

def test_backoffice_display_message(pub):
    user = create_user(pub)
    create_environment(pub)

    workflow = Workflow(name='test')
    st1 = workflow.add_status('Status1', 'st1')

    display1 = DisplayMessageWorkflowStatusItem()
    display1.message = 'message-to-all'
    display1.to = []
    st1.items.append(display1)
    display1.parent = st1

    display2 = DisplayMessageWorkflowStatusItem()
    display2.message = 'message-to-submitter'
    display2.to = ['_submitter']
    st1.items.append(display2)
    display2.parent = st1

    display3 = DisplayMessageWorkflowStatusItem()
    display3.message = 'message-to-receiver'
    display3.to = [user.roles[0]]
    st1.items.append(display3)
    display3.parent = st1

    workflow.store()

    formdef = FormDef.get_by_urlname('form-title')
    formdef.workflow = workflow
    formdef.store()

    formdata = formdef.data_class().select()[0]
    formdata.status = 'wf-st1'
    formdata.store()

    app = login(get_app(pub))
    resp = app.get(formdata.get_url(backoffice=True))

    assert 'message-to-all' in resp.text
    assert 'message-to-submitter' not in resp.text
    assert 'message-to-receiver' in resp.text

    # display first message at the bottom of the page
    display1.position = 'bottom'
    workflow.store()
    resp = app.get(formdata.get_url(backoffice=True))
    assert resp.text.index('message-to-all') > resp.text.index('message-to-receiver')

    # display first message on top of actions
    display1.position = 'actions'
    workflow.store()

    resp = app.get(formdata.get_url(backoffice=True))
    assert not 'message-to-all' in resp.text  # no actions no message

    again = ChoiceWorkflowStatusItem()
    again.id = '_again'
    again.label = 'Again'
    again.by = ['_receiver']
    again.status = st1.id
    st1.items.append(again)
    again.parent = st1
    workflow.store()

    resp = app.get(formdata.get_url(backoffice=True))
    assert 'message-to-all' in resp.text
    assert resp.text.index('message-to-all') > resp.text.index('message-to-receiver')

def test_backoffice_forms_condition_on_button(pub):
    create_superuser(pub)
    create_environment(pub, set_receiver=True)

    workflow = Workflow.get_default_workflow()
    workflow.id = '2'
    workflow.store()

    formdef = FormDef.get_by_urlname('form-title')
    formdef.workflow = workflow
    formdef.store()

    # move some forms from new to accepted
    for i, formdata in enumerate(formdef.data_class().select(lambda x: x.status == 'wf-new')):
        if i % 2:
            formdata.status = 'wf-accepted'
            formdata.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/')
    resp = resp.click('Management', index=0)
    resp = resp.follow()
    assert '17 open on 50' in resp.text

    formdata = [x for x in formdef.data_class().select() if x.status == 'wf-new'][0]
    resp = app.get(formdata.get_url(backoffice=True))
    assert 'button_commentable' in resp.text
    assert 'button_accept' in resp.text
    assert 'button_reject' in resp.text

    # commentable
    workflow.possible_status[1].items[0].condition = {'type': 'python', 'value': 'False'}
    # reject
    workflow.possible_status[1].items[2].condition = {'type': 'python', 'value': 'False'}
    workflow.store()

    resp = app.get(formdata.get_url(backoffice=True))
    assert 'button_commentable' not in resp.text
    assert 'button_accept' in resp.text
    assert 'button_reject' not in resp.text

    formdef = FormDef.get_by_urlname('form-title')
    assert formdef.data_class().get(formdata.id).actions_roles == set(['1'])

    # accept
    workflow.possible_status[1].items[1].condition = {'type': 'python', 'value': 'False'}
    workflow.store()

    resp = app.get(formdata.get_url(backoffice=True))
    assert 'button_commentable' not in resp.text
    assert 'button_accept' not in resp.text
    assert 'button_reject' not in resp.text

    formdef = FormDef.get_by_urlname('form-title')
    assert formdef.data_class().get(formdata.id).actions_roles == set()

    app = login(get_app(pub))
    resp = app.get('/backoffice/')
    resp = resp.click('Management', index=0)
    resp = resp.follow()
    assert '8 open on 50' in resp.text  # only the accepted ones

def test_workflow_inspect_page(pub):
    create_superuser(pub)
    create_environment(pub, set_receiver=True)

    workflow = Workflow.get_default_workflow()
    workflow.id = '2'

    st1 = workflow.add_status('Status1')
    jump = JumpWorkflowStatusItem()
    jump.id = '_jump'
    jump.timeout = '=86400'
    jump.status = 'finished'
    st1.items.append(jump)
    jump.parent = st1
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/%s/inspect' % workflow.id)
    assert '=86400' in resp.text

    jump.timeout = '82800'
    workflow.store()
    resp = app.get('/backoffice/workflows/%s/inspect' % workflow.id)
    assert '23 hours' in resp.text

def test_workflow_comment_required(pub):
    user = create_user(pub)
    create_environment(pub)

    wf = Workflow(name='blah')
    st1 = wf.add_status('Comment')
    st1.id = 'comment'
    commentable = CommentableWorkflowStatusItem()
    commentable.id = '_commentable'
    commentable.by = ['_submitter', '_receiver']
    st1.items.append(commentable)
    commentable.parent = st1
    wf.store()

    formdef = FormDef.get_by_urlname('form-title')
    formdef.data_class().wipe()
    formdef.workflow = wf
    formdef.store()

    formdata = formdef.data_class()()
    formdata.data = {}
    formdata.just_created()
    formdata.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/management/form-title/%s/' % formdata.id)
    assert 'widget-required' not in resp.text
    resp.form['comment'] = 'HELLO WORLD 1'
    resp = resp.form.submit('button_commentable')
    resp = resp.follow()
    assert 'HELLO WORLD 1' in resp.text
    assert 'widget-required' not in resp.text
    resp.form['comment'] = None
    resp = resp.form.submit('button_commentable')
    resp = resp.follow()

    commentable.required = True
    wf.store()
    resp = app.get('/backoffice/management/form-title/%s/' % formdata.id)
    assert 'widget-required' in resp.text
    resp.form['comment'] = '  '  # spaces == empty
    resp = resp.form.submit('button_commentable')
    assert 'widget-with-error' in resp.text
    resp.form['comment'] = 'HELLO WORLD 2'
    resp = resp.form.submit('button_commentable')
    resp = resp.follow()
    assert 'widget-with-error' not in resp.text
    assert 'HELLO WORLD 2' in resp.text

def test_carddata_management(pub, studio):
    user = create_user(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/')
    assert not 'Cards' in resp.text
    carddef = CardDef()
    carddef.name = 'foo'
    carddef.fields = [
        fields.StringField(id='1', label='Test', type='string', varname='foo'),
    ]
    carddef.store()

    resp = app.get('/backoffice/')
    assert not 'Cards' in resp.text

    carddef.backoffice_submission_roles = user.roles
    carddef.store()
    resp = app.get('/backoffice/')
    assert 'Cards' in resp.text

    carddef.backoffice_submission_roles = None
    carddef.workflow_roles = {'_editor': user.roles[0]}
    carddef.store()
    resp = app.get('/backoffice/')
    assert 'Cards' in resp.text

    resp = app.get('/backoffice/data/')
    resp = resp.click('foo')
    assert 'Add' not in resp.text

    carddef.backoffice_submission_roles = user.roles
    carddef.store()

    resp = app.get('/backoffice/data/')
    resp = resp.click('foo')
    assert resp.text.count('<tr') == 1  # header
    assert 'Add' in resp.text
    resp = resp.click('Add')
    resp.form['f1'] = 'blah'
    resp = resp.form.submit('submit')
    assert resp.location.endswith('/backoffice/data/foo/1/')
    resp = resp.follow()
    assert 'Edit Card' in resp.text
    assert 'Delete Card' in resp.text

    resp = app.get('/backoffice/data/')
    resp = resp.click('foo')
    assert resp.text.count('<tr') == 2  # header + row of data
