ParallelListField.py 10.6 KB
Newer Older
Romain Courteaud's avatar
Romain Courteaud committed
1 2
##############################################################################
#
3
# Copyright (c) 2005, 2006 Nexedi SARL and Contributors. All Rights Reserved.
Romain Courteaud's avatar
Romain Courteaud committed
4 5 6 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 34 35 36 37 38
#                    Romain Courteaud <romain@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# 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.Formulator import StandardFields 
from Products.Formulator.DummyField import fields
from Products.PythonScripts.Utility import allow_class

from zLOG import LOG
from AccessControl import ClassSecurityInfo
from Products.Formulator.Errors import ValidationError

39 40 41 42
# Field is is not used in keyword in order to be compatible with Proxyfield
KEYWORD = '_v_plf_%s'
MARKER = []

43 44
class ParallelListWidget(Widget.MultiListWidget,
                         Widget.ListWidget):
Romain Courteaud's avatar
Romain Courteaud committed
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
    """
      Make the MultilistField more usable for the user.

      ParallelListWidget display a list of (Multi)ListField.
      Each can be required.

      Separation of items list is made with a Hash Script, which take 
      the items list in input, and return a list of dictionnaries.

      Each dictionnary describes a (Multi)Listfield.
      The keys are:
        - key: 
            default: default
        - required: {1, 0}  
            default: 0
        - field_type: {ListField, MultiListField}
            default: MultiListField
        - item_list: [(display, value), ...]
            default: []
        - value: 
            default: []
        - is_right_display: {1, 0}
            default: 0
    """

    property_names = Widget.MultiListWidget.property_names + \
71
                     Widget.ListWidget.property_names + \
Romain Courteaud's avatar
Romain Courteaud committed
72 73 74 75 76 77 78 79
      ['hash_script_id']

    hash_script_id = fields.StringField('hash_script_id',
                               title='Hash script',
                               description=(
        "The method to call to hash items list."),
                               required=0)

80 81 82 83 84 85 86 87 88 89 90
    # 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

91 92 93 94 95 96 97
    def __init__(self):
      """
      Generate some subwidget used for rendering.
      """
      self.sub_widget = {
        'ListField': Widget.ListWidgetInstance,
        'MultiListField': Widget.MultiListWidgetInstance,
Romain Courteaud's avatar
Romain Courteaud committed
98 99
      }

100
    def render(self, field, key, value, REQUEST):
101
      hash_list = generateSubForm(field, value, REQUEST)
102
      # Call render on each sub field
Romain Courteaud's avatar
Romain Courteaud committed
103 104
      sub_field_render_list = []
      for sub_field_property_dict in hash_list:
105 106
        sub_field_render_list.append(self.render_sub_field(
                          field, key,
107
                          sub_field_property_dict['value'], REQUEST,
108
                          sub_field_property_dict))
109
      # Aggregate all renders
110 111
      html_string = field.get_value('view_separator').\
                                join(sub_field_render_list)
Romain Courteaud's avatar
Romain Courteaud committed
112 113
      return html_string

114
    def render_htmlgrid(self, field, key, value, REQUEST):
115
      hash_list = generateSubForm(field, value, REQUEST)
116 117 118
      # Call render on each sub field
      sub_field_render_list = []
      for sub_field_property_dict in hash_list:
119 120 121 122 123 124
        sub_field_render_list.append((
                          sub_field_property_dict['title'],
                          self.render_sub_field(
                            field, key,
                            sub_field_property_dict['value'], REQUEST,
                            sub_field_property_dict)))
125 126
      return sub_field_render_list

127 128 129 130 131
    def render_sub_field(self, field, key, value, REQUEST,
                        sub_field_property_dict):
      """
      Render dynamically a subfield
      """
132 133 134 135 136
      for parameter in ('title', 'required', 'size'):
        REQUEST.set(KEYWORD % parameter, sub_field_property_dict[parameter])
      REQUEST.set(KEYWORD % 'default', "")
      REQUEST.set(KEYWORD % 'first_item', 0)
      REQUEST.set(KEYWORD % 'items', sub_field_property_dict['item_list'])
137
      if sub_field_property_dict.get('editable', 1):
138
        result = self.sub_widget[sub_field_property_dict['field_type']].render(
139 140 141 142 143 144
                field,
                field.generate_subfield_key(sub_field_property_dict['key'],
                                            key=key),
                sub_field_property_dict['value'],
                REQUEST)
      else:
145
        result = self.sub_widget[sub_field_property_dict['field_type']].render_view(
146 147 148
                field,
                sub_field_property_dict['value'],
                )
149 150 151 152 153 154
      for parameter in ('title', 'required', 'size', 'default', 'first_item',
                        'items'):
        # As it doesn't seem possible to delete value in the REQUEST,
        # use a marker
        REQUEST.set(KEYWORD % parameter, MARKER)
      return result
155

Romain Courteaud's avatar
Romain Courteaud committed
156 157 158 159
class ParallelListValidator(Validator.MultiSelectionValidator):

  property_names = Validator.MultiSelectionValidator.property_names 

160 161 162 163 164 165 166 167 168
  def __init__(self):
    """
    Generate some subvalidator used for rendering.
    """
    self.sub_validator = {
      'ListField': Validator.SelectionValidatorInstance,
      'MultiListField': Validator.MultiSelectionValidatorInstance,
    }

Romain Courteaud's avatar
Romain Courteaud committed
169 170
  def validate(self, field, key, REQUEST):    

171
    result_list = []
172
    hash_list = generateSubForm(field, field.get_value('default'), REQUEST)
Romain Courteaud's avatar
Romain Courteaud committed
173
    is_sub_field_required = 0
174
    for sub_field_property_dict in hash_list:
Romain Courteaud's avatar
Romain Courteaud committed
175
      try:
176 177 178 179 180 181 182 183
        sub_result_list = self.validate_sub_field(
                                  field,
                                  field.generate_subfield_key(
                                      sub_field_property_dict['key'], 
                                      validation=1, key=key),
                                  REQUEST,
                                  sub_field_property_dict)
        if not isinstance(sub_result_list, (list, tuple)):
Romain Courteaud's avatar
Romain Courteaud committed
184 185 186 187 188 189
          sub_result_list = [sub_result_list]
        else:
          sub_result_list = list(sub_result_list)
        result_list.extend(sub_result_list)
      except ValidationError:
        is_sub_field_required = 1
190 191
    
    result_list = [x for x in result_list if x!='']
Romain Courteaud's avatar
Romain Courteaud committed
192 193 194 195 196 197 198 199
    if result_list == []:
      if field.get_value('required'):
        self.raise_error('required_not_found', field)
    else:
      if is_sub_field_required:
        self.raise_error('required_not_found', field)
    return result_list

200 201 202 203
  def validate_sub_field(self, field, id, REQUEST, sub_field_property_dict):
    """
    Validates a subfield (as part of field validation).
    """
204 205 206 207 208
    for parameter in ('title', 'required', 'size'):
      REQUEST.set(KEYWORD % parameter, sub_field_property_dict[parameter])
    REQUEST.set(KEYWORD % 'default', "")
    REQUEST.set(KEYWORD % 'items', sub_field_property_dict['item_list'])
    result = self.sub_validator[sub_field_property_dict['field_type']].validate(
209
        field, id, REQUEST)
210 211 212 213 214 215
    for parameter in ('title', 'required', 'size', 'default', 'first_item',
                      'items'):
      # As it doesn't seem possible to delete value in the REQUEST,
      # use a marker
      REQUEST.set(KEYWORD % parameter, MARKER)
    return result
216

Romain Courteaud's avatar
Romain Courteaud committed
217 218 219 220
ParallelListWidgetInstance = ParallelListWidget()
ParallelListFieldValidatorInstance = ParallelListValidator()

class ParallelListField(ZMIField):
221 222
  security = ClassSecurityInfo()
  meta_type = "ParallelListField"
Romain Courteaud's avatar
Romain Courteaud committed
223

224 225
  widget = ParallelListWidgetInstance
  validator = ParallelListFieldValidatorInstance 
Romain Courteaud's avatar
Romain Courteaud committed
226

227
  security.declareProtected('Access contents information', 'get_value')
228
  def get_value(self, id, REQUEST=None, **kw):
229 230 231 232 233
    """
    Get value for id.
    Optionally pass keyword arguments that get passed to TALES
    expression.
    """
234 235
    result = MARKER
    key = KEYWORD % id
236 237 238
    if (REQUEST is not None) and \
       (REQUEST.has_key(key)):
      result = REQUEST.get(key)
239
    if result is MARKER:
240
      result = ZMIField.get_value(self, id, REQUEST=REQUEST, **kw)
241
    return result
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259

def generateSubForm(self, value, REQUEST):
  item_list = [x for x in self.get_value('items') \
               if x not in (('',''), ['',''])]

  value_list = value
  if not isinstance(value_list, (list, tuple)):
    value_list = [value_list]

  empty_sub_field_property_dict = {
    'key': 'default',
    'title': self.get_value('title'),
    'required': 0,
    'field_type': 'MultiListField',
    'item_list': [],
    'value': [],
    'is_right_display': 0,
    'size': 5,
260
    'editable' : self.get_value('editable', REQUEST=REQUEST)
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
  }

  hash_list = []
  hash_script_id = self.get_value('hash_script_id')
  if hash_script_id not in [None, '']:
    script = getattr(self, hash_script_id)
    script_hash_list = script(
            item_list,
            value_list,
            default_sub_field_property_dict=empty_sub_field_property_dict,
            is_right_display=0)
    hash_list.extend(script_hash_list)
  else:
    # No hash_script founded, generate a little hash_script 
    # to display only a MultiListField
    default_sub_field_property_dict = empty_sub_field_property_dict.copy()
    default_sub_field_property_dict.update({
        'item_list': item_list,
        'value': value_list,
    })
    hash_list.append(default_sub_field_property_dict)
  # XXX Clean up old ParallelListField
  if hasattr(self, 'sub_form'):
     delattr(self, 'sub_form')
  return hash_list
286 287 288 289 290


# Register get_value
from Products.ERP5Form.ProxyField import registerOriginalGetValueClassAndArgument
registerOriginalGetValueClassAndArgument(ParallelListField, '*')