Back to index

plone3  3.1.7
fti.py
Go to the documentation of this file.
00001 # -*- coding: utf-8 -*-
00002 # $Id: fti.py 48191 2007-08-29 16:08:43Z glenfant $
00003 
00004 from zope.interface import implements
00005 
00006 from AccessControl import ClassSecurityInfo
00007 from Globals import InitializeClass
00008 from Globals import DTMLFile
00009 from Acquisition import aq_base
00010 from types import ClassType
00011 
00012 from Products.CMFCore.TypesTool import FactoryTypeInformation
00013 from Products.CMFCore.TypesTool import TypesTool
00014 from Products.CMFCore.permissions import View
00015 from Products.CMFCore.permissions import ManagePortal
00016 from Products.CMFCore.utils import _dtmldir
00017 from Products.CMFCore.utils import getToolByName
00018 
00019 from Products.CMFDynamicViewFTI.interface import IDynamicViewTypeInformation
00020 from Products.CMFDynamicViewFTI.interfaces import IDynamicViewTypeInformation as ZopeTwoIDynamicViewTypeInformation
00021 
00022 
00023 def safe_hasattr(obj, name, _marker=object()):
00024     """Make sure we don't mask exceptions like hasattr().
00025 
00026     We don't want exceptions other than AttributeError to be masked,
00027     since that too often masks other programming errors.
00028     Three-argument getattr() doesn't mask those, so we use that to
00029     implement our own hasattr() replacement.
00030     """
00031     return getattr(obj, name, _marker) is not _marker
00032 
00033 
00034 def safe_callable(obj):
00035     """Make sure our callable checks are ConflictError safe."""
00036     if safe_hasattr(obj, '__class__'):
00037         if safe_hasattr(obj, '__call__'):
00038             return True
00039         else:
00040             return isinstance(obj, ClassType)
00041     else:
00042         return callable(obj)
00043 
00044 
00045 def om_has_key(context, key):
00046     """Object Manager has_key method with optimization for btree folders
00047 
00048     Zope's OFS.ObjectManager has no method for checking if an object with an id
00049     exists inside a folder.
00050     """
00051     klass = getattr(aq_base(context), '__class__', None)
00052     if hasattr(klass, 'has_key'):
00053         # BTreeFolder2 optimization
00054         if context.has_key(key):
00055             return True
00056     else:
00057         # standard ObjectManager api
00058         if key in context.objectIds():
00059             return True
00060     return False
00061 
00062 fti_meta_type = 'Factory-based Type Information with dynamic views'
00063 
00064 class DynamicViewTypeInformation(FactoryTypeInformation):
00065     """FTI with dynamic views
00066 
00067     A value of (dynamic view) as alias is replaced by the output of defaultView()
00068     """
00069 
00070     implements(IDynamicViewTypeInformation)
00071     __implements__ = (ZopeTwoIDynamicViewTypeInformation,)
00072 
00073     meta_type = fti_meta_type
00074     security = ClassSecurityInfo()
00075 
00076     _properties = FactoryTypeInformation._properties + (
00077         { 'id': 'default_view', 'type': 'string', 'mode': 'w',
00078           'label': 'Default view method'
00079         },
00080         { 'id': 'view_methods', 'type': 'lines', 'mode': 'w',
00081           'label': 'Available view methods'
00082         },
00083         { 'id': 'default_view_fallback', 'type': 'boolean', 'mode': 'w',
00084           'label': 'Fall back to default view?'
00085         },
00086     )
00087 
00088     default_view = ''
00089     view_methods = ()
00090     default_view_fallback = False
00091 
00092     def manage_changeProperties(self, **kw):
00093         """Overwrite change properties to verify that default_view is in the method
00094         list
00095         """
00096         FactoryTypeInformation.manage_changeProperties(self, **kw)
00097         default_view = self.default_view
00098         view_methods = self.view_methods
00099         if not default_view:
00100             # TODO: use view action
00101             self.default_view = default_view = self.immediate_view
00102         if not view_methods:
00103             self.view_methods = view_methods = (default_view,)
00104         if default_view and default_view not in view_methods:
00105             raise ValueError, "%s not in %s" % (default_view, view_methods)
00106 
00107     security.declareProtected(View, 'getDefaultViewMethod')
00108     def getDefaultViewMethod(self, context):
00109         """Get the default view method from the FTI
00110         """
00111         return str(self.default_view)
00112 
00113     security.declareProtected(View, 'getAvailableViewMethods')
00114     def getAvailableViewMethods(self, context):
00115         """Get a list of registered view methods
00116         """
00117         methods = self.view_methods
00118         if isinstance(methods, basestring):
00119             methods = (methods,)
00120         return tuple(methods)
00121 
00122     security.declareProtected(View, 'getViewMethod')
00123     def getViewMethod(self, context, enforce_available=False, check_exists=False):
00124         """Get view method (aka layout) name from context
00125 
00126         Return -- view method from context or default view name
00127         """
00128         default = self.getDefaultViewMethod(context)
00129         layout = getattr(aq_base(context), 'layout', None)
00130 
00131         if safe_callable(layout):
00132             layout = layout()
00133         if not layout:
00134             return default
00135         if not isinstance(layout, basestring):
00136             raise TypeError, "layout of %s must be a string, got %s" % (
00137                               repr(context), type(layout))
00138         if enforce_available:
00139             available = self.getAvailableViewMethods(context)
00140             if layout not in available:
00141                 return default
00142         if check_exists:
00143             method = getattr(context, layout, None)
00144             if method is None:
00145                 return default
00146         return layout
00147 
00148     security.declareProtected(View, 'getDefaultPage')
00149     def getDefaultPage(self, context, check_exists=False):
00150         """Get the default page from a folderish object
00151 
00152         Non folderish objects don't have a default view.
00153 
00154         If check_exists is enabled the method makes sure the object with the default
00155         page id exists.
00156 
00157         Return -- None for no default page or a string
00158         """
00159         if not getattr(aq_base(context), 'isPrincipiaFolderish', False):
00160             return None # non folderish objects don't have a default page per se
00161 
00162         default_page = getattr(aq_base(context), 'default_page', None)
00163 
00164         if safe_callable(default_page):
00165             default_page = default_page()
00166         if not default_page:
00167             return None
00168         if isinstance(default_page, (tuple, list)):
00169             default_page = default_page[0]
00170         if not isinstance(default_page, str):
00171             raise TypeError, ("default_page must be a string, got %s(%s):" %
00172                               (default_page, type(default_page)))
00173 
00174         if check_exists and not om_has_key(context, default_page):
00175             return None
00176 
00177         return default_page
00178 
00179     security.declareProtected(View, 'defaultView')
00180     def defaultView(self, context):
00181         """Get the current view to use for an object. If a default page is  set,
00182         use that, else use the currently selected view method/layout.
00183         """
00184 
00185         # Delegate to PloneTool's version if we have it else, use own rules
00186         plone_utils = getToolByName(self, 'plone_utils', None)
00187         if plone_utils is not None:
00188             obj, path = plone_utils.browserDefault(context)
00189             return path[-1]
00190         else:
00191             default_page = self.getDefaultPage(context, check_exists=True)
00192             if default_page is not None:
00193                 return default_page
00194             fallback = self.default_view_fallback
00195             return self.getViewMethod(context, check_exists=fallback)
00196 
00197     security.declarePublic('queryMethodID')
00198     def queryMethodID(self, alias, default=None, context=None):
00199         """ Query method ID by alias.
00200 
00201         Use "(dynamic view)" as the alias target to look up as per defaultView()
00202         Use "(selected layout)" as the alias target to look up as per
00203             getViewMethod()
00204         """
00205         methodTarget = FactoryTypeInformation.queryMethodID(self, alias,
00206                                                          default=default,
00207                                                          context=context)
00208         if not isinstance(methodTarget, basestring):
00209             # nothing to do, method_id is probably None
00210             return methodTarget
00211 
00212         if context is None or default == '':
00213             # the edit zpts like typesAliases don't apply a context and set the
00214             # default to ''. We do not want to resolve (dynamic view) for these
00215             # methods.
00216             return methodTarget
00217 
00218         # Our two special targets:
00219 
00220         if methodTarget.lower() == "(dynamic view)":
00221             methodTarget = self.defaultView(context)
00222 
00223         if methodTarget.lower() == "(selected layout)":
00224             fallback = self.default_view_fallback
00225             methodTarget = self.getViewMethod(context, check_exists=fallback)
00226 
00227         return methodTarget
00228 
00229 InitializeClass(DynamicViewTypeInformation)
00230 
00231 
00232 #def manage_addFactoryDynamivViewTIForm(self, REQUEST):
00233 #    """ Get the add form for factory-based type infos.
00234 #    """
00235 #    addTIForm = DTMLFile('addTypeInfo', _dtmldir).__of__(self)
00236 #    ttool = getToolByName(self, 'portal_types')
00237 #    return addTIForm( self, REQUEST,
00238 #                      add_meta_type=DynamicViewTypeInformation.meta_type,
00239 #                      types=ttool.listDefaultTypeInformation() )
00240 
00241 
00242 # BBB: the following lines are required to register the new FTI in CMF 1.5 and may
00243 # be removed after switching to CMF 1.6
00244 try:
00245     from Products.CMFCore.TypesTool import typeClasses
00246 except ImportError:
00247     pass
00248 else:
00249     setattr(TypesTool, 'manage_addFactoryDynamivViewTIForm',
00250                         manage_addFactoryDynamivViewTIForm)
00251 
00252     setattr(TypesTool, 'manage_addFactoryDynamivViewTIForm__roles__',
00253                         ('Manager', ))
00254 
00255     typeClasses.append(
00256         {'class' : DynamicViewTypeInformation,
00257          'name' : DynamicViewTypeInformation.meta_type,
00258          'action' : 'manage_addFactoryDynamivViewTIForm',
00259          'permission' : ManagePortal,
00260          },
00261         )