1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
|
##############################################################################
#
# CMFDynamicViewFTI
# Copyright (c) 2005 Plone Foundation. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
# Authors: Martin Aspeli
# Christian Heimes
#
##############################################################################
"""
"""
from zope.interface import implements
from AccessControl import ClassSecurityInfo
from Globals import InitializeClass
from Globals import DTMLFile
from Acquisition import aq_base
from types import ClassType
from Products.CMFCore.TypesTool import FactoryTypeInformation
from Products.CMFCore.TypesTool import TypesTool
from Products.CMFCore.permissions import View
from Products.CMFCore.permissions import ManagePortal
from Products.CMFCore.utils import _dtmldir
from Products.CMFCore.utils import _wwwdir
from Products.CMFCore.utils import getToolByName
from Products.CMFDynamicViewFTI.interface import IDynamicViewTypeInformation
from Products.CMFDynamicViewFTI.interfaces import IDynamicViewTypeInformation as ZopeTwoIDynamicViewTypeInformation
def safe_hasattr(obj, name, _marker=object()):
"""Make sure we don't mask exceptions like hasattr().
We don't want exceptions other than AttributeError to be masked,
since that too often masks other programming errors.
Three-argument getattr() doesn't mask those, so we use that to
implement our own hasattr() replacement.
"""
return getattr(obj, name, _marker) is not _marker
def safe_callable(obj):
"""Make sure our callable checks are ConflictError safe."""
if safe_hasattr(obj, '__class__'):
if safe_hasattr(obj, '__call__'):
return True
else:
return isinstance(obj, ClassType)
else:
return callable(obj)
def om_has_key(context, key):
"""Object Manager has_key method with optimization for btree folders
Zope's OFS.ObjectManager has no method for checking if an object with an id
exists inside a folder.
"""
klass = getattr(aq_base(context), '__class__', None)
if hasattr(klass, 'has_key'):
# BTreeFolder2 optimization
if context.has_key(key):
return True
else:
# standard ObjectManager api
if key in context.objectIds():
return True
return False
fti_meta_type = 'Factory-based Type Information with dynamic views'
class DynamicViewTypeInformation(FactoryTypeInformation):
"""FTI with dynamic views
A value of (dynamic view) as alias is replaced by the output of defaultView()
"""
implements(IDynamicViewTypeInformation)
__implements__ = (ZopeTwoIDynamicViewTypeInformation,)
meta_type = fti_meta_type
security = ClassSecurityInfo()
_properties = FactoryTypeInformation._properties + (
{ 'id': 'default_view', 'type': 'string', 'mode': 'w',
'label': 'Default view method'
},
{ 'id': 'view_methods', 'type': 'lines', 'mode': 'w',
'label': 'Available view methods'
},
{ 'id': 'default_view_fallback', 'type': 'boolean', 'mode': 'w',
'label': 'Fall back to default view?'
},
)
default_view = ''
view_methods = ()
default_view_fallback = False
def manage_changeProperties(self, **kw):
"""Overwrite change properties to verify that default_view is in the method
list
"""
FactoryTypeInformation.manage_changeProperties(self, **kw)
default_view = self.default_view
view_methods = self.view_methods
if not default_view:
# TODO: use view action
self.default_view = default_view = self.immediate_view
if not view_methods:
self.view_methods = view_methods = (default_view,)
if default_view and default_view not in view_methods:
raise ValueError, "%s not in %s" % (default_view, view_methods)
security.declareProtected(View, 'getDefaultViewMethod')
def getDefaultViewMethod(self, context):
"""Get the default view method from the FTI
"""
return str(self.default_view)
security.declareProtected(View, 'getAvailableViewMethods')
def getAvailableViewMethods(self, context):
"""Get a list of registered view methods
"""
methods = self.view_methods
if isinstance(methods, basestring):
methods = (methods,)
return tuple(methods)
security.declareProtected(View, 'getViewMethod')
def getViewMethod(self, context, enforce_available=False, check_exists=False):
"""Get view method (aka layout) name from context
Return -- view method from context or default view name
"""
default = self.getDefaultViewMethod(context)
layout = getattr(aq_base(context), 'layout', None)
if safe_callable(layout):
layout = layout()
if not layout:
return default
if not isinstance(layout, basestring):
raise TypeError, "layout of %s must be a string, got %s" % (
repr(context), type(layout))
if enforce_available:
available = self.getAvailableViewMethods(context)
if layout not in available:
return default
if check_exists:
method = getattr(context, layout, None)
if method is None:
return default
return layout
security.declareProtected(View, 'getDefaultPage')
def getDefaultPage(self, context, check_exists=False):
"""Get the default page from a folderish object
Non folderish objects don't have a default view.
If check_exists is enabled the method makes sure the object with the default
page id exists.
Return -- None for no default page or a string
"""
if not getattr(aq_base(context), 'isPrincipiaFolderish', False):
return None # non folderish objects don't have a default page per se
default_page = getattr(aq_base(context), 'default_page', None)
if safe_callable(default_page):
default_page = default_page()
if not default_page:
return None
if isinstance(default_page, (tuple, list)):
default_page = default_page[0]
if not isinstance(default_page, str):
raise TypeError, ("default_page must be a string, got %s(%s):" %
(default_page, type(default_page)))
if check_exists and not om_has_key(context, default_page):
return None
return default_page
security.declareProtected(View, 'defaultView')
def defaultView(self, context):
"""Get the current view to use for an object. If a default page is set,
use that, else use the currently selected view method/layout.
"""
# Delegate to PloneTool's version if we have it else, use own rules
plone_utils = getToolByName(self, 'plone_utils', None)
if plone_utils is not None:
obj, path = plone_utils.browserDefault(context)
return path[-1]
else:
default_page = self.getDefaultPage(context, check_exists=True)
if default_page is not None:
return default_page
fallback = self.default_view_fallback
return self.getViewMethod(context, check_exists=fallback)
security.declarePublic('queryMethodID')
def queryMethodID(self, alias, default=None, context=None):
""" Query method ID by alias.
Use "(dynamic view)" as the alias target to look up as per defaultView()
Use "(selected layout)" as the alias target to look up as per
getViewMethod()
"""
methodTarget = FactoryTypeInformation.queryMethodID(self, alias,
default=default,
context=context)
if not isinstance(methodTarget, basestring):
# nothing to do, method_id is probably None
return methodTarget
if context is None or default == '':
# the edit zpts like typesAliases don't apply a context and set the
# default to ''. We do not want to resolve (dynamic view) for these
# methods.
return methodTarget
# Our two special targets:
if methodTarget.lower() == "(dynamic view)":
methodTarget = self.defaultView(context)
if methodTarget.lower() == "(selected layout)":
fallback = self.default_view_fallback
methodTarget = self.getViewMethod(context, check_exists=fallback)
return methodTarget
InitializeClass(DynamicViewTypeInformation)
def manage_addFactoryDynamivViewTIForm(self, REQUEST):
""" Get the add form for factory-based type infos.
"""
addTIForm = DTMLFile('addTypeInfo', _dtmldir).__of__(self)
ttool = getToolByName(self, 'portal_types')
return addTIForm( self, REQUEST,
add_meta_type=DynamicViewTypeInformation.meta_type,
types=ttool.listDefaultTypeInformation() )
# BBB: the following lines are required to register the new FTI in CMF 1.5 and may
# be removed after switching to CMF 1.6
try:
from Products.CMFCore.TypesTool import typeClasses
except ImportError:
pass
else:
setattr(TypesTool, 'manage_addFactoryDynamivViewTIForm',
manage_addFactoryDynamivViewTIForm)
setattr(TypesTool, 'manage_addFactoryDynamivViewTIForm__roles__',
('Manager', ))
typeClasses.append(
{'class' : DynamicViewTypeInformation,
'name' : DynamicViewTypeInformation.meta_type,
'action' : 'manage_addFactoryDynamivViewTIForm',
'permission' : ManagePortal,
},
)
|