Projet

Général

Profil

0001-workflows-store-workflow-form-data-for-structured-ac.patch

Frédéric Péters, 02 décembre 2021 11:48

Télécharger (15,5 ko)

Voir les différences:

Subject: [PATCH] workflows: store workflow form data for structured access
 (#50795)

 tests/form_pages/test_all.py |  95 ++++++++++++++++++++++++++++++++
 tests/test_formdef.py        |  35 ++++++++++--
 wcs/formdef.py               |   5 ++
 wcs/variables.py             |   9 +++
 wcs/wf/form.py               | 104 ++++++++++++++++++++++++++++++++---
 5 files changed, 236 insertions(+), 12 deletions(-)
tests/form_pages/test_all.py
14 14
from webtest import Hidden, Radio, Upload
15 15

  
16 16
from wcs import fields
17
from wcs.blocks import BlockDef
17 18
from wcs.carddef import CardDef
18 19
from wcs.categories import Category
19 20
from wcs.data_sources import NamedDataSource
......
23 24
from wcs.qommon.emails import docutils
24 25
from wcs.qommon.ident.password_accounts import PasswordAccount
25 26
from wcs.qommon.misc import ConnectionError
27
from wcs.qommon.substitution import CompatibilityNamesDict
26 28
from wcs.roles import logged_users_role
27 29
from wcs.tracking_code import TrackingCode
28 30
from wcs.wf.backoffice_fields import SetBackofficeFieldsWorkflowStatusItem
......
7332 7334

  
7333 7335
    formdata = formdef.data_class()()
7334 7336
    formdata.user_id = user.id
7337
    formdata.just_created()
7335 7338
    formdata.status = 'wf-new'
7336 7339
    formdata.data = {'0': 'plop'}
7337 7340
    formdata.store()
......
7472 7475

  
7473 7476
    formdata = formdef.data_class()()
7474 7477
    formdata.user_id = user.id
7478
    formdata.just_created()
7475 7479
    formdata.status = 'wf-new'
7476 7480
    formdata.data = {'0': 'plop'}
7477 7481
    formdata.store()
......
8388 8392
    # and persist after being saved again
8389 8393
    resp = resp.form.submit('submit').follow()
8390 8394
    assert '<span>test.txt</span>' in resp.text
8395

  
8396

  
8397
def test_workflow_form_structured_data(pub):
8398
    FormDef.wipe()
8399
    Workflow.wipe()
8400
    BlockDef.wipe()
8401

  
8402
    user = create_user(pub)
8403

  
8404
    block = BlockDef()
8405
    block.name = 'foobar'
8406
    block.fields = [
8407
        fields.StringField(id='123', required=True, label='Test', type='string', varname='test'),
8408
    ]
8409
    block.store()
8410

  
8411
    wf = Workflow(name='test')
8412
    status = wf.add_status('New', 'st1')
8413
    status.items = []
8414
    display_form = FormWorkflowStatusItem()
8415
    display_form.id = '_display_form'
8416
    display_form.by = ['_submitter']
8417
    display_form.varname = 'blah'
8418
    display_form.formdef = WorkflowFormFieldsFormDef(item=display_form)
8419
    display_form.formdef.fields = [
8420
        fields.BlockField(id='1', label='test', type='block:foobar', varname='fooblock'),
8421
    ]
8422
    status.items.append(display_form)
8423
    display_form.parent = status
8424
    wf.store()
8425

  
8426
    formdef = create_formdef()
8427
    formdef.workflow_id = wf.id
8428
    formdef.fields = []
8429
    formdef.store()
8430

  
8431
    formdef.data_class().wipe()
8432

  
8433
    formdata = formdef.data_class()()
8434
    formdata.user_id = user.id
8435
    formdata.just_created()
8436
    formdata.store()
8437

  
8438
    app = login(get_app(pub), username='foo', password='foo')
8439
    resp = app.get(formdata.get_url(backoffice=False))
8440
    resp.form['fblah_1$element0$f123'] = 'ABC'
8441
    resp = resp.form.submit('submit').follow()
8442

  
8443
    resp.form['fblah_1$element0$f123'] = 'XYZ'
8444
    resp = resp.form.submit('submit').follow()
8445

  
8446
    formdata.refresh_from_storage()
8447
    assert formdata.workflow_data == {
8448
        'blah_var_fooblock_raw': {'data': [{'123': 'XYZ'}], 'schema': {'123': 'string'}},
8449
        'blah_var_fooblock': 'foobar',
8450
    }
8451

  
8452
    substvars = CompatibilityNamesDict()
8453
    substvars.update(formdata.get_substitution_variables())
8454
    keys = substvars.get_flat_keys()
8455
    for key in keys:
8456
        # noqa pylint: disable=unused-variable
8457
        var = substvars[key]  # check it doesn't raise, ignore the value
8458

  
8459
    assert substvars['form_workflow_form_blah_var_fooblock_var_test'] == 'XYZ'
8460
    assert substvars['form_workflow_form_blah_0_var_fooblock_var_test'] == 'ABC'
8461
    assert substvars['form_workflow_form_blah_1_var_fooblock_var_test'] == 'XYZ'
8462

  
8463
    # disable dumping in workflow_data
8464
    pub.load_site_options()
8465
    if not pub.site_options.has_section('options'):
8466
        pub.site_options.add_section('options')
8467
    pub.site_options.set('options', 'disable-workflow-form-to-workflow-data', 'true')
8468
    with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
8469
        pub.site_options.write(fd)
8470

  
8471
    formdata = formdef.data_class()()
8472
    formdata.user_id = user.id
8473
    formdata.just_created()
8474
    formdata.store()
8475

  
8476
    app = login(get_app(pub), username='foo', password='foo')
8477
    resp = app.get(formdata.get_url(backoffice=False))
8478
    resp.form['fblah_1$element0$f123'] = 'ABC'
8479
    resp = resp.form.submit('submit').follow()
8480

  
8481
    resp.form['fblah_1$element0$f123'] = 'XYZ'
8482
    resp = resp.form.submit('submit').follow()
8483

  
8484
    formdata.refresh_from_storage()
8485
    assert not formdata.workflow_data
tests/test_formdef.py
17 17
from wcs.formdef import FormDef, get_formdefs_of_all_kinds
18 18
from wcs.qommon.http_request import HTTPRequest
19 19
from wcs.qommon.upload_storage import PicklableUpload
20
from wcs.wf.form import FormWorkflowStatusItem, WorkflowFormFieldsFormDef
20
from wcs.wf.form import FormWorkflowStatusItem, WorkflowFormEvolutionPart, WorkflowFormFieldsFormDef
21 21
from wcs.workflows import (
22 22
    AttachmentEvolutionPart,
23 23
    Workflow,
......
460 460
        assert len(glob.glob(os.path.join(pub.app_dir, 'attachments', '*/*'))) == 0
461 461

  
462 462
        # files in user profile
463

  
464 463
        user_formdef = UserFieldsFormDef(pub)
465 464
        user_formdef.fields.append(fields.FileField(id='3', label='test', type='file'))
466 465
        user_formdef.store()
......
479 478
        clean_unused_files(pub)
480 479
        assert len(os.listdir(os.path.join(pub.app_dir, 'uploads'))) == 0
481 480

  
481
        # file from workflow form
482
        formdata = formdef.data_class()()
483
        formdata.just_created()
484
        formdata.data = {}
485
        formdata.store()
486

  
487
        display_form = FormWorkflowStatusItem()
488
        display_form.varname = 'blah'
489
        display_form.formdef = WorkflowFormFieldsFormDef(item=display_form)
490
        display_form.formdef.fields = []
491

  
492
        data = {'blah_1': PicklableUpload('test.txt', 'text/plain')}
493
        data['blah_1'].receive([b'hello world wf form'])
494
        formdata.evolution[-1].parts = [
495
            WorkflowFormEvolutionPart(display_form, data),
496
        ]
497

  
498
        count = len(os.listdir(os.path.join(pub.app_dir, 'uploads')))
499
        formdata.store()
500
        assert len(os.listdir(os.path.join(pub.app_dir, 'uploads'))) == count + 1
501
        clean_unused_files(pub)
502
        assert len(os.listdir(os.path.join(pub.app_dir, 'uploads'))) == count + 1
503

  
504
        formdata.evolution[-1].parts = []
505
        formdata.store()
506
        clean_unused_files(pub)
507
        assert len(os.listdir(os.path.join(pub.app_dir, 'uploads'))) == count
508

  
482 509
        if behaviour == 'move':
483
            # 3 files ("hello world" + "hello world 2" + "hello world block")
484
            assert len(os.listdir(os.path.join(pub.app_dir, 'unused-files/uploads/'))) == 3
510
            # 4 files ("hello world" + "hello world 2" + "hello world block" + "hello world wf form")
511
            assert len(os.listdir(os.path.join(pub.app_dir, 'unused-files/uploads/'))) == 4
485 512
            # 1 attachment
486 513
            assert len(glob.glob(os.path.join(pub.app_dir, 'unused-files/attachments/*/*'))) == 1
487 514

  
wcs/formdef.py
1911 1911

  
1912 1912
    def accumulate_filenames():
1913 1913
        from wcs.carddef import CardDef
1914
        from wcs.wf.form import WorkflowFormEvolutionPart
1914 1915

  
1915 1916
        for formdef in FormDef.select(ignore_migration=True) + CardDef.select(ignore_migration=True):
1916 1917
            for option_data in (formdef.workflow_options or {}).values():
......
1931 1932
                for part in formdata.iter_evolution_parts():
1932 1933
                    if is_attachment(part):
1933 1934
                        yield part.filename
1935
                    elif isinstance(part, WorkflowFormEvolutionPart):
1936
                        for field_data in (part.data or {}).values():
1937
                            if is_upload(field_data):
1938
                                yield field_data.get_fs_filename()
1934 1939
        for user in publisher.user_class.select():
1935 1940
            for field_data in (user.form_data or {}).values():
1936 1941
                if is_upload(field_data):
wcs/variables.py
596 596

  
597 597
        return LazyFormDataLinks(self._formdata)
598 598

  
599
    @property
600
    def workflow_form(self):
601
        # form_ workflow_form_ <slug (action varname)> _ <index> _var_ etc.
602
        # ex: form_workflow_form_xxx_2_var_file
603
        # (index can be "latest")
604
        from .wf.form import LazyFormDataWorkflowForms
605

  
606
        return LazyFormDataWorkflowForms(self._formdata)
607

  
599 608
    @property
600 609
    def parent(self):
601 610
        formdata = self._formdata.get_parent()
wcs/wf/form.py
24 24
from wcs.formdef import FormDef, lax_int
25 25
from wcs.forms.common import FileDirectory
26 26
from wcs.forms.root import FormPage
27
from wcs.workflows import RedisplayFormException, WorkflowStatusItem, register_item_class
27
from wcs.variables import LazyFormDataVar
28
from wcs.workflows import EvolutionPart, RedisplayFormException, WorkflowStatusItem, register_item_class
28 29

  
29 30
from ..qommon import _
30 31
from ..qommon.form import SingleSelectWidget, VarnameWidget, WidgetList
31 32

  
32 33

  
34
class WorkflowFormEvolutionPart(EvolutionPart):
35
    data = None
36
    formdef = None
37
    varname = None
38

  
39
    def __init__(self, action, data, live=False):
40
        self.varname = action.varname
41
        self.formdef = action.formdef
42
        self.data = data
43
        self.live = live
44

  
45

  
33 46
def lookup_wf_form_file(self, filename):
34 47
    # supports for URLs such as /$formdata/$id/files/form-$formvar-$fieldvar/test.txt
35 48
    try:
......
189 202
                self.formdef.readonly = True
190 203
            fields_directory = WorkflowFormFieldsDirectory(self.formdef)
191 204
            if self.varname:
192
                fields_directory.field_var_prefix = '%s_var_' % self.varname
205
                fields_directory.field_var_prefix = 'form_workflow_form_%s_var_' % self.varname
193 206
            fields_directory.html_top = html_top
194 207
            return fields_directory
195 208
        return None
......
229 242

  
230 243
        FormPage.apply_field_prefills({}, form, fields)
231 244

  
232
    def evaluate_live_form(self, form, formdata, user):
245
    def evaluate_live_form(self, form, formdata, user, submit=False):
233 246
        if not self.formdef:
234 247
            return
235 248
        workflow_data = {}
236 249
        self.prefix_form_fields()
237
        for k, v in get_dict_with_varnames(
238
            self.formdef.fields, self.formdef.get_data(form), varnames_only=True
239
        ).items():
250
        formdef_data = self.formdef.get_data(form)
251
        for k, v in get_dict_with_varnames(self.formdef.fields, formdef_data, varnames_only=True).items():
240 252
            workflow_data['%s_%s' % (self.varname, k)] = v
241
        formdata.update_workflow_data(workflow_data)
253
        if not get_publisher().has_site_option('disable-workflow-form-to-workflow-data'):
254
            formdata.update_workflow_data(workflow_data)
255
        if self.varname and submit:
256
            # do not keep parts used for live evaluation (evaluate_live_form will
257
            # be called when creating form and it shouldn't record twice the values)
258
            formdata.evolution[-1].parts = [
259
                x
260
                for x in formdata.evolution[-1].parts or []
261
                if not (isinstance(x, WorkflowFormEvolutionPart) and x.live)
262
            ]
263
            formdata.evolution[-1].add_part(
264
                WorkflowFormEvolutionPart(self, formdef_data, live=bool(not submit))
265
            )
242 266

  
243 267
    def submit_form(self, form, formdata, user, evo):
244 268
        if not self.formdef:
......
251 275
                    if add_element_widget and add_element_widget.parse():
252 276
                        raise RedisplayFormException()
253 277
        if form.get_submit() == 'submit' and not form.has_errors():
254
            self.evaluate_live_form(form, formdata, user)
278
            self.evaluate_live_form(form, formdata, user, submit=True)
255 279
            formdata.store()
256 280
        get_publisher().substitutions.unfeed(lambda x: x.__class__.__name__ == 'ConditionVars')
257 281

  
......
275 299

  
276 300

  
277 301
register_item_class(FormWorkflowStatusItem)
302

  
303

  
304
class LazyFormDataWorkflowForms:
305
    def __init__(self, formdata):
306
        self._formdata = formdata
307

  
308
    def __getattr__(self, varname):
309
        wfform_formdatas = []
310
        for part in self._formdata.iter_evolution_parts():
311
            if not isinstance(part, WorkflowFormEvolutionPart):
312
                continue
313
            if part.varname == varname and part.data:
314
                wfform_formdatas.append(LazyFormDataWorkflowFormsItem(part))
315
        if wfform_formdatas:
316
            return LazyFormDataWorkflowFormsItems(wfform_formdatas)
317
        raise AttributeError(varname)
318

  
319
    def inspect_keys(self):
320
        for part in self._formdata.iter_evolution_parts():
321
            if isinstance(part, WorkflowFormEvolutionPart) and part.varname and part.data:
322
                yield part.varname
323

  
324

  
325
class LazyFormDataWorkflowFormsItems:
326
    def __init__(self, wfform_formdatas):
327
        self._wfform_formdatas = wfform_formdatas
328

  
329
    def inspect_keys(self):
330
        return [str(x) for x in range(len(self._wfform_formdatas))] + ['var']
331

  
332
    @property
333
    def var(self):
334
        # alias to latest values
335
        return self._wfform_formdatas[-1].var
336

  
337
    def __getitem__(self, key):
338
        try:
339
            key = int(key)
340
        except ValueError:
341
            try:
342
                return getattr(self, key)
343
            except AttributeError:
344
                return self._wfform_formdatas[0][key]
345
        return self._wfform_formdatas[key]
346

  
347
    def __len__(self):
348
        return len(self._wfform_formdatas)
349

  
350
    def __iter__(self):
351
        yield from self._wfform_formdatas
352

  
353

  
354
class LazyFormDataWorkflowFormsItem:
355
    def __init__(self, part):
356
        self._part = part
357
        self.data = part.data
358

  
359
    def inspect_keys(self):
360
        return ['var']
361

  
362
    @property
363
    def var(self):
364
        # pass self as formdata, it will be used to access self.data in LazyFieldVarBlock
365
        return LazyFormDataVar(self._part.formdef.get_all_fields(), self._part.data, formdata=self)
278
-