from copy import deepcopy
from types import DictType, FileType, ListType, StringTypes
from Products.CMFCore.utils import getToolByName
from Products.CMFCore.Expression import Expression
from Products.CMFCore.Expression import createExprContext
from Products.Archetypes.utils import className
from Products.Archetypes.utils import unique
from Products.Archetypes.utils import capitalize
from Products.generator.widget import macrowidget
from Products.Archetypes.debug import log
from Products.Archetypes.Registry import registerPropertyType
from Products.Archetypes.Registry import registerWidget
from ExtensionClass import Base
from AccessControl import ClassSecurityInfo
from Globals import InitializeClass
from Acquisition import aq_base
from Acquisition import Implicit
_marker = []
class TypesWidget(macrowidget, Base):
_properties = macrowidget._properties.copy()
_properties.update({
'modes' : ('view', 'edit'),
'populate' : True, # should this field be populated in edit and view?
'postback' : True, # should this field be repopulated with POSTed
# value when an error occurs?
'show_content_type' : False,
'helper_js': (),
'helper_css': (),
})
security = ClassSecurityInfo()
security.declarePublic('getName')
def getName(self):
return self.__class__.__name__
security.declarePublic('getType')
def getType(self):
"""Return the type of this field as a string"""
return className(self)
security.declarePublic('bootstrap')
def bootstrap(self, instance):
"""Override if your widget needs data from the instance."""
return
security.declarePublic('populateProps')
def populateProps(self, field):
"""This is called when the field is created."""
name = field.getName()
if not self.label:
self.label = capitalize(name)
security.declarePublic('isVisible')
def isVisible(self, instance, mode='view'):
"""decide if a field is visible in a given mode -> 'state'
Return values are visible, hidden, invisible
The value for the attribute on the field may either be a dict with a
mapping for edit and view::
visible = { 'edit' :'hidden', 'view' : 'invisible' }
Or a single value for all modes::
True/1: 'visible'
False/0: 'invisible'
-1: 'hidden'
visible: The field is shown in the view/edit screen
invisible: The field is skipped when rendering the view/edit screen
hidden: The field is added as
The default state is 'visible'.
"""
vis_dic = getattr(aq_base(self), 'visible', _marker)
state = 'visible'
if vis_dic is _marker:
return state
if type(vis_dic) is DictType:
state = vis_dic.get(mode, state)
elif not vis_dic:
state = 'invisible'
elif vis_dic < 0:
state = 'hidden'
#assert(state in ('visible', 'hidden', 'invisible',),
# 'Invalid view state %s' % state
# )
return state
# XXX
security.declarePublic('setCondition')
def setCondition(self, condition):
"""Set the widget expression condition."""
self.condition = condition
security.declarePublic('getCondition')
def getCondition(self):
"""Return the widget text condition."""
return self.condition
security.declarePublic('testCondition')
def testCondition(self, folder, portal, object):
"""Test the widget condition."""
try:
if self.condition:
__traceback_info__ = (folder, portal, object, self.condition)
ec = createExprContext(folder, portal, object)
return Expression(self.condition)(ec)
else:
return True
except AttributeError:
return True
# XXX
security.declarePublic('process_form')
def process_form(self, instance, field, form, empty_marker=None,
emptyReturnsMarker=False):
"""Basic impl for form processing in a widget"""
value = form.get(field.getName(), empty_marker)
if value is empty_marker:
return empty_marker
if emptyReturnsMarker and value == '':
return empty_marker
return value, {}
security.declarePublic('copy')
def copy(self):
"""
Return a copy of widget instance, consisting of field name and
properties dictionary.
"""
cdict = dict(vars(self))
properties = deepcopy(cdict)
return self.__class__(**properties)
InitializeClass(TypesWidget)
class StringWidget(TypesWidget):
_properties = TypesWidget._properties.copy()
_properties.update({
'macro' : "widgets/string",
'size' : '30',
'maxlength' : '255',
})
security = ClassSecurityInfo()
class DecimalWidget(TypesWidget):
_properties = TypesWidget._properties.copy()
_properties.update({
'macro' : "widgets/decimal",
'size' : '5',
'maxlength' : '255',
'dollars_and_cents' : False,
'whole_dollars' : False,
'thousands_commas' : False,
})
security = ClassSecurityInfo()
class IntegerWidget(TypesWidget):
_properties = TypesWidget._properties.copy()
_properties.update({
'macro' : "widgets/integer",
'size' : '5',
'maxlength' : '255',
})
security = ClassSecurityInfo()
class ReferenceWidget(TypesWidget):
_properties = TypesWidget._properties.copy()
_properties.update({
'macro' : "widgets/reference",
'checkbox_bound': 5,
'addable' : False, # create createObject link for every addable type
'destination_types' : None,
'destination' : None, # may be:
# - ".", context object;
# - None, any place where
# Field.allowed_types can be added;
# - string path;
# - name of method on instance
# (it can be a combination list);
# - a list, combining all item above;
# - a dict, where
# {portal_type:}
# destination is relative to portal root
'helper_css' : ('content_types.css',),
})
security = ClassSecurityInfo()
def lookupDestinationsFor(self, typeinfo, tool, purl, destination_types=None):
"""
search where the user can add a typeid instance
"""
searchFor = []
# first, discover who can contain the type
if destination_types is not None:
if type(destination_types) in (type(()), type([])):
searchFor += list(destination_types[:])
else:
searchFor.append(destination_types)
else:
for regType in tool.listTypeInfo():
if typeinfo.globalAllow():
searchFor.append(regType.getId())
elif regType.filter_content_types and regType.allowed_content_types:
act_dict = dict([ (act, 0) for act in regType.allowed_content_types ])
if act_dict.has_key(typeinfo.getId()):
searchFor.append(regType.getId())
catalog = getToolByName(purl, 'portal_catalog')
containers = []
portal_path = "/".join(purl.getPortalObject().getPhysicalPath())
for wanted in searchFor:
for brain in catalog(portal_type=wanted):
relative_path = brain.getPath().replace(portal_path + '/', '')
containers.append(relative_path)
return containers
security.declarePublic('addableTypes')
def addableTypes(self, instance, field):
""" Returns a list of dictionaries which maps portal_type
to a human readable form.
"""
tool = getToolByName(instance, 'portal_types')
purl = getToolByName(instance, 'portal_url')
lookupDestinationsFor = self.lookupDestinationsFor
getRelativeContentURL = purl.getRelativeContentURL
# if destination_types is None (by default) it will do
# N-portal_types queries to the catalog which is horribly inefficient
destination_types = getattr(self, 'destination_types', None)
destination = self.destination
types = []
options = {}
for typeid in field.allowed_types:
_info = tool.getTypeInfo(typeid)
if _info is None:
# The portal_type asked for was not
# installed/has been removed.
log("Warning: in Archetypes.Widget.lookupDestinationsFor: " \
"portal type %s not found" % typeid )
continue
if destination == None:
options[typeid]=[None]
elif isinstance(destination, DictType):
options[typeid]=destination.get(typeid, [None])
if not isinstance(options[typeid], ListType):
options[typeid] = [options[typeid]]
elif isinstance(destination, ListType):
options[typeid]=destination
else:
place = getattr(aq_base(instance), destination, destination)
if callable(place):
#restore acq.wrapper
place = getattr(instance, destination)
place = place()
if isinstance(place, ListType):
options[typeid] = place
else:
options[typeid] = [place]
value = {}
value['id'] = typeid
value['name'] = _info.Title()
value['destinations'] = []
for option in options.get(typeid):
if option == None:
value['destinations'] = value['destinations'] + \
lookupDestinationsFor(_info, tool, purl,
destination_types=destination_types)
elif option == '.':
value['destinations'].append(getRelativeContentURL(instance))
else:
try:
place = getattr(aq_base(instance), option, option)
except TypeError:
place = option
if callable(place):
#restore acq.wrapper
place = getattr(instance, option)
place = place()
if isinstance(place, ListType):
value['destinations'] = place + value['destinations']
else:
#XXX Might as well check for type, doing it everywhere else
value['destinations'].append(place)
if value['destinations']:
types.append(value)
return types
class ComputedWidget(TypesWidget):
_properties = TypesWidget._properties.copy()
_properties.update({
'macro' : "widgets/computed",
})
security = ClassSecurityInfo()
class TextAreaWidget(TypesWidget):
_properties = TypesWidget._properties.copy()
_properties.update({
'macro' : "widgets/textarea",
'rows' : 5,
'cols' : 40,
'format': 0,
'append_only': False,
'divider':"\n\n========================\n\n",
})
security = ClassSecurityInfo()
# XXX
security.declarePublic('process_form')
def process_form(self, instance, field, form, empty_marker=None,
emptyReturnsMarker=False):
"""handle text formatting"""
text_format = None
value = None
# text field with formatting
value = form.get(field.getName(), empty_marker)
if value is empty_marker:
return empty_marker
if emptyReturnsMarker and value == '':
return empty_marker
if hasattr(field, 'allowable_content_types') and \
field.allowable_content_types:
format_field = "%s_text_format" % field.getName()
text_format = form.get(format_field, empty_marker)
kwargs = {}
if text_format is not empty_marker and text_format:
kwargs['mimetype'] = text_format
""" handle append_only """
# SPANKY: It would be nice to add a datestamp too, if desired
# Don't append if the existing data is empty or nothing was passed in
if getattr(field.widget, 'append_only', None):
if field.getEditAccessor(instance)():
if (value and not value.isspace()):
# using default_output_type caused a recursive transformation
# that sucked, thus mimetype= here to keep it in line
value = value + field.widget.divider + \
field.getEditAccessor(instance)()
else:
# keep historical entries
value = field.getEditAccessor(instance)()
return value, kwargs
class LinesWidget(TypesWidget):
_properties = TypesWidget._properties.copy()
_properties.update({
'macro' : "widgets/lines",
'rows' : 5,
'cols' : 40,
})
security = ClassSecurityInfo()
class BooleanWidget(TypesWidget):
_properties = TypesWidget._properties.copy()
_properties.update({
'macro' : "widgets/boolean",
})
security = ClassSecurityInfo()
class CalendarWidget(TypesWidget):
_properties = TypesWidget._properties.copy()
_properties.update({
'macro' : "widgets/calendar",
'format' : '', # time.strftime string
# the following five vars aren't supported by the plone templates yet
'show_hm' : True,
'show_ymd' : True, # False not supported by the plone templates yet
'starting_year' : 1999, # not supported by the plone templates yet
'ending_year' : None, # not supported by the plone templates yet
'future_years' : 5, # not supported by the plone templates yet
'helper_js': ('jscalendar/calendar_stripped.js',
'jscalendar/calendar-en.js'),
'helper_css': ('jscalendar/calendar-system.css',),
})
security = ClassSecurityInfo()
class SelectionWidget(TypesWidget):
_properties = TypesWidget._properties.copy()
_properties.update({
'format': "flex", # possible values: flex, select, radio
'macro' : "widgets/selection",
})
security = ClassSecurityInfo()
class MultiSelectionWidget(TypesWidget):
_properties = TypesWidget._properties.copy()
_properties.update({
'format': "select", # possible values: select, checkbox
'macro' : "widgets/multiselection",
'size' : 5,
})
security = ClassSecurityInfo()
security.declarePublic('process_form')
def process_form(self, instance, field, form, empty_marker=None,
emptyReturnsMarker=False):
"""Basic impl for form processing in a widget"""
value = form.get(field.getName(), empty_marker)
if value is empty_marker:
return empty_marker
if emptyReturnsMarker and value == '':
return empty_marker
if isinstance(value, StringTypes):
values = [v.strip() for v in value.split('\n')]
elif isinstance(value, ListType):
values = value
else:
values = []
return values, {}
class KeywordWidget(TypesWidget):
_properties = TypesWidget._properties.copy()
_properties.update({
'macro' : "widgets/keyword",
'size' : 5,
'vocab_source' : 'portal_catalog',
'roleBasedAdd' : True,
})
security = ClassSecurityInfo()
# XXX
security.declarePublic('process_form')
def process_form(self, instance, field, form, empty_marker=None,
emptyReturnsMarker=False):
"""process keywords from form where this widget has a list of
available keywords and any new ones"""
name = field.getName()
existing_keywords = form.get('%s_existing_keywords' % name, empty_marker)
if existing_keywords is empty_marker:
existing_keywords = []
new_keywords = form.get('%s_keywords' % name, empty_marker)
if new_keywords is empty_marker:
new_keywords = []
value = existing_keywords + new_keywords
value = [k for k in list(unique(value)) if k]
if not value and emptyReturnsMarker: return empty_marker
return value, {}
class FileWidget(TypesWidget):
_properties = TypesWidget._properties.copy()
_properties.update({
'macro' : "widgets/file",
'show_content_type' : True,
})
security = ClassSecurityInfo()
# XXX
security.declarePublic('process_form')
def process_form(self, instance, field, form, empty_marker=None,
emptyReturnsMarker=False):
"""form processing that deals with binary data"""
delete = form.get('%s_delete' % field.getName(), empty_marker)
if delete=='delete': return "DELETE_FILE", {}
if delete=='nochange' : return empty_marker
value = None
fileobj = form.get('%s_file' % field.getName(), empty_marker)
if fileobj is empty_marker: return empty_marker
filename = getattr(fileobj, 'filename', '') or \
(isinstance(fileobj, FileType) and \
getattr(fileobj, 'name', ''))
if filename:
value = fileobj
if not value: return None
return value, {}
class RichWidget(TypesWidget):
_properties = TypesWidget._properties.copy()
_properties.update({
'macro' : "widgets/rich",
'rows' : 5,
'cols' : 40,
'format': 1,
'allow_file_upload': True,
})
security = ClassSecurityInfo()
# XXX
security.declarePublic('process_form')
def process_form(self, instance, field, form, empty_marker=None,
emptyReturnsMarker=False):
"""complex form processing, includes handling for text
formatting and file objects"""
# This is basically the old processing chain from base object
text_format = None
isFile = False
value = None
# text field with formatting
if hasattr(field, 'allowable_content_types') and \
field.allowable_content_types:
# was a mimetype specified
format_field = "%s_text_format" % field.getName()
text_format = form.get(format_field, empty_marker)
# or a file?
fileobj = form.get('%s_file' % field.getName(), empty_marker)
if fileobj is not empty_marker:
filename = getattr(fileobj, 'filename', '') or \
(isinstance(fileobj, FileType) and \
getattr(fileobj, 'name', ''))
if filename:
value = fileobj
isFile = True
kwargs = {}
if not value:
value = form.get(field.getName(), empty_marker)
if text_format is not empty_marker and text_format:
kwargs['mimetype'] = text_format
if value is empty_marker: return empty_marker
if value and not isFile:
# Value filled, no file uploaded
if kwargs.get('mimetype') == str(field.getContentType(instance)) \
and instance.isBinary(field.getName()):
# Was an uploaded file, same content type
del kwargs['mimetype']
return value, kwargs
class IdWidget(TypesWidget):
_properties = TypesWidget._properties.copy()
_properties.update({
'macro' : "widgets/zid",
# show IDs in edit boxes when they are autogenerated?
'display_autogenerated' : True,
# script used to determine if an ID is autogenerated
'is_autogenerated' : 'isIDAutoGenerated',
})
security = ClassSecurityInfo()
# XXX
security.declarePublic('process_form')
def process_form(self, instance, field, form, empty_marker=None,
emptyReturnsMarker=False):
"""the id might be hidden by the widget and not submitted"""
value = form.get('id', empty_marker)
if not value or value is empty_marker or not value.strip():
value = instance.getId()
return value, {}
class RequiredIdWidget(IdWidget):
_properties = IdWidget._properties.copy()
_properties.update({
})
security = ClassSecurityInfo()
# XXX
security.declarePublic('process_form')
def process_form(self, instance, field, form, empty_marker=None):
"""Override IdWidget.process_form to require id."""
return TypesWidget.process_form(self, instance, field, form, empty_marker)
class ImageWidget(FileWidget):
_properties = FileWidget._properties.copy()
_properties.update({
'macro' : "widgets/image",
# only display if size <= threshold, otherwise show link
'display_threshold': 102400,
})
security = ClassSecurityInfo()
# XXX
security.declarePublic('process_form')
def process_form(self, instance, field, form, empty_marker=None,
emptyReturnsMarker=False):
"""form processing that deals with image data (and its delete case)"""
value = None
## check to see if the delete hidden was selected
delete = form.get('%s_delete' % field.getName(), empty_marker)
if delete=='delete': return "DELETE_IMAGE", {}
if delete=='nochange' : return empty_marker
fileobj = form.get('%s_file' % field.getName(), empty_marker)
if fileobj is empty_marker: return empty_marker
filename = getattr(fileobj, 'filename', '') or \
(isinstance(fileobj, FileType) and \
getattr(fileobj, 'name', ''))
if filename:
value = fileobj
if not value: return None
return value, {}
# LabelWidgets are used to display instructions on a form. The widget only
# displays the label for a value -- no values and no form elements.
class LabelWidget(TypesWidget):
_properties = TypesWidget._properties.copy()
_properties.update({
'macro' : "widgets/label",
})
security = ClassSecurityInfo()
class PasswordWidget(TypesWidget):
_properties = TypesWidget._properties.copy()
_properties.update({
'macro' : 'widgets/password',
'modes' : ('edit',),
'populate' : False,
'postback' : False,
'size' : 20,
'maxlength' : '255',
})
security = ClassSecurityInfo()
class VisualWidget(TextAreaWidget):
_properties = TextAreaWidget._properties.copy()
_properties.update({
'macro' : "widgets/visual",
'rows' : 25, #rows of TextArea if VE is not available
'cols' : 80, #same for cols
'width' : '507px', #width of VE frame (if VE is avalilable)
'height': '400px', #same for height
'format': 0,
'append_only': False, #creates a textarea you can only add to, not edit
'divider': '\n\n\n\n', # default divider for append only divider
})
security = ClassSecurityInfo()
class EpozWidget(TextAreaWidget):
_properties = TextAreaWidget._properties.copy()
_properties.update({
'macro' : "widgets/epoz",
})
security = ClassSecurityInfo()
class InAndOutWidget(ReferenceWidget):
_properties = ReferenceWidget._properties.copy()
_properties.update({
'macro' : "widgets/inandout",
'size' : '6',
'helper_js': ('widgets/js/inandout.js',),
})
security = ClassSecurityInfo()
class PicklistWidget(TypesWidget):
_properties = TypesWidget._properties.copy()
_properties.update({
'macro' : "widgets/picklist",
'size' : '6',
'helper_js': ('widgets/js/picklist.js',),
})
security = ClassSecurityInfo()
__all__ = ('StringWidget', 'DecimalWidget', 'IntegerWidget',
'ReferenceWidget', 'ComputedWidget', 'TextAreaWidget',
'LinesWidget', 'BooleanWidget', 'CalendarWidget',
'SelectionWidget', 'MultiSelectionWidget', 'KeywordWidget',
'RichWidget', 'FileWidget', 'IdWidget', 'ImageWidget',
'LabelWidget', 'PasswordWidget', 'VisualWidget', 'EpozWidget',
'InAndOutWidget', 'PicklistWidget', 'RequiredIdWidget',
)
registerWidget(StringWidget,
title='String',
description=('Renders a HTML text input box which '
'accepts a single line of text'),
used_for=('Products.Archetypes.Field.StringField',)
)
registerWidget(DecimalWidget,
title='Decimal',
description=('Renders a HTML text input box which '
'accepts a fixed point value'),
used_for=('Products.Archetypes.Field.FixedPointField',)
)
registerWidget(IntegerWidget,
title='Integer',
description=('Renders a HTML text input box which '
'accepts a integer value'),
used_for=('Products.Archetypes.Field.IntegerField',)
)
registerWidget(ReferenceWidget,
title='Reference',
description=('Renders a HTML text input box which '
'accepts a reference value'),
used_for=('Products.Archetypes.Field.ReferenceField',)
)
registerWidget(ComputedWidget,
title='Computed',
description='Renders the computed value as HTML',
used_for=('Products.Archetypes.Field.ComputedField',)
)
registerWidget(TextAreaWidget,
title='Text Area',
description=('Renders a HTML Text Area for typing '
'a few lines of text'),
used_for=('Products.Archetypes.Field.StringField',
'Products.Archetypes.Field.TextField')
)
registerWidget(LinesWidget,
title='Lines',
description=('Renders a HTML textarea for a list '
'of values, one per line'),
used_for=('Products.Archetypes.Field.LinesField',)
)
registerWidget(BooleanWidget,
title='Boolean',
description='Renders a HTML checkbox',
used_for=('Products.Archetypes.Field.BooleanField',)
)
registerWidget(CalendarWidget,
title='Calendar',
description=('Renders a HTML input box with a helper '
'popup box for choosing dates'),
used_for=('Products.Archetypes.Field.DateTimeField',)
)
registerWidget(SelectionWidget,
title='Selection',
description=('Renders a HTML selection widget, which '
'can be represented as a dropdown, or as '
'a group of radio buttons'),
used_for=('Products.Archetypes.Field.StringField',
'Products.Archetypes.Field.LinesField',)
)
registerWidget(MultiSelectionWidget,
title='Multi Selection',
description=('Renders a HTML selection widget, where '
'you can be choose more than one value'),
used_for=('Products.Archetypes.Field.LinesField',)
)
registerWidget(KeywordWidget,
title='Keyword',
description='Renders a HTML widget for choosing keywords',
used_for=('Products.Archetypes.Field.LinesField',)
)
registerWidget(RichWidget,
title='Rich Widget',
description=('Renders a HTML widget that allows you to '
'type some content, choose formatting '
'and/or upload a file'),
used_for=('Products.Archetypes.Field.TextField',)
)
registerWidget(FileWidget,
title='File',
description='Renders a HTML widget upload a file',
used_for=('Products.Archetypes.Field.FileField',)
)
registerWidget(IdWidget,
title='ID',
description='Renders a HTML widget for typing an Id',
used_for=('Products.Archetypes.Field.StringField',)
)
registerWidget(RequiredIdWidget,
title='ID',
description='Renders a HTML widget for typing an required Id',
used_for=('Products.Archetypes.Field.StringField',)
)
registerWidget(ImageWidget,
title='Image',
description=('Renders a HTML widget for '
'uploading/displaying an image'),
used_for=('Products.Archetypes.Field.ImageField',)
)
registerWidget(LabelWidget,
title='Label',
description=('Renders a HTML widget that only '
'displays the label'),
used_for=None
)
registerWidget(PasswordWidget,
title='Password',
description='Renders a HTML password widget',
used_for=('Products.Archetypes.Field.StringField',)
)
registerWidget(VisualWidget,
title='Visual',
description='Renders a HTML visual editing widget widget',
used_for=('Products.Archetypes.Field.StringField',)
)
registerWidget(EpozWidget,
title='Epoz',
description='Renders a HTML Epoz widget',
used_for=('Products.Archetypes.Field.StringField',)
)
registerWidget(InAndOutWidget,
title='In & Out',
description=('Renders a widget for moving items '
'from one list to another. Items are '
'removed from the first list.'),
used_for=('Products.Archetypes.Field.LinesField',
'Products.Archetypes.Field.ReferenceField',)
)
registerWidget(PicklistWidget,
title='Picklist',
description=('Render a widget to pick from one '
'list to populate another. Items '
'stay in the first list.'),
used_for=('Products.Archetypes.Field.LinesField',)
)
registerPropertyType('maxlength', 'integer', StringWidget)
registerPropertyType('populate', 'boolean')
registerPropertyType('postback', 'boolean')
registerPropertyType('rows', 'integer', RichWidget)
registerPropertyType('cols', 'integer', RichWidget)
registerPropertyType('rows', 'integer', TextAreaWidget)
registerPropertyType('cols', 'integer', TextAreaWidget)
registerPropertyType('append_only', 'boolean', TextAreaWidget)
registerPropertyType('divider', 'string', TextAreaWidget)
registerPropertyType('rows', 'integer', LinesWidget)
registerPropertyType('cols', 'integer', LinesWidget)
registerPropertyType('rows', 'integer', VisualWidget)
registerPropertyType('cols', 'integer', VisualWidget)