diff --git a/product/ERP5Form/Form.py b/product/ERP5Form/Form.py index 413a77de795801aa5e6f8907ab469fc6b37eb09f..83bc81f505162e9eb3b3487dc5cab2d204efe97c 100755 --- a/product/ERP5Form/Form.py +++ b/product/ERP5Form/Form.py @@ -38,7 +38,6 @@ from urllib import quote from Globals import InitializeClass, PersistentMapping, DTMLFile, get_request from AccessControl import Unauthorized, getSecurityManager, ClassSecurityInfo from ZODB.POSException import ConflictError - from Products.ERP5Type.Utils import UpperCase import psyco @@ -111,6 +110,7 @@ def get_value(self, id, **kw): else: # get normal value value = self.get_orig_value(id) + # For the 'default' value, we try to get a default value if id == 'default': if (value is None or value == '' or value == [] or value == ()) \ @@ -355,7 +355,6 @@ class ERP5Form(ZMIForm, ZopePageTemplate): # Proxy method to PageTemplate def __call__(self, *args, **kwargs): - self._v_relation_field_index = 0 # We initialize here an index which is used to generate different method ids for every field if not kwargs.has_key('args'): kwargs['args'] = args form = self @@ -370,6 +369,11 @@ class ERP5Form(ZMIForm, ZopePageTemplate): extra_context['form'] = self extra_context['container'] = container ## PROBLEM NOT TAKEN INTO ACCOUNT extra_context['here'] = object + # We initialize here an index which is used to generate + # different method ids for every field + request = extra_context['request'] + # XXX We must not use a counter, but a ID for each field + request.set('_v_relation_field_index', 0) return pt.pt_render(extra_context=extra_context) def _exec(self, bound_names, args, kw): @@ -456,6 +460,7 @@ class ERP5Form(ZMIForm, ZopePageTemplate): # More optimizations #psyco.bind(ERP5Field) +# XXX Not useful, as we patch those methods in FormulatorPatch psyco.bind(Field.render) psyco.bind(Field._render_helper) psyco.bind(Field.get_value) diff --git a/product/ERP5Form/MultiRelationField.py b/product/ERP5Form/MultiRelationField.py index c8286224f6a35f7bc193c10b6649ec0800a9f9a7..c4d2bcbdc0232eed2d58cdf4b6869edac9b368b2 100755 --- a/product/ERP5Form/MultiRelationField.py +++ b/product/ERP5Form/MultiRelationField.py @@ -1,6 +1,7 @@ ############################################################################## # -# Copyright (c) 2002, 2004 Nexedi SARL and Contributors. All Rights Reserved. +# Copyright (c) 2002, 2004, 2006 Nexedi SARL and Contributors. +# All Rights Reserved. # Jean-Paul Smets-Solanes <jp@nexedi.com> # Romain Courteaud <romain@nexedi.com> # @@ -29,19 +30,22 @@ from Products.Formulator import Widget, Validator from Products.Formulator.Field import ZMIField -from Products.Formulator.DummyField import fields from Products.ERP5Type.Utils import convertToUpperCase from Products.CMFCore.utils import getToolByName -from Products.ERP5Form import RelationField -from Products.ERP5Form.RelationField import MAX_SELECT, new_content_prefix -from Globals import get_request from Products.PythonScripts.Utility import allow_class +from Products.ERP5Type.Message import Message from AccessControl import ClassSecurityInfo - -import string +from types import StringType from zLOG import LOG -#MAX_SELECT = 50 # Max. number of catalog result -#new_content_prefix = '_newContent_' +from Products.Formulator.DummyField import fields +from Globals import get_request + +# Max. number of catalog result +MAX_SELECT = 30 +NEW_CONTENT_PREFIX = '_newContent_' +# Key for sub listfield +SUB_FIELD_ID = 'relation' +ITEM_ID = 'item' def checkSameKeys(a , b): """ @@ -52,199 +56,352 @@ def checkSameKeys(a , b): for ka in a: if (not ka in b) and (ka != ''): same = 0 - for kb in b: - if (not kb in a) and (kb != ''): - same = 0 + break + if same: + for kb in b: + if (not kb in a) and (kb != ''): + same = 0 + break return same - -class MultiRelationStringFieldWidget(Widget.LinesTextAreaWidget, RelationField.RelationStringFieldWidget): +class MultiRelationStringFieldWidget(Widget.LinesTextAreaWidget, + Widget.TextWidget, + Widget.ListWidget): + """ + RelationStringField widget + Works like a string field but includes one buttons + - one search button which updates the field and sets a relation + - creates object if not there + """ + local_property_names = ['update_method', 'jump_method', 'allow_jump', + 'base_category', 'portal_type', 'allow_creation', + 'container_getter_id', 'catalog_index', + 'relation_setter_id', 'columns', 'sort', + 'parameter_list','list_method', + 'first_item', 'items', 'size', 'extra_item'] + + property_names = Widget.LinesTextAreaWidget.property_names + \ + Widget.TextWidget.property_names + \ + local_property_names + + # XXX Field to remove... + update_method = fields.StringField('update_method', + title='Update Method', + description=( + "The method to call to set the relation. Required."), + default="Base_validateRelation", + required=1) + + jump_method = fields.StringField('jump_method', + title='Jump Method', + description=( + "The method to call to jump to the relation. Required."), + default="Base_jumpToRelatedDocument", + required=1) + + allow_jump = fields.CheckBoxField('allow_jump', + title='Allow Jump', + description=( + "Do we allow to jump to the relation ?"), + default=1, + required=0) + + base_category = fields.StringField('base_category', + title='Base Category', + description=( + "The method to call to set the relation. Required."), + default="", + required=1) + + portal_type = fields.ListTextAreaField('portal_type', + title='Portal Type', + description=( + "The method to call to set the relation. Required."), + default="", + required=1) + + allow_creation = fields.CheckBoxField('allow_creation', + title='Allow Creation', + description=( + "Do we allow to create new objects ?"), + default=1, + required=0) + + container_getter_id = fields.StringField('container_getter_id', + title='Container Getter Method', + description=( + "The method to call to get a container object."), + default="", + required=0) + + catalog_index = fields.StringField('catalog_index', + title='Catalog Index', + description=( + "The method to call to set the relation. Required."), + default="", + required=1) + + # XXX Is it a good idea to keep such a field ?? + # User can redefine setter method with a script (and so, don't use the API) + relation_setter_id = fields.StringField('relation_setter_id', + title='Relation Update Method', + description=( + "The method to invoke in order to update the relation"), + default="", + required=0) + + size = fields.IntegerField('size', + title='Size', + description=( + "The display size in rows of the field. If set to 1, the " + "widget will be displayed as a drop down box by many browsers, " + "if set to something higher, a list will be shown. Required."), + default=1, + required=1) + + columns = fields.ListTextAreaField('columns', + title="Columns", + description=( + "A list of attributes names to display."), + default=[], + required=0) + + sort = fields.ListTextAreaField('sort', + title='Default Sort', + description=('The default sort keys and order'), + default=[], + required=0) + + parameter_list = fields.ListTextAreaField('parameter_list', + title="Parameter List", + description=( + "A list of paramters used for the portal_catalog."), + default=[], + required=0) + + list_method = fields.MethodField('list_method', + title='List Method', + description=('The method to use to list' + 'objects'), + default='', + required=0) + + # delete double in order to keep a usable ZMI... + # XXX need to keep order ! + #property_names = dict([(i,0) for i in property_names]).keys() + _v_dict = {} + _v_property_name_list = [] + for property_name in property_names: + if not _v_dict.has_key(property_name): + _v_property_name_list.append(property_name) + _v_dict[property_name] = 1 + property_names = _v_property_name_list + + default_widget_rendering_instance = Widget.LinesTextAreaWidgetInstance + + def _generateRenderValueList(self, field, key, value_list, REQUEST): + result_list = [] + need_validation = 0 + #################################### + # Check value + #################################### + if isinstance(value_list, StringType): + # Value is a string, reformat it correctly + value_list = value_list.split("\n") + # Check all relation + for i in range(len(value_list)): + ################################### + # Sub field + ################################### + relation_field_id = field.generate_subfield_key("%s_%s" % \ + (SUB_FIELD_ID, i), + key=key) + relation_item_id = field.generate_subfield_key("%s_%s" % \ + (ITEM_ID, i), + key=key) + relation_item_list = REQUEST.get(relation_item_id, None) + value = value_list[i] + if (relation_item_list is not None) and \ + (value != ''): + need_validation = 1 + if value is None : + # rather than displaying nothing, display a marker when the + # property is not set + # XXX Translate ? + value = '??? (no value)' + # If we get a empty string, display nothing ! + if value != '': + result_list.append((Widget.TextWidgetInstance, relation_field_id, + relation_item_list, value, i)) + if not need_validation: + ################################### + # Main field + ################################### + result_list = [(Widget.LinesTextAreaWidgetInstance, None, [], + value_list, None)] + return result_list + + def render(self, field, key, value, REQUEST): """ - RelationStringField widget - - Works like a string field but includes one buttons - - - one search button which updates the field and sets a relation - - - creates object if not there - + Render text input field. """ - property_names = Widget.LinesTextAreaWidget.property_names + \ - RelationField.RelationStringFieldWidget.property_names - - # delete double in order to keep a usable ZMI... - #property_names = dict([(i,0) for i in property_names]).keys() # XXX need to keep order ! - _v_dict = {} - _v_property_name_list = [] - for property_name in property_names: - if not _v_dict.has_key(property_name): - _v_property_name_list.append(property_name) - _v_dict[property_name] = 1 - property_names = _v_property_name_list - - - def render(self, field, key, value, REQUEST): - """ - Render text input field. - """ - here = REQUEST['here'] - - relation_field_id = 'relation_%s' % key - relation_item_id = 'item_%s' % key - - portal_url = getToolByName(here, 'portal_url') - portal_url_string = portal_url() - portal_object = portal_url.getPortalObject() - - if type(value) == type(''): - # Value is a string, reformat it correctly - value_list = string.split(value, "\r\n") - else: - value_list = value - - need_validation = 0 - # Check all relation - for i in range( len(value_list) ): - relation_field_id = 'relation_%s_%s' % ( key, i ) - relation_item_id = 'item_%s_%s' % ( key, i ) - if REQUEST.has_key(relation_item_id) and value_list[i] != '': - need_validation = 1 - break - - html_string = '' - if need_validation: - # Check all relation - for i in range( len(value_list) ): - value = value_list[i] - relation_field_id = 'relation_%s_%s' % ( key, i ) - relation_item_id = 'item_%s_%s' % ( key, i ) - - - # If we get a empty string, display nothing ! - if value == '': - pass - - else: - - html_string += Widget.TextWidget.render(self, field, key, value, REQUEST) - - if REQUEST.has_key(relation_item_id): - relation_item_list = REQUEST.get(relation_item_id) - - if relation_item_list != []: - # Define default tales on the fly - tales_expr = field.tales.get('items', None) - defined_tales = 0 - if not tales_expr: - defined_tales = 1 - from Products.Formulator.TALESField import TALESMethod - field.tales['items'] = TALESMethod('REQUEST/relation_item_list') - - - REQUEST['relation_item_list'] = relation_item_list - html_string += ' %s ' % Widget.ListWidget.render(self, - field, relation_field_id, None, REQUEST) - REQUEST['relation_item_list'] = None - - if defined_tales: - # Delete default tales on the fly - field.tales['items'] = None - - else: - html_string += ' <input type="image" src="%s/images/exec16.png" value="update..." name="%s/portal_selections/viewSearchRelatedDocumentDialog%s_%s:method"/>' \ - % (portal_url_string, portal_object.getPath(), field.aq_parent._v_relation_field_index, i) - - html_string += '<br/>' - + html_string = '' + relation_field_index = REQUEST.get('_v_relation_field_index') + render_parameter_list = self._generateRenderValueList( + field, key, value, + REQUEST) + #################################### + # Render subfield + #################################### + html_string_list = [] + for widget_instance, relation_field_id, relation_item_list, \ + value_instance, sub_index in render_parameter_list: + sub_html_string = widget_instance.render(field, key, + value_instance, REQUEST) + if relation_item_list is not None: + if relation_item_list != []: + #################################### + # Render listfield + #################################### + tales_expr = field.tales.get('items', None) + defined_tales = 0 + if not tales_expr: + defined_tales = 1 + from Products.Formulator.TALESField import TALESMethod + # XXX XXX Do not write in the ZODB + field.tales['items'] = TALESMethod('REQUEST/relation_item_list') + + REQUEST['relation_item_list'] = relation_item_list + sub_html_string += ' %s ' % \ + Widget.ListWidgetInstance.render( + field, relation_field_id, None, REQUEST) + REQUEST['relation_item_list'] = None + if defined_tales: + # Delete default tales on the fly + field.tales['items'] = None else: - clean_value_list = [] - for v in value_list : - # rather than displaying nothing, display a marker when the - # property is not set - if v is None : v = '??? (no value)' - clean_value_list += [v] - # no modification made, we can display only a lines text area widget - html_string += Widget.LinesTextAreaWidget.render(self, field, key, clean_value_list, REQUEST) - - html_string += ' <input type="image" src="%s/images/exec16.png" value="update..." name="%s/portal_selections/viewSearchRelatedDocumentDialog%s:method"/>' \ - % (portal_url_string, portal_object.getPath(), field.aq_parent._v_relation_field_index) - - if value_list not in ((), [], None, ['']) and value_list == field.get_value('default') and field.get_value('allow_jump') == 1 : - if REQUEST.get('selection_name') is not None: - html_string += ' <a href="%s?field_id=%s&form_id=%s&selection_name=%s&selection_index=%s"><img src="%s/images/jump.png"/></a>' \ - % (field.get_value('jump_method'), field.id, field.aq_parent.id, REQUEST.get('selection_name'), REQUEST.get('selection_index'),portal_url_string) - else: - html_string += ' <a href="%s?field_id=%s&form_id=%s"><img src="%s/images/jump.png"/></a>' \ - % (field.get_value('jump_method'), field.id, field.aq_parent.id,portal_url_string) - - relation_field_index = getattr(field.aq_parent, '_v_relation_field_index', 0) - field.aq_parent._v_relation_field_index = relation_field_index + 1 # Increase index - return html_string - - def render_view(self, field, value): - """ - Render text field. - """ - if field.get_value('allow_jump') == 0 : - return Widget.LinesTextAreaWidget.render_view(self, field, value) - - REQUEST = get_request() - here = REQUEST['here'] - - portal_url = getToolByName(here, 'portal_url') - portal_url_string = portal_url() - - # no modification made, we can display only a lines text area widget - html_string = Widget.LinesTextAreaWidget.render_view(self, field, value) - if value not in ((), [], None, ''): - if REQUEST.get('selection_name') is not None: - html_string += ' <a href="%s?field_id=%s&form_id=%s&selection_name=%s&selection_index=%s"><img src="%s/images/jump.png"/></a>' \ - % (field.get_value('jump_method'), field.id, field.aq_parent.id, REQUEST.get('selection_name'), REQUEST.get('selection_index'),portal_url_string) - else: - html_string += ' <a href="%s?field_id=%s&form_id=%s"><img src="%s/images/jump.png"/></a>' \ - % (field.get_value('jump_method'), field.id, field.aq_parent.id,portal_url_string) - - return html_string + #################################### + # Render wheel + #################################### + sub_html_string += self.render_wheel( + field, value_instance, REQUEST, + relation_index=relation_field_index, + sub_index=sub_index) + html_string_list.append(sub_html_string) + #################################### + # Generate html + #################################### + html_string = '<br/>'.join(html_string_list) + #################################### + # Render jump + #################################### + if (value == field.get_value('default')): + # XXX Default rendering with value... + relation_html_string = self.render_relation_link(field, value, + REQUEST) + if relation_html_string != '': + html_string += ' %s' % relation_html_string + #################################### + # Update relation field index + #################################### + REQUEST.set('_v_relation_field_index', relation_field_index + 1) + return html_string + + def render_view(self, field, value): + """ + Render read only field. + """ + html_string = self.default_widget_rendering_instance.render_view( + field, value) + REQUEST = get_request() + relation_html_string = self.render_relation_link(field, value, REQUEST) + if relation_html_string != '': + html_string += ' %s' % relation_html_string + return html_string + + def render_wheel(self, field, value, REQUEST, relation_index=0, + sub_index=None): + """ + Render wheel used to display a listbox + """ + here = REQUEST['here'] + portal_url = getToolByName(here, 'portal_url') + portal_url_string = portal_url() + portal_object = portal_url.getPortalObject() + if sub_index is None: + sub_index_string = '' + else: + sub_index_string = '_%s' % sub_index + return ' <input type="image" ' \ + 'src="%s/images/exec16.png" value="update..." ' \ + 'name="%s/portal_selections/viewSearchRelatedDocumentDialog%s%s' \ + ':method"/>' % \ + (portal_url_string, portal_object.getPath(), + relation_index, sub_index_string) + + def render_relation_link(self, field, value, REQUEST): + """ + Render link to the related object. + """ + html_string = '' + here = REQUEST['here'] + portal_url = getToolByName(here, 'portal_url') + portal_url_string = portal_url() + portal_object = portal_url.getPortalObject() + if (value not in ((), [], None, '')) and \ + (field.get_value('allow_jump') == 1): + # Keep the selection name in the URL + if REQUEST.get('selection_name') is not None: + selection_name_html = '&selection_name=%s&selection_index=%s' % \ + (REQUEST.get('selection_name'), REQUEST.get('selection_index')) + else: + selection_name_html = '' + # Generate plan link + html_string += '<a href="%s/%s?field_id=%s&form_id=%s%s">' \ + '<img src="%s/images/jump.png" />' \ + '</a>' % \ + (here.absolute_url(), + field.get_value('jump_method'), + field.id, field.aq_parent.id, + selection_name_html, + portal_url_string) + return html_string class MultiRelationEditor: """ A class holding all values required to update a relation """ - def __init__(self, field_id, base_category, portal_type, portal_type_item, key, relation_setter_id, relation_editor_list): - - + def __init__(self, field_id, base_category, + portal_type_list, + portal_type_item, key, relation_setter_id, + relation_editor_list): self.field_id = field_id self.base_category = base_category - self.portal_type = portal_type + self.portal_type_list = portal_type_list self.portal_type_item = portal_type_item self.key = key self.relation_setter_id = relation_setter_id self.relation_editor_list = relation_editor_list - def __call__(self, REQUEST): if self.relation_editor_list != None: value_list = [] - - for i, value, uid, display_text in self.relation_editor_list: + for value, uid, display_text, relation_key, item_key in \ + self.relation_editor_list: value_list.append(value) if uid is not None: # Decorate the request so that we can display # the select item in a popup - #relation_field_id = 'relation_%s_%s' % ( self.key, i ) - #relation_item_id = 'item_%s_%s' % ( self.key, i ) - relation_field_id = 'relation_field_%s_%s' % ( self.field_id, i ) - relation_item_id = 'item_field_%s_%s' % ( self.field_id, i ) - - + # XXX To be unified + relation_field_id = relation_key + relation_item_id = item_key REQUEST.set(relation_item_id, ((display_text, uid),)) + # XXX Is it useful ? REQUEST.set(relation_field_id, uid) - REQUEST.set(self.field_id, value_list) # XXX Dirty else: # Make sure no default value appears - #REQUEST.set(self.field_id[len('field_'):], None) REQUEST.set(self.field_id, None) # XXX Dirty def view(self): @@ -255,276 +412,367 @@ class MultiRelationEditor: relation_uid_list = [] relation_object_list = [] - - for i, value, uid, display_text in self.relation_editor_list: + for value, uid, display_text, relation_key, item_key in \ + self.relation_editor_list: if uid is not None: - if type(uid) is type('a') and uid.startswith(new_content_prefix): + if isinstance(uid, StringType) and \ + uid.startswith(NEW_CONTENT_PREFIX): # Create a new content - portal_type = uid[len(new_content_prefix):] + portal_type = uid[len(NEW_CONTENT_PREFIX):] portal_module = None for p_item in self.portal_type_item: if p_item[0] == portal_type: - portal_module = o.getPortalObject().getDefaultModuleId( p_item[0] ) + portal_module = o.getPortalObject().getDefaultModuleId( + p_item[0]) if portal_module is not None: - portal_module_object = getattr(o.getPortalObject(), portal_module) + portal_module_object = getattr(o.getPortalObject(), + portal_module) kw ={} - #kw[self.key] = value - kw[self.key] = string.join( string.split(value,'%'), '' ) + kw[self.key] = value.replace('%', '') kw['portal_type'] = portal_type kw['immediate_reindex'] = 1 new_object = portal_module_object.newContent(**kw) uid = new_object.getUid() else: raise - relation_uid_list.append(int(uid)) - - relation_object_list.append( o.portal_catalog.getObject(uid) ) - - #if relation_uid_list != []: + + relation_uid_list.append(int(uid)) + relation_object_list.append( o.portal_catalog.getObject(uid)) # Edit relation if self.relation_setter_id: relation_setter = getattr(o, self.relation_setter_id) - relation_setter((), portal_type=self.portal_type) - relation_setter( relation_uid_list , portal_type=self.portal_type) + relation_setter((), portal_type=self.portal_type_list) + relation_setter(relation_uid_list, + portal_type=self.portal_type_list) else: # we could call a generic method which create the setter method name - set_method_name = '_set'+convertToUpperCase(self.base_category)+'ValueList' - getattr(o, set_method_name)( relation_object_list , portal_type=self.portal_type) - - else: - # Nothing to do - pass -# # Delete relation -# if self.relation_setter_id: -# relation_setter = getattr(o, self.relation_setter_id) -# relation_setter((), portal_type=self.portal_type) -# else: -# o._setValueUids(self.base_category, (), portal_type=self.portal_type) + set_method_name = '_set%sValueList' % \ + convertToUpperCase(self.base_category) + getattr(o, set_method_name)(relation_object_list, + portal_type=self.portal_type_list) allow_class(MultiRelationEditor) +class MultiRelationStringFieldValidator(Validator.LinesValidator): + """ + Validation includes lookup of relared instances + """ + message_names = Validator.LinesValidator.message_names +\ + ['relation_result_too_long', 'relation_result_ambiguous', + 'relation_result_empty',] -class MultiRelationStringFieldValidator(Validator.LinesValidator, RelationField.RelationStringFieldValidator): - """ - Validation includes lookup of relared instances - """ - message_names = Validator.LinesValidator.message_names + \ - RelationField.RelationStringFieldValidator.message_names - - # delete double in order to keep a usable ZMI... - #message_names = dict([(i,0) for i in message_names]).keys() # XXX need to keep order ! - _v_dict = {} - _v_message_name_list = [] - for message_name in message_names: - if not _v_dict.has_key(message_name): - _v_message_name_list.append(message_name) - _v_dict[message_name] = 1 - message_names = _v_message_name_list - - def validate(self, field, key, REQUEST): - portal_type = map(lambda x:x[0],field.get_value('portal_type')) - portal_type_item = field.get_value('portal_type') - base_category = field.get_value( 'base_category') - - # If the value is different, build a query - portal_selections = getToolByName(field, 'portal_selections') - portal_catalog = getToolByName(field, 'portal_catalog') - - # Get the current value - value_list = Validator.LinesValidator.validate(self, field, key, REQUEST) + # XXX Do we need to translate here ? + relation_result_too_long = "Too many documents were found." + relation_result_ambiguous = "Select appropriate document in the list." + relation_result_empty = "No such document was found." -# if type(value_list) == type(''): -# value_list = [value_list] + # Relation field variable + editor = MultiRelationEditor + default_validator_instance = Validator.LinesValidatorInstance + def _generateItemUidList(self, field, key, relation_uid_list, REQUEST=None): + """ + Generate tuple... + """ + result_list = [] + for i in range(len(relation_uid_list)): + # Generate a Item id for each value. + relation_item_id = field.generate_subfield_key("%s_%s" % \ + (ITEM_ID, i), + key=key) + relation_uid = relation_uid_list[i] + result_list.append((relation_item_id, relation_uid, None)) + return result_list + + def _generateFieldValueList(self, field, key, + value_list, current_value_list): + """ + Generate list of value, item_key + """ + item_value_list = [] + if isinstance(current_value_list, StringType): + current_value_list = [current_value_list] + # Check value list + if not (checkSameKeys(value_list, current_value_list)): + for i in range(len(value_list)): + value = value_list[i] + relation_field_id = field.generate_subfield_key("%s_%s" % \ + (SUB_FIELD_ID, i), + key=key) + relation_item_id = field.generate_subfield_key("%s_%s" % \ + (ITEM_ID, i), + key=key) + item_value_list.append((relation_field_id, value, relation_item_id)) + # Make possible to delete the content of the field. + if item_value_list == []: + relation_field_id = field.generate_subfield_key("%s" % \ + SUB_FIELD_ID, key=key) + relation_item_key = field.generate_subfield_key(ITEM_ID, key=key) + item_value_list.append((relation_field_id, '', relation_item_key)) + return item_value_list + + def validate(self, field, key, REQUEST): + """ + Validate the field. + """ + raising_error_needed = 0 + relation_editor_list = None + # Get some tool + catalog_index = field.get_value('catalog_index') + portal_type_list = [x[0] for x in field.get_value('portal_type')] + portal_catalog = getToolByName(field, 'portal_catalog') + #################################### + # Check list input + #################################### + relation_field_id = field.generate_subfield_key("%s" % \ + SUB_FIELD_ID, key=key) + relation_uid_list = REQUEST.get(relation_field_id, None) + #################################### + # User clicked on the wheel + #################################### + need_to_revalidate = 1 + if relation_uid_list is not None: + need_to_revalidate = 0 + relation_editor_list = [] + for relation_item_id, relation_uid, value in \ + self._generateItemUidList(field, key, relation_uid_list, + REQUEST=REQUEST): + found = 0 + try: + related_object = portal_catalog.getObject(relation_uid) + display_text = str(related_object.getProperty(catalog_index)) + found = 1 + except ValueError: + # Catch the error raised when the uid is a string + if relation_uid.startswith(NEW_CONTENT_PREFIX): + ############################## + # New content was selected, but the + # form is not validated + ############################## + portal_type = relation_uid[len(NEW_CONTENT_PREFIX):] + translated_portal_type = Message(domain='erp5_ui', + message=portal_type) + message = Message( + domain='erp5_ui', message='New ${portal_type}', + mapping={'portal_type': translated_portal_type}) + display_text = message + else: + display_text = 'Object has been deleted' + ################################ + # Modify if user modified his value + ################################ + if (found == 1) and \ + (value != display_text): + relation_editor_list = None +# import pdb; pdb.set_trace() + need_to_revalidate = 1 + REQUEST.set(relation_field_id, None) + break + if value is None: + value = display_text + # Storing display_text as value is needed in this case + relation_editor_list.append((value, + relation_uid, display_text, + None, relation_item_id)) +# str(relation_uid), display_text, + #################################### + # User validate the form + #################################### + if need_to_revalidate == 1: +# else: + #################################### + # Check the default field + #################################### + value_list = self.default_validator_instance.validate(field, + key, REQUEST) # If the value is the same as the current field value, do nothing current_value_list = field.get_value('default') - if type(current_value_list) == type(''): - current_value_list = [current_value_list] - - catalog_index = field.get_value('catalog_index') - relation_setter_id = field.get_value('relation_setter_id') - - relation_field_id = 'relation_%s' % ( key ) - # we must know if user validate the form or click on the wheel button - relation_uid_list = REQUEST.get(relation_field_id, None) - relation_field_sub_id = 'relation_%s_0' % ( key ) - if checkSameKeys( value_list, current_value_list ) and (relation_uid_list is None) and (not REQUEST.has_key( relation_field_sub_id )): - # Will be interpreted by Editor as "do nothing" - return MultiRelationEditor(field.id, base_category, - portal_type, portal_type_item, - catalog_index, relation_setter_id, None) - else: - - relation_field_id = 'relation_%s' % ( key ) - - # We must be able to erase the relation - if (value_list == ['']) and (not REQUEST.has_key( relation_field_id )): - display_text = 'Delete the relation' - return MultiRelationEditor(field.id, base_category, portal_type, portal_type_item, catalog_index, relation_setter_id, []) -# return RelationEditor(key, base_category, portal_type, None, -# portal_type_item, catalog_index, value, relation_setter_id, display_text) - # Will be interpreted by Base_edit as "delete relation" (with no uid and value = '') - - if REQUEST.has_key( relation_field_id ): - # we must know if user validate the form or click on the wheel button - relation_uid_list = REQUEST.get(relation_field_id, None) - if relation_uid_list != None: - relation_editor_list = [] - for i in range( len(relation_uid_list) ): - - relation_item_id = 'item_%s_%s' % ( key, i ) - relation_uid = relation_uid_list[i] - - related_object = portal_catalog.getObject(relation_uid) + field_value_list = self._generateFieldValueList(field, key, value_list, + current_value_list) + if len(field_value_list) != 0: + #################################### + # Values were changed + #################################### + relation_editor_list = [] + for relation_field_id, value, relation_item_id in field_value_list: + if value == '': + #################################### + # User want to delete this line + #################################### + # Clean request if necessary + if REQUEST.has_key(relation_field_id): + for subdict_name in ['form', 'other']: + subdict = getattr(REQUEST, subdict_name) + if subdict.has_key(relation_field_id): + subdict.pop(relation_field_id) + display_text = 'Delete the relation' + relation_editor_list.append((value, None, + display_text, None, None)) + # XXX RelationField implementation +# # We must be able to erase the relation +# display_text = 'Delete the relation' +# # Will be interpreted by Base_edit as "delete relation" +# # (with no uid and value = '') +# relation_editor_list = [(value, None, +# display_text, None, None)] + else: + relation_uid = REQUEST.get(relation_field_id, None) +# need_to_revalidate = 1 + if relation_uid not in (None, ''): +# need_to_revalidate = 0 +# found = 0 + #################################### + # User selected in a popup menu + #################################### + if isinstance(relation_uid, (list, tuple)): + relation_uid = relation_uid[0] + try: + related_object = portal_catalog.getObject(relation_uid) + except ValueError: + # Catch the exception raised when the uid is a string + related_object = None if related_object is not None: display_text = str(related_object.getProperty(catalog_index)) +# found = 1 else: - display_text = 'Object has been deleted' - # Check - REQUEST.set(relation_item_id, ( (display_text, relation_uid), )) - # Storing display_text as value is needded in this case - relation_editor_list.append( (i, display_text, str(relation_uid), display_text) ) - - return MultiRelationEditor(field.id, base_category, portal_type, portal_type_item, catalog_index, relation_setter_id, relation_editor_list) - - - else: - # User validate the form - - relation_editor_list = [] - raising_error_needed = 0 - raising_error_value = '' - - # Check all relation - for i in range( len(value_list) ): - relation_field_id = 'relation_%s_%s' % ( key, i ) - relation_item_id = 'item_%s_%s' % ( key, i ) - - relation_uid = REQUEST.get(relation_field_id, None) - - value = value_list[i] - - - # If we get a empty string, delete this line - if value == '': - # Clean request if necessary - if REQUEST.has_key( relation_field_id): - for subdict_name in ['form', 'other']: - subdict = getattr(REQUEST, subdict_name) - if subdict.has_key(relation_field_id): - subdict.pop(relation_field_id) + ############################## + # New content was selected, but the + # form is not validated + ############################## + if relation_uid.startswith(NEW_CONTENT_PREFIX): + ############################## + # New content was selected, but the + # form is not validated + ############################## + portal_type = relation_uid[len(NEW_CONTENT_PREFIX):] + translated_portal_type = Message(domain='erp5_ui', + message=portal_type) + message = Message( + domain='erp5_ui', message='New ${portal_type}', + mapping={'portal_type': translated_portal_type}) + display_text = message + else: + display_text = 'Object has been deleted' +# ################################ +# # Modify if user modified his value +# ################################ +# if (found == 1) and \ +# (value != display_text): +# REQUEST.set(relation_field_id, None) +# need_to_revalidate = 1 +# else: +# # Check +# REQUEST.set(relation_item_id, ((display_text, relation_uid),)) +# relation_editor_list.append((value, str(relation_uid), +# display_text, relation_field_id, +# relation_item_id)) + REQUEST.set(relation_item_id, ((display_text, relation_uid),)) + relation_editor_list.append((value, str(relation_uid), + display_text, relation_field_id, + relation_item_id)) +# if need_to_revalidate == 1: else: - # Got a true value - - if relation_uid not in (None, ''): - # A value has been defined by the user in popup menu - if type(relation_uid) in (type([]), type(())): relation_uid = relation_uid[0] - try: - related_object = portal_catalog.getObject(relation_uid) - except ValueError: - # Catch the exception raised when the uid is a string - related_object = None + #################################### + # User validate the form for this line + #################################### + kw ={} + kw[catalog_index] = value + kw['portal_type'] = portal_type_list + kw['sort_on'] = catalog_index + # Get the query results + relation_list = portal_catalog(**kw) + relation_uid_list = [x.uid for x in relation_list] + menu_item_list = [] + if len(relation_list) >= MAX_SELECT: + # If the length is long, raise an error + # This parameter means we need listbox help + # XXX XXX XXX Do we need to delete it ? + REQUEST.set(relation_item_id, []) + raising_error_needed = 1 + raising_error_value = 'relation_result_too_long' + elif len(relation_list) == 1: + # If the length is 1, return uid + relation_uid = relation_uid_list[0] + related_object = portal_catalog.getObject(relation_uid) if related_object is not None: display_text = str(related_object.getProperty(catalog_index)) + # Modify the value, in order to let the user + # modify it later... + value = display_text else: display_text = 'Object has been deleted' - # Check - REQUEST.set(relation_item_id, ( (display_text, relation_uid), )) - relation_editor_list.append( (i, value, str(relation_uid), display_text) ) - + # XXX XXX XXX + REQUEST.set(relation_item_id, ((display_text, + relation_uid),)) + relation_editor_list.append((value, relation_uid, + display_text, None, + relation_item_id)) +# relation_editor_list.append((0, value, relation_uid, +# display_text, None, None)) + elif len(relation_list) == 0: + # If the length is 0, raise an error + if field.get_value('allow_creation') == 1 : + # XXX + for portal_type in portal_type_list: + translated_portal_type = Message(domain='erp5_ui', + message=portal_type) + message = Message( + domain='erp5_ui', message='New ${portal_type}', + mapping={'portal_type': translated_portal_type}) + menu_item_list.append((message, + '%s%s' % (NEW_CONTENT_PREFIX, + portal_type))) + REQUEST.set(relation_item_id, menu_item_list) + raising_error_needed = 1 + raising_error_value = 'relation_result_empty' else: - - kw ={} - kw[catalog_index] = value - kw['portal_type'] = portal_type - kw['sort_on'] = catalog_index - # Get the query results - relation_list = portal_catalog(**kw) - relation_uid_list = map(lambda x: x.uid, relation_list) - localizer = getToolByName( field - , 'Localizer' - , None - ) - # Prepare a menu - if localizer is not None: - N_ = localizer.erp5_ui.gettext - else : - N_ = lambda msg, **kw: msg - menu_item_list = [('', '')] - new_object_menu_item_list = [] - for p in portal_type: - new_object_menu_item_list += [ ( N_('New %s') % (N_(p)) - , '%s%s' % (new_content_prefix,p) - ) - ] - - if len(relation_list) >= MAX_SELECT: - # If the length is long, raise an error - # This parameter means we need listbox help - REQUEST.set(relation_item_id, []) - raising_error_needed = 1 - raising_error_value = 'relation_result_too_long' - - elif len(relation_list) == 1: - # If the length is 1, return uid - relation_uid = relation_uid_list[0] - related_object = portal_catalog.getObject(relation_uid) - if related_object is not None: - display_text = str(related_object.getProperty(catalog_index)) - else: - display_text = 'Object has been deleted' - - REQUEST.set(relation_item_id, ( (display_text, relation_uid), )) - relation_editor_list.append( (0, value, relation_uid, display_text) ) - - elif len(relation_list) == 0: - # If the length is 0, raise an error - if field.get_value('allow_creation') == 1 : - menu_item_list += new_object_menu_item_list - REQUEST.set(relation_item_id, menu_item_list) - raising_error_needed = 1 - raising_error_value = 'relation_result_empty' - - else: - # If the length is short, raise an error - # len(relation_list) < MAX_SELECT: - - #menu_item_list += [('-', '')] - menu_item_list += map(lambda x: (x.getObject().getProperty(catalog_index), x.uid), - relation_list) - REQUEST.set(relation_item_id, menu_item_list) - raising_error_needed = 1 - raising_error_value = 'relation_result_ambiguous' - - # validate MultiRelation field - if raising_error_needed: - # Raise error - self.raise_error(raising_error_value, field) - return value_list - else: - # Can return editor - return MultiRelationEditor(field.id, base_category, portal_type, portal_type_item, catalog_index, relation_setter_id, relation_editor_list) + # If the length is short, raise an error + # len(relation_list) < MAX_SELECT: + menu_item_list.extend([( + x.getObject().getProperty(catalog_index), + x.uid) for x in relation_list]) + REQUEST.set(relation_item_id, menu_item_list) + raising_error_needed = 1 + raising_error_value = 'relation_result_ambiguous' + + ##################################### + # Validate MultiRelation field + ##################################### + if raising_error_needed: + # Raise error + self.raise_error(raising_error_value, field) + return value_list + else: + # Can return editor + base_category = field.get_value('base_category') + portal_type_item = field.get_value('portal_type') + relation_setter_id = field.get_value('relation_setter_id') + return self.editor(field.id, + base_category, + portal_type_list, + portal_type_item, catalog_index, + relation_setter_id, relation_editor_list) MultiRelationStringFieldWidgetInstance = MultiRelationStringFieldWidget() MultiRelationStringFieldValidatorInstance = MultiRelationStringFieldValidator() class MultiRelationStringField(ZMIField): - meta_type = "MultiRelationStringField" - security = ClassSecurityInfo() - - widget = MultiRelationStringFieldWidgetInstance - validator = MultiRelationStringFieldValidatorInstance + meta_type = "MultiRelationStringField" + security = ClassSecurityInfo() - security.declareProtected('Access contents information', 'get_value') - def get_value(self, id, **kw): - """Get value for id. + widget = MultiRelationStringFieldWidgetInstance + validator = MultiRelationStringFieldValidatorInstance - Optionally pass keyword arguments that get passed to TALES - expression. - """ - if id in ('is_relation_field', 'is_multi_relation_field'): - result = 1 - else: - result = ZMIField.get_value(self, id, **kw) - return result + security.declareProtected('Access contents information', 'get_value') + def get_value(self, id, **kw): + """ + Get value for id. + Optionally pass keyword arguments that get passed to TALES + expression. + """ + if id in ('is_relation_field', 'is_multi_relation_field'): + result = 1 + else: + result = ZMIField.get_value(self, id, **kw) + return result diff --git a/product/ERP5Form/RelationField.py b/product/ERP5Form/RelationField.py index 05c427572280d0d421a49f7000b43bb1c41f3b2c..86ce53074f7c41074528506fb825002c5ab0f243 100755 --- a/product/ERP5Form/RelationField.py +++ b/product/ERP5Form/RelationField.py @@ -1,7 +1,8 @@ ############################################################################## # -# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved. +# Copyright (c) 2002, 2006 Nexedi SARL and Contributors. All Rights Reserved. # Jean-Paul Smets-Solanes <jp@nexedi.com> +# Romain Courteaud <romain@nexedi.com> # # WARNING: This program as such is intended to be used by professional # programmers who take the whole responsability of assessing all potential @@ -31,448 +32,120 @@ from Products.Formulator.Field import ZMIField from Products.Formulator.DummyField import fields from Products.ERP5Type.Utils import convertToUpperCase from Products.CMFCore.utils import getToolByName -from Globals import get_request from Products.PythonScripts.Utility import allow_class from Products.ERP5Type.Message import Message - -import string - +from Products.ERP5Form import MultiRelationField +from Products.ERP5Form.MultiRelationField import MAX_SELECT, \ + NEW_CONTENT_PREFIX, \ + SUB_FIELD_ID, ITEM_ID +from types import StringType from AccessControl import ClassSecurityInfo from zLOG import LOG -MAX_SELECT = 30 # Max. number of catalog result -new_content_prefix = '_newContent_' - - -class RelationStringFieldWidget(Widget.TextWidget, Widget.ListWidget): - """ - RelationStringField widget - Works like a string field but includes one buttons - - one search button which updates the field and sets a relation - - creates object if not there - """ - property_names = Widget.TextWidget.property_names + \ - ['update_method', 'jump_method', 'allow_jump', 'base_category', - 'portal_type', 'allow_creation', 'container_getter_id', 'catalog_index', - 'relation_setter_id', 'columns','sort','parameter_list','list_method', - 'first_item', 'items', 'size', 'extra_item'] - - # XXX Field to remove... - update_method = fields.StringField('update_method', - title='Update Method', - description=( - "The method to call to set the relation. Required."), - default="Base_validateRelation", - required=1) - - jump_method = fields.StringField('jump_method', - title='Jump Method', - description=( - "The method to call to jump to the relation. Required."), - default="Base_jumpToRelatedDocument", - required=1) - - allow_jump = fields.CheckBoxField('allow_jump', - title='Allow Jump', - description=( - "Do we allow to jump to the relation ?"), - default=1, - required=0) - - base_category = fields.StringField('base_category', - title='Base Category', - description=( - "The method to call to set the relation. Required."), - default="", - required=1) - - portal_type = fields.ListTextAreaField('portal_type', - title='Portal Type', - description=( - "The method to call to set the relation. Required."), - default="", - required=1) - - allow_creation = fields.CheckBoxField('allow_creation', - title='Allow Creation', - description=( - "Do we allow to create new objects ?"), - default=1, - required=0) - - container_getter_id = fields.StringField('container_getter_id', - title='Container Getter Method', - description=( - "The method to call to get a container object."), - default="", - required=0) - - catalog_index = fields.StringField('catalog_index', - title='Catalog Index', - description=( - "The method to call to set the relation. Required."), - default="", - required=1) - - # XXX Is it a good idea to keep such a field ?? - # User can redefine setter method with a script (and so, don't use the API) - relation_setter_id = fields.StringField('relation_setter_id', - title='Relation Update Method', - description=( - "The method to invoke in order to update the relation"), - default="", - required=0) - - size = fields.IntegerField('size', - title='Size', - description=( - "The display size in rows of the field. If set to 1, the " - "widget will be displayed as a drop down box by many browsers, " - "if set to something higher, a list will be shown. Required."), - default=1, - required=1) - - columns = fields.ListTextAreaField('columns', - title="Columns", - description=( - "A list of attributes names to display."), - default=[], - required=0) - - sort = fields.ListTextAreaField('sort', - title='Default Sort', - description=('The default sort keys and order'), - default=[], - required=0) - - parameter_list = fields.ListTextAreaField('parameter_list', - title="Parameter List", - description=( - "A list of paramters used for the portal_catalog."), - default=[], - required=0) - - list_method = fields.MethodField('list_method', - title='List Method', - description=('The method to use to list' - 'objects'), - default='', - required=0) - - def render(self, field, key, value, REQUEST): - """Render text input field. - """ - relation_field_id = 'relation_%s' % key - relation_item_id = 'item_%s' % key - here = REQUEST['here'] - portal_url = getToolByName(here, 'portal_url') - portal_url_string = portal_url() - portal_object = portal_url.getPortalObject() - html_string = Widget.TextWidget.render(self, field, key, value, - REQUEST) - - if REQUEST.has_key(relation_item_id): - # Define default tales on the fly - tales_expr = field.tales.get('items', None) - if not tales_expr: - from Products.Formulator.TALESField import TALESMethod - field.tales['items'] = TALESMethod('REQUEST/relation_item_list') - REQUEST['relation_item_list'] = REQUEST.get(relation_item_id) - html_string += ' %s ' % Widget.ListWidget.render(self, - field, relation_field_id, None, REQUEST) - REQUEST['relation_item_list'] = None - - # We used to add a button which has a path reference to a base category... - # but it really created too many problems - # now we do it in another way - # we compare what has been changed in the relation update script - - #elif value != field.get_value('default'): - else: - html_string += \ - ' <input type="image" ' \ - 'src="%s/images/exec16.png" value="update..." ' \ - 'name="%s/portal_selections/viewSearchRelatedDocumentDialog%s' \ - ':method"/>' % \ - (portal_url_string, portal_object.getPath(), - getattr(field.aq_parent, '_v_relation_field_index', 0)) - relation_field_index = getattr(field.aq_parent, - '_v_relation_field_index', 0) - # Increase index - field.aq_parent._v_relation_field_index = relation_field_index + 1 - -# import pdb; pdb.set_trace() -# field.get_value('default') - if (value not in ( None, '' )) and \ - (not REQUEST.has_key(relation_item_id)) and \ - (value == field.get_value('default')) and \ - (field.get_value('allow_jump') == 1): - if REQUEST.get('selection_name') is not None: - html_string += ' <a href="%s/%s?field_id=%s&' \ - 'form_id=%s&selection_name=%s&selection_index=%s' \ - '"><img src="%s/images/jump.png"/></a>' % \ - (here.absolute_url(), field.get_value('jump_method'), - field.id, field.aq_parent.id, - REQUEST.get('selection_name'), - REQUEST.get('selection_index'), - portal_url_string) - else: - html_string += ' <a href="%s/%s?field_id=%s&' \ - 'form_id=%s"><img src="%s/images/jump.png"/></a>' \ - % (here.absolute_url(), field.get_value('jump_method'), - field.id, field.aq_parent.id,portal_url_string) - return html_string +class RelationStringFieldWidget( + MultiRelationField.MultiRelationStringFieldWidget): + """ + RelationStringField widget + Works like a string field but includes one buttons + - one search button which updates the field and sets a relation + - creates object if not there + """ + property_names = Widget.TextWidget.property_names + \ + MultiRelationField.MultiRelationStringFieldWidget.local_property_names + + default_widget_rendering_instance = Widget.TextWidgetInstance + + def _generateRenderValueList(self, field, key, value, REQUEST): + relation_field_id = field.generate_subfield_key(SUB_FIELD_ID, key=key) + relation_item_key = field.generate_subfield_key(ITEM_ID, key=key) + relation_item_list = REQUEST.get(relation_item_key, []) + return [(Widget.TextWidgetInstance, relation_field_id, + relation_item_list, value, None)] + +class RelationEditor(MultiRelationField.MultiRelationEditor): + """ + A class holding all values required to update a relation + """ + def __call__(self, REQUEST): + MultiRelationField.MultiRelationEditor.__call__(self, REQUEST) + value = REQUEST.get(self.field_id) + if value is not None: + REQUEST.set(self.field_id, value[0]) - def render_view(self, field, value): - """Render text input field. - """ - if field.get_value('allow_jump') == 0 : - return Widget.TextWidget.render_view(self, field, value) - REQUEST = get_request() - here = REQUEST['here'] - html_string = Widget.TextWidget.render_view(self, field, value) - portal_url_string = getToolByName(here, 'portal_url')() - if value not in ('', None): - html_string = '<a href="%s/%s?field_id=%s&form_id=%s">%s</a>' \ - % (here.absolute_url(), field.get_value('jump_method'), - field.id, field.aq_parent.id, html_string) - html_string += ' <a href="%s/%s?field_id=%s&form_id=%s">' \ - '<img src="%s/images/jump.png"/></a>' % \ - (here.absolute_url(), field.get_value('jump_method'), - field.id, field.aq_parent.id, portal_url_string) - return html_string +allow_class(RelationEditor) -class RelationEditor: +class RelationStringFieldValidator( + MultiRelationField.MultiRelationStringFieldValidator): + """ + Validation includes lookup of relared instances + """ + + message_names = Validator.StringValidator.message_names + \ + MultiRelationField.MultiRelationStringFieldValidator.message_names + # Delete double in order to keep a usable ZMI... + # Need to keep order ! + _v_dict = {} + _v_message_name_list = [] + for message_name in message_names: + if not _v_dict.has_key(message_name): + _v_message_name_list.append(message_name) + _v_dict[message_name] = 1 + message_names = _v_message_name_list + + # Relation field variable + editor = RelationEditor + default_validator_instance = Validator.StringValidatorInstance + + def _generateItemUidList(self, field, key, relation_uid_list, REQUEST=None): """ - A class holding all values required to update a relation + Generate list of uid, item_key """ - - def __init__(self, field_id, base_category, portal_type, uid, - portal_type_item, key, value, relation_setter_id, - container_getter_id, display_text): - self.field_id = field_id - self.uid = uid - self.base_category = base_category - self.portal_type = portal_type - self.portal_type_item = portal_type_item - self.key = key - self.value = value - self.relation_setter_id = relation_setter_id - self.container_getter_id = container_getter_id - self.display_text = display_text - - def __call__(self, REQUEST): - if self.uid is not None: - # Decorate the request so that we can display - # the select item in a popup - relation_field_id = 'relation_%s' % self.field_id - relation_item_id = 'item_%s' % self.field_id - REQUEST.set(relation_item_id, ((self.display_text, self.uid),)) - REQUEST.set(relation_field_id, self.uid) - # XXX Dirty - REQUEST.set(self.field_id[len('field_'):], self.value) - else: - # Make sure no default value appears - REQUEST.set(self.field_id[len('field_'):], None) - - def view(self): - return self.__dict__ - - def edit(self, o): - if self.uid is not None: - if type(self.uid) is type('a') and \ - self.uid.startswith(new_content_prefix): - # Create a new content - portal_type = self.uid[len(new_content_prefix):] - container = None - for p_item in self.portal_type_item: - if p_item[0] == portal_type: - if self.container_getter_id: - container = getattr(o, self.container_getter_id)( - portal_type=portal_type) - else: - portal_module = o.getPortalObject().getDefaultModuleId( p_item[0] ) - container = getattr(o.getPortalObject(), portal_module) - if container is not None: - kw ={} - kw[self.key] = string.join( string.split(self.value,'%'), '' ) - kw['portal_type'] = portal_type - kw['immediate_reindex'] = 1 - new_object = container.newContent(**kw) - self.uid = new_object.getUid() - else: - raise - - # Edit relation - if self.relation_setter_id: - relation_setter = getattr(o, self.relation_setter_id) - relation_setter((), portal_type=self.portal_type) - relation_setter((int(self.uid),), portal_type=self.portal_type) - else: - # we could call a generic method which create the setter method name - set_method_name = '_set'+convertToUpperCase(self.base_category)+'Value' - object = o.portal_catalog.getObject( self.uid ) - getattr(o, set_method_name)( object,portal_type=self.portal_type ) - - else: - if self.value == '': - # Delete relation - if self.relation_setter_id: - relation_setter = getattr(o, self.relation_setter_id) - relation_setter((), portal_type=self.portal_type) - else: - # we could call a generic method which create the setter method name - set_method_name = '_set'+convertToUpperCase(self.base_category) - getattr(o, set_method_name)( None ,portal_type=self.portal_type) - - -allow_class(RelationEditor) - -class RelationStringFieldValidator(Validator.StringValidator): + relation_item_id = field.generate_subfield_key(ITEM_ID, + key=key) + if isinstance(relation_uid_list, (list, tuple)): + try: + relation_uid_list = relation_uid_list[0] + except IndexError: + # No object was selected + return [] + value = self.default_validator_instance.validate(field, + key, REQUEST) + return [(relation_item_id, relation_uid_list, value)] + + def _generateFieldValueList(self, field, key, + value_list, current_value_list): """ - Validation includes lookup of relared instances + Generate list of value, item_key """ - - message_names = Validator.StringValidator.message_names +\ - ['relation_result_too_long', 'relation_result_ambiguous', - 'relation_result_empty',] - - relation_result_too_long = "Too many documents were found." - relation_result_ambiguous = "Select appropriate document in the list." - relation_result_empty = "No such document was found." - - def validate(self, field, key, REQUEST): - relation_field_id = 'relation_%s' % key - relation_item_id = 'item_%s' % key - portal_type = map(lambda x:x[0],field.get_value('portal_type')) - portal_type_item = field.get_value('portal_type') - base_category = field.get_value( 'base_category') - # If the value is different, build a query - portal_selections = getToolByName(field, 'portal_selections') - portal_catalog = getToolByName(field, 'portal_catalog') - # Get the current value - value = Validator.StringValidator.validate(self, field, key, REQUEST) - # If the value is the same as the current field value, do nothing - current_value = field.get_value('default') - # If a relation has been defined in a popup menu, use it - relation_uid = REQUEST.get(relation_field_id, None) - catalog_index = field.get_value('catalog_index') - parameter_list = field.get_value('parameter_list') - relation_setter_id = field.get_value('relation_setter_id') - container_getter_id = field.get_value('container_getter_id') - - if (value == current_value) and (relation_uid is None): - # Will be interpreted by Editor as "do nothing" - return RelationEditor(key, base_category, portal_type, None, - portal_type_item, catalog_index, None, - relation_setter_id, container_getter_id, None) - if relation_uid not in (None, ''): - # A value has been defined by the user - if type(relation_uid) in (type([]), type(())): - if len( relation_uid ) == 0: - # No object was selected... - return None - else: - relation_uid = relation_uid[0] - - try: - related_object = portal_catalog.getObject(relation_uid) - except ValueError: - # Catch the error raised when the uid is a string - related_object = None - if related_object is not None: - display_text = str(related_object.getProperty(catalog_index)) - else: - display_text = 'Object has been deleted' - return RelationEditor(key, base_category, portal_type, relation_uid, - portal_type_item, catalog_index, value, - relation_setter_id, container_getter_id, - display_text) - - # We must be able to erase the relation - if value == '': - display_text = 'Delete the relation' - # Will be interpreted by Base_edit as "delete relation" - # (with no uid and value = '') - return RelationEditor(key, base_category, portal_type, None, - portal_type_item, catalog_index, value, - relation_setter_id, container_getter_id, display_text) - - kw ={} - kw[catalog_index] = value - kw['portal_type'] = portal_type - kw['sort_on'] = catalog_index - if len(parameter_list) > 0: - for k,v in parameter_list: - kw[k] = v - # Get the query results - relation_list = portal_catalog(**kw) - relation_uid_list = map(lambda x: x.uid, relation_list) - # Prepare a menu - menu_item_list = [('', '')] - new_object_menu_item_list = [] - for p in portal_type: - translated_p = Message(domain='erp5_ui',message=p) - message = Message(domain='erp5_ui',message = 'New ${portal_type}', - mapping={'portal_type':translated_p}) - new_object_menu_item_list += [ ( message - , '%s%s' % (new_content_prefix,p) - ) - ] - # If the length is 1, return uid - if len(relation_list) == 1: - relation_uid = relation_uid_list[0] - related_object = portal_catalog.getObject(relation_uid) - if related_object is not None: - display_text = str(related_object.getProperty(catalog_index)) - else: - display_text = 'Object has been deleted' - - return RelationEditor( - key, base_category, portal_type, relation_uid, - portal_type_item, catalog_index, value, - relation_setter_id, container_getter_id, display_text) - # If the length is 0, raise an error - elif len(relation_list) == 0: - if field.get_value('allow_creation') == 1 : - menu_item_list += new_object_menu_item_list - REQUEST.set(relation_item_id, menu_item_list) - self.raise_error('relation_result_empty', field) - # If the length is short, raise an error - elif len(relation_list) < MAX_SELECT: - #menu_item_list += [('-', '')] - menu_item_list.extend([(x.getObject().getProperty(catalog_index), - x.uid) for x in relation_list]) - REQUEST.set(relation_item_id, menu_item_list) - self.raise_error('relation_result_ambiguous', field) - else: - # If the length is long, raise an error - - # If this error is raise, we don t want to create a new object... - #REQUEST.set(relation_item_id, menu_item_list) - self.raise_error('relation_result_too_long', field) + if value_list == current_value_list: + return [] + else: + relation_field_id = field.generate_subfield_key("%s" % \ + SUB_FIELD_ID, key=key) + relation_item_key = field.generate_subfield_key(ITEM_ID, key=key) + return [(relation_field_id, value_list, relation_item_key)] RelationStringFieldWidgetInstance = RelationStringFieldWidget() RelationStringFieldValidatorInstance = RelationStringFieldValidator() class RelationStringField(ZMIField): - meta_type = "RelationStringField" - security = ClassSecurityInfo() - - widget = RelationStringFieldWidgetInstance - validator = RelationStringFieldValidatorInstance + meta_type = "RelationStringField" + security = ClassSecurityInfo() - security.declareProtected('Access contents information', 'get_value') - def get_value(self, id, **kw): - """Get value for id. + widget = RelationStringFieldWidgetInstance + validator = RelationStringFieldValidatorInstance - Optionally pass keyword arguments that get passed to TALES - expression. - """ - if id == 'is_relation_field': - result = 1 - elif id == 'is_multi_relation_field': - result = 0 - else: - result = ZMIField.get_value(self, id, **kw) - return result + security.declareProtected('Access contents information', 'get_value') + def get_value(self, id, **kw): + """ + Get value for id. + Optionally pass keyword arguments that get passed to TALES + expression. + """ + if id == 'is_relation_field': + result = 1 + elif id == 'is_multi_relation_field': + result = 0 + else: + result = ZMIField.get_value(self, id, **kw) + return result diff --git a/product/ERP5Form/SelectionTool.py b/product/ERP5Form/SelectionTool.py index 9718b05e35f211d7b22d0bc8d82b3cebdaeeae30..7b48f3f3b8af25f81cfcdb292690daaad269e253 100755 --- a/product/ERP5Form/SelectionTool.py +++ b/product/ERP5Form/SelectionTool.py @@ -52,6 +52,7 @@ import random import string from zLOG import LOG from Acquisition import Implicit, aq_base +from Products.ERP5Type.Message import Message class SelectionError( Exception ): @@ -920,7 +921,7 @@ class SelectionTool( UniqueObject, SimpleItem ): return object # Related document searching - def viewSearchRelatedDocumentDialog(self, index, form_id, REQUEST=None, + def viewSearchRelatedDocumentDialog(self, index, form_id, REQUEST=None, sub_index=None, **kw): """ Returns a search related document dialog @@ -928,8 +929,7 @@ class SelectionTool( UniqueObject, SimpleItem ): """ if sub_index != None: REQUEST.form['sub_index'] = sub_index - - # Find the object which needs to be updated + # Find the object which needs to be updated object_uid = REQUEST.get('object_uid', None) object_path = REQUEST.get('object_path', None) if object_uid is not None: @@ -942,72 +942,56 @@ class SelectionTool( UniqueObject, SimpleItem ): if object_path is not None: o = self.getPortalObject().restrictedTraverse(object_path) if o is not None: + # XXX o.immediateReindexObject() object_uid = o.getUid() else: - return "Sorrry, Error, the calling object was not catalogued. " \ - "Do not know how to do ?" - + raise SelectionError, \ + "Sorrry, Error, the calling object was not catalogued. " \ + "Do not know how to do ?" # Find the field which was clicked on # Important to get from the object instead of self form = getattr(o, form_id) field = None + # Search the correct field + relation_field_found = 0 relation_index = 0 - - # find the correct field - field_list = [] # XXX may be should support another parameter, - # like include_non_editable=0 for field in form.get_fields(include_disabled=0): - if field.get_value('editable',REQUEST=REQUEST): - field_list.append(field) - - relation_field_found = 0 - for field in field_list: - try: - dumb = field.get_value('is_relation_field') - # XXX FIXME Exception name is not in locals. - # This can be related to a bad python file import - # I already had this kind of error with another python software, - # and the only solution I found was to use ihooks to - # import python files. - # I have to check this. -# except KeyError: - except: - pass -# relation_index += 1 - else: - if index == relation_index: - relation_field_found = 1 - break + if field.get_value('editable', REQUEST=REQUEST): + try: + dumb = field.get_value('is_relation_field') + except: + # except KeyError: + # XXX FIXME Exception name is not in locals. + # Namespace seems a bit broken... + LOG("SelectionTool", 0, "Exception catched with broken namespace!") + pass else: - relation_index += 1 - -# if getattr(field, 'is_relation_field', None): -# if index == relation_index: -# relation_field_found = 1 -# break -# else: -# relation_index += 1 - + if index == relation_index: + relation_field_found = 1 + break + else: + relation_index += 1 if not relation_field_found: + # We didn't find the field... raise SelectionError, "SelectionTool: can not find the relation" \ " field %s" % index - - field_value = REQUEST.form['field_%s' % field.id] - - selection_name = 'Base_viewRelatedObjectList' - - # reselt current selection - self.portal_selections.setSelectionFor(selection_name, None) - - # XXX portal_status_message = - # "Please select one object to precise the value: - # '%s' in the field: '%s'" % (field_value, field.get_orig_value('title')) - portal_status_message = "Please select one object." - - if field.get_value('is_multi_relation_field'): - if sub_index is None: + else: + # Field found + field_key = field.generate_field_key() + field_value = REQUEST.form[field_key] + # XXX Hardcoded form name + redirect_form_id = 'Base_viewRelatedObjectList' + redirect_form = getattr(o, redirect_form_id) + # XXX Hardcoded listbox field + selection_name = redirect_form.listbox.get_value('selection_name') + # Reset current selection + self.portal_selections.setSelectionFor(selection_name, None) + + + if (field.get_value('is_multi_relation_field')) and \ + (sub_index is None): # user click on the wheel, not on the validation button # we need to facilitate user search @@ -1029,110 +1013,60 @@ class SelectionTool( UniqueObject, SimpleItem ): self.portal_selections.setSelectionCheckedUidsFor( selection_name, current_uid_list) - - REQUEST.form['field_%s' % field.id] = field_value - # XXX portal_status_message = - # "Please select one or more object to define the field: - # '%s'" % field.get_orig_value('title') - portal_status_message = "Please select one (or more) object." + # XXX +# field_value = '' + field_value = str(field_value).splitlines() + REQUEST.form[field_key] = field_value + portal_status_message = Message( + domain='erp5_ui', + message="Please select one (or more) object.") + else: + portal_status_message = Message(domain='erp5_ui', + message="Please select one object.") - # Save the current REQUEST form - # We can't put FileUpload instances because we can't pickle them - pickle_kw = {} - for key in REQUEST.form.keys(): - if not isinstance(REQUEST.form[key],FileUpload): - pickle_kw[key] = REQUEST.form[key] + # Save the current REQUEST form + # We can't put FileUpload instances because we can't pickle them + pickle_kw = {} + for key in REQUEST.form.keys(): + if not isinstance(REQUEST.form[key],FileUpload): + pickle_kw[key] = REQUEST.form[key] - form_pickle, form_signature = self.getPickleAndSignature(**pickle_kw) - REQUEST.form_pickle = form_pickle - REQUEST.form_signature = form_signature - - base_category = None - kw = {} - - kw['object_uid'] = object_uid - kw['form_id'] = 'Base_viewRelatedObjectList' - kw['selection_name'] = 'Base_viewRelatedObjectList' - kw['selection_index'] = 0 # We start on the first page - kw['field_id'] = field.id - kw['portal_type'] = map(lambda x:x[0],field.get_value('portal_type')) - parameter_list = field.get_value('parameter_list') - if len(parameter_list) > 0: - for k,v in parameter_list: - kw[k] = v - kw['reset'] = 0 - kw['base_category'] = field.get_value( 'base_category') - kw['cancel_url'] = REQUEST.get('HTTP_REFERER') - kw['previous_form_id'] = form_id - - - kw[field.get_value('catalog_index')] = str(field_value).splitlines() - - """ - # We work with strings - ie. single values - kw ={} - context.portal_selections.setSelectionParamsFor('Base_viewRelatedObjectList', kw.copy()) - previous_uids = o.getValueUids(base_category, portal_type=portal_type) - relation_list = context.portal_catalog(**kw) - relation_uid_list = map(lambda x: x.uid, relation_list) - uids = [] - """ - - # Need to redirect, if we want listbox nextPage to work - kw['form_pickle'] = form_pickle - kw['form_signature'] = form_signature - kw['portal_status_message'] = portal_status_message - - redirect_url = '%s/%s?%s' % ( o.absolute_url() - , 'Base_viewRelatedObjectList' - , make_query(kw) - ) - - REQUEST[ 'RESPONSE' ].redirect( redirect_url ) - -# # Empty the selection (uid) -# REQUEST.form = kw # New request form -# -# # Define new HTTP_REFERER -# REQUEST.HTTP_REFERER = '%s/Base_viewRelatedObjectList' % o.absolute_url() -# -# # Return the search dialog -# return o.Base_viewRelatedObjectList(REQUEST=REQUEST) - - - # XXX do not use this method, use aq_dynamic (JPS) -# def __getattr__(self, name): -# dynamic_method_name = 'viewSearchRelatedDocumentDialog' -# if name[:len(dynamic_method_name)] == dynamic_method_name: -# method_count_string = name[len(dynamic_method_name):] -# # be sure that method name is correct -# try: -# import string -# method_count = string.atoi(method_count_string) -# except: -# raise AttributeError, name -# else: -# # generate dynamicaly needed forwarder methods -# def viewSearchRelatedDocumentDialogWrapper(self, form_id, REQUEST=None, **kw): -# """ -# viewSearchRelatedDocumentDialog Wrapper -# """ -# return self.viewSearchRelatedDocumentDialog(method_count, form_id, REQUEST=REQUEST, **kw) -# -# setattr(self.__class__, name, viewSearchRelatedDocumentDialogWrapper) -# -# klass = self.__class__ -# if hasattr(klass, 'security'): -# from Products.ERP5Type import Permissions as ERP5Permissions -# klass.security.declareProtected(ERP5Permissions.View, name) -# else: -# # XXX security declaration always failed.... -# LOG('WARNING ERP5Form SelectionTool, security not defined on',0,klass.__name__) -# -# return getattr(self, name) -# else: -# raise AttributeError, name + form_pickle, form_signature = self.getPickleAndSignature(**pickle_kw) + REQUEST.form_pickle = form_pickle + REQUEST.form_signature = form_signature + + base_category = None + kw = {} + kw['object_uid'] = object_uid + kw['form_id'] = redirect_form_id + kw['selection_name'] = selection_name + kw['selection_index'] = 0 # We start on the first page + kw['field_id'] = field.id + kw['portal_type'] = [x[0] for x in field.get_value('portal_type')] + parameter_list = field.get_value('parameter_list') + if len(parameter_list) > 0: + for k,v in parameter_list: + kw[k] = v + kw['reset'] = 0 + kw['base_category'] = field.get_value( 'base_category') + kw['cancel_url'] = REQUEST.get('HTTP_REFERER') + kw['previous_form_id'] = form_id + + # XXX +# kw[field.get_value('catalog_index')] = str(field_value).splitlines() + kw[field.get_value('catalog_index')] = field_value + # Need to redirect, if we want listbox nextPage to work + kw['form_pickle'] = form_pickle + kw['form_signature'] = form_signature + kw['portal_status_message'] = portal_status_message + + redirect_url = '%s/%s?%s' % ( o.absolute_url() + , redirect_form_id + , make_query(kw) + ) + + REQUEST[ 'RESPONSE' ].redirect( redirect_url ) def _aq_dynamic(self, name): """ @@ -1141,14 +1075,13 @@ class SelectionTool( UniqueObject, SimpleItem ): """ aq_base_name = getattr(aq_base(self), name, None) if aq_base_name == None: - dynamic_method_name = 'viewSearchRelatedDocumentDialog' + DYNAMIC_METHOD_NAME = 'viewSearchRelatedDocumentDialog' + method_name_length = len(DYNAMIC_METHOD_NAME) + zope_security = '__roles__' - if (name[:len(dynamic_method_name)] == dynamic_method_name) and \ + if (name[:method_name_length] == DYNAMIC_METHOD_NAME) and \ (name[-len(zope_security):] != zope_security): - - method_count_string_list = string.split( - name[len(dynamic_method_name):], - '_') + method_count_string_list = name[method_name_length:].split('_') method_count_string = method_count_string_list[0] # be sure that method name is correct try: @@ -1173,15 +1106,9 @@ class SelectionTool( UniqueObject, SimpleItem ): """ LOG('SelectionTool.viewSearchRelatedDocumentDialogWrapper, kw', 0, kw) - if sub_index == None: - return self.viewSearchRelatedDocumentDialog( - method_count, form_id, - REQUEST=REQUEST, **kw) - else: - return self.viewSearchRelatedDocumentDialog( + return self.viewSearchRelatedDocumentDialog( method_count, form_id, REQUEST=REQUEST, sub_index=sub_index, **kw) - setattr(self.__class__, name, viewSearchRelatedDocumentDialogWrapper)