Package Products :: Package ZenModel :: Module ZenModelBase |
[hide private]
[frames] | no frames]

# Source Code for Module Products.ZenModel.ZenModelBase

      1  ###########################################################################
      2  #
      3  # This program is part of Zenoss Core, an open source monitoring platform.
      4  # Copyright (C) 2007, Zenoss Inc.
      5  #
      6  # This program is free software; you can redistribute it and/or modify it
      7  # under the terms of the GNU General Public License version 2 as published by
      8  # the Free Software Foundation.
      9  #
     10  # For complete information please visit: http://www.zenoss.com/oss/
     11  #
     12  ###########################################################################
     13
     14  __doc__="""ZenModelBase
     15
     16  $Id: ZenModelBase.py,v 1.17 2004/04/23 19:11:58 edahl Exp $"""
     17
     18  __version__ = "$Revision: 1.17 $"[11:-2]
     19
     20  import re
     21  import time
     22  import types
     23  import sys
     24
     25  from xml.sax import saxutils
     26  from urllib import unquote
     27  from cgi import escape
     28  import zope.component
     29  import zope.interface
     30
     31  from OFS.ObjectManager import checkValidId as globalCheckValidId
     32
     33  from AccessControl import ClassSecurityInfo, getSecurityManager, Unauthorized
     34  from Globals import InitializeClass
     35  from Acquisition import aq_base, aq_chain
     36
     37  from Products.ZenModel.interfaces import IZenDocProvider
     38  from Products.ZenUtils.Utils import zenpathsplit, zenpathjoin
     39  from Products.ZenUtils.Utils import createHierarchyObj, getHierarchyObj
     40  from Products.ZenUtils.Utils import getObjByPath
     41
     42  from Products.ZenUtils.Utils import prepId as globalPrepId, isXmlRpc
     43  from Products.ZenWidgets import messaging
     44  from Products.ZenUI3.browser.interfaces import INewPath
     45
     46  from ZenossSecurity import *
     47
     48  _MARKER = object()
     49
     50  # Custom device properties start with c
     51  iscustprop = re.compile("^c[A-Z]").search
     52
     53 -class ZenModelBase(object):
     54      """
     55      All ZenModel Persistent classes inherit from this class.  It provides some
     56      screen management functionality, and general utility methods.
     57      """
     58      _zendoc = ''
     59
     60      sub_meta_types = ()
     61      #prodStateThreshold = 500
     62
     63      security = ClassSecurityInfo()
     64
     65 -    def __call__(self):
     66          """
     67          Invokes the default view.
     68          """
     69          if isXmlRpc(self.REQUEST):
     70              return self
     71          else:
     72              newpath = INewPath(self)
     73              self.REQUEST.response.redirect(newpath)
     74
     75      index_html = None  # This special value informs ZPublisher to use __call__
     76
     77
     78      security.declareProtected(ZEN_VIEW, 'view')
     79 -    def view(self):
     80          '''
     81          Returns the default view even if index_html is overridden.
     82
     83          @permission: ZEN_VIEW
     84          '''
     85          return self()
     86
     87
     88 -    def __hash__(self):
     89          return hash(self.id)
     90
     91 -    def prepId(self, id, subchar='_'):
     92          """
     93          Clean out an id of illegal characters.
     94
     95          @type id: string
     96          @param subchar: Character to be substituted with illegal characters
     97          @type subchar: string
     98          @rtype: string
     99
    100          >>> dmd.Devices.prepId('ab^*cd')
    101          'ab__cd'
    102          >>> dmd.Devices.prepId('ab^*cd', subchar='Z')
    103          'abZZcd'
    104          >>> dmd.Devices.prepId('/boot')
    105          'boot'
    106          >>> dmd.Devices.prepId('/')
    107          '-'
    108          >>> dmd.Devices.prepId(' mydev ')
    109          'mydev'
    110          """
    111          return globalPrepId(id, subchar)
    112
    113 -    def checkValidId(self, id, prep_id = False):
    114          """
    115          Checks that an id is a valid Zope id.  Looks for invalid characters and
    116          checks that the id doesn't already exist in this context.
    117
    118          @type id: string
    119          @type prep_id: boolean
    120          @rtype: boolean
    121
    122          >>> dmd.Devices.checkValidId('^*')
    123          'The id "^*" contains characters illegal in URLs.'
    124          >>> dmd.Devices.checkValidId('Server')
    125          'The id "Server" is invalid - it is already in use.'
    126          >>> dmd.Devices.checkValidId('ZenTestId')
    127          True
    128          """
    129          new_id = unquote(id)
    130          if prep_id: new_id = self.prepId(id)
    131          try:
    132              globalCheckValidId(self, new_id)
    133              return True
    134          except:
    135              return str(sys.exc_info()[1])
    136
    137
    138 -    def getUnusedId(self, relName, baseKey, extensionIter=None):
    139          """
    140          Return a new id that is not already in use in the relationship.  If
    141          baseKey is not already in use, return that.  Otherwise append values
    142          from extensionIter to baseKey until an used key is found.  The default
    143          extensionIter appends integers starting with 2 and counting up.
    144
    145          @type relName: string
    146          @type baseKey: string
    147          @type extensionIter: iterator
    148          @rtype: string
    149
    150          >>> id1 = dmd.Devices.getUnusedId('devices', 'dev')
    151          >>> id1
    152          'dev'
    153          >>> dmd.Devices.createInstance(id1)
    154          <Device at /zport/dmd/Devices/devices/dev>
    155          >>> id2 = dmd.Devices.getUnusedId('devices', 'dev')
    156          >>> id2
    157          'dev2'
    158          """
    159          import itertools
    160          if extensionIter is None:
    161              extensionIter = itertools.count(2)
    162          rel = getattr(self, relName)
    163          candidate = baseKey
    164          while candidate in rel.objectIds():
    165              candidate = self.prepId('%s%s' % (baseKey, extensionIter.next()))
    166          return candidate
    167
    168
    169 -    def getIdLink(self):
    170          """
    171          DEPRECATED Return an a link to this object with its id as the name.
    172
    173          @return: An HTML link to this object
    174          @rtype: string
    175
    176          >>> dmd.Devices.getIdLink()
    177          '<a href="/zport/dmd/Devices">/</a>'
    178          """
    179          return self.urlLink()
    180
    181
    182 -    def callZenScreen(self, REQUEST, redirect=False):
    183          """
    184          Call and return screen specified by zenScreenName value of REQUEST.
    185          If zenScreenName is not present call the default screen.  This is used
    186          in functions that are called from forms to get back to the correct
    187          screen with the correct context.
    188          """
    189          if REQUEST is None or getattr(REQUEST, 'dontRender', False):
    190              # EventView uses a FakeRequest class to avoid the overhead
    191              # of rendering pages as result of ajax calls.
    192              return ''
    193          screenName = REQUEST.get("zenScreenName", "")
    194          if not redirect and REQUEST.get("redirect", None) :
    195              redirect = True
    196          if redirect:
    197              nurl = "%s/%s" % (self.getPrimaryUrlPath(), screenName)
    198              REQUEST['RESPONSE'].redirect(nurl)
    199          else:
    200              REQUEST['URL'] = "%s/%s" % (self.absolute_url_path(), screenName)
    201              screen = getattr(self, screenName, False)
    202              if not screen: return self()
    203              return screen()
    204
    205
    206 -    def zenScreenUrl(self):
    207          """
    208          Return the url for the current screen as defined by zenScreenName.
    209          If zenScreenName is not found in the request the request url is used.
    210
    211          @return: An url to this object
    212          @rtype: string
    213          """
    214          screenName = self.REQUEST.get("zenScreenName", "")
    215          if not screenName: return self.REQUEST.URL
    216          return self.getPrimaryUrlPath() + "/" + screenName
    217
    218
    219 -    def urlLink(self, text=None, url=None, attrs={}):
    220          """
    221          Return an anchor tag if the user has access to the remote object.
    222
    223          @param text: the text to place within the anchor tag or string.
    224                       Defaults to the id of this object.
    225          @param url: url for the href. Default is getPrimaryUrlPath
    226          @type attrs: dict
    227          @param attrs: any other attributes to be place in the in the tag.
    228          @return: An HTML link to this object
    229          @rtype: string
    230          """
    231          if not text:
    232              text = self.titleOrId()
    233          text = escape(text)
    234          if not self.checkRemotePerm("View", self):
    235              return text
    236          if not url:
    237              url = self.getPrimaryUrlPath()
    238          if len(attrs):
    239              return '<a href="%s" %s>%s</a>' % (url,
    240                  ' '.join(['%s="%s"' % (x,y) for x,y in attrs.items()]),
    241                  text)
    242          else:
    243              return '<a href="%s">%s</a>' % (url, text)
    244
    245
    246 -    def getBreadCrumbUrlPath(self):
    247          """
    248          Return the url to be used in breadcrumbs for this object.  normally
    249          this is equal to getPrimaryUrlPath. It can be used as a hook to modify
    250          the url so that it points towards a different tab then the default.
    251
    252          @return: A url to this object
    253          @rtype: string
    254
    255          >>> dmd.Devices.getBreadCrumbUrlPath()
    256          '/zport/dmd/Devices'
    257          >>> rc = dmd.Reports._getOb('Graph Reports')
    258          >>> rc.manage_addGraphReport('test').getBreadCrumbUrlPath()
    259          '/zport/dmd/Reports/Graph%20Reports/test/editGraphReport'
    260          """
    261          return self.getPrimaryUrlPath()
    262
    263
    264 -    def getBreadCrumbName(self):
    265          return self.title_or_id()
    266
    267
    268 -    def breadCrumbs(self, terminator='dmd', terminate=lambda x: False):
    269          """
    270          Return the data to create the breadcrumb links for this object.
    271
    272          This is a list of tuples where the first value is the URL of the bread
    273          crumb and the second is the lable.
    274
    275          @return: List of tuples to create a bread crumbs
    276          @rtype: list
    277
    278          >>> dmd.Devices.Server.breadCrumbs()
    279          [('/zport/dmd/Devices', 'Devices'),
    280              ('/zport/dmd/Devices/Server', 'Server')]
    281          """
    282          links = []
    283          curDir = self.primaryAq()
    284          while curDir.id != terminator and not terminate(curDir):
    285              if curDir.meta_type == 'ToManyContRelationship':
    286                  curDir = curDir.getPrimaryParent()
    287                  continue
    288              if not getattr(aq_base(curDir),"getBreadCrumbUrlPath", False):
    289                  break
    290              url = ""
    291              if self.checkRemotePerm("View", curDir):
    292                  url = curDir.getBreadCrumbUrlPath()
    293              links.append((url, curDir.getBreadCrumbName()))
    294              curDir = curDir.aq_parent
    295          links.reverse()
    296          return links
    297
    298
    299 -    def upToOrganizerBreadCrumbs(self, terminator='dmd'):
    300
    301          def isOrganizer(curDir):
    302              from Products.ZenModel.Organizer import Organizer
    303              try:
    304                  return isinstance(curDir, Organizer)
    305              except:
    306                  return False
    307
    308          return ZenModelBase.breadCrumbs(self, terminator, isOrganizer)
    309
    310
    311      security.declareProtected(ZEN_COMMON, 'checkRemotePerm')
    312 -    def checkRemotePerm(self, permission, robject):
    313          """
    314          Look to see if the current user has permission on remote object.
    315
    316          @param permission: Zope permission to be tested. ie "View"
    317          @param robject: remote objecct on which test is run.  Will test on
    318          primary acquisition path.
    319          @rtype: boolean
    320          @permission: ZEN_COMMON
    321          """
    322          user = getSecurityManager().getUser()
    323          return user.has_permission(permission, robject.primaryAq())
    324
    325
    326
    327      security.declareProtected(ZEN_VIEW, 'zentinelTabs')
    328 -    def zentinelTabs(self, templateName, REQUEST=None):
    329          """
    330          Return a list of hashes that define the screen tabs for this object.
    331
    332          Keys in the hash are:
    333              - action = the name of the page template for this tab
    334              - name = the label used on the tab
    335              - permissions = a tuple of permissions to view this template
    336
    337          @permission: ZEN_VIEW
    338
    339          >>> dmd.Devices.zentinelTabs('deviceOrganizerStatus')
    340          [{'action': 'deviceOrganizerStatus', 'selected': True,
    341              'name': 'Classes', 'permissions': ('View',)},
    342          {'action': 'viewEvents', 'name': 'Events', 'permissions': ('View',)},
    343          {'action': 'zPropertyEdit', 'name': 'Configuration Properties',
    344              'permissions': ('View',)},
    345          {'action': 'perfConfig', 'name': 'Templates',
    346              'permissions': ('Manage DMD',)}]
    347          """
    348          tabs = []
    349          user = getSecurityManager().getUser()
    350          actions = self.factory_type_information[0]['actions']
    351          selectedTabName = self._selectedTabName(templateName, REQUEST)
    352          for a in actions:
    353              def permfilter(p): return user.has_permission(p,self)
    354              permok = filter(permfilter, a['permissions'])
    355              if not a.get('visible', True) or not permok:
    356                  continue
    357              a = a.copy()
    358              if a['action'] == selectedTabName: a['selected'] = True
    359              tabs.append(a)
    360          return tabs
    361
    362 -    def _selectedTabName(self, templateName, REQUEST=None):
    363          if REQUEST and REQUEST.get('selectedTabName', '') :
    364              selectedTabName = REQUEST.get('selectedTabName', '')
    365          else:
    366              selectedTabName = templateName
    367          requestUrl = REQUEST['URL'] if REQUEST else None
    368          if not selectedTabName and requestUrl and requestUrl.rfind('/') != -1:
    369              selectedTabName = requestUrl[requestUrl.rfind('/') + 1:]
    370              if selectedTabName.startswith('@@'):
    371                  selectedTabName = selectedTabName[2:]
    372          return selectedTabName
    373
    374
    375      security.declareProtected(ZEN_MANAGE_DMD, 'zmanage_editProperties')
    376 -    def zmanage_editProperties(self, REQUEST=None, redirect=False):
    377          """
    378          Edit a ZenModel object and return its proper page template.
    379          Object will be reindexed if nessesary.
    380
    381          @permission: ZEN_MANAGE_DMD
    382          """
    383          self.manage_changeProperties(**REQUEST.form)
    384          index_object = getattr(self, 'index_object', lambda self: None)
    385          index_object()
    386          if REQUEST:
    387              from Products.ZenUtils.Time import SaveMessage
    388              messaging.IMessageSender(self).sendToBrowser(
    389                  'Properties Saved',
    390                  SaveMessage()
    391              )
    392              return self.callZenScreen(REQUEST, redirect=redirect)
    393
    394
    395      security.declareProtected(ZEN_VIEW, 'getPrimaryDmdId')
    396 -    def getPrimaryDmdId(self, rootName="dmd", subrel=""):
    397          """
    398          Return the full dmd id of this object for instance /Devices/Server.
    399          Everything before dmd is removed.  A different rootName can be passed
    400          to stop at a different object in the path.  If subrel is passed any
    401          relationship name in the path to the object will be removed.
    402
    403          @param rootName: Name of root
    404          @type rootName: string
    405          @param subrel: Name of relation
    406          @type subrel: string
    407          @return: Path to object
    408          @rtype: string
    409          @permission: ZEN_VIEW
    410
    411          >>> d = dmd.Devices.Server.createInstance('test')
    412          >>> d.getPrimaryDmdId()
    413          '/Devices/Server/devices/test'
    414          >>> d.getPrimaryDmdId('Devices')
    415          '/Server/devices/test'
    416          >>> d.getPrimaryDmdId('Devices','devices')
    417          '/Server/test'
    418          """
    419          path = list(self.getPrimaryPath())
    420          path = path[path.index(rootName)+1:]
    421          if subrel: path = filter(lambda x: x != subrel, path)
    422          return '/'+'/'.join(path)
    423
    424
    425 -    def zenpathjoin(self, path):
    426          """
    427          DEPRECATED Build a Zenoss path based on a list or tuple.
    428
    429          @type path: list or tuple
    430
    431          >>> dmd.zenpathjoin(('zport', 'dmd', 'Devices', 'Server'))
    432          '/zport/dmd/Devices/Server'
    433          """
    434          return zenpathjoin(path)
    435
    436
    437 -    def zenpathsplit(self, path):
    438          """
    439          DEPRECATED Split a path on its '/'.
    440          """
    441          return zenpathsplit(path)
    442
    443
    444 -    def createHierarchyObj(self, root, name, factory, relpath="", alog=None):
    445          """
    446          DEPRECATED this is only seems to be used in Organizer.createOrganizer -
    447          Create an object from its path we use relpath to skip down any missing
    448          relations in the path and factory is the constructor for this object.
    449          """
    450          return createHierarchyObj(root, name, factory, relpath, alog)
    451
    452
    453 -    def getHierarchyObj(self, root, name, relpath):
    454          """
    455          DEPRECATED this doesn't seem to be used anywere don't use it!!!
    456          """
    457          return getHierarchyObj(root, name, relpath)
    458
    459
    460 -    def getDmd(self):
    461          """
    462          DEPRECATED Return the dmd root object with unwraped acquisition path.
    463
    464          >>> dmd.Devices.Server.getDmd()
    465          <DataRoot at /zport/dmd>
    466          """
    467          for obj in aq_chain(self):
    468              if getattr(obj, 'id', None) == 'dmd': return obj
    469
    470
    471 -    def getDmdRoot(self, name):
    472          """
    473          Return a dmd root organizer such as "Systems".  The acquisition path
    474          will be cleaned so that it points directly to the root.
    475
    476          >>> dmd.Devices.Server.getDmdRoot("Systems")
    477          <System at /zport/dmd/Systems>
    478          """
    479          dmd = self.getDmd()
    480          return dmd._getOb(name)
    481
    482
    483 -    def getDmdObj(self, path):
    484          """
    485          DEPRECATED Return an object from path that starts at dmd.
    486
    487          >>> dmd.getDmdObj('/Devices/Server')
    488          <DeviceClass at /zport/dmd/Devices/Server>
    489          """
    490          if path.startswith("/"): path = path[1:]
    491          return self.getDmd().getObjByPath(path)
    492
    493
    494 -    def getZopeObj(self, path):
    495          """
    496          DEPRECATED Return an object from path tat starts at zope root.
    497
    498          >>> dmd.getZopeObj('/zport/dmd/Devices/Server')
    499          <DeviceClass at /zport/dmd/Devices/Server>
    500          """
    501          return self.getObjByPath(path)
    502
    503
    504 -    def getNowString(self):
    505          """
    506          Return the current time as a string in the format '2007/09/27 14:09:53'.
    507
    508          @rtype: string
    509          """
    510          return time.strftime("%Y/%m/%d %H:%M:%S", time.localtime())
    511
    512
    513 -    def todayDate(self):
    514          """
    515          Return today's date as a string in the format 'mm/dd/yyyy'.
    516
    517          @rtype: string
    518          """
    519          return time.strftime("%m/%d/%Y", time.localtime())
    520
    521
    522 -    def yesterdayDate(self):
    523          """
    524          Return yesterday's date as a string in the format 'mm/dd/yyyy'.
    525
    526          @rtype: string
    527          """
    528          yesterday = time.time() - 24*3600
    529          return time.strftime("%m/%d/%Y", time.localtime(yesterday))
    530
    531
    532 -    def all_meta_types(self, interfaces=None):
    533          """
    534          DEPRECATED Override the ObjectManager method that is used to control
    535          the items available in the add drop down in the ZMI.  It uses the
    536          attribute sub_menu_items to create the data structures.  This is a list
    537          of meta_types for the available classes.  This functionality is rarely
    538          used in Zenoss because the ZMI is not the perfered management
    539          interface.
    540          """
    541          mts = super(ZenModelBase,self).all_meta_types(interfaces)
    542          if self.sub_meta_types:
    543              mts = filter(lambda mt: mt['name'] in self.sub_meta_types, mts)
    544          return mts
    545
    546
    547      security.declareProtected('Delete objects', 'manage_deleteObjects')
    548 -    def manage_deleteObjects(self, ids=(), REQUEST=None):
    549          """
    550          Delete objects by id from this object and return to the current
    551          template as defined by callZenScreen.  Uses ObjectManager._delObject to
    552          remove the object.
    553
    554          @permission: ZEN_VIEW
    555          """
    556          for id in ids:  self._delObject(id)
    557          if REQUEST:
    558              return self.callZenScreen(REQUEST)
    559
    560
    561 -    def custPropertyIds(self):
    562          """
    563          List custom properties that are defined at root node. Custom properties
    564          start with a lower "c" followed by a uppercase character.
    565          """
    566          return self.zenPropertyIds(pfilt=iscustprop)
    567
    568
    569 -    def custPropertyMap(self):
    570          """
    571          Return custom property definitions.
    572
    573          @rtype: [{'id':'cName','label':'Name', 'type':'string'},]
    574          """
    575          return self.zenPropertyMap(pfilt=iscustprop)
    576
    577
    578 -    def visibleCustPropertyMap(self):
    579          """
    580          List custom property definitions that are visible using
    581          custPropertyMap::
    582
    583          @rtype: [{'id':'cName','label':'Name', 'type':'string'},]
    584          """
    585          return [ p for p in self.zenPropertyMap(pfilt=iscustprop) \
    586                      if p.get('visible', True) ]
    587
    588
    589      security.declareProtected(ZEN_MANAGE_DMD, 'saveCustProperties')
    590 -    def saveCustProperties(self, REQUEST):
    591          """
    592          Save custom properties from REQUEST.form.
    593
    594          @permission: ZEN_MANAGE_DMD
    595          """
    596          return self.saveZenProperties(iscustprop, REQUEST)
    597
    598
    599 -    def getObjByPath(self, path):
    600          """
    601          Lookup and object by its path.  Basically does a Zope unrestricted
    602          traverse on the path given.
    603
    604          @type path: list or string /zport/dmd/Devices
    605
    606          >>> dmd.getObjByPath(('zport','dmd','Devices'))
    607          <DeviceClass at /zport/dmd/Devices>
    608          >>> dmd.getObjByPath(('Devices','Server'))
    609          <DeviceClass at /zport/dmd/Devices/Server>
    610          >>> dmd.getObjByPath('/zport/dmd/Devices/Server')
    611          <DeviceClass at /zport/dmd/Devices/Server>
    612          >>> dmd.getObjByPath('Devices/Server')
    613          <DeviceClass at /zport/dmd/Devices/Server>
    614          """
    615          return getObjByPath(self, path)
    616
    617
    618 -    def isLocalName(self, name):
    619          """
    620          Check to see if a name is local to our current context or if it comes
    621          from our acquisition chain.
    622
    623          @rtype: boolean
    624
    625          >>> dmd.isLocalName('Devices')
    626          True
    627          >>> dmd.Devices.Server.isLocalName('Devices')
    628          False
    629          """
    630          v = getattr(aq_base(self), name, '__ZENMARKER__')
    631          return v != '__ZENMARKER__'
    632
    633      security.declareProtected(ZEN_VIEW, 'helpLink')
    634 -    def helpLink(self):
    635          """
    636          DEPRECATED Return a link to the objects help file.
    637
    638          @permission: ZEN_VIEW
    639          """
    640          path = self.__class__.__module__.split('.')
    641          className = path[-1].replace('Class','')
    642          product = path[-2]
    643
    644          path = ("", "Control_Panel", "Products", product, "Help",
    645                  "%s.stx"%className)
    646
    647          # check to see if we have a help screen
    648          app = self.getPhysicalRoot()
    649          try:
    650              app.restrictedTraverse(path)
    651          except (KeyError, Unauthorized):
    652              return ""
    653
    654          url = "/HelpSys?help_url="+ "/".join(path)
    655
    656          return """<a class="tabletitle" href="%s" \
    657              onClick="window.open('%s','zope_help','width=600,height=500, \
    658              menubar=yes,toolbar=yes,scrollbars=yes,resizable=yes');  \
    659              return false;" onMouseOver="window.status='Open online help'; \
    660              return true;" onMouseOut="window.status=''; return true;">Help!</a>
    661              """ % (url, url)
    662
    663
    664      security.declareProtected(ZEN_VIEW, 'getIconPath')
    665 -    def getIconPath(self):
    666          """
    667          Return the icon associated with this object.  The icon path is defined
    668          in the zProperty zIcon.
    669
    670          @return: Path to icon
    671          @rtype: string
    672          @permission: ZEN_VIEW
    673
    674          >>> dmd.Devices.Server.zIcon = '/zport/dmd/img/icons/server.png'
    675          >>> d = dmd.Devices.Server.createInstance('test')
    676          >>> d.getIconPath()
    677          '/zport/dmd/img/icons/server.png'
    678          """
    679          try:
    680              return self.primaryAq().zIcon
    681          except AttributeError:
    682              return '/zport/dmd/img/icons/noicon.png'
    683
    684
    685 -    def aqBaseHasAttr(self, attr):
    686          """
    687          Return hasattr(aq_base(self), attr)
    688          This is a convenience function for use in templates, where it's not
    689          so easy to make a similar call directly.
    690          hasattr itself will swallow exceptions, so we don't want to use that.
    691          We also need to allow for values of None, so something like
    692          getattr(aq_base(self, attr, None) doesn't really tell us anything.
    693          Testing __dict__ is not a good choice because it doesn't allow
    694          for properties (and I believe __getitem__ calls.)
    695          So while this looks pretty attrocious, it might be the most sane
    696          solution.
    697          """
    698          return getattr(aq_base(self), attr, _MARKER) is not _MARKER
    699
    700
    701 -class ZenModelZenDocProvider(object):
    702      zope.interface.implements(IZenDocProvider)
    703      zope.component.adapts(ZenModelBase)
    704
    705 -    def __init__(self, zenModelBase):
    706          self._underlyingObject = zenModelBase
    707
    708 -    def getZendoc(self):
    709          zendoc = self._underlyingObject._zendoc
    710          if not zendoc and self._underlyingObject.aqBaseHasAttr( 'description' ):
    711              zendoc = self._underlyingObject.description
    712          return zendoc
    713
    714 -    def setZendoc(self, zendocText):
    715          self._underlyingObject._zendoc = zendocText
    716
    717 -    def exportZendoc(self,ofile):
    718          """Return an xml representation of a RelationshipManagers zendoc
    719          <property id='_zendoc' type='string' mode='w'>
    720              value
    721          </property>
    722          """
    723          value = self.getZendoc()
    724          if not value: return
    725          ofile.write("<property id='zendoc' type='string'>\n")
    726          if type(value) not in types.StringTypes:
    727              value = unicode(value)
    728          elif type(value) == types.StringType:
    729              value = value.decode('latin-1')
    730          ofile.write(saxutils.escape(value).encode('utf-8')+"\n")
    731          ofile.write("</property>\n")
    732
    733
    734  InitializeClass(ZenModelBase)
    735