Projet

Général

Profil

0001-general-add-soft-hard-limits-to-number-of-field-6006.patch

Frédéric Péters, 28 mars 2022 18:05

Télécharger (16,3 ko)

Voir les différences:

Subject: [PATCH] general: add soft/hard limits to number of field (#60061)

 tests/admin_pages/test_block.py | 34 ++++++++++++++++++++++++
 tests/admin_pages/test_card.py  | 43 ++++++++++++++++++++++++++++++
 tests/admin_pages/test_form.py  | 41 +++++++++++++++++++++-------
 wcs/admin/blocks.py             |  5 ++++
 wcs/admin/fields.py             | 47 ++++++++++++++++++++++++++-------
 wcs/admin/forms.py              |  3 ++-
 wcs/admin/workflows.py          |  9 +++++++
 wcs/backoffice/cards.py         |  9 ++++++-
 wcs/wf/form.py                  |  2 ++
 9 files changed, 172 insertions(+), 21 deletions(-)
tests/admin_pages/test_block.py
359 359
    resp = resp.forms[0].submit()
360 360
    block.refresh_from_storage()
361 361
    assert not block.category_id
362

  
363

  
364
def test_block_edit_field_warnings(pub):
365
    create_superuser(pub)
366

  
367
    BlockDef.wipe()
368
    blockdef = BlockDef()
369
    blockdef.name = 'block title'
370
    blockdef.fields = [
371
        fields.StringField(id='%d' % i, label='field %d' % i, type='string') for i in range(1, 10)
372
    ]
373
    blockdef.store()
374

  
375
    app = login(get_app(pub))
376
    resp = app.get('/backoffice/forms/blocks/%s/' % blockdef.id)
377
    assert 'more than 20 fields' not in resp.text
378

  
379
    blockdef.fields.extend(
380
        [fields.StringField(id='%d' % i, label='field %d' % i, type='string') for i in range(10, 21)]
381
    )
382
    blockdef.store()
383
    resp = app.get('/backoffice/forms/blocks/%s/' % blockdef.id)
384
    assert 'more than 20 fields' not in resp.text
385
    assert '<div id="new-field"><h3>New Field</h3>' in resp.text
386
    assert '>Duplicate<' in resp.text
387

  
388
    blockdef.fields.extend(
389
        [fields.StringField(id='%d' % i, label='field %d' % i, type='string') for i in range(21, 31)]
390
    )
391
    blockdef.store()
392
    resp = app.get('/backoffice/forms/blocks/%s/' % blockdef.id)
393
    assert 'This block of fields contains 30 fields.' in resp.text
394
    assert '<div id="new-field"><h3>New Field</h3>' not in resp.text
395
    assert '>Duplicate<' not in resp.text
tests/admin_pages/test_card.py
850 850
    assert resp.text.count('<title>card_card_1_1&#45;&gt;card_card_2_2</title>') == 0
851 851
    assert resp.text.count('<title>card_card_2_1&#45;&gt;card_card_2_2</title>') == 8
852 852
    assert resp.text.count('<title>card_card_3_1&#45;&gt;card_card_3_2</title>') == 0
853

  
854

  
855
def test_card_edit_field_warnings(pub):
856
    create_superuser(pub)
857

  
858
    CardDef.wipe()
859
    carddef = CardDef()
860
    carddef.name = 'card title'
861
    carddef.fields = [
862
        fields.StringField(id='%d' % i, label='field %d' % i, type='string') for i in range(1, 10)
863
    ]
864
    carddef.store()
865

  
866
    app = login(get_app(pub))
867
    resp = app.get('/backoffice/cards/%s/fields/' % carddef.id)
868
    assert 'more than 200 fields' not in resp.text
869
    assert 'first field should be of type "page"' not in resp.text
870

  
871
    carddef.fields.append(fields.PageField(id='1000', label='page', type='page'))
872
    carddef.store()
873
    resp = app.get('/backoffice/cards/%s/fields/' % carddef.id)
874
    assert 'more than 200 fields' not in resp.text
875
    assert 'first field should be of type "page"' in resp.text
876
    assert '<div id="new-field"><h3>New Field</h3>' in resp.text
877

  
878
    carddef.fields.extend(
879
        [fields.StringField(id='%d' % i, label='field %d' % i, type='string') for i in range(10, 210)]
880
    )
881
    carddef.store()
882
    resp = app.get('/backoffice/cards/%s/fields/' % carddef.id)
883
    assert 'more than 200 fields' in resp.text
884
    assert 'first field should be of type "page"' in resp.text
885
    assert '>Duplicate<' in resp.text
886

  
887
    carddef.fields.extend(
888
        [fields.StringField(id='%d' % i, label='field %d' % i, type='string') for i in range(210, 310)]
889
    )
890
    carddef.store()
891
    resp = app.get('/backoffice/cards/%s/fields/' % carddef.id)
892
    assert 'This card model contains 310 fields.' in resp.text
893
    assert 'first field should be of type "page"' in resp.text
894
    assert '<div id="new-field"><h3>New Field</h3>' not in resp.text
895
    assert '>Duplicate<' not in resp.text
tests/admin_pages/test_form.py
2590 2590
    formdef.store()
2591 2591

  
2592 2592
    app = login(get_app(pub))
2593
    resp = app.get('/backoffice/forms/1/')
2594
    resp = resp.click(href='fields/')
2595
    assert 'more than 500 fields' not in resp.text
2593
    resp = app.get('/backoffice/forms/%s/fields/' % formdef.id)
2594
    assert 'more than 200 fields' not in resp.text
2596 2595
    assert 'first field should be of type "page"' not in resp.text
2597 2596

  
2598 2597
    formdef.fields.append(fields.PageField(id='1000', label='page', type='page'))
2599 2598
    formdef.store()
2600
    resp = app.get('/backoffice/forms/1/')
2601
    resp = resp.click(href='fields/', index=0)
2602
    assert 'more than 500 fields' not in resp.text
2599
    resp = app.get('/backoffice/forms/%s/fields/' % formdef.id)
2600
    assert 'more than 200 fields' not in resp.text
2603 2601
    assert 'first field should be of type "page"' in resp.text
2602
    assert '<div id="new-field"><h3>New Field</h3>' in resp.text
2604 2603

  
2605 2604
    formdef.fields.extend(
2606
        [fields.StringField(id='%d' % i, label='field %d' % i, type='string') for i in range(10, 510)]
2605
        [fields.StringField(id='%d' % i, label='field %d' % i, type='string') for i in range(10, 210)]
2607 2606
    )
2608 2607
    formdef.store()
2609
    resp = app.get('/backoffice/forms/1/')
2610
    resp = resp.click(href='fields/', index=0)
2611
    assert 'more than 500 fields' in resp.text
2608
    resp = app.get('/backoffice/forms/%s/fields/' % formdef.id)
2609
    assert 'more than 200 fields' in resp.text
2612 2610
    assert 'first field should be of type "page"' in resp.text
2611
    assert '>Duplicate<' in resp.text
2612

  
2613
    formdef.fields.extend(
2614
        [fields.StringField(id='%d' % i, label='field %d' % i, type='string') for i in range(210, 310)]
2615
    )
2616
    formdef.store()
2617
    resp = app.get('/backoffice/forms/%s/fields/' % formdef.id)
2618
    assert 'This form contains 310 fields.' in resp.text
2619
    assert 'no new fields can be added.' in resp.text
2620
    assert 'first field should be of type "page"' in resp.text
2621
    assert '<div id="new-field"><h3>New Field</h3>' not in resp.text
2622
    assert '>Duplicate<' not in resp.text
2623

  
2624
    if not pub.site_options.has_section('options'):
2625
        pub.site_options.add_section('options')
2626
    pub.site_options.set('options', 'ignore-hard-limits', 'true')
2627
    with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
2628
        pub.site_options.write(fd)
2629

  
2630
    resp = app.get('/backoffice/forms/%s/fields/' % formdef.id)
2631
    assert 'no new fields should be added.' in resp.text
2632
    assert '<div id="new-field"><h3>New Field</h3>' in resp.text
2633
    assert '>Duplicate<' in resp.text
2613 2634

  
2614 2635
    FormDef.wipe()
2615 2636

  
wcs/admin/blocks.py
53 53
    support_import = False
54 54
    readonly_message = _('This block of fields is readonly.')
55 55

  
56
    field_count_message = _('This block of fields contains %d fields.')
57
    field_over_count_message = _('This block of fields contains more than %d fields.')
58
    fields_count_total_soft_limit = 20
59
    fields_count_total_hard_limit = 30
60

  
56 61
    def __init__(self, section='forms', *args, **kwargs):
57 62
        if kwargs.pop('component', None):  # snapshot
58 63
            kwargs['objectdef'] = kwargs.pop('instance')
wcs/admin/fields.py
17 17
import copy
18 18
import json
19 19

  
20
from quixote import get_request, get_response, get_session, redirect
20
from quixote import get_publisher, get_request, get_response, get_session, redirect
21 21
from quixote.directory import Directory
22 22
from quixote.html import TemplateIO, htmlescape, htmltext
23 23

  
......
279 279
    page_id = None
280 280
    field_var_prefix = '..._'
281 281
    readonly_message = _('The fields are readonly.')
282
    field_count_message = _('This form contains %d fields.')
283
    field_over_count_message = _('This form contains more than %d fields.')
282 284

  
283 285
    support_import = True
286
    fields_count_total_soft_limit = 200
287
    fields_count_total_hard_limit = 300
284 288

  
285 289
    def html_top(self, title, *args, **kwargs):
286 290
        html_top(self.section, title, *args, **kwargs)
......
309 313
        r = TemplateIO(html=True)
310 314

  
311 315
        r += self.index_top()
316
        ignore_hard_limits = get_publisher().has_site_option('ignore-hard-limits')
312 317

  
313 318
        if self.objectdef.fields:
314
            if len(self.objectdef.fields) > 500:
319
            if len(self.objectdef.fields) >= self.fields_count_total_hard_limit:
315 320
                r += htmltext('<div class="errornotice">')
316
                r += htmltext(
317
                    _(
318
                        'This form contains more than 500 fields. '
319
                        'It is close to the database limits and no new fields should be added.'
320
                    )
321
                )
321
                r += htmltext(self.field_count_message % len(self.objectdef.fields))
322
                r += htmltext(' ')
323
                if ignore_hard_limits:
324
                    r += htmltext(_('It is over system limits and no new fields should be added.'))
325
                else:
326
                    r += htmltext(_('It is over system limits and no new fields can be added.'))
327
                r += htmltext('</div>')
328
            elif len(self.objectdef.fields) > self.fields_count_total_soft_limit:
329
                r += htmltext('<div class="errornotice">')
330
                r += htmltext(self.field_over_count_message % self.fields_count_total_soft_limit)
331
                r += htmltext(' ')
332
                r += htmltext(_('It is close to the system limits and no new fields should be added.'))
322 333
                r += htmltext('</div>')
323 334

  
324 335
            if [x for x in self.objectdef.fields if x.type == 'page']:
......
404 415
                        )
405 416
                    r += command_icon('%s/' % field.id, 'edit')
406 417
                if not self.objectdef.is_readonly():
407
                    r += command_icon('%s/duplicate' % field.id, 'duplicate', popup=(field.type == 'page'))
418
                    if len(self.objectdef.fields) < self.fields_count_total_hard_limit or ignore_hard_limits:
419
                        r += command_icon(
420
                            '%s/duplicate' % field.id, 'duplicate', popup=(field.type == 'page')
421
                        )
408 422
                    r += command_icon('%s/delete' % field.id, 'remove', popup=True)
409 423
                r += htmltext('</p></li>')
410 424
            r += htmltext('</ul>')
......
424 438

  
425 439
    def get_new_field_form(self, page_id):
426 440
        r = TemplateIO(html=True)
441
        ignore_hard_limits = get_publisher().has_site_option('ignore-hard-limits')
442

  
443
        if len(self.objectdef.fields) >= self.fields_count_total_hard_limit:
444
            if ignore_hard_limits:
445
                r += htmltext('<div class="errornotice"><p>%s %s</p></div>') % (
446
                    self.field_count_message % self.fields_count_total_hard_limit,
447
                    _('It is over system limits and no new fields should be added.'),
448
                )
449
            else:
450
                r += htmltext('<div class="errornotice"><p>%s %s</p></div>') % (
451
                    self.field_count_message % self.fields_count_total_hard_limit,
452
                    _('It is over system limits and no new fields can be added.'),
453
                )
454
                return r.getvalue()
455

  
427 456
        r += htmltext('<div id="new-field">')
428 457
        r += htmltext('<h3>%s</h3>') % _('New Field')
429 458
        get_request().form = None  # ignore the eventual ?page=x
wcs/admin/forms.py
628 628
    formdef_default_workflow = '_default'
629 629
    section = 'forms'
630 630
    options_directory_class = OptionsDirectory
631
    fields_directory_class = AdminFieldsDirectory
631 632

  
632 633
    delete_message = _('You are about to irrevocably delete this form.')
633 634
    delete_title = _('Deleting Form:')
......
647 648
            raise TraversalError()
648 649
        self.formdefui = self.formdef_ui_class(self.formdef)
649 650
        get_response().breadcrumb.append((component + '/', self.formdef.name))
650
        self.fields = AdminFieldsDirectory(self.formdef)
651
        self.fields = self.fields_directory_class(self.formdef)
651 652
        self.fields.html_top = self.html_top
652 653
        self.role = WorkflowRoleDirectory(self.formdef)
653 654
        self.role.html_top = self.html_top
wcs/admin/workflows.py
1051 1051
    field_var_prefix = 'form_option_'
1052 1052
    readonly_message = _('This workflow is readonly.')
1053 1053

  
1054
    field_count_message = _('This workflow contains %d variables.')
1055
    field_over_count_message = _('This workflow contains more than %d variables.')
1056
    fields_count_total_soft_limit = 20
1057
    fields_count_total_hard_limit = 30
1058

  
1054 1059
    def index_top(self):
1055 1060
        r = TemplateIO(html=True)
1056 1061
        r += htmltext('<h2>%s - %s - %s</h2>') % (_('Workflow'), self.objectdef.name, _('Variables'))
......
1073 1078
    blacklisted_attributes = ['condition']
1074 1079
    field_var_prefix = 'form_var_'
1075 1080
    readonly_message = _('This workflow is readonly.')
1081
    field_count_message = _('This workflow contains %d backoffice fields.')
1082
    field_over_count_message = _('This workflow contains more than %d backoffice fields.')
1083
    fields_count_total_soft_limit = 30
1084
    fields_count_total_hard_limit = 45
1076 1085

  
1077 1086
    def index_top(self):
1078 1087
        r = TemplateIO(html=True)
wcs/backoffice/cards.py
19 19

  
20 20
from wcs.admin import utils
21 21
from wcs.admin.categories import CardDefCategoriesDirectory
22
from wcs.admin.forms import FormDefPage, FormDefUI, FormsDirectory, OptionsDirectory
22
from wcs.admin.forms import AdminFieldsDirectory, FormDefPage, FormDefUI, FormsDirectory, OptionsDirectory
23 23
from wcs.carddef import CardDef, get_cards_graph
24 24
from wcs.categories import CardDefCategory
25 25
from wcs.workflows import Workflow
......
41 41
    section = 'cards'
42 42

  
43 43

  
44
class CardDefFieldsDirectory(AdminFieldsDirectory):
45
    field_count_message = _('This card model contains %d fields.')
46
    field_over_count_message = _('This card model contains more than %d fields.')
47
    readonly_message = _('This card model is readonly.')
48

  
49

  
44 50
class CardDefPage(FormDefPage):
45 51
    formdef_class = CardDef
46 52
    formdef_export_prefix = 'card'
......
49 55
    section = 'cards'
50 56

  
51 57
    options_directory_class = CardDefOptionsDirectory
58
    fields_directory_class = CardDefFieldsDirectory
52 59

  
53 60
    delete_message = _('You are about to irrevocably delete this card model.')
54 61
    delete_title = _('Deleting Card Model:')
wcs/wf/form.py
100 100
    support_import = False
101 101
    blacklisted_types = ['page', 'computed']
102 102
    field_def_page_class = WorkflowFormFieldDefPage
103
    fields_count_total_soft_limit = 20
104
    fields_count_total_hard_limit = 30
103 105

  
104 106

  
105 107
class FormWorkflowStatusItem(WorkflowStatusItem):
106
-