############################################################################## # # Copyright (c) 2002 Zope Corporation and Contributors. 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. # ############################################################################## """This module implements a simple item mix-in for objects that have a very simple (e.g. one-screen) management interface, like documents, Aqueduct database adapters, etc. This module can also be used as a simple template for implementing new item types. $Id: SimpleItem.py 40478 2005-12-02 15:52:43Z chrisw $ """ import warnings import marshal, re, sys, time import AccessControl.Role, AccessControl.Owned, App.Common import Globals, App.Management, Acquisition, App.Undo from AccessControl import getSecurityManager, Unauthorized from AccessControl.ZopeSecurityPolicy import getRoles from Acquisition import aq_base, aq_parent, aq_inner, aq_acquire from ComputedAttribute import ComputedAttribute from DocumentTemplate.html_quote import html_quote from DocumentTemplate.ustr import ustr from ExtensionClass import Base from webdav.Resource import Resource from zExceptions import Redirect from zExceptions.ExceptionFormatter import format_exception from zope.interface import implements import ZDOM from CopySupport import CopySource from interfaces import IItem from interfaces import IItemWithName from interfaces import ISimpleItem from Traversable import Traversable HTML=Globals.HTML import logging logger = logging.getLogger() class Item(Base, Resource, CopySource, App.Management.Tabs, Traversable, ZDOM.Element, AccessControl.Owned.Owned, App.Undo.UndoSupport, ): """A common base class for simple, non-container objects.""" implements(IItem) isPrincipiaFolderish=0 isTopLevelPrincipiaApplicationObject=0 def manage_afterAdd(self, item, container): pass manage_afterAdd.__five_method__ = True def manage_beforeDelete(self, item, container): pass manage_beforeDelete.__five_method__ = True def manage_afterClone(self, item): pass manage_afterClone.__five_method__ = True # Direct use of the 'id' attribute is deprecated - use getId() id='' getId__roles__=None def getId(self): """Return the id of the object as a string. This method should be used in preference to accessing an id attribute of an object directly. The getId method is public. """ name=getattr(self, 'id', None) if callable(name): return name() if name is not None: return name if hasattr(self, '__name__'): return self.__name__ raise AttributeError, 'This object has no id' # Alias id to __name__, which will make tracebacks a good bit nicer: __name__=ComputedAttribute(lambda self: self.getId()) # Name, relative to SOFTWARE_URL of icon used to display item # in folder listings. icon='' # Meta type used for selecting all objects of a given type. meta_type='simple item' # Default title. title='' # Default propertysheet info: __propsets__=() manage_options=( App.Undo.UndoSupport.manage_options +AccessControl.Owned.Owned.manage_options +({'label': 'Interfaces', 'action': 'manage_interfaces'},) ) # Attributes that must be acquired REQUEST=Acquisition.Acquired # Allow (reluctantly) access to unprotected attributes __allow_access_to_unprotected_subobjects__=1 def title_or_id(self): """Return the title if it is not blank and the id otherwise. """ title=self.title if callable(title): title=title() if title: return title return self.getId() def title_and_id(self): """Return the title if it is not blank and the id otherwise. If the title is not blank, then the id is included in parens. """ title=self.title if callable(title): title=title() id = self.getId() return title and ("%s (%s)" % (title,id)) or id def this(self): # Handy way to talk to ourselves in document templates. return self def tpURL(self): # My URL as used by tree tag return self.getId() def tpValues(self): # My sub-objects as used by the tree tag return () _manage_editedDialog=Globals.DTMLFile('dtml/editedDialog', globals()) def manage_editedDialog(self, REQUEST, **args): return apply(self._manage_editedDialog,(self, REQUEST), args) def raise_standardErrorMessage( self, client=None, REQUEST={}, error_type=None, error_value=None, tb=None, error_tb=None, error_message='', tagSearch=re.compile(r'[a-zA-Z]>').search, error_log_url=''): try: if error_type is None: error_type =sys.exc_info()[0] if error_value is None: error_value=sys.exc_info()[1] # allow for a few different traceback options if tb is None and error_tb is None: tb=sys.exc_info()[2] if type(tb) is not type('') and (error_tb is None): error_tb = pretty_tb(error_type, error_value, tb) elif type(tb) is type('') and not error_tb: error_tb = tb # turn error_type into a string if hasattr(error_type, '__name__'): error_type=error_type.__name__ if hasattr(self, '_v_eek'): # Stop if there is recursion. raise error_type, error_value, tb self._v_eek=1 if str(error_type).lower() in ('redirect',): raise error_type, error_value, tb if not error_message: try: s = ustr(error_value) except: s = error_value try: match = tagSearch(s) except TypeError: match = None if match is not None: error_message=error_value if client is None: client=self if not REQUEST: REQUEST=self.aq_acquire('REQUEST') try: if hasattr(client, 'standard_error_message'): s=getattr(client, 'standard_error_message') else: client = client.aq_parent s=getattr(client, 'standard_error_message') kwargs = {'error_type': error_type, 'error_value': error_value, 'error_tb': error_tb, 'error_traceback': error_tb, 'error_message': error_message, 'error_log_url': error_log_url} if getattr(aq_base(s),'isDocTemp',0): v = s(client, REQUEST, **kwargs) elif callable(s): v = s(**kwargs) else: v = HTML.__call__(s, client, REQUEST, **kwargs) except: logger.error( 'Exception while rendering an error message', exc_info=True ) try: strv = str(error_value) except: strv = ('' % str(type(error_value).__name__)) v = strv + ( (" (Also, the following error occurred while attempting " "to render the standard error message, please see the " "event log for full details: %s)")%( html_quote(sys.exc_info()[1]), )) raise error_type, v, tb finally: if hasattr(self, '_v_eek'): del self._v_eek tb=None def manage(self, URL1): """ """ raise Redirect, "%s/manage_main" % URL1 # This keeps simple items from acquiring their parents # objectValues, etc., when used in simple tree tags. def objectValues(self, spec=None): return () objectIds=objectItems=objectValues # FTP support methods def manage_FTPstat(self,REQUEST): """Psuedo stat, used by FTP for directory listings. """ from AccessControl.User import nobody mode=0100000 if (hasattr(aq_base(self),'manage_FTPget')): try: if getSecurityManager().validate( None, self, 'manage_FTPget', self.manage_FTPget): mode=mode | 0440 except Unauthorized: pass if nobody.allowed( self.manage_FTPget, getRoles(self, 'manage_FTPget', self.manage_FTPget, ()), ): mode=mode | 0004 # check write permissions if hasattr(aq_base(self),'PUT'): try: if getSecurityManager().validate(None, self, 'PUT', self.PUT): mode=mode | 0220 except Unauthorized: pass if nobody.allowed( self.PUT, getRoles(self, 'PUT', self.PUT, ()), ): mode=mode | 0002 # get size if hasattr(aq_base(self), 'get_size'): size=self.get_size() elif hasattr(aq_base(self),'manage_FTPget'): size=len(self.manage_FTPget()) else: size=0 # get modification time if hasattr(aq_base(self), 'bobobase_modification_time'): mtime=self.bobobase_modification_time().timeTime() else: mtime=time.time() # get owner and group owner=group='Zope' if hasattr(aq_base(self), 'get_local_roles'): for user, roles in self.get_local_roles(): if 'Owner' in roles: owner=user break return marshal.dumps((mode,0,0,1,owner,group,size,mtime,mtime,mtime)) def manage_FTPlist(self,REQUEST): """Directory listing for FTP. In the case of non-Foldoid objects, the listing should contain one object, the object itself. """ # check to see if we are being acquiring or not ob=self while 1: if App.Common.is_acquired(ob): raise ValueError('FTP List not supported on acquired objects') if not hasattr(ob,'aq_parent'): break ob=ob.aq_parent stat=marshal.loads(self.manage_FTPstat(REQUEST)) id = self.getId() return marshal.dumps((id,stat)) def __len__(self): return 1 def __repr__(self): """Show the physical path of the object and its context if available. """ try: path = '/'.join(self.getPhysicalPath()) except: return Base.__repr__(self) context_path = None context = aq_parent(self) container = aq_parent(aq_inner(self)) if aq_base(context) is not aq_base(container): try: context_path = '/'.join(context.getPhysicalPath()) except: context_path = None res = '<%s' % self.__class__.__name__ res += ' at %s' % path if context_path: res += ' used for %s' % context_path res += '>' return res Globals.default__class_init__(Item) class Item_w__name__(Item): """Mixin class to support common name/id functions""" implements(IItemWithName) def getId(self): """Return the id of the object as a string. """ return self.__name__ def title_or_id(self): """Return the title if it is not blank and the id otherwise. """ return self.title or self.__name__ def title_and_id(self): """Return the title if it is not blank and the id otherwise. If the title is not blank, then the id is included in parens. """ t=self.title return t and ("%s (%s)" % (t,self.__name__)) or self.__name__ def _setId(self, id): self.__name__=id def getPhysicalPath(self): """Get the physical path of the object. Returns a path (an immutable sequence of strings) that can be used to access this object again later, for example in a copy/paste operation. getPhysicalRoot() and getPhysicalPath() are designed to operate together. """ path = (self.__name__,) p = aq_parent(aq_inner(self)) if p is not None: path = p.getPhysicalPath() + path return path def pretty_tb(t, v, tb, as_html=1): tb = format_exception(t, v, tb, as_html=as_html) tb = '\n'.join(tb) return tb class SimpleItem(Item, Globals.Persistent, Acquisition.Implicit, AccessControl.Role.RoleManager, ): # Blue-plate special, Zope Masala """Mix-in class combining the most common set of basic mix-ins """ implements(ISimpleItem) manage_options=Item.manage_options+( {'label':'Security', 'action':'manage_access', 'help':('OFSP', 'Security.stx')}, ) __ac_permissions__=(('View', ()),)