| 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) |
|---|