Back to index

plone3  3.1.7
BaseObject.py
Go to the documentation of this file.
00001 import sys
00002 from Globals import InitializeClass
00003 
00004 from Products.Archetypes import PloneMessageFactory as _
00005 from Products.Archetypes.debug import log_exc
00006 from Products.Archetypes.utils import DisplayList
00007 from Products.Archetypes.utils import mapply
00008 from Products.Archetypes.utils import fixSchema
00009 from Products.Archetypes.utils import shasattr
00010 from Products.Archetypes.Field import StringField
00011 from Products.Archetypes.Field import TextField
00012 from Products.Archetypes.Renderer import renderer
00013 from Products.Archetypes.Schema import Schema
00014 from Products.Archetypes.Schema import getSchemata
00015 from Products.Archetypes.Widget import IdWidget
00016 from Products.Archetypes.Widget import StringWidget
00017 from Products.Archetypes.Marshall import RFC822Marshaller
00018 from Products.Archetypes.interfaces import IBaseObject
00019 from Products.Archetypes.interfaces import IReferenceable
00020 from Products.Archetypes.interfaces import ISchema
00021 from Products.Archetypes.interfaces.base import IBaseObject as z2IBaseObject
00022 from Products.Archetypes.interfaces.field import IFileField
00023 from Products.Archetypes.validator import AttributeValidator
00024 from Products.Archetypes.config import ATTRIBUTE_SECURITY
00025 from Products.Archetypes.config import RENAME_AFTER_CREATION_ATTEMPTS
00026 from Products.Archetypes.ArchetypeTool import getType
00027 from Products.Archetypes.ArchetypeTool import _guessPackage
00028 
00029 from Products.Archetypes.event import ObjectInitializedEvent
00030 from Products.Archetypes.event import ObjectEditedEvent
00031 
00032 from Products.Archetypes.interfaces import IMultiPageSchema
00033 from Products.Archetypes.interfaces import IObjectPreValidation
00034 from Products.Archetypes.interfaces import IObjectPostValidation
00035 
00036 from AccessControl import ClassSecurityInfo
00037 from AccessControl import Unauthorized
00038 from AccessControl.Permissions import copy_or_move as permission_copy_or_move
00039 from Acquisition import aq_base
00040 from Acquisition import aq_inner
00041 from Acquisition import aq_parent
00042 from Acquisition import ImplicitAcquisitionWrapper
00043 from Acquisition import ExplicitAcquisitionWrapper
00044 from Acquisition import Explicit
00045 
00046 from ComputedAttribute import ComputedAttribute
00047 from ZODB.POSException import ConflictError
00048 import transaction
00049 
00050 from Products.CMFCore import permissions
00051 from Products.CMFCore.utils import getToolByName
00052 
00053 from Referenceable import Referenceable
00054 
00055 from ZPublisher import xmlrpc
00056 from webdav.NullResource import NullResource
00057 
00058 from zope import event
00059 from zope.interface import implements, Interface
00060 from zope.component import subscribers
00061 from zope.component import queryMultiAdapter
00062 from zope.component import queryUtility
00063 
00064 # Import conditionally, so we don't introduce a hard depdendency
00065 try:
00066     from plone.i18n.normalizer.interfaces import IUserPreferredURLNormalizer
00067     from plone.i18n.normalizer.interfaces import IURLNormalizer
00068     URL_NORMALIZER = True
00069 except ImportError:
00070     URL_NORMALIZER = False
00071 
00072 try:
00073     from plone.locking.interfaces import ILockable
00074     HAS_LOCKING = True
00075 except ImportError:
00076     HAS_LOCKING = False
00077 
00078 _marker = []
00079 
00080 content_type = Schema((
00081 
00082     StringField(
00083         name='id',
00084         required=0, # Still actually required, but the widget will
00085                     # supply the missing value on non-submits
00086         mode='rw',
00087         permission=permission_copy_or_move,
00088         accessor='getId',
00089         mutator='setId',
00090         default=None,
00091         widget=IdWidget(
00092             label=_(u'label_short_name', default=u'Short Name'),
00093             description=_(u'help_shortname',
00094                           default=u'Should not contain spaces, underscores or mixed case. '
00095                                    'Short Name is part of the item\'s web address.'),
00096             visible={'view' : 'invisible'}
00097         ),
00098     ),
00099 
00100     StringField(
00101         name='title',
00102         required=1,
00103         searchable=1,
00104         default='',
00105         accessor='Title',
00106         widget=StringWidget(
00107             label_msgid='label_title',
00108             visible={'view' : 'invisible'},
00109             i18n_domain='plone',
00110         ),
00111     ),
00112 
00113     ), marshall = RFC822Marshaller())
00114 
00115 
00116 class BaseObject(Referenceable):
00117 
00118     security = ClassSecurityInfo()
00119 
00120     # Protect AttributeStorage-based attributes. See the docstring of
00121     # AttributeValidator for the low-down.
00122     if ATTRIBUTE_SECURITY:
00123         attr_security = AttributeValidator()
00124         security.setDefaultAccess(attr_security)
00125         # Delete so it cannot be accessed anymore.
00126         del attr_security
00127 
00128     schema = type = content_type
00129     _signature = None
00130 
00131     installMode = ['type', 'actions', 'indexes']
00132 
00133     _at_rename_after_creation = False # rename object according to title?
00134 
00135     __implements__ = (z2IBaseObject, ) + Referenceable.__implements__
00136     implements(IBaseObject, IReferenceable)
00137 
00138     def __init__(self, oid, **kwargs):
00139         self.id = oid
00140 
00141     security.declareProtected(permissions.ModifyPortalContent,
00142                               'initializeArchetype')
00143     def initializeArchetype(self, **kwargs):
00144         """Called by the generated add* factory in types tool.
00145         """
00146         try:
00147             self.initializeLayers()
00148             self.markCreationFlag()
00149             self.setDefaults()
00150             if kwargs:
00151                 kwargs['_initializing_'] = True
00152                 self.edit(**kwargs)
00153             self._signature = self.Schema().signature()
00154         except (ConflictError, KeyboardInterrupt):
00155             raise
00156         except:
00157             log_exc()
00158 
00159     security.declarePrivate('manage_afterAdd')
00160     def manage_afterAdd(self, item, container):
00161         __traceback_info__ = (self, item, container)
00162         Referenceable.manage_afterAdd(self, item, container)
00163         self.initializeLayers(item, container)
00164 
00165     security.declarePrivate('manage_afterClone')
00166     def manage_afterClone(self, item):
00167         __traceback_info__ = (self, item)
00168         Referenceable.manage_afterClone(self, item)
00169 
00170     security.declarePrivate('manage_beforeDelete')
00171     def manage_beforeDelete(self, item, container):
00172         __traceback_info__ = (self, item, container)
00173         self.cleanupLayers(item, container)
00174         Referenceable.manage_beforeDelete(self, item, container)
00175 
00176     security.declarePrivate('initializeLayers')
00177     def initializeLayers(self, item=None, container=None):
00178         self.Schema().initializeLayers(self, item, container)
00179 
00180     security.declarePrivate('cleanupLayers')
00181     def cleanupLayers(self, item=None, container=None):
00182         self.Schema().cleanupLayers(self, item, container)
00183 
00184     security.declareProtected(permissions.View, 'title_or_id')
00185     def title_or_id(self):
00186         """Returns the title if it is not blank and the id otherwise.
00187         """
00188         if shasattr(self, 'Title'):
00189             if callable(self.Title):
00190                 return self.Title() or self.getId()
00191 
00192         return self.getId()
00193 
00194     security.declareProtected(permissions.View, 'getId')
00195     def getId(self):
00196         """Gets the object id.
00197         """
00198         return self.id
00199 
00200     security.declareProtected(permissions.ModifyPortalContent, 'setId')
00201     def setId(self, value):
00202         """Sets the object id.
00203         """
00204         if value != self.getId():
00205             parent = aq_parent(aq_inner(self))
00206             if parent is not None:
00207                 # See Referenceable, keep refs on what is a move/rename
00208                 self._v_cp_refs = 1
00209                 # We can't rename if the object is locked
00210                 if HAS_LOCKING:
00211                     lockable = ILockable(self)
00212                     was_locked = False
00213                     if lockable.locked():
00214                         was_locked = True
00215                         lockable.unlock()
00216                     parent.manage_renameObject(self.id, value)
00217                     if was_locked:
00218                         lockable.lock()
00219                 else:
00220                     parent.manage_renameObject(self.id, value)
00221             self._setId(value)
00222 
00223     security.declareProtected(permissions.View, 'Type')
00224     def Type(self):
00225         """Dublin Core element - Object type.
00226 
00227         this method is redefined in ExtensibleMetadata but we need this
00228         at the object level (i.e. with or without metadata) to interact
00229         with the uid catalog.
00230         """
00231         if shasattr(self, 'getTypeInfo'):
00232             ti = self.getTypeInfo()
00233             if ti is not None:
00234                 return ti.Title()
00235         return self.meta_type
00236 
00237     security.declareProtected(permissions.View, 'getField')
00238     def getField(self, key, wrapped=False):
00239         """Returns a field object.
00240         """
00241         return self.Schema().get(key)
00242 
00243     security.declareProtected(permissions.View, 'getWrappedField')
00244     def getWrappedField(self, key):
00245         """Gets a field by id which is explicitly wrapped.
00246 
00247         XXX Maybe we should subclass field from Acquisition.Explicit?
00248         """
00249         return ExplicitAcquisitionWrapper(self.getField(key), self)
00250 
00251     security.declareProtected(permissions.View, 'getDefault')
00252     def getDefault(self, field):
00253         """Return the default value of a field.
00254         """
00255         field = self.getField(field)
00256         return field.getDefault(self)
00257 
00258     security.declareProtected(permissions.View, 'isBinary')
00259     def isBinary(self, key):
00260         """Return wether a field contains binary data.
00261         """
00262         field = self.getField(key)
00263         if IFileField.isImplementedBy(field):
00264             value = field.getBaseUnit(self)
00265             return value.isBinary()
00266         mimetype = self.getContentType(key)
00267         if mimetype and shasattr(mimetype, 'binary'):
00268             return mimetype.binary
00269         elif mimetype and mimetype.find('text') >= 0:
00270             return 0
00271         return 1
00272 
00273     security.declareProtected(permissions.View, 'isTransformable')
00274     def isTransformable(self, name):
00275         """Returns wether a field is transformable.
00276         """
00277         field = self.getField(name)
00278         return isinstance(field, TextField) or not self.isBinary(name)
00279 
00280     security.declareProtected(permissions.View, 'widget')
00281     def widget(self, field_name, mode="view", field=None, **kwargs):
00282         """Returns the rendered widget.
00283         """
00284         if field is None:
00285             field = self.Schema()[field_name]
00286         widget = field.widget
00287         return renderer.render(field_name, mode, widget, self, field=field,
00288                                **kwargs)
00289 
00290     security.declareProtected(permissions.View, 'getFilename')
00291     def getFilename(self, key=None):
00292         """Returns the filename from a field.
00293         """
00294         value = None
00295 
00296         if key is None:
00297             field = self.getPrimaryField()
00298         else:
00299             field = self.getField(key) or getattr(self, key, None)
00300 
00301         if field and shasattr(field, 'getFilename'):
00302             return field.getFilename(self)
00303 
00304         return value
00305 
00306     security.declareProtected(permissions.View, 'getContentType')
00307     def getContentType(self, key=None):
00308         """Returns the content type from a field.
00309         """
00310         value = 'text/plain'
00311 
00312         if key is None:
00313             field = self.getPrimaryField()
00314         else:
00315             field = self.getField(key) or getattr(self, key, None)
00316 
00317         if field and shasattr(field, 'getContentType'):
00318             return field.getContentType(self)
00319 
00320         return value
00321 
00322     # Backward compatibility
00323     # Note: ComputedAttribute should never be protected by a security
00324     # declaration! See http://dev.plone.org/archetypes/ticket/712
00325     content_type = ComputedAttribute(getContentType, 1)
00326 
00327     # XXX Where's get_content_type comes from??? There's no trace at both
00328     # Zope and CMF. It should be removed ASAP!
00329     security.declareProtected(permissions.View, 'get_content_type')
00330     get_content_type = getContentType
00331 
00332     security.declareProtected(permissions.ModifyPortalContent,
00333                               'setContentType')
00334     def setContentType(self, value, key=None):
00335         """Sets the content type of a field.
00336         """
00337         if key is None:
00338             field = self.getPrimaryField()
00339         else:
00340             field = self.getField(key) or getattr(self, key, None)
00341 
00342         if field and IFileField.isImplementedBy(field):
00343             field.setContentType(self, value)
00344 
00345     security.declareProtected(permissions.ModifyPortalContent, 'setFilename')
00346     def setFilename(self, value, key=None):
00347         """Sets the filename of a field.
00348         """
00349         if key is None:
00350             field = self.getPrimaryField()
00351         else:
00352             field = self.getField(key) or getattr(self, key, None)
00353 
00354         if field and IFileField.isImplementedBy(field):
00355             field.setFilename(self, value)
00356 
00357     security.declareProtected(permissions.View, 'getPrimaryField')
00358     def getPrimaryField(self):
00359         """The primary field is some object that responds to
00360         PUT/manage_FTPget events.
00361         """
00362         fields = self.Schema().filterFields(primary=1)
00363         if fields:
00364             return fields[0]
00365         return None
00366 
00367     security.declareProtected(permissions.View, 'get_portal_metadata')
00368     def get_portal_metadata(self, field):
00369         """Returns the portal_metadata for a field.
00370         """
00371         pmt = getToolByName(self, 'portal_metadata')
00372         policy = None
00373         try:
00374             schema = getattr(pmt, 'DCMI', None)
00375             spec = schema.getElementSpec(field.accessor)
00376             policy = spec.getPolicy(self.portal_type)
00377         except (ConflictError, KeyboardInterrupt):
00378             raise
00379         except:
00380             log_exc()
00381             return None, False
00382 
00383         if not policy:
00384             policy = spec.getPolicy(None)
00385 
00386         return DisplayList(map(lambda x: (x,x), policy.allowedVocabulary())), \
00387                policy.enforceVocabulary()
00388 
00389     security.declareProtected(permissions.View, 'Vocabulary')
00390     def Vocabulary(self, key):
00391         """Returns the vocabulary for a specified field.
00392         """
00393         vocab, enforce = None, 0
00394         field = self.getField(key)
00395         if field:
00396             if field.isMetadata:
00397                 vocab, enforce = self.get_portal_metadata(field)
00398 
00399             if vocab is None:
00400                 vocab, enforce = field.Vocabulary(self), \
00401                                  field.enforceVocabulary
00402         if vocab is None:
00403             vocab = DisplayList()
00404         return vocab, enforce
00405 
00406     def __getitem__(self, key):
00407         """Overloads the object's item access.
00408         """
00409         # Don't allow key access to hidden attributes
00410         if key.startswith('_'):
00411             raise Unauthorized, key
00412 
00413         schema = self.Schema()
00414         keys = schema.keys()
00415 
00416         if key not in keys and not key.startswith('_'):
00417             # XXX Fix this in AT 1.4
00418             value= getattr(aq_inner(self).aq_explicit, key, _marker) or \
00419                    getattr(aq_parent(aq_inner(self)).aq_explicit, key, _marker)
00420             if value is _marker:
00421                 raise KeyError, key
00422             else:
00423                 return value
00424 
00425         field = schema[key]
00426         accessor = field.getEditAccessor(self)
00427         if not accessor:
00428             accessor = field.getAccessor(self)
00429 
00430         # This is the access mode used by external editor. We need the
00431         # handling provided by BaseUnit when its available
00432         kw = {'raw':1, 'field': field.__name__}
00433         value = mapply(accessor, **kw)
00434         return value
00435 
00436     security.declarePrivate('setDefaults')
00437     def setDefaults(self):
00438         """Sets the field values to the default values.
00439         """
00440         self.Schema().setDefaults(self)
00441 
00442     security.declareProtected(permissions.ModifyPortalContent, 'update')
00443     def update(self, **kwargs):
00444         """Changes the values of the field and reindex the object.
00445         """
00446         initializing = kwargs.get('_initializing_', False)
00447         if initializing:
00448             del kwargs['_initializing_']
00449         self.Schema().updateAll(self, **kwargs)
00450         self._p_changed = 1
00451         if not initializing:
00452             # Avoid double indexing during initialization.
00453             self.reindexObject()
00454 
00455     security.declareProtected(permissions.ModifyPortalContent, 'edit')
00456     edit = update
00457 
00458     security.declareProtected(permissions.View,
00459                               'validate_field')
00460     def validate_field(self, name, value, errors):
00461         """Field's validate hook.
00462 
00463         Write a method: validate_foo(new_value) -> "error" or None
00464         If there is a validate method defined for a given field invoke
00465         it by name
00466         name -- the name to register errors under
00467         value -- the proposed new value
00468         errors -- dict to record errors in
00469         """
00470         methodName = "validate_%s" % name
00471         result = None
00472         if shasattr(self, methodName):
00473             method = getattr(self, methodName)
00474             result = method(value)
00475             if result is not None:
00476                 errors[name] = result
00477         return result
00478 
00479     ## Pre/post validate hooks that will need to write errors
00480     ## into the errors dict directly using errors[fieldname] = ""
00481     security.declareProtected(permissions.View, 'pre_validate')
00482     def pre_validate(self, REQUEST=None, errors=None):
00483         pass
00484 
00485     security.declareProtected(permissions.View, 'post_validate')
00486     def post_validate(self, REQUEST=None, errors=None):
00487         pass
00488 
00489     security.declareProtected(permissions.View, 'validate')
00490     def validate(self, REQUEST=None, errors=None, data=None, metadata=None):
00491         """Validates the form data from the request.
00492         """
00493         if errors is None:
00494             errors = {}
00495 
00496         self.pre_validate(REQUEST, errors)
00497 
00498         for pre_validator in subscribers((self,), IObjectPreValidation):
00499             pre_errors = pre_validator(REQUEST)
00500             if pre_errors is not None:
00501                 for field_name, error_message in pre_errors.items():
00502                     if field_name in errors:
00503                         errors[field_name] += " %s" % error_message
00504                     else:
00505                         errors[field_name] = error_message
00506 
00507         if errors:
00508             return errors
00509         self.Schema().validate(instance=self, REQUEST=REQUEST,
00510                                errors=errors, data=data, metadata=metadata)
00511 
00512         self.post_validate(REQUEST, errors)
00513 
00514         for post_validator in subscribers((self,), IObjectPostValidation):
00515             post_errors = post_validator(REQUEST)
00516             if post_errors is not None:
00517                 for field_name, error_message in post_errors.items():
00518                     if field_name in errors:
00519                         errors[field_name] += " %s" % error_message
00520                     else:
00521                         errors[field_name] = error_message
00522 
00523         return errors
00524 
00525     security.declareProtected(permissions.View, 'SearchableText')
00526     def SearchableText(self):
00527         """All fields marked as 'searchable' are concatenated together
00528         here for indexing purpose.
00529         """
00530         data = []
00531         charset = self.getCharset()
00532         for field in self.Schema().fields():
00533             if not field.searchable:
00534                 continue
00535             method = field.getIndexAccessor(self)
00536             try:
00537                 datum =  method(mimetype="text/plain")
00538             except TypeError:
00539                 # Retry in case typeerror was raised because accessor doesn't
00540                 # handle the mimetype argument
00541                 try:
00542                     datum =  method()
00543                 except (ConflictError, KeyboardInterrupt):
00544                     raise
00545                 except:
00546                     continue
00547             if datum:
00548                 type_datum = type(datum)
00549                 vocab = field.Vocabulary(self)
00550                 if isinstance(datum, list) or isinstance(datum, tuple):
00551                     # Unmangle vocabulary: we index key AND value
00552                     vocab_values = map(lambda value, vocab=vocab: vocab.getValue(value, ''), datum)
00553                     datum = list(datum)
00554                     datum.extend(vocab_values)
00555                     datum = ' '.join(datum)
00556                 elif isinstance(datum, basestring):
00557                     if isinstance(datum, unicode):
00558                         datum = datum.encode(charset)
00559                     value = vocab.getValue(datum, '')
00560                     if isinstance(value, unicode):
00561                         value = value.encode(charset)
00562                     datum = "%s %s" % (datum, value, )
00563 
00564                 if isinstance(datum, unicode):
00565                     datum = datum.encode(charset)
00566                 data.append(str(datum))
00567 
00568         data = ' '.join(data)
00569         return data
00570 
00571     security.declareProtected(permissions.View, 'getCharset')
00572     def getCharset(self):
00573         """Returns the site default charset, or utf-8.
00574         """
00575         properties = getToolByName(self, 'portal_properties', None)
00576         if properties is not None:
00577             site_properties = getattr(properties, 'site_properties', None)
00578             if site_properties is not None:
00579                 return site_properties.getProperty('default_charset')
00580         return 'utf-8'
00581 
00582     security.declareProtected(permissions.View, 'get_size')
00583     def get_size(self):
00584         """Used for FTP and apparently the ZMI now too.
00585         """
00586         size = 0
00587         for field in self.Schema().fields():
00588             size+=field.get_size(self)
00589         return size
00590 
00591     security.declarePrivate('_processForm')
00592     def _processForm(self, data=1, metadata=None, REQUEST=None, values=None):
00593         request = REQUEST or self.REQUEST
00594         if values:
00595             form = values
00596         else:
00597             form = request.form
00598         fieldset = form.get('fieldset', None)
00599         schema = self.Schema()
00600         schemata = self.Schemata()
00601         fields = []
00602 
00603         if not IMultiPageSchema.providedBy(self):
00604             fields = schema.fields()
00605         elif fieldset is not None:
00606             fields = schemata[fieldset].fields()
00607         else:
00608             if data: fields += schema.filterFields(isMetadata=0)
00609             if metadata: fields += schema.filterFields(isMetadata=1)
00610 
00611         form_keys = form.keys()
00612 
00613         for field in fields:
00614             ## Delegate to the widget for processing of the form
00615             ## element.  This means that if the widget needs _n_
00616             ## fields under a naming convention it can handle this
00617             ## internally.  The calling API is process_form(instance,
00618             ## field, form) where instance should rarely be needed,
00619             ## field is the field object and form is the dict. of
00620             ## kv_pairs from the REQUEST
00621             ##
00622             ## The product of the widgets processing should be:
00623             ##   (value, **kwargs) which will be passed to the mutator
00624             ##   or None which will simply pass
00625 
00626             if not field.writeable(self):
00627                 # If the field has no 'w' in mode, or the user doesn't
00628                 # have the required permission, or the mutator doesn't
00629                 # exist just bail out.
00630                 continue
00631 
00632             try:
00633                 # Pass validating=False to inform the widget that we
00634                 # aren't in the validation phase, IOW, the returned
00635                 # data will be forwarded to the storage
00636                 result = field.widget.process_form(self, field, form,
00637                                                    empty_marker=_marker,
00638                                                    validating=False)
00639             except TypeError:
00640                 # Support for old-style process_form methods
00641                 result = field.widget.process_form(self, field, form,
00642                                                    empty_marker=_marker)
00643 
00644             if result is _marker or result is None:
00645                 continue
00646 
00647             # Set things by calling the mutator
00648             mutator = field.getMutator(self)
00649             __traceback_info__ = (self, field, mutator)
00650             result[1]['field'] = field.__name__
00651             mapply(mutator, result[0], **result[1])
00652 
00653         self.reindexObject()
00654 
00655     security.declareProtected(permissions.ModifyPortalContent, 'processForm')
00656     def processForm(self, data=1, metadata=0, REQUEST=None, values=None):
00657         """Processes the schema looking for data in the form.
00658         """
00659         is_new_object = self.checkCreationFlag()
00660         self._processForm(data=data, metadata=metadata,
00661                           REQUEST=REQUEST, values=values)
00662         self.unmarkCreationFlag()
00663         if self._at_rename_after_creation and is_new_object:
00664             self._renameAfterCreation(check_auto_id=True)
00665 
00666         # Post create/edit hooks
00667         if is_new_object:
00668             event.notify(ObjectInitializedEvent(self))
00669             self.at_post_create_script()
00670         else:
00671             event.notify(ObjectEditedEvent(self))
00672             self.at_post_edit_script()
00673 
00674     # This method is only called once after object creation.
00675     security.declarePrivate('at_post_create_script')
00676     def at_post_create_script(self):
00677         pass
00678 
00679     # This method is called after every subsequent edit
00680     security.declarePrivate('at_post_edit_script')
00681     def at_post_edit_script(self):
00682         pass
00683 
00684     security.declareProtected(permissions.ModifyPortalContent,
00685                               'markCreationFlag')
00686     def markCreationFlag(self):
00687         """Sets flag on the instance to indicate that the object hasn't been
00688         saved properly (unset in content_edit).
00689 
00690         This will only be done if a REQUEST is present to ensure that objects
00691         created programmatically are considered fully created.
00692         """
00693         req = getattr(self, 'REQUEST', None)
00694         if shasattr(req, 'get'):
00695             if req.get('SCHEMA_UPDATE', None) is not None:
00696                 return
00697             meth = req.get('REQUEST_METHOD', None)
00698             # Ensure that we have an HTTP request, if you're creating an
00699             # object with something other than a GET or POST, then we assume
00700             # you are making a complete object.
00701             if meth in ('GET', 'POST'):
00702                 self._at_creation_flag = True
00703 
00704     security.declareProtected(permissions.ModifyPortalContent,
00705                               'unmarkCreationFlag')
00706     def unmarkCreationFlag(self):
00707         """Removes the creation flag.
00708         """
00709         if shasattr(aq_inner(self), '_at_creation_flag'):
00710             self._at_creation_flag = False
00711 
00712     security.declareProtected(permissions.ModifyPortalContent,
00713                               'checkCreationFlag')
00714     def checkCreationFlag(self):
00715         """Returns True if the object has been fully saved, False otherwise.
00716         """
00717         return getattr(aq_base(self), '_at_creation_flag', False)
00718 
00719     def generateNewId(self):
00720         """Suggest an id for this object.
00721         This id is used when automatically renaming an object after creation.
00722         """
00723         title = self.Title()
00724         # Can't work w/o a title
00725         if not title:
00726             return None
00727 
00728         # Don't do anything without the plone.i18n package
00729         if not URL_NORMALIZER:
00730             return None
00731 
00732         if not isinstance(title, unicode):
00733             charset = self.getCharset()
00734             title = unicode(title, charset)
00735 
00736         request = getattr(self, 'REQUEST', None)
00737         if request is not None:
00738             return IUserPreferredURLNormalizer(request).normalize(title)
00739 
00740         return queryUtility(IURLNormalizer).normalize(title)
00741 
00742     security.declarePrivate('_renameAfterCreation')
00743     def _renameAfterCreation(self, check_auto_id=False):
00744         """Renames an object like its normalized title.
00745         """
00746         old_id = self.getId()
00747         if check_auto_id and not self._isIDAutoGenerated(old_id):
00748             # No auto generated id
00749             return False
00750 
00751         new_id = self.generateNewId()
00752         if new_id is None:
00753             return False
00754 
00755         invalid_id = True
00756         check_id = getattr(self, 'check_id', None)
00757         if check_id is not None:
00758             invalid_id = check_id(new_id, required=1)
00759 
00760         # If check_id told us no, or if it was not found, make sure we have an
00761         # id unique in the parent folder.
00762         if invalid_id:
00763             unique_id = self._findUniqueId(new_id)
00764             if unique_id is not None:
00765                 if check_id is None or check_id(new_id, required=1):
00766                     new_id = unique_id
00767                     invalid_id = False
00768 
00769         if not invalid_id:
00770             # Can't rename without a subtransaction commit when using
00771             # portal_factory!
00772             transaction.savepoint(optimistic=True)
00773             self.setId(new_id)
00774             return new_id
00775 
00776         return False
00777 
00778     security.declarePrivate('_findUniqueId')
00779     def _findUniqueId(self, id):
00780         """Find a unique id in the parent folder, based on the given id, by
00781         appending -n, where n is a number between 1 and the constant
00782         RENAME_AFTER_CREATION_ATTEMPTS, set in config.py. If no id can be
00783         found, return None.
00784         """
00785         check_id = getattr(self, 'check_id', None)
00786         if check_id is None:
00787             parent = aq_parent(aq_inner(self))
00788             parent_ids = parent.objectIds()
00789             check_id = lambda id, required: id in parent_ids
00790 
00791         invalid_id = check_id(id, required=1)
00792         if not invalid_id:
00793             return id
00794 
00795         idx = 1
00796         while idx <= RENAME_AFTER_CREATION_ATTEMPTS:
00797             new_id = "%s-%d" % (id, idx)
00798             if not check_id(new_id, required=1):
00799                 return new_id
00800             idx += 1
00801 
00802         return None
00803 
00804     security.declarePrivate('_isIDAutoGenerated')
00805     def _isIDAutoGenerated(self, id):
00806         """Avoid busting setDefaults if we don't have a proper acquisition
00807         context.
00808         """
00809         plone_tool = getToolByName(self, 'plone_utils', None)
00810         if plone_tool is not None and \
00811            shasattr(plone_tool, 'isIDAutoGenerated'):
00812             return plone_tool.isIDAutoGenerated(id)
00813         # Plone 2.0 compatibility
00814         script = getattr(self, 'isIDAutoGenerated', None)
00815         if script is not None:
00816             return script(id)
00817         return False
00818 
00819     security.declareProtected(permissions.View, 'Schemata')
00820     def Schemata(self):
00821         """Returns the Schemata for the Object.
00822         """
00823         return getSchemata(self)
00824 
00825     def Schema(self):
00826         """Return a (wrapped) schema instance for this object instance.
00827         """
00828         schema = ISchema(self)
00829         return ImplicitAcquisitionWrapper(schema, self)
00830 
00831     security.declarePrivate('_isSchemaCurrent')
00832     def _isSchemaCurrent(self):
00833         """Determines whether the current object's schema is up to date.
00834         """
00835         return self._signature == self.Schema().signature()
00836 
00837     security.declarePrivate('_updateSchema')
00838     def _updateSchema(self, excluded_fields=[], out=None,
00839                       remove_instance_schemas=False):
00840         """Updates an object's schema when the class schema changes.
00841 
00842         For each field we use the existing accessor to get its value,
00843         then we re-initialize the class, then use the new schema
00844         mutator for each field to set the values again.
00845 
00846         We also copy over any class methods to handle product
00847         refreshes gracefully (when a product refreshes, you end up
00848         with both the old version of the class and the new in memory
00849         at the same time -- you really should restart zope after doing
00850         a schema update).
00851         """
00852         if out is not None:
00853             print >> out, 'Updating %s' % (self.getId())
00854 
00855         if remove_instance_schemas and 'schema' in self.__dict__:
00856             if out is not None:
00857                 print >> out, 'Removing schema from instance dict.'
00858             del self.schema
00859         new_schema = self.Schema()
00860 
00861         # Read all the old values into a dict
00862         values = {}
00863         mimes = {}
00864         for f in new_schema.fields():
00865             name = f.getName()
00866             if name in excluded_fields: continue
00867             if f.type == "reference": continue
00868             try:
00869                 values[name] = self._migrateGetValue(name, new_schema)
00870             except ValueError:
00871                 if out is not None:
00872                     print >> out, ('Unable to get %s.%s'
00873                                    % (str(self.getId()), name))
00874             else:
00875                 if shasattr(f, 'getContentType'):
00876                     mimes[name] = f.getContentType(self)
00877 
00878         obj_class = self.__class__
00879         current_class = getattr(sys.modules[self.__module__],
00880                                 self.__class__.__name__)
00881         if obj_class.schema != current_class.schema:
00882             # XXX This is kind of brutish.  We do this to make sure that old
00883             # class instances have the proper methods after a refresh.  The
00884             # best thing to do is to restart Zope after doing an update, and
00885             # the old versions of the class will disappear.
00886 
00887             for k in current_class.__dict__.keys():
00888                 obj_class.__dict__[k] = current_class.__dict__[k]
00889 
00890         # Set a request variable to avoid resetting the newly created flag
00891         req = getattr(self, 'REQUEST', None)
00892         if req is not None:
00893             req.set('SCHEMA_UPDATE','1')
00894         self.initializeArchetype()
00895 
00896         for f in new_schema.fields():
00897             name = f.getName()
00898             kw = {}
00899             if name not in excluded_fields and values.has_key(name):
00900                 if mimes.has_key(name):
00901                     kw['mimetype'] = mimes[name]
00902                 try:
00903                     self._migrateSetValue(name, values[name], **kw)
00904                 except ValueError:
00905                     if out is not None:
00906                         print >> out, ('Unable to set %s.%s to '
00907                                        '%s' % (str(self.getId()),
00908                                                name, str(values[name])))
00909         # Make sure the changes are persisted
00910         self._p_changed = 1
00911         if out is not None:
00912             return out
00913 
00914     security.declarePrivate('_migrateGetValue')
00915     def _migrateGetValue(self, name, new_schema=None):
00916         """Try to get a value from an object using a variety of methods."""
00917         schema = self.Schema()
00918         # Migrate pre-AT 1.3 schemas.
00919         schema = fixSchema(schema)
00920         # First see if the new field name is managed by the current schema
00921         field = schema.get(getattr(new_schema.get(name,None),'old_field_name',name), None)
00922         if field:
00923             # At very first try to use the BaseUnit itself
00924             try:
00925                 if IFileField.isImplementedBy(field):
00926                     return field.getBaseUnit(self)
00927 
00928             except (ConflictError, KeyboardInterrupt):
00929                 raise
00930             except:
00931                 pass
00932 
00933             # First try the edit accessor
00934             try:
00935                 editAccessor = field.getEditAccessor(self)
00936                 if editAccessor:
00937                     return editAccessor()
00938 
00939             except (ConflictError, KeyboardInterrupt):
00940                 raise
00941             except:
00942                 pass
00943 
00944             # No luck -- now try the accessor
00945             try:
00946                 accessor = field.getAccessor(self)
00947                 if accessor:
00948                     return accessor()
00949 
00950             except (ConflictError, KeyboardInterrupt):
00951                 raise
00952             except:
00953                 pass
00954             # No luck use standard method to get the value
00955             return field.get(self)
00956 
00957             # Still no luck -- try to get the value directly
00958             # this part should be remove because for some fields this will fail
00959             # if you get the value directly for example for FixPointField
00960             # stored value is (0,0) but the input value is a string.
00961             # at this time FixPointField fails if he got a tuple as input value
00962             # Because of this line value = value.replace(',','.')
00963             try:
00964                 return self[field.getName()]
00965 
00966             except (ConflictError, KeyboardInterrupt):
00967                 raise
00968             except:
00969                 pass
00970 
00971         # Nope -- see if the new accessor method is present
00972         # in the current object.
00973         if new_schema:
00974             new_field = new_schema.get(name)
00975             # Try the new edit accessor
00976             try:
00977                 editAccessor = new_field.getEditAccessor(self)
00978                 if editAccessor:
00979                     return editAccessor()
00980 
00981             except (ConflictError, KeyboardInterrupt):
00982                 raise
00983             except:
00984                 pass
00985 
00986             # Nope -- now try the accessor
00987             try:
00988                 accessor = new_field.getAccessor(self)
00989                 if accessor:
00990                     return accessor()
00991 
00992             except (ConflictError, KeyboardInterrupt):
00993                 raise
00994             except:
00995                 pass
00996 
00997             # Still no luck -- try to get the value directly using the new name
00998             try:
00999                 return self[new_field.getName()]
01000 
01001             except (ConflictError, KeyboardInterrupt):
01002                 raise
01003             except:
01004                 pass
01005 
01006         # Nope -- now see if the current object has an attribute
01007         # with the same name
01008         # as the new field
01009         if shasattr(self, name):
01010             return getattr(self, name)
01011 
01012         raise ValueError, 'name = %s' % (name)
01013 
01014     security.declarePrivate('_migrateSetValue')
01015     def _migrateSetValue(self, name, value, old_schema=None, **kw):
01016         """Try to set an object value using a variety of methods."""
01017         schema = self.Schema()
01018         # Migrate pre-AT 1.3 schemas.
01019         schema = fixSchema(schema)
01020         field = schema.get(name, None)
01021         # Try using the field's mutator
01022         if field:
01023             mutator = field.getMutator(self)
01024             if mutator is not None:
01025                 try:
01026                     args = [value,]
01027                     mapply(mutator, *args, **kw)
01028                     return
01029 
01030                 except (ConflictError, KeyboardInterrupt):
01031                     raise
01032                 except:
01033                     log_exc()
01034         else:
01035             # Try setting an existing attribute
01036             if shasattr(self, name):
01037                 setattr(self, name, value)
01038                 return
01039         raise ValueError, 'name = %s, value = %s' % (name, value)
01040 
01041     security.declareProtected(permissions.View, 'isTemporary')
01042     def isTemporary(self):
01043         """Checks to see if we are created as temporary object by
01044         portal factory.
01045         """
01046         parent = aq_parent(aq_inner(self))
01047         return shasattr(parent, 'meta_type') and \
01048                parent.meta_type == 'TempFolder'
01049 
01050     security.declareProtected(permissions.View, 'getFolderWhenPortalFactory')
01051     def getFolderWhenPortalFactory(self):
01052         """Returns the folder where this object was created temporarily.
01053         """
01054         ctx = aq_inner(self)
01055         if not ctx.isTemporary():
01056             # Not a temporary object!
01057             return aq_parent(ctx)
01058         utool = getToolByName(self, 'portal_url')
01059         portal_object = utool.getPortalObject()
01060 
01061         while ctx.getId() != 'portal_factory':
01062             # Find the portal factory object
01063             if ctx == portal_object:
01064                 # uups, shouldn't happen!
01065                 return ctx
01066             ctx = aq_parent(ctx)
01067         # ctx is now the portal_factory in our parent folder
01068         return aq_parent(ctx)
01069 
01070     #
01071     # Subobject Access
01072     #
01073     # Some temporary objects could be set by fields (for instance
01074     # additional images that may result from the transformation of
01075     # a PDF field to html).
01076     #
01077     # Those objects are specific to a session.
01078     #
01079 
01080     security.declareProtected(permissions.ModifyPortalContent,
01081                               'addSubObjects')
01082     def addSubObjects(self, objects, REQUEST=None):
01083         """Adds a dictionary of objects to a volatile attribute.
01084         """
01085         if objects:
01086             storage = getattr(aq_base(self), '_v_at_subobjects', None)
01087             if storage is None:
01088                 setattr(self, '_v_at_subobjects', {})
01089                 storage = getattr(aq_base(self), '_v_at_subobjects')
01090             for name, obj in objects.items():
01091                 storage[name] = aq_base(obj)
01092 
01093     security.declareProtected(permissions.View, 'getSubObject')
01094     def getSubObject(self, name, REQUEST, RESPONSE=None):
01095         """Gets a dictionary of objects from a volatile attribute.
01096         """
01097         storage = getattr(aq_base(self), '_v_at_subobjects', None)
01098         if storage is None:
01099             return None
01100 
01101         data = storage.get(name, None)
01102         if data is None:
01103             return None
01104 
01105         mtr = self.mimetypes_registry
01106         mt = mtr.classify(data, filename=name)
01107         return Wrapper(data, name, str(mt) or 'application/octet-stream').__of__(self)
01108 
01109     def __bobo_traverse__(self, REQUEST, name):
01110         """Allows transparent access to session subobjects.
01111         """
01112         # sometimes, the request doesn't have a response, e.g. when
01113         # PageTemplates traverse through the object path, they pass in
01114         # a phony request (a dict).
01115         RESPONSE = getattr(REQUEST, 'RESPONSE', None)
01116 
01117         # Is it a registered sub object
01118         data = self.getSubObject(name, REQUEST, RESPONSE)
01119         if data is not None:
01120             return data
01121         # Or a standard attribute (maybe acquired...)
01122         target = None
01123         method = REQUEST.get('REQUEST_METHOD', 'GET').upper()
01124         # Logic from "ZPublisher.BaseRequest.BaseRequest.traverse"
01125         # to check whether this is a browser request
01126         if (len(REQUEST.get('TraversalRequestNameStack', ())) == 0 and
01127             not (method in ('GET', 'HEAD', 'POST') and not
01128                  isinstance(RESPONSE, xmlrpc.Response))):
01129             if shasattr(self, name):
01130                 target = getattr(self, name)
01131         else:
01132             if shasattr(self, name): # attributes of self come first
01133                 target = getattr(self, name)
01134             else: # then views
01135                 target = queryMultiAdapter((self, REQUEST), Interface, name)
01136                 if target is not None:
01137                     # We don't return the view, we raise an
01138                     # AttributeError instead (below)
01139                     target = None
01140                 else: # then acquired attributes
01141                     target = getattr(self, name, None)
01142 
01143         if target is not None:
01144             return target
01145         elif (method not in ('GET', 'POST') and not
01146               isinstance(RESPONSE, xmlrpc.Response) and
01147               REQUEST.maybe_webdav_client):
01148             return NullResource(self, name, REQUEST).__of__(self)
01149         else:
01150             # Raising AttributeError will look up views for us
01151             raise AttributeError(name)
01152 
01153 InitializeClass(BaseObject)
01154 
01155 class Wrapper(Explicit):
01156     """Wrapper object for access to sub objects."""
01157     __allow_access_to_unprotected_subobjects__ = 1
01158 
01159     def __init__(self, data, filename, mimetype):
01160         self._data = data
01161         self._filename = filename
01162         self._mimetype = mimetype
01163 
01164     def __call__(self, REQUEST=None, RESPONSE=None):
01165         if RESPONSE is None:
01166             RESPONSE = REQUEST.RESPONSE
01167         if RESPONSE is not None:
01168             mt = self._mimetype
01169             name = self._filename
01170             RESPONSE.setHeader('Content-type', str(mt))
01171             RESPONSE.setHeader('Content-Disposition',
01172                                'inline;filename=%s' % name)
01173             RESPONSE.setHeader('Content-Length', len(self._data))
01174         return self._data
01175 
01176 MinimalSchema = BaseObject.schema
01177 
01178 __all__ = ('BaseObject', 'MinimalSchema')