1
2
3
4
5
6
7
8
9
10
11 __doc__="""DataRoot
12
13 DataRoot is the object manager which contains all confmon
14 data objects. It can be used as a global acquisition
15 name space.
16 """
17
18 import re
19 from zope.interface import implements
20 from AccessControl import ClassSecurityInfo
21 from AccessControl import getSecurityManager
22 from OFS.OrderedFolder import OrderedFolder
23 from Globals import DTMLFile
24 from Globals import InitializeClass
25 from Globals import DevelopmentMode
26 from Products.ZenModel.SiteError import SiteError
27 from Products.ZenModel.ZenModelBase import ZenModelBase
28 from Products.ZenModel.ZenMenuable import ZenMenuable
29 from Products.ZenRelations.RelSchema import *
30 from Products.ZenUtils.IpUtil import IpAddressError
31 from Products.ZenWidgets import messaging
32 from Products.ZenUtils.Security import activateSessionBasedAuthentication, activateCookieBasedAuthentication
33 from Commandable import Commandable
34 import socket
35 import os
36 import sys
37 import string
38 from Products.ZenMessaging.audit import audit
39 from Products.ZenUtils.Utils import zenPath, binPath
40 from Products.ZenUtils.Utils import extractPostContent
41 from Products.ZenUtils.jsonutils import json
42
43 from Products.ZenEvents.Exceptions import *
44
45 from ZenModelRM import ZenModelRM
46 from ZenossSecurity import ZEN_COMMON, ZEN_MANAGE_DMD, ZEN_VIEW
47 from interfaces import IDataRoot
56
57
58 addDataRoot = DTMLFile('dtml/addDataRoot',globals())
59
60 __pychecker__='no-override'
61
62 -class DataRoot(ZenModelRM, OrderedFolder, Commandable, ZenMenuable):
63 implements(IDataRoot)
64
65 meta_type = portal_type = 'DataRoot'
66
67 manage_main = OrderedFolder.manage_main
68
69 manage_options = OrderedFolder.manage_options
70
71
72
73 _rq = True
74 uuid = None
75 availableVersion = None
76 lastVersionCheck = 0
77 lastVersionCheckAttempt = 0
78 versionCheckOptIn = True
79 reportMetricsOptIn = True
80 acceptedTerms = True
81 instanceIdentifier = 'Zenoss'
82 smtpHost = 'localhost'
83 pageCommand = '$ZENHOME/bin/zensnpp localhost 444 $RECIPIENT'
84 smtpPort = 25
85 smtpUser = ''
86 smtpPass = ''
87 smtpUseTLS = 0
88 emailFrom = ''
89 iconMap = {}
90 geomapapikey = ''
91 geocache = ''
92 version = ""
93
94 AUTH_TYPE_SESSION = "session"
95 AUTH_TYPE_COOKIE = "cookie"
96 userAuthType = AUTH_TYPE_SESSION
97 pauseHubNotifications = False
98
99 _properties=(
100 {'id':'title', 'type': 'string', 'mode':'w'},
101 {'id':'prodStateDashboardThresh','type':'int','mode':'w'},
102 {'id':'prodStateConversions','type':'lines','mode':'w'},
103 {'id':'priorityConversions','type':'lines','mode':'w'},
104 {'id':'priorityDashboardThresh','type':'int','mode':'w'},
105 {'id':'statusConversions','type':'lines','mode':'w'},
106 {'id':'interfaceStateConversions','type':'lines','mode':'w'},
107 {'id':'administrativeRoles','type':'lines','mode':'w'},
108 {'id':'uuid', 'type': 'string', 'mode':'w'},
109 {'id':'availableVersion', 'type': 'string', 'mode':'w'},
110 {'id':'lastVersionCheck', 'type': 'long', 'mode':'w'},
111 {'id':'lastVersionCheckAttempt', 'type': 'long', 'mode':'w'},
112 {'id':'versionCheckOptIn', 'type': 'boolean', 'mode':'w'},
113 {'id':'reportMetricsOptIn', 'type': 'boolean', 'mode':'w'},
114 {'id':'instanceIdentifier', 'type': 'string', 'mode':'w'},
115 {'id':'smtpHost', 'type': 'string', 'mode':'w'},
116 {'id':'smtpPort', 'type': 'int', 'mode':'w'},
117 {'id':'pageCommand', 'type': 'string', 'mode':'w'},
118 {'id':'smtpUser', 'type': 'string', 'mode':'w'},
119 {'id':'smtpPass', 'type': 'string', 'mode':'w'},
120 {'id':'smtpUseTLS', 'type': 'int', 'mode':'w'},
121 {'id':'emailFrom', 'type': 'string', 'mode':'w'},
122 {'id':'geomapapikey', 'type': 'string', 'mode':'w'},
123 {'id':'userAuthType', 'type': 'string', 'mode':'w'},
124 {'id':'geocache', 'type': 'string', 'mode':'w'},
125 {'id':'pauseHubNotifications', 'type': 'boolean', 'mode':'w'},
126 )
127
128 _relations = (
129 ('userCommands', ToManyCont(ToOne, 'Products.ZenModel.UserCommand', 'commandable')),
130
131
132
133 ('packs', ToManyCont(ToOne, 'Products.ZenModel.ZenPack', 'root')),
134 ('zenMenus', ToManyCont(
135 ToOne, 'Products.ZenModel.ZenMenu', 'menuable')),
136 )
137
138
139 factory_type_information = (
140 {
141 'id' : 'DataRoot',
142 'meta_type' : 'DataRoot',
143 'description' : """Arbitrary device grouping class""",
144 'icon' : 'DataRoot_icon.gif',
145 'product' : 'ZenModel',
146 'factory' : 'manage_addStatusMonitorconf',
147 'immediate_view' : 'Dashboard',
148 'actions' :
149 (
150 { 'id' : 'settings'
151 , 'name' : 'Settings'
152 , 'action' : 'editSettings'
153 , 'permissions' : ( "Manage DMD", )
154 },
155 { 'id' : 'manage'
156 , 'name' : 'Commands'
157 , 'action' : 'dataRootManage'
158 , 'permissions' : ('Manage DMD',)
159 },
160 { 'id' : 'users'
161 , 'name' : 'Users'
162 , 'action' : 'ZenUsers/manageUserFolder'
163 , 'permissions' : ( 'Manage DMD', )
164 },
165 { 'id' : 'packs'
166 , 'name' : 'ZenPacks'
167 , 'action' : 'ZenPackManager/viewZenPacks'
168 , 'permissions' : ( "Manage DMD", )
169 },
170 { 'id' : 'portlets'
171 , 'name' : 'Portlets'
172 , 'action' : 'editPortletPerms'
173 , 'permissions' : ( "Manage DMD", )
174 },
175 { 'id' : 'daemons'
176 , 'name' : 'Daemons'
177 , 'action' : '../About/zenossInfo'
178 , 'permissions' : ( "Manage DMD", )
179 },
180 { 'id' : 'versions'
181 , 'name' : 'Versions'
182 , 'action' : '../About/zenossVersions'
183 , 'permissions' : ( "Manage DMD", )
184 },
185 { 'id' : 'backups'
186 , 'name' : 'Backups'
187 , 'action' : 'backupInfo'
188 , 'permissions' : ( "Manage DMD", )
189 },
190 { 'id' : 'eventConfig'
191 , 'name' : 'Events'
192 , 'action' : 'eventConfig'
193 , 'permissions' : ( "Manage DMD", )
194 },
195 { 'id' : 'userInterfaceConfig'
196 , 'name' : 'User Interface'
197 , 'action' : 'userInterfaceConfig'
198 , 'permissions' : ( "Manage DMD", )
199 },
200 )
201 },
202 )
203
204 security = ClassSecurityInfo()
205
206
207 prodStateDashboardThresh = 1000
208
209
210 priorityDashboardThresh = 2
211
212 prodStateConversions = [
213 'Production:1000',
214 'Pre-Production:500',
215 'Test:400',
216 'Maintenance:300',
217 'Decommissioned:-1',
218 ]
219
220 priorityConversions = [
221 'Highest:5',
222 'High:4',
223 'Normal:3',
224 'Low:2',
225 'Lowest:1',
226 'Trivial:0',
227 ]
228
229 statusConversions = [
230 'Up:0',
231 'None:-1',
232 'No DNS:-2',
233 ]
234
235 interfaceStateConversions = [
236 'up:1',
237 'down:2',
238 'testing:3',
239 'unknown:4',
240 'dormant:5',
241 'notPresent:6',
242 'lowerLayerDown:7',
243 ]
244
245 administrativeRoles = (
246 "Administrator",
247 "Analyst",
248 "Engineer",
249 "Tester",
250 )
251
252 defaultDateRange = 129600
253 performanceDateRanges = [
254 ('Hourly',129600,),
255 ('Daily',864000,),
256 ('Weekly',3628800,),
257 ('Monthly',41472000,),
258 ('Yearly',62208000,)
259 ]
260
261
262
263 zPrimaryBasePath = ("", "zport")
264
265
270
272 """
273 Override to force redirection to quickstart.
274 """
275 if not self._rq:
276 return self.unrestrictedTraverse('quickstart')()
277 return self()
278
280 """Return the current event list for this managed entity.
281 """
282 return self.ZenEventManager.getEventCount(**kwargs)
283
284 security.declareProtected(ZEN_COMMON, 'getEventClassNames')
286 """
287 Get a list of all event class names within the permission scope.
288 """
289 return self.Events.getOrganizerNames()
290
291
294
295
298
299
300 security.declareProtected(ZEN_COMMON, 'getProdStateConversions')
305
306
307 security.declareProtected(ZEN_COMMON, 'convertProdState')
313
314
315 security.declareProtected(ZEN_COMMON, 'getStatusConversions')
319
320
321 security.declareProtected(ZEN_COMMON, 'convertStatus')
325
326 security.declareProtected(ZEN_COMMON, 'getPriorityConversions')
329
330 security.declareProtected(ZEN_COMMON, 'convertPriority')
333
334 security.declareProtected(ZEN_COMMON, 'getInterfaceStateConversions')
339
340
341 security.declareProtected(ZEN_COMMON, 'convertAttribute')
343 '''convert a numeric production state to a
344 textual representation using the prodStateConversions
345 map'''
346 numbValue = int(numbValue)
347 for line in conversions:
348 line = line.rstrip()
349 (name, number) = line.split(':')
350 if int(number) == numbValue:
351 return name
352 return numbValue
353
354 security.declareProtected(ZEN_COMMON, 'convertStatusToDot')
356 colors = ['green', 'yellow', 'orange', 'red']
357 try:
358 return colors[status]
359 except IndexError:
360 return 'grey'
361
362 security.declareProtected(ZEN_COMMON, 'getConversions')
364 """get the text list of itmes that convert to ints"""
365 convs = []
366 for item in attribute:
367 tup = item.split(':')
368 try:
369 tup[1] = int(tup[1])
370 except (IndexError, ValueError):
371 continue
372 convs.append(tup)
373 return convs
374
375 security.declarePublic('filterObjectsRegex')
378 """filter a list of objects based on a regex"""
379 filter = re.compile(filter).search
380 filteredObjects = []
381 for obj in objects:
382 value = getattr(obj, filteratt, None)
383 if callable(value):
384 value = value()
385 fvalue = filter(value)
386 if (fvalue and not negatefilter) or (not fvalue and negatefilter):
387 filteredObjects.append(obj)
388 return filteredObjects
389
390
391 security.declareProtected('View', 'myUserGroups')
393 user = self.REQUEST.get('AUTHENTICATED_USER')
394 if hasattr(user, 'getGroups'):
395 return user.getGroups()
396 else:
397 return ()
398
399
400 security.declareProtected('View', 'getAllUserGroups')
402 return self.acl_users.getGroups()
403
404
405 security.declareProtected(ZEN_VIEW, 'zenoss_error_message')
408 """Return an error page that is more friendly then the standard stack
409 trace + feedback page for ConflictErrors and MySQL errors (we need to
410 add out of disk space errors). If one of these is not found we return
411 the old stacktrace page
412 """
413 from ZODB.POSException import ConflictError
414 from Products.ZenEvents.Exceptions import MySQLConnectionError
415
416 from zope.component import getUtility
417 from Products.ZenUtils.ZodbFactory import IZodbFactoryLookup
418 connectionFactory = getUtility(IZodbFactoryLookup).get()
419
420 if isinstance(error_value, ConflictError):
421 return self.zenoss_conflict_error_message()
422 elif isinstance(error_value, MySQLConnectionError) \
423 or isinstance(error_value, connectionFactory.exceptions.Error):
424 return self.zenoss_mysql_error_message(error_value=error_value)
425
426 from traceback import format_exception
427 error_formatted = ''.join(format_exception(error_type, error_value, error_traceback))
428 return self.zenoss_feedback_error_message(error_type=error_type,
429 error_value=error_value,
430 error_traceback=error_traceback,
431 error_formatted=error_formatted)
432
433
435 ''' send an email to the zenoss error email address
436 then send user to a thankyou page or an email error page.
437 '''
438 if self.smtpHost: host = self.smtpHost
439 else: host = None
440 port = self.smtpPort and self.smtpPort or 25
441 usetls = self.smtpUseTLS
442 usr = self.smtpUser
443 pwd = self.smtpPass
444
445 mailSent = SiteError.sendErrorEmail(
446 self.REQUEST.errorType,
447 self.REQUEST.errorValue,
448 self.REQUEST.errorTrace,
449 self.REQUEST.errorUrl,
450 self.About.getZenossRevision(),
451 self.About.getZenossVersionShort(),
452 self.REQUEST.contactName,
453 self.REQUEST.contactEmail,
454 self.REQUEST.comments,
455 host, port, usetls, usr, pwd)
456 if not mailSent:
457 body = SiteError.createReport(
458 self.REQUEST.errorType,
459 self.REQUEST.errorValue,
460 self.REQUEST.errorTrace,
461 self.REQUEST.errorUrl,
462 self.About.getZenossRevision(),
463 self.About.getZenossVersionShort(),
464 True,
465 self.REQUEST.contactName,
466 self.REQUEST.contactEmail,
467 self.REQUEST.comments)
468 return self.errorEmailFailure(toAddress=SiteError.ERRORS_ADDRESS,
469 body=body)
470 return self.errorEmailThankYou()
471
472
473
475 '''Write out csv rows with the given objects and fields.
476 If out is not None then call out.write() with the result and return None
477 otherwise return the result.
478 Each item in fieldsAndLabels is either a string representing a
479 field/key/index (see getDataField) or it is a tuple of (field, label)
480 where label is the string to be used in the first row as label
481 for that column.
482 Objects can be either dicts, lists/tuples or other objects. Field
483 is interpreted as a key, index or attribute depending on what
484 object is.
485 Method names can be passed instead of attribute/key/indices as field.
486 In this case the method is called and the return value is used in
487 the export.
488 '''
489 import csv
490 import StringIO
491 if out:
492 buffer = out
493 else:
494 buffer = StringIO.StringIO()
495 fields = []
496 labels = []
497 if not fieldsAndLabels:
498 fieldsAndLabels = []
499 if not objects:
500 objects = []
501 for p in fieldsAndLabels:
502 if isinstance(p, tuple):
503 fields.append(p[0])
504 labels.append(p[1])
505 else:
506 fields.append(p)
507 labels.append(p)
508 writer = csv.writer(buffer)
509 writer.writerow(labels)
510 def getDataField(thing, field):
511 if isinstance(thing, dict):
512 value = thing.get(field, '')
513 elif isinstance(thing, list) or isinstance(thing, tuple):
514 value = thing[int(field)]
515 else:
516 value = getattr(thing, field, '')
517 if isinstance(value, ZenModelBase):
518 value = value.id
519 elif callable(value):
520 value = value()
521 if value == None:
522 value = ''
523 return str(value)
524 for o in objects:
525 writer.writerow([getDataField(o,f) for f in fields])
526 if out:
527 result = None
528 else:
529 result = buffer.getvalue()
530 return result
531
532
534 ''' Called by Commandable.doCommand() to ascertain objects on which
535 a UserCommand should be executed.
536 '''
537 raise NotImplemented
538
539
542
543
545 return 'zenossuser_%s@%s' % (getSecurityManager().getUser().getId(), socket.getfqdn())
546
547
552
553
555 """Checks a valid id
556 """
557 if len(id) > 128:
558 return 'Command definition names can not be longer than 128 characters.'
559 allowed = set(string.ascii_letters + string.digits + '_')
560 attempted = set(id)
561 if not attempted.issubset(allowed):
562 return 'Only letters, digits and underscores are allowed' + \
563 ' in command definition names.'
564 return ZenModelRM.checkValidId(self, id, prep_id)
565
566
576
578 """
579 Clear the Google Maps cache.
580 """
581 self.geocache = ''
582
583 security.declareProtected(ZEN_COMMON, 'getGeoCache')
584 @json
586 cachestr = self.geocache
587 for char in ('\\r', '\\n'):
588 cachestr = cachestr.replace(char, ' ')
589 return cachestr
590
591 - def goToStatusPage(self, objid, REQUEST=None):
592 """ Find a device or network and redirect
593 to its status page.
594 """
595 import urllib
596 objid = urllib.unquote(objid)
597 try:
598 devid = objid
599 if not devid.endswith('*'): devid += '*'
600 obj = self.Devices.findDevice(devid)
601 except:
602 obj=None
603 if not obj:
604 try:
605 obj = self.Networks.getNet(objid)
606 except IpAddressError:
607 return None
608 if not obj: return None
609 if REQUEST is not None:
610 REQUEST['RESPONSE'].redirect(obj.getPrimaryUrlPath())
611
612
614 """ Get the XML representation of network nodes
615 and edges using the obj with objid as a root
616 """
617 import urllib
618 objid = urllib.unquote(objid)
619 try:
620 devid = objid
621 if not devid.endswith('*'): devid += '*'
622 obj = self.Networks.getNet(objid)
623 except: obj=None
624 if not obj:
625 obj = self.Devices.findDevice(devid)
626 if not obj:
627 return '<graph><Start name="%s"/></graph>' % objid
628 return obj.getXMLEdges(int(depth), filter,
629 start=(obj.id,obj.getPrimaryUrlPath()))
630
631
632 security.declareProtected(ZEN_MANAGE_DMD, 'getBackupFilesInfo')
634 """
635 Retrieve a list of dictionaries describing the files in
636 $ZENHOME/backups.
637 """
638 import stat
639 import datetime
640 import operator
641
642 def FmtFileSize(size):
643 for power, units in ((3, 'GB'), (2, 'MB'), (1, 'KB')):
644 if size > pow(1024, power):
645 fmt = '%.2f %s' % ((size * 1.0)/pow(1024, power), units)
646 break
647 else:
648 fmt = '%s bytes' % size
649 return fmt
650
651 backupsDir = zenPath('backups')
652 fileInfo = []
653 if os.path.isdir(backupsDir):
654 for dirPath, dirNames, fileNames in os.walk(backupsDir):
655 dirNames[:] = []
656 for fileName in fileNames:
657 filePath = os.path.join(backupsDir, fileName)
658 info = os.stat(filePath)
659 fileInfo.append({
660 'fileName': fileName,
661 'size': info[stat.ST_SIZE],
662 'sizeFormatted': FmtFileSize(info[stat.ST_SIZE]),
663 'modDate': info[stat.ST_MTIME],
664 'modDateFormatted': datetime.datetime.fromtimestamp(
665 info[stat.ST_MTIME]).strftime(
666 '%c'),
667 })
668 fileInfo.sort(key=operator.itemgetter('modDate'))
669 return fileInfo
670
671
672 security.declareProtected(ZEN_MANAGE_DMD, 'manage_createBackup')
673 - def manage_createBackup(self, includeEvents=None, includeMysqlLogin=None,
674 timeout=120, REQUEST=None, writeMethod=None):
675 """
676 Create a new backup file using zenbackup and the options specified
677 in the request.
678
679 This method makes use of the fact that DataRoot is a Commandable
680 in order to use Commandable.write
681 """
682 import popen2
683 import fcntl
684 import time
685 import select
686
687 def write(s):
688 if writeMethod:
689 writeMethod(s)
690 elif REQUEST:
691 self.write(REQUEST.RESPONSE, s)
692
693 footer = None
694 if REQUEST and not writeMethod:
695 header, footer = self.commandOutputTemplate().split('OUTPUT_TOKEN')
696 REQUEST.RESPONSE.write(str(header))
697 write('')
698 try:
699 cmd = binPath('zenbackup') + ' -v10'
700 if not includeEvents:
701 cmd += ' --no-eventsdb'
702 if not includeMysqlLogin:
703 cmd += ' --no-save-mysql-access'
704 try:
705 timeout = int(timeout)
706 except ValueError:
707 timeout = 120
708 timeout = max(timeout, 1)
709 child = popen2.Popen4(cmd)
710 flags = fcntl.fcntl(child.fromchild, fcntl.F_GETFL)
711 fcntl.fcntl(child.fromchild, fcntl.F_SETFL, flags | os.O_NDELAY)
712 endtime = time.time() + timeout
713 write('%s' % cmd)
714 write('')
715 pollPeriod = 1
716 firstPass = True
717 while time.time() < endtime and (firstPass or child.poll() == -1):
718 firstPass = False
719 r, w, e = select.select([child.fromchild], [], [], pollPeriod)
720 if r:
721 t = child.fromchild.read()
722
723
724
725 if t:
726 write(t)
727
728 if child.poll() == -1:
729 write('Backup timed out after %s seconds.' % timeout)
730 import signal
731 os.kill(child.pid, signal.SIGKILL)
732
733 write('DONE')
734 except Exception:
735 write('Exception while performing backup.')
736 write('type: %s value: %s' % tuple(sys.exc_info()[:2]))
737 else:
738 if REQUEST or writeMethod:
739 audit('UI.Backup.Create')
740 write('')
741 if REQUEST and footer:
742 REQUEST.RESPONSE.write(footer)
743
744
745 security.declareProtected(ZEN_MANAGE_DMD, 'manage_deleteBackups')
747 """
748 Delete the specified files from $ZENHOME/backups
749 """
750 backupsDir = zenPath('backups')
751 removed = []
752 if os.path.isdir(backupsDir):
753 for dirPath, dirNames, dirFileNames in os.walk(backupsDir):
754 dirNames[:] = []
755 for fileName in fileNames:
756 if fileName in dirFileNames:
757 toRemove = os.path.join(dirPath, fileName)
758 res = os.remove(toRemove)
759 if not res:
760 removed.append(toRemove)
761 if REQUEST:
762 audit('UI.Backup.Delete', files=removed)
763 messaging.IMessageSender(self).sendToBrowser(
764 'Backups Deleted',
765 '%s backup files have been deleted.' % len(removed)
766 )
767 else:
768 if REQUEST:
769 messaging.IMessageSender(self).sendToBrowser(
770 'Backup Directory Missing',
771 'Unable to find $ZENHOME/backups.',
772 messaging.WARNING
773 )
774 if REQUEST:
775 return self.callZenScreen(REQUEST)
776
777
779 """
780 Return a string that represents the Zenoss product that is installed.
781 Currently this is something like 'core' or 'enterprise'. This is
782 used in the version check code to retrieve the available version
783 for the correct product.
784 """
785 return getattr(self, 'productName', 'core')
786
787
789 """
790 Returns pretty messages when errors are raised in templates.
791
792 Access this method from a template like so:
793 <div tal:content="..."
794 ...
795 tal:on-error="structure python:here.dmd.error_handler(error)">
796
797 @param error: A TALES.ErrorInfo instance with attributes type, value
798 and traceback.
799 @return: HTML fragment with an error message
800 """
801 if error.type==MySQLConnectionError:
802 msg = "Unable to connect to the MySQL server."
803
804 elif error.type in [ pythonThresholdException, rpnThresholdException ]:
805 msg= error.value
806
807 else:
808 raise
809
810 return '<b class="errormsg">%s</b>' % msg
811
812 @json
814 """
815 Whether we're in debug mode, so that javascript will behave accordingly
816 """
817 return DevelopmentMode
818
820 """
821 Get a string representative of the code version, to override JS
822 caching.
823 """
824 return self.About.getZenossVersion().full().replace(
825 'Zenoss','').replace(' ','').replace('.','')
826
828 """update email notifications that use system-wide settings"""
829
830
831 email_props = dict(smtpHost="host",
832 smtpPort="port",
833 smtpUser="user",
834 smtpPass="password",
835 smtpUseTLS="useTls",
836 emailFrom="email_from")
837
838
839 email_props_changed = False
840 for prop in email_props:
841 if self.getProperty(prop) != REQUEST.get(prop):
842 email_props_changed = True
843 break
844
845
846
847 if email_props_changed:
848 for notif in self.NotificationSubscriptions.objectValues():
849 if notif.action == "email":
850
851 notif_uses_system_props = True
852 for prop, content_key in email_props.iteritems():
853 if prop == "emailFrom":
854 if notif.content[content_key] != self.getEmailFrom():
855 notif_uses_system_props = False
856 break
857 if prop == "smtpPort":
858 if int(notif.content[content_key]) != self.getProperty(prop):
859 notif_uses_system_props = False
860 break
861 elif notif.content[content_key] != self.getProperty(prop):
862 notif_uses_system_props = False
863 break
864
865 if notif_uses_system_props:
866 notif._p_changed = True
867 for prop, content_key in email_props.iteritems():
868 notif.content[content_key] = REQUEST.get(prop)
869 if not notif.content["email_from"]:
870 notif.content["email_from"] = self.getDefaultEmailFrom()
871
872 security.declareProtected('Manage DMD', 'zmanage_editProperties')
886
887
888 InitializeClass(DataRoot)
889