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 |
... | ... | |
7113 | 7115 | |
7114 | 7116 |
formdata = formdef.data_class()() |
7115 | 7117 |
formdata.user_id = user.id |
7118 |
formdata.just_created() |
|
7116 | 7119 |
formdata.status = 'wf-new' |
7117 | 7120 |
formdata.data = {'0': 'plop'} |
7118 | 7121 |
formdata.store() |
... | ... | |
7253 | 7256 | |
7254 | 7257 |
formdata = formdef.data_class()() |
7255 | 7258 |
formdata.user_id = user.id |
7259 |
formdata.just_created() |
|
7256 | 7260 |
formdata.status = 'wf-new' |
7257 | 7261 |
formdata.data = {'0': 'plop'} |
7258 | 7262 |
formdata.store() |
... | ... | |
8169 | 8173 |
# and persist after being saved again |
8170 | 8174 |
resp = resp.form.submit('submit').follow() |
8171 | 8175 |
assert '<span>test.txt</span>' in resp.text |
8176 | ||
8177 | ||
8178 |
def test_workflow_form_structured_data(pub): |
|
8179 |
FormDef.wipe() |
|
8180 |
Workflow.wipe() |
|
8181 |
BlockDef.wipe() |
|
8182 | ||
8183 |
user = create_user(pub) |
|
8184 | ||
8185 |
block = BlockDef() |
|
8186 |
block.name = 'foobar' |
|
8187 |
block.fields = [ |
|
8188 |
fields.StringField(id='123', required=True, label='Test', type='string', varname='test'), |
|
8189 |
] |
|
8190 |
block.store() |
|
8191 | ||
8192 |
wf = Workflow(name='test') |
|
8193 |
status = wf.add_status('New', 'st1') |
|
8194 |
status.items = [] |
|
8195 |
display_form = FormWorkflowStatusItem() |
|
8196 |
display_form.id = '_display_form' |
|
8197 |
display_form.by = ['_submitter'] |
|
8198 |
display_form.varname = 'blah' |
|
8199 |
display_form.formdef = WorkflowFormFieldsFormDef(item=display_form) |
|
8200 |
display_form.formdef.fields = [ |
|
8201 |
fields.BlockField(id='1', label='test', type='block:foobar', varname='fooblock'), |
|
8202 |
] |
|
8203 |
status.items.append(display_form) |
|
8204 |
display_form.parent = status |
|
8205 |
wf.store() |
|
8206 | ||
8207 |
formdef = create_formdef() |
|
8208 |
formdef.workflow_id = wf.id |
|
8209 |
formdef.fields = [] |
|
8210 |
formdef.store() |
|
8211 | ||
8212 |
formdef.data_class().wipe() |
|
8213 | ||
8214 |
formdata = formdef.data_class()() |
|
8215 |
formdata.user_id = user.id |
|
8216 |
formdata.just_created() |
|
8217 |
formdata.store() |
|
8218 | ||
8219 |
app = login(get_app(pub), username='foo', password='foo') |
|
8220 |
resp = app.get(formdata.get_url(backoffice=False)) |
|
8221 |
resp.form['fblah_1$element0$f123'] = 'ABC' |
|
8222 |
resp = resp.form.submit('submit').follow() |
|
8223 | ||
8224 |
resp.form['fblah_1$element0$f123'] = 'XYZ' |
|
8225 |
resp = resp.form.submit('submit').follow() |
|
8226 | ||
8227 |
formdata.refresh_from_storage() |
|
8228 |
assert formdata.workflow_data == { |
|
8229 |
'blah_var_fooblock_raw': {'data': [{'123': 'XYZ'}], 'schema': {'123': 'string'}}, |
|
8230 |
'blah_var_fooblock': 'foobar', |
|
8231 |
} |
|
8232 | ||
8233 |
substvars = CompatibilityNamesDict() |
|
8234 |
substvars.update(formdata.get_substitution_variables()) |
|
8235 |
keys = substvars.get_flat_keys() |
|
8236 |
for key in keys: |
|
8237 |
# noqa pylint: disable=unused-variable |
|
8238 |
var = substvars[key] # check it doesn't raise, ignore the value |
|
8239 | ||
8240 |
assert substvars['form_workflow_form_blah_var_fooblock_var_test'] == 'ABC' |
|
8241 |
assert substvars['form_workflow_form_blah_0_var_fooblock_var_test'] == 'ABC' |
|
8242 |
assert substvars['form_workflow_form_blah_1_var_fooblock_var_test'] == 'XYZ' |
|
8243 |
assert substvars['form_workflow_form_blah_latest_var_fooblock_var_test'] == 'XYZ' |
|
8244 | ||
8245 |
# disable dumping in workflow_data |
|
8246 |
pub.load_site_options() |
|
8247 |
if not pub.site_options.has_section('options'): |
|
8248 |
pub.site_options.add_section('options') |
|
8249 |
pub.site_options.set('options', 'disable-workflow-form-to-workflow-data', 'true') |
|
8250 |
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd: |
|
8251 |
pub.site_options.write(fd) |
|
8252 | ||
8253 |
formdata = formdef.data_class()() |
|
8254 |
formdata.user_id = user.id |
|
8255 |
formdata.just_created() |
|
8256 |
formdata.store() |
|
8257 | ||
8258 |
app = login(get_app(pub), username='foo', password='foo') |
|
8259 |
resp = app.get(formdata.get_url(backoffice=False)) |
|
8260 |
resp.form['fblah_1$element0$f123'] = 'ABC' |
|
8261 |
resp = resp.form.submit('submit').follow() |
|
8262 | ||
8263 |
resp.form['fblah_1$element0$f123'] = 'XYZ' |
|
8264 |
resp = resp.form.submit('submit').follow() |
|
8265 | ||
8266 |
formdata.refresh_from_storage() |
|
8267 |
assert not formdata.workflow_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', 'latest'] |
|
331 | ||
332 |
@property |
|
333 |
def var(self): |
|
334 |
# alias when there's a single workflow form |
|
335 |
return self._wfform_formdatas[0].var |
|
336 | ||
337 |
@property |
|
338 |
def latest(self): |
|
339 |
# alias to latest form |
|
340 |
return {'var': self._wfform_formdatas[-1].var} |
|
341 | ||
342 |
def __getitem__(self, key): |
|
343 |
try: |
|
344 |
key = int(key) |
|
345 |
except ValueError: |
|
346 |
try: |
|
347 |
return getattr(self, key) |
|
348 |
except AttributeError: |
|
349 |
return self._wfform_formdatas[0][key] |
|
350 |
return self._wfform_formdatas[key] |
|
351 | ||
352 |
def __len__(self): |
|
353 |
return len(self._wfform_formdatas) |
|
354 | ||
355 |
def __iter__(self): |
|
356 |
yield from self._wfform_formdatas |
|
357 | ||
358 | ||
359 |
class LazyFormDataWorkflowFormsItem: |
|
360 |
def __init__(self, part): |
|
361 |
self._part = part |
|
362 |
self.data = part.data |
|
363 | ||
364 |
def inspect_keys(self): |
|
365 |
return ['var'] |
|
366 | ||
367 |
@property |
|
368 |
def var(self): |
|
369 |
# pass self as formdata, it will be used to access self.data in LazyFieldVarBlock |
|
370 |
return LazyFormDataVar(self._part.formdef.get_all_fields(), self._part.data, formdata=self) |
|
278 |
- |