Back to index

plone3  3.1.7
constraintypes.py
Go to the documentation of this file.
00001 #  ATContentTypes http://plone.org/products/atcontenttypes/
00002 #  Archetypes reimplementation of the CMF core types
00003 #  Copyright (c) 2003-2006 AT Content Types development team
00004 #
00005 # GNU General Public Licence (GPL)
00006 #
00007 # This program is free software; you can redistribute it and/or modify it under
00008 # the terms of the GNU General Public License as published by the Free Software
00009 # Foundation; either version 2 of the License, or (at your option) any later
00010 # version.
00011 # This program is distributed in the hope that it will be useful, but WITHOUT
00012 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
00013 # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
00014 # details.
00015 # You should have received a copy of the GNU General Public License along with
00016 # this program; if not, write to the Free Software Foundation, Inc., 59 Temple
00017 # Place, Suite 330, Boston, MA  02111-1307  USA
00018 #
00019 
00020 """This module contains a mixin-class and a schema snippet to constrain
00021 which types can be added in a folder-instance
00022 """
00023 __author__  = 'Jens Klein <jens.klein@jensquadrat.de>'
00024 __docformat__ = 'plaintext'
00025 
00026 from AccessControl import ClassSecurityInfo
00027 from AccessControl import Unauthorized
00028 from Globals import InitializeClass
00029 from Acquisition import aq_parent
00030 from Acquisition import aq_inner
00031 
00032 from Products.CMFCore.utils import getToolByName
00033 from Products.CMFCore.permissions import View
00034 from Products.CMFCore.permissions import ModifyPortalContent
00035 from Products.CMFCore.permissions import AddPortalContent
00036 from Products.CMFCore.PortalFolder import PortalFolderBase as PortalFolder
00037 
00038 from Products.Archetypes.atapi import Schema
00039 from Products.Archetypes.atapi import LinesField
00040 from Products.Archetypes.atapi import IntegerField
00041 from Products.Archetypes.atapi import MultiSelectionWidget
00042 from Products.Archetypes.atapi import SelectionWidget
00043 from Products.Archetypes.atapi import IntDisplayList
00044 from Products.Archetypes.atapi import DisplayList
00045 
00046 from Products.ATContentTypes import ATCTMessageFactory as _
00047 from Products.ATContentTypes import permission as ATCTPermissions
00048 
00049 from Products.ATContentTypes.interface import ISelectableConstrainTypes
00050 from Products.ATContentTypes.interfaces import ISelectableConstrainTypes as ZopeTwoISelectableConstrainTypes
00051 
00052 # constants for enableConstrainMixin
00053 ACQUIRE = -1 # acquire locallyAllowedTypes from parent (default)
00054 DISABLED = 0 # use default behavior of PortalFolder which uses the FTI information
00055 ENABLED  = 1 # allow types from locallyAllowedTypes only
00056 
00057 # Note: ACQUIRED means get allowable types from parent (regardless of
00058 #  whether it supports IConstrainTypes) but only if parent is the same
00059 #  portal_type (folder within folder). Otherwise, use the global_allow/default
00060 #  behaviour (same as DISABLED).
00061 
00062 enableDisplayList = IntDisplayList((
00063     (ACQUIRE,  _(u'constraintypes_acquire_label', default=u'Use parent folder settings')),
00064     (DISABLED, _(u'constraintypes_disable_label', default=u'Use portal default')),
00065     (ENABLED,  _(u'constraintypes_enable_label', default=u'Select manually')),
00066     ))
00067 
00068 ConstrainTypesMixinSchema = Schema((
00069     IntegerField('constrainTypesMode',
00070         required = False,
00071         default_method = "_ct_defaultConstrainTypesMode",
00072         vocabulary = enableDisplayList,
00073         languageIndependent = True,
00074         write_permission = ATCTPermissions.ModifyConstrainTypes,
00075         widget = SelectionWidget(
00076             label = _(u'label_contrain_types_mode',
00077                       default=u'Constrain types mode'),
00078             description = _(u'description_constrain_types_mode',
00079                             default=u'Select the constraint type mode for this folder.'),
00080             visible = {'view' : 'invisible',
00081                        'edit' : 'invisible',
00082                       },
00083             ),
00084         ),
00085 
00086     LinesField('locallyAllowedTypes',
00087         vocabulary = '_ct_vocabularyPossibleTypes',
00088         enforceVocabulary = False,
00089         languageIndependent = True,
00090         default_method = '_ct_defaultAddableTypeIds',
00091         accessor = 'getLocallyAllowedTypes', # Respects ENABLE/DISABLE/ACQUIRE
00092         write_permission = ATCTPermissions.ModifyConstrainTypes,
00093         multiValued = True,
00094         widget = MultiSelectionWidget(
00095             size = 10,
00096             label = _(u'label_constrain_allowed_types',
00097                       default=u'Permitted types'),
00098             description = _(u'description_constrain_allowed_types',
00099                             default=u'Select the types which will be addable inside this folder.'
00100                            ),
00101             visible = {'view' : 'invisible',
00102                        'edit' : 'invisible',
00103                       },
00104             ),
00105         ),
00106 
00107      LinesField('immediatelyAddableTypes',
00108         vocabulary = '_ct_vocabularyPossibleTypes',
00109         enforceVocabulary = False,
00110         languageIndependent = True,
00111         default_method = '_ct_defaultAddableTypeIds',
00112         accessor = 'getImmediatelyAddableTypes', # Respects ENABLE/DISABLE/ACQUIRE
00113         write_permission = ATCTPermissions.ModifyConstrainTypes,
00114         multiValued=True,
00115         widget = MultiSelectionWidget(
00116             size = 10,
00117             label = _(u'label_constrain_preferred_types', u'Preferred types'),
00118             description = _(u'description_constrain_preferred_types',
00119                             default=u'Select the types which will be addable '
00120                                      'from the "Add new item" menu. Any '
00121                                      'additional types set in the list above '
00122                                      'will be addable from a separate form.'),
00123             visible = {'view' : 'invisible',
00124                        'edit' : 'invisible',
00125                       },
00126             ),
00127         ),
00128     ))
00129 
00130 def parentPortalTypeEqual(obj):
00131     """Compares the portal type of obj to the portal type of its parent
00132     
00133     Return values:
00134         None - no acquisition context / parent available
00135         False - unequal
00136         True - equal
00137     """
00138     portal_factory = getToolByName(obj, 'portal_factory', None)
00139     if portal_factory is not None and portal_factory.isTemporary(obj):
00140         # created by portal_factory
00141         parent = aq_parent(aq_parent(aq_parent(aq_inner(obj))))
00142     else:
00143         parent = aq_parent(aq_inner(obj))
00144 
00145     if parent is None:
00146         return None # no context
00147     parent_type = getattr(parent.aq_explicit, 'portal_type', None)
00148     obj_type = getattr(obj.aq_explicit, 'portal_type')
00149     if obj_type and parent_type == obj_type:
00150         return True
00151     return False
00152 
00153 
00154 class ConstrainTypesMixin:
00155     """ Gives the user with given rights the possibility to
00156         constrain the addable types on a per-folder basis.
00157     """
00158 
00159     __implements__ = (ZopeTwoISelectableConstrainTypes, )
00160 
00161     security = ClassSecurityInfo()
00162 
00163     #
00164     # Sanity validator
00165     #
00166     security.declareProtected(ModifyPortalContent, 'validate_preferredTypes')
00167     def validate_preferredTypes(self, value):
00168         """Ensure that the preferred types is a subset of the allowed types.
00169         """
00170         allowed = self.getField('locallyAllowedTypes').get(self)
00171         preferred = value.split('\n')
00172 
00173         disallowed = []
00174         for p in preferred:
00175             if not p in allowed:
00176                 disallowed.append(p)
00177 
00178         if disallowed:
00179             return "The following types are not permitted: %s" % \
00180                         ','.join(disallowed)
00181 
00182     #
00183     # Overrides + supplements for CMF types machinery
00184     #
00185 
00186     security.declareProtected(View, 'getLocallyAllowedTypes')
00187     def getLocallyAllowedTypes(self, context=None):
00188         """If enableTypeRestrictions is ENABLE, return the list of types
00189         set. If it is ACQUIRE, get the types set on the parent so long
00190         as the parent is of the same type - if not, use the same behaviuor as
00191         DISABLE: return the types allowable in the item.
00192         """
00193         if context is None:
00194             context=self
00195         mode = self.getConstrainTypesMode()
00196 
00197         if mode == DISABLED:
00198             return [fti.getId() for fti in self.getDefaultAddableTypes(context)]
00199         elif mode == ENABLED:
00200             return self.getField('locallyAllowedTypes').get(self)
00201         elif mode == ACQUIRE:
00202             #if not parent or parent.portal_type != self.portal_type:
00203             if not parentPortalTypeEqual(self):
00204                 return [fti.getId() for fti in self.getDefaultAddableTypes(context)]
00205             else:
00206                 parent = aq_parent(aq_inner(self))
00207                 if ISelectableConstrainTypes.providedBy(parent):
00208                     return parent.getLocallyAllowedTypes(context)
00209                 else:
00210                     return parent.getLocallyAllowedTypes()
00211         else:
00212             raise ValueError, "Invalid value for enableAddRestriction"
00213 
00214 
00215     security.declareProtected(View, 'getImmediatelyAddableTypes')
00216     def getImmediatelyAddableTypes(self, context=None):
00217         """Get the list of type ids which should be immediately addable.
00218         If enableTypeRestrictions is ENABLE, return the list set; if it is
00219         ACQUIRE, use the value from the parent; if it is DISABLE, return
00220         all type ids allowable on the item.
00221         """
00222         if context is None:
00223             context=self
00224         mode = self.getConstrainTypesMode()
00225 
00226         if mode == DISABLED:
00227             return [fti.getId() for fti in \
00228                         self.getDefaultAddableTypes(context)]
00229         elif mode == ENABLED:
00230             return self.getField('immediatelyAddableTypes').get(self)
00231         elif mode == ACQUIRE:
00232             #if not parent or parent.portal_type != self.portal_type:
00233             if not parentPortalTypeEqual(self):
00234                 return [fti.getId() for fti in \
00235                         PortalFolder.allowedContentTypes(self)]
00236             else:
00237                 parent = aq_parent(aq_inner(self))
00238                 return parent.getImmediatelyAddableTypes(context)
00239         else:
00240             raise ValueError, "Invalid value for enableAddRestriction"
00241 
00242     # overrides CMFCore's PortalFolder allowedTypes
00243     def allowedContentTypes(self, context=None):
00244         """returns constrained allowed types as list of fti's
00245         """
00246         if context is None:
00247             context=self
00248         mode = self.getConstrainTypesMode()
00249 
00250         # Short circuit if we are disabled or acquiring from non-compatible
00251         # parent
00252 
00253         #if mode == DISABLED or \
00254         #        (parent and parent.portal_types != self.portal_types):
00255         if mode == DISABLED or \
00256                 (mode == ACQUIRE and not parentPortalTypeEqual(self) ):
00257             return PortalFolder.allowedContentTypes(self)
00258 
00259         globalTypes = self.getDefaultAddableTypes(context)
00260         allowed = list(self.getLocallyAllowedTypes())
00261         ftis = [ fti for fti in globalTypes if fti.getId() in allowed ]
00262 
00263         return ftis
00264 
00265 
00266     # overrides CMFCore's PortalFolder invokeFactory
00267     security.declareProtected(AddPortalContent, 'invokeFactory')
00268     def invokeFactory(self, type_name, id, RESPONSE=None, *args, **kw):
00269         """Invokes the portal_types tool
00270         """
00271         mode = self.getConstrainTypesMode()
00272 
00273         # Short circuit if we are disabled or acquiring from non-compatible
00274         # parent
00275 
00276         #if mode == DISABLED or \
00277         #        (parent and parent.portal_types != self.portal_types):
00278         if mode == DISABLED or \
00279                  (mode == ACQUIRE and not parentPortalTypeEqual(self) ):
00280             return PortalFolder.invokeFactory(self, type_name, id,
00281                                                 RESPONSE=None, *args, **kw)
00282 
00283         if not type_name in [fti.getId() for fti in self.allowedContentTypes()]:
00284             raise Unauthorized('Disallowed subobject type: %s' % type_name)
00285 
00286         pt = getToolByName( self, 'portal_types' )
00287         args = (type_name, self, id, RESPONSE) + args
00288         return pt.constructContent(*args, **kw)
00289 
00290 
00291     security.declareProtected(View, 'getDefaultAddableTypes')
00292     def getDefaultAddableTypes(self, context=None):
00293         """returns a list of normally allowed objects as ftis.
00294         Exactly like PortalFolder.allowedContentTypes except this
00295         will check in a specific context.
00296         """
00297         if context is None:
00298             context = self
00299 
00300         result = []
00301         portal_types = getToolByName(self, 'portal_types')
00302         myType = portal_types.getTypeInfo(self)
00303         if myType is not None:
00304             for contentType in portal_types.listTypeInfo(context):
00305                 if myType.allowType( contentType.getId() ):
00306                     result.append( contentType )
00307         else:
00308             result = portal_types.listTypeInfo()
00309 
00310         return [ t for t in result if t.isConstructionAllowed(context) ]
00311 
00312 
00313     security.declarePublic('canSetConstrainTypes')
00314     def canSetConstrainTypes(self):
00315         """Find out if the current user is allowed to set the allowable types
00316         """
00317         mtool = getToolByName(self, 'portal_membership')
00318         member = mtool.getAuthenticatedMember()
00319         return member.has_permission(ATCTPermissions.ModifyConstrainTypes, self)
00320 
00321     #
00322     # Helper methods
00323     #
00324 
00325     # Vocab for type lists
00326     security.declarePrivate('_ct_vocabularyPossibleTypes')
00327     def _ct_vocabularyPossibleTypes(self):
00328         """Get a DisplayList of types which may be added (id -> title)
00329         """
00330         typelist = [(fti.title_or_id(), fti.getId())
00331                      for fti in self.getDefaultAddableTypes()]
00332         typelist.sort()
00333         return DisplayList([(id, title) for title, id in typelist])
00334 
00335     # Default method for type lists
00336     security.declarePrivate('_ct_defaultAddableTypeIds')
00337     def _ct_defaultAddableTypeIds(self):
00338         """Get a list of types which are addable in the ordinary case w/o the
00339         constraint machinery.
00340         """
00341         return [fti.getId() for fti in self.getDefaultAddableTypes()]
00342 
00343     def _ct_defaultConstrainTypesMode(self):
00344        """Configure constrainTypeMode depending on the parent
00345 
00346        ACQUIRE if parent support ISelectableConstrainTypes
00347        DISABLE if not
00348        """
00349        portal_factory = getToolByName(self, 'portal_factory', None)
00350        if portal_factory is not None and portal_factory.isTemporary(self):
00351            # created by portal_factory
00352            parent = aq_parent(aq_parent(aq_parent(aq_inner(self))))
00353        else:
00354            parent = aq_parent(aq_inner(self))
00355 
00356        if ISelectableConstrainTypes.providedBy(parent) and parentPortalTypeEqual(self):
00357            return ACQUIRE
00358        else:
00359            return DISABLED
00360 
00361 InitializeClass(ConstrainTypesMixin)