Commit 92a861fd authored by Romain Courteaud's avatar Romain Courteaud

Added new method to provide some kind of auto completion and simplify the user interface.


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@2361 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent f93a8cec
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
# Copyright (c) 2002, 2004 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,8 +32,32 @@ 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
class MultiRelationStringFieldWidget(Widget.LinesTextAreaWidget):
import string
from zLOG import LOG
#MAX_SELECT = 50 # Max. number of catalog result
#new_content_prefix = '_newContent_'
def checkSameKeys(a , b):
"""
Checks if the two lists contain
the same values
"""
same = 1
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
return same
class MultiRelationStringFieldWidget(Widget.LinesTextAreaWidget, RelationField.RelationStringFieldWidget):
"""
RelationStringField widget
......@@ -44,74 +69,118 @@ class MultiRelationStringFieldWidget(Widget.LinesTextAreaWidget):
"""
property_names = Widget.LinesTextAreaWidget.property_names + \
['update_method', 'jump_method', 'base_category', 'portal_type', 'catalog_index',
'default_module', 'relation_setter_id','columns']
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)
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)
catalog_index = fields.StringField('catalog_index',
title='Catalog Index',
description=(
"The method to call to set the relation. Required."),
default="",
required=1)
default_module = fields.StringField('default_module',
title='Default Module',
description=(
"The module which should be invoked to create new objects."),
default="",
required=1)
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)
columns = fields.ListTextAreaField('columns',
title="Columns",
description=(
"A list of attributes names to display."),
default=[],
required=0)
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.
"""
Render text input field.
"""
here = REQUEST['here']
html_string = Widget.LinesTextAreaWidget.render(self, field, key, value, REQUEST)
portal_url_string = getToolByName(here, 'portal_url')()
# We add a button which has a path reference to a base category...
html_string += '&nbsp;&nbsp;<input type="image" src="%s/images/exec16.png" value="update..." name="%s:method">' \
% (portal_url_string,field.get_value('update_method'))
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 += '&nbsp;%s&nbsp;' % 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 += '&nbsp;<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/>'
else:
# no modification made, we can display only a lines text area widget
html_string += Widget.LinesTextAreaWidget.render(self, field, key, value_list, REQUEST)
if value_list not in ((), [], None) and value_list == field.get_value('default'):
if REQUEST.get('selection_name') is not None:
html_string += '&nbsp;&nbsp;<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 += '&nbsp;&nbsp;<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)
field.aq_parent._v_relation_field_index += 1 # Increase index
return html_string
def render_view(self, field, value):
"""
Render text field.
"""
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 += '&nbsp;&nbsp;<a href="%s?field_id=%s&form_id=%s&selection_name=%s&selection_index=%s"><img src="%s/images/jump.png"></a>' \
......@@ -119,10 +188,261 @@ class MultiRelationStringFieldWidget(Widget.LinesTextAreaWidget):
else:
html_string += '&nbsp;&nbsp;<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
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):
self.field_id = field_id
self.base_category = base_category
self.portal_type = portal_type
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:
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 )
REQUEST.set(relation_item_id, ((display_text, uid),))
REQUEST.set(relation_field_id, uid)
#REQUEST.set(self.field_id[len('field_'):], value_list) # XXX Dirty
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):
return self.__dict__
def edit(self, o):
if self.relation_editor_list != None:
relation_uid_list = []
for i, value, uid, display_text in self.relation_editor_list:
if uid is not None:
if type(uid) is type('a') and uid.startswith(new_content_prefix):
# Create a new content
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] )
if portal_module is not None:
portal_module_object = getattr(o.getPortalObject(), portal_module)
kw ={}
#kw[self.key] = value
kw[self.key] = string.join( string.split(value,'%'), '' )
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))
#if relation_uid_list != []:
# 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)
else:
o._setValueUids(self.base_category, (), portal_type=self.portal_type)
o._setValueUids(self.base_category, relation_uid_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)
allow_class(MultiRelationEditor)
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)
# if type(value_list) == type(''):
# value_list = [value_list]
# 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')
if checkSameKeys( value_list, current_value_list ):
# XXX Will be interpreted by Base_edit as "do nothing"
#return MultiRelationEditor(field.id, base_category, portal_type, portal_type_item, catalog_index, relation_setter_id, None)
return None
else:
# We must be able to erase the relation
if value_list == ['']:
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 = '')
else:
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):
REQUEST.pop(relation_field_id)
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]
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'
# Check
REQUEST.set(relation_item_id, ( (display_text, relation_uid), ))
relation_editor_list.append( (i, value, str(relation_uid), display_text) )
else:
kw ={}
kw[catalog_index] = value
kw['portal_type'] = portal_type
# 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:
new_object_menu_item_list += [('New %s' % 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
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)
MultiRelationStringFieldWidgetInstance = MultiRelationStringFieldWidget()
MultiRelationStringFieldValidatorInstance = Validator.LinesValidator()
MultiRelationStringFieldValidatorInstance = MultiRelationStringFieldValidator()
class MultiRelationStringField(ZMIField):
meta_type = "MultiRelationStringField"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment