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