From 3155de269bc932f936fe4494e8dee3c6866df1f0 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Sat, 18 May 2019 14:17:33 +0200 Subject: [PATCH 5/5] add new action create-formdata (#33186) --- wcs/wf/create_formdata.py | 405 ++++++++++++++++++++++++++++++++++++++ wcs/workflows.py | 2 +- 2 files changed, 406 insertions(+), 1 deletion(-) create mode 100644 wcs/wf/create_formdata.py diff --git a/wcs/wf/create_formdata.py b/wcs/wf/create_formdata.py new file mode 100644 index 00000000..dfbf900c --- /dev/null +++ b/wcs/wf/create_formdata.py @@ -0,0 +1,405 @@ +# w.c.s. - web application for online forms +# Copyright (C) 2005-2016 Entr'ouvert +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . + +import time +import copy + +from quixote import get_request, get_session +from quixote.html import htmltext + +from django.utils import functional, six + +from qommon import _ +from qommon.form import (WidgetListAsTable, CompositeWidget, + SingleSelectWidget, ComputedExpressionWidget, + CheckboxWidget, StringWidget, VarnameWidget) + +from wcs.logged_errors import LoggedError +from wcs.workflows import WorkflowStatusItem, register_item_class +from wcs.formdef import FormDef +from wcs.variables import LazyFormData + +EXPRESSION_VALUE = '@expression@' + + +class FieldMapping(object): + expression = None + + def __init__(self, field_id, value, expression=None): + self.field_id = field_id + self.value = value + assert value != EXPRESSION_VALUE or expression is not None + if expression == EXPRESSION_VALUE: + self.expression = expression + + +class FieldsMappings(object): + def __init__(self, formdef, mappings): + self.formdef_slug = formdef.url_name + self.mappings = mappings + + @functional.cached_property + def formdef(self): + return FormDef.get_by_urlname(self.formdef_slug) + + def apply(self, status_item, to_formdata, from_formdata): + from_formdef = from_formdata.formdef + from_varname_fields = {field.varname: field for field in from_formdef.get_all_widget_fields()} + to_id_fields = {field.id: field for field in self.formdef.get_all_widget_fields()} + + for mapping in self.mappings: + to_field = to_id_fields[mapping.field_id] + if mapping.expression: + try: + value = self.compute(mapping.expression, formdata=from_formdata, raises=True, status_item=self) + except Exception: + # already logged by self.compute + continue + else: + from_field = from_varname_fields[mapping.value] + value = from_formdata.data.get('%s' % from_field.id) + + try: + self._set_value( + to_formdata=to_formdata, + to_field=to_field, + value=value) + except ValueError as e: + summary = _('Failed to convert %(class)s value to %(kind)s field (%(id)s)') % { + 'class': type(value), + 'kind': _(getattr(to_field, 'description', 'unknown')), + 'id': str(to_field.id), + } + expression_dict = mapping.expression and self.get_expression(mapping.expression) or {} + LoggedError.record(summary, formdata=from_formdata, + status_item=status_item, + expression=expression_dict.get('value'), + expression_type=expression_dict.get('type'), + exception=e) + return + + def _set_value(self, to_formdata, to_field, value, expression=None): + if to_field.convert_value_from_anything: + old_value = value # noqa: F841, copy value for debug + value = to_field.convert_value_from_anything(value) + + to_formdata.data['%s' % to_field.id] = value + if to_field.store_display_value: + display_value = to_field.store_display_value( + to_formdata.data, to_field.id) + if display_value: + to_formdata.data['%s_display' % to_field.id] = display_value + if value and to_field.store_structured_value: + structured_value = to_field.store_structured_value( + to_formdata.data, to_field.id) + if structured_value: + if isinstance(structured_value, dict) and structured_value.get('id'): + # in case of list field, override id + to_formdata.data['%s' % to_field.id] = str(structured_value.get('id')) + to_formdata.data['%s_structured' % to_field.id] = structured_value + + +class FieldOrExpressionWidget(CompositeWidget): + def __init__(self, name, value=None, options=None, **kwargs): + super(FieldOrExpressionWidget, self).__init__(name, value, **kwargs) + + options = list(options) + options.insert(0, (None, '---', '')) + options.append((EXPRESSION_VALUE, _('Expression'), EXPRESSION_VALUE)) + + self.add(SingleSelectWidget, + name='value', + value=value.get('value'), + options=options, + attrs={'data-dynamic-display-parent': 'true'}) + + self.add(ComputedExpressionWidget, + name='expression', + value=value.get('expression'), + attrs={ + 'data-dynamic-display-child-of': self.get_widget('value').name, + 'data-dynamic-display-value': EXPRESSION_VALUE, + }) + + def _parse(self, request): + super(FieldOrExpressionWidget, self)._parse(request) + if self.get('value') == EXPRESSION_VALUE and self.get('expression'): + self.value = { + 'value': EXPRESSION_VALUE, + 'expression': self.get('expression'), + } + elif self.get('value') != EXPRESSION_VALUE: + self.value = { + 'value': self.get('value'), + 'expression': None, + } + else: + self.value = None + + +class FieldMappingWidget(CompositeWidget): + def __init__(self, name, value=None, to_formdef=None, from_options=None, **kwargs): + value = value or {} + if isinstance(value, FieldMapping): + value = { + 'field_id': value.field_id, + 'value': value.value, + 'expression': value.expression, + } + super(FieldMappingWidget, self).__init__(name, value, **kwargs) + + to_fields = self._fields_to_options(to_formdef.get_all_widget_fields()) + + self.add(SingleSelectWidget, + name='field_id', + title=_('Field'), + value=value.get('field_id'), + options=to_fields) + + self.add(FieldOrExpressionWidget, + name='value', + title=_('Value'), + value={ + 'value': value.get('value'), + 'expression': value.get('expression'), + }, + options=from_options) + + def _fields_to_options(self, fields): + return [(None, '---', '')] + [(field.id, field.label, field.id) for field in fields] + + def _parse(self, request): + if self.get('field_id') and self.get('value'): + self.value = FieldMapping( + field_id=self.get('field_id'), + value=self.get('value')['value'], + expression=self.get('value')['expression']) + else: + self.value = None + + +class FieldsMappingsWidget(WidgetListAsTable): + readonly = False + + def __init__(self, name, to_formdef=None, from_options=None, **kwargs): + self.to_formdef = to_formdef + + value = kwargs.get('value') + if isinstance(value, FieldsMappings): + kwargs['value'] = value.mappings + + super(FieldsMappingsWidget, self).__init__( + name, + element_type=FieldMappingWidget, + element_kwargs={ + 'to_formdef': to_formdef, + 'from_options': from_options, + }, + **kwargs) + + def _parse(self, request): + super(FieldsMappingsWidget, self)._parse(request) + + if self.value: + self.value = FieldsMappings( + formdef=self.to_formdef, + mappings=self.value) + + +class LazySubFormData(object): + __formdata = None + + def __init__(self, part): + self.__part = part + + def __getattr__(self, name): + if not self.__formdata: + self.__formdata = self.__part.formdata + return getattr(LazyFormData(self.__formdata), name) + + +class SubFormdatasSubstitutionProxy(object): + __subformdatas = None + __varnames = None + __varname = None + + def __init__(self, parts): + self.__subformdatas = [(part.varname, part, LazySubFormData(part)) for part in parts] + self.__varnames = set(varname for varname, _1, _2 in self.__subformdatas) + self.__varname = None + + def __iter__(self): + for varname, part, subformdata in self.__subformdatas: + if self.__varname is None or self.__varname == varname: + yield subformdata + + def __getitem__(self, idx): + if isinstance(idx, six.integer_types): + return list(self)[idx] + raise KeyError(idx) + + def __getattr__(self, name): + if self.__varname: + return getattr(self[0], name) + elif name in self.__varnames: + new_sub = copy.copy(self) + new_sub.__varname = name + return new_sub + elif name.startswith('_'): + raise AttributeError(name) + else: + return getattr(self[0], name) + + +class CreateFormdataEvolutionPart(object): + def __init__(self, formdata, varname=None): + self.formdef_slug = formdata.formdef.url_name + self.formdata_id = formdata.id + self.varname = varname + + @property + def formdef(self): + return FormDef.get_by_urlname(self.formdef_slug) + + @property + def formdata(self): + return self.formdef.data_class().get(self.formdata_id) + + @classmethod + def get_substitution_variables(cls, formdata): + return { + 'sub_formdatas': SubFormdatasSubstitutionProxy( + formdata.iter_evolution_parts(CreateFormdataEvolutionPart)), + } + + def view(self): + formdata = self.formdata + if get_request().is_in_backoffice(): + url = formdata.get_url(backoffice=True) + else: + url = formdata.get_url(backoffice=False).rstrip('/') + return htmltext('

%s') % ( + url, formdata.get_display_name()) + + +class CreateFormdataWorkflowStatusItem(WorkflowStatusItem): + description = N_('Create formdata') + key = 'create_formdata' + category = 'formdata-action' + support_substitution_variables = True + + label = None + formdef_slug = None + draft = True + fields_mappings = None + varname = None + + def get_line_details(self): + if self.label: + s = self.label + if self.varname: + s += ' - %s' % self.varname + return s + else: + return None + + @property + def formdef(self): + if self.formdef_slug: + return FormDef.get_by_urlname(self.formdef_slug) + return None + + def add_parameters_widgets(self, form, parameters, prefix='', formdef=None): + super(CreateFormdataWorkflowStatusItem, self).add_parameters_widgets( + form, parameters, prefix=prefix, formdef=formdef) + if 'label' in parameters: + form.add(StringWidget, '%slabel' % prefix, + title=_('Label'), + value=self.label or _('Create formdata')) + if 'formdef_slug' in parameters: + list_forms = [('_same', _('Same as form'), '_same')] + list_forms.extend([(x.url_name, x.name, x.url_name) for x in FormDef.select(order_by='name')]) + form.add(SingleSelectWidget, 'formdef_slug', + title=_('Form'), + value=self.formdef_slug, + required=False, + options=list_forms) + if 'draft' in parameters and self.formdef: + form.add(CheckboxWidget, '%sdraft' % prefix, + title=_('Draft'), + value=self.draft, + advanced=not(self.draft)) + if 'fields_mappings' in parameters and self.formdef: + workflow = self.parent.parent + from_options = [ + (varname, field.label, varname) + for varname, field in workflow.formdefs_common_fields.items()] + form.add(FieldsMappingsWidget, '%sfields_mappings' % prefix, + title=_('Information Text for Backoffice'), + to_formdef=self.formdef, + from_options=from_options, + value=self.fields_mappings) + if 'varname' in parameters: + form.add(VarnameWidget, '%svarname' % prefix, + title=_('Identifier'), value=self.varname, + hint=_('This is used to get generated document in expressions.'), + advanced=not(self.varname)) + + def get_parameters(self): + return ('label', 'formdef_slug', 'draft', 'fields_mappings', 'varname') + + def perform(self, formdata): + formdef = self.formdef + if not formdef: + return + + new_formdata = formdef.data_class()() + new_formdata.status = 'draft' + new_formdata.receipt_time = time.localtime() + new_formdata.user_id = formdata.user_id + # XXX: what is the context ? Should it be customizable as the fields ? + new_formdata.submission_context = formdata.submission_context + new_formdata.submission_channel = formdata.submission_channel + new_formdata.backoffice_submission = get_request().is_in_backoffice() + if not new_formdata.submission_context: + new_formdata.submission_context = {} + new_formdata.submission_context['orig_formdef_id'] = formdata.formdef.id + new_formdata.submission_context['orig_formdata_id'] = formdata.id + new_formdata.data = {} + + if self.fields_mappings: + self.fields_mappings.apply( + status_item=self, + to_formdata=new_formdata, + from_formdata=formdata) + + if self.draft: + new_formdata.store() + else: + new_formdata.just_created() + new_formdata.store() + new_formdata.perform_workflow() + new_formdata.store() + + if formdata.user_id is None and not new_formdata.backoffice_submission: + get_session().mark_anonymous_formdata(new_formdata) + + evo = formdata.evolution[-1] + evo.add_part(CreateFormdataEvolutionPart(new_formdata, varname=self.varname)) + formdata.store() + + +register_item_class(CreateFormdataWorkflowStatusItem) diff --git a/wcs/workflows.py b/wcs/workflows.py index b33926f0..9950ecfa 100644 --- a/wcs/workflows.py +++ b/wcs/workflows.py @@ -2809,6 +2809,6 @@ def load_extra(): import wf.profile import wf.backoffice_fields import wf.redirect_to_url - import wf.formdata + import wf.create_formdata from wf.export_to_model import ExportToModel -- 2.20.1