From f00262fe4de3fcbf5d039d434fe1607b941b9198 Mon Sep 17 00:00:00 2001
From: Jean-Paul Smets <jp@nexedi.com>
Date: Tue, 25 Jan 2005 10:10:31 +0000
Subject: [PATCH] Added new method to provide some kind of auto completion and
 simplify the user interface

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@2282 20353a03-c40f-0410-a6d1-a30d3c3de9de
---
 product/ERP5Form/RelationField.py | 185 ++++++++++++++++++++++++++++--
 1 file changed, 177 insertions(+), 8 deletions(-)

diff --git a/product/ERP5Form/RelationField.py b/product/ERP5Form/RelationField.py
index 3689f2a443..0c3ad71836 100755
--- a/product/ERP5Form/RelationField.py
+++ b/product/ERP5Form/RelationField.py
@@ -32,10 +32,14 @@ from Products.Formulator.DummyField import fields
 from Products.ERP5Type.Utils import convertToUpperCase
 from Products.CMFCore.utils import getToolByName
 from Globals import get_request
+from Products.PythonScripts.Utility import allow_class
 
 from zLOG import LOG
+MAX_SELECT = 50 # Max. number of catalog result
+new_content_prefix = '_newContent_'
 
-class RelationStringFieldWidget(Widget.TextWidget):
+
+class RelationStringFieldWidget(Widget.TextWidget, Widget.ListWidget):
     """
         RelationStringField widget
 
@@ -48,7 +52,8 @@ class RelationStringFieldWidget(Widget.TextWidget):
     """
     property_names = Widget.TextWidget.property_names + \
       ['update_method', 'jump_method', 'base_category', 'portal_type', 'catalog_index',
-       'default_module', 'relation_setter_id', 'columns']
+       'default_module', 'relation_setter_id', 'columns',
+       'first_item', 'items', 'size', 'extra_item']
 
     update_method = fields.StringField('update_method',
                                title='Update Method',
@@ -99,6 +104,15 @@ class RelationStringFieldWidget(Widget.TextWidget):
                                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=(
@@ -109,22 +123,37 @@ class RelationStringFieldWidget(Widget.TextWidget):
     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_string = getToolByName(here, 'portal_url')()
+        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
-        html_string += '&nbsp;<input type="image" src="%s/images/exec16.png" value="update..." name="%s:method">' \
-            %  (portal_url_string,field.get_value('update_method'))
-        if value not in ('', None):
+        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(), field.aq_parent._v_relation_field_index)    
+        field.aq_parent._v_relation_field_index += 1 # Increase index                
+        if value not in ('', None) and not REQUEST.has_key(relation_item_id):
           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)
+              % (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):
@@ -141,11 +170,151 @@ class RelationStringFieldWidget(Widget.TextWidget):
             % (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
+    """
+
+    def __init__(self, field_id, base_category, portal_type, uid, portal_type_item, 
+                       key, value, relation_setter_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.display_text = display_text
+      
+    def __call__(self, REQUEST):
+      if self.uid is not None:      
+        # Decorate the request so that we can display
+        # the select item in a popup
+        relation_field_id = 'relation_%s' % self.field_id      
+        relation_item_id = 'item_%s' % self.field_id
+        REQUEST.set(relation_item_id, ((self.display_text, self.uid),))
+        REQUEST.set(relation_field_id, self.uid)
+        REQUEST.set(self.field_id[len('field_'):], self.value) # XXX Dirty
+      else:
+        # Make sure no default value appears
+        REQUEST.set(key, 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):]
+          portal_module = None
+          for p_item in self.portal_type_item:
+            if p_item[0] == portal_type:
+              portal_module = p_item[1]
+          if portal_module is not None:              
+            portal_module_object = getattr(o.getPortalObject(), portal_module)
+            kw ={}
+            kw[self.key] = self.value
+            kw['portal_type'] = portal_type
+            new_object = portal_module_object.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:
+          o._setValueUids(self.base_category, (), portal_type=self.portal_type)      
+          o._setValueUids(self.base_category, (int(self.uid),), portal_type=self.portal_type)      
+
+allow_class(RelationEditor)
+
+class RelationStringFieldValidator(Validator.StringValidator):   
+    """
+        Validation includes lookup of relared instances
+    """    
+    
+    message_names = Validator.StringValidator.message_names +\
+                    ['relation_result_too_long', 'relation_result_ambiguous', 'relation_result_empty',]
+
+    relation_result_too_long = "Too many documents were found."
+    relation_result_ambiguous = "Select appropriate document in the list."
+    relation_result_empty = "No such document was found."
+                          
+    def validate(self, field, key, REQUEST):
+      relation_field_id = 'relation_%s' % key      
+      relation_item_id = 'item_%s' % key
+      portal_type = map(lambda x:x[0],field.get_value('portal_type'))
+      portal_type_item = field.get_value('portal_type')
+      base_category = field.get_value( 'base_category')
+      # If the value is different, build a query
+      portal_selections = getToolByName(field, 'portal_selections')
+      portal_catalog = getToolByName(field, 'portal_catalog')      
+      # Get the current value
+      value = Validator.StringValidator.validate(self, field, key, REQUEST)
+      # If the value is the same as the current field value, do nothing
+      current_value = field.get_value('default')
+      if value == current_value:
+        return RelationEditor(None, None, None, None, None, None, None, None, None) # Will be interpreted by Base_edit as "do nothing"
+      # 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')
+      relation_setter_id = field.get_value('relation_setter_id')
+      if relation_uid not in (None, ''):
+        # A value has been defined by the user
+        if type(relation_uid) in (type([]), type(())): relation_uid = relation_uid[0]
+        related_object = portal_catalog.getObject(relation_uid)
+        if related_object is not None:
+          display_text = str(related_object.getProperty(catalog_index))
+        else:
+          display_text = 'Object has been deleted'        
+        return RelationEditor(key, base_category, portal_type, relation_uid, 
+                              portal_type_item, catalog_index, value, relation_setter_id, display_text)
+      kw ={}
+      kw[catalog_index] = value
+      kw['portal_type'] = portal_type
+      # Get the query results
+      relation_list = portal_catalog(**kw)
+      relation_uid_list = map(lambda x: x.uid, relation_list)
+      # Prepare a menu
+      menu_item_list = [('', '')]
+      for p in portal_type:
+        menu_item_list += [('New %s' % p, '%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, display_text)
+      # If the length is 0, raise an error
+      if len(relation_list) == 0:
+        REQUEST.set(relation_item_id, menu_item_list)
+        self.raise_error('relation_result_empty', field)
+      # If the length is short, raise an error
+      if 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)
+        self.raise_error('relation_result_ambiguous', field)
+      # If the length is long, raise an error
+      REQUEST.set(relation_item_id, menu_item_list)
+      self.raise_error('relation_result_too_long', field)    
+        
 RelationStringFieldWidgetInstance = RelationStringFieldWidget()
-RelationStringFieldValidatorInstance = Validator.StringValidator()
+RelationStringFieldValidatorInstance = RelationStringFieldValidator()
 
 class RelationStringField(ZMIField):
     meta_type = "RelationStringField"
+    is_relation_field = 1
 
     widget = RelationStringFieldWidgetInstance
     validator = RelationStringFieldValidatorInstance
-- 
2.30.9