diff --git a/product/ERP5Form/ProxyField.py b/product/ERP5Form/ProxyField.py index 9af37cbb0d9a18ee12959886d0f6b58234dadd6e..44eba583846d615bddfe02ec13b0cceff6316088 100644 --- a/product/ERP5Form/ProxyField.py +++ b/product/ERP5Form/ProxyField.py @@ -671,6 +671,7 @@ class ProxyField(ZMIField): # defined by a TALES if self._p_oid is None or self.tales['field_id'] or self.tales['form_id']: return self._get_value(id, **kw) + # XXX: Are these disabled? proxy_field = self.getTemplateField(cache=False) if proxy_field is not None: return proxy_field.get_value(id, **kw) diff --git a/product/ERP5Form/Report.py b/product/ERP5Form/Report.py index 5f9d785dc1cb733cbf78305504aa0db149f5d75d..01c6f54416ea7e59f37a79707efd20675193b879 100644 --- a/product/ERP5Form/Report.py +++ b/product/ERP5Form/Report.py @@ -28,6 +28,7 @@ from Globals import InitializeClass, DTMLFile, get_request from AccessControl import ClassSecurityInfo +from Acquisition import aq_base from Products.PythonScripts.Utility import allow_class from Products.Formulator.DummyField import fields from Products.Formulator.Form import ZMIForm @@ -123,6 +124,7 @@ class ERP5Report(ERP5Form): # Proxy method to PageTemplate def __call__(self, *args, **kwargs): + warn("ERP5Report to be obsolete, please use ReportBox and normal ERP5Form instead.", DeprecationWarning) if not self.report_method: raise KeyError, 'report method is not set on the report' @@ -205,7 +207,8 @@ class ReportSection: selection_stats=None, selection_sort_order=None, selection_report_path=None, - selection_report_list=None): + selection_report_list=None, + temporary_selection=True): """ Initialize the line and set the default values Selected columns must be defined in parameter of listbox.render... @@ -213,6 +216,8 @@ class ReportSection: In ReportTree listbox display mode, you can override : selection_report_path, the root category for this report selection_report_list, the list of unfolded categories (defaults to all) + + If temporary_selection is False, the selection will be written which is specified by selection_name. """ self.path = path @@ -222,20 +227,18 @@ class ReportSection: warn("Don't use translated_title, but title directly", DeprecationWarning) self.translated_title = translated_title self.level = level - self.saved_request = {} self.selection_name = selection_name self.selection_params = selection_params self.listbox_display_mode = listbox_display_mode self.selection_columns = selection_columns self.selection_stats = selection_stats self.selection_sort_order = selection_sort_order - self.saved_selections = {} self.selection_report_path = selection_report_path self.selection_report_list = selection_report_list - self.saved_request_form = {} self.param_dict = param_dict or {} self.param_list = param_list or [] self.method_id = method_id + self.temporary_selection = temporary_selection security.declarePublic('getTitle') def getTitle(self): @@ -266,17 +269,36 @@ class ReportSection: def getFormId(self): return self.form_id - _no_parameter_ = [] - + def pushRequest(self): + self = aq_base(self) + if hasattr(self, '_REQUEST'): + raise ValueError, "can not replace the backupped REQUEST" + self._REQUEST = {'form': {}, 'other': {},} + REQUEST = get_request() + self._REQUEST['form'].update(REQUEST.form) + self._REQUEST['other'].update(REQUEST.other) + REQUEST.form.update(self.param_dict) + + def popRequest(self): + self = aq_base(self) + if not hasattr(self, '_REQUEST'): + raise ValueError, "no backupped REQUEST" + REQUEST = get_request() + REQUEST.form.clear() + REQUEST.other.clear() + REQUEST.form.update(self._REQUEST['form']) + REQUEST.other.update(self._REQUEST['other']) + del self._REQUEST + security.declarePublic('pushReport') def pushReport(self, context, render_prefix=None): - REQUEST = get_request() - for k,v in self.param_dict.items(): - self.saved_request[k] = REQUEST.form.get(k, self._no_parameter_) - REQUEST.form[k] = v + self.pushRequest() + REQUEST = get_request() portal_selections = context.portal_selections selection_list = [self.selection_name] + # when the Form which is specified by form_id, has a listbox, make prefixed_selection_name. + # which is based on specified selection_name in the listbox. if self.getFormId() and hasattr(context[self.getFormId()], 'listbox') : selection_name = context[self.getFormId()].listbox.get_value('selection_name') if render_prefix is not None: @@ -285,62 +307,46 @@ class ReportSection: selection_list += [selection_name] # save report's selection and orignal form's selection, #as ListBox will overwrite it - for selection_name in selection_list : - if selection_name is not None : - if not self.saved_selections.has_key(selection_name) : - self.saved_selections[selection_name] = {} - if self.selection_report_list is not None: - selection = portal_selections.getSelectionFor(selection_name, - REQUEST=REQUEST) - if selection is None: - selection = Selection() - portal_selections.setSelectionFor(selection_name, selection, + for selection_name in filter(lambda x: x is not None, selection_list): + if self.temporary_selection: + portal_selections.pushSelection(selection_name) + else: + if portal_selections.getSelectionFor(selection_name) is None: + portal_selections.setSelectionFor(selection_name, Selection()) + + if self.selection_report_list is not None: + selection = portal_selections.getSelectionFor(selection_name, + REQUEST=REQUEST) + selection.edit(report_list=self.selection_report_list) + if self.selection_report_path is not None: + selection = portal_selections.getSelectionFor(selection_name, + REQUEST=REQUEST) + selection.edit(report_path=self.selection_report_path) + if self.listbox_display_mode is not None: + # XXX Dirty fix, to be able to change the display mode in form_view + REQUEST.list_selection_name = selection_name + portal_selections.setListboxDisplayMode(REQUEST, + self.listbox_display_mode, + selection_name=selection_name) + if self.selection_params is not None: + params = portal_selections.getSelectionParamsFor(selection_name, + REQUEST=REQUEST) + params.update(self.selection_params) + portal_selections.setSelectionParamsFor(selection_name, + params, + REQUEST=REQUEST) + if self.selection_columns is not None: + portal_selections.setSelectionColumns(selection_name, + self.selection_columns, REQUEST=REQUEST) - self.saved_selections[selection_name]['report_list'] = \ - selection.getReportList() - selection.edit(report_list=self.selection_report_list) - if self.selection_report_path is not None: - selection = portal_selections.getSelectionFor(selection_name, - REQUEST=REQUEST) - self.saved_selections[selection_name]['report_path'] = \ - selection.getReportPath() - selection.edit(report_path=self.selection_report_path) - if self.listbox_display_mode is not None: - self.saved_selections[selection_name]['display_mode'] = \ - portal_selections.getListboxDisplayMode(selection_name, - REQUEST=REQUEST) - # XXX Dirty fix, to be able to change the display mode in form_view - REQUEST.list_selection_name = selection_name - portal_selections.setListboxDisplayMode( - REQUEST, self.listbox_display_mode, - selection_name=selection_name) - if self.selection_params is not None: - params = portal_selections.getSelectionParams( - selection_name, REQUEST=REQUEST) - self.saved_selections[selection_name]['params'] = params.copy() - params.update(self.selection_params) - portal_selections.setSelectionParamsFor(selection_name, - params, REQUEST=REQUEST) - if self.selection_columns is not None: - self.saved_selections[selection_name]['columns'] = \ - portal_selections.getSelectionColumns(selection_name, - REQUEST=REQUEST) - portal_selections.setSelectionColumns(selection_name, - self.selection_columns, REQUEST=REQUEST) - if self.selection_sort_order is not None: - self.saved_selections[selection_name]['sort_order'] = \ - portal_selections.getSelectionSortOrder(selection_name, - REQUEST=REQUEST) - portal_selections.setSelectionSortOrder(selection_name, - self.selection_sort_order, REQUEST=REQUEST) - if self.selection_stats is not None: - self.saved_selections[selection_name]['stats'] = \ - portal_selections.getSelectionStats(selection_name, - REQUEST=REQUEST) - portal_selections.setSelectionStats(selection_name, - self.selection_stats, REQUEST=REQUEST) - - self.saved_request_form = REQUEST.form + if self.selection_sort_order is not None: + portal_selections.setSelectionSortOrder(selection_name, + self.selection_sort_order, + REQUEST=REQUEST) + if self.selection_stats is not None: + portal_selections.setSelectionStats(selection_name, + self.selection_stats, + REQUEST=REQUEST) # When rendering a report section with a listbox, listbox gets parameters # from request.form and edits selection with those parameters, so if you @@ -354,68 +360,18 @@ class ReportSection: security.declarePublic('popReport') def popReport(self, context, render_prefix=None): - REQUEST = get_request() - for k,v in self.param_dict.items(): - if self.saved_request[k] is self._no_parameter_: - if REQUEST.form.has_key(k): - del REQUEST.form[k] - else: - REQUEST.form[k] = self.saved_request[k] + self.popRequest() portal_selections = context.portal_selections selection_list = [] if self.getFormId() and hasattr(context[self.getFormId()], 'listbox') : selection_name = context[self.getFormId()].listbox.get_value('selection_name') - if render_prefix is not None: - del REQUEST.other['prefixed_selection_name'] - # Return before cleanup, because there is no interest in cleaning up - # what won't be shared. - return selection_list += [selection_name] selection_list += [self.selection_name] - # restore report then form selection - for selection_name in selection_list: - if selection_name is not None: - if self.selection_report_list is not None: - selection = portal_selections.getSelectionFor( - selection_name, REQUEST=REQUEST) - selection.edit(report_list = - self.saved_selections[selection_name]['report_list']) - if self.selection_report_path is not None: - selection = portal_selections.getSelectionFor( - selection_name, REQUEST=REQUEST) - selection.edit(report_path = - self.saved_selections[selection_name]['report_path']) - if self.listbox_display_mode is not None: - # XXX Dirty fix, to be able to change the display mode in form_view - REQUEST.list_selection_name = selection_name - portal_selections.setListboxDisplayMode( - REQUEST, - self.saved_selections[selection_name]['display_mode'], - selection_name=selection_name) - - # first make sure no parameters that have been pushed are kept - portal_selections.setSelectionParamsFor(selection_name, - {}, REQUEST=REQUEST) - if self.selection_params is not None: - # then restore the original params - portal_selections.setSelectionParamsFor(selection_name, - self.saved_selections[selection_name]['params'], - REQUEST=REQUEST) - if self.selection_columns is not None: - portal_selections.setSelectionColumns(selection_name, - self.saved_selections[selection_name]['columns'], - REQUEST=REQUEST) - if self.selection_sort_order is not None: - portal_selections.setSelectionSortOrder(selection_name, - self.saved_selections[selection_name]['sort_order'], - REQUEST=REQUEST) - if self.selection_stats is not None: - portal_selections.setSelectionStats(selection_name, - self.saved_selections[selection_name]['stats'], - REQUEST=REQUEST) - - REQUEST.form = self.saved_request_form + if self.temporary_selection: + for selection_name in selection_list: + if selection_name is not None: + portal_selections.popSelection(selection_name) InitializeClass(ReportSection) allow_class(ReportSection) diff --git a/product/ERP5Form/ReportBox.py b/product/ERP5Form/ReportBox.py new file mode 100644 index 0000000000000000000000000000000000000000..468b711f531adf94eb2b38f5affe01f3a2c4456c --- /dev/null +++ b/product/ERP5Form/ReportBox.py @@ -0,0 +1,84 @@ +############################################################################## +# +# Copyright (c) 2009 Nexedi SARL and Contributors. All Rights Reserved. +# Jean-Paul Smets-Solanes <jp@nexedi.com> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +from Globals import get_request +from AccessControl.ZopeGuards import guarded_getattr +from Products.Formulator import Widget, Validator +from Products.Formulator.Field import ZMIField +from Products.Formulator.DummyField import fields + +class ReportBoxWidget(Widget.Widget): + + property_names = list(Widget.Widget.property_names) + property_names.append('report_method') + + # XXX this is only needed on bootstrap + default = fields.StringField('default', + title='Default', + description="", + default="", + required=0) + + report_method = fields.StringField('report_method', + title='Report Method', + description="", + default="", + required=0) + + def render_view(self, field, value, REQUEST=None, key='reportbox', render_prefix=None): + """ + """ + if REQUEST is None: + REQUEST = get_request() + return self.render(field, key, value, REQUEST) + + def render(self, field, key, value, REQUEST, render_prefix=None): + """ + """ + form = getattr(field, 'aq_parent', None) + if form is not None: + obj = getattr(form, 'aq_parent', None) + else: + obj = None + if obj is not None: + report_method = guarded_getattr(obj, field['report_method']) + if callable(report_method): + return report_method() + + +class ReportBoxValidator(Validator.Validator): + + def validate(self, field, key, REQUEST): + return True + + +class ReportBox(ZMIField): + meta_type = "ReportBox" + + widget = ReportBoxWidget() + validator = ReportBoxValidator() diff --git a/product/ERP5Form/SelectionTool.py b/product/ERP5Form/SelectionTool.py index a33af7eac296cabb224bdbce1d2e2434a99528ec..a70b4a0122f399cb8ba4a4a540d3807c7ce61a36 100644 --- a/product/ERP5Form/SelectionTool.py +++ b/product/ERP5Form/SelectionTool.py @@ -37,6 +37,7 @@ from Globals import InitializeClass, DTMLFile, PersistentMapping, get_request from AccessControl import ClassSecurityInfo from Products.ERP5Type.Tool.BaseTool import BaseTool from Products.ERP5Type import Permissions as ERP5Permissions +from Products.ERP5Type.TransactionalVariable import getTransactionalVariable from Products.ERP5Form import _dtmldir from Selection import Selection, DomainSelection from ZPublisher.HTTPRequest import FileUpload @@ -1320,9 +1321,38 @@ class SelectionTool( BaseTool, UniqueObject, SimpleItem ): # XXX It would be good to add somthing here # So that 2 anonymous users do not share the same selection + def getTemporarySelectionDict(self): + """ Temporary selections are used in push/pop nested scope, + to prevent from editting for stored selection in the scope. + Typically, it is used for ReportSection.""" + tv = getTransactionalVariable(self) + return tv.setdefault('_temporary_selection_dict', {}) + + def pushSelection(self, selection_name): + selection = self.getSelectionFor(selection_name) + # a temporary selection is kept in transaction. + temp_selection = Selection() + if selection: + temp_selection.__dict__.update(selection.__dict__) + self.getTemporarySelectionDict()\ + .setdefault(selection_name, []).append(temp_selection) + + def popSelection(self, selection_name): + temporary_selection_dict = self.getTemporarySelectionDict() + if selection_name in temporary_selection_dict and \ + temporary_selection_dict[selection_name]: + temporary_selection_dict[selection_name].pop() + def _getSelectionFromContainer(self, selection_name): user_id = self._getUserId() if user_id is None: return None + + temporary_selection_dict = self.getTemporarySelectionDict() + if temporary_selection_dict and selection_name in temporary_selection_dict: + if temporary_selection_dict[selection_name]: + # focus the temporary selection in the most narrow scope. + return temporary_selection_dict[selection_name][-1] + if self.isMemcachedUsed(): return self._getMemcachedContainer().get('%s-%s' % (user_id, selection_name)) @@ -1333,6 +1363,14 @@ class SelectionTool( BaseTool, UniqueObject, SimpleItem ): def _setSelectionToContainer(self, selection_name, selection): user_id = self._getUserId() if user_id is None: return + + temporary_selection_dict = self.getTemporarySelectionDict() + if temporary_selection_dict and selection_name in temporary_selection_dict: + if temporary_selection_dict[selection_name]: + # focus the temporary selection in the most narrow scope. + temporary_selection_dict[selection_name][-1] = selection + return + if self.isMemcachedUsed(): self._getMemcachedContainer().set('%s-%s' % (user_id, selection_name), aq_base(selection)) else: @@ -1360,7 +1398,9 @@ class SelectionTool( BaseTool, UniqueObject, SimpleItem ): else: user_id = self._getUserId() if user_id is None: return [] - return self._getPersistentContainer(user_id).keys() + + tv = getTransactionalVariable(self) + return list(set(self._getPersistentContainer(user_id).keys() + self.getTemporarySelectionDict().keys())) def _getMemcachedContainer(self): value = getattr(aq_base(self), '_v_selection_data', None) diff --git a/product/ERP5Form/__init__.py b/product/ERP5Form/__init__.py index 2d946fd631c6157e65c97934f84dccc44d475ac5..e71145a0c90af032ea7708d9d1b1c549db0e3041 100644 --- a/product/ERP5Form/__init__.py +++ b/product/ERP5Form/__init__.py @@ -40,7 +40,7 @@ document_classes = updateGlobals( this_module, globals(), permissions_module = Permissions) # Define object classes and tools -import Form, FSForm, ListBox, MatrixBox, SelectionTool +import Form, FSForm, ListBox, ReportBox, MatrixBox, SelectionTool import OOoChart, PDFTemplate, Report, PDFForm, ParallelListField import PlanningBox, POSBox, FormBox, EditorField, ProxyField, DurationField import RelationField, ImageField, MultiRelationField, MultiLinkField, InputButtonField @@ -86,6 +86,8 @@ def initialize( context ): 'www/StringField.gif') FieldRegistry.registerField(ListBox.ListBox, 'www/StringField.gif') + FieldRegistry.registerField(ReportBox.ReportBox, + 'www/StringField.gif') FieldRegistry.registerField(PlanningBox.PlanningBox, 'www/StringField.gif') FieldRegistry.registerField(MatrixBox.MatrixBox, diff --git a/product/Formulator/Field.py b/product/Formulator/Field.py index abc410ff11a0dadf174ad175198ec48983d675fa..a8c5aa174f1df8b650fe5b47663184e4c77a96c5 100644 --- a/product/Formulator/Field.py +++ b/product/Formulator/Field.py @@ -625,6 +625,10 @@ class ZMIField( return 1 except ImportError: return 0 + + def getTemplateField(self): + return self + getRecursiveTemplateField = getTemplateField Globals.InitializeClass(ZMIField) PythonField = ZMIField # NOTE: for backwards compatibility