ParallelListField.py 11.1 KB
Newer Older
1
# -*- coding: utf-8 -*-
Romain Courteaud's avatar
Romain Courteaud committed
2 3
##############################################################################
#
4
# Copyright (c) 2005, 2006 Nexedi SARL and Contributors. All Rights Reserved.
Romain Courteaud's avatar
Romain Courteaud committed
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 39
#                    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

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

44 45
class ParallelListWidget(Widget.MultiListWidget,
                         Widget.ListWidget):
Romain Courteaud's avatar
Romain Courteaud committed
46 47 48 49 50 51 52 53 54
    """
      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.

55
      Each dictionnary describes a (Multi)ListField.
Romain Courteaud's avatar
Romain Courteaud committed
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
      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 + \
72
                     Widget.ListWidget.property_names + \
Romain Courteaud's avatar
Romain Courteaud committed
73 74 75 76 77 78 79 80
      ['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)

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

92 93 94 95 96 97 98
    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
99 100
      }

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

115
    def render_htmlgrid(self, field, key, value, REQUEST, render_prefix=None):
116
      hash_list = generateSubForm(field, value, REQUEST)
117 118 119
      # Call render on each sub field
      sub_field_render_list = []
      for sub_field_property_dict in hash_list:
120 121 122 123 124 125
        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)))
126 127
      return sub_field_render_list

128
    def render_sub_field(self, field, key, value, REQUEST,
129
                        sub_field_property_dict, render_prefix=None):
130 131 132
      """
      Render dynamically a subfield
      """
133 134 135 136 137
      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'])
138
      sub_widget = self.sub_widget[sub_field_property_dict['field_type']]
139
      if sub_field_property_dict.get('editable', 1):
140 141 142 143 144
        result = sub_widget.render(field,
                                   field.generate_subfield_key(
                                     sub_field_property_dict['key'], key=key),
                                   sub_field_property_dict['value'],
                                   REQUEST=REQUEST)
145
      else:
146 147 148
        result = sub_widget.render_view(field,
                                        sub_field_property_dict['value'],
                                        REQUEST)
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

156 157 158 159
    def render_odt(self, field, value, as_string, ooo_builder, REQUEST,
                        render_prefix, attr_dict, local_name):
      """
      """
160
      return Widget.MultiListWidget.render_odt(self, field, value, as_string,
161 162 163 164 165 166 167 168
                                          ooo_builder, REQUEST, render_prefix,
                                          attr_dict, local_name)


    def render_odt_view(self, field, value, as_string, ooo_builder, REQUEST,
                        render_prefix, attr_dict, local_name):
      """
      """
169
      return Widget.MultiListWidget.render_odt_view(self, field, value, as_string,
170 171 172 173
                                               ooo_builder, REQUEST,
                                               render_prefix, attr_dict,
                                               local_name)

Romain Courteaud's avatar
Romain Courteaud committed
174 175
class ParallelListValidator(Validator.MultiSelectionValidator):

176
  property_names = Validator.MultiSelectionValidator.property_names
Romain Courteaud's avatar
Romain Courteaud committed
177

178 179 180 181
  sub_validator = {
    'ListField': Validator.SelectionValidatorInstance,
    'MultiListField': Validator.MultiSelectionValidatorInstance,
  }
Romain Courteaud's avatar
Romain Courteaud committed
182

183
  def validate(self, field, key, REQUEST):
184
    result_list = []
185
    hash_list = generateSubForm(field, (), REQUEST)
186
    for sub_field_property_dict in hash_list:
187 188 189 190 191 192 193
      id = field.generate_subfield_key(sub_field_property_dict['key'],
                                       validation=1, key=key)
      sub_result_list = self.validate_sub_field(field, id, REQUEST,
                                                sub_field_property_dict)
      if not isinstance(sub_result_list, (list, tuple)):
        sub_result_list = [sub_result_list]
      result_list.extend(sub_result_list)
Romain Courteaud's avatar
Romain Courteaud committed
194 195
    return result_list

196 197 198 199
  def validate_sub_field(self, field, id, REQUEST, sub_field_property_dict):
    """
    Validates a subfield (as part of field validation).
    """
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
    try:
      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'])
      field_type = sub_field_property_dict['field_type']
      if id[-5:] == ':list':
        id = id[:-5]
        field_type = 'Multi' + field_type
        REQUEST.set(id, [x for x in REQUEST.get(id, ()) if x != ''])
      return self.sub_validator[field_type].validate(field, id, REQUEST)
    finally:
      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)
217

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

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

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

228
  security.declareProtected('Access contents information', 'get_value')
229
  def get_value(self, id, REQUEST=None, **kw):
230 231 232 233 234
    """
    Get value for id.
    Optionally pass keyword arguments that get passed to TALES
    expression.
    """
235
    return paralellListFieldGetValue(self, id, REQUEST=REQUEST, **kw)
236 237

def generateSubForm(self, value, REQUEST):
238
  item_list = [x for x in self.get_value('items', REQUEST=REQUEST)
239
                 if x[0] != '' and x[1]]
240 241 242 243 244 245 246 247 248 249 250 251

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

  empty_sub_field_property_dict = {
    'key': 'default',
    'field_type': 'MultiListField',
    'item_list': [],
    'value': [],
    'is_right_display': 0,
  }
252
  for property in 'title', 'size', 'required', 'editable':
253 254
    empty_sub_field_property_dict[property] = self.get_value(property,
                                                             REQUEST=REQUEST)
255

256
  hash_script_id = self.get_value('hash_script_id', REQUEST=REQUEST)
257 258
  if hash_script_id:
    return getattr(self, hash_script_id)(
259 260 261 262 263 264 265
            item_list,
            value_list,
            default_sub_field_property_dict=empty_sub_field_property_dict,
            is_right_display=0)
  else:
    # No hash_script founded, generate a little hash_script 
    # to display only a MultiListField
266 267 268
    empty_sub_field_property_dict['item_list'] = item_list
    empty_sub_field_property_dict['value'] = value_list
    return [empty_sub_field_property_dict]
269

270 271 272 273 274 275 276 277 278
def paralellListFieldGetValue(field, id, REQUEST=None, **kw):
  result = MARKER
  key = KEYWORD % id
  if REQUEST is not None and REQUEST.has_key(key):
    result = REQUEST.get(key)
  if result is MARKER:
    result = ZMIField.get_value(field, id, REQUEST=REQUEST, **kw)
  return result

279 280
# Register get_value
from Products.ERP5Form.ProxyField import registerOriginalGetValueClassAndArgument
281 282 283 284 285
registerOriginalGetValueClassAndArgument(
    ParallelListField, 
    ('title', 'required', 'size', 'default', 'first_item', 'items'),
    paralellListFieldGetValue)

286