Projet

Général

Profil

0001-forms-revamp-pages-computation-24706.patch

Frédéric Péters, 30 juin 2018 12:43

Télécharger (22,9 ko)

Voir les différences:

Subject: [PATCH] forms: revamp pages computation (#24706)

 tests/test_form_pages.py     |  16 +++-
 wcs/backoffice/submission.py |   4 +-
 wcs/formdef.py               |  35 ++------
 wcs/forms/root.py            | 166 ++++++++++++++++++-----------------
 4 files changed, 109 insertions(+), 112 deletions(-)
tests/test_form_pages.py
518 518
                condition={'type': 'python', 'value': 'var_foo == "Foo"'}),
519 519
            fields.PageField(id='3', label='3rd page', type='page',
520 520
                condition={'type': 'python', 'value': 'var_foo == "Bar"'}),
521
            fields.StringField(id='3', label='string 2')]
521
            fields.StringField(id='4', label='string 2')]
522 522
    formdef.store()
523 523
    formdef.data_class().wipe()
524 524
    resp = get_app(pub).get('/test/')
......
743 743
                condition={'type': 'python', 'value': 'False'}),
744 744
            fields.StringField(id='1', label='string'),
745 745
            fields.PageField(id='2', label='2nd page', type='page'),
746
            fields.StringField(id='3', label='string 2')]
746
            fields.StringField(id='3', label='string 2'),
747
            fields.PageField(id='4', label='3rd page', type='page'),
748
            ]
747 749
    formdef.store()
748 750
    resp = get_app(pub).get('/test/')
749 751
    formdef.data_class().wipe()
......
751 753
    with pytest.raises(AssertionError):
752 754
        resp.form.get('previous')
753 755
    resp.form['f3'] = 'foo'
754
    resp = resp.form.submit('submit')
756
    assert_current_page(resp, '2nd page')
757
    resp = resp.form.submit('submit') # -> 3rd page
758
    assert_current_page(resp, '3rd page')
759
    resp = resp.form.submit('submit') # -> validation page
755 760
    assert 'Check values then click submit.' in resp.body
756 761
    assert resp.form['previous']
757
    resp = resp.form.submit('previous')
762
    resp = resp.form.submit('previous') # -> 3rd page
763
    assert_current_page(resp, '3rd page')
764
    resp = resp.form.submit('previous') # -> 2nd page
765
    assert_current_page(resp, '2nd page')
758 766
    assert resp.form['f3']
759 767
    with pytest.raises(AssertionError):
760 768
        resp.form.get('previous')
wcs/backoffice/submission.py
156 156
            form.add_hidden('submission_channel', self.selected_submission_channel)
157 157
        return form
158 158

  
159
    def form_side(self, step_no, page_no, data=None, magictoken=None):
159
    def form_side(self, step_no, page, data=None, magictoken=None):
160 160
        r = TemplateIO(html=True)
161 161
        get_response().filter['sidebar'] = self.get_sidebar(data)
162 162
        r += htmltext('<div id="appbar">')
......
167 167
                r += htmltext('<a rel="popup" href="remove/%s">%s</a>') % (draft_formdata_id, _('Delete this form'))
168 168
        r += htmltext('</div>')
169 169
        r += htmltext('<div id="side">')
170
        r += self.step(step_no, page_no, data=data)
170
        r += self.step(step_no, page, data=data)
171 171
        r += htmltext('</div> <!-- #side -->')
172 172
        return mark_safe(str(r.getvalue()))
173 173

  
wcs/formdef.py
439 439
            return []
440 440
        return [x.strip() for x in self.appearance_keywords.split()]
441 441

  
442
    _start_page = None
443
    def get_start_page(self):
444
        if self._start_page is not None:
445
            return self._start_page
446
        start_page = 0
447
        if self.fields and self.fields[0].type == 'page' and self.fields[0].condition:
448
            # multipage form with conditions on the first page
449
            for field in self.fields:
450
                if field.type != 'page':
451
                    continue
452
                if field.is_visible(None, self):
453
                    break
454
                start_page += 1
455
            else:
456
                # not a single page :/
457
                return None
458
        self._start_page = start_page
459
        return self._start_page
460

  
461 442
    def get_variable_options(self):
462 443
        variables = {}
463 444
        if not self.workflow.variables_formdef:
......
523 504
    def get_display_id_format(self):
524 505
        return '[formdef_id]-[form_number_raw]'
525 506

  
526
    def create_form(self, page_no=0, displayed_fields=None):
507
    def create_form(self, page=None, displayed_fields=None):
527 508
        form = Form(enctype="multipart/form-data", use_tokens=False)
528 509
        if self.appearance_keywords:
529 510
            form.attrs['class'] = 'quixote %s' % self.appearance_keywords
......
532 513
        form.ERROR_NOTICE = _('There were errors processing the form and '
533 514
                               'you cannot go to the next page. Do '
534 515
                               'check below that you filled all fields correctly.')
535
        self.add_fields_to_form(form, page_no = page_no, displayed_fields = displayed_fields)
516
        self.add_fields_to_form(form, page=page, displayed_fields=displayed_fields)
536 517
        return form
537 518

  
538
    def add_fields_to_form(self, form, page_no = 0, displayed_fields = None, form_data = None):
519
    def add_fields_to_form(self, form, page=None, displayed_fields=None, form_data=None):
539 520
        current_page = 0
521
        on_page = (page is None)
540 522
        for field in self.fields:
541 523
            if field.type == 'page':
542
                if field is self.fields[0]:
543
                    continue
544
                current_page += 1
545
                if current_page > page_no:
524
                if on_page:
546 525
                    break
526
                if page.id == field.id:
527
                    on_page = True
547 528
                continue
548
            if current_page != page_no:
529
            if not on_page:
549 530
                continue
550 531
            if type(displayed_fields) is list:
551 532
                displayed_fields.append(field)
wcs/forms/root.py
231 231
                return True
232 232
        return False
233 233

  
234
    def step(self, step_no, page_no, data=None):
235
        if step_no == 0:
236
            self.substvars['current_page_no'] = str(page_no + 1)
234
    def step(self, step_no, current_page, data=None):
237 235
        get_logger().info('form %s - step %s' % (self.formdef.name, step_no))
238 236

  
239 237
        page_labels = []
240 238
        current_position = 1
241 239

  
242
        page_index = self.formdef.get_start_page()
243
        for field in self.formdef.fields:
244
            if field.type != 'page':
245
                continue
246
            if field.is_visible(data, self.formdef):
247
                page_labels.append(field.label)
248
                if page_index == page_no:
249
                    current_position = len(page_labels)
250
            page_index += 1
251

  
252
        if not page_labels:
253
            page_labels.append(_('Filling'))
240
        for i, page in enumerate(self.pages):
241
            if page is None: # monopage form
242
                page_labels.append(_('Filling'))
243
            else:
244
                page_labels.append(page.label)
245
            if page is current_page:
246
                current_position = i + 1
254 247

  
255 248
        if step_no > 0:
256 249
            current_position = len(page_labels) + step_no
257 250

  
251
        if step_no == 0:
252
            self.substvars['current_page_no'] = current_position
253

  
258 254
        if self.has_confirmation_page() and not self.edit_mode:
259 255
            page_labels.append(_('Validating'))
260 256

  
......
282 278
        r += htmltext('</ol></div>')
283 279
        return r.getvalue()
284 280

  
285
    def page(self, page_no, page_change=True, page_error_messages=None):
281
    def page(self, page, page_change=True, page_error_messages=None):
286 282
        displayed_fields = []
287 283

  
288 284
        session = get_session()
289 285

  
290
        if page_no > self.formdef.get_start_page():
286
        if page and self.pages.index(page) > 0:
291 287
            magictoken = get_request().form['magictoken']
292 288
            self.feed_current_data(magictoken)
293 289

  
294
        form = self.create_form(page_no, displayed_fields)
290
        form = self.create_form(page, displayed_fields)
295 291
        if page_error_messages:
296 292
            form.add_global_errors(page_error_messages)
297 293
        if getattr(session, 'ajax_form_token', None):
......
309 305
        else:
310 306
            form_data = {}
311 307

  
312
        if page_no == self.formdef.get_start_page() and not get_request().form.has_key('magictoken'):
308
        if page == self.pages[0] and not get_request().form.has_key('magictoken'):
313 309
            magictoken = randbytes(8)
314 310
        else:
315 311
            magictoken = get_request().form['magictoken']
316 312
        form.add_hidden('magictoken', magictoken)
317 313
        data = session.get_by_magictoken(magictoken, {})
318 314

  
319
        if page_no == self.formdef.get_start_page() and get_request().form.has_key('cancelurl'):
315
        if page == self.pages[0] and get_request().form.has_key('cancelurl'):
320 316
            cancelurl = get_request().form['cancelurl']
321 317
            session.add_magictoken(magictoken, {'__cancelurl': cancelurl})
322 318

  
323
        if self.edit_mode and page_no == self.page_number - 1:
319
        if self.edit_mode and (page is None or page == self.pages[-1]):
324 320
            form.add_submit('submit', _('Save Changes'))
325
        elif not self.has_confirmation_page() and page_no == self.page_number - 1:
321
        elif not self.has_confirmation_page() and (page is None or page == self.pages[-1]):
326 322
            form.add_submit('submit', _('Submit'))
327 323
        else:
328 324
            form.add_submit('submit', _('Next'))
329 325

  
330
        if page_no > self.formdef.get_start_page():
326
        if self.pages.index(page) > 0:
331 327
            form.add_submit('previous', _('Previous'))
332 328

  
333 329
        if page_change:
......
394 390
        self.html_top(self.formdef.name)
395 391

  
396 392
        form.add_hidden('step', '0')
397
        form.add_hidden('page', page_no)
393
        form.add_hidden('page', self.pages.index(page))
398 394

  
399 395
        form.add_submit('cancel', _('Cancel'), css_class = 'cancel')
400 396
        if self.formdef.enable_tracking_codes and not self.edit_mode:
......
404 400
        context = {
405 401
            'view': self,
406 402
            'form': form,
407
            'form_side': lambda: self.form_side(0, page_no, data=data, magictoken=magictoken),
408
            'steps': lambda: self.step(0, page_no, data=data),
403
            'form_side': lambda: self.form_side(0, page, data=data, magictoken=magictoken),
404
            'steps': lambda: self.step(0, page, data=data),
409 405
        }
410 406
        if self.formdef.enable_tracking_codes and data:
411 407
            context['tracking_code_box'] = lambda: self.tracking_code_box(data, magictoken)
......
414 410
                list(self.get_formdef_template_variants(self.filling_templates)),
415 411
                context)
416 412

  
417
    def form_side(self, step_no, page_no, data=None, magictoken=None):
413
    def form_side(self, step_no, page, data=None, magictoken=None):
418 414
        '''Create the elements that typically appear aside the main form
419 415
           (tracking code and steps).'''
420 416
        r = TemplateIO(html=True)
......
424 420
            # data (e.g. the user is not on a insufficient authenticiation
425 421
            # context page)
426 422
            r += self.tracking_code_box(data, magictoken)
427
        r += self.step(step_no, page_no, data=data)
423
        r += self.step(step_no, page, data=data)
428 424
        r += htmltext('</div> <!-- #side -->')
429 425
        return mark_safe(str(r.getvalue()))
430 426

  
......
458 454
        r += htmltext('</div>') # <!-- #tracking-code -->
459 455
        return r.getvalue()
460 456

  
461
    def feed_current_data(self, magictoken):
462
        # create a fake FormData to feed variables
457
    def get_transient_formdata(self, magictoken=Ellipsis):
458
        if magictoken is Ellipsis:
459
            magictoken = get_request().form.get('magictoken')
460
        # create a fake FormData with current submission data
463 461
        formdata = FormData()
464 462
        formdata._formdef = self.formdef
465 463
        formdata.user = get_request().user
......
469 467
            draft_formdata_id = formdata.data.get('draft_formdata_id')
470 468
            if draft_formdata_id:
471 469
                formdata = self.formdef.data_class().get(draft_formdata_id)
472
        formdata.status = str('')
470
        formdata.status = ''
471
        return formdata
472

  
473
    def feed_current_data(self, magictoken):
474
        formdata = self.get_transient_formdata(magictoken)
473 475
        get_publisher().substitutions.feed(formdata)
474 476

  
475 477
    def check_disabled(self):
......
495 497

  
496 498
        self.html_top(self.formdef.name)
497 499
        r = TemplateIO(html=True)
498
        r += self.form_side(step_no=0, page_no=self.formdef.get_start_page())
500
        r += self.form_side(step_no=0, page=self.pages[0])
499 501
        auth_contexts = get_publisher().get_supported_authentication_contexts()
500 502
        r += htmltext('<div class="errornotice">')
501 503
        r += htmltext('<p>%s</p>') % _('You need a stronger authentication level to fill this form.')
......
506 508
                    root_url, _('Login with %s') % auth_contexts[auth_context])
507 509
        return r.getvalue()
508 510

  
511
    _pages = None
512
    @property
513
    def pages(self):
514
        if self._pages:
515
            return self._pages
516
        current_data = self.get_transient_formdata().data
517
        pages = []
518
        field_page = None
519
        for field in self.formdef.fields:
520
            if field.type == 'page':
521
                field_page = field
522
                if field.is_visible(current_data, self.formdef):
523
                    pages.append(field)
524
        if not field_page:  # form without page fields
525
            pages = [None]
526
        self._pages = pages
527
        return pages
528

  
529
    def reset_pages_cache(self):
530
        self._pages = None
531

  
509 532
    def _q_index(self):
510 533
        self.check_role()
511 534
        authentication_context_check_result = self.check_authentication_context()
......
539 562
            session_magic_token = session.get_by_magictoken(
540 563
                    get_request().form.get('magictoken'), no_magic)
541 564
            if session_magic_token is no_magic:
542
                if (get_request().form.get('page') != str(self.formdef.get_start_page()) or
565
                if (get_request().form.get('page') != '0' or
543 566
                        get_request().form.get('step') != '0'):
544 567
                    # the magictoken that has been submitted is not available
545 568
                    # in the session and we're not on the first page of the
......
615 638
                                    req.form['f%s' % field.id] = field.convert_value_to_str(data[field.id])
616 639
                            return self.validating(data)
617 640
                    else:
618
                        page_no = self.formdef.get_start_page()
619
                    return self.page(page_no, True)
641
                        page_no = 0
642
                    return self.page(self.pages[page_no], True)
620 643
            self.feed_current_data(None)
621
            if self.formdef.get_start_page() is None:
644
            if not self.pages:
622 645
                raise errors.TraversalError()
623
            return self.page(self.formdef.get_start_page())
646
            return self.page(self.pages[0])
624 647

  
625 648
        if form.get_submit() == 'cancel':
626 649
            get_logger().info('form %s - cancel' % (self.formdef.name))
......
660 683
            try:
661 684
                page_no = int(form.get_widget('page').parse())
662 685
            except TypeError:
663
                page_no = -1
686
                # this situation shouldn't arise (that likely means the
687
                # page hidden field had an error in its submission), in
688
                # that case we just fall back to the first page.
689
                page_no = 0
690
            page = self.pages[page_no]
664 691
            try:
665 692
                magictoken = form.get_widget('magictoken').parse()
666 693
            except KeyError:
......
668 695

  
669 696
            self.feed_current_data(magictoken)
670 697

  
671
            form = self.create_form(page_no)
698
            form = self.create_form(page=page)
672 699
            form.add_submit('previous')
673 700
            if self.formdef.enable_tracking_codes:
674 701
                form.add_submit('removedraft')
675 702
                form.add_submit('savedraft')
676 703
            form.add_submit('submit')
677
            if page_no > self.formdef.get_start_page() and form.get_submit() == 'previous':
704
            if page_no > 0 and form.get_submit() == 'previous':
678 705
                return self.previous_page(page_no, magictoken)
679 706

  
680 707
            if self.formdef.enable_tracking_codes and form.get_submit() == 'removedraft':
......
688 715
                return redirect(filled.get_url().rstrip('/'))
689 716

  
690 717
            page_error_messages = []
691
            if form.get_submit() == 'submit':
692
                pages = [x for x in self.formdef.fields if x.type == 'page']
693
                try:
694
                    page = pages[page_no]
695
                    post_conditions = page.post_conditions or []
696
                except IndexError:
697
                    post_conditions = []
718
            if form.get_submit() == 'submit' and page:
719
                post_conditions = page.post_conditions or []
698 720
                form_data = session.get_by_magictoken(magictoken, {})
699 721
                data = self.formdef.get_data(form)
700 722
                form_data.update(data)
......
717 739
            # by clicking on a submit widget; for example if an "add row"
718 740
            # button is clicked.
719 741
            if form.has_errors() or form.get_submit() is True:
720
                if page_no == -1:
721
                    # this situation shouldn't arise (that likely means the
722
                    # page hidden field had an error in its submission), in
723
                    # that case we just fall back to the first page.
724
                    page_no = self.formdef.get_start_page()
725
                return self.page(page_no, page_change=False,
742
                return self.page(page, page_change=False,
726 743
                        page_error_messages=page_error_messages)
727 744

  
728 745
            form_data = session.get_by_magictoken(magictoken, {})
......
731 748

  
732 749
            session.add_magictoken(magictoken, form_data)
733 750

  
734
            while True:
735
                page_no = int(page_no) + 1
736
                try:
737
                    next_page = self.formdef.get_page(page_no)
738
                except IndexError:
739
                    break
740
                if next_page.is_visible(form_data, self.formdef):
741
                    break
751
            page_no += 1
742 752

  
743 753
            draft_id = session.get_by_magictoken(magictoken, {}).get('draft_formdata_id')
744 754
            if draft_id:
......
756 766
                # if there's no draft yet and tracking codes are enabled, create one
757 767
                filled = self.save_draft(form_data, page_no)
758 768

  
759
            if page_no == self.page_number:
769
            # the page has been successfully submitted, maybe new pages
770
            # should be revealed.
771
            self.feed_current_data(magictoken)
772
            self.reset_pages_cache()
773

  
774
            if int(page_no) == len(self.pages):
760 775
                # last page has been submitted
761 776
                req = get_request()
762 777
                for field in self.formdef.fields:
......
784 799
                        form.add_submit('savedraft')
785 800

  
786 801
            else:
787
                return self.page(page_no)
802
                return self.page(self.pages[page_no])
788 803

  
789 804
        if step == 1:
790 805
            form.add_submit('previous')
791 806
            magictoken = form.get_widget('magictoken').parse()
792 807

  
793 808
            if form.get_submit() == 'previous':
794
                return self.previous_page(self.page_number, magictoken)
809
                return self.previous_page(len(self.pages), magictoken)
795 810
            magictoken = form.get_widget('magictoken').parse()
796 811
            form_data = session.get_by_magictoken(magictoken, {})
797 812
            data = self.formdef.get_data(form)
......
812 827
            form_data = session.get_by_magictoken(magictoken, {})
813 828

  
814 829
            if form.get_submit() == 'previous':
815
                return self.previous_page(self.page_number, magictoken)
830
                return self.previous_page(len(self.pages), magictoken)
816 831

  
817 832
            if self.formdef.enable_tracking_codes and form.get_submit() == 'removedraft':
818 833
                return self.removedraft()
......
841 856
        session = get_session()
842 857
        form_data = session.get_by_magictoken(magictoken, {})
843 858

  
844
        while True:
845
            page_no = int(page_no) - 1
846
            try:
847
                previous_page = self.formdef.get_page(page_no)
848
            except IndexError:
849
                break
850
            if not previous_page.condition:
851
                break
852

  
853
            if previous_page.is_visible(form_data, self.formdef):
854
                break
859
        try:
860
            previous_page = self.pages[int(page_no - 1)]
861
        except IndexError:
862
            previous_page = self.pages[0]
855 863

  
856
        return self.page(page_no, page_change=True)
864
        return self.page(previous_page, page_change=True)
857 865

  
858 866
    def removedraft(self):
859 867
        magictoken = get_request().form.get('magictoken')
......
912 920
        if not form_data:
913 921
            return result_error('missing data')
914 922

  
915
        form = self.create_form(page_no)
923
        form = self.create_form(page=self.pages[page_no])
916 924
        data = self.formdef.get_data(form)
917 925
        if not data:
918 926
            return result_error('nothing to save')
......
1102 1110
        context = {
1103 1111
            'view': self,
1104 1112
            'form': form,
1105
            'form_side': lambda: self.form_side(step_no=1, page_no=None, data=data,
1113
            'form_side': lambda: self.form_side(step_no=1, page=None, data=data,
1106 1114
                magictoken=magictoken),
1107 1115
        }
1108 1116

  
1109
-