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,28 +56,139 @@ def checkSameKeys(a , b): ...@@ -52,28 +56,139 @@ 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
break
if same:
for kb in b: for kb in b:
if (not kb in a) and (kb != ''): if (not kb in a) and (kb != ''):
same = 0 same = 0
break
return same return same
class MultiRelationStringFieldWidget(Widget.LinesTextAreaWidget,
class MultiRelationStringFieldWidget(Widget.LinesTextAreaWidget, RelationField.RelationStringFieldWidget): Widget.TextWidget,
Widget.ListWidget):
""" """
RelationStringField widget RelationStringField widget
Works like a string field but includes one buttons Works like a string field but includes one buttons
- one search button which updates the field and sets a relation - one search button which updates the field and sets a relation
- creates object if not there - 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 + \ property_names = Widget.LinesTextAreaWidget.property_names + \
RelationField.RelationStringFieldWidget.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... # 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 ! # XXX need to keep order !
#property_names = dict([(i,0) for i in property_names]).keys()
_v_dict = {} _v_dict = {}
_v_property_name_list = [] _v_property_name_list = []
for property_name in property_names: for property_name in property_names:
...@@ -82,169 +197,211 @@ class MultiRelationStringFieldWidget(Widget.LinesTextAreaWidget, RelationField.R ...@@ -82,169 +197,211 @@ class MultiRelationStringFieldWidget(Widget.LinesTextAreaWidget, RelationField.R
_v_dict[property_name] = 1 _v_dict[property_name] = 1
property_names = _v_property_name_list property_names = _v_property_name_list
default_widget_rendering_instance = Widget.LinesTextAreaWidgetInstance
def render(self, field, key, value, REQUEST): def _generateRenderValueList(self, field, key, value_list, REQUEST):
""" result_list = []
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 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 # Check all relation
for i in range( len(value_list) ): for i in range(len(value_list)):
relation_field_id = 'relation_%s_%s' % ( key, i ) ###################################
relation_item_id = 'item_%s_%s' % ( key, i ) # Sub field
if REQUEST.has_key(relation_item_id) and value_list[i] != '': ###################################
need_validation = 1 relation_field_id = field.generate_subfield_key("%s_%s" % \
break (SUB_FIELD_ID, i),
key=key)
html_string = '' relation_item_id = field.generate_subfield_key("%s_%s" % \
if need_validation: (ITEM_ID, i),
# Check all relation key=key)
for i in range( len(value_list) ): relation_item_list = REQUEST.get(relation_item_id, None)
value = value_list[i] value = value_list[i]
relation_field_id = 'relation_%s_%s' % ( key, i ) if (relation_item_list is not None) and \
relation_item_id = 'item_%s_%s' % ( key, i ) (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 we get a empty string, display nothing !
if value == '': if value != '':
pass result_list.append((Widget.TextWidgetInstance, relation_field_id,
relation_item_list, value, i))
else: if not need_validation:
###################################
html_string += Widget.TextWidget.render(self, field, key, value, REQUEST) # Main field
###################################
if REQUEST.has_key(relation_item_id): result_list = [(Widget.LinesTextAreaWidgetInstance, None, [],
relation_item_list = REQUEST.get(relation_item_id) value_list, None)]
return result_list
def render(self, field, key, value, REQUEST):
"""
Render text input field.
"""
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 != []: if relation_item_list != []:
# Define default tales on the fly ####################################
# Render listfield
####################################
tales_expr = field.tales.get('items', None) tales_expr = field.tales.get('items', None)
defined_tales = 0 defined_tales = 0
if not tales_expr: if not tales_expr:
defined_tales = 1 defined_tales = 1
from Products.Formulator.TALESField import TALESMethod from Products.Formulator.TALESField import TALESMethod
# XXX XXX Do not write in the ZODB
field.tales['items'] = TALESMethod('REQUEST/relation_item_list') field.tales['items'] = TALESMethod('REQUEST/relation_item_list')
REQUEST['relation_item_list'] = relation_item_list REQUEST['relation_item_list'] = relation_item_list
html_string += '&nbsp;%s&nbsp;' % Widget.ListWidget.render(self, sub_html_string += '&nbsp;%s&nbsp;' % \
Widget.ListWidgetInstance.render(
field, relation_field_id, None, REQUEST) field, relation_field_id, None, REQUEST)
REQUEST['relation_item_list'] = None REQUEST['relation_item_list'] = None
if defined_tales: if defined_tales:
# Delete default tales on the fly # Delete default tales on the fly
field.tales['items'] = None field.tales['items'] = None
else: 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) # Render wheel
####################################
html_string += '<br/>' sub_html_string += self.render_wheel(
field, value_instance, REQUEST,
else: relation_index=relation_field_index,
clean_value_list = [] sub_index=sub_index)
for v in value_list : html_string_list.append(sub_html_string)
# rather than displaying nothing, display a marker when the ####################################
# property is not set # Generate html
if v is None : v = '??? (no value)' ####################################
clean_value_list += [v] html_string = '<br/>'.join(html_string_list)
# no modification made, we can display only a lines text area widget ####################################
html_string += Widget.LinesTextAreaWidget.render(self, field, key, clean_value_list, REQUEST) # Render jump
####################################
html_string += '&nbsp;<input type="image" src="%s/images/exec16.png" value="update..." name="%s/portal_selections/viewSearchRelatedDocumentDialog%s:method"/>' \ if (value == field.get_value('default')):
% (portal_url_string, portal_object.getPath(), field.aq_parent._v_relation_field_index) # XXX Default rendering with value...
relation_html_string = self.render_relation_link(field, value,
if value_list not in ((), [], None, ['']) and value_list == field.get_value('default') and field.get_value('allow_jump') == 1 : REQUEST)
if REQUEST.get('selection_name') is not None: if relation_html_string != '':
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>' \ html_string += '&nbsp;&nbsp;%s' % relation_html_string
% (field.get_value('jump_method'), field.id, field.aq_parent.id, REQUEST.get('selection_name'), REQUEST.get('selection_index'),portal_url_string) ####################################
else: # Update relation field index
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) REQUEST.set('_v_relation_field_index', relation_field_index + 1)
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 return html_string
def render_view(self, field, value): def render_view(self, field, value):
""" """
Render text field. Render read only field.
""" """
if field.get_value('allow_jump') == 0 : html_string = self.default_widget_rendering_instance.render_view(
return Widget.LinesTextAreaWidget.render_view(self, field, value) field, value)
REQUEST = get_request() REQUEST = get_request()
here = REQUEST['here'] relation_html_string = self.render_relation_link(field, value, REQUEST)
if relation_html_string != '':
html_string += '&nbsp;&nbsp;%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 = getToolByName(here, 'portal_url')
portal_url_string = portal_url() portal_url_string = portal_url()
portal_object = portal_url.getPortalObject()
# no modification made, we can display only a lines text area widget if sub_index is None:
html_string = Widget.LinesTextAreaWidget.render_view(self, field, value) sub_index_string = ''
if value not in ((), [], None, ''): 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: 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>' \ selection_name_html = '&selection_name=%s&selection_index=%s' % \
% (field.get_value('jump_method'), field.id, field.aq_parent.id, REQUEST.get('selection_name'), REQUEST.get('selection_index'),portal_url_string) (REQUEST.get('selection_name'), REQUEST.get('selection_index'))
else: else:
html_string += '&nbsp;&nbsp;<a href="%s?field_id=%s&form_id=%s"><img src="%s/images/jump.png"/></a>' \ selection_name_html = ''
% (field.get_value('jump_method'), field.id, field.aq_parent.id,portal_url_string) # 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 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,171 +412,221 @@ class MultiRelationEditor: ...@@ -255,171 +412,221 @@ 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_object_list.append( o.portal_catalog.getObject(uid) ) relation_uid_list.append(int(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):
class MultiRelationStringFieldValidator(Validator.LinesValidator, RelationField.RelationStringFieldValidator):
""" """
Validation includes lookup of relared instances Validation includes lookup of relared instances
""" """
message_names = Validator.LinesValidator.message_names + \ message_names = Validator.LinesValidator.message_names +\
RelationField.RelationStringFieldValidator.message_names ['relation_result_too_long', 'relation_result_ambiguous',
'relation_result_empty',]
# 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 # XXX Do we need to translate here ?
portal_selections = getToolByName(field, 'portal_selections') relation_result_too_long = "Too many documents were found."
portal_catalog = getToolByName(field, 'portal_catalog') relation_result_ambiguous = "Select appropriate document in the list."
relation_result_empty = "No such document was found."
# Get the current value # Relation field variable
value_list = Validator.LinesValidator.validate(self, field, key, REQUEST) editor = MultiRelationEditor
default_validator_instance = Validator.LinesValidatorInstance
# if type(value_list) == type(''): def _generateItemUidList(self, field, key, relation_uid_list, REQUEST=None):
# value_list = [value_list] """
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
# If the value is the same as the current field value, do nothing def _generateFieldValueList(self, field, key,
current_value_list = field.get_value('default') value_list, current_value_list):
if type(current_value_list) == type(''): """
Generate list of value, item_key
"""
item_value_list = []
if isinstance(current_value_list, StringType):
current_value_list = [current_value_list] 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') catalog_index = field.get_value('catalog_index')
relation_setter_id = field.get_value('relation_setter_id') portal_type_list = [x[0] for x in field.get_value('portal_type')]
portal_catalog = getToolByName(field, 'portal_catalog')
relation_field_id = 'relation_%s' % ( key ) ####################################
# we must know if user validate the form or click on the wheel button # Check list input
relation_uid_list = REQUEST.get(relation_field_id, None) ####################################
relation_field_sub_id = 'relation_%s_0' % ( key ) relation_field_id = field.generate_subfield_key("%s" % \
if checkSameKeys( value_list, current_value_list ) and (relation_uid_list is None) and (not REQUEST.has_key( relation_field_sub_id )): SUB_FIELD_ID, key=key)
# 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) relation_uid_list = REQUEST.get(relation_field_id, None)
if relation_uid_list != None: ####################################
# User clicked on the wheel
####################################
need_to_revalidate = 1
if relation_uid_list is not None:
need_to_revalidate = 0
relation_editor_list = [] relation_editor_list = []
for i in range( len(relation_uid_list) ): for relation_item_id, relation_uid, value in \
self._generateItemUidList(field, key, relation_uid_list,
relation_item_id = 'item_%s_%s' % ( key, i ) REQUEST=REQUEST):
relation_uid = relation_uid_list[i] found = 0
try:
related_object = portal_catalog.getObject(relation_uid) related_object = portal_catalog.getObject(relation_uid)
if related_object is not None:
display_text = str(related_object.getProperty(catalog_index)) 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: else:
display_text = 'Object has been deleted' display_text = 'Object has been deleted'
# Check ################################
REQUEST.set(relation_item_id, ( (display_text, relation_uid), )) # Modify if user modified his value
# Storing display_text as value is needded in this case ################################
relation_editor_list.append( (i, display_text, str(relation_uid), display_text) ) if (found == 1) and \
(value != display_text):
return MultiRelationEditor(field.id, base_category, portal_type, portal_type_item, catalog_index, relation_setter_id, relation_editor_list) relation_editor_list = None
# import pdb; pdb.set_trace()
need_to_revalidate = 1
else: 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 # 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')
field_value_list = self._generateFieldValueList(field, key, value_list,
current_value_list)
if len(field_value_list) != 0:
####################################
# Values were changed
####################################
relation_editor_list = [] relation_editor_list = []
raising_error_needed = 0 for relation_field_id, value, relation_item_id in field_value_list:
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 == '': if value == '':
####################################
# User want to delete this line
####################################
# Clean request if necessary # Clean request if necessary
if REQUEST.has_key( relation_field_id): if REQUEST.has_key(relation_field_id):
for subdict_name in ['form', 'other']: for subdict_name in ['form', 'other']:
subdict = getattr(REQUEST, subdict_name) subdict = getattr(REQUEST, subdict_name)
if subdict.has_key(relation_field_id): if subdict.has_key(relation_field_id):
subdict.pop(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: else:
# Got a true value relation_uid = REQUEST.get(relation_field_id, None)
# need_to_revalidate = 1
if relation_uid not in (None, ''): if relation_uid not in (None, ''):
# A value has been defined by the user in popup menu # need_to_revalidate = 0
if type(relation_uid) in (type([]), type(())): relation_uid = relation_uid[0] # found = 0
####################################
# User selected in a popup menu
####################################
if isinstance(relation_uid, (list, tuple)):
relation_uid = relation_uid[0]
try: try:
related_object = portal_catalog.getObject(relation_uid) related_object = portal_catalog.getObject(relation_uid)
except ValueError: except ValueError:
...@@ -427,84 +634,125 @@ class MultiRelationStringFieldValidator(Validator.LinesValidator, RelationField ...@@ -427,84 +634,125 @@ class MultiRelationStringFieldValidator(Validator.LinesValidator, RelationField
related_object = None 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:
##############################
# 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: else:
display_text = 'Object has been deleted' display_text = 'Object has been deleted'
# Check # ################################
REQUEST.set(relation_item_id, ( (display_text, relation_uid), )) # # Modify if user modified his value
relation_editor_list.append( (i, value, str(relation_uid), display_text) ) # ################################
# 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: else:
####################################
# User validate the form for this line
####################################
kw ={} kw ={}
kw[catalog_index] = value kw[catalog_index] = value
kw['portal_type'] = portal_type kw['portal_type'] = portal_type_list
kw['sort_on'] = catalog_index kw['sort_on'] = catalog_index
# Get the query results # Get the query results
relation_list = portal_catalog(**kw) relation_list = portal_catalog(**kw)
relation_uid_list = map(lambda x: x.uid, relation_list) relation_uid_list = [x.uid for x in relation_list]
localizer = getToolByName( field menu_item_list = []
, '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 len(relation_list) >= MAX_SELECT:
# If the length is long, raise an error # If the length is long, raise an error
# This parameter means we need listbox help # This parameter means we need listbox help
# XXX XXX XXX Do we need to delete it ?
REQUEST.set(relation_item_id, []) REQUEST.set(relation_item_id, [])
raising_error_needed = 1 raising_error_needed = 1
raising_error_value = 'relation_result_too_long' raising_error_value = 'relation_result_too_long'
elif len(relation_list) == 1: elif len(relation_list) == 1:
# If the length is 1, return uid # If the length is 1, return uid
relation_uid = relation_uid_list[0] relation_uid = relation_uid_list[0]
related_object = portal_catalog.getObject(relation_uid) 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'
# XXX XXX XXX
REQUEST.set(relation_item_id, ( (display_text, relation_uid), )) REQUEST.set(relation_item_id, ((display_text,
relation_editor_list.append( (0, value, 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: elif len(relation_list) == 0:
# If the length is 0, raise an error # If the length is 0, raise an error
if field.get_value('allow_creation') == 1 : if field.get_value('allow_creation') == 1 :
menu_item_list += new_object_menu_item_list # 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) REQUEST.set(relation_item_id, menu_item_list)
raising_error_needed = 1 raising_error_needed = 1
raising_error_value = 'relation_result_empty' raising_error_value = 'relation_result_empty'
else: else:
# If the length is short, raise an error # If the length is short, raise an error
# len(relation_list) < MAX_SELECT: # len(relation_list) < MAX_SELECT:
menu_item_list.extend([(
#menu_item_list += [('-', '')] x.getObject().getProperty(catalog_index),
menu_item_list += map(lambda x: (x.getObject().getProperty(catalog_index), x.uid), x.uid) for x in relation_list])
relation_list)
REQUEST.set(relation_item_id, menu_item_list) REQUEST.set(relation_item_id, menu_item_list)
raising_error_needed = 1 raising_error_needed = 1
raising_error_value = 'relation_result_ambiguous' raising_error_value = 'relation_result_ambiguous'
# validate MultiRelation field #####################################
# Validate MultiRelation field
#####################################
if raising_error_needed: if raising_error_needed:
# Raise error # Raise error
self.raise_error(raising_error_value, field) self.raise_error(raising_error_value, field)
return value_list return value_list
else: else:
# Can return editor # Can return editor
return MultiRelationEditor(field.id, base_category, portal_type, portal_type_item, catalog_index, relation_setter_id, relation_editor_list) 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() MultiRelationStringFieldWidgetInstance = MultiRelationStringFieldWidget()
MultiRelationStringFieldValidatorInstance = MultiRelationStringFieldValidator() MultiRelationStringFieldValidatorInstance = MultiRelationStringFieldValidator()
...@@ -518,8 +766,8 @@ class MultiRelationStringField(ZMIField): ...@@ -518,8 +766,8 @@ class MultiRelationStringField(ZMIField):
security.declareProtected('Access contents information', 'get_value') security.declareProtected('Access contents information', 'get_value')
def get_value(self, id, **kw): def get_value(self, id, **kw):
"""Get value for id. """
Get value for id.
Optionally pass keyword arguments that get passed to TALES Optionally pass keyword arguments that get passed to TALES
expression. expression.
""" """
......
############################################################################## ##############################################################################
# #
# 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,19 +32,18 @@ from Products.Formulator.Field import ZMIField ...@@ -31,19 +32,18 @@ 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(
class RelationStringFieldWidget(Widget.TextWidget, Widget.ListWidget): MultiRelationField.MultiRelationStringFieldWidget):
""" """
RelationStringField widget RelationStringField widget
Works like a string field but includes one buttons Works like a string field but includes one buttons
...@@ -51,406 +51,79 @@ class RelationStringFieldWidget(Widget.TextWidget, Widget.ListWidget): ...@@ -51,406 +51,79 @@ class RelationStringFieldWidget(Widget.TextWidget, Widget.ListWidget):
- creates object if not there - creates object if not there
""" """
property_names = Widget.TextWidget.property_names + \ property_names = Widget.TextWidget.property_names + \
['update_method', 'jump_method', 'allow_jump', 'base_category', MultiRelationField.MultiRelationStringFieldWidget.local_property_names
'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', default_widget_rendering_instance = Widget.TextWidgetInstance
title='Default Sort',
description=('The default sort keys and order'),
default=[],
required=0)
parameter_list = fields.ListTextAreaField('parameter_list', def _generateRenderValueList(self, field, key, value, REQUEST):
title="Parameter List", relation_field_id = field.generate_subfield_key(SUB_FIELD_ID, key=key)
description=( relation_item_key = field.generate_subfield_key(ITEM_ID, key=key)
"A list of paramters used for the portal_catalog."), relation_item_list = REQUEST.get(relation_item_key, [])
default=[], return [(Widget.TextWidgetInstance, relation_field_id,
required=0) relation_item_list, value, None)]
list_method = fields.MethodField('list_method', class RelationEditor(MultiRelationField.MultiRelationEditor):
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,
'_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 += '&nbsp;&nbsp;<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 += '&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
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 += '&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:
""" """
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, 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): def __call__(self, REQUEST):
if self.uid is not None: MultiRelationField.MultiRelationEditor.__call__(self, REQUEST)
# Decorate the request so that we can display value = REQUEST.get(self.field_id)
# the select item in a popup if value is not None:
relation_field_id = 'relation_%s' % self.field_id REQUEST.set(self.field_id, value[0])
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) allow_class(RelationEditor)
class RelationStringFieldValidator(Validator.StringValidator): class RelationStringFieldValidator(
MultiRelationField.MultiRelationStringFieldValidator):
""" """
Validation includes lookup of relared instances Validation includes lookup of relared instances
""" """
message_names = Validator.StringValidator.message_names +\ message_names = Validator.StringValidator.message_names + \
['relation_result_too_long', 'relation_result_ambiguous', MultiRelationField.MultiRelationStringFieldValidator.message_names
'relation_result_empty',] # Delete double in order to keep a usable ZMI...
# Need to keep order !
relation_result_too_long = "Too many documents were found." _v_dict = {}
relation_result_ambiguous = "Select appropriate document in the list." _v_message_name_list = []
relation_result_empty = "No such document was found." for message_name in message_names:
if not _v_dict.has_key(message_name):
def validate(self, field, key, REQUEST): _v_message_name_list.append(message_name)
relation_field_id = 'relation_%s' % key _v_dict[message_name] = 1
relation_item_id = 'item_%s' % key message_names = _v_message_name_list
portal_type = map(lambda x:x[0],field.get_value('portal_type'))
portal_type_item = field.get_value('portal_type') # Relation field variable
base_category = field.get_value( 'base_category') editor = RelationEditor
# If the value is different, build a query default_validator_instance = Validator.StringValidatorInstance
portal_selections = getToolByName(field, 'portal_selections')
portal_catalog = getToolByName(field, 'portal_catalog') def _generateItemUidList(self, field, key, relation_uid_list, REQUEST=None):
# Get the current value """
value = Validator.StringValidator.validate(self, field, key, REQUEST) Generate list of uid, item_key
# If the value is the same as the current field value, do nothing """
current_value = field.get_value('default') relation_item_id = field.generate_subfield_key(ITEM_ID,
# If a relation has been defined in a popup menu, use it key=key)
relation_uid = REQUEST.get(relation_field_id, None) if isinstance(relation_uid_list, (list, tuple)):
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: try:
related_object = portal_catalog.getObject(relation_uid) relation_uid_list = relation_uid_list[0]
except ValueError: except IndexError:
# Catch the error raised when the uid is a string # No object was selected
related_object = None return []
if related_object is not None: value = self.default_validator_instance.validate(field,
display_text = str(related_object.getProperty(catalog_index)) key, REQUEST)
else: return [(relation_item_id, relation_uid_list, value)]
display_text = 'Object has been deleted'
return RelationEditor(key, base_category, portal_type, relation_uid, def _generateFieldValueList(self, field, key,
portal_type_item, catalog_index, value, value_list, current_value_list):
relation_setter_id, container_getter_id, """
display_text) Generate list of value, item_key
"""
# We must be able to erase the relation if value_list == current_value_list:
if value == '': return []
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: else:
# If the length is long, raise an error relation_field_id = field.generate_subfield_key("%s" % \
SUB_FIELD_ID, key=key)
# If this error is raise, we don t want to create a new object... relation_item_key = field.generate_subfield_key(ITEM_ID, key=key)
#REQUEST.set(relation_item_id, menu_item_list) return [(relation_field_id, value_list, relation_item_key)]
self.raise_error('relation_result_too_long', field)
RelationStringFieldWidgetInstance = RelationStringFieldWidget() RelationStringFieldWidgetInstance = RelationStringFieldWidget()
RelationStringFieldValidatorInstance = RelationStringFieldValidator() RelationStringFieldValidatorInstance = RelationStringFieldValidator()
...@@ -464,8 +137,8 @@ class RelationStringField(ZMIField): ...@@ -464,8 +137,8 @@ class RelationStringField(ZMIField):
security.declareProtected('Access contents information', 'get_value') security.declareProtected('Access contents information', 'get_value')
def get_value(self, id, **kw): def get_value(self, id, **kw):
"""Get value for id. """
Get value for id.
Optionally pass keyword arguments that get passed to TALES Optionally pass keyword arguments that get passed to TALES
expression. expression.
""" """
......
...@@ -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 ):
...@@ -928,7 +929,6 @@ class SelectionTool( UniqueObject, SimpleItem ): ...@@ -928,7 +929,6 @@ 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)
...@@ -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, \
"Sorrry, Error, the calling object was not catalogued. " \
"Do not know how to do ?" "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)
relation_field_found = 0
for field in field_list:
try: try:
dumb = field.get_value('is_relation_field') 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: 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 pass
# relation_index += 1
else: else:
if index == relation_index: if index == relation_index:
relation_field_found = 1 relation_field_found = 1
break break
else: else:
relation_index += 1 relation_index += 1
# if getattr(field, 'is_relation_field', None):
# if index == relation_index:
# relation_field_found = 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'
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) 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 (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,12 +1013,16 @@ class SelectionTool( UniqueObject, SimpleItem ): ...@@ -1029,12 +1013,16 @@ 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
...@@ -1050,13 +1038,12 @@ class SelectionTool( UniqueObject, SimpleItem ): ...@@ -1050,13 +1038,12 @@ class SelectionTool( UniqueObject, SimpleItem ):
base_category = None base_category = None
kw = {} kw = {}
kw['object_uid'] = object_uid kw['object_uid'] = object_uid
kw['form_id'] = 'Base_viewRelatedObjectList' kw['form_id'] = redirect_form_id
kw['selection_name'] = 'Base_viewRelatedObjectList' kw['selection_name'] = selection_name
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'] = map(lambda x:x[0],field.get_value('portal_type')) kw['portal_type'] = [x[0] for x in 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:
...@@ -1066,74 +1053,21 @@ class SelectionTool( UniqueObject, SimpleItem ): ...@@ -1066,74 +1053,21 @@ class SelectionTool( UniqueObject, SimpleItem ):
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
"""
# 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 # Need to redirect, if we want listbox nextPage to work
kw['form_pickle'] = form_pickle kw['form_pickle'] = form_pickle
kw['form_signature'] = form_signature kw['form_signature'] = form_signature
kw['portal_status_message'] = portal_status_message kw['portal_status_message'] = portal_status_message
redirect_url = '%s/%s?%s' % ( o.absolute_url() redirect_url = '%s/%s?%s' % ( o.absolute_url()
, 'Base_viewRelatedObjectList' , redirect_form_id
, make_query(kw) , make_query(kw)
) )
REQUEST[ 'RESPONSE' ].redirect( redirect_url ) 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):
""" """
Generate viewSearchRelatedDocumentDialog0, Generate viewSearchRelatedDocumentDialog0,
...@@ -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(
method_count, form_id,
REQUEST=REQUEST, **kw)
else:
return self.viewSearchRelatedDocumentDialog( 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