0001-workflows-store-workflow-form-data-for-structured-ac.patch
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 |
- |