1
2
3
4
5
6
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
47 iscustprop = re.compile("^c[A-Z]").search
48
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
58
59 security = ClassSecurityInfo()
60
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
72
73
74 security.declareProtected(ZEN_VIEW, 'view')
76 '''
77 Returns the default view even if index_html is overridden.
78
79 @permission: ZEN_VIEW
80 '''
81 return self()
82
83
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
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
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
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
187
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
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
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
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
303
304 return ZenModelBase.breadCrumbs(self, terminator, isOrganizer)
305
306
307 security.declareProtected(ZEN_COMMON, 'checkRemotePerm')
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')
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
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')
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')
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
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
444 """
445 DEPRECATED Split a path on its '/'.
446 """
447 return zenpathsplit(path)
448
449
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
460 """
461 DEPRECATED this doesn't seem to be used anywere don't use it!!!
462 """
463 return getHierarchyObj(root, name, relpath)
464
465
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
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
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
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
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
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
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
551
552
553 security.declareProtected('Delete objects', 'manage_deleteObjects')
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
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
576 """
577 Return custom property definitions.
578
579 @rtype: [{'id':'cName','label':'Name', 'type':'string'},]
580 """
581 return self.zenPropertyMap(pfilt=iscustprop)
582
583
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')
606
607
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
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')
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
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')
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
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
711 zope.interface.implements(IZenDocProvider)
712 zope.component.adapts(ZenModelBase)
713
715 self._underlyingObject = zenModelBase
716
718 zendoc = self._underlyingObject._zendoc
719 if not zendoc and self._underlyingObject.aqBaseHasAttr( 'description' ):
720 zendoc = self._underlyingObject.description
721 return zendoc
722
724 self._underlyingObject._zendoc = zendocText
725
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