From e2535554b1ebb98cd37723f9cff2673c7a2b275a Mon Sep 17 00:00:00 2001 From: Kazuhiko Shiozaki <kazuhiko@nexedi.com> Date: Mon, 18 Sep 2017 11:24:23 +0200 Subject: [PATCH] Formulator: prepare DateTimeField's sub_form dynamically. otherwise TALES in input_style does not work and changes in the original proxy field will not be reflected. --- product/ERP5Form/ProxyField.py | 8 +- product/ERP5Form/tests/testFields.py | 60 +++++---------- product/Formulator/Field.py | 6 +- product/Formulator/StandardFields.py | 110 ++++++++++++++------------- product/Formulator/Widget.py | 4 - product/Formulator/XMLToForm.py | 5 -- product/Formulator/tests/testForm.py | 27 +++++-- 7 files changed, 105 insertions(+), 115 deletions(-) diff --git a/product/ERP5Form/ProxyField.py b/product/ERP5Form/ProxyField.py index 961578200c..240baa58f8 100644 --- a/product/ERP5Form/ProxyField.py +++ b/product/ERP5Form/ProxyField.py @@ -493,10 +493,10 @@ class ProxyField(ZMIField): field = template_field return template_field - def _aq_dynamic(self, name): - if name == 'sub_form': - return self.getTemplateField().sub_form - return None + def _get_sub_form(self, field=None): + if field is None: + field = self + return self.getTemplateField()._get_sub_form(field=field) security.declareProtected('Access contents information', 'is_delegated') diff --git a/product/ERP5Form/tests/testFields.py b/product/ERP5Form/tests/testFields.py index 57933de1a7..05fb91ea1e 100644 --- a/product/ERP5Form/tests/testFields.py +++ b/product/ERP5Form/tests/testFields.py @@ -764,14 +764,31 @@ class TestProxyField(ERP5TypeTestCase): 'my_date', 'Date', 'ProxyField') proxy_field.manage_edit_xmlrpc(dict(form_id='Base_viewProxyFieldLibrary', field_id='my_date',)) - self.assertTrue(hasattr(proxy_field, 'sub_form')) - self.assertTrue(aq_base(proxy_field.sub_form) is - aq_base(original_field.sub_form)) + self.assertTrue(hasattr(proxy_field, '_get_sub_form')) + self.assertEqual(proxy_field._get_sub_form().render(), + original_field._get_sub_form().render()) + # we can render proxy_field.render() # and validate self.container.Base_view.validate_all_to_request(self.portal.REQUEST) + # change style in the original field + original_field.manage_edit_xmlrpc(dict(input_style='number')) + self.assertTrue('type="number"' in original_field.render()) + self.assertTrue('type="number"' in proxy_field.render()) + + # override style in the proxy field + original_field.manage_edit_xmlrpc(dict(input_style='text')) + proxy_field._surcharged_edit({'input_style': 'number'}, ['input_style']) + self.assertTrue('type="text"' in original_field.render()) + self.assertTrue('type="number"' in proxy_field.render()) + + # unproxify the proxy field + self.container.Base_view.unProxifyField({'my_date': 'on'}) + unproxified_field = self.container.Base_view.my_date + self.assertTrue('type="number"' in unproxified_field.render()) + def test_manage_edit_surcharged_xmlrpc(self): # manage_edit_surcharged_xmlrpc is a method to edit proxyfields # programmatically @@ -942,7 +959,7 @@ class TestFieldValueCache(ERP5TypeTestCase): addField(DateTimeField('datetime_field')) form.datetime_field._p_oid = makeDummyOid() form.datetime_field._edit(dict(input_style='list')) - for i in form.datetime_field.sub_form.fields.values(): + for i in form.datetime_field._get_sub_form().fields.values(): i._p_oid = makeDummyOid() def test_method_field(self): @@ -987,41 +1004,6 @@ class TestFieldValueCache(ERP5TypeTestCase): self.root.form.proxy_field_tales.get_value('title') self.assertEqual(True, cache_size == self._getCacheSize('ProxyField.get_value')) - def test_datetime_field(self): - field_value_cache.clear() - - # make sure that boundmethod must not be cached. - year_field = self.root.form.datetime_field.sub_form.get_field('year', include_disabled=1) - self.assertEqual(True, type(year_field.overrides['items']) is BoundMethod) - - cache_size = len(field_value_cache) - year_field.get_value('items') - - # See Formulator/StandardFields.py(line:174) - # there are two get_value, start_datetime and end_datetime - cache_size += 2 - - # make sure that boundmethod is not cached(cache size does not change) - self.assertEqual(True, ('Form.get_value', - self.root.form.datetime_field._p_oid, - self.root.form.datetime_field._p_oid, - 'start_datetime' - ) in field_value_cache) - self.assertEqual(True, ('Form.get_value', - self.root.form.datetime_field._p_oid, - self.root.form.datetime_field._p_oid, - 'end_datetime' - ) in field_value_cache) - self.assertEqual(False, ('Form.get_value', - year_field._p_oid, - year_field._p_oid, - 'items' - ) in field_value_cache) - self.assertEqual(cache_size, len(field_value_cache)) - - year_field.get_value('size') - year_field.get_value('default') - self.assertEqual(cache_size+2, len(field_value_cache)) def makeDummyOid(): import time, random diff --git a/product/Formulator/Field.py b/product/Formulator/Field.py index 546ca4a271..ab0f583e26 100644 --- a/product/Formulator/Field.py +++ b/product/Formulator/Field.py @@ -403,7 +403,7 @@ class Field: a form. Works like render() but for sub field. Added key parameter for ERP5 in order to be compatible with listbox/matrixbox """ - return self.sub_form.get_field(id)._render_helper( + return self._get_sub_form().get_field(id)._render_helper( self.generate_subfield_key(id, key=key), value, REQUEST, render_prefix) security.declareProtected('View', 'render_sub_field_from_request') @@ -411,7 +411,7 @@ class Field: """Convenience method; render the field widget from REQUEST (unvalidated data), or default if no raw data is found. """ - return self.sub_form.get_field(id)._render_helper( + return self._get_sub_form().get_field(id)._render_helper( self.generate_subfield_key(id), None, REQUEST) security.declarePrivate('_validate_helper') @@ -441,7 +441,7 @@ class Field: def validate_sub_field(self, id, REQUEST, key=None): """Validates a subfield (as part of field validation). """ - return self.sub_form.get_field(id)._validate_helper( + return self._get_sub_form().get_field(id)._validate_helper( self.generate_subfield_key(id, validation=1, key=key), REQUEST) def PrincipiaSearchSource(self): diff --git a/product/Formulator/StandardFields.py b/product/Formulator/StandardFields.py index 10cda57915..b803087578 100644 --- a/product/Formulator/StandardFields.py +++ b/product/Formulator/StandardFields.py @@ -127,60 +127,19 @@ class DateTimeField(ZMIField): widget = Widget.DateTimeWidgetInstance validator = Validator.DateTimeValidatorInstance - def __init__(self, id, **kw): - # icky but necessary... - apply(ZMIField.__init__, (self, id), kw) - - input_style = self.get_value('input_style') + def _get_sub_form(self, field=None): + if field is None: + field = self + input_style = field.get_value('input_style') if input_style == 'text': - self.sub_form = create_datetime_text_sub_form() + return create_datetime_text_sub_form(self) elif input_style == 'list': - self.sub_form = create_datetime_list_sub_form() + return create_datetime_list_sub_form(self) elif input_style == 'number': - self.sub_form = create_datetime_number_sub_form() + return create_datetime_number_sub_form(self) else: assert 0, "Unknown input_style '%s'" % input_style - def on_value_input_style_changed(self, value): - if value == 'text': - self.sub_form = create_datetime_text_sub_form() - elif value == 'list': - self.sub_form = create_datetime_list_sub_form() - year_field = self.sub_form.get_field('year', include_disabled=1) - year_field.overrides['items'] = BoundMethod(self, - '_override_year_items') - elif value == 'number': - self.sub_form = create_datetime_number_sub_form() - else: - assert 0, "Unknown input_style." - self.on_value_css_class_changed(self.values['css_class']) - - def on_value_timezone_style_changed(self, value): - if value: - input_style = self.get_value('input_style') - self.on_value_input_style_changed(input_style) - - def on_value_css_class_changed(self, value): - for field in self.sub_form.get_fields(): - field.values['css_class'] = value - field._p_changed = 1 - - def _override_year_items(self): - """The method gets called to get the right amount of years. - """ - start_datetime = self.get_value('start_datetime') - end_datetime = self.get_value('end_datetime') - current_year = DateTime().year() - if start_datetime: - first_year = start_datetime.year() - else: - first_year = current_year - if end_datetime: - last_year = end_datetime.year() + 1 - else: - last_year = first_year + 11 - return create_items(first_year, last_year, digits=4) - def _get_user_input_value(self, key, REQUEST): """ Try to get a value of the field from the REQUEST @@ -188,12 +147,14 @@ class DateTimeField(ZMIField): if REQUEST.form['subfield_%s_%s' % (key, 'year')]: return None -def create_datetime_text_sub_form(): +def create_datetime_text_sub_form(field): sub_form = BasicForm() + css_class = field.get_value('css_class') year = IntegerField('year', title="Year", required=0, + css_class=css_class, display_width=4, display_maxwidth=4, max_length=4) @@ -201,6 +162,7 @@ def create_datetime_text_sub_form(): month = IntegerField('month', title="Month", required=0, + css_class=css_class, display_width=2, display_maxwidth=2, max_length=2) @@ -208,6 +170,7 @@ def create_datetime_text_sub_form(): day = IntegerField('day', title="Day", required=0, + css_class=css_class, display_width=2, display_maxwidth=2, max_length=2) @@ -217,6 +180,7 @@ def create_datetime_text_sub_form(): hour = IntegerField('hour', title="Hour", required=0, + css_class=css_class, display_width=2, display_maxwidth=2, max_length=2) @@ -224,6 +188,7 @@ def create_datetime_text_sub_form(): minute = IntegerField('minute', title="Minute", required=0, + css_class=css_class, display_width=2, display_maxwidth=2, max_length=2) @@ -231,32 +196,48 @@ def create_datetime_text_sub_form(): ampm = StringField('ampm', title="am/pm", required=0, + css_class=css_class, display_width=2, display_maxwidth=2, max_length=2) timezone = ListField('timezone', title = "Timezone", required = 0, + css_class=css_class, default = 'GMT', items = Widget.gmt_timezones, size = 1) sub_form.add_fields([hour, minute, ampm, timezone], "time") return sub_form -def create_datetime_list_sub_form(): +def create_datetime_list_sub_form(field): """ Patch Products.Formulator.StandardFields so we can add timezone subfield """ sub_form = BasicForm() + css_class = field.get_value('css_class') + start_datetime = field.get_value('start_datetime') + end_datetime = field.get_value('end_datetime') + current_year = DateTime().year() + if start_datetime: + first_year = start_datetime.year() + else: + first_year = current_year + if end_datetime: + last_year = end_datetime.year() + 1 + else: + last_year = first_year + 11 year = ListField('year', title="Year", required=0, + css_class=css_class, default="", - items=create_items(2000, 2010, digits=4), + items=create_items(first_year, last_year, digits=4), size=1) month = ListField('month', title="Month", required=0, + css_class=css_class, default="", items=create_items(1, 13, digits=2), size=1) @@ -264,6 +245,7 @@ def create_datetime_list_sub_form(): day = ListField('day', title="Day", required=0, + css_class=css_class, default="", items=create_items(1, 32, digits=2), size=1) @@ -274,6 +256,7 @@ def create_datetime_list_sub_form(): hour = IntegerField('hour', title="Hour", required=0, + css_class=css_class, display_width=2, display_maxwidth=2, max_length=2) @@ -281,6 +264,7 @@ def create_datetime_list_sub_form(): minute = IntegerField('minute', title="Minute", required=0, + css_class=css_class, display_width=2, display_maxwidth=2, max_length=2) @@ -288,6 +272,7 @@ def create_datetime_list_sub_form(): ampm = ListField('ampm', title="am/pm", required=0, + css_class=css_class, default="am", items=[("am","am"), ("pm","pm")], @@ -295,6 +280,7 @@ def create_datetime_list_sub_form(): timezone = ListField('timezone', title = "Timezone", required = 0, + css_class=css_class, default = 'GMT', items = Widget.gmt_timezones, size = 1) @@ -303,14 +289,26 @@ def create_datetime_list_sub_form(): sub_form.add_fields([hour, minute, ampm, timezone], "time") return sub_form -def create_datetime_number_sub_form(): +def create_datetime_number_sub_form(field): sub_form = BasicForm() + css_class = field.get_value('css_class') + start_datetime = field.get_value('start_datetime') + end_datetime = field.get_value('end_datetime') + if start_datetime: + first_year = start_datetime.year() + else: + first_year = 0 + if end_datetime: + last_year = end_datetime.year() + else: + last_year = 9999 year = IntegerField('year', title="Year", required=0, input_type='number', - extra='min="0" max="9999"', + css_class=css_class, + extra='min="%s" max="%s"' % (first_year, last_year), display_width=4, display_maxwidth=4, max_length=4) @@ -319,6 +317,7 @@ def create_datetime_number_sub_form(): title="Month", required=0, input_type='number', + css_class=css_class, extra='min="1" max="12"', display_width=2, display_maxwidth=2, @@ -327,6 +326,7 @@ def create_datetime_number_sub_form(): day = IntegerField('day', title="Day", required=0, + css_class=css_class, input_type='number', extra='min="1" max="31"', display_width=2, @@ -338,6 +338,7 @@ def create_datetime_number_sub_form(): hour = IntegerField('hour', title="Hour", required=0, + css_class=css_class, input_type='number', extra='min="0" max="23"', display_width=2, @@ -347,6 +348,7 @@ def create_datetime_number_sub_form(): minute = IntegerField('minute', title="Minute", required=0, + css_class=css_class, input_type='number', extra='min="0" max="59"', display_width=2, @@ -356,12 +358,14 @@ def create_datetime_number_sub_form(): ampm = StringField('ampm', title="am/pm", required=0, + css_class=css_class, display_width=2, display_maxwidth=2, max_length=2) timezone = ListField('timezone', title = "Timezone", required = 0, + css_class=css_class, default = 'GMT', items = Widget.gmt_timezones, size = 1) diff --git a/product/Formulator/Widget.py b/product/Formulator/Widget.py index 7e675ebe9e..1088dc427f 100644 --- a/product/Formulator/Widget.py +++ b/product/Formulator/Widget.py @@ -1455,10 +1455,6 @@ class DateTimeWidget(Widget): def render(self, field, key, value, REQUEST, render_prefix=None): use_ampm = field.get_value('ampm_time_style') use_timezone = field.get_value('timezone_style') - # FIXME: backwards compatibility hack: - if not hasattr(field, 'sub_form'): - from StandardFields import create_datetime_text_sub_form - field.sub_form = create_datetime_text_sub_form() # Is it still usefull to test the None value, # as DateTimeField should be considerer as the other field diff --git a/product/Formulator/XMLToForm.py b/product/Formulator/XMLToForm.py index 08d9303018..68d0c723cd 100644 --- a/product/Formulator/XMLToForm.py +++ b/product/Formulator/XMLToForm.py @@ -91,11 +91,6 @@ def XMLToForm(s, form, override_encoding=None): else: field.values[name] = encode(value.text, encoding) - # special hack for the DateTimeField - if field.meta_type=='DateTimeField': - field.on_value_input_style_changed( - field.get_value('input_style')) - # set tales tales = entry.first.tales for name in tales.getElementNames(): diff --git a/product/Formulator/tests/testForm.py b/product/Formulator/tests/testForm.py index aa054a859d..efd8893721 100644 --- a/product/Formulator/tests/testForm.py +++ b/product/Formulator/tests/testForm.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import unittest, re +from DateTime import DateTime import Zope2 # XXX this does not work for zope2.x if x < 3 @@ -120,14 +121,10 @@ class FormTestCase(unittest.TestCase): self.form.manage_addProduct['Formulator']\ .manage_addField('date_time','Test Field','DateTimeField') field = self.form.date_time - sub_form = field.sub_form - if sub_form.has_field('timezone'): - del sub_form.fields['timezone'] - #now timezone is not presented - self.assertFalse(self.form.date_time.sub_form.has_field('timezone')) + field._edit({'timezone_style': 0}) + self.assertFalse('<select size="1" name="subfield_field_date_time_timezone" >' in field.render()) field._edit({'timezone_style': 1}) - #test if timezone's presented - self.assertTrue(self.form.date_time.sub_form.has_field('timezone')) + self.assertTrue('<select size="1" name="subfield_field_date_time_timezone" >' in field.render()) def test_datetime_css_class_rendering(self): @@ -177,6 +174,22 @@ class FormTestCase(unittest.TestCase): self.assertEqual(0, len(css_matches)) + def test_datetime_number_style(self): + # test that a bug is fixed, which causing the css_class value + # not to be rendered + self.form.manage_addProduct['Formulator']\ + .manage_addField('date_time','Test Field','DateTimeField') + field = self.form.date_time + field._edit({'input_style': 'number'}) + self.assertTrue('<input name="subfield_field_date_time_year" value="" maxlength="4" type="number" size="4" min="0" max="9999" />' in field.render()) + field._edit({'start_datetime': DateTime('1900/01/01'), 'end_datetime': None}) + self.assertTrue('<input name="subfield_field_date_time_year" value="" maxlength="4" type="number" size="4" min="1900" max="9999" />' in field.render()) + field._edit({'start_datetime': None, 'end_datetime': DateTime('2099/12/31')}) + self.assertTrue('<input name="subfield_field_date_time_year" value="" maxlength="4" type="number" size="4" min="0" max="2099" />' in field.render()) + field._edit({'start_datetime': DateTime('1900/01/01'), 'end_datetime': DateTime('2099/12/31')}) + self.assertTrue('<input name="subfield_field_date_time_year" value="" maxlength="4" type="number" size="4" min="1900" max="2099" />' in field.render()) + + def test_suite(): suite = unittest.TestSuite() -- 2.30.9