Projet

Général

Profil

0001-backoffice-make-it-possible-to-submit-forms-from-the.patch

Frédéric Péters, 19 juillet 2015 17:59

Télécharger (19,4 ko)

Voir les différences:

Subject: [PATCH] backoffice: make it possible to submit forms from the
 backoffice (#7052)

 tests/test_admin_pages.py           |   6 +-
 tests/test_backoffice_pages.py      |  58 ++++++++++++++
 wcs/admin/forms.py                  |  55 ++++++++-----
 wcs/backoffice/root.py              |   7 +-
 wcs/backoffice/submission.py        | 149 ++++++++++++++++++++++++++++++++++++
 wcs/formdef.py                      |   1 +
 wcs/forms/root.py                   |  24 ++++--
 wcs/qommon/static/css/dc2/admin.css |   3 +
 8 files changed, 274 insertions(+), 29 deletions(-)
 create mode 100644 wcs/backoffice/submission.py
tests/test_admin_pages.py
459 459

  
460 460
    app = login(get_app(pub))
461 461
    resp = app.get('/backoffice/forms/1/')
462
    resp = resp.click('change', href='roles')
462
    resp = resp.click('change', href='roles', index=0)
463 463
    resp = resp.forms[0].submit('cancel')
464 464

  
465 465
    resp = app.get('/backoffice/forms/1/')
466
    resp = resp.click('change', href='roles')
466
    resp = resp.click('change', href='roles', index=0)
467 467
    resp.forms[0]['roles$element0'].value = role.name
468 468
    resp = resp.forms[0].submit('submit')
469 469
    assert FormDef.get(1).roles == [role.id]
......
826 826
    assert resp.forms[0]['workflow_id'].value
827 827

  
828 828
    resp = app.get('/backoffice/forms/1/')
829
    resp = resp.click('change', href='roles')
829
    resp = resp.click('change', href='roles', index=0)
830 830
    assert resp.forms[0]['roles$element0'].value == 'Logged Users'
831 831
    assert resp.forms[0]['roles$element1'].value == 'ZAB'
832 832

  
tests/test_backoffice_pages.py
453 453
    resp.forms[0]['end'] = '2014-12-31'
454 454
    resp = resp.forms[0].submit()
455 455
    assert 'Total count: 20' in resp.body
456

  
457
def test_backoffice_submission(pub):
458
    user = create_user(pub)
459
    create_environment(pub)
460

  
461
    app = login(get_app(pub))
462
    resp = app.get('/backoffice/')
463
    assert 'Submission' in resp.body
464

  
465
    resp = resp.click('Submission', index=0)
466
    formdef = FormDef.select()[0]
467
    assert not formdef.url_name in resp.body
468

  
469
    formdef.backoffice_submission_roles = user.roles[:]
470
    formdef.store()
471
    resp = app.get('/backoffice/submission/')
472
    assert formdef.url_name in resp.body
473

  
474
    resp = resp.click(formdef.name)
475
    resp.form['f1'] = 'test submission'
476
    resp.form['f2'] = 'baz'
477
    resp.form['f3'] = 'C'
478
    resp = resp.form.submit('submit')
479
    assert 'Check values then click submit.' in resp.body
480

  
481
    # going back to first page, to check
482
    resp = resp.form.submit('previous')
483
    assert resp.form['f1'].value == 'test submission'
484
    resp = resp.form.submit('submit')
485

  
486
    # final submit
487
    resp = resp.form.submit('submit')
488

  
489
    formdata_no = resp.location.split('/')[-2]
490
    data_class = formdef.data_class()
491
    assert data_class.get(formdata_no).data['1'] == 'test submission'
492
    assert data_class.get(formdata_no).data['2'] == 'baz'
493
    assert data_class.get(formdata_no).status == 'wf-new'
494
    assert data_class.get(formdata_no).user is None
495

  
496
    resp = resp.follow() # get to the formdata page
497

  
498
    formdata_count = data_class.count()
499

  
500
    # test submission when agent is not receiver
501
    formdef.workflow_roles = {}
502
    formdef.store()
503
    resp = app.get('/backoffice/submission/')
504
    resp = resp.click(formdef.name)
505
    resp.form['f1'] = 'test submission'
506
    resp.form['f2'] = 'baz'
507
    resp.form['f3'] = 'C'
508
    resp = resp.form.submit('submit') # to validation screen
509
    resp = resp.form.submit('submit') # final submit
510
    assert resp.location == 'http://example.net/backoffice/submission/'
511
    resp = resp.follow() # should go back to submission screen
512

  
513
    assert data_class.count() == formdata_count + 1
wcs/admin/forms.py
175 175
                  ('workflow-status-remapping', 'workflow_status_remapping'),
176 176
                  'roles', 'title', 'options', ('acl-read', 'acl_read'),
177 177
                  'overwrite', 'qrcode', 'information',
178
                  ('public-url', 'public_url')]
178
                  ('public-url', 'public_url'),
179
                  ('backoffice-submission-roles', 'backoffice_submission_roles'),]
179 180

  
180 181
    def __init__(self, component):
181 182
        try:
......
299 300
            r += htmltext('</ul>')
300 301
            r += htmltext('</li>')
301 302

  
302
        r += htmltext('<li>%s ') % _('User Roles:')
303
        if self.formdef.roles:
304
            roles = []
305
            for x in self.formdef.roles:
306
                if x == logged_users_role().id:
307
                    roles.append(logged_users_role().name)
308
                else:
309
                    try:
310
                        roles.append(htmltext('<a href="../../roles/%s/">%s</a>') % (x, Role.get(x).name))
311
                    except KeyError:
312
                        # removed role ?
313
                        roles.append(htmltext('<em>%s</em>') % _('Unknown role (%s)') % role_id)
314
            r += htmltext(', ').join(roles)
315
        else:
316
            r += '-'
317
        r += ' '
318
        r += htmltext('(<a href="roles" rel="popup">%s</a>)') % _('change')
319
        r += htmltext('</li>')
303
        r += self._display_roles(_('User Roles:'), 'roles', 'roles')
304
        r += self._display_roles(_('Backoffice Submission Roles:'),
305
                'backoffice_submission_roles',
306
                'backoffice-submission-roles')
320 307

  
321 308
        r += htmltext('<li>%s ') % _('Read Access:')
322 309
        r += '%s' % {'none': _('None'),
......
366 353
        r += htmltext('</div>')
367 354
        return r.getvalue()
368 355

  
356
    def _display_roles(self, title, attribute, change_url):
357
        r = TemplateIO(html=True)
358
        r += htmltext('<li>%s ') % title
359
        if getattr(self.formdef, attribute):
360
            roles = []
361
            for x in getattr(self.formdef, attribute):
362
                if x == logged_users_role().id:
363
                    roles.append(logged_users_role().name)
364
                else:
365
                    try:
366
                        roles.append(htmltext('<a href="../../roles/%s/">%s</a>') % (x, Role.get(x).name))
367
                    except KeyError:
368
                        # removed role ?
369
                        roles.append(htmltext('<em>%s</em>') % _('Unknown role (%s)') % role_id)
370
            r += htmltext(', ').join(roles)
371
        else:
372
            r += '-'
373
        r += ' '
374
        r += htmltext('(<a href="%s" rel="popup">%s</a>)') % (
375
                change_url, _('change'))
376
        r += htmltext('</li>')
377
        return r.getvalue()
378

  
369 379
    def get_sidebar(self):
370 380
        r = TemplateIO(html=True)
371 381
        r += htmltext('<ul id="sidebar-actions">')
......
475 485
                attribute='roles',
476 486
                description=_('Select the roles that can access this form.'))
477 487

  
488
    def backoffice_submission_roles(self):
489
        return self._roles_selection(
490
                title=_('Backoffice Submission Roles'),
491
                attribute='backoffice_submission_roles',
492
                description=_('Select the roles that will be allowed to '
493
                              'fill out forms of this kind in the backoffice.'))
494

  
478 495
    def title(self):
479 496
        form = Form(enctype='multipart/form-data')
480 497
        form.add(StringWidget, 'name', title=_('Form Title'), required=True,
wcs/backoffice/root.py
39 39
import wcs.admin.users
40 40
import wcs.admin.workflows
41 41

  
42
from . import submission
42 43
from . import management
43 44

  
44 45

  
......
54 55
    users = wcs.admin.users.UsersDirectory()
55 56
    workflows = wcs.admin.workflows.WorkflowsDirectory()
56 57
    management = management.ManagementDirectory()
58
    submission = submission.SubmissionDirectory()
57 59

  
58 60
    menu_items = [
59 61
        ('management/', N_('Management')),
62
        ('submission/', N_('Submission')),
60 63
        ('forms/', N_('Forms Workshop')),
61 64
        ('workflows/', N_('Workflows Workshop')),
62 65
        ('users/', N_('Users')),
......
104 107

  
105 108
        # for some subdirectories, the user needs to be part of a role allowed
106 109
        # to go in the backoffice
107
        if subdirectory in ('management',):
110
        if subdirectory in ('management', 'submission'):
108 111
            return get_request().user.can_go_in_backoffice()
109 112

  
110 113
        # for the other directories, an extra level is required, the user needs
......
271 274
                'slug': slug,
272 275
                'url': backoffice_url + k})
273 276
            if slug in ('home', 'forms', 'workflows', 'users', 'roles',
274
                    'categories', 'settings', 'management'):
277
                    'categories', 'settings', 'management', 'submission'):
275 278
                menu_items[-1]['icon'] = k.strip('/')
276 279
        return menu_items
wcs/backoffice/submission.py
1
# w.c.s. - web application for online forms
2
# Copyright (C) 2005-2015  Entr'ouvert
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, see <http://www.gnu.org/licenses/>.
16

  
17
from quixote import get_publisher, get_request, get_response, get_session, redirect
18
from quixote.directory import Directory
19
from quixote.html import TemplateIO, htmltext
20

  
21
from qommon.backoffice.menu import html_top
22
from qommon import errors
23

  
24
from wcs.formdef import FormDef
25
from wcs.categories import Category
26
from wcs.forms.root import FormPage as PublicFormFillPage
27

  
28

  
29
class FormFillPage(PublicFormFillPage):
30
    def html_top(self, *args, **kwargs):
31
        return html_top('submission', *args, **kwargs)
32

  
33
    def check_role(self):
34
        if not self.formdef.backoffice_submission_roles:
35
            raise errors.AccessUnauthorizedError()
36
        for role in get_request().user.roles or []:
37
            if role in self.formdef.backoffice_submission_roles:
38
                break
39
        else:
40
            raise errors.AccessUnauthorizedError()
41

  
42
    def get_sidebar(self, data):
43
        r = TemplateIO(html=True)
44

  
45
        if self.formdef.enable_tracking_codes:
46
            draft_formdata_id = data.get('draft_formdata_id')
47
            r += htmltext('<h3>%s</h3>') % _('Tracking Code')
48
            tracking_code = None
49
            if draft_formdata_id:
50
                formdata = self.formdef.data_class().get(draft_formdata_id)
51
                if formdata.tracking_code:
52
                    tracking_code = formdata.tracking_code
53
            if tracking_code:
54
                r += htmltext('<p>%s</p>') % tracking_code
55
            else:
56
                r += htmltext('<p>-</p>')
57

  
58
        return r.getvalue()
59

  
60
    def form_side(self, step_no, page_no=0, log_detail=None, data=None, editing=None):
61
        r = TemplateIO(html=True)
62
        get_response().filter['sidebar'] = self.get_sidebar(data)
63
        r += htmltext('<div id="side">')
64
        r += self.step(step_no, page_no, log_detail, data=data, editing=editing)
65
        r += htmltext('</div> <!-- #side -->')
66
        return r.getvalue()
67

  
68
    def submitted(self, form, *args):
69
        filled = self.formdef.data_class()()
70
        filled.just_created()
71
        filled.data = self.formdef.get_data(form)
72
        filled.store()
73

  
74
        self.keep_tracking_code(filled)
75
        get_session().remove_magictoken(get_request().form.get('magictoken'))
76

  
77
        url = filled.perform_workflow()
78
        if not url:
79
            url = filled.get_url(backoffice=True)
80

  
81
        if not self.formdef.is_of_concern_for_user(self.user, filled):
82
            # if the agent is not allowed to see the submitted formdef,
83
            # redirect to the submission homepage
84
            return redirect(get_publisher().get_backoffice_url() + '/submission/')
85

  
86
        return redirect(url)
87

  
88

  
89
class SubmissionDirectory(Directory):
90
    _q_exports = ['']
91

  
92
    def _q_index(self):
93
        get_response().breadcrumb.append(('submission/', _('Submission')))
94
        html_top('submission', _('Submission'))
95
        user = get_request().user
96

  
97
        list_forms = []
98
        for formdef in FormDef.select(order_by='name', ignore_errors=True):
99
            if formdef.is_disabled():
100
                continue
101
            if not formdef.backoffice_submission_roles:
102
                continue
103
            for role in user.roles or []:
104
                if role in formdef.backoffice_submission_roles:
105
                    break
106
            else:
107
                continue
108
            list_forms.append(formdef)
109

  
110
        r = TemplateIO(html=True)
111

  
112
        cats = Category.select()
113
        Category.sort_by_position(cats)
114
        one = False
115
        for c in cats:
116
            l2 = [x for x in list_forms if str(x.category_id) == str(c.id)]
117
            if l2:
118
                r += self.form_list(l2, title=c.name)
119
                one = True
120

  
121
        l2 = [x for x in list_forms if not x.category]
122
        if l2:
123
            if one:
124
                title = _('Misc')
125
            else:
126
                title = None
127
            r += self.form_list(l2, title=title)
128

  
129
        return r.getvalue()
130

  
131
    def form_list(self, formdefs, title=None):
132
        r = TemplateIO(html=True)
133
        r += htmltext('<div class="bo-block">')
134
        if title:
135
            r += htmltext('<h2>%s</h2>') % title
136

  
137
        r += htmltext('<ul class="biglist">')
138
        for formdef in formdefs:
139
            r += htmltext('<li>')
140
            r += htmltext('<strong class="label"><a href="%s/">%s</a></strong>') % (
141
                    formdef.url_name, formdef.name)
142
            r += htmltext('</li>')
143
        r += htmltext('</ul>')
144
        r += htmltext('</div>')
145
        return r.getvalue()
146

  
147
    def _q_lookup(self, component):
148
        get_response().breadcrumb.append(('submission/', _('Submission')))
149
        return FormFillPage(component)
wcs/formdef.py
67 67
    workflow_options = None
68 68
    workflow_roles = None
69 69
    roles = None
70
    backoffice_submission_roles = None
70 71
    discussion = False
71 72
    confirmation = True
72 73
    detailed_emails = True
wcs/forms/root.py
221 221
        self.user = get_request().user
222 222
        get_response().breadcrumb.append( (component + '/', self.formdef.name) )
223 223

  
224
    def html_top(self, *args, **kwargs):
225
        html_top(*args, **kwargs)
226

  
224 227
    def get_substitution_variables(self):
225 228
        return self.substvars
226 229

  
......
352 355
                    v = data[k]
353 356
                elif field.prefill:
354 357
                    v = field.get_prefill_value()
358
                    if get_request().get_path().startswith('/backoffice/') and (
359
                            field.prefill and field.prefill.get('type') in ('user', 'geoloc')):
360
                        # turn off prefilling from user and geolocation
361
                        # attributes if the form is filled from the backoffice
362
                        v = None
355 363
                    if v:
356 364
                        prefilled = True
357 365
                        form.get_widget('f%s' % k).set_message(
......
372 380
            if not one:
373 381
                req.form = {}
374 382

  
375
        html_top(self.formdef.name)
383
        self.html_top(self.formdef.name)
376 384
        r += self.form_side(0, page_no, log_detail=log_detail, data=data, editing=editing)
377 385

  
378 386
        form.add_hidden('step', '0')
......
776 784
            except AttributeError:
777 785
                filled.user_id = get_request().user.id
778 786

  
787
        if get_request().get_path().startswith('/backoffice/'):
788
            filled.user_id = None
789

  
779 790
        if self.formdef.only_allow_one:
780 791
            # this is already checked in _q_index but it's done a second time
781 792
            # just before a new form is to be stored.
......
796 807
            get_session().mark_anonymous_formdata(filled)
797 808

  
798 809
        if not url:
799
            url = filled.get_url()
810
            if get_request().get_path().startswith('/backoffice/'):
811
                url = filled.get_url(backoffice=True)
812
            else:
813
                url = filled.get_url()
800 814
        return redirect(url)
801 815

  
802 816
    def keep_tracking_code(self, formdata):
......
846 860
        return get_session().get_tempfile_content(t).get_file_pointer().read()
847 861

  
848 862
    def validating(self, data):
849
        html_top(self.formdef.name)
863
        self.html_top(self.formdef.name)
850 864
        r = TemplateIO(html=True)
851 865
        r += htmltext('<div class="form-validation">')
852 866
        r += self.form_side(step_no=1, data=data)
......
869 883
        return r.getvalue()
870 884

  
871 885
    def error(self, msg):
872
        html_top(self.formdef.name)
886
        self.html_top(self.formdef.name)
873 887
        homepage = get_publisher().get_root_url()
874 888
        r = TemplateIO(html=True)
875 889
        r += htmltext('<div class="errornotice">%s</div>') % msg
......
880 894
        if not self.formdef.is_user_allowed_read(get_request().user):
881 895
            raise errors.AccessForbiddenError()
882 896
        get_response().breadcrumb.append( ('listing', _('Listing')) )
883
        html_top('%s - %s' % (_('Listing'), self.formdef.name))
897
        self.html_top('%s - %s' % (_('Listing'), self.formdef.name))
884 898
        r = TemplateIO(html=True)
885 899

  
886 900
        fields = []
wcs/qommon/static/css/dc2/admin.css
854 854
li.zone-home a { background-image: url(icons/home.large.png); }
855 855
li.zone-home a:hover { background-image: url(icons/home.large-hover.png); }
856 856

  
857
li.zone-submission a { background-image: url(icons/submission.large.png); }
858
li.zone-submission a:hover { background-image: url(icons/submission.large-hover.png); }
859

  
857 860
ul.apps li.zone-no-icon a {
858 861
	height: 29px;
859 862
	padding-top: 24px;
860
-