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

# Source Code for Module Products.ZenModel.ZenModelBase

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