From 9af4b3952fea2b2234e49e6d57ec502a42116c5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Fri, 26 Nov 2021 09:02:05 +0100 Subject: [PATCH] fields: add support for card data sources to computed fields (#58913) --- tests/form_pages/test_computed_field.py | 47 ++++++++++++ wcs/fields.py | 15 +++- wcs/variables.py | 98 ++++++++++++++----------- 3 files changed, 116 insertions(+), 44 deletions(-) diff --git a/tests/form_pages/test_computed_field.py b/tests/form_pages/test_computed_field.py index b6b3ccb99..21c86ff0c 100644 --- a/tests/form_pages/test_computed_field.py +++ b/tests/form_pages/test_computed_field.py @@ -5,6 +5,7 @@ import pytest from django.utils.timezone import make_aware from wcs import fields +from wcs.carddef import CardDef from wcs.formdef import FormDef from wcs.qommon.substitution import CompatibilityNamesDict from wcs.workflows import EditableWorkflowStatusItem, Workflow @@ -586,3 +587,49 @@ def test_computed_field_usage_in_criteria(pub): resp = get_app(pub).get('/test/?param=plop') resp = resp.forms[0].submit('submit') # -> 2nd page assert 'count with this value: 0' in resp.text + + +def test_computed_field_with_data_source(pub): + CardDef.wipe() + FormDef.wipe() + + carddef = CardDef() + carddef.name = 'items' + carddef.digest_templates = {'default': '{{form_var_name}}'} + carddef.fields = [ + fields.StringField(id='0', label='string', varname='name'), + fields.StringField(id='1', label='string', varname='attr'), + ] + carddef.store() + for i, value in enumerate(['foo', 'bar', 'baz']): + carddata = carddef.data_class()() + carddata.data = { + '0': value, + '1': 'attr%s' % i, + } + carddata.just_created() + carddata.store() + + ds = {'type': 'carddef:%s' % carddef.url_name} + + formdef = FormDef() + formdef.name = 'test' + formdef.fields = [ + fields.ComputedField( + id='1', + label='computed', + varname='computed', + value_template='{{ request.GET.param }}', + freeze_on_initial_value=True, + data_source=ds, + ), + fields.CommentField(id='2', label='X{{ form_var_computed_live_var_name }}Y', type='comment'), + ] + formdef.store() + formdef.data_class().wipe() + + resp = get_app(pub).get('/test/?param=%s' % carddata.id) + assert 'XbazY' in resp.text + + resp = get_app(pub).get('/test/?param=%s' % 'invalid') + assert 'XY' in resp.text diff --git a/wcs/fields.py b/wcs/fields.py index 6f661df70..3ddd95edd 100644 --- a/wcs/fields.py +++ b/wcs/fields.py @@ -3484,6 +3484,7 @@ class ComputedField(Field): value_template = None freeze_on_initial_value = False + data_source = {} add_to_form = None add_to_view_form = None @@ -3497,7 +3498,7 @@ class ComputedField(Field): def get_admin_attributes(self): attributes = super().get_admin_attributes() attributes.remove('condition') - return attributes + ['varname', 'value_template', 'freeze_on_initial_value'] + return attributes + ['varname', 'value_template', 'freeze_on_initial_value', 'data_source'] def fill_admin_form(self, form): form.add(StringWidget, 'label', title=_('Label'), value=self.label, required=True, size=50) @@ -3526,6 +3527,18 @@ class ComputedField(Field): title=_('Freeze on initial value'), value=self.freeze_on_initial_value, ) + form.add( + data_sources.DataSourceSelectionWidget, + 'data_source', + value=self.data_source, + allow_jsonp=False, + title=_('Data Source'), + hint=_('This will make linked card data available for expressions.'), + required=False, + ) + + def get_real_data_source(self): + return data_sources.get_real(self.data_source) register_field_class(ComputedField) diff --git a/wcs/variables.py b/wcs/variables.py index 1c7bea9a6..87cdccd47 100644 --- a/wcs/variables.py +++ b/wcs/variables.py @@ -844,49 +844,7 @@ class LazyFieldVarComplex(LazyFieldVar): raise KeyError(key) -class LazyFieldVarComputed(LazyFieldVarComplex): - def get_field_var_value(self): - return self.get_value() - - -class LazyFieldVarStructured(LazyFieldVarComplex): - def inspect_keys(self): - if not self._data.get(self._field.id): - return [] - real_data_source = self._field.get_real_data_source() - if real_data_source and real_data_source.get('type', '') == 'wcs:users': - return ['raw', 'live'] - - structured_value = self._field.get_structured_value(self._data) - if not structured_value: - return ['raw'] - - keys = ['raw', 'structured'] - if real_data_source and real_data_source.get('type', '').startswith('carddef:'): - try: - self.live - except AttributeError: - # don't advertise "live" if linked data is missing - pass - else: - keys.append('live') - - keys.extend(super().inspect_keys()) - - return keys - - @property - def structured_raw(self): - # backward compatibility, _structured should be use. - return self._field.get_structured_value(self._data) - - @property - def structured(self): - return self._field.get_structured_value(self._data) - - def get_field_var_value(self): - return self.structured - +class LazyFieldVarLiveCardMixin: @property def live(self): real_data_source = self._field.get_real_data_source() @@ -928,6 +886,60 @@ class LazyFieldVarStructured(LazyFieldVarComplex): return LazyFormData(carddata) +class LazyFieldVarComputed(LazyFieldVarComplex, LazyFieldVarLiveCardMixin): + def inspect_keys(self): + keys = super().inspect_keys() + try: + self.live + except AttributeError: + pass # don't advertise if there's no value behind + else: + keys.append('live') + return keys + + def get_field_var_value(self): + return self.get_value() + + +class LazyFieldVarStructured(LazyFieldVarComplex, LazyFieldVarLiveCardMixin): + def inspect_keys(self): + if not self._data.get(self._field.id): + return [] + real_data_source = self._field.get_real_data_source() + if real_data_source and real_data_source.get('type', '') == 'wcs:users': + return ['raw', 'live'] + + structured_value = self._field.get_structured_value(self._data) + if not structured_value: + return ['raw'] + + keys = ['raw', 'structured'] + if real_data_source and real_data_source.get('type', '').startswith('carddef:'): + try: + self.live + except AttributeError: + # don't advertise "live" if linked data is missing + pass + else: + keys.append('live') + + keys.extend(super().inspect_keys()) + + return keys + + @property + def structured_raw(self): + # backward compatibility, _structured should be use. + return self._field.get_structured_value(self._data) + + @property + def structured(self): + return self._field.get_structured_value(self._data) + + def get_field_var_value(self): + return self.structured + + class DateOperatorsMixin: def __eq__(self, other): if hasattr(other, 'timetuple'):