Projet

Général

Profil

0001-misc-load-drafts-from-the-form-URL-with-trailing-sla.patch

Frédéric Péters, 01 février 2020 14:39

Télécharger (28,8 ko)

Voir les différences:

Subject: [PATCH] misc: load drafts from the form URL, with trailing slash
 (#39356)

 tests/test_api.py                       |  3 +-
 tests/test_backoffice_pages.py          | 32 +++++++-------
 tests/test_form_pages.py                | 49 ++++++++++------------
 wcs/api.py                              |  3 +-
 wcs/backoffice/management.py            |  2 +-
 wcs/backoffice/submission.py            | 19 ++++++++-
 wcs/forms/common.py                     | 42 +++++++++++++++++++
 wcs/forms/root.py                       | 56 ++++---------------------
 wcs/templates/wcs/formdata_filling.html |  4 +-
 wcs/wf/resubmit.py                      |  2 +-
 10 files changed, 111 insertions(+), 101 deletions(-)
tests/test_api.py
1459 1459
    assert len(resp.json['data']) == 2
1460 1460

  
1461 1461
    draft_formdata = [x for x in resp.json['data'] if x['status'] == 'Draft'][0]
1462
    assert draft_formdata.get('url')[-1] != '/'
1463 1462

  
1464 1463
    formdata = formdef.data_class()()
1465 1464
    formdata.data = {'0': 'foo@localhost', '1': 'xyy'}
......
2595 2594

  
2596 2595
    resp = get_app(pub).get(sign_url('/api/code/%s?orig=coucou' % code.id, '1234'), status=200)
2597 2596
    assert resp.json['err'] == 0
2598
    assert resp.json['url'] == 'http://example.net/test/%s' % formdata.id
2597
    assert resp.json['url'] == 'http://example.net/test/%s/' % formdata.id
2599 2598
    assert resp.json['load_url'] == 'http://example.net/code/%s/load' % code.id
2600 2599

  
2601 2600
    formdef.enable_tracking_codes = False
tests/test_backoffice_pages.py
2072 2072
        return resp.json['data']['id']
2073 2073

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

  
2078 2078
    # second page should be shown
......
2085 2085
    formdef.store()
2086 2086

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

  
2091 2091
    pq = resp.pyquery.remove_namespaces()
......
2152 2152

  
2153 2153
    resp = app.get('/backoffice/submission/')
2154 2154
    assert 'Submission to complete' in resp.text
2155
    resp1 = app.get('/backoffice/submission/form-title/%s' % formdata.id)
2155
    resp1 = app.get('/backoffice/submission/form-title/%s/' % formdata.id)
2156 2156
    resp1 = resp1.follow()
2157
    resp2 = app.get('/backoffice/submission/form-title/%s' % formdata.id)
2157
    resp2 = app.get('/backoffice/submission/form-title/%s/' % formdata.id)
2158 2158
    resp2 = resp2.follow()
2159
    resp3 = app.get('/backoffice/submission/form-title/%s' % formdata.id)
2159
    resp3 = app.get('/backoffice/submission/form-title/%s/' % formdata.id)
2160 2160
    resp3 = resp3.follow()
2161 2161

  
2162 2162
    resp1.form['f1'] = 'foo'
......
2196 2196
    assert formdef.data_class().get(formdata.id).data['1'] == 'foo'
2197 2197

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

  
......
2343 2343

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

  
2349 2349
    resp = resp.click('#%s' % formdata_no)
......
2595 2595
    assert resp.form['f1'].value == ''
2596 2596

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

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

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

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

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

  
2709 2709
        # restore another, without user id
2710
        resp = app.get('/backoffice/submission/form-title/%s' % formdata2.id)
2710
        resp = app.get('/backoffice/submission/form-title/%s/' % formdata2.id)
2711 2711
        resp = resp.follow()
2712 2712
        resp = resp.form.submit('submit')
2713 2713
        # and check it was not prefilled
......
2719 2719
    formdata.page_no = 0
2720 2720
    formdata.user_id = other_user.id
2721 2721
    formdata.store()
2722
    resp = app.get('/backoffice/submission/form-title/%s' % formdata.id)
2722
    resp = app.get('/backoffice/submission/form-title/%s/' % formdata.id)
2723 2723
    resp = resp.follow()
2724 2724
    resp.form['f1'] = 'Hello'
2725 2725
    resp_autosave = app.post('/backoffice/submission/form-title/autosave', params=resp.form.submit_fields())
......
2827 2827
    formdata.backoffice_submission = True
2828 2828
    formdata.store()
2829 2829

  
2830
    resp = app.get('/backoffice/submission/form-title/%s' % formdata.id)
2830
    resp = app.get('/backoffice/submission/form-title/%s/' % formdata.id)
2831 2831
    resp = resp.follow()
2832 2832
    resp.form['f1'].value = 'PLOP'
2833 2833
    resp.form['f10'].value = 'bar'
......
2901 2901
    resp = app.get('/backoffice/submission/form-title/?NameID=%s&channel=mail' % local_user.name_identifiers[0])
2902 2902
    assert resp.location.startswith('http://example.net/backoffice/submission/form-title/')
2903 2903

  
2904
    formdata_no = resp.location.split('/')[-1]
2904
    formdata_no = resp.location.split('/')[-2]
2905 2905
    formdata = formdef.data_class().get(formdata_no)
2906 2906
    assert formdata.user_id == str(local_user.id)
2907 2907
    assert formdata.submission_channel == 'mail'
tests/test_form_pages.py
175 175
    assert '<a href="test/%s"' % draft.id in resp
176 176
    resp = app.get('/test/%s' % formdata.id)
177 177
    resp.status_int = 200
178
    resp = app.get('/test/%s' % draft.id, status=302)
178
    resp = app.get('/test/%s/' % draft.id, status=302)
179 179
    resp = resp.follow(status=200)
180 180

  
181 181
    # disable formdef: formdatas are still visible and accessible, drafts are not
......
186 186
    assert '<a href="test/%s/"' % formdata.id in resp
187 187
    assert not 'Draft' in resp
188 188
    assert not '<a href="test/%s"' % draft.id in resp
189
    resp = app.get('/test/%s' % formdata.id)
190
    resp.status_int = 200
191
    resp = app.get('/test/%s' % draft.id, status=302)
189
    resp = app.get('/test/%s/' % formdata.id)
190
    resp = app.get('/test/%s/' % draft.id, status=302)
192 191
    resp = resp.follow(status=403)
193 192

  
194 193

  
......
1478 1477
    resp = resp.forms[0].submit()
1479 1478
    assert resp.location == 'http://example.net/code/%s/load' % tracking_code
1480 1479
    resp = resp.follow()
1481
    assert resp.location == 'http://example.net/test/%s' % formdata_id
1480
    assert resp.location == 'http://example.net/test/%s/' % formdata_id
1482 1481
    resp = resp.follow()
1483 1482
    assert resp.location.startswith('http://example.net/test/?mt=')
1484 1483
    resp = resp.follow()
1485 1484

  
1486 1485
    # check anonymous user can't get to it from the URL
1487 1486
    pub.session_manager.session_class.wipe()
1488
    resp = get_app(pub).get('http://example.net/test/%s' % formdata_id)
1487
    resp = get_app(pub).get('http://example.net/test/%s/' % formdata_id)
1489 1488
    assert resp.location.startswith('http://example.net/login')
1490 1489

  
1491 1490
    # or logged users that didn't enter the code:
1492 1491
    user = create_user(pub)
1493 1492
    login(get_app(pub), username='foo', password='foo').get(
1494
            'http://example.net/test/%s' % formdata_id, status=403)
1493
            'http://example.net/test/%s/' % formdata_id, status=403)
1495 1494

  
1496 1495
    # check we can also get to it as a logged user
1497 1496
    pub.session_manager.session_class.wipe()
......
1500 1499
    resp = resp.forms[0].submit()
1501 1500
    assert resp.location == 'http://example.net/code/%s/load' % tracking_code.lower()
1502 1501
    resp = resp.follow()
1503
    assert resp.location == 'http://example.net/test/%s' % formdata_id
1502
    assert resp.location == 'http://example.net/test/%s/' % formdata_id
1504 1503
    resp = resp.follow()
1505 1504

  
1506 1505
    # go back as anonymous
......
1510 1509
    resp = resp.forms[0].submit()
1511 1510
    assert resp.location == 'http://example.net/code/%s/load' % tracking_code
1512 1511
    resp = resp.follow()
1513
    assert resp.location == 'http://example.net/test/%s' % formdata_id
1512
    assert resp.location == 'http://example.net/test/%s/' % formdata_id
1514 1513
    resp = resp.follow()
1515 1514
    assert resp.location.startswith('http://example.net/test/?mt=')
1516 1515
    resp = resp.follow()
......
1536 1535
    resp = resp.forms[0].submit()
1537 1536
    assert resp.location == 'http://example.net/code/%s/load' % tracking_code
1538 1537
    resp = resp.follow()
1539
    assert resp.location == 'http://example.net/test/%s' % formdata_id
1540
    resp = resp.follow()
1538
    assert resp.location == 'http://example.net/test/%s/' % formdata_id
1541 1539
    resp = resp.follow()
1542 1540
    assert 'form_comment' in resp.text # makes sure user is treated as submitter
1543 1541
    resp.forms[0]['comment'] = 'hello world'
......
1551 1549
    resp = resp.forms[0].submit()
1552 1550
    assert resp.location == 'http://example.net/code/%s/load' % tracking_code.lower()
1553 1551
    resp = resp.follow()
1554
    assert resp.location == 'http://example.net/test/%s' % formdata_id
1552
    assert resp.location == 'http://example.net/test/%s/' % formdata_id
1555 1553
    resp = resp.follow()
1556 1554

  
1557 1555

  
......
1605 1603
    resp = resp.forms[0].submit()
1606 1604
    assert resp.location == 'http://example.net/code/%s/load' % tracking_code
1607 1605
    resp = resp.follow()
1608
    assert resp.location == 'http://example.net/test/%s' % formdata_id
1606
    assert resp.location == 'http://example.net/test/%s/' % formdata_id
1609 1607
    resp = resp.follow()
1610 1608
    assert resp.location.startswith('http://example.net/test/?mt=')
1611 1609
    resp = resp.follow()
......
1631 1629
    resp = resp.forms[0].submit()
1632 1630
    assert resp.location == 'http://example.net/code/%s/load' % tracking_code
1633 1631
    resp = resp.follow()
1634
    assert resp.location == 'http://example.net/test/%s' % formdata_id
1635
    resp = resp.follow()
1632
    assert resp.location == 'http://example.net/test/%s/' % formdata_id
1636 1633
    resp = resp.follow()
1637 1634
    assert 'form_comment' in resp.text # makes sure user is treated as submitter
1638 1635
    resp.forms[0]['comment'] = 'hello world'
......
1646 1643
    resp = resp.forms[0].submit()
1647 1644
    assert resp.location == 'http://example.net/code/%s/load' % tracking_code
1648 1645
    resp = resp.follow()
1649
    assert resp.location == 'http://example.net/test/%s' % formdata_id
1650
    resp = resp.follow()
1646
    assert resp.location == 'http://example.net/test/%s/' % formdata_id
1651 1647
    resp = resp.follow()
1652 1648
    assert 'form_comment' in resp.text # makes sure user is treated as submitter
1653 1649

  
......
1740 1736
    resp = resp.forms[0].submit()
1741 1737
    assert resp.location == 'http://example.net/code/%s/load' % tracking_code
1742 1738
    resp = resp.follow()
1743
    assert resp.location == 'http://example.net/test/%s' % formdata_id
1739
    assert resp.location == 'http://example.net/test/%s/' % formdata_id
1744 1740
    resp = resp.follow()
1745 1741
    assert resp.location.startswith('http://example.net/test/?mt=')
1746 1742
    resp = resp.follow()
......
1790 1786
    resp = resp.forms[0].submit()
1791 1787
    assert resp.location == 'http://example.net/code/%s/load' % tracking_code
1792 1788
    resp = resp.follow()
1793
    assert resp.location == 'http://example.net/test/%s' % formdata_id
1789
    assert resp.location == 'http://example.net/test/%s/' % formdata_id
1794 1790
    resp = resp.follow()
1795 1791
    assert resp.location.startswith('http://example.net/test/?mt=')
1796 1792
    resp = resp.follow()
......
1933 1929
    resp = resp.forms[0].submit()
1934 1930
    assert resp.location == 'http://example.net/code/%s/load' % code.id
1935 1931
    resp = resp.follow()
1936
    assert resp.location == 'http://example.net/test/%s' % formdata.id
1932
    assert resp.location == 'http://example.net/test/%s/' % formdata.id
1937 1933
    resp = resp.follow()
1938 1934

  
1939 1935
    # check we get a not found error message on non-existent code
......
2075 2071
    formdata.status = 'draft'
2076 2072
    formdata.store()
2077 2073

  
2078
    resp = get_app(pub).get('/test/%s' % formdata.id, status=302)
2074
    resp = get_app(pub).get('/test/%s/' % formdata.id, status=302)
2079 2075
    assert resp.location.startswith('http://example.net/login')
2080 2076

  
2081 2077
    formdata.user_id = user.id
2082 2078
    formdata.store()
2083
    resp = get_app(pub).get('/test/%s' % formdata.id, status=302)
2079
    resp = get_app(pub).get('/test/%s/' % formdata.id, status=302)
2084 2080
    assert resp.location.startswith('http://example.net/login')
2085 2081

  
2086
    resp = login(get_app(pub), 'foo', 'foo').get('/test/%s' % formdata.id, status=302)
2082
    resp = login(get_app(pub), 'foo', 'foo').get('/test/%s/' % formdata.id, status=302)
2087 2083
    assert resp.location.startswith('http://example.net/test/?mt=')
2088 2084

  
2089 2085
    formdata.user_id = 1000
2090 2086
    formdata.store()
2091
    resp = login(get_app(pub), 'foo', 'foo').get('/test/%s' % formdata.id, status=403)
2087
    resp = login(get_app(pub), 'foo', 'foo').get('/test/%s/' % formdata.id, status=403)
2092 2088

  
2093 2089

  
2094 2090
def form_password_field_submit(app, password):
......
6338 6334
    code.store()
6339 6335

  
6340 6336
    resp = app.get('/code/%s/load' % code.id)
6341
    resp = resp.follow() # -> /test/1
6342
    assert not 'backoffice' in resp.location
6343 6337
    resp = resp.follow() # -> /test/1/
6338
    assert 'backoffice' not in resp.request.path
6344 6339
    assert 'The form has been recorded' in resp.text
6345 6340

  
6346 6341
    # authorized access but not backoffice access
......
7171 7166
    app = login(get_app(pub), username='foo', password='foo')
7172 7167
    resp = app.get('/test/')
7173 7168
    assert 'You already started to fill this form.' in resp.text
7174
    assert 'href="%s"' % draft.id in resp.text
7169
    assert 'href="%s/"' % draft.id in resp.text
7175 7170

  
7176 7171
    draft2 = formdef.data_class()()
7177 7172
    draft2.user_id = user.id
wcs/api.py
105 105
        d['last_update_time'] = misc.strftime('%Y-%m-%d %H:%M:%S', formdata.last_update_time)
106 106

  
107 107
    if formdata.is_draft():
108
        d['url'] = d['url'].rstrip('/')
109 108
        d['form_number_raw'] = d['form_number'] = None
110 109
        d['title'] = _('%(name)s (draft)') % {'name': formdata.formdef.name}
111 110
    else:
......
790 789
            raise TraversalError()
791 790
        data = {
792 791
            'err': 0,
793
            'url': formdata.get_url().rstrip('/'),
792
            'url': formdata.get_url(),
794 793
            'load_url': get_publisher().get_frontoffice_url() + '/code/%s/load' % component,
795 794
        }
796 795
        return json.dumps(data)
wcs/backoffice/management.py
2221 2221
            if self.filled.backoffice_submission:
2222 2222
                for role in get_request().user.get_roles():
2223 2223
                    if role in self.formdef.backoffice_submission_roles:
2224
                        return redirect('../../../submission/%s/%s' % (
2224
                        return redirect('../../../submission/%s/%s/' % (
2225 2225
                            self.formdef.url_name, self.filled.id))
2226 2226
            raise errors.AccessForbiddenError()
2227 2227

  
wcs/backoffice/submission.py
30 30
from wcs.formdata import FormData
31 31
from wcs.formdef import FormDef
32 32
from wcs.categories import Category
33
from wcs.forms.common import FormStatusPage
33 34
from wcs.forms.root import FormPage as PublicFormFillPage
34 35

  
35 36

  
......
73 74
        return redirect('../..')
74 75

  
75 76

  
77
class SubmissionFormStatusPage(FormStatusPage):
78
    _q_exports_orig = ['', 'download', 'live']
79

  
80
    def _q_index(self):
81
        if not self.filled.is_draft():
82
            get_session().message = ('error', _('This form has already been submitted.'))
83
            return redirect(get_publisher().get_backoffice_url() + '/submission/')
84
        return super(SubmissionFormStatusPage, self)._q_index()
85

  
86

  
76 87
class FormFillPage(PublicFormFillPage):
77 88
    _q_exports = ['', 'tempfile', 'autosave', 'code',
78 89
            ('remove', 'remove_draft'), 'live']
......
108 119
                formdata.submission_context['return_url'] = return_url
109 120
            formdata.store()
110 121
            self.set_tracking_code(formdata)
111
            return redirect('%s' % formdata.id)
122
            return redirect('%s/' % formdata.id)
112 123

  
113 124
        self.selected_submission_channel = get_request().form.get('submission_channel') or ''
114 125
        return super(FormFillPage, self)._q_index(*args, **kwargs)
......
116 127
    def html_top(self, *args, **kwargs):
117 128
        return html_top('submission', *args, **kwargs)
118 129

  
130
    @classmethod
131
    def get_status_page_class(cls):
132
        return SubmissionFormStatusPage
133

  
119 134
    def check_authentication_context(self):
120 135
        pass
121 136

  
......
380 395
                            formdata.submission_context['agent_id'], ignore_errors=True)
381 396
                    if agent_user:
382 397
                        label += ' (%s)' % agent_user.display_name
383
                r += htmltext('<a href="%s/%s">%s</a>') % (
398
                r += htmltext('<a href="%s/%s/">%s</a>') % (
384 399
                        formdef.url_name, formdata.id, label)
385 400
                r += htmltext('</li>')
386 401

  
wcs/forms/common.py
19 19
from quixote import get_publisher, get_request, get_response, get_session, redirect
20 20
from quixote.directory import Directory
21 21
from quixote.html import TemplateIO, htmltext
22
from quixote.util import randbytes
22 23

  
23 24
from wcs import data_sources
24 25
from wcs.api_utils import get_user_from_api_query_string, is_url_signed
......
226 227
        return r.getvalue()
227 228

  
228 229
    def _q_index(self):
230
        if self.filled.is_draft():
231
            return self.restore_draft()
232

  
229 233
        mine = self.check_auth()
230 234
        if not mine and not get_request().is_in_backoffice():
231 235
            # Access authorized but the form doesn't belong to the user; if the
......
259 263
                templates=list(self.get_formdef_template_variants(self.status_templates)),
260 264
                context=context)
261 265

  
266
    def restore_draft(self):
267
        # restore and redirect to draft
268
        session = get_session()
269
        filled = self.filled
270
        if not (get_request().is_in_backoffice() and filled.backoffice_submission):
271
            if session.is_anonymous_submitter(filled):
272
                pass
273
            elif session.user:
274
                if str(session.user) != str(filled.user_id):
275
                    raise errors.AccessUnauthorizedError()
276
            else:
277
                raise errors.AccessUnauthorizedError()
278

  
279
        if get_request().get_query() == 'remove-draft':
280
            filled.remove_self()
281
            return redirect(get_publisher().get_root_url())
282

  
283
        magictoken = randbytes(8)
284
        filled.feed_session()
285
        form_data = filled.data
286
        for field in filled.formdef.fields:
287
            if field.id not in form_data:
288
                continue
289
            if form_data[field.id] is None:
290
                # remove keys that were not set, this is required when we restore a
291
                # draft from SQL (where all columns are always defined).
292
                del form_data[field.id]
293
                continue
294
            if field.type == 'file':
295
                # add back file to session
296
                tempfile = session.add_tempfile(form_data[field.id])
297
                form_data[field.id].token = tempfile['token']
298
        form_data['is_recalled_draft'] = True
299
        form_data['draft_formdata_id'] = filled.id
300
        form_data['page_no'] = filled.page_no
301
        session.add_magictoken(magictoken, form_data)
302
        return redirect('../?mt=%s' % magictoken)
303

  
262 304
    def get_workflow_form(self, user):
263 305
        submitted_fields = []
264 306
        form = self.filled.get_workflow_form(user, displayed_fields=submitted_fields)
wcs/forms/root.py
175 175
        if BotFilter.is_bot():
176 176
            raise errors.AccessForbiddenError()
177 177
        get_session().mark_anonymous_formdata(formdata)
178
        return redirect(formdata.get_url().rstrip('/'))
178
        return redirect(formdata.get_url())
179 179

  
180 180

  
181 181
class TrackingCodesDirectory(Directory):
......
794 794

  
795 795
            if self.has_draft_support() and form.get_submit() == 'savedraft':
796 796
                filled = self.save_draft(form_data, page_no)
797
                return redirect(filled.get_url().rstrip('/'))
797
                return redirect(filled.get_url())
798 798

  
799 799
            for field in submitted_fields:
800 800
                if not field.is_visible(form_data, self.formdef) and 'f%s' % field.id in form._names:
......
925 925

  
926 926
            if self.has_draft_support() and form.get_submit() == 'savedraft':
927 927
                filled = self.save_draft(form_data, page_no = -1)
928
                return redirect(filled.get_url().rstrip('/'))
928
                return redirect(filled.get_url())
929 929

  
930 930
            # so it gets FakeFileWidget in preview mode
931 931
            form = self.create_view_form(form_data,
......
1282 1282
        get_response().set_content_type('image/png')
1283 1283
        return s.getvalue()
1284 1284

  
1285
    @classmethod
1286
    def get_status_page_class(cls):
1287
        return PublicFormStatusPage
1288

  
1285 1289
    def _q_lookup(self, component):
1286 1290
        try:
1287 1291
            filled = self.formdef.data_class().get(component)
1288 1292
        except KeyError:
1289 1293
            raise errors.TraversalError()
1290 1294

  
1291
        if not filled.is_draft():
1292
            if get_request().is_in_backoffice():
1293
                get_session().message = ('error', _('This form has already been submitted.'))
1294
                return redirect(get_publisher().get_backoffice_url() + '/submission/')
1295
            return PublicFormStatusPage(self.formdef, filled)
1296

  
1297
        # restore draft
1298
        session = get_session()
1299
        if not (get_request().is_in_backoffice() and filled.backoffice_submission):
1300
            if session.is_anonymous_submitter(filled):
1301
                pass
1302
            elif session.user:
1303
                if str(session.user) != str(filled.user_id):
1304
                    raise errors.AccessUnauthorizedError()
1305
            else:
1306
                raise errors.AccessUnauthorizedError()
1307

  
1308
        if get_request().get_query() == 'remove-draft':
1309
            filled.remove_self()
1310
            return redirect(get_publisher().get_root_url())
1311

  
1312
        magictoken = randbytes(8)
1313
        filled.feed_session()
1314
        form_data = filled.data
1315
        for field in filled.formdef.fields:
1316
            if not field.id in form_data:
1317
                continue
1318
            if form_data[field.id] is None:
1319
                # remove keys that were not set, this is required when we restore a
1320
                # draft from SQL (where all columns are always defined).
1321
                del form_data[field.id]
1322
                continue
1323
            if field.type == 'file':
1324
                # add back file to session
1325
                tempfile = session.add_tempfile(form_data[field.id])
1326
                form_data[field.id].token = tempfile['token']
1327
        form_data['is_recalled_draft'] = True
1328
        form_data['draft_formdata_id'] = filled.id
1329
        form_data['page_no'] = filled.page_no
1330
        session.add_magictoken(magictoken, form_data)
1331

  
1332
        if get_request().is_in_backoffice():
1333
            return redirect('./?mt=%s' % magictoken)
1334
        else:
1335
            return redirect('%s?mt=%s' % (filled.formdef.get_url(), magictoken))
1295
        return self.get_status_page_class()(self.formdef, filled)
1336 1296

  
1337 1297

  
1338 1298
class RootDirectory(AccessControlled, Directory):
wcs/templates/wcs/formdata_filling.html
22 22
       {% endblocktrans %}
23 23
     </p>
24 24
     {% if drafts_length == 1 %}
25
       <p><a class="pk-button" href="{{drafts.0.internal_id}}">{% trans "Continue with draft" %}</a></p>
25
       <p><a class="pk-button" href="{{drafts.0.internal_id}}/">{% trans "Continue with draft" %}</a></p>
26 26
     {% elif drafts_length > 1 %}
27 27
     <ul>
28 28
     {% for draft in drafts %}
29
     <li><a href="{{draft.internal_id}}">{% trans "continue with draft from " %} {{draft.receipt_date}}
29
     <li><a href="{{draft.internal_id}}/">{% trans "continue with draft from " %} {{draft.receipt_date}}
30 30
                     {{draft.receipt_time}}</a>, {% blocktrans with page_no=draft.page_no|add:1 %}on page {{page_no}}{% endblocktrans %}</li>
31 31
     {% endfor %}
32 32
     </ul>
wcs/wf/resubmit.py
106 106

  
107 107
        workflow_data = {
108 108
            'resubmit_formdata_backoffice_url': new_formdata.get_url(backoffice=True),
109
            'resubmit_formdata_draft_url': new_formdata.get_url(backoffice=False).rstrip('/'),
109
            'resubmit_formdata_draft_url': new_formdata.get_url(backoffice=False),
110 110
        }
111 111
        formdata.update_workflow_data(workflow_data)
112 112
        formdata.store()
113
-