Back to index

plone3  3.1.7
Field.py
Go to the documentation of this file.
00001 import sys
00002 
00003 from copy import deepcopy
00004 from cgi import escape
00005 from cStringIO import StringIO
00006 from logging import ERROR
00007 from types import ListType, TupleType, ClassType, FileType
00008 from types import StringType, UnicodeType, BooleanType
00009 
00010 from zope.contenttype import guess_content_type
00011 from zope.i18n import translate
00012 from zope.i18nmessageid import Message
00013 from zope import schema
00014 from zope import component
00015 
00016 from AccessControl import ClassSecurityInfo
00017 from AccessControl import getSecurityManager
00018 from Acquisition import aq_base
00019 from Acquisition import aq_get
00020 from Acquisition import aq_parent
00021 from Acquisition import aq_inner
00022 from Acquisition import Implicit
00023 from BTrees.OOBTree import OOBTree
00024 from ComputedAttribute import ComputedAttribute
00025 from DateTime import DateTime
00026 from ExtensionClass import Base
00027 from Globals import InitializeClass
00028 from OFS.Image import File
00029 from OFS.Image import Pdata
00030 from OFS.Image import Image as BaseImage
00031 from OFS.Traversable import Traversable
00032 from OFS.Cache import ChangeCacheSettingsPermission
00033 from ZPublisher.HTTPRequest import FileUpload
00034 from ZODB.POSException import ConflictError
00035 
00036 from Products.CMFCore.utils import getToolByName
00037 from Products.CMFCore.utils import _getAuthenticatedUser
00038 from Products.CMFCore import permissions
00039 
00040 from Products.Archetypes import PloneMessageFactory as _
00041 from Products.Archetypes.config import REFERENCE_CATALOG
00042 from Products.Archetypes.Layer import DefaultLayerContainer
00043 from Products.Archetypes.interfaces.storage import IStorage
00044 from Products.Archetypes.interfaces.base import IBaseUnit
00045 from Products.Archetypes.interfaces.field import IField
00046 from Products.Archetypes.interfaces.field import IObjectField
00047 from Products.Archetypes.interfaces.field import IFileField
00048 from Products.Archetypes.interfaces.layer import ILayerContainer
00049 from Products.Archetypes.interfaces.vocabulary import IVocabulary
00050 from Products.Archetypes.exceptions import ObjectFieldException
00051 from Products.Archetypes.exceptions import TextFieldException
00052 from Products.Archetypes.exceptions import FileFieldException
00053 from Products.Archetypes.exceptions import ReferenceException
00054 from Products.Archetypes.Widget import BooleanWidget
00055 from Products.Archetypes.Widget import CalendarWidget
00056 from Products.Archetypes.Widget import ComputedWidget
00057 from Products.Archetypes.Widget import DecimalWidget
00058 from Products.Archetypes.Widget import FileWidget
00059 from Products.Archetypes.Widget import ImageWidget
00060 from Products.Archetypes.Widget import IntegerWidget
00061 from Products.Archetypes.Widget import LinesWidget
00062 from Products.Archetypes.Widget import StringWidget
00063 from Products.Archetypes.Widget import ReferenceWidget
00064 from Products.Archetypes.BaseUnit import BaseUnit
00065 from Products.Archetypes.ReferenceEngine import Reference
00066 from Products.Archetypes.utils import DisplayList
00067 from Products.Archetypes.utils import Vocabulary
00068 from Products.Archetypes.utils import className
00069 from Products.Archetypes.utils import mapply
00070 from Products.Archetypes.utils import shasattr
00071 from Products.Archetypes.utils import contentDispositionHeader
00072 from Products.Archetypes.mimetype_utils import getAllowedContentTypes as getAllowedContentTypesProperty
00073 from Products.Archetypes.debug import log
00074 from Products.Archetypes.debug import log_exc
00075 from Products.Archetypes.debug import deprecated
00076 from Products.Archetypes import config
00077 from Products.Archetypes.Storage import AttributeStorage
00078 from Products.Archetypes.Storage import ObjectManagedStorage
00079 from Products.Archetypes.Storage import ReadOnlyStorage
00080 from Products.Archetypes.Registry import setSecurity
00081 from Products.Archetypes.Registry import registerField
00082 from Products.Archetypes.Registry import registerPropertyType
00083 
00084 from Products.validation import ValidationChain
00085 from Products.validation import UnknowValidatorError
00086 from Products.validation import FalseValidatorError
00087 from Products.validation.interfaces.IValidator import IValidator, IValidationChain
00088 
00089 from Products.Archetypes.interfaces import IFieldDefaultProvider
00090 
00091 # Import conditionally, so we don't introduce a hard depdendency
00092 try:
00093     from plone.i18n.normalizer.interfaces import IUserPreferredFileNameNormalizer
00094     FILE_NORMALIZER = True
00095 except ImportError:
00096     FILE_NORMALIZER = False
00097 
00098 try:
00099     import PIL.Image
00100 except ImportError:
00101     # no PIL, no scaled versions!
00102     log("Warning: no Python Imaging Libraries (PIL) found."+\
00103         "Archetypes based ImageField's don't scale if neccessary.")
00104     HAS_PIL=False
00105     PIL_ALGO = None
00106 else:
00107     HAS_PIL=True
00108     PIL_ALGO = PIL.Image.ANTIALIAS
00109 
00110 STRING_TYPES = [StringType, UnicodeType]
00111 """String-types currently supported"""
00112 
00113 _marker = []
00114 CHUNK = 1 << 14
00115 
00116 __docformat__ = 'reStructuredText'
00117 
00118 
00119 def encode(value, instance, **kwargs):
00120     """ensure value is an encoded string"""
00121     if isinstance(value, unicode):
00122         encoding = kwargs.get('encoding')
00123         if encoding is None:
00124             try:
00125                 encoding = instance.getCharset()
00126             except AttributeError:
00127                 # that occurs during object initialization
00128                 # (no acquisition wrapper)
00129                 encoding = 'UTF8'
00130         value = value.encode(encoding)
00131     return value
00132 
00133 def decode(value, instance, **kwargs):
00134     """ensure value is an unicode string"""
00135     if isinstance(value, str):
00136         encoding = kwargs.get('encoding')
00137         if encoding is None:
00138             try:
00139                 encoding = instance.getCharset()
00140             except AttributeError:
00141                 # that occurs during object initialization
00142                 # (no acquisition wrapper)
00143                 encoding = 'UTF8'
00144         value = unicode(value, encoding)
00145     return value
00146 
00147 _field_count = 0
00148 
00149 class Field(DefaultLayerContainer):
00150     """
00151     Extend `DefaultLayerContainer`.
00152     Implements `IField` and `ILayerContainer` interfaces.
00153     Class security = public with default access = allow.
00154     Class attribute _properties is a dictionary containing all of a
00155     field's property values.
00156     """
00157 
00158     __implements__ = IField, ILayerContainer
00159 
00160     security = ClassSecurityInfo()
00161 
00162     _properties = {
00163         'old_field_name':None,
00164         'required' : False,
00165         'default' : None,
00166         'default_method' : None,
00167         'vocabulary' : (),
00168         'vocabulary_factory' : None,
00169         'enforceVocabulary' : False,
00170         'multiValued' : False,
00171         'searchable' : False,
00172         'isMetadata' : False,
00173 
00174         'accessor' : None,
00175         'edit_accessor' : None,
00176         'mutator' : None,
00177         'mode' : 'rw',
00178 
00179         'read_permission' : permissions.View,
00180         'write_permission' : permissions.ModifyPortalContent,
00181 
00182         'storage' : AttributeStorage(),
00183 
00184         'generateMode' : 'veVc',
00185         'force' : '',
00186         'type' : None,
00187         'widget': StringWidget,
00188         'validators' : (),
00189         'index' : None, # "KeywordIndex" or "<index_type>:schema"
00190         'index_method' : '_at_accessor', # method used for the index
00191                                          # _at_accessor an _at_edit_accessor
00192                                          # are the accessor and edit accessor
00193         'schemata' : 'default',
00194         'languageIndependent' : False,
00195         }
00196 
00197     def __init__(self, name=None, **kwargs):
00198         """
00199         Assign name to __name__. Add properties and passed-in
00200         keyword args to __dict__. Validate assigned validator(s).
00201         """
00202         DefaultLayerContainer.__init__(self)
00203 
00204         if name is None:
00205             global _field_count
00206             _field_count += 1
00207             name = 'field.%s' % _field_count
00208 
00209         self.__name__ = name
00210 
00211         self.__dict__.update(self._properties)
00212         self.__dict__.update(kwargs)
00213 
00214         self._widgetLayer()
00215         self._validationLayer()
00216 
00217         self.registerLayer('storage', self.storage)
00218 
00219     security.declarePrivate('copy')
00220     def copy(self, name=None):
00221         """
00222         Return a copy of field instance, consisting of field name and
00223         properties dictionary. field name can be changed to given name.
00224         """
00225         cdict = dict(vars(self))
00226         cdict.pop('__name__')
00227         # Widget must be copied separatedly
00228         widget = cdict['widget']
00229         del cdict['widget']
00230         properties = deepcopy(cdict)
00231         properties['widget'] = widget.copy()
00232         name = name is not None and name or self.getName()
00233         return self.__class__(name, **properties)
00234 
00235     
00236     def __repr__(self):
00237         """
00238         Return a string representation consisting of name, type and permissions.
00239         """
00240         return "<Field %s(%s:%s)>" % (self.getName(), self.type, self.mode)
00241 
00242     def _widgetLayer(self):
00243         """
00244         instantiate the widget if a class was given and call
00245         widget.populateProps
00246         """
00247         if shasattr(self, 'widget'):
00248             if type(self.widget) in (ClassType, type(Base)):
00249                 self.widget = self.widget()
00250             self.widget.populateProps(self)
00251 
00252     def _validationLayer(self):
00253         """
00254         Resolve that each validator is in the service. If validator is
00255         not, log a warning.
00256 
00257         We could replace strings with class refs and keep things impl
00258         the ivalidator in the list.
00259 
00260         Note: this is not compat with aq_ things like scripts with __call__
00261         """
00262         chainname = 'Validator_%s' % self.getName()
00263 
00264         if isinstance(self.validators, dict):
00265             raise NotImplementedError, 'Please use the new syntax with validation chains'
00266         elif IValidationChain.isImplementedBy(self.validators):
00267             validators = self.validators
00268         elif IValidator.isImplementedBy(self.validators):
00269             validators = ValidationChain(chainname, validators=self.validators)
00270         elif type(self.validators) in (TupleType, ListType, StringType):
00271             if len(self.validators):
00272                 # got a non empty list or string - create a chain
00273                 try:
00274                     validators = ValidationChain(chainname, validators=self.validators)
00275                 except (UnknowValidatorError, FalseValidatorError), msg:
00276                     log("WARNING: Disabling validation for %s: %s" % (self.getName(), msg))
00277                     validators = ()
00278             else:
00279                 validators = ()
00280         else:
00281             log('WARNING: Unknow validation %s. Disabling!' % self.validators)
00282             validators = ()
00283 
00284         if not self.required:
00285             if validators == ():
00286                 validators = ValidationChain(chainname)
00287             if len(validators):
00288                 # insert isEmpty validator at position 0 if first validator
00289                 # is not isEmpty
00290                 if not validators[0][0].name.startswith('isEmpty'):
00291                     validators.insertSufficient('isEmptyNoError')
00292                     #validators.insertSufficient('isEmpty')
00293             else:
00294                 validators.insertSufficient('isEmpty')
00295 
00296         self.validators = validators
00297 
00298     security.declarePublic('validate')
00299     def validate(self, value, instance, errors=None, **kwargs):
00300         """
00301         Validate passed-in value using all field validators.
00302         Return None if all validations pass; otherwise, return failed
00303         result returned by validator
00304         """
00305         if errors is None:
00306             errors = {}
00307         name = self.getName()
00308         if errors and errors.has_key(name):
00309             return True
00310 
00311         if self.required:
00312             res = self.validate_required(instance, value, errors)
00313             if res is not None:
00314                 return res
00315 
00316         if self.enforceVocabulary:
00317             res = self.validate_vocabulary(instance, value, errors)
00318             if res is not None:
00319                 return res
00320 
00321         res = instance.validate_field(name, value, errors)
00322         if res is not None:
00323             return res
00324 
00325         if self.validators:
00326             res = self.validate_validators(value, instance, errors, **kwargs)
00327             if res is not True:
00328                 return res
00329 
00330         # all ok
00331         return None
00332 
00333     security.declarePrivate('validate_validators')
00334     def validate_validators(self, value, instance, errors, **kwargs):
00335         """
00336         """
00337         if self.validators:
00338             result = self.validators(value, instance=instance, errors=errors,
00339                                      field=self, **kwargs)
00340         else:
00341             result = True
00342 
00343         if result is not True:
00344             return result
00345 
00346     security.declarePrivate('validate_required')
00347     def validate_required(self, instance, value, errors):
00348         if not value:
00349             request = aq_get(instance, 'REQUEST')
00350             label = self.widget.Label(instance)
00351             name = self.getName()
00352             if isinstance(label, Message):
00353                 label = translate(label, context=request)
00354             error = _(u'error_required',
00355                       default=u'${name} is required, please correct.',
00356                       mapping={'name': label})
00357             error = translate(error, context=request)
00358             errors[name] = error
00359             return error
00360         return None
00361 
00362     security.declarePrivate('validate_vocabulary')
00363     def validate_vocabulary(self, instance, value, errors):
00364         """Make sure value is inside the allowed values
00365         for a given vocabulary"""
00366         badvalues = []
00367         if value:
00368             # coerce value into a list called values
00369             values = value
00370             if type(value) in STRING_TYPES:
00371                 values = [value]
00372             elif type(value) == BooleanType:
00373                 values = [str(value)]
00374             elif type(value) not in (TupleType, ListType):
00375                 raise TypeError("Field value type error: %s" % type(value))
00376             vocab = self.Vocabulary(instance)
00377             # filter empty
00378             values = [instance.unicodeEncode(v)
00379                       for v in values if v.strip()]
00380             # extract valid values from vocabulary
00381             valids = []
00382             for v in vocab:
00383                 if type(v) in (TupleType, ListType):
00384                     v = v[0]
00385                 if not type(v) in STRING_TYPES:
00386                     v = str(v)
00387                 valids.append(instance.unicodeEncode(v))
00388             # check field values
00389             badvalues = [val for val in values if not val in valids]
00390 
00391         error = None
00392         if badvalues:
00393             request = aq_get(instance, 'REQUEST')
00394             label = self.widget.Label(instance)
00395             if isinstance(label, Message):
00396                 label = translate(label, context=request)
00397             if isinstance(val, Message):
00398                 val = translate(val, context=request)
00399             error = _( u'error_vocabulary',
00400                 default=u'Values ${val} is not allowed for vocabulary of element ${label}.',
00401                 mapping={'val': unicode(badvalues), 'name': label})
00402             error = translate(error, context=request)
00403             errors[self.getName()] = error
00404         return error
00405 
00406     security.declarePublic('Vocabulary')
00407     def Vocabulary(self, content_instance=None):
00408         """
00409         Returns a DisplayList.
00410 
00411         Uses self.vocabulary as source.
00412 
00413         1) Static vocabulary
00414 
00415            - is already a DisplayList
00416            - is a list of 2-tuples with strings (see above)
00417            - is a list of strings (in this case a DisplayList
00418              with key=value will be created)
00419 
00420         2) Dynamic vocabulary:
00421 
00422            - precondition: a content_instance is given.
00423 
00424            - has to return a:
00425 
00426                 * DisplayList or
00427                 * list of strings or
00428                 * list of 2-tuples with strings:
00429                     '[("key1","value 1"),("key 2","value 2"),]'
00430 
00431            - the output is postprocessed like a static vocabulary.
00432 
00433            - vocabulary is a string:
00434                 if a method with the name of the string exists it will be called
00435 
00436            - vocabulary is a class implementing IVocabulary:
00437                 the "getDisplayList" method of the class will be called.
00438 
00439         3) Zope 3 vocabulary factory vocabulary
00440         
00441             - precondition: a content_instance is given
00442             
00443             - self.vocabulary_factory is given
00444             
00445             - a named utility providing zope.schema.interfaces.IVocbularyFactory 
00446               exists for the name self.vocabulary_factory.
00447 
00448         """
00449         value = self.vocabulary
00450         
00451         # Attempt to get the value from a a vocabulary factory if one was given
00452         # and no explicit vocabulary was set
00453         if not isinstance(value, DisplayList) and not value:
00454             factory_name = getattr(self, 'vocabulary_factory', None)
00455             if factory_name is not None:
00456                 factory = component.getUtility(schema.interfaces.IVocabularyFactory, name=factory_name)
00457                 factory_context = content_instance
00458                 if factory_context is None:
00459                     factory_context = self
00460                 value = DisplayList([(t.value, t.title or t.token) for t in factory(factory_context)])
00461                     
00462         if not isinstance(value, DisplayList):
00463 
00464             if content_instance is not None and isinstance(value, basestring):
00465                 # Dynamic vocabulary by method on class of content_instance
00466                 method = getattr(content_instance, value, None)
00467                 if method and callable(method):
00468                     args = []
00469                     kw = {'content_instance' : content_instance,
00470                           'field' : self}
00471                     value = mapply(method, *args, **kw)
00472             elif content_instance is not None and \
00473                  IVocabulary.isImplementedBy(value):
00474                 # Dynamic vocabulary provided by a class that
00475                 # implements IVocabulary
00476                 value = value.getDisplayList(content_instance)
00477 
00478             # Post process value into a DisplayList
00479             # Templates will use this interface
00480             sample = value[:1]
00481             if isinstance(sample, DisplayList):
00482                 # Do nothing, the bomb is already set up
00483                 pass
00484             elif isinstance(sample, (list, tuple)):
00485                 # Assume we have ((value, display), ...)
00486                 # and if not ('', '', '', ...)
00487                 if sample and not isinstance((sample[0]), (list, tuple)):
00488                     # if not a 2-tuple
00489                     value = zip(value, value)
00490                 value = DisplayList(value)
00491             elif len(sample) and isinstance(sample[0], basestring):
00492                 value = DisplayList(zip(value, value))
00493             else:
00494                 log('Unhandled type in Vocab')
00495                 log(value)
00496 
00497         if content_instance:
00498             # Translate vocabulary
00499             i18n_domain = (getattr(self, 'i18n_domain', None) or
00500                           getattr(self.widget, 'i18n_domain', None))
00501 
00502             return Vocabulary(value, content_instance, i18n_domain)
00503 
00504         return value
00505 
00506     security.declarePublic('checkPermission')
00507     def checkPermission(self, mode, instance):
00508         """
00509         Check whether the security context allows the given permission on
00510         the given object.
00511 
00512         Arguments:
00513 
00514         mode -- 'w' for write or 'r' for read
00515         instance -- The object being accessed according to the permission
00516         """
00517         if mode in ('w', 'write', 'edit', 'set'):
00518             perm = self.write_permission
00519         elif mode in ('r', 'read', 'view', 'get'):
00520             perm = self.read_permission
00521         else:
00522             return None
00523         return getSecurityManager().checkPermission( perm, instance )
00524 
00525 
00526     security.declarePublic('writeable')
00527     def writeable(self, instance, debug=False):
00528         if 'w' not in self.mode:
00529             if debug:
00530                 log("Tried to update %s:%s but field is not writeable." % \
00531                     (instance.portal_type, self.getName()))
00532             return False
00533 
00534         method = self.getMutator(instance)
00535         if not method:
00536             if debug:
00537                 log("No method %s on %s." % (self.mutator, instance))
00538             return False
00539 
00540         if not self.checkPermission('edit', instance):
00541             if debug:
00542                 log("User %s tried to update %s:%s but "
00543                     "doesn't have enough permissions." %
00544                     (_getAuthenticatedUser(instance).getId(),
00545                      instance.portal_type, self.getName()))
00546             return False
00547         return True
00548 
00549     security.declarePublic('checkExternalEditor')
00550     def checkExternalEditor(self, instance):
00551         """ Checks if the user may edit this field and if
00552         external editor is enabled on this instance """
00553 
00554         pp = getToolByName(instance, 'portal_properties')
00555         sp = getattr(pp, 'site_properties', None)
00556         if sp is not None:
00557             if getattr(sp, 'ext_editor', None) \
00558                    and self.checkPermission(mode='edit', instance=instance):
00559                 return True
00560         return None
00561 
00562     security.declarePublic('getWidgetName')
00563     def getWidgetName(self):
00564         """Return the widget name that is configured for this field as
00565         a string"""
00566         return self.widget.getName()
00567 
00568     security.declarePublic('getName')
00569     def getName(self):
00570         """Return the name of this field as a string"""
00571         return self.__name__
00572 
00573     security.declarePublic('getType')
00574     def getType(self):
00575         """Return the type of this field as a string"""
00576         return className(self)
00577 
00578     security.declarePublic('getDefault')
00579     def getDefault(self, instance):
00580         """Return the default value to be used for initializing this
00581         field"""
00582         dm = self.default_method
00583         if dm:
00584             if isinstance(dm, basestring) and shasattr(instance, dm):
00585                 method = getattr(instance, dm)
00586                 return method()
00587             elif callable(dm):
00588                 return dm()
00589             else:
00590                 raise ValueError('%s.default_method is neither a method of %s'
00591                                  ' nor a callable' % (self.getName(),
00592                                                       instance.__class__))
00593         
00594         if not self.default:
00595             default_adapter = component.queryAdapter(instance, IFieldDefaultProvider, name=self.__name__)
00596             if default_adapter is not None:
00597                 return default_adapter()
00598                 
00599         return self.default
00600 
00601     security.declarePublic('getAccessor')
00602     def getAccessor(self, instance):
00603         """Return the accessor method for getting data out of this
00604         field"""
00605         if self.accessor:
00606             return getattr(instance, self.accessor, None)
00607         return None
00608 
00609     security.declarePublic('getEditAccessor')
00610     def getEditAccessor(self, instance):
00611         """Return the accessor method for getting raw data out of this
00612         field e.g.: for editing
00613         """
00614         if self.edit_accessor:
00615             return getattr(instance, self.edit_accessor, None)
00616         return None
00617 
00618     security.declarePublic('getMutator')
00619     def getMutator(self, instance):
00620         """Return the mutator method used for changing the value
00621         of this field"""
00622         if self.mutator:
00623             return getattr(instance, self.mutator, None)
00624         return None
00625 
00626     security.declarePublic('getIndexAccessor')
00627     def getIndexAccessor(self, instance):
00628         """Return the index accessor, i.e. the getter for an indexable
00629         value."""
00630         return getattr(instance, self.getIndexAccessorName())
00631 
00632     security.declarePublic('getIndexAccessorName')
00633     def getIndexAccessorName(self):
00634         """Return the index accessor's name defined by the
00635         'index_method' field property."""
00636         if not hasattr(self, 'index_method'):
00637             return self.accessor
00638         elif self.index_method == '_at_accessor':
00639             return self.accessor
00640         elif self.index_method == '_at_edit_accessor':
00641             return self.edit_accessor or self.accessor
00642 
00643         # If index_method is not a string, we raise ValueError (this
00644         # is actually tested for in test_extensions_utils):
00645         elif not isinstance(self.index_method, (str, unicode)):
00646             raise ValueError("Bad index accessor value : %r"
00647                              % self.index_method)
00648         else:
00649             return self.index_method
00650 
00651     security.declarePrivate('toString')
00652     def toString(self):
00653         """Utility method for converting a Field to a string for the
00654         purpose of comparing fields.  This comparison is used for
00655         determining whether a schema has changed in the auto update
00656         function. Right now it's pretty crude."""
00657         # TODO fixme
00658         s = '%s(%s): {' % ( self.__class__.__name__, self.__name__ )
00659         sorted_keys = self._properties.keys()
00660         sorted_keys.sort()
00661         for k in sorted_keys:
00662             value = getattr( self, k, self._properties[k] )
00663             if k == 'widget':
00664                 value = value.__class__.__name__
00665             if type(value) is UnicodeType:
00666                 value = value.encode('utf-8')
00667             s = s + '%s:%s,' % (k, value )
00668         s = s + '}'
00669         return s
00670 
00671     security.declarePublic('isLanguageIndependent')
00672     def isLanguageIndependent(self, instance):
00673         """Get the language independed flag for i18n content
00674         """
00675         return self.languageIndependent
00676 
00677     security.declarePublic('getI18nDomain')
00678     def getI18nDomain(self):
00679         """ returns the internationalization domain for translation """
00680         pass
00681 
00682 #InitializeClass(Field)
00683 setSecurity(Field)
00684 
00685 class ObjectField(Field):
00686     """Base Class for Field objects that fundamentaly deal with raw
00687     data. This layer implements the interface to IStorage and other
00688     Field Types should subclass this to delegate through the storage
00689     layer.
00690     """
00691     __implements__ = IObjectField, ILayerContainer
00692 
00693     _properties = Field._properties.copy()
00694     _properties.update({
00695         'type' : 'object',
00696         'default_content_type' : 'application/octet-stream',
00697         })
00698 
00699     security  = ClassSecurityInfo()
00700 
00701     security.declarePrivate('get')
00702     def get(self, instance, **kwargs):
00703         __traceback_info__ = (self.getName(), instance, kwargs)
00704         try:
00705             kwargs['field'] = self
00706             return self.getStorage(instance).get(self.getName(), instance, **kwargs)
00707         except AttributeError:
00708             # happens if new Atts are added and not yet stored in the instance
00709             # @@ and at every other possible occurence of an AttributeError?!!
00710             default = self.getDefault(instance)
00711             if not kwargs.get('_initializing_', False):
00712                 self.set(instance, default, _initializing_=True, **kwargs)
00713             return default
00714 
00715     security.declarePrivate('getRaw')
00716     def getRaw(self, instance, **kwargs):
00717         if self.accessor is not None:
00718             accessor = self.getAccessor(instance)
00719         else:
00720             # self.accessor is None for fields wrapped by an I18NMixIn
00721             accessor = None
00722         kwargs.update({'field': self,
00723                        'encoding':kwargs.get('encoding', None),
00724                      })
00725         if accessor is None:
00726             args = [instance,]
00727             return mapply(self.get, *args, **kwargs)
00728         return mapply(accessor, **kwargs)
00729 
00730     security.declarePrivate('set')
00731     def set(self, instance, value, **kwargs):
00732         kwargs['field'] = self
00733         kwargs['mimetype'] = kwargs.get('mimetype', getattr(self, 'default_content_type', 'application/octet-stream'))
00734         # Remove acquisition wrappers
00735         value = aq_base(value)
00736         __traceback_info__ = (self.getName(), instance, value, kwargs)
00737         self.getStorage(instance).set(self.getName(), instance, value, **kwargs)
00738 
00739     security.declarePrivate('unset')
00740     def unset(self, instance, **kwargs):
00741         #kwargs['field'] = self
00742         __traceback_info__ = (self.getName(), instance, kwargs)
00743         self.getStorage(instance).unset(self.getName(), instance, **kwargs)
00744 
00745     security.declarePrivate('setStorage')
00746     def setStorage(self, instance, storage):
00747         if not IStorage.isImplementedBy(storage):
00748             raise ObjectFieldException, "Not a valid Storage method"
00749         # raw=1 is required for TextField
00750         value = self.get(instance, raw=True)
00751         self.unset(instance)
00752         self.storage = storage
00753         if shasattr(self.storage, 'initializeInstance'):
00754             self.storage.initializeInstance(instance)
00755         self.set(instance, value)
00756 
00757     security.declarePrivate('getStorage')
00758     def getStorage(self, instance=None):
00759         return self.storage
00760 
00761     security.declarePublic('getStorageName')
00762     def getStorageName(self, instance=None):
00763         """Return the storage name that is configured for this field
00764         as a string"""
00765         return self.getStorage(instance).getName()
00766 
00767     security.declarePublic('getStorageType')
00768     def getStorageType(self, instance=None):
00769         """Return the type of the storage of this field as a string"""
00770         return className(self.getStorage(instance))
00771 
00772     security.declarePrivate('setContentType')
00773     def setContentType(self, instance, value):
00774         """Set mimetype in the base unit.
00775         """
00776         pass
00777 
00778     security.declarePublic('getContentType')
00779     def getContentType(self, instance, fromBaseUnit=True):
00780         """Return the mime type of object if known or can be guessed;
00781         otherwise, return default_content_type value or fallback to
00782         'application/octet-stream'.
00783         """
00784         value = ''
00785         if fromBaseUnit and shasattr(self, 'getBaseUnit'):
00786             bu = self.getBaseUnit(instance)
00787             if IBaseUnit.isImplementedBy(bu):
00788                 return str(bu.getContentType())
00789         raw = self.getRaw(instance)
00790         mimetype = getattr(aq_base(raw), 'mimetype', None)
00791         # some instances like OFS.Image have a getContentType method
00792         if mimetype is None:
00793             getCT = getattr(raw, 'getContentType', None)
00794             if callable(getCT):
00795                 mimetype = getCT()
00796         # try to guess
00797         if mimetype is None:
00798             mimetype, enc = guess_content_type('', str(raw), None)
00799         else:
00800             # mimetype may be an mimetype object
00801             mimetype = str(mimetype)
00802         # failed
00803         if mimetype is None:
00804             mimetype = getattr(self, 'default_content_type',
00805                                'application/octet-stream')
00806         return mimetype
00807 
00808     security.declarePublic('get_size')
00809     def get_size(self, instance):
00810         """Get size of the stored data used for get_size in BaseObject
00811 
00812         Should be overwritte by special fields like FileField. It's safe for
00813         fields which are storing strings, ints and BaseUnits but it won't return
00814         the right results for fields containing OFS.Image.File instances or
00815         lists/tuples/dicts.
00816         """
00817         data = self.getRaw(instance)
00818         try:
00819             return len(data)
00820         except (TypeError, AttributeError):
00821             return len(str(data))
00822 
00823 #InitializeClass(ObjectField)
00824 setSecurity(ObjectField)
00825 
00826 class StringField(ObjectField):
00827     """A field that stores strings"""
00828     _properties = Field._properties.copy()
00829     _properties.update({
00830         'type' : 'string',
00831         'default': '',
00832         'default_content_type' : 'text/plain',
00833         })
00834 
00835     security  = ClassSecurityInfo()
00836 
00837     security.declarePrivate('get')
00838     def get(self, instance, **kwargs):
00839         value = ObjectField.get(self, instance, **kwargs)
00840         if getattr(self, 'raw', False):
00841             return value
00842         return encode(value, instance, **kwargs)
00843 
00844     security.declarePrivate('set')
00845     def set(self, instance, value, **kwargs):
00846         kwargs['field'] = self
00847         # Remove acquisition wrappers
00848         if not getattr(self, 'raw', False):
00849             value = decode(aq_base(value), instance, **kwargs)
00850         self.getStorage(instance).set(self.getName(), instance, value, **kwargs)
00851 
00852 class FileField(ObjectField):
00853     """Something that may be a file, but is not an image and doesn't
00854     want text format conversion"""
00855 
00856     __implements__ = IFileField, ILayerContainer
00857 
00858     _properties = ObjectField._properties.copy()
00859     _properties.update({
00860         'type' : 'file',
00861         'default' : '',
00862         'primary' : False,
00863         'widget' : FileWidget,
00864         'content_class' : File,
00865         'default_content_type' : 'application/octet-stream',
00866         })
00867 
00868     security  = ClassSecurityInfo()
00869 
00870     security.declarePrivate('setContentType')
00871     def setContentType(self, instance, value):
00872         """Set mimetype in the base unit.
00873         """
00874         file = self.get(instance)
00875         try: 
00876             # file might be None or an empty string
00877             setattr(file, 'content_type', value)
00878         except AttributeError:
00879             pass
00880         else:
00881             self.set(instance, file)
00882 
00883     security.declarePublic('getContentType')
00884     def getContentType(self, instance, fromBaseUnit=True):
00885         file = self.get(instance)
00886         return getattr(file, 'content_type', self.default_content_type)
00887 
00888     def _process_input(self, value, file=None, default=None, mimetype=None,
00889                        instance=None, filename='', **kwargs):
00890         if file is None:
00891             file = self._make_file(self.getName(), title='',
00892                                    file='', instance=instance)
00893         if IBaseUnit.isImplementedBy(value):
00894             mimetype = value.getContentType() or mimetype
00895             filename = value.getFilename() or filename
00896             value = value.getRaw()
00897         elif isinstance(value, self.content_class):
00898             filename = getattr(value, 'filename', value.getId())
00899             mimetype = getattr(value, 'content_type', mimetype)
00900             return value, mimetype, filename
00901         elif isinstance(value, File):
00902             # In case someone changes the 'content_class'
00903             filename = getattr(value, 'filename', value.getId())
00904             mimetype = getattr(value, 'content_type', mimetype)
00905             value = value.data
00906         elif isinstance(value, FileUpload) or shasattr(value, 'filename'):
00907             filename = value.filename
00908         elif isinstance(value, FileType) or shasattr(value, 'name'):
00909             # In this case, give preference to a filename that has
00910             # been detected before. Usually happens when coming from PUT().
00911             if not filename:
00912                 filename = value.name
00913                 # Should we really special case here?
00914                 for v in (filename, repr(value)):
00915                     # Windows unnamed temporary file has '<fdopen>' in
00916                     # repr() and full path in 'file.name'
00917                     if '<fdopen>' in v:
00918                         filename = ''
00919         elif isinstance(value, basestring):
00920             # Let it go, mimetypes_registry will be used below if available
00921             pass
00922         elif (isinstance(value, Pdata) or (shasattr(value, 'read') and
00923                                            shasattr(value, 'seek'))):
00924             # Can't get filename from those.
00925             pass
00926         elif value is None:
00927             # Special case for setDefault
00928             value = ''
00929         else:
00930             klass = getattr(value, '__class__', None)
00931             raise FileFieldException('Value is not File or String (%s - %s)' %
00932                                      (type(value), klass))
00933         filename = filename[max(filename.rfind('/'),
00934                                 filename.rfind('\\'),
00935                                 filename.rfind(':'),
00936                                 )+1:]
00937         file.manage_upload(value)
00938         if mimetype is None or mimetype == 'text/x-unknown-content-type':
00939             body = file.data
00940             if not isinstance(body, basestring):
00941                 body = body.data
00942             mtr = getToolByName(instance, 'mimetypes_registry', None)
00943             if mtr is not None:
00944                 kw = {'mimetype':None,
00945                       'filename':filename}
00946                 # this may split the encoded file inside a multibyte character
00947                 try:
00948                     d, f, mimetype = mtr(body[:8096], **kw)
00949                 except UnicodeDecodeError:
00950                     d, f, mimetype = mtr(len(body) < 8096 and body or '', **kw)
00951             else:
00952                 mimetype = getattr(file, 'content_type', None)
00953                 if mimetype is None:
00954                     mimetype, enc = guess_content_type(filename, body, mimetype)
00955         # mimetype, if coming from request can be like:
00956         # text/plain; charset='utf-8'
00957         mimetype = str(mimetype).split(';')[0].strip()
00958         setattr(file, 'content_type', mimetype)
00959         setattr(file, 'filename', filename)
00960         return file, mimetype, filename
00961 
00962     def _migrate_old(self, value, default=None, mimetype=None, **kwargs):
00963         filename = kwargs.get('filename', '')
00964         if isinstance(value, basestring):
00965             filename = kwargs.get('filename', '')
00966             if mimetype is None:
00967                 mimetype, enc = guess_content_type(filename, value, mimetype)
00968             if not value:
00969                 return default, mimetype, filename
00970             return value, mimetype, filename
00971         elif IBaseUnit.isImplementedBy(value):
00972             return value.getRaw(), value.getContentType(), value.getFilename()
00973 
00974         value = aq_base(value)
00975 
00976         if isinstance(value, File):
00977             # OFS.Image.File based
00978             filename = getattr(value, 'filename', value.getId())
00979             mimetype = value.content_type
00980             data = value.data
00981             if len(data) == 0:
00982                 return default, mimetype, filename
00983             else:
00984                 return data, mimetype, filename
00985 
00986         return '', mimetype, filename
00987 
00988     def _make_file(self, id, title='', file='', instance=None):
00989         """File content factory"""
00990         return self.content_class(id, title, file)
00991 
00992     security.declarePrivate('get')
00993     def get(self, instance, **kwargs):
00994         value = ObjectField.get(self, instance, **kwargs)
00995         if value and not isinstance(value, self.content_class):
00996             value = self._wrapValue(instance, value)
00997         if (shasattr(value, '__of__', acquire=True)
00998             and not kwargs.get('unwrapped', False)):
00999             return value.__of__(instance)
01000         else:
01001             return value
01002 
01003     security.declarePrivate('set')
01004     def set(self, instance, value, **kwargs):
01005         """
01006         Assign input value to object. If mimetype is not specified,
01007         pass to processing method without one and add mimetype returned
01008         to kwargs. Assign kwargs to instance.
01009         """
01010         if value == "DELETE_FILE":
01011             if shasattr(instance, '_FileField_types'):
01012                 delattr(aq_base(instance), '_FileField_types')
01013             ObjectField.unset(self, instance, **kwargs)
01014             return
01015 
01016         if not kwargs.has_key('mimetype'):
01017             kwargs['mimetype'] = None
01018 
01019         kwargs['default'] = self.getDefault(instance)
01020         initializing = kwargs.get('_initializing_', False)
01021 
01022         if not initializing:
01023             file = self.get(instance, raw=True, unwrapped=True)
01024         else:
01025             file = None
01026         factory = self.content_class
01027         if not initializing and not isinstance(file, factory):
01028             # Convert to same type as factory
01029             # This is here mostly for backwards compatibility
01030             v, m, f = self._migrate_old(file, **kwargs)
01031             kwargs['mimetype'] = m
01032             kwargs['filename'] = f
01033             obj = self._wrapValue(instance, v, **kwargs)
01034             # Store so the object gets a _p_jar,
01035             # if we are using a persistent storage, that is.
01036             ObjectField.set(self, instance, obj, **kwargs)
01037             file = self.get(instance, raw=True, unwrapped=True)
01038             # Should be same as factory now, but if it isn't, that's
01039             # very likely a bug either in the storage implementation
01040             # or on the field implementation.
01041 
01042         value, mimetype, filename = self._process_input(value, file=file,
01043                                                         instance=instance,
01044                                                         **kwargs)
01045 
01046         kwargs['mimetype'] = mimetype
01047         kwargs['filename'] = filename
01048 
01049         # remove ugly hack
01050         if shasattr(instance, '_FileField_types'):
01051             del instance._FileField_types
01052         if value is None:
01053             # do not send None back as file value if we get a default (None)
01054             # value back from _process_input.  This prevents
01055             # a hard error (NoneType object has no attribute 'seek') from
01056             # occurring if someone types in a bogus name in a file upload
01057             # box (at least under Mozilla).
01058             value = ''
01059         obj = self._wrapValue(instance, value, **kwargs)
01060         ObjectField.set(self, instance, obj, **kwargs)
01061 
01062     def _wrapValue(self, instance, value, **kwargs):
01063         """Wraps the value in the content class if it's not wrapped
01064         """
01065         if isinstance(value, self.content_class):
01066             return value
01067         mimetype = kwargs.get('mimetype', self.default_content_type)
01068         filename = kwargs.get('filename', '')
01069         obj = self._make_file(self.getName(), title='',
01070                               file=value, instance=instance)
01071         setattr(obj, 'filename', filename)
01072         setattr(obj, 'content_type', mimetype)
01073         try:
01074             delattr(obj, 'title')
01075         except (KeyError, AttributeError):
01076             pass
01077 
01078         return obj
01079 
01080     security.declarePrivate('getBaseUnit')
01081     def getBaseUnit(self, instance, full=False):
01082         """Return the value of the field wrapped in a base unit object
01083         """
01084         filename = self.getFilename(instance, fromBaseUnit=False)
01085         if not filename:
01086             filename = '' # self.getName()
01087         mimetype = self.getContentType(instance, fromBaseUnit=False)
01088         value = self.getRaw(instance) or self.getDefault(instance)
01089         if isinstance(aq_base(value), File):
01090             value = value.data
01091             if full:
01092                 # This will read the whole file in memory, which is
01093                 # very expensive specially with big files over
01094                 # ZEO. With small files is not that much of an issue.
01095                 value = str(value)
01096             elif not isinstance(value, basestring):
01097                 # It's a Pdata object, get only the first chunk, which
01098                 # should be good enough for detecting the mimetype
01099                 value = value.data
01100         bu = BaseUnit(filename, aq_base(value), instance,
01101                       filename=filename, mimetype=mimetype)
01102         return bu
01103 
01104     security.declarePrivate('getFilename')
01105     def getFilename(self, instance, fromBaseUnit=True):
01106         """Get file name of underlaying file object
01107         """
01108         filename = None
01109         if fromBaseUnit:
01110             bu = self.getBaseUnit(instance)
01111             return bu.getFilename()
01112         raw = self.getRaw(instance)
01113         filename = getattr(aq_base(raw), 'filename', None)
01114         # for OFS.Image.*
01115         if filename is None:
01116             filename = getattr(raw, 'filename', None)
01117         # might still be None
01118         if filename:
01119             # taking care of stupid IE and be backward compatible
01120             # BaseUnit hasn't have a fix for long so we might have an old name
01121             filename = filename.split("\\")[-1]
01122         return filename
01123 
01124     security.declarePrivate('setFilename')
01125     def setFilename(self, instance, filename):
01126         """Set file name in the base unit.
01127         """
01128         bu = self.getBaseUnit(instance, full=True)
01129         bu.setFilename(filename)
01130         self.set(instance, bu)
01131 
01132     security.declarePrivate('validate_required')
01133     def validate_required(self, instance, value, errors):
01134         value = getattr(value, 'get_size', lambda: value and str(value))()
01135         return ObjectField.validate_required(self, instance, value, errors)
01136 
01137     security.declareProtected(permissions.View, 'download')
01138     def download(self, instance, REQUEST=None, RESPONSE=None, no_output=False):
01139         """Kicks download.
01140 
01141         Writes data including file name and content type to RESPONSE
01142         """
01143         file = self.get(instance, raw=True)
01144         if not REQUEST:
01145             REQUEST = aq_get(instance, 'REQUEST')
01146         if not RESPONSE:
01147             RESPONSE = REQUEST.RESPONSE
01148         filename = self.getFilename(instance)
01149         if filename is not None:
01150             if FILE_NORMALIZER:
01151                 filename = IUserPreferredFileNameNormalizer(REQUEST).normalize(
01152                     unicode(filename, instance.getCharset()))
01153             else:
01154                 filename = unicode(filename, instance.getCharset())
01155             header_value = contentDispositionHeader(
01156                 disposition='attachment',
01157                 filename=filename)
01158             RESPONSE.setHeader("Content-Disposition", header_value)
01159         if no_output:
01160             return file
01161         return file.index_html(REQUEST, RESPONSE)
01162 
01163     security.declarePublic('get_size')
01164     def get_size(self, instance):
01165         """Get size of the stored data used for get_size in BaseObject
01166         """
01167         file = self.get(instance)
01168         if isinstance(file, self.content_class):
01169             return file.get_size()
01170         # Backwards compatibility
01171         return len(str(file))
01172 
01173     security.declarePublic('getIndexAccessor')
01174     def getIndexAccessor(self, instance):
01175         name = self.getIndexAccessorName()
01176         if name in (self.edit_accessor, self.accessor):
01177             return lambda: self.getIndexable(instance)
01178         else:
01179             return ObjectField.getIndexAccessor(self, instance)
01180 
01181     security.declarePrivate('getIndexable')
01182     def getIndexable(self, instance):
01183         # XXX Naive implementation that loads all data contents into
01184         # memory.  To have this not happening set your field to not
01185         # 'searchable' (the default) or define your own 'index_method'
01186         # property.
01187         orig_mt = self.getContentType(instance)
01188 
01189         # If there's no path to text/plain, don't do anything
01190         transforms = getToolByName(instance, 'portal_transforms')
01191         if transforms._findPath(orig_mt, 'text/plain') is None:
01192             return ''
01193 
01194         f = self.get(instance)
01195 
01196         datastream = ''
01197         try:
01198             datastream = transforms.convertTo(
01199                 "text/plain",
01200                 str(f),
01201                 mimetype = orig_mt,
01202                 filename = self.getFilename(instance, 0),
01203                 )
01204         except (ConflictError, KeyboardInterrupt):
01205             raise
01206         except Exception, e:
01207             log("Error while trying to convert file contents to 'text/plain' "
01208                 "in %r.getIndexable() of %r: %s" % (self, instance, e))
01209 
01210         value = str(datastream)
01211         return value
01212 
01213 class TextField(FileField):
01214     """Base Class for Field objects that rely on some type of
01215     transformation"""
01216 
01217     __implements__ = FileField.__implements__
01218 
01219     _properties = FileField._properties.copy()
01220     _properties.update({
01221         'type' : 'text',
01222         'default' : '',
01223         'widget': StringWidget,
01224         'default_content_type' : None,
01225         'default_output_type'  : 'text/plain',
01226         'allowable_content_types' : None,
01227         'primary' : False,
01228         'content_class': BaseUnit,
01229         })
01230 
01231     security  = ClassSecurityInfo()
01232 
01233     security.declarePublic('defaultView')
01234     def defaultView(self):
01235         return self.default_output_type
01236 
01237     security.declarePrivate('setContentType')
01238     def setContentType(self, instance, value):
01239         """Set mimetype in the base unit.
01240         """
01241         bu = self.get(instance, raw=True)
01242         if shasattr(bu, 'setContentType'):
01243             bu.setContentType(instance, value)
01244             self.set(instance, bu)
01245         else:
01246             log('Did not get a BaseUnit to set the content type',
01247                 level=ERROR)
01248 
01249     getContentType = ObjectField.getContentType.im_func
01250 
01251     security.declarePublic('getAllowedContentTypes')
01252     def getAllowedContentTypes(self, instance):
01253         """ returns the list of allowed content types for this field.
01254             If the fields schema doesn't define any, the site's default
01255             values are returned.
01256         """
01257         act_attribute = getattr(self, 'allowable_content_types', None)
01258         if act_attribute is None:
01259             return getAllowedContentTypesProperty(instance) 
01260         else:
01261             return act_attribute
01262 
01263     def _make_file(self, id, title='', file='', instance=None):
01264         return self.content_class(id, file=file, instance=instance)
01265 
01266     def _process_input(self, value, file=None, default=None,
01267                        mimetype=None, instance=None, **kwargs):
01268         if file is None:
01269             file = self._make_file(self.getName(), title='',
01270                                    file='', instance=instance)
01271         filename = kwargs.get('filename') or ''
01272         body = None
01273         if IBaseUnit.isImplementedBy(value):
01274             mimetype = value.getContentType() or mimetype
01275             filename = value.getFilename() or filename
01276             return value, mimetype, filename
01277         elif isinstance(value, self.content_class):
01278             filename = getattr(value, 'filename', value.getId())
01279             mimetype = getattr(value, 'content_type', mimetype)
01280             return value, mimetype, filename
01281         elif isinstance(value, File):
01282             # In case someone changes the 'content_class'
01283             filename = getattr(value, 'filename', value.getId())
01284             mimetype = getattr(value, 'content_type', mimetype)
01285             value = value.data
01286         elif isinstance(value, FileUpload) or shasattr(value, 'filename'):
01287             filename = value.filename
01288             # TODO Should be fixed eventually
01289             body = value.read(CHUNK)
01290             value.seek(0)
01291         elif isinstance(value, FileType) or shasattr(value, 'name'):
01292             # In this case, give preference to a filename that has
01293             # been detected before. Usually happens when coming from PUT().
01294             if not filename:
01295                 filename = value.name
01296                 # Should we really special case here?
01297                 for v in (filename, repr(value)):
01298                     # Windows unnamed temporary file has '<fdopen>' in
01299                     # repr() and full path in 'file.name'
01300                     if '<fdopen>' in v:
01301                         filename = ''
01302             # TODO Should be fixed eventually
01303             body = value.read(CHUNK)
01304             value.seek(0)
01305         elif isinstance(value, basestring):
01306             # Let it go, mimetypes_registry will be used below if available
01307             pass
01308         elif isinstance(value, Pdata):
01309             pass
01310         elif shasattr(value, 'read') and shasattr(value, 'seek'):
01311             # Can't get filename from those.
01312             body = value.read(CHUNK)
01313             value.seek(0)
01314         elif value is None:
01315             # Special case for setDefault.
01316             value = ''
01317         else:
01318             klass = getattr(value, '__class__', None)
01319             raise TextFieldException('Value is not File or String (%s - %s)' %
01320                                      (type(value), klass))
01321         if isinstance(value, Pdata):
01322             # TODO Should be fixed eventually
01323             value = str(value)
01324         filename = filename[max(filename.rfind('/'),
01325                                 filename.rfind('\\'),
01326                                 filename.rfind(':'),
01327                                 )+1:]
01328 
01329         if mimetype is None or mimetype == 'text/x-unknown-content-type':
01330             if body is None:
01331                 body = value[:CHUNK]
01332             mtr = getToolByName(instance, 'mimetypes_registry', None)
01333             if mtr is not None:
01334                 kw = {'mimetype':None,
01335                       'filename':filename}
01336                 d, f, mimetype = mtr(body, **kw)
01337             else:
01338                 mimetype, enc = guess_content_type(filename, body, mimetype)
01339         # mimetype, if coming from request can be like:
01340         # text/plain; charset='utf-8'
01341         mimetype = str(mimetype).split(';')[0]
01342         file.update(value, instance, mimetype=mimetype, filename=filename)
01343         file.setContentType(instance, mimetype)
01344         file.setFilename(filename)
01345         return file, str(file.getContentType()), file.getFilename()
01346 
01347     security.declarePrivate('getRaw')
01348     def getRaw(self, instance, raw=False, **kwargs):
01349         """
01350         If raw, return the base unit object, else return encoded raw data
01351         """
01352         value = self.get(instance, raw=True, **kwargs)
01353         if raw or not IBaseUnit.isImplementedBy(value):
01354             return value
01355         kw = {'encoding':kwargs.get('encoding'),
01356               'instance':instance}
01357         args = []
01358         return mapply(value.getRaw, *args, **kw)
01359 
01360     security.declarePrivate('get')
01361     def get(self, instance, mimetype=None, raw=False, **kwargs):
01362         """ If raw, return the base unit object, else return value of
01363         object transformed into requested mime type.
01364 
01365         If no requested type, then return value in default type. If raw
01366         format is specified, try to transform data into the default output type
01367         or to plain text.
01368         If we are unable to transform data, return an empty string. """
01369         try:
01370             kwargs['field'] = self
01371             storage = self.getStorage(instance)
01372             value = storage.get(self.getName(), instance, **kwargs)
01373             if not IBaseUnit.isImplementedBy(value):
01374                 value = self._wrapValue(instance, value)
01375         except AttributeError:
01376             # happens if new Atts are added and not yet stored in the instance
01377             if not kwargs.get('_initializing_', False):
01378                 self.set(instance, self.getDefault(instance),
01379                          _initializing_=True, **kwargs)
01380             value = self._wrapValue(instance, self.getDefault(instance))
01381 
01382         if raw:
01383             return value
01384 
01385         if mimetype is None:
01386             mimetype = self.default_output_type or 'text/plain'
01387 
01388         if not shasattr(value, 'transform'): # oldBaseUnits have no transform
01389             return str(value)
01390         data = value.transform(instance, mimetype,
01391                                encoding=kwargs.get('encoding',None))
01392         if not data and mimetype != 'text/plain':
01393             data = value.transform(instance, 'text/plain',
01394                                    encoding=kwargs.get('encoding',None))
01395         return data or ''
01396 
01397     security.declarePrivate('getBaseUnit')
01398     def getBaseUnit(self, instance, full=False):
01399         """Return the value of the field wrapped in a base unit object
01400         """
01401         return self.get(instance, raw=True)
01402 
01403     security.declarePublic('get_size')
01404     def get_size(self, instance):
01405         """Get size of the stored data used for get_size in BaseObject
01406         """
01407         return len(self.getBaseUnit(instance))
01408 
01409 class DateTimeField(ObjectField):
01410     """A field that stores dates and times"""
01411     __implements__ = ObjectField.__implements__
01412 
01413     _properties = Field._properties.copy()
01414     _properties.update({
01415         'type' : 'datetime',
01416         'widget' : CalendarWidget,
01417         })
01418 
01419     security  = ClassSecurityInfo()
01420     
01421     security.declarePrivate('validate_required')
01422     def validate_required(self, instance, value, errors):
01423         try:
01424             DateTime(value)
01425         except DateTime.DateTimeError:
01426             result = False
01427         else:
01428             # None is a valid DateTime input, but does not validate for
01429             # required.
01430             result = value is not None
01431         return ObjectField.validate_required(self, instance, result, errors)
01432 
01433 
01434     security.declarePrivate('set')
01435     def set(self, instance, value, **kwargs):
01436         """
01437         Check if value is an actual date/time value. If not, attempt
01438         to convert it to one; otherwise, set to None. Assign all
01439         properties passed as kwargs to object.
01440         """
01441         if not value:
01442             value = None
01443         elif not isinstance(value, DateTime):
01444             try:
01445                 value = DateTime(value)
01446             except DateTime.DateTimeError:
01447                 value = None
01448 
01449         ObjectField.set(self, instance, value, **kwargs)
01450 
01451 class LinesField(ObjectField):
01452     """For creating lines objects"""
01453     __implements__ = ObjectField.__implements__
01454 
01455     _properties = Field._properties.copy()
01456     _properties.update({
01457         'type' : 'lines',
01458         'default' : (),
01459         'widget' : LinesWidget,
01460         })
01461 
01462     security  = ClassSecurityInfo()
01463 
01464     security.declarePrivate('set')
01465     def set(self, instance, value, **kwargs):
01466         """
01467         If passed-in value is a string, split at line breaks and
01468         remove leading and trailing white space before storing in object
01469         with rest of properties.
01470         """
01471         __traceback_info__ = value, type(value)
01472         if type(value) in STRING_TYPES:
01473             value =  value.split('\n')
01474         value = [decode(v.strip(), instance, **kwargs)
01475                  for v in value if v and v.strip()]
01476         if config.ZOPE_LINES_IS_TUPLE_TYPE:
01477             value = tuple(value)
01478         ObjectField.set(self, instance, value, **kwargs)
01479 
01480     security.declarePrivate('get')
01481     def get(self, instance, **kwargs):
01482         value = ObjectField.get(self, instance, **kwargs) or ()
01483         data = [encode(v, instance, **kwargs) for v in value]
01484         if config.ZOPE_LINES_IS_TUPLE_TYPE:
01485             return tuple(data)
01486         else:
01487             return data
01488 
01489     security.declarePrivate('getRaw')
01490     def getRaw(self, instance, **kwargs):
01491         return self.get(instance, **kwargs)
01492 
01493     security.declarePublic('get_size')
01494     def get_size(self, instance):
01495         """Get size of the stored data used for get_size in BaseObject
01496         """
01497         size=0
01498         for line in self.get(instance):
01499             size+=len(str(line))
01500         return size
01501 
01502 
01503 class IntegerField(ObjectField):
01504     """A field that stores an integer"""
01505     __implements__ = ObjectField.__implements__
01506 
01507     _properties = Field._properties.copy()
01508     _properties.update({
01509         'type' : 'integer',
01510         'size' : '10',
01511         'widget' : IntegerWidget,
01512         'default' : None,
01513         })
01514 
01515     security  = ClassSecurityInfo()
01516 
01517     security.declarePrivate('validate_required')
01518     def validate_required(self, instance, value, errors):
01519         try:
01520             int(value)
01521         except (ValueError, TypeError):
01522             result = False
01523         else:
01524             result = True
01525         return ObjectField.validate_required(self, instance, result, errors)
01526 
01527     security.declarePrivate('set')
01528     def set(self, instance, value, **kwargs):
01529         if value=='':
01530             value=None
01531         elif value is not None:
01532             # should really blow if value is not valid
01533             __traceback_info__ = (self.getName(), instance, value, kwargs)
01534             value = int(value)
01535 
01536         ObjectField.set(self, instance, value, **kwargs)
01537 
01538 class FloatField(ObjectField):
01539     """A field that stores floats"""
01540     _properties = Field._properties.copy()
01541     _properties.update({
01542         'type' : 'float',
01543         'default': None
01544         })
01545 
01546     security  = ClassSecurityInfo()
01547 
01548     security.declarePrivate('validate_required')
01549     def validate_required(self, instance, value, errors):
01550         try:
01551             float(value)
01552         except (ValueError, TypeError):
01553             result = False
01554         else:
01555             result = True
01556         return ObjectField.validate_required(self, instance, result, errors)
01557 
01558 
01559     security.declarePrivate('set')
01560     def set(self, instance, value, **kwargs):
01561         """Convert passed-in value to a float. If failure, set value to
01562         None."""
01563         if value=='':
01564             value=None
01565         elif value is not None:
01566             # should really blow if value is not valid
01567             __traceback_info__ = (self.getName(), instance, value, kwargs)
01568             value = float(value)
01569 
01570         ObjectField.set(self, instance, value, **kwargs)
01571 
01572 class FixedPointField(ObjectField):
01573     """A field for storing numerical data with fixed points"""
01574     __implements__ = ObjectField.__implements__
01575 
01576     _properties = Field._properties.copy()
01577     _properties.update({
01578         'type' : 'fixedpoint',
01579         'precision' : 2,
01580         'default' : '0.00',
01581         'widget' : DecimalWidget,
01582         'validators' : ('isDecimal'),
01583         })
01584 
01585     security  = ClassSecurityInfo()
01586 
01587     def _to_tuple(self, instance, value):
01588         """ COMMENT TO-DO """
01589         if not value:
01590             value = self.getDefault(instance)
01591 
01592         # XXX :-(
01593         # Dezimal Point is very english. as a first hack
01594         # we should allow also the more contintental european comma.
01595         # The clean solution is to lookup:
01596         # * the locale settings of the zope-server, Plone, logged in user
01597         # * maybe the locale of the browser sending the value.
01598         # same should happen with the output.
01599         if isinstance(value, basestring):
01600             value = value.replace(',','.')
01601 
01602         value = value.split('.')
01603         __traceback_info__ = (self, value)
01604         if len(value) < 2:
01605             value = (int(value[0]), 0)
01606         else:
01607             fra = value[1][:self.precision]
01608             fra += '0' * (self.precision - len(fra))
01609             #handle leading comma e.g. .36
01610             if value[0]=='':
01611                 value[0]='0'
01612             value = (int(value[0]), int(fra))
01613         return value
01614 
01615     security.declarePrivate('set')
01616     def set(self, instance, value, **kwargs):
01617         value = self._to_tuple(instance, value)
01618         ObjectField.set(self, instance, value, **kwargs)
01619 
01620     security.declarePrivate('get')
01621     def get(self, instance, **kwargs):
01622         template = '%%d.%%0%dd' % self.precision
01623         value = ObjectField.get(self, instance, **kwargs)
01624         __traceback_info__ = (template, value)
01625         if value is None: return self.getDefault(instance)
01626         if isinstance(value, basestring):
01627             value = self._to_tuple(instance, value)
01628         return template % value
01629 
01630     security.declarePrivate('validate_required')
01631     def validate_required(self, instance, value, errors):
01632         value = sum(self._to_tuple(instance, value))
01633         return ObjectField.validate_required(self, instance, value, errors)
01634 
01635 class ReferenceField(ObjectField):
01636     """A field for creating references between objects.
01637 
01638     get() returns the list of objects referenced under the relationship
01639     set() converts a list of target UIDs into references under the
01640     relationship associated with this field.
01641 
01642     If no vocabulary is provided by you, one will be assembled based on
01643     allowed_types.
01644     """
01645 
01646     __implements__ = ObjectField.__implements__
01647 
01648     _properties = Field._properties.copy()
01649     _properties.update({
01650         'type' : 'reference',
01651         'default' : None,
01652         'widget' : ReferenceWidget,
01653 
01654         'relationship' : None, # required
01655         'allowed_types' : (),  # a tuple of portal types, empty means allow all
01656         'allowed_types_method' :None,
01657         'vocabulary_display_path_bound': 5, # if len(vocabulary) > 5, we'll
01658                                             # display path as well
01659         'vocabulary_custom_label': None, # e.g. "b.getObject().title_or_id()".
01660                                          # if given, this will
01661                                          # override display_path_bound
01662         'referenceClass' : Reference,
01663         'referenceReferences' : False,
01664         'keepReferencesOnCopy' : False,
01665         'callStorageOnSet': False,
01666         'index_method' : '_at_edit_accessor',
01667         })
01668 
01669     security  = ClassSecurityInfo()
01670 
01671     security.declarePrivate('get')
01672     def get(self, instance, aslist=False, **kwargs):
01673         """get() returns the list of objects referenced under the relationship
01674         """
01675         res = instance.getRefs(relationship=self.relationship)
01676 
01677         # singlevalued ref fields return only the object, not a list,
01678         # unless explicitely specified by the aslist option
01679    
01680         if not self.multiValued:
01681             if len(res) > 1:
01682                 log("%s references for non multivalued field %s of %s" % (len(res),
01683                                                                           self.getName(),
01684                                                                           instance))
01685             if not aslist:
01686                 if res:
01687                     res = res[0]
01688                 else:
01689                     res = None
01690 
01691         return res
01692 
01693     security.declarePrivate('set')
01694     def set(self, instance, value, **kwargs):
01695         """Mutator.
01696 
01697         ``value`` is a either a list of UIDs or one UID string, or a
01698         list of objects or one object to which I will add a reference
01699         to. None and [] are equal.
01700 
01701         >>> for node in range(3):
01702         ...     _ = self.folder.invokeFactory('Refnode', 'n%s' % node)
01703 
01704         Use set with a list of objects:
01705 
01706         >>> nodes = self.folder.n0, self.folder.n1, self.folder.n2
01707         >>> nodes[0].setLinks(nodes[1:])
01708         >>> nodes[0].getLinks()
01709         [<Refnode...>, <Refnode...>]
01710 
01711         Use it with None or () to delete references:
01712 
01713         >>> nodes[0].setLinks(None)
01714         >>> nodes[0].getLinks()
01715         []
01716 
01717         Use a list of UIDs to set:
01718         
01719         >>> nodes[0].setLinks([n.UID() for n in nodes[1:]])
01720         >>> nodes[0].getLinks()
01721         [<Refnode...>, <Refnode...>]
01722         >>> nodes[0].setLinks(())
01723         >>> nodes[0].getLinks()
01724         []
01725 
01726         Setting multiple values for a non multivalued field will fail:
01727         
01728         >>> nodes[1].setLink(nodes)
01729         Traceback (most recent call last):
01730         ...
01731         ValueError: Multiple values ...
01732 
01733         Keyword arguments may be passed directly to addReference(),
01734         thereby creating properties on the reference objects:
01735         
01736         >>> nodes[1].setLink(nodes[0].UID(), foo='bar', spam=1)
01737         >>> ref = nodes[1].getReferenceImpl()[0]
01738         >>> ref.foo, ref.spam
01739         ('bar', 1)
01740 
01741         Empty BTreeFolders work as values (#1212048):
01742 
01743         >>> _ = self.folder.invokeFactory('SimpleBTreeFolder', 'btf')
01744         >>> nodes[2].setLink(self.folder.btf)
01745         >>> nodes[2].getLink()
01746         <SimpleBTreeFolder...>
01747         """
01748         tool = getToolByName(instance, REFERENCE_CATALOG)
01749         targetUIDs = [ref.targetUID for ref in
01750                       tool.getReferences(instance, self.relationship)]
01751 
01752         if value is None:
01753             value = ()
01754 
01755         if not isinstance(value, (ListType, TupleType)):
01756             value = value,
01757         elif not self.multiValued and len(value) > 1:
01758             raise ValueError, \
01759                   "Multiple values given for single valued field %r" % self
01760 
01761         #convert objects to uids if necessary
01762         uids = []
01763         for v in value:
01764             if type(v) in STRING_TYPES:
01765                 uids.append(v)
01766             else:
01767                 uids.append(v.UID())
01768 
01769         add = [v for v in uids if v and v not in targetUIDs]
01770         sub = [t for t in targetUIDs if t not in uids]
01771 
01772         # tweak keyword arguments for addReference
01773         addRef_kw = kwargs.copy()
01774         addRef_kw.setdefault('referenceClass', self.referenceClass)
01775         if addRef_kw.has_key('schema'): del addRef_kw['schema']
01776 
01777         for uid in add:
01778             __traceback_info__ = (instance, uid, value, targetUIDs)
01779             # throws IndexError if uid is invalid
01780             tool.addReference(instance, uid, self.relationship, **addRef_kw)
01781 
01782         for uid in sub:
01783             tool.deleteReference(instance, uid, self.relationship)
01784 
01785         if self.callStorageOnSet:
01786             #if this option is set the reference fields's values get written
01787             #to the storage even if the reference field never use the storage
01788             #e.g. if i want to store the reference UIDs into an SQL field
01789             ObjectField.set(self, instance, self.getRaw(instance), **kwargs)
01790 
01791     security.declarePrivate('getRaw')
01792     def getRaw(self, instance, aslist=False, **kwargs):
01793         """Return the list of UIDs referenced under this fields
01794         relationship
01795         """
01796         rc = getToolByName(instance, REFERENCE_CATALOG)
01797         brains = rc(sourceUID=instance.UID(),
01798                     relationship=self.relationship)
01799         res = [b.targetUID for b in brains]
01800         if not self.multiValued and not aslist:
01801             if res:
01802                 res = res[0]
01803             else:
01804                 res = None
01805         return res
01806 
01807     security.declarePublic('Vocabulary')
01808     def Vocabulary(self, content_instance=None):
01809         """Use vocabulary property if it's been defined."""
01810         if self.vocabulary or getattr(self, 'vocabulary_factory', None):
01811             return ObjectField.Vocabulary(self, content_instance)
01812         else:
01813             return self._Vocabulary(content_instance).sortedByValue()
01814 
01815     def _brains_title_or_id(self, brain, instance):
01816         """ ensure the brain has a title or an id and return it as unicode"""
01817         title = None
01818         if shasattr(brain, 'getId'):
01819             title = brain.getId
01820         if shasattr(brain, 'Title') and brain.Title != '':
01821             title = brain.Title
01822 
01823         if title is not None and isinstance(title, basestring):
01824             return decode(title, instance)
01825         
01826         raise AttributeError, "Brain has no title or id"
01827 
01828     def _Vocabulary(self, content_instance):
01829         pairs = []
01830         pc = getToolByName(content_instance, 'portal_catalog')
01831         uc = getToolByName(content_instance, config.UID_CATALOG)
01832         purl = getToolByName(content_instance, 'portal_url')
01833 
01834         allowed_types = self.allowed_types
01835         allowed_types_method = getattr(self, 'allowed_types_method', None)
01836         if allowed_types_method:
01837             meth = getattr(content_instance,allowed_types_method)
01838             allowed_types = meth(self)
01839 
01840         skw = allowed_types and {'portal_type':allowed_types} or {}
01841         brains = uc.searchResults(**skw)
01842 
01843         if self.vocabulary_custom_label is not None:
01844             label = lambda b:eval(self.vocabulary_custom_label, {'b': b})
01845         elif self.vocabulary_display_path_bound != -1 and len(brains) > self.vocabulary_display_path_bound:
01846             at = _(u'label_at', default=u'at')
01847             label = lambda b:u'%s %s %s' % (self._brains_title_or_id(b, content_instance),
01848                                              at, b.getPath())
01849         else:
01850             label = lambda b:self._brains_title_or_id(b, content_instance)
01851 
01852         # The UID catalog is the correct catalog to pull this
01853         # information from, however the workflow and perms are not accounted
01854         # for there. We thus check each object in the portal catalog
01855         # to ensure it validity for this user.
01856         portal_base = purl.getPortalPath()
01857         path_offset = len(portal_base) + 1
01858 
01859         abs_paths = {}
01860         abs_path = lambda b, p=portal_base: '%s/%s' % (p, b.getPath())
01861         [abs_paths.update({abs_path(b):b}) for b in brains]
01862 
01863         pc_brains = pc(path=abs_paths.keys(), **skw)
01864 
01865         for b in pc_brains:
01866             b_path = b.getPath()
01867             # translate abs path to rel path since uid_cat stores
01868             # paths relative now
01869             path = b_path[path_offset:]
01870             # The reference field will not expose Refrerences by
01871             # default, this is a complex use-case and makes things too hard to
01872             # understand for normal users. Because of reference class
01873             # we don't know portal type but we can look for the annotation key in
01874             # the path
01875             if self.referenceReferences is False and \
01876                path.find(config.REFERENCE_ANNOTATION) != -1:
01877                 continue
01878 
01879             # now check if the results from the pc is the same as in uc.
01880             # so we verify that b is a result that was also returned by uc,
01881             # hence the check in abs_paths.
01882             if abs_paths.has_key(b_path):
01883                 uid = abs_paths[b_path].UID
01884                 if uid is None:
01885                     # the brain doesn't have an uid because the catalog has a
01886                     # staled object. THAT IS BAD!
01887                     raise ReferenceException("Brain for the object at %s "\
01888                         "doesn't have an UID assigned with. Please update your"\
01889                         " reference catalog!" % b_path)
01890                 pairs.append((uid, label(b)))
01891 
01892         if not self.required and not self.multiValued:
01893             no_reference = _(u'label_no_reference',
01894                              default=u'<no reference>')
01895             pairs.insert(0, ('', no_reference))
01896 
01897         __traceback_info__ = (content_instance, self.getName(), pairs)
01898         return DisplayList(pairs)
01899 
01900     security.declarePublic('get_size')
01901     def get_size(self, instance):
01902         """Get size of the stored data used for get_size in BaseObject
01903         """
01904         return 0
01905 
01906 
01907 class ComputedField(Field):
01908     """A field that stores a read-only computation."""
01909     __implements__ = Field.__implements__
01910 
01911     _properties = Field._properties.copy()
01912     _properties.update({
01913         'type' : 'computed',
01914         'expression': None,
01915         'widget' : ComputedWidget,
01916         'mode' : 'r',
01917         'storage': ReadOnlyStorage(),
01918         })
01919 
01920     security = ClassSecurityInfo()
01921 
01922     security.declarePrivate('set')
01923     def set(self, *ignored, **kwargs):
01924         pass
01925 
01926     security.declarePrivate('get')
01927     def get(self, instance, **kwargs):
01928         """Return the computed value."""
01929         return eval(self.expression, {'context': instance, 'here' : instance})
01930 
01931     security.declarePublic('get_size')
01932     def get_size(self, instance):
01933         """Get size of the stored data.
01934 
01935         Used for get_size in BaseObject.
01936         """
01937         return 0
01938 
01939 class BooleanField(ObjectField):
01940     """A field that stores boolean values."""
01941     __implements__ = ObjectField.__implements__
01942     _properties = Field._properties.copy()
01943     _properties.update({
01944         'type' : 'boolean',
01945         'default': None,
01946         'vocabulary': (('True','Yes', 'yes'),('False','No', 'no')),
01947         'widget' : BooleanWidget,        
01948         })
01949 
01950     security  = ClassSecurityInfo()
01951 
01952     security.declarePrivate('get')
01953     def get(self, instance, **kwargs):
01954         value = super(BooleanField, self).get(instance, **kwargs) 
01955         if value is None:
01956             return value
01957         return bool(value)
01958 
01959     security.declarePrivate('getRaw')
01960     def getRaw(self, instance, **kwargs):
01961         value = super(BooleanField, self).getRaw(instance, **kwargs) 
01962         if value is None:
01963             return value
01964         return bool(value)
01965 
01966     security.declarePrivate('set')
01967     def set(self, instance, value, **kwargs):
01968         """If value is not defined or equal to 0, set field to false;
01969         otherwise, set to true."""
01970         if not value or value == '0' or value == 'False':
01971             value = False
01972         else:
01973             value = True
01974 
01975         ObjectField.set(self, instance, value, **kwargs)
01976 
01977     security.declarePublic('get_size')
01978     def get_size(self, instance):
01979         """Get size of the stored data used for get_size in BaseObject
01980         """
01981         return True
01982 
01983 class CMFObjectField(ObjectField):
01984     """
01985     COMMENT TODO
01986     """
01987     __implements__ = ObjectField.__implements__
01988     _properties = Field._properties.copy()
01989     _properties.update({
01990         'type' : 'object',
01991         'portal_type': 'File',
01992         'default': None,
01993         'default_mime_type': 'application/octet-stream',
01994         'widget' : FileWidget,
01995         'storage': ObjectManagedStorage(),
01996         'workflowable': True,
01997         })
01998 
01999     security  = ClassSecurityInfo()
02000 
02001     def _process_input(self, value, default=None, **kwargs):
02002         __traceback_info__ = (value, type(value))
02003         if not isinstance(value, basestring):
02004             if ((isinstance(value, FileUpload) and value.filename != '') or \
02005                 (isinstance(value, FileType) and value.name != '')):
02006                 # OK, its a file, is it empty?
02007                 value.seek(-1, 2)
02008                 size = value.tell()
02009                 value.seek(0)
02010                 if size == 0:
02011                     # This new file has no length, so we keep
02012                     # the orig
02013                     return default
02014                 return value
02015             if value is None:
02016                 return default
02017         else:
02018             if value == '':
02019                 return default
02020             return value
02021 
02022         raise ObjectFieldException('Value is not File or String')
02023 
02024     security.declarePrivate('get')
02025     def get(self, instance, **kwargs):
02026         try:
02027             return self.getStorage(instance).get(self.getName(), instance, **kwargs)
02028         except AttributeError:
02029             # object doesnt exists
02030             tt = getToolByName(instance, 'portal_types', None)
02031             if tt is None:
02032                 msg = "Coudln't get portal_types tool from this context"
02033                 raise AttributeError(msg)
02034             type_name = self.portal_type
02035             info = tt.getTypeInfo(type_name)
02036             if info is None:
02037                 raise ValueError('No such content type: %s' % type_name)
02038             if not shasattr(info, 'constructInstance'):
02039                 raise ValueError('Cannot construct content type: %s' % \
02040                                  type_name)
02041             args = [instance, self.getName()]
02042             for k in ['field', 'schema']:
02043                 del kwargs[k]
02044             return mapply(info.constructInstance, *args, **kwargs)
02045 
02046     security.declarePrivate('set')
02047     def set(self, instance, value, **kwargs):
02048         obj = self.get(instance, **kwargs)
02049         value = self._process_input(value, default=self.getDefault(instance), \
02050                                     **kwargs)
02051         if value is None or value == '':
02052             # do nothing
02053             return
02054 
02055         obj.edit(file=value)
02056         # The object should be already stored, so we dont 'set' it,
02057         # but just change instead.
02058         # ObjectField.set(self, instance, obj, **kwargs)
02059 
02060 
02061 # ImageField.py
02062 # Written in 2003 by Christian Scholz (cs@comlounge.net)
02063 # version: 1.0 (26/02/2002)
02064 
02065 class Image(BaseImage):
02066 
02067     security  = ClassSecurityInfo()
02068 
02069     def title(self):
02070         parent = aq_parent(aq_inner(self))
02071         if parent is not None:
02072             return parent.Title() or parent.getId()
02073         return self.getId()
02074 
02075     title = ComputedAttribute(title, 1)
02076 
02077     alt = title_or_id = title
02078 
02079     def isBinary(self):
02080         return True
02081 
02082 class ImageField(FileField):
02083     """ implements an image attribute. it stores
02084         it's data in an image sub-object
02085 
02086         sizes is an dictionary containing the sizes to
02087         scale the image to. PIL is required for that.
02088 
02089         Format:
02090         sizes={'mini': (50,50),
02091                'normal' : (100,100), ... }
02092         syntax: {'name': (width,height), ... }
02093 
02094         the scaled versions can then be accessed as
02095         object/<imagename>_<scalename>
02096 
02097         e.g. object/image_mini
02098 
02099         where <imagename> is the fieldname and <scalename>
02100         is the name from the dictionary
02101 
02102         original_size -- this parameter gives the size in (w,h)
02103         to which the original image will be scaled. If it's None,
02104         then no scaling will take place.
02105         This is important if you don't want to store megabytes of
02106         imagedata if you only need a max. of 100x100 ;-)
02107 
02108         max_size -- similar to max_size but if it's given then the image
02109                     is checked to be no bigger than any of the given values
02110                     of width or height.
02111 
02112         example:
02113 
02114         ImageField('image',
02115             original_size=(600,600),
02116             sizes={ 'mini' : (80,80),
02117                     'normal' : (200,200),
02118                     'big' : (300,300),
02119                     'maxi' : (500,500)})
02120 
02121         will create an attribute called "image"
02122         with the sizes mini, normal, big, maxi as given
02123         and a original sized image of max 600x600.
02124         This will be accessible as
02125         object/image
02126 
02127         and the sizes as
02128 
02129         object/image_mini
02130         object/image_normal
02131         object/image_big
02132         object/image_maxi
02133 
02134         the official API to get tag (in a pagetemplate) is
02135         obj.getField('image').tag(obj, scale='mini')
02136         ...
02137 
02138         sizes may be the name of a method in the instance or a callable which
02139         returns a dict.
02140         
02141         Don't remove scales once they exist! Instead of removing a scale
02142         from the list of sizes you should set the size to (0,0). Thus
02143         removeScales method is able to find the scales to delete the
02144         data.
02145 
02146         Scaling will only be available if PIL is installed!
02147 
02148         If 'DELETE_IMAGE' will be given as value, then all the images
02149         will be deleted (None is understood as no-op)
02150         """
02151 
02152     # XXX__implements__ = FileField.__implements__ , IImageField
02153 
02154     _properties = FileField._properties.copy()
02155     _properties.update({
02156         'type' : 'image',
02157         'default' : '',
02158         'original_size': None,
02159         'max_size': None,
02160         'sizes' : {'thumb':(80,80)},
02161         'swallowResizeExceptions' : False,
02162         'pil_quality' : 88,
02163         'pil_resize_algo' : PIL_ALGO, 
02164         'default_content_type' : 'image/png',
02165         'allowable_content_types' : ('image/gif','image/jpeg','image/png'),
02166         'widget': ImageWidget,
02167         'storage': AttributeStorage(),
02168         'content_class': Image,
02169         })
02170 
02171     security  = ClassSecurityInfo()
02172 
02173     default_view = "view"
02174 
02175     security.declarePrivate('set')
02176     def set(self, instance, value, **kwargs):
02177         if not value:
02178             return
02179         # Do we have to delete the image?
02180         if value=="DELETE_IMAGE":
02181             self.removeScales(instance, **kwargs)
02182             # unset main field too
02183             ObjectField.unset(self, instance, **kwargs)
02184             return
02185 
02186         kwargs.setdefault('mimetype', None)
02187         default = self.getDefault(instance)
02188         value, mimetype, filename = self._process_input(value, default=default,
02189                                                         instance=instance, **kwargs)
02190         # value is an OFS.Image.File based instance
02191         # don't store empty images
02192         get_size = getattr(value, 'get_size', None)
02193         if get_size is not None and get_size() == 0:
02194             return
02195         
02196         kwargs['mimetype'] = mimetype
02197         kwargs['filename'] = filename
02198 
02199         try:
02200             data = self.rescaleOriginal(value, **kwargs)
02201         except (ConflictError, KeyboardInterrupt):
02202             raise
02203         except:
02204             if not self.swallowResizeExceptions:
02205                 raise
02206             else:
02207                 log_exc()
02208                 data = str(value.data)
02209         # TODO add self.ZCacheable_invalidate() later
02210         self.createOriginal(instance, data, **kwargs)
02211         self.createScales(instance, value=data)
02212 
02213     security.declareProtected(permissions.View, 'getAvailableSizes')
02214     def getAvailableSizes(self, instance):
02215         """Get sizes
02216 
02217         Supports:
02218             self.sizes as dict
02219             A method in instance called like sizes that returns dict
02220             A callable
02221         """
02222         sizes = self.sizes
02223         if isinstance(sizes, dict):
02224             return sizes
02225         elif isinstance(sizes, basestring):
02226             assert(shasattr(instance, sizes))
02227             method = getattr(instance, sizes)
02228             data = method()
02229             assert(isinstance(data, dict))
02230             return data
02231         elif callable(sizes):
02232             return sizes()
02233         elif sizes is None:
02234             return {}
02235         else:
02236             raise TypeError, 'Wrong self.sizes has wrong type: %s' % type(sizes)
02237 
02238     security.declareProtected(permissions.ModifyPortalContent, 'rescaleOriginal')
02239     def rescaleOriginal(self, value, **kwargs):
02240         """rescales the original image and sets the data
02241 
02242         for self.original_size or self.max_size
02243         
02244         value must be an OFS.Image.Image instance
02245         """
02246         data = str(value.data)
02247         if not HAS_PIL:
02248             return data
02249         
02250         mimetype = kwargs.get('mimetype', self.default_content_type)
02251         
02252         if self.original_size or self.max_size:
02253             if not value:
02254                 return self.default
02255             w=h=0
02256             if self.max_size:
02257                 if value.width > self.max_size[0] or \
02258                        value.height > self.max_size[1]:
02259                     factor = min(float(self.max_size[0])/float(value.width),
02260                                  float(self.max_size[1])/float(value.height))
02261                     w = int(factor*value.width)
02262                     h = int(factor*value.height)
02263             elif self.original_size:
02264                 w,h = self.original_size
02265             if w and h:
02266                 __traceback_info__ = (self, value, w, h)
02267                 fvalue, format = self.scale(data, w, h)
02268                 data = fvalue.read()
02269         else:
02270             data = str(value.data)
02271             
02272         return data
02273 
02274     security.declarePrivate('createOriginal')
02275     def createOriginal(self, instance, value, **kwargs):
02276         """create the original image (save it)
02277         """
02278         if value:
02279             image = self._wrapValue(instance, value, **kwargs)
02280         else:
02281             image = self.getDefault(instance)
02282 
02283         ObjectField.set(self, instance, image, **kwargs)
02284 
02285     security.declarePrivate('removeScales')
02286     def removeScales(self, instance, **kwargs):
02287         """Remove the scaled image
02288         """
02289         sizes = self.getAvailableSizes(instance)
02290         if sizes:
02291             for name, size in sizes.items():
02292                 id = self.getName() + "_" + name
02293                 try:
02294                     # the following line may throw exceptions on types, if the
02295                     # type-developer add sizes to a field in an existing
02296                     # instance and a user try to remove an image uploaded before
02297                     # that changed. The problem is, that the behavior for non
02298                     # existent keys isn't defined. I assume a keyerror will be
02299                     # thrown. Ignore that.
02300                     self.getStorage(instance).unset(id, instance, **kwargs)
02301                 except KeyError:
02302                     pass
02303 
02304     security.declareProtected(permissions.ModifyPortalContent, 'createScales')
02305     def createScales(self, instance, value=_marker):
02306         """creates the scales and save them
02307         """
02308         sizes = self.getAvailableSizes(instance)
02309         if not HAS_PIL or not sizes:
02310             return
02311         # get data from the original size if value is None
02312         if value is _marker:
02313             img = self.getRaw(instance)
02314             if not img:
02315                 return
02316             data = str(img.data)
02317         else:
02318             data = value
02319 
02320         # empty string - stop rescaling because PIL fails on an empty string
02321         if not data:
02322             return
02323 
02324         filename = self.getFilename(instance)
02325 
02326         for n, size in sizes.items():
02327             if size == (0,0):
02328                 continue
02329             w, h = size
02330             id = self.getName() + "_" + n
02331             __traceback_info__ = (self, instance, id, w, h)
02332             try:
02333                 imgdata, format = self.scale(data, w, h)
02334             except (ConflictError, KeyboardInterrupt):
02335                 raise
02336             except:
02337                 if not self.swallowResizeExceptions:
02338                     raise
02339                 else:
02340                     log_exc()
02341                     # scaling failed, don't create a scaled version
02342                     continue
02343 
02344             mimetype = 'image/%s' % format.lower()
02345             image = self._make_image(id, title=self.getName(), file=imgdata,
02346                                      content_type=mimetype, instance=instance)
02347             # nice filename: filename_sizename.ext
02348             #fname = "%s_%s%s" % (filename, n, ext)
02349             #image.filename = fname
02350             image.filename = filename
02351             try:
02352                 delattr(image, 'title')
02353             except (KeyError, AttributeError):
02354                 pass
02355             # manually use storage
02356             self.getStorage(instance).set(id, instance, image,
02357                                           mimetype=mimetype, filename=filename)
02358 
02359     def _make_image(self, id, title='', file='', content_type='', instance=None):
02360         """Image content factory"""
02361         return self.content_class(id, title, file, content_type)
02362 
02363     security.declarePrivate('scale')
02364     def scale(self, data, w, h, default_format = 'PNG'):
02365         """ scale image (with material from ImageTag_Hotfix)"""
02366         #make sure we have valid int's
02367         size = int(w), int(h)
02368 
02369         original_file=StringIO(data)
02370         image = PIL.Image.open(original_file)
02371         # consider image mode when scaling
02372         # source images can be mode '1','L,','P','RGB(A)'
02373         # convert to greyscale or RGBA before scaling
02374         # preserve palletted mode (but not pallette)
02375         # for palletted-only image formats, e.g. GIF
02376         # PNG compression is OK for RGBA thumbnails
02377         original_mode = image.mode
02378         if original_mode == '1':
02379             image = image.convert('L')
02380         elif original_mode == 'P':
02381             image = image.convert('RGBA')
02382         image.thumbnail(size, self.pil_resize_algo)
02383         format = image.format and image.format or default_format
02384         # decided to only preserve palletted mode
02385         # for GIF, could also use image.format in ('GIF','PNG')
02386         if original_mode == 'P' and format == 'GIF':
02387             image = image.convert('P')
02388         thumbnail_file = StringIO()
02389         # quality parameter doesn't affect lossless formats
02390         image.save(thumbnail_file, format, quality=self.pil_quality)
02391         thumbnail_file.seek(0)
02392         return thumbnail_file, format.lower()
02393 
02394     security.declareProtected(permissions.View, 'getSize')
02395     def getSize(self, instance, scale=None):
02396         """get size of scale or original
02397         """
02398         img = self.getScale(instance, scale=scale)
02399         if not img:
02400             return 0, 0
02401         return img.width, img.height
02402 
02403     security.declareProtected(permissions.View, 'getScale')
02404     def getScale(self, instance, scale=None, **kwargs):
02405         """Get scale by name or original
02406         """
02407         if scale is None:
02408             return self.get(instance, **kwargs)
02409         else:
02410             assert(scale in self.getAvailableSizes(instance).keys(),
02411                    'Unknown scale %s for %s' % (scale, self.getName()))
02412             id = self.getScaleName(scale=scale)
02413             try:
02414                 image = self.getStorage(instance).get(id, instance, **kwargs)
02415             except AttributeError:
02416                 return ''
02417             image = self._wrapValue(instance, image, **kwargs)
02418             if shasattr(image, '__of__', acquire=True) and not kwargs.get('unwrapped', False):
02419                 return image.__of__(instance)
02420             else:
02421                 return image
02422 
02423     security.declareProtected(permissions.View, 'getScaleName')
02424     def getScaleName(self, scale=None):
02425         """Get the full name of the attribute for the scale
02426         """
02427         if scale:
02428             return self.getName() + "_" + scale
02429         else:
02430             return ''
02431 
02432     security.declarePublic('get_size')
02433     def get_size(self, instance):
02434         """Get size of the stored data used for get_size in BaseObject
02435         
02436         TODO: We should only return the size of the original image
02437         """
02438         sizes = self.getAvailableSizes(instance)
02439         original = self.get(instance)
02440         size = original and original.get_size() or 0
02441 
02442         if sizes:
02443             for name in sizes.keys():
02444                 id = self.getScaleName(scale=name)
02445                 try:
02446                     data = self.getStorage(instance).get(id, instance)
02447                 except AttributeError:
02448                     pass
02449                 else:
02450                     size+=data and data.get_size() or 0
02451         return size
02452 
02453     security.declareProtected(permissions.View, 'tag')
02454     def tag(self, instance, scale=None, height=None, width=None, alt=None,
02455             css_class=None, title=None, **kwargs):
02456         """Create a tag including scale
02457         """
02458         image = self.getScale(instance, scale=scale)
02459         if image:
02460             img_width, img_height = self.getSize(instance, scale=scale)
02461         else:
02462             img_height=0
02463             img_width=0
02464 
02465         if height is None:
02466             height=img_height
02467         if width is None:
02468             width=img_width
02469 
02470         url = instance.absolute_url()
02471         if scale:
02472             url+= '/' + self.getScaleName(scale)
02473         else:
02474             url+= '/' + self.getName()
02475 
02476         values = {'src' : url,
02477                   'alt' : escape(alt and alt or instance.Title(), 1),
02478                   'title' : escape(title and title or instance.Title(), 1),
02479                   'height' : height,
02480                   'width' : width,
02481                  }
02482 
02483         result = '<img src="%(src)s" alt="%(alt)s" title="%(title)s" '\
02484                  'height="%(height)s" width="%(width)s"' % values
02485 
02486         if css_class is not None:
02487             result = '%s class="%s"' % (result, css_class)
02488 
02489         for key, value in kwargs.items():
02490             if value:
02491                 result = '%s %s="%s"' % (result, key, value)
02492 
02493         return '%s />' % result
02494 
02495 # photo field implementation, derived from CMFPhoto by Magnus Heino
02496 # DEPRECATED
02497 
02498 class DynVariantWrapper(Base):
02499     """Provide a transparent wrapper from image to dynvariant call it
02500     with url ${image_url}/variant/${variant}
02501     """
02502     
02503     def __init__(self):
02504         deprecated('DynVariantWrapper (for PhotoField) is deprecated after work '
02505                    'done on ImageField and ATImage. It will be removed in '
02506                    'Archetypes 1.5. If someone like to keep the code please '
02507                    'move it over to an own Product in MoreFieldsAndWidgets '
02508                    'repository.'
02509         )   
02510     def __of__(self, parent):
02511         return parent.Variants()
02512 
02513 class DynVariant(Implicit, Traversable):
02514     """Provide access to the variants."""
02515 
02516     def __init__(self):
02517         deprecated('DynVariant (for PhotoField) is deprecated after work '
02518                    'done on ImageField and ATImage. It will be removed in '
02519                    'Archetypes 1.5. If someone like to keep the code please '
02520                    'move it over to an own Product in MoreFieldsAndWidgets '
02521                    'repository.'
02522         )   
02523 
02524     def __getitem__(self, name):
02525         if self.checkForVariant(name):
02526             return self.getPhoto(name).__of__(aq_parent(self))
02527         else:
02528             return aq_parent(self)
02529 
02530 class ScalableImage(BaseImage):
02531     """A scalable image class."""
02532 
02533     __implements__ = BaseImage.__implements__
02534 
02535     meta_type = 'Scalable Image'
02536 
02537     isBinary = lambda self: True
02538 
02539     security  = ClassSecurityInfo()
02540 
02541     def __init__(self, id, title='', file='', displays={}):
02542         deprecated('ScalableImage (for PhotoField) is deprecated after work '
02543                    'done on ImageField and ATImage. It will be removed in '
02544                    'Archetypes 1.5. If someone like to keep the code please '
02545                    'move it over to an own Product in MoreFieldsAndWidgets '
02546                    'repository.'
02547         )        
02548         BaseImage.__init__(self, id, title, file)
02549         self._photos = OOBTree()
02550         self.displays = displays
02551 
02552     # make image variants accesable via url
02553     variant=DynVariantWrapper()
02554 
02555     security.declareProtected(permissions.View, 'Variants')
02556     def Variants(self):
02557         # Returns a variants wrapper instance
02558         return DynVariant().__of__(self)
02559 
02560     security.declareProtected(permissions.View, 'getPhoto')
02561     def getPhoto(self,size):
02562         '''returns the Photo of the specified size'''
02563         return self._photos[size]
02564 
02565     security.declareProtected(permissions.View, 'getDisplays')
02566     def getDisplays(self):
02567         result = []
02568         for name, size in self.displays.items():
02569             result.append({'name':name, 'label':'%s (%dx%d)' % (
02570                 name, size[0], size[1]),'size':size}
02571                 )
02572 
02573         #sort ascending by size
02574         result.sort(lambda d1,d2: cmp(
02575             d1['size'][0]*d1['size'][0],
02576             d2['size'][1]*d2['size'][1])
02577             )
02578         return result
02579 
02580     security.declarePrivate('checkForVariant')
02581     def checkForVariant(self, size):
02582         """Create variant if not there."""
02583         if size in self.displays.keys():
02584             # Create resized copy, if it doesnt already exist
02585             if not self._photos.has_key(size):
02586                 self._photos[size] = BaseImage(
02587                     size, size, self._resize(self.displays.get(size, (0,0)))
02588                     )
02589             # a copy with a content type other than image/* exists, this
02590             # probably means that the last resize process failed. retry
02591             elif not self._photos[size].getContentType().startswith('image'):
02592                 self._photos[size] = BaseImage(
02593                     size, size, self._resize(self.displays.get(size, (0,0)))
02594                     )
02595             return True
02596         else:
02597             return False
02598 
02599     security.declareProtected(permissions.View, 'index_html')
02600     def index_html(self, REQUEST, RESPONSE, size=None):
02601         """Return the image data."""
02602         if self.checkForVariant(size):
02603             return self.getPhoto(size).index_html(REQUEST, RESPONSE)
02604         return BaseImage.index_html(self, REQUEST, RESPONSE)
02605 
02606     security.declareProtected(permissions.View, 'tag')
02607     def tag(self, height=None, width=None, alt=None,
02608             scale=False, xscale=False, yscale=False, css_class=None,
02609             title=None, size='original', **args):
02610         """Return an HTML img tag (See OFS.Image)"""
02611 
02612         # Default values
02613         w=self.width
02614         h=self.height
02615 
02616         if height is None or width is None:
02617 
02618             if size in self.displays.keys():
02619                 if not self._photos.has_key(size):
02620                     # This resized image isn't created yet.
02621                     # Calculate a size for it
02622                     x,y = self.displays.get(size)
02623                     try:
02624                         if self.width > self.height:
02625                             w=x
02626                             h=int(round(1.0/(float(self.width)/w/self.height)))
02627                         else:
02628                             h=y
02629                             w=int(round(1.0/(float(self.height)/x/self.width)))
02630                     except ValueError:
02631                         # OFS.Image only knows about png, jpeg and gif.
02632                         # Other images like bmp will not have height and
02633                         # width set, and will generate a ValueError here.
02634                         # Everything will work, but the image-tag will render
02635                         # with height and width attributes.
02636                         w=None
02637                         h=None
02638                 else:
02639                     # The resized image exist, get it's size
02640                     photo = self._photos.get(size)
02641                     w=photo.width
02642                     h=photo.height
02643 
02644         if height is None: height=h
02645         if width is None:  width=w
02646 
02647         # Auto-scaling support
02648         xdelta = xscale or scale
02649         ydelta = yscale or scale
02650 
02651         if xdelta and width:
02652             width =  str(int(round(int(width) * xdelta)))
02653         if ydelta and height:
02654             height = str(int(round(int(height) * ydelta)))
02655 
02656         result='<img src="%s/variant/%s"' % (self.absolute_url(), escape(size))
02657 
02658         if alt is None:
02659             alt=getattr(self, 'title', '')
02660         result = '%s alt="%s"' % (result, escape(alt, 1))
02661 
02662         if title is None:
02663             title=getattr(self, 'title', '')
02664         result = '%s title="%s"' % (result, escape(title, 1))
02665 
02666         if height:
02667             result = '%s height="%s"' % (result, height)
02668 
02669         if width:
02670             result = '%s width="%s"' % (result, width)
02671 
02672         if not 'border' in [ x.lower() for x in args.keys()]:
02673             result = '%s border="0"' % result
02674 
02675         if css_class is not None:
02676             result = '%s class="%s"' % (result, css_class)
02677 
02678         for key in args.keys():
02679             value = args.get(key)
02680             result = '%s %s="%s"' % (result, key, value)
02681 
02682         return '%s />' % result
02683 
02684     security.declarePrivate('update_data')
02685     def update_data(self, data, content_type=None, size=None):
02686         """
02687             Update/upload image -> remove all copies
02688         """
02689         BaseImage.update_data(self, data, content_type, size)
02690         self._photos = OOBTree()
02691 
02692     def _resize(self, size, quality=100):
02693         """Resize and resample photo."""
02694         image = StringIO()
02695 
02696         width, height = size
02697 
02698         try:
02699             if HAS_PIL:
02700                 img = PIL.Image.open(StringIO(str(self.data)))
02701                 fmt = img.format
02702                 # Resize photo
02703                 img.thumbnail((width, height))
02704                 # Store copy in image buffer
02705                 img.save(image, fmt, quality=quality)
02706             else:
02707                 if sys.platform == 'win32':
02708                     from win32pipe import popen2
02709                     imgin, imgout = popen2(('convert -quality %s '
02710                                             '-geometry %sx%s - -'
02711                                             % (quality, width, height), 'b'))
02712                 else:
02713                     from popen2 import Popen3
02714                     convert=Popen3(('convert -quality %s '
02715                                     '-geometry %sx%s - -'
02716                                     % (quality, width, height)))
02717                     imgout=convert.fromchild
02718                     imgin=convert.tochild
02719 
02720                 imgin.write(str(self.data))
02721                 imgin.close()
02722                 image.write(imgout.read())
02723                 imgout.close()
02724 
02725                 # Wait for process to close if unix.
02726                 # Should check returnvalue for wait
02727                 if sys.platform !='win32':
02728                     convert.wait()
02729 
02730                 image.seek(0)
02731 
02732         except (ConflictError, KeyboardInterrupt):
02733             raise
02734         except Exception, e:
02735             log_exc('Error while resizing image')
02736 
02737         return image
02738 
02739     security.declareProtected(ChangeCacheSettingsPermission,
02740                               'ZCacheable_setManagerId')
02741     def ZCacheable_setManagerId(self, manager_id, REQUEST=None):
02742         '''Changes the manager_id for this object.
02743            overridden because we must propagate the change to all variants'''
02744         for size in self._photos.keys():
02745             variant = self.getPhoto(size).__of__(self)
02746             variant.ZCacheable_setManagerId(manager_id)
02747         inherited_attr = ScalableImage.inheritedAttribute('ZCacheable_setManagerId')
02748         return inherited_attr(self, manager_id, REQUEST)
02749 
02750 
02751 InitializeClass(ScalableImage)
02752 
02753 class PhotoField(ObjectField):
02754     """A photo field class."""
02755 
02756     _properties = Field._properties.copy()
02757     _properties.update({
02758         'type' : 'image',
02759         'default' : '',
02760         'default_content_type' : 'image/gif',
02761         'allowable_content_types' : ('image/gif','image/jpeg'),
02762         'displays': {
02763             'thumbnail': (128,128),
02764             'xsmall': (200,200),
02765             'small': (320,320),
02766             'medium': (480,480),
02767             'large': (768,768),
02768             'xlarge': (1024,1024)
02769             },
02770         'widget': ImageWidget,
02771         'storage': AttributeStorage(),
02772         })
02773 
02774     security  = ClassSecurityInfo()
02775 
02776     default_view = "view"
02777     
02778     def __init__(self, *args, **kwargs):
02779         deprecated('PhotoField is deprecated after work done on ImageField and '
02780                    'ATImage. It will be removed in Archetypes 1.5. If someone '
02781                    'like to keep the code please move it over to an own '
02782                    'Product in MoreFieldsAndWidgets repository.'
02783         )
02784         super(PhotoField, self).__init__(*args, **kwargs)
02785 
02786     security.declarePrivate('set')
02787     def set(self, instance, value, **kw):
02788         if isinstance(value, str):
02789             value = StringIO(value)
02790         image = ScalableImage(self.getName(), file=value,
02791                               displays=self.displays)
02792         ObjectField.set(self, instance, image, **kw)
02793 
02794     security.declarePrivate('validate_required')
02795     def validate_required(self, instance, value, errors):
02796         value = getattr(value, 'get_size', lambda: str(value))()
02797         return ObjectField.validate_required(self, instance, value, errors)
02798     
02799 # end of DEPRECATED PhotoField code    
02800 
02801 __all__ = ('Field', 'ObjectField', 'StringField',
02802            'FileField', 'TextField', 'DateTimeField', 'LinesField',
02803            'IntegerField', 'FloatField', 'FixedPointField',
02804            'ReferenceField', 'ComputedField', 'BooleanField',
02805            'CMFObjectField', 'ImageField', 'PhotoField',
02806            )
02807 
02808 
02809 registerField(StringField,
02810               title='String',
02811               description='Used for storing simple strings')
02812 
02813 registerField(FileField,
02814               title='File',
02815               description='Used for storing files')
02816 
02817 registerField(TextField,
02818               title='Text',
02819               description=('Used for storing text which can be '
02820                            'used in transformations'))
02821 
02822 registerField(DateTimeField,
02823               title='Date Time',
02824               description='Used for storing date/time')
02825 
02826 registerField(LinesField,
02827               title='LinesField',
02828               description=('Used for storing text which can be '
02829                            'used in transformations'))
02830 
02831 registerField(IntegerField,
02832               title='Integer',
02833               description='Used for storing integer values')
02834 
02835 registerField(FloatField,
02836               title='Float',
02837               description='Used for storing float values')
02838 
02839 registerField(FixedPointField,
02840               title='Fixed Point',
02841               description='Used for storing fixed point values')
02842 
02843 registerField(ReferenceField,
02844               title='Reference',
02845               description=('Used for storing references to '
02846                            'other Archetypes Objects'))
02847 
02848 registerField(ComputedField,
02849               title='Computed',
02850               description=('Read-only field, which value is '
02851                            'computed from a python expression'))
02852 
02853 registerField(BooleanField,
02854               title='Boolean',
02855               description='Used for storing boolean values')
02856 
02857 registerField(CMFObjectField,
02858               title='CMF Object',
02859               description=('Used for storing value inside '
02860                            'a CMF Object, which can have workflow. '
02861                            'Can only be used for BaseFolder-based '
02862                            'content objects'))
02863 
02864 registerField(ImageField,
02865               title='Image',
02866               description=('Used for storing images. '
02867                            'Images can then be retrieved in '
02868                            'different thumbnail sizes'))
02869 
02870 registerField(PhotoField,
02871               title='Photo',
02872               description=('Used for storing images. '
02873                            'Based on CMFPhoto. ')
02874              )
02875 
02876 registerPropertyType('required', 'boolean')
02877 registerPropertyType('default', 'string')
02878 registerPropertyType('default', 'integer', IntegerField)
02879 registerPropertyType('default', 'boolean', BooleanField)
02880 registerPropertyType('default', 'datetime', DateTimeField)
02881 registerPropertyType('vocabulary', 'string')
02882 registerPropertyType('enforceVocabulary', 'boolean')
02883 registerPropertyType('multiValued', 'boolean', LinesField)
02884 registerPropertyType('searchable', 'boolean')
02885 registerPropertyType('isMetadata', 'boolean')
02886 registerPropertyType('accessor', 'string')
02887 registerPropertyType('edit_accessor', 'string')
02888 registerPropertyType('mutator', 'string')
02889 registerPropertyType('mode', 'string')
02890 registerPropertyType('read_permission', 'string')
02891 registerPropertyType('write_permission', 'string')
02892 registerPropertyType('widget', 'widget')
02893 registerPropertyType('validators', 'validators')
02894 registerPropertyType('storage', 'storage')
02895 registerPropertyType('index', 'string')
02896 registerPropertyType('old_field_name', 'string')