root/CPS3/products/CPSSchemas/trunk/BasicWidgets.py

Revision 53569, 82.9 kB (checked in by gracinet, 4 months ago)

Misplaced comment

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1 # -*- coding: iso-8859-15 -*-
2 # (C) Copyright 2003-2009 Nuxeo SA <http://nuxeo.com>
3 # Authors:
4 # Florent Guillaume <fg@nuxeo.com>
5 # M.-A. Darche <madarche@nuxeo.com>
6 #
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License version 2 as published
9 # by the Free Software Foundation.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19 # 02111-1307, USA.
20 #
21 # $Id$
22 """BasicWidgets
23
24 Definition of standard widget types.
25 """
26
27 import warnings
28 import operator
29 from re import compile, search
30 from cgi import escape
31 from urlparse import urlparse
32 from StringIO import StringIO
33
34 from Products.CPSUtil.html import renderHtmlTag
35 from Products.CPSUtil.mail import make_cid
36
37 from logging import getLogger
38 logger = getLogger('CPSSchemas.BasicWidgets')
39
40 try:
41     import PIL.Image
42 except ImportError:
43     logger.info("No PIL library found so no image resizing will be done")
44
45 from DateTime.DateTime import DateTime
46 from Globals import InitializeClass
47 from Acquisition import aq_parent, aq_inner, aq_base
48 from ZPublisher.HTTPRequest import FileUpload
49 from OFS.Image import cookId, File, Image
50 from Products.PythonScripts.standard import structured_text, newline_to_br
51
52 from Products.CMFCore.utils import getToolByName
53
54 from Products.CPSUtil.id import generateFileName
55 from Products.CPSUtil.file import PersistableFileUpload
56 from Products.CPSUtil.file import makeFileUploadFromOFSFile
57
58 from Products.CPSSchemas.utils import getHumanReadableSize
59 from Products.CPSSchemas.Widget import CPSWidget
60 from Products.CPSSchemas.Widget import widgetRegistry
61 from Products.CPSSchemas.Widget import CIDPARTS_KEY
62 from Products.CPSSchemas.Widget import EMAIL_LAYOUT_MODE
63 from Products.CPSSchemas.MethodVocabulary import MethodVocabularyWithContext
64 from Products.CPSSchemas.Vocabulary import EmptyKeyVocabularyWrapper
65
66 # BBB (remove this in CPS-3.6)
67 def cleanFileName(name):
68     return generateFileName(name)
69
70
71 ##################################################
72 class CPSNoneWidget(CPSWidget):
73     """None widget.
74
75     Deprecated widget can inherit form this widget, they will
76     disapear without breaking the rest of the document.
77     """
78     meta_type = 'None Widget'
79
80     def isHidden(self):
81         return 1
82
83     def prepare(self, datastructure, **kw):
84         """Prepare datastructure from datamodel."""
85         pass
86
87     def validate(self, datastructure, **kw):
88         """Validate datastructure and update datamodel."""
89         return 1
90
91     def render(self, mode, datastructure, **kw):
92         return ''
93
94 InitializeClass(CPSNoneWidget)
95
96
97 ##################################################
98
99 class CPSHtmlWidget(CPSWidget):
100     """Html widget."""
101     meta_type = 'Html Widget'
102
103     _properties = CPSWidget._properties + (
104         {'id': 'html_view', 'type': 'text', 'mode': 'w',
105          'label': 'Html for view'},
106         {'id': 'html_edit', 'type': 'text', 'mode': 'w',
107          'label': 'Html for edit'},
108         )
109     html_view = ''
110     html_edit = ''
111     field_types = ('CPS String Field',)
112
113     def prepare(self, datastructure, **kw):
114         """Prepare datastructure from datamodel."""
115         pass
116
117     def validate(self, datastructure, **kw):
118         """Validate datastructure and update datamodel."""
119         return 1
120
121     def render(self, mode, datastructure, **kw):
122         """Render in mode from datastructure."""
123         if mode == 'view':
124             return self.html_view
125         elif mode == 'edit':
126             return self.html_edit
127         raise RuntimeError('unknown mode %s' % mode)
128
129 InitializeClass(CPSHtmlWidget)
130
131 widgetRegistry.register(CPSHtmlWidget)
132
133 ##################################################
134
135 class CPSMethodWidget(CPSWidget):
136     """Method widget."""
137     meta_type = 'Method Widget'
138
139     _properties = CPSWidget._properties + (
140         {'id': 'render_method', 'type': 'string', 'mode': 'w',
141          'label': 'the zpt or py script method'},
142         {'id': 'field_types', 'type': 'lines', 'mode': 'w',
143          'label': 'Field types'},)
144
145     field_types = ('CPS String Field',)
146     render_method = ''
147
148     def prepare(self, datastructure, **kw):
149         """Prepare datastructure from datamodel."""
150         datamodel = datastructure.getDataModel()
151         if len(self.fields):
152             datastructure[self.getWidgetId()] = datamodel[self.fields[0]]
153         else:
154             datastructure[self.getWidgetId()] = None
155
156     def validate(self, datastructure, **kw):
157         """Validate datastructure and update datamodel."""
158         widget_id = self.getWidgetId()
159         err = 0
160         v = datastructure[widget_id]
161         if err:
162             datastructure.setError(widget_id, err)
163             datastructure[widget_id] = v
164         else:
165             datamodel = datastructure.getDataModel()
166             if len(self.fields):
167                 datamodel[self.fields[0]] = v
168
169         return not err
170
171     def render(self, mode, datastructure, **kw):
172         """Render in mode from datastructure."""
173         meth = getattr(self, self.render_method, None)
174         if meth is None:
175             msg = "Unknown Render Method %s for widget type %s. " \
176             + "Please set or change the 'render_method' attribute on " \
177             + "your widget declaration."
178             raise RuntimeError(msg % (self.render_method, self.getId()))
179         return meth(mode=mode, datastructure=datastructure)
180
181 InitializeClass(CPSMethodWidget)
182
183 widgetRegistry.register(CPSMethodWidget)
184
185 ##################################################
186
187 class CPSStringWidget(CPSWidget):
188     """String widget."""
189     meta_type = 'String Widget'
190
191     field_types = ('CPS String Field',)
192     field_inits = ({'is_searchabletext': 1,},)
193
194     display_width = 20
195     size_min = 0
196     size_max = 0
197     _properties = CPSWidget._properties + (
198         {'id': 'display_width', 'type': 'int', 'mode': 'w',
199          'label': 'Display width'},
200         {'id': 'size_min', 'type': 'int', 'mode': 'w',
201          'label': 'Minimum input width'},
202         {'id': 'size_max', 'type': 'int', 'mode': 'w',
203          'label': 'Maximum input width'},
204         )
205
206     # Associating the widget label with an input area to improve the widget
207     # accessibility.
208     has_input_area = True
209
210     def prepare(self, datastructure, **kw):
211         """Prepare datastructure from datamodel."""
212         datamodel = datastructure.getDataModel()
213         datastructure[self.getWidgetId()] = str(datamodel[self.fields[0]])
214
215
216     def _extractValue(self, value):
217         """Return err and new value."""
218         err = None
219         if not value:
220             v = ''
221         else:
222             v = value
223         try:
224             v = v.strip()
225         except AttributeError:
226             err = 'cpsschemas_err_string'
227         else:
228             if self.is_required and not v:
229                 err = 'cpsschemas_err_required'
230             elif self.size_min and len(v) < self.size_min:
231                 err = 'cpsschemas_err_string_too_short'
232             elif self.size_max and len(v) > self.size_max:
233                 err = 'cpsschemas_err_string_too_long'
234         return err, v
235
236     def validate(self, datastructure, **kw):
237         """Validate datastructure and update datamodel."""
238         widget_id = self.getWidgetId()
239         err, v = self._extractValue(datastructure[widget_id])
240         if err:
241             datastructure.setError(widget_id, err)
242             datastructure[widget_id] = v
243         else:
244             datamodel = datastructure.getDataModel()
245             datamodel[self.fields[0]] = v
246
247         return not err
248
249     def render(self, mode, datastructure, **kw):
250         """Render in mode from datastructure."""
251         value = datastructure[self.getWidgetId()]
252         if mode == 'view':
253             return escape(value)
254         elif mode == 'edit':
255             # XXX TODO should use an other name than kw !
256             # XXX change this everywhere
257             html_widget_id = self.getHtmlWidgetId()
258             kw = {'type': 'text',
259                   'id'  : html_widget_id,
260                   'name': html_widget_id,
261                   'value': value,
262                   'size': self.display_width,
263                   }
264             if self.size_max:
265                 kw['maxlength'] = self.size_max
266             return renderHtmlTag('input', **kw)
267         raise RuntimeError('unknown mode %s' % mode)
268
269 InitializeClass(CPSStringWidget)
270
271 widgetRegistry.register(CPSStringWidget)
272
273 ##################################################
274
275 class CPSURLWidget(CPSStringWidget):
276     """URL widget."""
277     meta_type = 'URL Widget'
278     _properties = CPSStringWidget._properties + (
279         {'id': 'target', 'type': 'string', 'mode': 'w',
280          'label': 'Target for the link'},)
281
282     target = ''
283     css_class = 'url'
284     display_width = 72
285     size_max = 4096
286
287     netloc_pat = compile(
288         # username:passwd (optional)
289         r"^([a-z0-9_!.~*\'\(\)-]*:[a-z0-9_!.~*\'\(\)-]*@)?"
290         # hostname
291         r"(([a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])\.)*"
292         r"([a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])"
293         # port (optional)
294         r"(:[0-9a-z]*)?"
295         "$")
296
297     path_pat = compile(
298         r"^[a-z0-9$_.+!*'(),;:@&=%/~-]*$")
299
300     # See rfc1738 and rfc2396
301     # NB: rfc1738 says that "/", ";", "?" can't appear in the query,
302     # but that's not what we see (ex: ?file=/tmp/toto)
303     def checkUrl(self, url):
304         url = url.lower()
305         try:
306             scheme, netloc, path, parameters, query, fragment = urlparse(url)
307         except:
308             return 0
309
310         if netloc and not self.netloc_pat.match(netloc):
311             return 0
312
313         if scheme in ('http', 'ftp', 'gopher', 'telnet',
314                       'nntp', 'wais', 'prospero') and not netloc:
315             return 0
316
317         if scheme in ('http', '', 'ftp'):
318             return self.path_pat.match(path)
319         else:
320             # TODO: match more URL schemes
321             return 1
322
323     def validate(self, datastructure, **kw):
324         """Validate datastructure and update datamodel."""
325         widget_id = self.getWidgetId()
326         err, v = self._extractValue(datastructure[widget_id])
327         if not err and v and not self.checkUrl(v):
328             err = 'cpsschemas_err_url'
329
330         if err:
331             datastructure.setError(widget_id, err)
332             datastructure[widget_id] = v
333         else:
334             datamodel = datastructure.getDataModel()
335             datamodel[self.fields[0]] = v
336
337         return not err
338
339     def render(self, mode, datastructure, **kw):
340         """Render in mode from datastructure."""
341         value = datastructure[self.getWidgetId()]
342         if mode == 'view':
343             if not value:
344                 return ''
345             value_for_display = escape(value)
346             if len(value_for_display) > self.display_width:
347                 value_for_display = value_for_display[:self.display_width] + '...'
348             kw = {'href': value, 'contents': value_for_display,
349                   'css_class': self.css_class,
350                   'target': self.target.strip()}
351             return renderHtmlTag('a', **kw)
352
353         return CPSStringWidget.render(self, mode, datastructure, **kw)
354
355
356 InitializeClass(CPSURLWidget)
357
358 widgetRegistry.register(CPSURLWidget)
359
360 ##################################################
361
362 class CPSEmailWidget(CPSStringWidget):
363     """Email widget"""
364     meta_type = 'Email Widget'
365
366     _properties = CPSStringWidget._properties + (
367         {'id': 'allow_extended_email', 'type': 'boolean', 'mode': 'w',
368          'label': 'Allow email of the form "Foo Bar <foo@bar.net>"'},
369     )
370
371     display_width = 72
372     size_max = 256
373     allow_extended_email = False
374
375     base_email_pat = r"([-\w_.'+])+@(([-\w])+\.)+([\w]{2,4})"
376
377     email_pat = compile(r"^%s$" % base_email_pat)
378     extended_email_pat = compile(r"^([^<>]+) <%s>$" % base_email_pat)
379
380     def validate(self, datastructure, **kw):
381         """Validate datastructure and update datamodel."""
382         widget_id = self.getWidgetId()
383         err, v = self._extractValue(datastructure[widget_id])
384         # no validation in search mode
385         if not err and kw.get('layout_mode') != 'search' and v:
386             v, err = self._validateValue(v, datastructure, **kw)
387         if err:
388             datastructure.setError(widget_id, err)
389         else:
390             datamodel = datastructure.getDataModel()
391             datamodel[self.fields[0]] = v
392         return not err
393
394     def _validateValue(self, value, datamodel, **kw):
395         """Helper method to make it easier to chain validation steps"""
396         if self.allow_extended_email:
397             if not (self.email_pat.match(value) or
398                     self.extended_email_pat.match(value)):
399                 return None, 'cpsschemas_err_email'
400         else:
401             if not self.email_pat.match(value):
402                 return None, 'cpsschemas_err_email'
403         return value, None
404
405     def render(self, mode, datastructure, **kw):
406         """Render in mode from datastructure."""
407         value = datastructure[self.getWidgetId()]
408         if mode == 'view' and value:
409             kw = {'href': 'mailto:' + value,
410                   'contents': escape(value)}
411             return renderHtmlTag('a', **kw)
412         return CPSStringWidget.render(self, mode, datastructure, **kw)
413
414 InitializeClass(CPSEmailWidget)
415
416 widgetRegistry.register(CPSEmailWidget)
417
418
419
420 ##################################################
421
422 class CPSIdentifierWidget(CPSStringWidget):
423     """Identifier widget."""
424     meta_type = 'Identifier Widget'
425     display_width = 30
426     size_max = 256
427
428     _properties = CPSStringWidget._properties + (
429                     {'id': 'id_pat',
430                      'type': 'string', 'mode': 'w',
431                      'label': 'Identifier regular expression (raw format)'},)
432
433     id_pat = r'^[a-zA-Z][a-zA-Z0-9@\-\._]*$'
434
435     def _checkIdentifier(self, value):
436         id_pat = compile(self.id_pat)
437         return id_pat.match(value.lower()) is not None
438
439     def validate(self, datastructure, **kw):
440         """Validate datastructure and update datamodel."""
441         widget_id = self.getWidgetId()
442         err, v = self._extractValue(datastructure[widget_id])
443
444         if not err and v and not self._checkIdentifier(v):
445             err = 'cpsschemas_err_identifier'
446
447         if err:
448             datastructure.setError(widget_id, err)
449             datastructure[widget_id] = v
450         else:
451             datamodel = datastructure.getDataModel()
452             datamodel[self.fields[0]] = v
453
454         return not err
455
456 InitializeClass(CPSIdentifierWidget)
457
458 widgetRegistry.register(CPSIdentifierWidget)
459
460 ##################################################
461
462 class CPSHeadingWidget(CPSStringWidget):
463     """HTML Heading widget like H1 H2..."""
464     meta_type = 'Heading Widget'
465     display_width = 72
466     size_max = 128
467     _properties = CPSStringWidget._properties + (
468         {'id': 'level', 'type': 'selection', 'mode': 'w',
469          'select_variable': 'all_levels',
470          'label': 'There are six levels of headings in HTML'},
471         )
472     all_levels = ['1', '2', '3', '4', '5', '6']
473     level = all_levels[0]
474
475     def render(self, mode, datastructure, **kw):
476         """Render in mode from datastructure."""
477         value = escape(datastructure[self.getWidgetId()])
478         if mode == 'view':
479             dm = datastructure.getDataModel()
480             obj = dm.getObject()
481             kw = {'contents': value,}
482             return renderHtmlTag('h%s' % self.level, **kw)
483         return CPSStringWidget.render(self, mode, datastructure, **kw)
484
485
486 InitializeClass(CPSHeadingWidget)
487
488 widgetRegistry.register(CPSHeadingWidget)
489
490 ##################################################
491
492 class CPSPasswordWidget(CPSStringWidget):
493     """Password widget.
494
495     The password widget displays stars in view mode, and in edit mode
496     it always starts with an empty string.
497
498     When validating, it doesn't update data if the user entry is empty.
499     """
500
501     meta_type = 'Password Widget'
502     _properties = CPSStringWidget._properties + (
503         {'id': 'password_widget', 'type': 'string', 'mode': 'w',
504          'label': 'Password widget to compare with'},
505         {'id': 'check_lower', 'type': 'boolean', 'mode': 'w',
506          'label': 'Checking at least one lower case [a-z]'},
507         {'id': 'check_upper', 'type': 'boolean', 'mode': 'w',
508          'label': 'Checking at least one upper case [A-Z]'},
509         {'id': 'check_digit', 'type': 'boolean', 'mode': 'w',
510          'label': 'Checking at least one digit [0-9]'},
511         {'id': 'check_extra', 'type': 'boolean', 'mode': 'w',
512          'label': 'Checking at least one extra char other than [a-zA-Z0-9]'},
513         )
514
515     field_types = ('CPS Password Field',)
516     password_widget = ''
517     check_lower = 0
518     check_upper = 0
519     check_digit = 0
520     check_extra = 0
521     display_width = 8
522     size_min = 5
523     size_max = 8
524
525     def prepare(self, datastructure, **kw):
526         """Prepare datastructure from datamodel."""
527         # Never fetch the real password.
528         datastructure[self.getWidgetId()] = ''
529
530     def render(self, mode, datastructure, **kw):
531         """Render in mode from datastructure."""
532         value = datastructure[self.getWidgetId()]
533         if mode == 'view':
534             return "********"
535         elif mode == 'edit':
536             kw = {'type': 'password',
537                   'name': self.getHtmlWidgetId(),
538                   'value': value,
539                   'size': self.display_width,
540                   }
541             if self.size_max:
542                 kw['maxlength'] = self.size_max
543             return renderHtmlTag('input', **kw)
544         raise RuntimeError('unknown mode %s' % mode)
545
546     def validate(self, datastructure, **kw):
547         """Validate datastructure and update datamodel."""
548         widget_id = self.getWidgetId()
549         value = datastructure[widget_id]
550         err = 0
551         try:
552             v = str(value).strip()
553         except ValueError:
554             err = 'cpsschemas_err_string'
555         else:
556             if self.password_widget:
557                 # here we only check that that our confirm match the pwd
558                 pwidget_id = self.password_widget
559                 pvalue = datastructure[pwidget_id]
560                 datastructure[widget_id] = ''
561                 datastructure[pwidget_id] = ''
562                 pv = str(pvalue).strip()
563                 if pv and v != pv:
564                     err = 'cpsschemas_err_password_mismatch'
565             else:
566                 if not v:
567                     if self.is_required:
568                         datamodel = datastructure.getDataModel()
569                         if not datamodel[self.fields[0]]:
570                             err = 'cpsschemas_err_required'
571                 else:
572                     # checking pw consistancy
573                     len_v = len(v)
574                     if not err and self.size_max and len_v > self.size_max:
575                         err = 'cpsschemas_err_string_too_long'
576                     if not err and self.size_min and len_v < self.size_min:
577                         err = 'cpsschemas_err_password_size_min'
578                     if not err and self.check_lower and not search(r'[a-z]', v):
579                         err = 'cpsschemas_err_password_lower'
580                     if not err and self.check_upper and not search(r'[A-Z]', v):
581                         err = 'cpsschemas_err_password_upper'
582                     if not err and self.check_digit and not search(r'[0-9]', v):
583                         err = 'cpsschemas_err_password_digit'
584                     if not err and self.check_extra and not search(r'[^a-zA-Z0-9]',
585                                                                    v):
586                         err = 'cpsschemas_err_password_extra'
587
588         if err:
589             datastructure[widget_id] = ''
590             datastructure.setError(widget_id, err)
591         elif v:
592             datamodel = datastructure.getDataModel()
593             datamodel[self.fields[0]] = v
594
595         return not err
596
597 InitializeClass(CPSPasswordWidget)
598
599 widgetRegistry.register(CPSPasswordWidget)
600
601 ##################################################
602
603 class CPSCheckBoxWidget(CPSWidget):
604     """CheckBox widget.
605        Deprecated, use CPS Boolean Widget !!!"""
606     meta_type = 'CheckBox Widget'
607
608     field_types = ('CPS Boolean Field',)
609
610     _properties = CPSWidget._properties + (
611         {'id': 'display_true', 'type': 'string', 'mode': 'w',
612          'label': 'Display for true'},
613         {'id': 'display_false', 'type': 'string', 'mode': 'w',
614          'label': 'Display for false'},
615         )
616     display_true = "Yes"
617     display_false = "No"
618
619     # Associating the widget label with an input area to improve the widget
620     # accessibility.
621     has_input_area = True
622
623     def prepare(self, datastructure, **kw):
624         """Prepare datastructure from datamodel."""
625         datamodel = datastructure.getDataModel()
626         datastructure[self.getWidgetId()] = not not datamodel[self.fields[0]]
627
628     def validate(self, datastructure, **kw):
629         """Validate datastructure and update datamodel."""
630         value = datastructure[self.getWidgetId()]
631         datamodel = datastructure.getDataModel()
632         datamodel[self.fields[0]] = not not value
633         return 1
634
635     def render(self, mode, datastructure, **kw):
636         """Render in mode from datastructure."""
637         value = datastructure[self.getWidgetId()]
638         if mode == 'view':
639             # XXX L10N Should expand view mode to be able to do i18n
640             if value:
641                 return self.display_true
642             else:
643                 return self.display_false
644         elif mode == 'edit':
645             html_widget_id = self.getHtmlWidgetId()
646             kw = {'type': 'checkbox',
647                   'name': html_widget_id,
648                   'id': html_widget_id,
649                   }
650             if value:
651                 kw['checked'] = 'checked'
652             tag = renderHtmlTag('input', **kw)
653             default_tag = renderHtmlTag('input',
654                                         type='hidden',
655                                         name=html_widget_id+':default',
656                                         value='')
657             return default_tag+tag
658         raise RuntimeError('unknown mode %s' % mode)
659
660 InitializeClass(CPSCheckBoxWidget)
661
662 widgetRegistry.register(CPSCheckBoxWidget)
663
664 ##################################################
665 # Warning textarea widget code is back to r1.75
666 # refactored textarea with position and format is now located in
667 # ExtendedWidgets and named CPSTextWidget
668
669 class CPSTextAreaWidget(CPSWidget):
670     """TextArea widget."""
671     meta_type = 'TextArea Widget'
672
673     field_types = ('CPS String Field',)
674     field_inits = ({'is_searchabletext': 1,},)
675
676     _properties = CPSWidget._properties + (
677         {'id': 'width', 'type': 'int', 'mode': 'w',
678          'label': 'Width'},
679         {'id': 'height', 'type': 'int', 'mode': 'w',
680          'label': 'Height'},
681         {'id': 'render_format', 'type': 'selection', 'mode': 'w',
682          'select_variable': 'all_render_formats',
683          'label': 'Render format'},
684         )
685     all_render_formats = ['text', 'pre', 'stx', 'html']
686     width = 40
687     height = 5
688     render_format = all_render_formats[0]
689
690     def prepare(self, datastructure, **kw):
691         """Prepare datastructure from datamodel."""
692         datamodel = datastructure.getDataModel()
693         datastructure[self.getWidgetId()] = str(datamodel[self.fields[0]])
694
695     def validate(self, datastructure, **kw):
696         """Validate datastructure and update datamodel."""
697         widget_id = self.getWidgetId()
698         value = datastructure[widget_id]
699         try:
700             v = str(value).strip()
701         except ValueError:
702             datastructure.setError(widget_id, "cpsschemas_err_textarea")
703             return 0
704         if self.is_required and not v:
705             datastructure[widget_id] = ''
706             datastructure.setError(widget_id, "cpsschemas_err_required")
707             return 0
708         datamodel = datastructure.getDataModel()
709         datamodel[self.fields[0]] = v
710         return 1
711
712     def render(self, mode, datastructure, **kw):
713         """Render in mode from datastructure."""
714         value = datastructure[self.getWidgetId()]
715         if mode == 'edit':
716             ret = renderHtmlTag('textarea',
717                                 name=self.getHtmlWidgetId(),
718                                 cols=self.width,
719                                 rows=self.height,
720                                 contents=value)
721         elif mode == 'view':
722             rformat = self.render_format
723             if rformat == 'pre':
724                 ret = '<pre>' + escape(value) + '</pre>'
725             elif rformat == 'stx':
726                 ret = structured_text(value)
727             elif rformat == 'text':
728                 ret = newline_to_br(escape(value))
729             elif rformat == 'html':
730                 ret = value
731             else:
732                 raise RuntimeError("unknown render_format '%s' for '%s'" %
733                                    (rformat, self.getId()))
734         else:
735             raise RuntimeError('unknown mode %s' % mode)
736
737         return ret
738
739 InitializeClass(CPSTextAreaWidget)
740
741 widgetRegistry.register(CPSTextAreaWidget)
742
743 ##################################################
744
745 class CPSLinesWidget(CPSWidget):
746     """Lines widget."""
747     meta_type = 'Lines Widget'
748
749     field_types = ('CPS String List Field',)
750     field_inits = ({'is_searchabletext': 1,},)
751
752     width = 40
753     height = 5
754     view_mode_separator = ', '
755     format_empty = ''
756     auto_strip = False
757
758     _properties = CPSWidget._properties + (
759         {'id': 'width', 'type': 'int', 'mode': 'w',
760          'label': 'Width'},
761         {'id': 'height', 'type': 'int', 'mode': 'w',
762          'label': 'Height'},
763         {'id': 'format_empty', 'type': 'string', 'mode': 'w',
764          'label': 'Format for empty list'},
765         {'id': 'view_mode_separator', 'type': 'string', 'mode': 'w',
766          'label': 'Separator in view mode'},
767         {'id': 'auto_strip', 'type': 'boolean', 'mode': 'w',
768          'label': 'Auto strip lines on validation'},
769         )
770
771     # Associating the widget label with an input area to improve the widget
772     # accessibility.
773     has_input_area = True
774
775     def prepare(self, datastructure, **kw):
776         """Prepare datastructure from datamodel."""
777         datamodel = datastructure.getDataModel()
778         value = datamodel[self.fields[0]]
779         if value == '':
780             # Buggy Zope :lines prop may give us '' instead of [] for default.
781             value = []
782         # XXX make a copy of the list ?
783         datastructure[self.getWidgetId()] = value
784
785     def validate(self, datastructure, **kw):
786         """Validate datastructure and update datamodel."""
787         widget_id = self.getWidgetId()
788         value = datastructure[widget_id]
789         v, err = self._validateValue(value, datastructure, **kw)
790         if err:
791             datastructure[widget_id] = ''
792             datastructure.setError(widget_id, err)
793             return 0
794         datamodel = datastructure.getDataModel()
795         datamodel[self.fields[0]] = v
796         return 1
797
798     def _validateValue(self, value, datastructure, **kw):
799         """Helper method to make it easier to chain validation steps"""
800         if value == ['']:
801             # Buggy Zope :lines prop may give us [''] instead of []
802             value = []
803         v = value # Zope handle lines automagically
804         if self.auto_strip:
805             v = [line.strip() for line in v if line.strip()]
806         if self.is_required and not v:
807             return None, "cpsschemas_err_required"
808         return v, None
809
810     def render(self, mode, datastructure, **kw):
811         """Render in mode from datastructure."""
812         value = datastructure[self.getWidgetId()]
813         if mode == 'view':
814             if not value:
815                 # XXX L10N empty format may be subject to i18n
816                 return self.format_empty
817             # XXX customize view mode, lots of displays are possible
818             return self.view_mode_separator.join([escape(i) for i in value])
819         elif mode == 'edit':
820             html_widget_id = self.getHtmlWidgetId()
821             return renderHtmlTag('textarea',
822                                  id=html_widget_id,
823                                  name=html_widget_id + ':lines',
824                                  cols=self.width,
825                                  rows=self.height,
826                                  contents='\n'.join(value))
827         raise RuntimeError('unknown mode %s' % mode)
828
829 InitializeClass(CPSLinesWidget)
830
831 widgetRegistry.register(CPSLinesWidget)
832
833
834 class CPSEmailListWidget(CPSLinesWidget, CPSEmailWidget):
835     """List of Emails with a textarea for input and rendered mailto: links"""
836
837     meta_type = 'Email List Widget'
838
839     _properties = CPSLinesWidget._properties  + CPSEmailWidget._properties
840
841     auto_strip = True
842
843     def validate(self, datastructure, **kw):
844         """Validate a list of email address by combining list and email tests"""
845         widget_id = self.getWidgetId()
846         value = datastructure[widget_id]
847
848         # first apply the Lines validation
849         v, err = CPSLinesWidget._validateValue(self, value, datastructure, **kw)
850
851         # then for each line apply email validation
852         if not err:
853             for line in v:
854                 _, line_err = CPSEmailWidget._validateValue(self, line,
855                                                             datastructure, **kw)
856                 if line_err:
857                     err = line_err
858                     break
859         if err:
860             datastructure.setError(widget_id, err)
861             return 0
862         datamodel = datastructure.getDataModel()
863         datamodel[self.fields[0]] = v
864         return 1
865
866     def render(self, mode, datastructure, **kw):
867         """Render in mode from datastructure"""
868         if mode == "view":
869             value = datastructure[self.getWidgetId()]
870             if not value:
871                 # XXX L10N empty format may be subject to i18n
872                 return self.format_empty
873             links = [renderHtmlTag('a',**{'href': 'mailto:%s' % l,
874                                           'contents': escape(l)})
875                      for l in value]
876             return self.view_mode_separator.join(links)
877         else:
878             return CPSLinesWidget.render(self, mode, datastructure, **kw)
879
880
881 InitializeClass(CPSEmailListWidget)
882
883 widgetRegistry.register(CPSEmailListWidget)
884
885
886 ##################################################
887
888 class CPSListWidget(CPSLinesWidget):
889     """Abstract list widget"""
890     meta_type = 'List Widget'
891     display = None
892
893     def render(self, mode, datastructure, **kw):
894         """Render in mode from datastructure."""
895         if not self.display:
896             raise NotImplementedError
897         value = datastructure[self.getWidgetId()]
898         meth = getattr(self, 'widget_list_render', None)
899         if meth is None:
900             raise RuntimeError(
901                 "Unknown Render Method widget_list_render for widget type %s"
902                     % self.getId())
903         return meth(mode=mode, value=value)
904
905 InitializeClass(CPSListWidget)
906
907 ##################################################
908
909 class CPSOrderedListWidget(CPSListWidget):
910     """Ordered list widget"""
911     meta_type = 'Ordered List Widget'
912     display = 'ordered'
913
914 InitializeClass(CPSOrderedListWidget)
915
916 widgetRegistry.register(CPSOrderedListWidget)
917
918 ##################################################
919
920 class CPSUnorderedListWidget(CPSListWidget):
921     """Unordered list widget"""
922     meta_type = 'Unordered List Widget'
923     display = 'unordered'
924
925 InitializeClass(CPSUnorderedListWidget)
926
927 widgetRegistry.register(CPSUnorderedListWidget)
928
929 ##################################################
930
931 class CPSSelectWidget(CPSWidget):
932     """Select widget."""
933     meta_type = 'Select Widget'
934
935     field_types = ('CPS String Field',)
936     field_inits = ({'is_searchabletext': 1,},)
937
938     _properties = CPSWidget._properties + (
939         {'id': 'vocabulary', 'type': 'string', 'mode': 'w',
940          'label': 'Vocabulary', 'is_required' : 1},
941         {'id': 'translated', 'type': 'boolean', 'mode': 'w',
942          'label': 'Vocabulary is i18n'},
943         {'id': 'sorted', 'type': 'boolean', 'mode': 'w',
944          'label': 'Are vocabulary values rendered sorted'},
945         {'id': 'add_empty_key', 'type': 'boolean', 'mode': 'w',
946          'label':'Add an empty key'},
947         {'id': 'empty_key_pos', 'type': 'selection', 'mode': 'w',
948          'select_variable': 'empty_key_pos_select',
949          'label':'Empty key position'},
950         {'id': 'empty_key_value', 'type': 'string', 'mode': 'w',
951          'label':'Empty key value'},
952         {'id': 'empty_key_value_i18n', 'type': 'string', 'mode': 'w',
953          'label':'Empty key i18n value'},
954         )
955
956     # XXX make a menu for the vocabulary.
957     vocabulary = ''
958     translated = False
959     sorted = False
960     add_empty_key = False
961     empty_key_pos_select = ('first', 'last')
962     empty_key_pos = empty_key_pos_select[0]
963     empty_key_value = ''
964     empty_key_value_i18n = ''
965
966     # Associating the widget label with an input area to improve the widget
967     # accessibility.
968     has_input_area = True
969
970     def _getVocabulary(self, datastructure=None):
971         """Get the vocabulary object for this widget."""
972         context = datastructure.getDataModel().getContext()
973         if not isinstance(self.vocabulary, str):
974             # this is in case vocabulary directly holds
975             # a vocabulary object
976             vocabulary = self.vocabulary
977         else:
978             vtool = getToolByName(self, 'portal_vocabularies')
979             vocabulary = vtool.getVocabularyFor(context, self.vocabulary)
980         if vocabulary.meta_type == 'CPS Method Vocabulary':
981             vocabulary = MethodVocabularyWithContext(vocabulary, context)
982         if self.add_empty_key:
983             vocabulary = EmptyKeyVocabularyWrapper(
984                 vocabulary, self.empty_key_value,
985                 msgid=self.empty_key_value_i18n,
986                 position=self.empty_key_pos,)
987         return vocabulary
988
989     def prepare(self, datastructure, **kw):
990         """Prepare datastructure from datamodel."""
991         datamodel = datastructure.getDataModel()
992         datastructure[self.getWidgetId()] = datamodel[self.fields[0]]
993
994     def validate(self, datastructure, **kw):
995         """Validate datastructure and update datamodel."""
996         widget_id = self.getWidgetId()
997         value = datastructure[widget_id]
998         try:
999             v = str(value)
1000         except ValueError:
1001             datastructure.setError(widget_id, "cpsschemas_err_select")
1002             return 0
1003         vocabulary = self._getVocabulary(datastructure)
1004         if not vocabulary.has_key(value):
1005             datastructure.setError(widget_id, "cpsschemas_err_select")
1006             return 0
1007         if self.is_required and not len(v):
1008             datastructure.setError(widget_id, "cpsschemas_err_required")
1009             return 0
1010
1011         datamodel = datastructure.getDataModel()
1012         datamodel[self.fields[0]] = v
1013         return 1
1014
1015     def render(self, mode, datastructure, **kw):
1016         """Render in mode from datastructure."""
1017         value = datastructure[self.getWidgetId()]
1018         vocabulary = self._getVocabulary(datastructure)
1019         portal = getToolByName(self, 'portal_url').getPortalObject()
1020         cpsmcat = portal.translation_service
1021         if mode == 'view':
1022             if self.translated:
1023                 return escape(cpsmcat(
1024                     vocabulary.getMsgid(value, value)).encode('ISO-8859-15',
1025                                                               'ignore'))
1026             else:
1027                 return escape(vocabulary.get(value, value))
1028         elif mode == 'edit':
1029             html_widget_id = self.getHtmlWidgetId()
1030             res = renderHtmlTag('select',
1031                                 name=html_widget_id, id=html_widget_id)
1032             in_selection = False
1033             vocabulary_items = vocabulary.items()
1034             if self.translated:
1035                 vocabulary_items_translated = []
1036                 for k, v in vocabulary_items:
1037                     label = cpsmcat(vocabulary.getMsgid(k, k), default=k)
1038                     label = label.encode('ISO-8859-15', 'ignore')
1039                     vocabulary_items_translated.append((k, label))
1040                 vocabulary_items = vocabulary_items_translated
1041             if self.sorted:
1042                 vocabulary_items.sort(key=lambda obj: obj[1].lower())
1043             for k, v in vocabulary_items:
1044                 kw = {'value': k, 'contents': v}
1045                 if value == k:
1046                     kw['selected'] = 'selected'
1047                     in_selection = True
1048                 res += renderHtmlTag('option', **kw)
1049             if value and not in_selection:
1050                 kw = {'value': value, 'contents': 'invalid: ' + str(value),
1051                       'selected': 'selected'}
1052                 res += renderHtmlTag('option', **kw)
1053             res += '</select>'
1054             return res
1055         raise RuntimeError('unknown mode %s' % mode)
1056
1057 InitializeClass(CPSSelectWidget)
1058
1059 widgetRegistry.register(CPSSelectWidget)
1060
1061 ##################################################
1062
1063 class CPSMultiSelectWidget(CPSSelectWidget):
1064     """MultiSelect widget."""
1065     meta_type = 'MultiSelect Widget'
1066
1067     field_types = ('CPS String List Field',)
1068     field_inits = ({'is_searchabletext': 1,},)
1069
1070     _properties = CPSSelectWidget._properties + (
1071         {'id': 'size', 'type': 'int', 'mode': 'w',
1072          'label': 'Size'},
1073         {'id': 'format_empty', 'type': 'string', 'mode': 'w',
1074          'label': 'Format for empty list'},
1075         )
1076     size = 0
1077     format_empty = ''
1078
1079     # Associating the widget label with an input area to improve the widget
1080     # accessibility.
1081     has_input_area = True
1082
1083     # BBB for [46410]. Remove this once an upgrade step has been written
1084     sorted = False
1085
1086     def prepare(self, datastructure, **kw):
1087         """Prepare datastructure from datamodel."""
1088         datamodel = datastructure.getDataModel()
1089         value = datamodel[self.fields[0]]
1090         if value == '':
1091             # Buggy Zope :lines prop may give us '' instead of [] for default
1092             value = []
1093         # XXX make a copy of the list ?
1094         datastructure[self.getWidgetId()] = value
1095
1096     def validate(self, datastructure, **kw):
1097         """Validate datastructure and update datamodel."""
1098         widget_id = self.getWidgetId()
1099         value = datastructure[widget_id]
1100         if not isinstance(value, (list, tuple)):
1101             datastructure.setError(widget_id, "cpsschemas_err_multiselect")
1102             return 0
1103         vocabulary = self._getVocabulary(datastructure)
1104         v = []
1105         for i in value:
1106             try:
1107                 i = str(i)
1108             except ValueError:
1109                 datastructure.setError(widget_id, "cpsschemas_err_multiselect")
1110                 return 0
1111             if not vocabulary.has_key(i):
1112                 datastructure.setError(widget_id, "cpsschemas_err_multiselect")
1113                 return 0
1114             v.append(i)
1115         if self.is_required and not len(v):
1116             datastructure.setError(widget_id, "cpsschemas_err_required")
1117             return 0
1118         datamodel = datastructure.getDataModel()
1119         datamodel[self.fields[0]] = v
1120         return 1
1121
1122     def render(self, mode, datastructure, **kw):
1123         """Render in mode from datastructure."""
1124         value = datastructure[self.getWidgetId()]
1125         vocabulary = self._getVocabulary(datastructure)
1126         portal = getToolByName(self, 'portal_url').getPortalObject()
1127         cpsmcat = portal.translation_service
1128         if mode == 'view':
1129             if not value:
1130                 # XXX L10N empty format may be subject to i18n
1131                 return self.format_empty
1132             # XXX customize view mode, lots of displays are possible
1133             else:
1134                 return self.getEntriesHtml(value, vocabulary, self.translated)
1135         elif mode == 'edit':
1136             html_widget_id = self.getHtmlWidgetId()
1137             kw = {'name': html_widget_id + ':list',
1138                   'multiple': 'multiple',
1139                   'id': html_widget_id,
1140                   }
1141             if self.size:
1142                 kw['size'] = self.size
1143             res = renderHtmlTag('select', **kw)
1144
1145             vocabulary_items = vocabulary.items()
1146             if self.translated:
1147                 vocabulary_items_translated = []
1148                 for k, v in vocabulary_items:
1149                     label = cpsmcat(vocabulary.getMsgid(k, k), default=k)
1150                     label = label.encode('ISO-8859-15', 'ignore')
1151                     vocabulary_items_translated.append((k, label))
1152                 vocabulary_items = vocabulary_items_translated
1153             if self.sorted:
1154                 vocabulary_items.sort(key=lambda obj: obj[1].lower())
1155             for k, v in vocabulary_items:
1156                 kw = {'value': k, 'contents': v}
1157                 if k in value:
1158                     kw['selected'] = 'selected'
1159                 res += renderHtmlTag('option', **kw)
1160             res += '</select>'
1161             default_tag = renderHtmlTag('input',
1162                                         type='hidden',
1163                                         name=html_widget_id+':tokens:default',
1164                                         value='')
1165             return default_tag+res
1166         raise RuntimeError('unknown mode %s' % mode)
1167
1168     def getEntriesHtml(self, entries, vocabulary, translated=False):
1169         if translated:
1170             cpsmcat = getToolByName(self, 'translation_service', None)
1171             if cpsmcat is None:
1172                 translated = False
1173         values = []
1174         for entry in entries:
1175             if translated:
1176                 value = vocabulary.getMsgid(entry, entry)
1177                 value = cpsmcat(value, default=value)
1178                 value = value.encode('ISO-8859-15', 'ignore')
1179             else:
1180                 value = vocabulary.get(entry, entry)
1181             values.append(value)
1182             if self.sorted:
1183                 values.sort(key=str.lower)
1184         return escape(', '.join(values))
1185
1186 InitializeClass(CPSMultiSelectWidget)
1187
1188 widgetRegistry.register(CPSMultiSelectWidget)
1189
1190 ##################################################
1191
1192 class CPSBooleanWidget(CPSWidget):
1193     """Boolean widget."""
1194     meta_type = 'Boolean Widget'
1195
1196     field_types = ('CPS Int Field',)
1197
1198     _properties = CPSWidget._properties + (
1199         {'id': 'label_false', 'type': 'string', 'mode': 'w',
1200          'label': 'False label'},
1201         {'id': 'label_true', 'type': 'string', 'mode': 'w',
1202          'label': 'True label'},
1203         {'id': 'render_format', 'type': 'selection', 'mode': 'w',
1204          'select_variable': 'render_formats',
1205          'label': 'Render format'},
1206         )
1207     label_false = 'cpsschemas_label_false'
1208     label_true = 'cpsschemas_label_true'
1209     render_formats = ('checkbox', 'radio', 'select')
1210     render_format = render_formats[2]
1211
1212     # Associating the widget label with an input area to improve the widget
1213     # accessibility.
1214     has_input_area = True
1215
1216     def prepare(self, datastructure, **kw):
1217         """Prepare datastructure from datamodel."""
1218         datamodel = datastructure.getDataModel()
1219         datastructure[self.getWidgetId()] = bool(datamodel[self.fields[0]])
1220
1221     def validate(self, datastructure, **kw):
1222         """Validate datastructure and update datamodel."""
1223         value = datastructure[self.getWidgetId()]
1224
1225         if self.render_format not in self.render_formats:
1226             self.render_format = 'select'
1227
1228         try:
1229             v = bool(value)
1230         except (ValueError, TypeError):
1231             datastructure.setError(self.getWidgetId(),
1232                                    "cpsschemas_err_boolean")
1233             return 0
1234         datamodel = datastructure.getDataModel()
1235         datamodel[self.fields[0]] = v
1236         return 1
1237
1238     def render(self, mode, datastructure, **kw):
1239         """Render in mode from datastructure."""
1240         value = datastructure[self.getWidgetId()]
1241         if value:
1242             label_value = self.label_true
1243         else:
1244             label_value = self.label_false
1245         render_method = 'widget_boolean_render'
1246         meth = getattr(self, render_method, None)
1247         render_format = self.render_format
1248         if not render_format:
1249             render_format = 'select'
1250         if meth is None:
1251             raise RuntimeError("Unknown Render Method %s for widget type %s"
1252                                % (render_method, self.getId()))
1253         return meth(mode=mode, value=value, label_value=label_value,
1254                     render_format=render_format)
1255
1256 InitializeClass(CPSBooleanWidget)
1257
1258 widgetRegistry.register(CPSBooleanWidget)
1259
1260 ##################################################
1261
1262 class CPSIntWidget(CPSWidget):
1263     """Integer widget."""
1264     meta_type = 'Int Widget'
1265
1266     field_types = ('CPS Int Field',)
1267
1268     _properties = CPSWidget._properties + (
1269         {'id': 'is_limited', 'type': 'boolean', 'mode': 'w',
1270          'label': 'Value must be in range'},
1271         {'id': 'min_value', 'type': 'int', 'mode': 'w',
1272          'label': 'Range minimum value'},
1273         {'id': 'max_value', 'type': 'int', 'mode': 'w',
1274          'label': 'Range maximum value'},
1275         {'id': 'thousands_separator', 'type': 'string', 'mode': 'w',
1276          'label': 'Thousands separator'},
1277         )
1278
1279     is_limited = 0
1280     min_value = 0
1281     max_value = 0
1282     thousands_separator = ''
1283
1284     def prepare(self, datastructure, **kw):
1285         """Prepare datastructure from datamodel."""
1286         datamodel = datastructure.getDataModel()
1287         v = datamodel[self.fields[0]]
1288         if v is None:
1289             value = ''
1290         else:
1291             value = str(v)
1292             if self.thousands_separator:
1293                 thousands = []
1294                 while value:
1295                     thousands.insert(0, value[-3:])
1296                     value = value[:-3]
1297                 value = self.thousands_separator.join(thousands)
1298         datastructure[self.getWidgetId()] = value
1299
1300     def validate(self, datastructure, **kw):
1301         """Validate datastructure and update datamodel."""
1302         datamodel = datastructure.getDataModel()
1303         widget_id = self.getWidgetId()
1304         value = datastructure[widget_id].strip()
1305         datastructure[widget_id] = value
1306
1307         if self.thousands_separator:
1308             value = value.replace(self.thousands_separator, '')
1309
1310         if not value:
1311             if self.is_required:
1312                 datastructure.setError(widget_id, 'cpsschemas_err_required')
1313                 return False
1314             v = None
1315         else:
1316             try:
1317                 v = int(value)
1318             except (ValueError, TypeError):
1319                 datastructure.setError(widget_id, 'cpsschemas_err_int')
1320                 return False
1321             if self.is_limited:
1322                 if (v < self.min_value) or (v > self.max_value):
1323                     datastructure.setError(widget_id,
1324                                            'cpsschemas_err_int_range')
1325                     return False
1326
1327         datamodel[self.fields[0]] = v
1328         return True
1329
1330     def render(self, mode, datastructure, **kw):
1331         """Render in mode from datastructure."""
1332         value = datastructure[self.getWidgetId()]
1333         if mode == 'view':
1334             return escape(value)
1335         elif mode == 'edit':
1336             return renderHtmlTag('input',
1337                                  type='text',
1338                                  name=self.getHtmlWidgetId(),
1339                                  value=value)
1340         raise RuntimeError('unknown mode %s' % mode)
1341
1342 InitializeClass(CPSIntWidget)
1343
1344 widgetRegistry.register(CPSIntWidget)
1345
1346 #######################################################
1347
1348 class CPSLongWidget(CPSIntWidget):
1349     """Long Widget
1350
1351     This widget is DEPRECATED, use the identical Int Widget instead.
1352     """
1353     meta_type = 'Long Widget'
1354
1355     def render(self, mode, datastructure, **kw):
1356         """Render in mode from datastructure."""
1357         warnings.warn("The Long Widget (%s/%s) is deprecated and will be "
1358                       "removed in CPS 3.5.0. Use a Int Widget instead" %
1359                       (aq_parent(aq_inner(self)).getId(), self.getWidgetId()),
1360                       DeprecationWarning)
1361         CPSIntWidget.render(self, mode, datastructure, **kw)
1362
1363 InitializeClass(CPSLongWidget)
1364
1365 widgetRegistry.register(CPSLongWidget)
1366
1367 ##################################################
1368
1369 class CPSFloatWidget(CPSWidget):
1370     """Float number widget."""
1371     meta_type = 'Float Widget'
1372
1373     field_types = ('CPS Float Field',)
1374
1375     _properties = CPSWidget._properties + (
1376         {'id': 'is_limited', 'type': 'boolean', 'mode': 'w',
1377          'label': 'Value must be in range'},
1378         {'id': 'min_value', 'type': 'float', 'mode': 'w',
1379          'label': 'Range minimum value'},
1380         {'id': 'max_value', 'type': 'float', 'mode': 'w',
1381          'label': 'Range maximum value'},
1382         {'id': 'thousands_separator', 'type': 'string', 'mode': 'w',
1383          'label': 'Thousands separator'},
1384         {'id': 'decimals_separator', 'type': 'string', 'mode': 'w',
1385          'label': 'Decimal separator'},
1386         {'id': 'decimals_number', 'type': 'int', 'mode': 'w',
1387          'label': 'Number of decimals'},
1388             )
1389
1390     is_limited = 0
1391     min_value = 0.0
1392     max_value = 0.0
1393     # XXX: find a way to localized thousands_separator and decimals_separator
1394     thousands_separator = ''
1395     decimals_separator = '.'
1396     decimals_number = 0
1397
1398     def prepare(self, datastructure, **kw):
1399         """Prepare datastructure from datamodel."""
1400         datamodel = datastructure.getDataModel()
1401         v = datamodel[self.fields[0]]
1402         if v is None:
1403             value = ''
1404         else:
1405             if self.decimals_number:
1406                 value = ('%%0.%df' % self.decimals_number) % v
1407             else:
1408                 value = str(v)
1409             if self.thousands_separator:
1410                 intpart, decpart = value.split('.')
1411                 thousands = []
1412                 while intpart:
1413                     thousands.insert(0, intpart[-3:])
1414                     intpart = intpart[:-3]
1415                 value = (self.thousands_separator.join(thousands) + '.' +
1416                          decpart)
1417             if self.decimals_separator:
1418                 value = value.replace('.', self.decimals_separator)
1419         datastructure[self.getWidgetId()] = value
1420
1421     def validate(self, datastructure, **kw):
1422         """Validate datastructure and update datamodel."""
1423         datamodel = datastructure.getDataModel()
1424         widget_id = self.getWidgetId()
1425         value = datastructure[widget_id].strip()
1426         datastructure[widget_id] = value
1427
1428         if self.thousands_separator:
1429             value = value.replace(self.thousands_separator, '')
1430         if self.decimals_separator:
1431             value = value.replace(self.decimals_separator, '.')
1432
1433         if not value:
1434             if self.is_required:
1435                 datastructure.setError(widget_id, 'cpsschemas_err_required')
1436                 return False
1437             v = None
1438         else:
1439             try:
1440                 v = float(value)
1441             except (ValueError, TypeError):
1442                 datastructure.setError(widget_id, 'cpsschemas_err_float')
1443                 return False
1444
1445             if self.is_limited:
1446                 if (v < self.min_value) or (v > self.max_value):
1447                     datastructure.setError(widget_id,
1448                                            'cpsschemas_err_float_range')
1449                     return False
1450
1451         datamodel[self.fields[0]] = v
1452         return True
1453
1454     def render(self, mode, datastructure, **kw):
1455         """Render in mode from datastructure."""
1456         value = datastructure[self.getWidgetId()]
1457         if mode == 'view':
1458             return escape(value)
1459         elif mode == 'edit':
1460             return renderHtmlTag('input',
1461                                  type='text',
1462                                  name=self.getHtmlWidgetId(),
1463                                  value=value)
1464         raise RuntimeError('unknown mode %s' % mode)
1465
1466 InitializeClass(CPSFloatWidget)
1467
1468 widgetRegistry.register(CPSFloatWidget)
1469
1470 ##################################################
1471 # Warning Date widget code is back to r1.49
1472 # refactored date widget is now located in
1473 # ExtendWidgets and named CPSDateTimeWidget
1474
1475 class CPSDateWidget(CPSWidget):
1476     """Date widget."""
1477     meta_type = 'Date Widget'
1478
1479     field_types = ('CPS DateTime Field',)
1480
1481     _properties = CPSWidget._properties + (
1482         {'id': 'view_format', 'type': 'string', 'mode': 'w',
1483          'label': 'View format'},
1484         {'id': 'view_format_none', 'type': 'string', 'mode': 'w',
1485          'label': 'View format empty'},
1486         {'id': 'use_javascript', 'type': 'boolean', 'mode': 'w',
1487          'label': 'Use Javascript'},
1488         )
1489     view_format = "%d/%m/%Y" # XXX unused for now
1490     view_format_none = "-"
1491     use_javascript = 0
1492
1493     # Associating the widget label with an input area to improve the widget
1494     # accessibility.
1495     has_input_area = True
1496
1497     def prepare(self, datastructure, **kw):
1498         """Prepare datastructure from datamodel."""
1499         datamodel = datastructure.getDataModel()
1500         v = datamodel[self.fields[0]]
1501         widget_id = self.getWidgetId()
1502         if v is not None:
1503             d = '%02i' % v.day()
1504             m = '%02i' % v.month()
1505             y = '%04i' % v.year()
1506         else:
1507             d = m = y = ''
1508         datastructure[widget_id+'_d'] = d
1509         datastructure[widget_id+'_m'] = m
1510         datastructure[widget_id+'_y'] = y
1511
1512     def validate(self, datastructure, **kw):
1513         """Update datamodel from user data in datastructure."""
1514         datamodel = datastructure.getDataModel()
1515         field_id = self.fields[0]
1516         widget_id = self.getWidgetId()
1517
1518         d = datastructure[widget_id+'_d'].strip()
1519         m = datastructure[widget_id+'_m'].strip()
1520         y = datastructure[widget_id+'_y'].strip()
1521
1522         if not (d+m+y):
1523             if self.is_required:
1524                 datastructure[widget_id] = ''
1525                 datastructure.setError(widget_id, "cpsschemas_err_required")
1526                 return 0
1527             else:
1528                 datamodel[field_id] = None
1529                 return 1
1530
1531         try:
1532             v = DateTime(int(y), int(m), int(d))
1533         except (ValueError, TypeError, DateTime.DateTimeError,
1534                 DateTime.SyntaxError, DateTime.DateError):
1535             datastructure.setError(widget_id, 'cpsschemas_err_date')
1536             return 0
1537         else:
1538             datamodel[field_id] = v
1539             return 1
1540
1541     def render(self, mode, datastructure, **kw):
1542         """Render this widget from the datastructure or datamodel."""
1543         if self.use_javascript:
1544             js_onKeyPress = """if (navigator.appName == 'Netscape')
1545                 { var key = event.which } else { var key = event.keyCode };
1546                 if ( key < 32 ) { return true; }
1547                 if ( key < 48 || key > 57 ) { return false; }"""
1548
1549             js_onKeyUp = """if (navigator.appName == 'Netscape')
1550                 { var key = event.which } else { var key = event.keyCode };
1551                 if ( key < 32 ) { return true; }
1552                 if ( this.value > %(max_value)s ) { return false};
1553                 if ( this.value >= %(low_trigger)s ||
1554                      this.value.length >= %(max_size)s ) {
1555                     form.%(next_widget)s.focus() }
1556                  """
1557         else:
1558             js_onKeyPress = ""
1559             js_onKeyUp = ""
1560
1561         widget_id = self.getWidgetId()
1562         d = datastructure[widget_id+'_d']
1563         m = datastructure[widget_id+'_m']
1564         y = datastructure[widget_id+'_y']
1565         if mode == 'view':
1566             if not (d+m+y):
1567                 return escape(self.view_format_none)
1568             else:
1569                 # XXX customize format
1570                 return escape(d+'/'+m+'/'+y)
1571         elif mode == 'edit':
1572             html_widget_id = self.getHtmlWidgetId()
1573             res = '<fieldset class="widget" id="%s">' % html_widget_id
1574             dtag = renderHtmlTag('input',
1575                                  type='text',
1576                                  name=html_widget_id+'_d',
1577                                  value=d,
1578                                  size=2,
1579                                  maxlength=2,
1580                                  onKeyPress=js_onKeyPress,
1581                                  onKeyUp=js_onKeyUp % {
1582                                     'max_value': '31',
1583                                     'max_size': '2',
1584                                     'low_trigger': '4',
1585                                     'next_widget': html_widget_id+'_m',
1586                                  })
1587             mtag = renderHtmlTag('input',
1588                                  type='text',
1589                                  name=html_widget_id+'_m',
1590                                  value=m,
1591                                  size=2,
1592                                  maxlength=2,
1593                                  onKeyPress=js_onKeyPress,
1594                                  onKeyUp=js_onKeyUp % {
1595                                     'max_value': '12',
1596                                     'max_size': '2',
1597                                     'low_trigger': '2',
1598                                     'next_widget': html_widget_id+'_y',
1599                                  })
1600             ytag = renderHtmlTag('input',
1601                                  type='text',
1602                                  name=html_widget_id+'_y',
1603                                  value=y,
1604                                  size=6,
1605                                  maxlength=6,
1606                                  onKeyPress=js_onKeyPress,
1607                                  )
1608             # XXX customize format
1609             res += dtag + '/' + mtag + '/' + ytag
1610             res += '</fieldset>'
1611             return res
1612         raise RuntimeError('unknown mode %s' % mode)
1613
1614 InitializeClass(CPSDateWidget)
1615
1616 widgetRegistry.register(CPSDateWidget)
1617
1618 ##################################################
1619
1620 class CPSFileWidget(CPSWidget):
1621     """File widget.
1622
1623     The DataStructure stores either a FileUpload or None.
1624
1625     A FileUpload itself has a filename.
1626
1627     When stored in the DataModel, the filename is stored as the File
1628     title. The File id is fixed to the id under which its parent
1629     container knows it.
1630     """
1631
1632     meta_type = 'File Widget'
1633
1634     field_types = ('CPS File Field',    # File content
1635                    'CPS String Field',  # Title
1636                    )
1637
1638     _properties = CPSWidget._properties + (
1639         {'id': 'size_max', 'type': 'int', 'mode': 'w',
1640          'label': 'Maximum file size'},
1641         {'id': 'display_external_editor', 'type': 'boolean', 'mode': 'w',
1642          'label': 'Display link to external editor in edit mode'},
1643         )
1644     size_max = 4*1024*1024
1645     display_external_editor = True
1646
1647     logger = getLogger('CPSSchemas.BasicWidgets.CPSFileWidget')
1648
1649     def getHumanReadableSize(self, size):
1650         """ get human readable size
1651         """
1652         hr = getHumanReadableSize(size)
1653         cpsmcat = getToolByName(self, 'translation_service')
1654         return str(hr[0]) + ' ' + cpsmcat(hr[1]).encode('ISO-8859-15',
1655                                                         'ignore')
1656
1657     def getFileSize(self, fileupload):
1658         """Find size of given fileupload.
1659
1660         fileupload is assumed not to be None."""
1661
1662         current = fileupload.tell()
1663         fileupload.seek(0, 2) # end of file
1664         size = fileupload.tell()
1665         fileupload.seek(current)
1666         return size
1667
1668     def getFileInfo(self, datastructure):
1669         """Get the file info from the datastructure."""
1670         widget_id = self.getWidgetId()
1671         fileupload = datastructure[widget_id]
1672         dm = datastructure.getDataModel()
1673         field_id = self.fields[0]
1674         if fileupload:
1675             empty_file = False
1676             session_file = isinstance(fileupload, PersistableFileUpload)
1677             current_filename = cleanFileName(fileupload.filename)
1678             size = self.getFileSize(fileupload)
1679             file = dm[field_id] # last stored file
1680             if file is not None:
1681                 last_modified = str(file._p_mtime or '')
1682             else:
1683                 last_modified = ''
1684         else:
1685             empty_file = True
1686             session_file = False
1687             current_filename = ''
1688             size = 0
1689             last_modified = ''
1690
1691         # Find the URL for the file  XXX Refactor this!
1692
1693         # get the adapter
1694         for adapter in dm._adapters:
1695             if adapter.getSchema().has_key(field_id):
1696                 break # Note: 'adapter' is still the right one
1697         else:
1698             raise ValueError('No schema for field %r' % field_id)
1699
1700         # get the content_url from the adapter
1701         content_url = None
1702         ob = dm.getProxy()
1703         if ob is None:
1704             # non proxy case
1705             ob = dm.getObject()
1706         if ob is None:
1707             # Not stored in the ZODB.
1708             # StorageAdapters that do not store the object in
1709             # ZODB takes the entry_id instead of object.
1710             # Get the entry_id from the datamodel context(typically
1711             # a directory).
1712             id_field = getattr(dm.getContext(), 'id_field', None)
1713             if id_field:
1714                 try:
1715                     entry_id = datastructure[id_field]
1716                 except KeyError:
1717                     entry_id = None
1718             else:
1719                 # No object passed, and no id_field
1720                 entry_id = None
1721             if entry_id:
1722                 # some adapters does not have _getContentUrl
1723                 if getattr(adapter, '_getContentUrl', None) is not None:
1724                     content_url = adapter._getContentUrl(entry_id, field_id)
1725         else:
1726             content_url = adapter._getContentUrl(ob, field_id,
1727                                                  current_filename)
1728
1729         # get the mimetype
1730         registry = getToolByName(self, 'mimetypes_registry')
1731         mimetype = (registry.lookupExtension(current_filename.lower()) or
1732                     registry.lookupExtension('file.bin'))
1733         # Using a title if there is one present in the datastructure
1734         # otherwise defaulting to the file name.
1735         title = datastructure.get(widget_id + '_title', current_filename)
1736
1737         file_info = {
1738             'empty_file': empty_file,
1739             'session_file': session_file,
1740             'current_filename': current_filename,
1741             'title': title,
1742             'size': size,
1743             'last_modified': last_modified,
1744             'content_url': content_url,
1745             'mimetype': mimetype,
1746             }
1747
1748         return file_info
1749
1750     def prepare(self, datastructure, **kw):
1751         """Prepare datastructure from datamodel."""
1752         datamodel = datastructure.getDataModel()
1753         widget_id = self.getWidgetId()
1754         file = datamodel[self.fields[0]]
1755         datastructure[widget_id] = makeFileUploadFromOFSFile(file)
1756         datastructure[widget_id + '_choice'] = ''
1757         if file is not None:
1758             filename = file.title
1759         else:
1760             filename = ''
1761         datastructure[widget_id + '_filename'] = filename
1762
1763         if len(self.fields) > 1:
1764             datastructure[widget_id + '_title'] = datamodel[self.fields[1]]
1765         else:
1766             datastructure[widget_id + '_title'] = ''
1767
1768     def unprepare(self, datastructure):
1769         # Remove costly things already stored from the datastructure
1770         try:
1771             del datastructure[self.getWidgetId()]
1772         except KeyError:
1773             # unprepare may be called several times
1774             pass
1775
1776     def getFileName(self, fileupload, datastructure, choice, old_filename=''):
1777         filename = datastructure[self.getWidgetId()+'_filename'].strip()
1778         if choice == 'change' and filename == old_filename:
1779             # if upload with input field unchanged, use fileupload filename
1780             filename = cookId('', '', fileupload)[0].strip()
1781         filename = cleanFileName(filename or 'file.bin')
1782         return filename
1783
1784     def checkFileName(self, filename, mimetype):
1785         return '', {}
1786
1787     def makeFile(self, filename, fileupload, datastructure):
1788         return File(self.fields[0], filename, fileupload)
1789
1790     def otherProcessing(self, choice, datastructure):
1791         return
1792
1793     def validate(self, datastructure, **kw):
1794         """Update datamodel from user data in datastructure.
1795         """
1796         datamodel = datastructure.getDataModel()
1797         field_id = self.fields[0]
1798         widget_id = self.getWidgetId()
1799         choice = datastructure[widget_id + '_choice']
1800         store = False
1801         fileupload = None
1802         mimetype = None
1803         old_file = datamodel[field_id]
1804         if old_file is not None:
1805             old_filename = old_file.title
1806         else:
1807             old_filename = ''
1808
1809         if choice == 'delete':
1810             if self.is_required:
1811                 return self.validateError('cpsschemas_err_required', {},
1812                                           datastructure)
1813             datamodel[field_id] = None
1814         elif choice == 'keep':
1815             fileupload = datastructure[widget_id]
1816             if fileupload is None and self.is_required:
1817                 return self.validateError('cpsschemas_err_required', {},
1818                                           datastructure)
1819
1820             if isinstance(fileupload, PersistableFileUpload):
1821                 # Keeping something from the session means we
1822                 # actually want to store it.
1823                 store = True
1824             else:
1825                 # Nothing to change, don't pollute datastructure
1826                 # with something costly already stored, which therefore
1827                 # doesn't need to be kept in the session.
1828                 self.unprepare(datastructure)
1829         elif choice == 'change':
1830             fileupload = datastructure[widget_id]
1831             if not fileupload:
1832                 return self.validateError('cpsschemas_err_file_empty', {},
1833                                           datastructure)
1834             if not isinstance(fileupload, FileUpload):
1835                 return self.validateError('cpsschemas_err_file', {},
1836                                           datastructure)
1837             size = self.getFileSize(fileupload)
1838             if not size:
1839                 return self.validateError('cpsschemas_err_file_empty', {},
1840                                           datastructure)
1841             if self.size_max and size > self.size_max:
1842                 max_size_str = self.getHumanReadableSize(self.size_max)
1843                 err = 'cpsschemas_err_file_too_big ${max_size}'
1844                 err_mapping = {'max_size': max_size_str}
1845                 return self.validateError(err, err_mapping, datastructure)
1846             store = True
1847
1848         self.otherProcessing(choice, datastructure)
1849
1850         # Find filename
1851         if fileupload is not None:
1852             filename = self.getFileName(fileupload, datastructure, choice,
1853                                         old_filename)
1854             if filename != old_filename:
1855                 registry = getToolByName(self, 'mimetypes_registry')
1856                 mimetype = registry.lookupExtension(filename.lower())
1857                 if mimetype is not None:
1858                     mimetype = str(mimetype) # normalize
1859                 err, err_mapping = self.checkFileName(filename, mimetype)
1860                 if err:
1861                     return self.validateError(err, err_mapping, datastructure)
1862         elif datamodel[field_id] is not None:
1863             # FIXME: not correct in the case of change=='resize' (CPSPhotoWidget)
1864             filename = datamodel[field_id].title
1865
1866         # Set/update data
1867         if store:
1868             # Create file
1869             file = self.makeFile(filename, fileupload, datastructure)
1870             # Fixup mimetype
1871             if mimetype and file.content_type != mimetype:
1872                 file.content_type = mimetype
1873             # Store
1874             datamodel[field_id] = file
1875         elif datamodel[field_id] is not None:
1876             # Change filename
1877             if datamodel[field_id].title != filename:
1878                 datamodel[field_id].title = filename
1879
1880         return True
1881
1882     def validateError(self, err, err_mapping, datastructure):
1883         self.logger.debug("Validation error %s", err)
1884         # Do not keep rejected file, revert to older
1885         self.unprepare(datastructure)
1886         datastructure.setError(self.getWidgetId(), err, err_mapping)
1887         return False
1888
1889     def render(self, mode, datastructure, **kw):
1890         """Render this widget from the datastructure or datamodel."""
1891         render_method = 'widget_file_render'
1892         meth = getattr(self, render_method, None)
1893         if meth is None:
1894             raise RuntimeError("Unknown Render Method %s for widget type %s"
1895                                % (render_method, self.getId()))
1896         file_info = self.getFileInfo(datastructure)
1897
1898         return meth(mode=mode, datastructure=datastructure, **file_info)
1899
1900 InitializeClass(CPSFileWidget)
1901
1902 widgetRegistry.register(CPSFileWidget)
1903
1904 ##################################################
1905
1906 class CPSImageWidget(CPSFileWidget):
1907     """Image widget."""
1908     meta_type = 'Image Widget'
1909
1910     field_types = ('CPS Image Field',
1911                    'CPS String Field',  # Title
1912                    'CPS String Field',  # Alternate text for accessibility
1913                    )
1914
1915     _properties = CPSFileWidget._properties + (
1916         {'id': 'display_width', 'type': 'int', 'mode': 'w',
1917          'label': 'Display width'},
1918         {'id': 'display_height', 'type': 'int', 'mode': 'w',
1919          'label': 'Display height'},
1920         {'id': 'allow_resize', 'type': 'boolean', 'mode': 'w',
1921          'label': 'Enable to resize img to lower size'},
1922         )
1923
1924     display_height = 0
1925     display_width = 0
1926     allow_resize = 0
1927
1928     logger = getLogger('CPSSchemas.BasicWidgets.CPSImageWidget')
1929
1930     def updateImageInfoForEmail(self, info, datastructure, dump=False):
1931         """Use the cid: URL scheme (RFC 2392) and dump parts in datastructure.
1932         """
1933
1934         info['mime_content_id'] = cid = make_cid(self.getHtmlWidgetId())
1935         info['content_url'] = 'cid:' + cid
1936         if not dump:
1937             return
1938
1939         fupload = datastructure[self.getWidgetId()]
1940         if fupload is not None:
1941             fupload.seek(0)
1942             parts = datastructure.setdefault(CIDPARTS_KEY, {})
1943             parts[cid] = {'content': fupload.read(),
1944                           'filename': info['current_filename'],
1945                           'content-type': info['mimetype'],
1946                           }
1947             fupload.seek(0)
1948
1949     def getImageInfo(self, datastructure, dump_cid_parts=False, **kw):
1950         """Get the image info from the datastructure.
1951
1952         if 'dump_cid_parts' is True, the image is dumped in the datastructure.
1953         This is a side-effect, but it's been made such to avoid code duplication
1954         in subclasses' render methods. To update these latter, just
1955         set this kwarg to True in the call of getImageInfo and pass **kw.
1956         """
1957         image_info = self.getFileInfo(datastructure)
1958         if kw.get('layout_mode') == EMAIL_LAYOUT_MODE:
1959             self.updateImageInfoForEmail(image_info, datastructure,
1960                                          dump=dump_cid_parts)
1961
1962         if image_info['empty_file']:
1963             height = 0
1964             width = 0
1965             tag = ''
1966             alt = ''
1967         else:
1968             widget_id = self.getWidgetId()
1969             image = datastructure[widget_id]
1970             from OFS.Image import getImageInfo
1971             image.seek(0)
1972             data = image.read(24)
1973             ct, width, height = getImageInfo(data)
1974
1975             if width < 0:
1976                 width = None
1977             if height < 0:
1978                 height = None
1979
1980             if (self.allow_resize
1981                 and height is not None
1982                 and width  is not None):
1983                 z_w = z_h = 1
1984                 h = int(self.display_height)
1985                 w = int(self.display_width)
1986                 if w and h:
1987                     if w < width:
1988                         z_w = w / float(width)
1989                     if h < height:
1990                         z_h = h / float(height)
1991                     zoom = min(z_w, z_h)
1992                     width = int(zoom * width)
1993                     height = int(zoom * height)
1994
1995             title = image_info['title']
1996             # Using an alt text if there is one present in the datastructure
1997             # otherwise defaulting to the title.
1998             alt = datastructure.get(widget_id + '_alt', title)
1999             if height is None or width is None:
2000                 tag = renderHtmlTag('img', src=image_info['content_url'],
2001                                     title=title, alt=alt)
2002             else:
2003                 tag = renderHtmlTag('img', src=image_info['content_url'],
2004                                     width=str(width), height=str(height),
2005                                     title=title, alt=alt)
2006
2007         image_info['height'] = height
2008         image_info['width'] = width
2009         image_info['image_tag'] = tag
2010         image_info['alt'] = alt
2011         return image_info
2012
2013     def getResizedImage(self, file, filename, resize_op):
2014         """Get the resized image from information in datastructure"""
2015         file = StringIO(str(file.data)) # XXX use OFSFileIO
2016         size = (self.display_width,
2017                 self.display_height)
2018
2019         # NB: skin method: CPSSchemas/skins/cps_schemas/getImgSizes.py
2020         for s in self.getImgSizes():
2021             if s['id'] == resize_op:
2022                 resize = s['size']
2023                 break
2024         else:
2025             resize = None
2026         if resize and resize < size:
2027             size = resize
2028         if size[0] and size[1]:
2029             try:
2030                 img = PIL.Image.open(file)
2031                 img.thumbnail(size,
2032                               resample=PIL.Image.ANTIALIAS)
2033                 # We now need a buffer to write to. It can't be the same
2034                 # as the inbuffer as the PNG writer will write over itself.
2035                 outfile = StringIO()
2036                 img.save(outfile, format=img.format)
2037             except (NameError, IOError, ValueError, SystemError), err:
2038                 self.logger.warning(
2039                     "Failed to resize file %s keep original (%s)", filename, err)
2040                 outfile = file
2041         # XXX: is this the correct default behaviour ?
2042         else:
2043             outfile = file
2044         image = Image(self.fields[0], filename, outfile)
2045         return image
2046
2047     def prepare(self, datastructure, **kw):
2048         """Prepare datastructure from datamodel."""
2049         CPSFileWidget.prepare(self, datastructure, **kw)
2050         datamodel = datastructure.getDataModel()
2051         widget_id = self.getWidgetId()
2052         if self.allow_resize:
2053             datastructure[widget_id + '_resize'] = ''
2054
2055         title = ''
2056         if len(self.fields) > 1:
2057             title = datamodel[self.fields[1]]
2058         datastructure[widget_id + '_title'] = title
2059
2060         alt = ''
2061         if len(self.fields) > 2:
2062             alt = datamodel[self.fields[2]]
2063             # Defaulting to the file name if there is an image file and if no
2064             # alt has been given yet. This is the case when the document is
2065             # created.
2066             if not alt:
2067                 alt = datastructure[widget_id + '_filename']
2068         datastructure[widget_id + '_alt'] = alt
2069
2070     def otherProcessing(self, choice, datastructure):
2071         datamodel = datastructure.getDataModel()
2072         widget_id = self.getWidgetId()
2073
2074         # Title
2075         if len(self.fields) > 1:
2076             title = datastructure[widget_id + '_title']
2077             datamodel[self.fields[1]] = title
2078
2079         # Alt
2080         if len(self.fields) > 2:
2081             alt = datastructure[widget_id + '_alt']
2082             datamodel[self.fields[2]] = alt
2083
2084     def maybeKeepOriginal(self, image, datastructure):
2085         return
2086
2087     def makeFile(self, filename, fileupload, datastructure):
2088         image = Image(self.fields[0], filename, fileupload)
2089         if self.allow_resize:
2090             self.maybeKeepOriginal(image, datastructure)
2091             resize_op = datastructure[self.getWidgetId() + '_resize']
2092             image = self.getResizedImage(image, filename, resize_op)
2093         return image
2094
2095     def checkFileName(self, filename, mimetype):
2096         if mimetype and mimetype.startswith('image'):
2097             return '', {}
2098         return 'cpsschemas_err_image', {}
2099
2100     def render(self, mode, datastructure, **kw):
2101         img_info = self.getImageInfo(datastructure, dump_cid_parts=True, **kw)
2102         render_method = 'widget_image_render'
2103         meth = getattr(self, render_method, None)
2104         if meth is None:
2105             raise RuntimeError("Unknown Render Method %s for widget type %s"
2106                                % (render_method, self.getId()))
2107         return meth(mode=mode, datastructure=datastructure, **img_info)
2108
2109 InitializeClass(CPSImageWidget)
2110
2111 widgetRegistry.register(CPSImageWidget)
2112
2113 ##################################################
2114
2115 class CPSCompoundWidget(CPSWidget):
2116     """Widget with customizable logic and presentation.
2117
2118     Allows the use of other widgets to do the rendering.
2119     """
2120     meta_type = 'Compound Widget'
2121
2122     _properties = (
2123         CPSWidget._properties[:2] + (
2124         {'id': 'widget_ids', 'type': 'tokens', 'mode': 'w',
2125          'label': 'Widget ids'},
2126         {'id': 'render_method', 'type': 'string', 'mode': 'w',
2127          'label': 'Render Method'},
2128         {'id': 'prepare_validate_method', 'type': 'string', 'mode': 'w',
2129          'label': 'Prepare & Validate Method'},
2130         ) + CPSWidget._properties[2:]
2131         )
2132     widget_ids = []
2133     widget_type = None # Compat with old instances
2134     render_method = 'widget_compound_default_render'
2135     prepare_validate_method = ''
2136
2137     _old_render_methods = {
2138         'Link Widget': 'widget_link_render',
2139         'Text Image Widget': 'widget_textimage_render',
2140         'Search Widget': 'widget_search_render',
2141         'Image Link Widget': 'widget_imagelink_render',
2142         'Search Location Widget': 'widget_searchlocation_render',
2143         }
2144
2145     def _getRenderMethod(self):
2146         """Get the render method."""
2147         name = self._old_render_methods.get(self.widget_type,
2148                                             self.render_method)
2149         meth = getattr(self, name, None)
2150         if meth is None:
2151             raise RuntimeError("Unknown render method %r for widget %s" %
2152                                (name, self.getWidgetId()))
2153         return meth
2154
2155     _old_prepare_validate_methods = {
2156         'Link Widget': '',
2157         'Text Image Widget': 'widget_textimage_prepare_validate',
2158         'Search Widget': '',
2159         'Image Link Widget': 'widget_imagelink_prepare_validate',
2160         'Search Location Widget': 'widget_searchlocation_prepare_validate',
2161         }
2162
2163
2164     def _getPrepareValidateMethod(self):
2165         """Get the prepare/validate method."""
2166         # Compatibility for old instances
2167         name = self._old_prepare_validate_methods.get(self.widget_type,
2168                                                self.prepare_validate_method)
2169         if not name:
2170             meth = lambda *args, **kw: True
2171         else:
2172             meth = getattr(self, name, None)
2173         if meth is None:
2174             raise RuntimeError("Unknown prepare/validate method '%s' "
2175                                "for widget %s" % (name, self.getWidgetId()))
2176         return meth
2177
2178     def getFieldTypes(self):
2179         """Get field types from the underlying widgets."""
2180         return [] #X XXX
2181
2182     def getFieldInits(self):
2183         """Get field inits from the underlying widgets."""
2184         return [] # XXX
2185
2186     def prepare(self, datastructure, **kw):
2187         """Prepare the underlying widgets."""
2188         # Prepare each widget
2189         layout = aq_parent(aq_inner(self))
2190         for widget_id in self.widget_ids:
2191             widget = layout[widget_id]
2192             widget.prepare(datastructure, **kw)
2193         # Now prepare compound
2194         prepare = self._getPrepareValidateMethod()
2195         return prepare('prepare', datastructure)
2196
2197     def validate(self, datastructure, **kw):
2198         """Validate the underlying widgets."""
2199         validate = self._getPrepareValidateMethod()
2200         # Pre-validate compound (may fixup the datastructure)
2201         ret = validate('prevalidate', datastructure)
2202         if not ret and ret is not None:
2203             # None is allowed to mean "ok"
2204             return False
2205         # Now validate each widget
2206         layout = aq_parent(aq_inner(self))
2207         ret = True
2208         for widget_id in self.widget_ids:
2209             widget = layout[widget_id]
2210             ret = widget.validate(datastructure, **kw) and ret
2211         # Post-validate
2212         return ret and validate('validate', datastructure)
2213
2214     def render(self, mode, datastructure, **kw):
2215         """Render in mode from datastructure."""
2216         layout = aq_parent(aq_inner(self))
2217         widget_infos = kw['widget_infos']
2218         cells = []
2219         for widget_id in self.widget_ids:
2220             cell = {}
2221             # widget, widget_mode, css_class
2222             cell.update(widget_infos[widget_id])
2223             widget = layout[widget_id]
2224             widget_mode = cell['widget_mode']
2225             if widget_mode == 'hidden':
2226                 continue
2227             rendered = widget.render(widget_mode, datastructure, **kw)
2228             rendered = rendered.strip()
2229             cell['widget_rendered'] = rendered
2230             if not widget.hidden_empty or rendered:
2231                 # do not add widgets to be hidden when empty
2232                 cells.append(cell)
2233         render = self._getRenderMethod()
2234         return render(mode=mode, datastructure=datastructure,
2235                       cells=cells, **kw)
2236
2237 InitializeClass(CPSCompoundWidget)
2238
2239 widgetRegistry.register(CPSCompoundWidget)
2240
2241
2242 class CPSProgrammerCompoundWidget(CPSCompoundWidget):
2243     """Base class for compound widgets defined in code.
2244
2245     They don't need to have the "method" fields customizable,
2246     because these are defined by their class.
2247     """
2248     meta_type = 'Code Compound Widget'
2249     _properties = (
2250         CPSCompoundWidget._properties[:3] +
2251         # skip render_method
2252         # skip prepare_validate_method
2253         CPSCompoundWidget._properties[5:]
2254         )
2255     field_types = ()
2256
2257 InitializeClass(CPSProgrammerCompoundWidget)
2258
2259
2260 class CPSCustomizableWidget(CPSCompoundWidget):
2261     """Obsolete customizable widget.
2262
2263     Obsolete, kept for old instances. CPSCompoundWidget should
2264     be used for new widgets.
2265     """
2266     meta_type = 'Obsolete Customizable Widget'
2267
2268 InitializeClass(CPSCustomizableWidget)
2269
2270 ##################################################
2271
2272 class CPSBylineWidget(CPSWidget):
2273     """Byline widget showing credentials and document status."""
2274     meta_type = 'Byline Widget'
2275
2276     def prepare(self, datastructure, **kw):
2277         """Prepare datastructure from datamodel."""
2278         pass
2279
2280     def validate(self, datastructure, **kw):
2281         """Validate datastructure and update datamodel."""
2282         return True
2283
2284     def render(self, mode, datastructure, **kw):
2285         """Render in mode from datastructure."""
2286         datamodel = datastructure.getDataModel()
2287         doc = datamodel.getObject()
2288         proxy = datamodel.getProxy()
2289         if proxy is None:
2290             proxy = doc
2291         render_method = 'widget_byline_render'
2292         meth = getattr(self, render_method, None)
2293         if meth is None:
2294             raise RuntimeError("Unknown Render Method %s for widget type %s"
2295                                % (render_method, self.getId()))
2296         return meth(mode=mode, proxy=proxy, doc=doc)
2297
2298 InitializeClass(CPSBylineWidget)
2299
2300 widgetRegistry.register(CPSBylineWidget)
2301
2302 ##################################################
2303
2304 class CPSRevisionWidget(CPSWidget):
2305     """Display the revision of the document"""
2306     meta_type = 'Revision Widget'
2307
2308     def prepare(self, datastructure, **kw):
2309         """Prepare datastructure from datamodel."""
2310         pass
2311
2312     def validate(self, datastructure, **kw):
2313         """Validate datastructure and update datamodel."""
2314         return 1
2315
2316     def render(self, mode, datastructure, **kw):
2317         """Render in mode from datastructure."""
2318         datamodel = datastructure.getDataModel()
2319         # get the proxy containing this widget
2320         proxy = datamodel.getProxy()
2321         if proxy and getattr(aq_base(proxy), 'getRevision', None) is not None:
2322             return str(proxy.getRevision())
2323         else:
2324             return ''
2325
2326 InitializeClass(CPSRevisionWidget)
2327
2328 widgetRegistry.register(CPSRevisionWidget)
Note: See TracBrowser for help on using the browser.