MultiRelationField.py 35.5 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2
##############################################################################
#
3 4
# Copyright (c) 2002, 2004, 2006 Nexedi SARL and Contributors. 
#                                All Rights Reserved.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5
#                    Jean-Paul Smets-Solanes <jp@nexedi.com>
6
#                    Romain Courteaud <romain@nexedi.com>
Jean-Paul Smets's avatar
Jean-Paul Smets committed
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################

from Products.Formulator import Widget, Validator
from Products.Formulator.Field import ZMIField
from Products.ERP5Type.Utils import convertToUpperCase
34
from Products.CMFCore.utils import getToolByName
35
from Products.PythonScripts.Utility import allow_class
36
from Products.ERP5Type.Message import translateString
37
from AccessControl import ClassSecurityInfo
38
from types import StringType
39
from zLOG import LOG
40
from Products.Formulator.DummyField import fields
41
from Products.ERP5Type.Globals import get_request
42 43
from AccessControl import Unauthorized
from AccessControl import getSecurityManager
44 45 46 47 48 49 50

# Max. number of catalog result
MAX_SELECT = 30
NEW_CONTENT_PREFIX = '_newContent_'
# Key for sub listfield
SUB_FIELD_ID = 'relation'
ITEM_ID = 'item'
Romain Courteaud's avatar
Romain Courteaud committed
51
NO_VALUE = '??? (No Value)'
52

53
class MultiRelationStringFieldWidget(Widget.LinesTextAreaWidget,
54 55 56 57 58 59 60 61 62 63 64
                                     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',
65 66 67 68
                          'relation_setter_id', 'relation_form_id', 'columns', 
                          'sort', 'parameter_list','list_method', 
                          'first_item', 'items', 'proxy_listbox_ids', 
                          'size', 'extra_item',
69
                          ]
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140

  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)

141 142 143 144 145 146 147
  relation_form_id = fields.StringField('relation_form_id',
                             title='Relation Form',
                             description=(
      "Form to display relation choices"),
                             default="",
                             required=0)

148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
  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)

184 185 186 187 188 189
  proxy_listbox_ids = fields.ListTextAreaField('proxy_listbox_ids',
                               title='Proxy Listbox IDs',
                               description=('A list of listbox that can be used as proxy'),
                               default='',
                               required=0)

190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
  # 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")
Romain Courteaud's avatar
Romain Courteaud committed
212 213 214 215 216 217
    else:
      # We get a list
      # rather than displaying nothing, display a marker when the
      # property is not set
      # XXX Translate ?
      value_list = [(x or NO_VALUE) for x in value_list]
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
    # 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 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

246
  def render(self, field, key, value, REQUEST, render_prefix=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
247
    """
248
    Render text input field.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
249
    """
250
    html_string = ''
251
    relation_field_index = REQUEST.get('_v_relation_field_index', 0)
252 253 254 255 256 257 258 259 260 261 262 263
    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:
Jérome Perrin's avatar
Jérome Perrin committed
264 265 266 267 268 269 270 271 272
        ####################################
        # Render wheel
        ####################################
        sub_html_string += self.render_wheel(
                  field, value_instance, REQUEST,
                  relation_index=relation_field_index,
                  sub_index=sub_index)

        if relation_item_list:
273 274 275 276 277 278 279 280 281
          ####################################
          # Render listfield
          ####################################

          REQUEST['relation_item_list'] = relation_item_list
          sub_html_string += '&nbsp;%s&nbsp;' % \
                                Widget.ListWidgetInstance.render(
                                field, relation_field_id, None, REQUEST)
          REQUEST['relation_item_list'] = None
282

Jérome Perrin's avatar
Jérome Perrin committed
283
      html_string_list.append(sub_html_string)
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
    ####################################
    # Generate html
    ####################################
    html_string = '<br/>'.join(html_string_list)
    ####################################
    # Render jump
    ####################################
    if (value == field.get_value('default')):
      # XXX Default rendering with value...
      relation_html_string = self.render_relation_link(field, value, 
                                                       REQUEST)
      if relation_html_string != '':
        html_string += '&nbsp;&nbsp;%s' % relation_html_string
    ####################################
    # Update relation field index
    ####################################
    REQUEST.set('_v_relation_field_index', relation_field_index + 1) 
    return html_string

303
  def render_view(self, field, value, REQUEST=None, render_prefix=None):
304 305 306
    """
    Render read only field.
    """
307 308 309 310 311 312 313 314 315 316 317 318 319 320
    html_string = ''
    here = REQUEST['here']
    portal_url = getToolByName(here, 'portal_url')
    portal_url_string = portal_url()
    if (value not in ((), [], None, '')) and \
        field.get_value('allow_jump'):
      string_list = []
      base_category = field.get_value('base_category')
      portal_type = map(lambda x:x[0],field.get_value('portal_type'))
      kw = {}
      for k, v in field.get_value('parameter_list') :
        kw[k] = v
      accessor_name = 'get%sValueList' % ''.join([part.capitalize() for part in base_category.split('_')])
      jump_reference_list = getattr(here, accessor_name)(portal_type=portal_type, filter=kw)
321 322 323 324
      for jump_reference in jump_reference_list:
        string_list.append('<a href="%s">%s</a>' % \
                (jump_reference.absolute_url(),
                  jump_reference.getTitle()))
325 326 327 328 329 330 331 332 333
      html_string = '<br />'.join(string_list)
    else:
      html_string = self.default_widget_rendering_instance.render_view(field,
          value, REQUEST=REQUEST)
      if REQUEST is None:
        REQUEST = get_request()
      relation_html_string = self.render_relation_link(field, value, REQUEST)
      if relation_html_string != '':
        html_string += '&nbsp;&nbsp;%s' % relation_html_string
334 335 336
    return html_string

  def render_wheel(self, field, value, REQUEST, relation_index=0,
337
                   sub_index=None, render_prefix=None):
338 339 340 341 342 343
    """
    Render wheel used to display a listbox
    """
    here = REQUEST['here']
    portal_url = getToolByName(here, 'portal_url')
    portal_url_string = portal_url()
344
    portal_selections_url_string = here.portal_url.getRelativeContentURL(here.portal_selections)
345 346 347 348 349 350
    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..." ' \
351
         'name="%s/viewSearchRelatedDocumentDialog%s%s' \
352
         ':method"/>' % \
353
           (portal_url_string, portal_selections_url_string,
354 355
           relation_index, sub_index_string)

356
  def render_relation_link(self, field, value, REQUEST, render_prefix=None):
357 358 359 360 361 362 363 364
    """
    Render link to the related object.
    """
    html_string = ''
    here = REQUEST['here']
    portal_url = getToolByName(here, 'portal_url')
    portal_url_string = portal_url()
    if (value not in ((), [], None, '')) and \
365
        field.get_value('allow_jump'):
366 367
      # Keep the selection name in the URL
      if REQUEST.get('selection_name') is not None:
368
        selection_name_html = '&amp;selection_name=%s&amp;selection_index=%s' % \
369 370 371
              (REQUEST.get('selection_name'), REQUEST.get('selection_index'))
      else:
        selection_name_html = ''
372
      if REQUEST.get('ignore_layout') is not None:
373
        selection_name_html += '&amp;ignore_layout:int=%s' % int(REQUEST.get('ignore_layout', 0))
374
      # Generate plan link
375
      html_string += '<a href="%s/%s?field_id=%s&amp;form_id=%s%s">' \
376
                       '<img src="%s/images/jump.png" alt="jump" />' \
377 378 379 380 381 382 383
                     '</a>' % \
                (here.absolute_url(), 
                 field.get_value('jump_method'), 
                 field.id, field.aq_parent.id,
                 selection_name_html,
                 portal_url_string)
    return html_string
Jean-Paul Smets's avatar
Jean-Paul Smets committed
384

385 386 387 388
class MultiRelationEditor:
    """
      A class holding all values required to update a relation
    """
389 390 391 392
    def __init__(self, field_id, base_category, 
                 portal_type_list, 
                 portal_type_item, key, relation_setter_id, 
                 relation_editor_list):
393 394
      self.field_id = field_id
      self.base_category = base_category
395
      self.portal_type_list = portal_type_list
396 397 398 399 400 401 402 403 404
      self.portal_type_item = portal_type_item
      self.key = key
      self.relation_setter_id = relation_setter_id
      self.relation_editor_list = relation_editor_list

    def __call__(self, REQUEST):
      if self.relation_editor_list != None:
        value_list = []

405 406
        for value, uid, display_text, relation_key, item_key in \
                               self.relation_editor_list:
407
          value_list.append(display_text)
408 409 410
          if uid is not None:
            # Decorate the request so that we can display
            # the select item in a popup
411 412 413
            # XXX To be unified
            relation_field_id = relation_key
            relation_item_id = item_key
414 415 416 417 418
            REQUEST.set(relation_item_id, ((display_text, uid),))
        REQUEST.set(self.field_id, value_list) # XXX Dirty
      else:
        # Make sure no default value appears
        REQUEST.set(self.field_id, None) # XXX Dirty
419

420
    def view(self):
421 422 423
      return self.__dict__

    def edit(self, o):
Nicolas Delaby's avatar
typo  
Nicolas Delaby committed
424
      if self.relation_editor_list is not None:
Nicolas Delaby's avatar
Nicolas Delaby committed
425
        portal = o.getPortalObject()
426

427
        relation_object_list = []
428 429
        for value, uid, display_text, relation_key, item_key in \
                               self.relation_editor_list:
430
          if uid is not None:
431 432
            if isinstance(uid, StringType) and \
               uid.startswith(NEW_CONTENT_PREFIX):
433
              # Create a new content
434
              portal_type = uid[len(NEW_CONTENT_PREFIX):]
435 436 437
              portal_module = None
              for p_item in self.portal_type_item:
                if p_item[0] == portal_type:
Nicolas Delaby's avatar
Nicolas Delaby committed
438
                  portal_module = portal.getDefaultModuleId(p_item[0])
439
              if portal_module is not None:
Nicolas Delaby's avatar
Nicolas Delaby committed
440
                portal_module_object = getattr(portal, portal_module)
441
                kw ={}
442
                kw[self.key] = value.replace('%', '')
443 444
                kw['portal_type'] = portal_type
                new_object = portal_module_object.newContent(**kw)
445
                relation_object_list.append(new_object)
446
              else:
447 448
                raise 
            else:
Nicolas Delaby's avatar
Nicolas Delaby committed
449
              relation_object_list.append(portal.portal_catalog.getObject(uid))
450

451
        # Edit relation
452 453
        if self.relation_setter_id:
          relation_setter = getattr(o, self.relation_setter_id)
454
          relation_setter((), portal_type=self.portal_type_list)
455 456
          relation_setter(relation_object_list,
                          portal_type=self.portal_type_list)
457
        else:
458
          # we could call a generic method which create the setter method name
459 460 461
          if len(relation_object_list) == 1:
            set_method_name = '_set%sValue' % \
                         convertToUpperCase(self.base_category)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
462
            getattr(o, set_method_name)(relation_object_list[0],
463 464
                                        portal_type=self.portal_type_list,
                                        checked_permission='View')
465 466 467
          else:
            set_method_name = '_set%sValueList' % \
                         convertToUpperCase(self.base_category)
468
            getattr(o, set_method_name)(relation_object_list,
469 470
                                        portal_type=self.portal_type_list,
                                        checked_permission='View')
471 472 473

allow_class(MultiRelationEditor)

474 475 476 477 478 479 480
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',]
481

482 483 484
  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."
485

486 487 488
  # Relation field variable
  editor = MultiRelationEditor
  default_validator_instance = Validator.LinesValidatorInstance
489

490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512
  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
513 514
    if value_list != current_value_list: # Changes in the order or in the number of occurences
                                         # must be taken into account
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541
      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')
542

543 544 545 546 547 548
    ####################################
    # 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)
549

550 551 552 553
    ####################################
    # User clicked on the wheel
    ####################################
    need_to_revalidate = 1
554
    if relation_uid_list not in (None, ''):
555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572
      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):]
573
            translated_portal_type = translateString(portal_type)
574
            # XXX Replace New by Add
575 576
            message = translateString('New ${portal_type}',
                                      mapping={'portal_type':translated_portal_type})
577 578 579
            display_text = message
          else:
            display_text = 'Object has been deleted'
580

581 582 583
        ################################
        # Modify if user modified his value
        ################################
584 585 586 587 588 589 590
        # XXX Does not work when user select a value in a ListField
#         if (found == 1) and \
#            (value != display_text):
#           relation_editor_list = None
#           need_to_revalidate = 1
#           REQUEST.set(relation_field_id, None)
#           break
591 592 593 594 595 596 597 598 599 600
        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
    ####################################
Aurel's avatar
Aurel committed
601
    if need_to_revalidate == 1:
602 603 604 605 606 607
#     else:
      ####################################
      # Check the default field
      ####################################
      value_list = self.default_validator_instance.validate(field, 
                                                       key, REQUEST)
608 609
      # If the value is the same as the current field value, do nothing
      current_value_list = field.get_value('default')
610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653
      field_value_list = self._generateFieldValueList(field, key, value_list,
                                                    current_value_list)
      if len(field_value_list) != 0:
        ####################################
        # Values were changed
        ####################################
        relation_editor_list = []
        for relation_field_id, value, relation_item_id in field_value_list:
          if value == '':
            ####################################
            # User want to delete this line
            ####################################
            # Clean request if necessary
            if REQUEST.has_key(relation_field_id):
              for subdict_name in ['form', 'other']:
                subdict = getattr(REQUEST, subdict_name)
                if subdict.has_key(relation_field_id):
                  subdict.pop(relation_field_id)
            display_text = 'Delete the relation'
            relation_editor_list.append((value, None, 
                                     display_text, None, None))
            # XXX RelationField implementation
#         # We must be able to erase the relation
#         display_text = 'Delete the relation'
#         # Will be interpreted by Base_edit as "delete relation" 
#         # (with no uid and value = '')
#         relation_editor_list = [(value, None, 
#                                      display_text, None, None)]
          else:
            relation_uid = REQUEST.get(relation_field_id, None)
#             need_to_revalidate = 1
            if relation_uid not in (None, ''):
#               need_to_revalidate = 0
#               found = 0
              ####################################
              # User selected in a popup menu
              ####################################
              if isinstance(relation_uid, (list, tuple)):
                relation_uid = relation_uid[0]
              try:
                related_object = portal_catalog.getObject(relation_uid)
              except ValueError:
                # Catch the exception raised when the uid is a string
                related_object = None
Romain Courteaud's avatar
Romain Courteaud committed
654 655
              if related_object is not None:
                display_text = str(related_object.getProperty(catalog_index))
656
#                 found = 1
Romain Courteaud's avatar
Romain Courteaud committed
657
              else:
658 659 660 661 662 663 664 665 666 667
                ##############################
                # 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):]
668 669 670
                  translated_portal_type = translateString(portal_type)
                  message = translateString('New ${portal_type}',
                                            mapping={'portal_type':translated_portal_type})
671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691
                  display_text = message
                else:
                  display_text = 'Object has been deleted'
#               ################################
#               # Modify if user modified his value
#               ################################
#               if (found == 1) and \
#                  (value != display_text):
#                 REQUEST.set(relation_field_id, None)
#                 need_to_revalidate = 1
#               else:
#                 # Check
#                 REQUEST.set(relation_item_id, ((display_text, relation_uid),))
#                 relation_editor_list.append((value, str(relation_uid), 
#                                             display_text, relation_field_id,
#                                             relation_item_id))
              REQUEST.set(relation_item_id, ((display_text, relation_uid),))
              relation_editor_list.append((value, str(relation_uid), 
                                          display_text, relation_field_id,
                                          relation_item_id))
#             if need_to_revalidate == 1:
692
            else:
693 694 695 696 697 698 699
              ####################################
              # User validate the form for this line
              ####################################
              kw ={}
              kw[catalog_index] = value
              kw['portal_type'] = portal_type_list
              kw['sort_on'] = catalog_index
700 701 702 703
              parameter_list = field.get_value('parameter_list')
              if len(parameter_list) > 0:
                for k,v in parameter_list:
                  kw[k] = v
704 705 706 707 708 709 710 711 712 713 714 715 716
              # Get the query results
              relation_list = portal_catalog(**kw)
              relation_uid_list = [x.uid for x in relation_list]
              menu_item_list = []
              if len(relation_list) >= MAX_SELECT:
                # If the length is long, raise an error
                # This parameter means we need listbox help
                # XXX XXX XXX Do we need to delete it ?
                REQUEST.set(relation_item_id, [])
                raising_error_needed = 1
                raising_error_value = 'relation_result_too_long'
              elif len(relation_list) == 1:
                # If the length is 1, return uid
717 718
                relation_uid = relation_uid_list[0]
                related_object = relation_list[0].getObject()
719 720
                if related_object is not None:
                  display_text = str(related_object.getProperty(catalog_index))
721 722 723
                  # Modify the value, in order to let the user 
                  # modify it later...
                  value = display_text
724
                else:
725
                  display_text = 'Object has been deleted'
726 727 728 729 730 731 732 733 734
                # XXX XXX XXX
                REQUEST.set(relation_item_id, ((display_text, 
                                                relation_uid),))
                relation_editor_list.append((value, relation_uid, 
                                             display_text, None,
                                             relation_item_id))
#                 relation_editor_list.append((0, value, relation_uid, 
#                                              display_text, None, None))
              elif len(relation_list) == 0:
735 736
                # Add blank line
                menu_item_list.append(('', ''))
737 738
                # If the length is 0, raise an error
                if field.get_value('allow_creation') == 1 :
739 740
                  user = getSecurityManager().getUser()
                  getDefaultModule = field.getDefaultModule
741 742
                  # XXX
                  for portal_type in portal_type_list:
743 744 745 746 747 748
                    try:
                      module = getDefaultModule(portal_type)
                    except ValueError:
                      pass
                    else:
                      if portal_type in module.getVisibleAllowedContentTypeList():
749 750 751
                        translated_portal_type = translateString(portal_type)
                        message = translateString('Add ${portal_type}',
                                                  mapping={'portal_type':translated_portal_type})
752 753 754
                        menu_item_list.append((message, 
                                               '%s%s' % (NEW_CONTENT_PREFIX, 
                                                         portal_type)))
755 756 757
                REQUEST.set(relation_item_id, menu_item_list)
                raising_error_needed = 1
                raising_error_value = 'relation_result_empty'
758
              else:
759 760 761 762 763
                # If the length is short, raise an error
                # len(relation_list) < MAX_SELECT:
                menu_item_list.extend([(
                                  x.getObject().getProperty(catalog_index),
                                  x.uid) for x in relation_list])
764 765
                # Add blank line
                menu_item_list.append(('', ''))
766 767 768 769 770 771
                REQUEST.set(relation_item_id, menu_item_list)
                raising_error_needed = 1
                raising_error_value = 'relation_result_ambiguous'

    ##################################### 
    # Validate MultiRelation field
772
    #####################################
773 774 775 776 777 778 779 780 781 782 783 784 785 786
    if raising_error_needed:
      # Raise error
      self.raise_error(raising_error_value, field)
      return value_list
    else:
      # Can return editor
      base_category = field.get_value('base_category')
      portal_type_item = field.get_value('portal_type')
      relation_setter_id = field.get_value('relation_setter_id')
      return self.editor(field.id, 
                         base_category,
                         portal_type_list, 
                         portal_type_item, catalog_index, 
                         relation_setter_id, relation_editor_list)
787

Jean-Paul Smets's avatar
Jean-Paul Smets committed
788
MultiRelationStringFieldWidgetInstance = MultiRelationStringFieldWidget()
789
MultiRelationStringFieldValidatorInstance = MultiRelationStringFieldValidator()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
790 791

class MultiRelationStringField(ZMIField):
792 793
  meta_type = "MultiRelationStringField"
  security = ClassSecurityInfo()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
794

795 796
  widget = MultiRelationStringFieldWidgetInstance
  validator = MultiRelationStringFieldValidatorInstance
Jean-Paul Smets's avatar
Jean-Paul Smets committed
797

798 799
  security.declareProtected('Access contents information', 'get_orig_value')
  def get_orig_value(self, id):
800
    """
801
    Get value for id; don't do any override calculation.
802 803 804 805
    """
    if id in ('is_relation_field', 'is_multi_relation_field'):
      result = 1
    else:
806
      result = ZMIField.get_orig_value(self, id)
807
    return result
808 809 810 811 812 813 814 815 816 817 818 819

  security.declareProtected('Access contents information', 'get_value')
  def get_value(self, id, REQUEST=None, **kw):
    """Get value for id.

    Optionally pass keyword arguments that get passed to TALES
    expression.
    """
    if (id == 'items') and (REQUEST is not None):
      # relation_item_list is not editable for the RelationField
      result = REQUEST.get('relation_item_list', None)
    else:
820
      result = ZMIField.get_value(self, id, REQUEST=REQUEST, **kw)
821
    return result
822 823 824 825

# Register get_value
from Products.ERP5Form.ProxyField import registerOriginalGetValueClassAndArgument
registerOriginalGetValueClassAndArgument(MultiRelationStringField, 'items')