Commit dd102ec0 authored by Romain Courteaud's avatar Romain Courteaud

Unify RelationField and MultiRelationField (in order to fix both of them at the

same time).
Bug fix: do not write the relation_index in the ZODB anymore.


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@6202 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent b9d9e7bf
...@@ -38,7 +38,6 @@ from urllib import quote ...@@ -38,7 +38,6 @@ from urllib import quote
from Globals import InitializeClass, PersistentMapping, DTMLFile, get_request from Globals import InitializeClass, PersistentMapping, DTMLFile, get_request
from AccessControl import Unauthorized, getSecurityManager, ClassSecurityInfo from AccessControl import Unauthorized, getSecurityManager, ClassSecurityInfo
from ZODB.POSException import ConflictError from ZODB.POSException import ConflictError
from Products.ERP5Type.Utils import UpperCase from Products.ERP5Type.Utils import UpperCase
import psyco import psyco
...@@ -111,6 +110,7 @@ def get_value(self, id, **kw): ...@@ -111,6 +110,7 @@ def get_value(self, id, **kw):
else: else:
# get normal value # get normal value
value = self.get_orig_value(id) value = self.get_orig_value(id)
# For the 'default' value, we try to get a default value # For the 'default' value, we try to get a default value
if id == 'default': if id == 'default':
if (value is None or value == '' or value == [] or value == ()) \ if (value is None or value == '' or value == [] or value == ()) \
...@@ -355,7 +355,6 @@ class ERP5Form(ZMIForm, ZopePageTemplate): ...@@ -355,7 +355,6 @@ class ERP5Form(ZMIForm, ZopePageTemplate):
# Proxy method to PageTemplate # Proxy method to PageTemplate
def __call__(self, *args, **kwargs): 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'): if not kwargs.has_key('args'):
kwargs['args'] = args kwargs['args'] = args
form = self form = self
...@@ -370,6 +369,11 @@ class ERP5Form(ZMIForm, ZopePageTemplate): ...@@ -370,6 +369,11 @@ class ERP5Form(ZMIForm, ZopePageTemplate):
extra_context['form'] = self extra_context['form'] = self
extra_context['container'] = container ## PROBLEM NOT TAKEN INTO ACCOUNT extra_context['container'] = container ## PROBLEM NOT TAKEN INTO ACCOUNT
extra_context['here'] = object 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) return pt.pt_render(extra_context=extra_context)
def _exec(self, bound_names, args, kw): def _exec(self, bound_names, args, kw):
...@@ -456,6 +460,7 @@ class ERP5Form(ZMIForm, ZopePageTemplate): ...@@ -456,6 +460,7 @@ class ERP5Form(ZMIForm, ZopePageTemplate):
# More optimizations # More optimizations
#psyco.bind(ERP5Field) #psyco.bind(ERP5Field)
# XXX Not useful, as we patch those methods in FormulatorPatch
psyco.bind(Field.render) psyco.bind(Field.render)
psyco.bind(Field._render_helper) psyco.bind(Field._render_helper)
psyco.bind(Field.get_value) psyco.bind(Field.get_value)
......
############################################################################## ##############################################################################
# #
# 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> # Jean-Paul Smets-Solanes <jp@nexedi.com>
# Romain Courteaud <romain@nexedi.com> # Romain Courteaud <romain@nexedi.com>
# #
...@@ -29,19 +30,22 @@ ...@@ -29,19 +30,22 @@
from Products.Formulator import Widget, Validator from Products.Formulator import Widget, Validator
from Products.Formulator.Field import ZMIField from Products.Formulator.Field import ZMIField
from Products.Formulator.DummyField import fields
from Products.ERP5Type.Utils import convertToUpperCase from Products.ERP5Type.Utils import convertToUpperCase
from Products.CMFCore.utils import getToolByName 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.PythonScripts.Utility import allow_class
from Products.ERP5Type.Message import Message
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from types import StringType
import string
from zLOG import LOG from zLOG import LOG
#MAX_SELECT = 50 # Max. number of catalog result from Products.Formulator.DummyField import fields
#new_content_prefix = '_newContent_' 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): def checkSameKeys(a , b):
""" """
...@@ -52,199 +56,352 @@ def checkSameKeys(a , b): ...@@ -52,199 +56,352 @@ def checkSameKeys(a , b):
for ka in a: for ka in a:
if (not ka in b) and (ka != ''): if (not ka in b) and (ka != ''):
same = 0 same = 0
for kb in b: break
if (not kb in a) and (kb != ''): if same:
same = 0 for kb in b:
if (not kb in a) and (kb != ''):
same = 0
break
return same return same
class MultiRelationStringFieldWidget(Widget.LinesTextAreaWidget,
class MultiRelationStringFieldWidget(Widget.LinesTextAreaWidget, RelationField.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
"""
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 Render text input field.
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.LinesTextAreaWidget.property_names + \ html_string = ''
RelationField.RelationStringFieldWidget.property_names relation_field_index = REQUEST.get('_v_relation_field_index')
render_parameter_list = self._generateRenderValueList(
# delete double in order to keep a usable ZMI... field, key, value,
#property_names = dict([(i,0) for i in property_names]).keys() # XXX need to keep order ! REQUEST)
_v_dict = {} ####################################
_v_property_name_list = [] # Render subfield
for property_name in property_names: ####################################
if not _v_dict.has_key(property_name): html_string_list = []
_v_property_name_list.append(property_name) for widget_instance, relation_field_id, relation_item_list, \
_v_dict[property_name] = 1 value_instance, sub_index in render_parameter_list:
property_names = _v_property_name_list sub_html_string = widget_instance.render(field, key,
value_instance, REQUEST)
if relation_item_list is not None:
def render(self, field, key, value, REQUEST): if relation_item_list != []:
""" ####################################
Render text input field. # Render listfield
""" ####################################
here = REQUEST['here'] tales_expr = field.tales.get('items', None)
defined_tales = 0
relation_field_id = 'relation_%s' % key if not tales_expr:
relation_item_id = 'item_%s' % key defined_tales = 1
from Products.Formulator.TALESField import TALESMethod
portal_url = getToolByName(here, 'portal_url') # XXX XXX Do not write in the ZODB
portal_url_string = portal_url() field.tales['items'] = TALESMethod('REQUEST/relation_item_list')
portal_object = portal_url.getPortalObject()
REQUEST['relation_item_list'] = relation_item_list
if type(value) == type(''): sub_html_string += '&nbsp;%s&nbsp;' % \
# Value is a string, reformat it correctly Widget.ListWidgetInstance.render(
value_list = string.split(value, "\r\n") field, relation_field_id, None, REQUEST)
else: REQUEST['relation_item_list'] = None
value_list = value if defined_tales:
# Delete default tales on the fly
need_validation = 0 field.tales['items'] = None
# 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: else:
clean_value_list = [] ####################################
for v in value_list : # Render wheel
# rather than displaying nothing, display a marker when the ####################################
# property is not set sub_html_string += self.render_wheel(
if v is None : v = '??? (no value)' field, value_instance, REQUEST,
clean_value_list += [v] relation_index=relation_field_index,
# no modification made, we can display only a lines text area widget sub_index=sub_index)
html_string += Widget.LinesTextAreaWidget.render(self, field, key, clean_value_list, REQUEST) html_string_list.append(sub_html_string)
####################################
html_string += '&nbsp;<input type="image" src="%s/images/exec16.png" value="update..." name="%s/portal_selections/viewSearchRelatedDocumentDialog%s:method"/>' \ # Generate html
% (portal_url_string, portal_object.getPath(), field.aq_parent._v_relation_field_index) ####################################
html_string = '<br/>'.join(html_string_list)
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: # Render jump
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) if (value == field.get_value('default')):
else: # XXX Default rendering with value...
html_string += '&nbsp;&nbsp;<a href="%s?field_id=%s&form_id=%s"><img src="%s/images/jump.png"/></a>' \ relation_html_string = self.render_relation_link(field, value,
% (field.get_value('jump_method'), field.id, field.aq_parent.id,portal_url_string) REQUEST)
if relation_html_string != '':
relation_field_index = getattr(field.aq_parent, '_v_relation_field_index', 0) html_string += '&nbsp;&nbsp;%s' % relation_html_string
field.aq_parent._v_relation_field_index = relation_field_index + 1 # Increase index ####################################
return html_string # Update relation field index
####################################
def render_view(self, field, value): REQUEST.set('_v_relation_field_index', relation_field_index + 1)
""" return html_string
Render text field.
""" def render_view(self, field, value):
if field.get_value('allow_jump') == 0 : """
return Widget.LinesTextAreaWidget.render_view(self, field, value) Render read only field.
"""
REQUEST = get_request() html_string = self.default_widget_rendering_instance.render_view(
here = REQUEST['here'] field, value)
REQUEST = get_request()
portal_url = getToolByName(here, 'portal_url') relation_html_string = self.render_relation_link(field, value, REQUEST)
portal_url_string = portal_url() if relation_html_string != '':
html_string += '&nbsp;&nbsp;%s' % relation_html_string
# no modification made, we can display only a lines text area widget return html_string
html_string = Widget.LinesTextAreaWidget.render_view(self, field, value)
if value not in ((), [], None, ''): def render_wheel(self, field, value, REQUEST, relation_index=0,
if REQUEST.get('selection_name') is not None: sub_index=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) Render wheel used to display a listbox
else: """
html_string += '&nbsp;&nbsp;<a href="%s?field_id=%s&form_id=%s"><img src="%s/images/jump.png"/></a>' \ here = REQUEST['here']
% (field.get_value('jump_method'), field.id, field.aq_parent.id,portal_url_string) portal_url = getToolByName(here, 'portal_url')
portal_url_string = portal_url()
return html_string portal_object = portal_url.getPortalObject()
if sub_index is None:
sub_index_string = ''
else:
sub_index_string = '_%s' % sub_index
return '&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(),
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: class MultiRelationEditor:
""" """
A class holding all values required to update a relation 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.field_id = field_id
self.base_category = base_category 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.portal_type_item = portal_type_item
self.key = key self.key = key
self.relation_setter_id = relation_setter_id self.relation_setter_id = relation_setter_id
self.relation_editor_list = relation_editor_list self.relation_editor_list = relation_editor_list
def __call__(self, REQUEST): def __call__(self, REQUEST):
if self.relation_editor_list != None: if self.relation_editor_list != None:
value_list = [] value_list = []
for value, uid, display_text, relation_key, item_key in \
for i, value, uid, display_text in self.relation_editor_list: self.relation_editor_list:
value_list.append(value) value_list.append(value)
if uid is not None: if uid is not None:
# Decorate the request so that we can display # Decorate the request so that we can display
# the select item in a popup # the select item in a popup
#relation_field_id = 'relation_%s_%s' % ( self.key, i ) # XXX To be unified
#relation_item_id = 'item_%s_%s' % ( self.key, i ) relation_field_id = relation_key
relation_field_id = 'relation_field_%s_%s' % ( self.field_id, i ) relation_item_id = item_key
relation_item_id = 'item_field_%s_%s' % ( self.field_id, i )
REQUEST.set(relation_item_id, ((display_text, uid),)) REQUEST.set(relation_item_id, ((display_text, uid),))
# XXX Is it useful ?
REQUEST.set(relation_field_id, uid) REQUEST.set(relation_field_id, uid)
REQUEST.set(self.field_id, value_list) # XXX Dirty REQUEST.set(self.field_id, value_list) # XXX Dirty
else: else:
# Make sure no default value appears # Make sure no default value appears
#REQUEST.set(self.field_id[len('field_'):], None)
REQUEST.set(self.field_id, None) # XXX Dirty REQUEST.set(self.field_id, None) # XXX Dirty
def view(self): def view(self):
...@@ -255,276 +412,367 @@ class MultiRelationEditor: ...@@ -255,276 +412,367 @@ class MultiRelationEditor:
relation_uid_list = [] relation_uid_list = []
relation_object_list = [] relation_object_list = []
for value, uid, display_text, relation_key, item_key in \
for i, value, uid, display_text in self.relation_editor_list: self.relation_editor_list:
if uid is not None: 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 # Create a new content
portal_type = uid[len(new_content_prefix):] portal_type = uid[len(NEW_CONTENT_PREFIX):]
portal_module = None portal_module = None
for p_item in self.portal_type_item: for p_item in self.portal_type_item:
if p_item[0] == portal_type: 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: if portal_module is not None:
portal_module_object = getattr(o.getPortalObject(), portal_module) portal_module_object = getattr(o.getPortalObject(),
portal_module)
kw ={} kw ={}
#kw[self.key] = value kw[self.key] = value.replace('%', '')
kw[self.key] = string.join( string.split(value,'%'), '' )
kw['portal_type'] = portal_type kw['portal_type'] = portal_type
kw['immediate_reindex'] = 1 kw['immediate_reindex'] = 1
new_object = portal_module_object.newContent(**kw) new_object = portal_module_object.newContent(**kw)
uid = new_object.getUid() uid = new_object.getUid()
else: else:
raise raise
relation_uid_list.append(int(uid))
relation_uid_list.append(int(uid))
relation_object_list.append( o.portal_catalog.getObject(uid) ) relation_object_list.append( o.portal_catalog.getObject(uid))
#if relation_uid_list != []:
# Edit relation # Edit relation
if self.relation_setter_id: if self.relation_setter_id:
relation_setter = getattr(o, self.relation_setter_id) relation_setter = getattr(o, self.relation_setter_id)
relation_setter((), portal_type=self.portal_type) relation_setter((), portal_type=self.portal_type_list)
relation_setter( relation_uid_list , portal_type=self.portal_type) relation_setter(relation_uid_list,
portal_type=self.portal_type_list)
else: else:
# we could call a generic method which create the setter method name # we could call a generic method which create the setter method name
set_method_name = '_set'+convertToUpperCase(self.base_category)+'ValueList' set_method_name = '_set%sValueList' % \
getattr(o, set_method_name)( relation_object_list , portal_type=self.portal_type) convertToUpperCase(self.base_category)
getattr(o, set_method_name)(relation_object_list,
else: portal_type=self.portal_type_list)
# 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) 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): # XXX Do we need to translate here ?
""" relation_result_too_long = "Too many documents were found."
Validation includes lookup of relared instances relation_result_ambiguous = "Select appropriate document in the list."
""" relation_result_empty = "No such document was found."
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(''): # Relation field variable
# value_list = [value_list] 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 # If the value is the same as the current field value, do nothing
current_value_list = field.get_value('default') current_value_list = field.get_value('default')
if type(current_value_list) == type(''): field_value_list = self._generateFieldValueList(field, key, value_list,
current_value_list = [current_value_list] current_value_list)
if len(field_value_list) != 0:
catalog_index = field.get_value('catalog_index') ####################################
relation_setter_id = field.get_value('relation_setter_id') # Values were changed
####################################
relation_field_id = 'relation_%s' % ( key ) relation_editor_list = []
# we must know if user validate the form or click on the wheel button for relation_field_id, value, relation_item_id in field_value_list:
relation_uid_list = REQUEST.get(relation_field_id, None) if value == '':
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 )): # User want to delete this line
# Will be interpreted by Editor as "do nothing" ####################################
return MultiRelationEditor(field.id, base_category, # Clean request if necessary
portal_type, portal_type_item, if REQUEST.has_key(relation_field_id):
catalog_index, relation_setter_id, None) for subdict_name in ['form', 'other']:
else: subdict = getattr(REQUEST, subdict_name)
if subdict.has_key(relation_field_id):
relation_field_id = 'relation_%s' % ( key ) subdict.pop(relation_field_id)
display_text = 'Delete the relation'
# We must be able to erase the relation relation_editor_list.append((value, None,
if (value_list == ['']) and (not REQUEST.has_key( relation_field_id )): display_text, None, None))
display_text = 'Delete the relation' # XXX RelationField implementation
return MultiRelationEditor(field.id, base_category, portal_type, portal_type_item, catalog_index, relation_setter_id, []) # # We must be able to erase the relation
# return RelationEditor(key, base_category, portal_type, None, # display_text = 'Delete the relation'
# portal_type_item, catalog_index, value, relation_setter_id, display_text) # # Will be interpreted by Base_edit as "delete relation"
# Will be interpreted by Base_edit as "delete relation" (with no uid and value = '') # # (with no uid and value = '')
# relation_editor_list = [(value, None,
if REQUEST.has_key( relation_field_id ): # display_text, None, None)]
# we must know if user validate the form or click on the wheel button else:
relation_uid_list = REQUEST.get(relation_field_id, None) relation_uid = REQUEST.get(relation_field_id, None)
if relation_uid_list != None: # need_to_revalidate = 1
relation_editor_list = [] if relation_uid not in (None, ''):
for i in range( len(relation_uid_list) ): # need_to_revalidate = 0
# found = 0
relation_item_id = 'item_%s_%s' % ( key, i ) ####################################
relation_uid = relation_uid_list[i] # User selected in a popup menu
####################################
related_object = portal_catalog.getObject(relation_uid) 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: if related_object is not None:
display_text = str(related_object.getProperty(catalog_index)) display_text = str(related_object.getProperty(catalog_index))
# found = 1
else: else:
display_text = 'Object has been deleted' ##############################
# Check # New content was selected, but the
REQUEST.set(relation_item_id, ( (display_text, relation_uid), )) # form is not validated
# Storing display_text as value is needded in this case ##############################
relation_editor_list.append( (i, display_text, str(relation_uid), display_text) ) if relation_uid.startswith(NEW_CONTENT_PREFIX):
##############################
return MultiRelationEditor(field.id, base_category, portal_type, portal_type_item, catalog_index, relation_setter_id, relation_editor_list) # New content was selected, but the
# form is not validated
##############################
else: portal_type = relation_uid[len(NEW_CONTENT_PREFIX):]
# User validate the form translated_portal_type = Message(domain='erp5_ui',
message=portal_type)
relation_editor_list = [] message = Message(
raising_error_needed = 0 domain='erp5_ui', message='New ${portal_type}',
raising_error_value = '' mapping={'portal_type': translated_portal_type})
display_text = message
# Check all relation else:
for i in range( len(value_list) ): display_text = 'Object has been deleted'
relation_field_id = 'relation_%s_%s' % ( key, i ) # ################################
relation_item_id = 'item_%s_%s' % ( key, i ) # # Modify if user modified his value
# ################################
relation_uid = REQUEST.get(relation_field_id, None) # if (found == 1) and \
# (value != display_text):
value = value_list[i] # REQUEST.set(relation_field_id, None)
# need_to_revalidate = 1
# else:
# If we get a empty string, delete this line # # Check
if value == '': # REQUEST.set(relation_item_id, ((display_text, relation_uid),))
# Clean request if necessary # relation_editor_list.append((value, str(relation_uid),
if REQUEST.has_key( relation_field_id): # display_text, relation_field_id,
for subdict_name in ['form', 'other']: # relation_item_id))
subdict = getattr(REQUEST, subdict_name) REQUEST.set(relation_item_id, ((display_text, relation_uid),))
if subdict.has_key(relation_field_id): relation_editor_list.append((value, str(relation_uid),
subdict.pop(relation_field_id) display_text, relation_field_id,
relation_item_id))
# if need_to_revalidate == 1:
else: else:
# Got a true value ####################################
# User validate the form for this line
if relation_uid not in (None, ''): ####################################
# A value has been defined by the user in popup menu kw ={}
if type(relation_uid) in (type([]), type(())): relation_uid = relation_uid[0] kw[catalog_index] = value
try: kw['portal_type'] = portal_type_list
related_object = portal_catalog.getObject(relation_uid) kw['sort_on'] = catalog_index
except ValueError: # Get the query results
# Catch the exception raised when the uid is a string relation_list = portal_catalog(**kw)
related_object = None 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: if related_object is not None:
display_text = str(related_object.getProperty(catalog_index)) display_text = str(related_object.getProperty(catalog_index))
# Modify the value, in order to let the user
# modify it later...
value = display_text
else: else:
display_text = 'Object has been deleted' display_text = 'Object has been deleted'
# Check # XXX XXX XXX
REQUEST.set(relation_item_id, ( (display_text, relation_uid), )) REQUEST.set(relation_item_id, ((display_text,
relation_editor_list.append( (i, value, str(relation_uid), 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: else:
# If the length is short, raise an error
kw ={} # len(relation_list) < MAX_SELECT:
kw[catalog_index] = value menu_item_list.extend([(
kw['portal_type'] = portal_type x.getObject().getProperty(catalog_index),
kw['sort_on'] = catalog_index x.uid) for x in relation_list])
# Get the query results REQUEST.set(relation_item_id, menu_item_list)
relation_list = portal_catalog(**kw) raising_error_needed = 1
relation_uid_list = map(lambda x: x.uid, relation_list) raising_error_value = 'relation_result_ambiguous'
localizer = getToolByName( field
, 'Localizer' #####################################
, None # Validate MultiRelation field
) #####################################
# Prepare a menu if raising_error_needed:
if localizer is not None: # Raise error
N_ = localizer.erp5_ui.gettext self.raise_error(raising_error_value, field)
else : return value_list
N_ = lambda msg, **kw: msg else:
menu_item_list = [('', '')] # Can return editor
new_object_menu_item_list = [] base_category = field.get_value('base_category')
for p in portal_type: portal_type_item = field.get_value('portal_type')
new_object_menu_item_list += [ ( N_('New %s') % (N_(p)) relation_setter_id = field.get_value('relation_setter_id')
, '%s%s' % (new_content_prefix,p) return self.editor(field.id,
) base_category,
] portal_type_list,
portal_type_item, catalog_index,
if len(relation_list) >= MAX_SELECT: relation_setter_id, relation_editor_list)
# 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)
MultiRelationStringFieldWidgetInstance = MultiRelationStringFieldWidget() MultiRelationStringFieldWidgetInstance = MultiRelationStringFieldWidget()
MultiRelationStringFieldValidatorInstance = MultiRelationStringFieldValidator() MultiRelationStringFieldValidatorInstance = MultiRelationStringFieldValidator()
class MultiRelationStringField(ZMIField): class MultiRelationStringField(ZMIField):
meta_type = "MultiRelationStringField" meta_type = "MultiRelationStringField"
security = ClassSecurityInfo() security = ClassSecurityInfo()
widget = MultiRelationStringFieldWidgetInstance
validator = MultiRelationStringFieldValidatorInstance
security.declareProtected('Access contents information', 'get_value') widget = MultiRelationStringFieldWidgetInstance
def get_value(self, id, **kw): validator = MultiRelationStringFieldValidatorInstance
"""Get value for id.
Optionally pass keyword arguments that get passed to TALES security.declareProtected('Access contents information', 'get_value')
expression. def get_value(self, id, **kw):
""" """
if id in ('is_relation_field', 'is_multi_relation_field'): Get value for id.
result = 1 Optionally pass keyword arguments that get passed to TALES
else: expression.
result = ZMIField.get_value(self, id, **kw) """
return result if id in ('is_relation_field', 'is_multi_relation_field'):
result = 1
else:
result = ZMIField.get_value(self, id, **kw)
return result
############################################################################## ##############################################################################
# #
# 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> # Jean-Paul Smets-Solanes <jp@nexedi.com>
# Romain Courteaud <romain@nexedi.com>
# #
# WARNING: This program as such is intended to be used by professional # WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential # programmers who take the whole responsability of assessing all potential
...@@ -31,448 +32,120 @@ from Products.Formulator.Field import ZMIField ...@@ -31,448 +32,120 @@ from Products.Formulator.Field import ZMIField
from Products.Formulator.DummyField import fields from Products.Formulator.DummyField import fields
from Products.ERP5Type.Utils import convertToUpperCase from Products.ERP5Type.Utils import convertToUpperCase
from Products.CMFCore.utils import getToolByName from Products.CMFCore.utils import getToolByName
from Globals import get_request
from Products.PythonScripts.Utility import allow_class from Products.PythonScripts.Utility import allow_class
from Products.ERP5Type.Message import Message from Products.ERP5Type.Message import Message
from Products.ERP5Form import MultiRelationField
import string from Products.ERP5Form.MultiRelationField import MAX_SELECT, \
NEW_CONTENT_PREFIX, \
SUB_FIELD_ID, ITEM_ID
from types import StringType
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from zLOG import LOG 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 += '&nbsp;%s&nbsp;' % 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 += \
'&nbsp;<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, class RelationStringFieldWidget(
'_v_relation_field_index', 0) MultiRelationField.MultiRelationStringFieldWidget):
# Increase index """
field.aq_parent._v_relation_field_index = relation_field_index + 1 RelationStringField widget
Works like a string field but includes one buttons
# import pdb; pdb.set_trace() - one search button which updates the field and sets a relation
# field.get_value('default') - creates object if not there
if (value not in ( None, '' )) and \ """
(not REQUEST.has_key(relation_item_id)) and \ property_names = Widget.TextWidget.property_names + \
(value == field.get_value('default')) and \ MultiRelationField.MultiRelationStringFieldWidget.local_property_names
(field.get_value('allow_jump') == 1):
if REQUEST.get('selection_name') is not None: default_widget_rendering_instance = Widget.TextWidgetInstance
html_string += '&nbsp;&nbsp;<a href="%s/%s?field_id=%s&' \
'form_id=%s&selection_name=%s&selection_index=%s' \ def _generateRenderValueList(self, field, key, value, REQUEST):
'"><img src="%s/images/jump.png"/></a>' % \ relation_field_id = field.generate_subfield_key(SUB_FIELD_ID, key=key)
(here.absolute_url(), field.get_value('jump_method'), relation_item_key = field.generate_subfield_key(ITEM_ID, key=key)
field.id, field.aq_parent.id, relation_item_list = REQUEST.get(relation_item_key, [])
REQUEST.get('selection_name'), return [(Widget.TextWidgetInstance, relation_field_id,
REQUEST.get('selection_index'), relation_item_list, value, None)]
portal_url_string)
else: class RelationEditor(MultiRelationField.MultiRelationEditor):
html_string += '&nbsp;&nbsp;<a href="%s/%s?field_id=%s&' \ """
'form_id=%s"><img src="%s/images/jump.png"/></a>' \ A class holding all values required to update a relation
% (here.absolute_url(), field.get_value('jump_method'), """
field.id, field.aq_parent.id,portal_url_string) def __call__(self, REQUEST):
return html_string 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): allow_class(RelationEditor)
"""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 += '&nbsp;&nbsp;<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 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
""" """
relation_item_id = field.generate_subfield_key(ITEM_ID,
def __init__(self, field_id, base_category, portal_type, uid, key=key)
portal_type_item, key, value, relation_setter_id, if isinstance(relation_uid_list, (list, tuple)):
container_getter_id, display_text): try:
self.field_id = field_id relation_uid_list = relation_uid_list[0]
self.uid = uid except IndexError:
self.base_category = base_category # No object was selected
self.portal_type = portal_type return []
self.portal_type_item = portal_type_item value = self.default_validator_instance.validate(field,
self.key = key key, REQUEST)
self.value = value return [(relation_item_id, relation_uid_list, value)]
self.relation_setter_id = relation_setter_id
self.container_getter_id = container_getter_id def _generateFieldValueList(self, field, key,
self.display_text = display_text value_list, current_value_list):
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):
""" """
Validation includes lookup of relared instances Generate list of value, item_key
""" """
if value_list == current_value_list:
message_names = Validator.StringValidator.message_names +\ return []
['relation_result_too_long', 'relation_result_ambiguous', else:
'relation_result_empty',] relation_field_id = field.generate_subfield_key("%s" % \
SUB_FIELD_ID, key=key)
relation_result_too_long = "Too many documents were found." relation_item_key = field.generate_subfield_key(ITEM_ID, key=key)
relation_result_ambiguous = "Select appropriate document in the list." return [(relation_field_id, value_list, relation_item_key)]
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)
RelationStringFieldWidgetInstance = RelationStringFieldWidget() RelationStringFieldWidgetInstance = RelationStringFieldWidget()
RelationStringFieldValidatorInstance = RelationStringFieldValidator() RelationStringFieldValidatorInstance = RelationStringFieldValidator()
class RelationStringField(ZMIField): class RelationStringField(ZMIField):
meta_type = "RelationStringField" meta_type = "RelationStringField"
security = ClassSecurityInfo() security = ClassSecurityInfo()
widget = RelationStringFieldWidgetInstance
validator = RelationStringFieldValidatorInstance
security.declareProtected('Access contents information', 'get_value') widget = RelationStringFieldWidgetInstance
def get_value(self, id, **kw): validator = RelationStringFieldValidatorInstance
"""Get value for id.
Optionally pass keyword arguments that get passed to TALES security.declareProtected('Access contents information', 'get_value')
expression. def get_value(self, id, **kw):
""" """
if id == 'is_relation_field': Get value for id.
result = 1 Optionally pass keyword arguments that get passed to TALES
elif id == 'is_multi_relation_field': expression.
result = 0 """
else: if id == 'is_relation_field':
result = ZMIField.get_value(self, id, **kw) result = 1
return result elif id == 'is_multi_relation_field':
result = 0
else:
result = ZMIField.get_value(self, id, **kw)
return result
...@@ -52,6 +52,7 @@ import random ...@@ -52,6 +52,7 @@ import random
import string import string
from zLOG import LOG from zLOG import LOG
from Acquisition import Implicit, aq_base from Acquisition import Implicit, aq_base
from Products.ERP5Type.Message import Message
class SelectionError( Exception ): class SelectionError( Exception ):
...@@ -920,7 +921,7 @@ class SelectionTool( UniqueObject, SimpleItem ): ...@@ -920,7 +921,7 @@ class SelectionTool( UniqueObject, SimpleItem ):
return object return object
# Related document searching # Related document searching
def viewSearchRelatedDocumentDialog(self, index, form_id, REQUEST=None, def viewSearchRelatedDocumentDialog(self, index, form_id, REQUEST=None,
sub_index=None, **kw): sub_index=None, **kw):
""" """
Returns a search related document dialog Returns a search related document dialog
...@@ -928,8 +929,7 @@ class SelectionTool( UniqueObject, SimpleItem ): ...@@ -928,8 +929,7 @@ class SelectionTool( UniqueObject, SimpleItem ):
""" """
if sub_index != None: if sub_index != None:
REQUEST.form['sub_index'] = sub_index 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_uid = REQUEST.get('object_uid', None)
object_path = REQUEST.get('object_path', None) object_path = REQUEST.get('object_path', None)
if object_uid is not None: if object_uid is not None:
...@@ -942,72 +942,56 @@ class SelectionTool( UniqueObject, SimpleItem ): ...@@ -942,72 +942,56 @@ class SelectionTool( UniqueObject, SimpleItem ):
if object_path is not None: if object_path is not None:
o = self.getPortalObject().restrictedTraverse(object_path) o = self.getPortalObject().restrictedTraverse(object_path)
if o is not None: if o is not None:
# XXX
o.immediateReindexObject() o.immediateReindexObject()
object_uid = o.getUid() object_uid = o.getUid()
else: else:
return "Sorrry, Error, the calling object was not catalogued. " \ raise SelectionError, \
"Do not know how to do ?" "Sorrry, Error, the calling object was not catalogued. " \
"Do not know how to do ?"
# Find the field which was clicked on # Find the field which was clicked on
# Important to get from the object instead of self # Important to get from the object instead of self
form = getattr(o, form_id) form = getattr(o, form_id)
field = None field = None
# Search the correct field
relation_field_found = 0
relation_index = 0 relation_index = 0
# find the correct field
field_list = []
# XXX may be should support another parameter, # XXX may be should support another parameter,
# like include_non_editable=0
for field in form.get_fields(include_disabled=0): for field in form.get_fields(include_disabled=0):
if field.get_value('editable',REQUEST=REQUEST): if field.get_value('editable', REQUEST=REQUEST):
field_list.append(field) try:
dumb = field.get_value('is_relation_field')
relation_field_found = 0 except:
for field in field_list: # except KeyError:
try: # XXX FIXME Exception name is not in locals.
dumb = field.get_value('is_relation_field') # Namespace seems a bit broken...
# XXX FIXME Exception name is not in locals. LOG("SelectionTool", 0, "Exception catched with broken namespace!")
# This can be related to a bad python file import pass
# 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
else: else:
relation_index += 1 if index == relation_index:
relation_field_found = 1
# if getattr(field, 'is_relation_field', None): break
# if index == relation_index: else:
# relation_field_found = 1 relation_index += 1
# break
# else:
# relation_index += 1
if not relation_field_found: if not relation_field_found:
# We didn't find the field...
raise SelectionError, "SelectionTool: can not find the relation" \ raise SelectionError, "SelectionTool: can not find the relation" \
" field %s" % index " field %s" % index
else:
field_value = REQUEST.form['field_%s' % field.id] # Field found
field_key = field.generate_field_key()
selection_name = 'Base_viewRelatedObjectList' field_value = REQUEST.form[field_key]
# XXX Hardcoded form name
# reselt current selection redirect_form_id = 'Base_viewRelatedObjectList'
self.portal_selections.setSelectionFor(selection_name, None) redirect_form = getattr(o, redirect_form_id)
# XXX Hardcoded listbox field
# XXX portal_status_message = selection_name = redirect_form.listbox.get_value('selection_name')
# "Please select one object to precise the value: # Reset current selection
# '%s' in the field: '%s'" % (field_value, field.get_orig_value('title')) self.portal_selections.setSelectionFor(selection_name, None)
portal_status_message = "Please select one object."
if field.get_value('is_multi_relation_field'): if (field.get_value('is_multi_relation_field')) and \
if sub_index is None: (sub_index is None):
# user click on the wheel, not on the validation button # user click on the wheel, not on the validation button
# we need to facilitate user search # we need to facilitate user search
...@@ -1029,110 +1013,60 @@ class SelectionTool( UniqueObject, SimpleItem ): ...@@ -1029,110 +1013,60 @@ class SelectionTool( UniqueObject, SimpleItem ):
self.portal_selections.setSelectionCheckedUidsFor( self.portal_selections.setSelectionCheckedUidsFor(
selection_name, selection_name,
current_uid_list) current_uid_list)
# XXX
REQUEST.form['field_%s' % field.id] = field_value # field_value = ''
# XXX portal_status_message = field_value = str(field_value).splitlines()
# "Please select one or more object to define the field: REQUEST.form[field_key] = field_value
# '%s'" % field.get_orig_value('title') portal_status_message = Message(
portal_status_message = "Please select one (or more) object." 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 # Save the current REQUEST form
# We can't put FileUpload instances because we can't pickle them # We can't put FileUpload instances because we can't pickle them
pickle_kw = {} pickle_kw = {}
for key in REQUEST.form.keys(): for key in REQUEST.form.keys():
if not isinstance(REQUEST.form[key],FileUpload): if not isinstance(REQUEST.form[key],FileUpload):
pickle_kw[key] = REQUEST.form[key] pickle_kw[key] = REQUEST.form[key]
form_pickle, form_signature = self.getPickleAndSignature(**pickle_kw) form_pickle, form_signature = self.getPickleAndSignature(**pickle_kw)
REQUEST.form_pickle = form_pickle REQUEST.form_pickle = form_pickle
REQUEST.form_signature = form_signature REQUEST.form_signature = form_signature
base_category = None base_category = None
kw = {} kw = {}
kw['object_uid'] = object_uid
kw['object_uid'] = object_uid kw['form_id'] = redirect_form_id
kw['form_id'] = 'Base_viewRelatedObjectList' kw['selection_name'] = selection_name
kw['selection_name'] = 'Base_viewRelatedObjectList' kw['selection_index'] = 0 # We start on the first page
kw['selection_index'] = 0 # We start on the first page kw['field_id'] = field.id
kw['field_id'] = field.id kw['portal_type'] = [x[0] for x in field.get_value('portal_type')]
kw['portal_type'] = map(lambda x:x[0],field.get_value('portal_type')) parameter_list = field.get_value('parameter_list')
parameter_list = field.get_value('parameter_list') if len(parameter_list) > 0:
if len(parameter_list) > 0: for k,v in parameter_list:
for k,v in parameter_list: kw[k] = v
kw[k] = v kw['reset'] = 0
kw['reset'] = 0 kw['base_category'] = field.get_value( 'base_category')
kw['base_category'] = field.get_value( 'base_category') kw['cancel_url'] = REQUEST.get('HTTP_REFERER')
kw['cancel_url'] = REQUEST.get('HTTP_REFERER') kw['previous_form_id'] = form_id
kw['previous_form_id'] = form_id
# XXX
# kw[field.get_value('catalog_index')] = str(field_value).splitlines()
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
# We work with strings - ie. single values kw['form_signature'] = form_signature
kw ={} kw['portal_status_message'] = portal_status_message
context.portal_selections.setSelectionParamsFor('Base_viewRelatedObjectList', kw.copy())
previous_uids = o.getValueUids(base_category, portal_type=portal_type) redirect_url = '%s/%s?%s' % ( o.absolute_url()
relation_list = context.portal_catalog(**kw) , redirect_form_id
relation_uid_list = map(lambda x: x.uid, relation_list) , make_query(kw)
uids = [] )
"""
REQUEST[ 'RESPONSE' ].redirect( redirect_url )
# 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
def _aq_dynamic(self, name): def _aq_dynamic(self, name):
""" """
...@@ -1141,14 +1075,13 @@ class SelectionTool( UniqueObject, SimpleItem ): ...@@ -1141,14 +1075,13 @@ class SelectionTool( UniqueObject, SimpleItem ):
""" """
aq_base_name = getattr(aq_base(self), name, None) aq_base_name = getattr(aq_base(self), name, None)
if aq_base_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__' 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): (name[-len(zope_security):] != zope_security):
method_count_string_list = name[method_name_length:].split('_')
method_count_string_list = string.split(
name[len(dynamic_method_name):],
'_')
method_count_string = method_count_string_list[0] method_count_string = method_count_string_list[0]
# be sure that method name is correct # be sure that method name is correct
try: try:
...@@ -1173,15 +1106,9 @@ class SelectionTool( UniqueObject, SimpleItem ): ...@@ -1173,15 +1106,9 @@ class SelectionTool( UniqueObject, SimpleItem ):
""" """
LOG('SelectionTool.viewSearchRelatedDocumentDialogWrapper, kw', LOG('SelectionTool.viewSearchRelatedDocumentDialogWrapper, kw',
0, kw) 0, kw)
if sub_index == None: return self.viewSearchRelatedDocumentDialog(
return self.viewSearchRelatedDocumentDialog(
method_count, form_id,
REQUEST=REQUEST, **kw)
else:
return self.viewSearchRelatedDocumentDialog(
method_count, form_id, method_count, form_id,
REQUEST=REQUEST, sub_index=sub_index, **kw) REQUEST=REQUEST, sub_index=sub_index, **kw)
setattr(self.__class__, name, setattr(self.__class__, name,
viewSearchRelatedDocumentDialogWrapper) viewSearchRelatedDocumentDialogWrapper)
......
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