[Python-checkins] r61858 - in tracker/instances/setuptools: detectors/config.ini.template detectors/sendmail.py extensions/timestamp.py extensions/timezone.py html/user.item.html html/user.register.html
martin.v.loewis
python-checkins at python.org
Mon Mar 24 22:53:15 CET 2008
Author: martin.v.loewis
Date: Mon Mar 24 22:53:14 2008
New Revision: 61858
Added:
tracker/instances/setuptools/detectors/config.ini.template (contents, props changed)
tracker/instances/setuptools/detectors/sendmail.py (contents, props changed)
tracker/instances/setuptools/extensions/timestamp.py (contents, props changed)
tracker/instances/setuptools/extensions/timezone.py (contents, props changed)
Modified:
tracker/instances/setuptools/html/user.item.html
tracker/instances/setuptools/html/user.register.html
Log:
Add our standard extensions and detectors.
Added: tracker/instances/setuptools/detectors/config.ini.template
==============================================================================
--- (empty file)
+++ tracker/instances/setuptools/detectors/config.ini.template Mon Mar 24 22:53:14 2008
@@ -0,0 +1,17 @@
+#This configuration file controls the behavior of busybody.py and tellteam.py
+#The two definitions can be comma-delimited lists of email addresses.
+#Be sure these addresses will accept mail from the tracker's email address.
+[main]
+triage_email = triage at example.com
+busybody_email= busybody at example.com
+
+# URI to XMLRPC server doing the actual spam check.
+spambayes_uri = http://www.webfast.com:80/sbrpc
+# These must match the {ham,spam}_cutoff setting in the SpamBayes server
+# config.
+spambayes_ham_cutoff = 0.2
+spambayes_spam_cutoff = 0.85
+
+spambayes_may_view_spam = User,Coordinator,Developer
+spambayes_may_classify = Coordinator
+spambayes_may_report_misclassified = User,Coordinator,Developer
Added: tracker/instances/setuptools/detectors/sendmail.py
==============================================================================
--- (empty file)
+++ tracker/instances/setuptools/detectors/sendmail.py Mon Mar 24 22:53:14 2008
@@ -0,0 +1,190 @@
+from roundup import roundupdb
+
+def determineNewMessages(cl, nodeid, oldvalues):
+ ''' Figure a list of the messages that are being added to the given
+ node in this transaction.
+ '''
+ messages = []
+ if oldvalues is None:
+ # the action was a create, so use all the messages in the create
+ messages = cl.get(nodeid, 'messages')
+ elif oldvalues.has_key('messages'):
+ # the action was a set (so adding new messages to an existing issue)
+ m = {}
+ for msgid in oldvalues['messages']:
+ m[msgid] = 1
+ messages = []
+ # figure which of the messages now on the issue weren't there before
+ for msgid in cl.get(nodeid, 'messages'):
+ if not m.has_key(msgid):
+ messages.append(msgid)
+ return messages
+
+
+def is_spam(db, msgid):
+ """Return true if message has a spambayes score above
+ db.config.detectors['SPAMBAYES_SPAM_CUTOFF']. Also return true if
+ msgid is None, which happens when there are no messages (i.e., a
+ property-only change)"""
+ if not msgid:
+ return False
+ cutoff_score = float(db.config.detectors['SPAMBAYES_SPAM_CUTOFF'])
+
+ msg = db.getnode("msg", msgid)
+ if msg.has_key('spambayes_score') and \
+ msg['spambayes_score'] > cutoff_score:
+ return True
+ return False
+
+
+def sendmail(db, cl, nodeid, oldvalues):
+ """Send mail to various recipients, when changes occur:
+
+ * For all changes (property-only, or with new message), send mail
+ to all e-mail addresses defined in
+ db.config.detectors['BUSYBODY_EMAIL']
+
+ * For all changes (property-only, or with new message), send mail
+ to all members of the nosy list.
+
+ * For new issues, and only for new issue, send mail to
+ db.config.detectors['TRIAGE_EMAIL']
+
+ """
+
+ sendto = []
+
+ # The busybody addresses always get mail.
+ try:
+ sendto += db.config.detectors['BUSYBODY_EMAIL'].split(",")
+ except KeyError:
+ pass
+
+ # New submission?
+ if None == oldvalues:
+ changenote = cl.generateCreateNote(nodeid)
+ try:
+ # Add triage addresses
+ sendto += db.config.detectors['TRIAGE_EMAIL'].split(",")
+ except KeyError:
+ pass
+ oldfiles = []
+ else:
+ changenote = cl.generateChangeNote(nodeid, oldvalues)
+ oldfiles = oldvalues.get('files', [])
+
+ newfiles = db.issue.get(nodeid, 'files', [])
+ if oldfiles != newfiles:
+ added = [fid for fid in newfiles if fid not in oldfiles]
+ removed = [fid for fid in oldfiles if fid not in newfiles]
+ filemsg = ""
+
+ for fid in added:
+ url = db.config.TRACKER_WEB + "file%s/%s" % \
+ (fid, db.file.get(fid, "name"))
+ changenote+="\nAdded file: %s" % url
+ for fid in removed:
+ url = db.config.TRACKER_WEB + "file%s/%s" % \
+ (fid, db.file.get(fid, "name"))
+ changenote+="\nRemoved file: %s" % url
+
+
+ authid = db.getuid()
+
+ new_messages = determineNewMessages(cl, nodeid, oldvalues)
+
+ # Make sure we send a nosy mail even for property-only
+ # changes.
+ if not new_messages:
+ new_messages = [None]
+
+ for msgid in [msgid for msgid in new_messages if not is_spam(db, msgid)]:
+ try:
+ cl.send_message(nodeid, msgid, changenote, sendto,
+ authid=authid)
+ nosymessage(db, nodeid, msgid, oldvalues, changenote)
+ except roundupdb.MessageSendError, message:
+ raise roundupdb.DetectorError, message
+
+def nosymessage(db, nodeid, msgid, oldvalues, note,
+ whichnosy='nosy',
+ from_address=None, cc=[], bcc=[]):
+ """Send a message to the members of an issue's nosy list.
+
+ The message is sent only to users on the nosy list who are not
+ already on the "recipients" list for the message.
+
+ These users are then added to the message's "recipients" list.
+
+ If 'msgid' is None, the message gets sent only to the nosy
+ list, and it's called a 'System Message'.
+
+ The "cc" argument indicates additional recipients to send the
+ message to that may not be specified in the message's recipients
+ list.
+
+ The "bcc" argument also indicates additional recipients to send the
+ message to that may not be specified in the message's recipients
+ list. These recipients will not be included in the To: or Cc:
+ address lists.
+ """
+ if msgid:
+ authid = db.msg.get(msgid, 'author')
+ recipients = db.msg.get(msgid, 'recipients', [])
+ else:
+ # "system message"
+ authid = None
+ recipients = []
+
+ sendto = []
+ bcc_sendto = []
+ seen_message = {}
+ for recipient in recipients:
+ seen_message[recipient] = 1
+
+ def add_recipient(userid, to):
+ # make sure they have an address
+ address = db.user.get(userid, 'address')
+ if address:
+ to.append(address)
+ recipients.append(userid)
+
+ def good_recipient(userid):
+ # Make sure we don't send mail to either the anonymous
+ # user or a user who has already seen the message.
+ return (userid and
+ (db.user.get(userid, 'username') != 'anonymous') and
+ not seen_message.has_key(userid))
+
+ # possibly send the message to the author, as long as they aren't
+ # anonymous
+ if (good_recipient(authid) and
+ (db.config.MESSAGES_TO_AUTHOR == 'yes' or
+ (db.config.MESSAGES_TO_AUTHOR == 'new' and not oldvalues))):
+ add_recipient(authid, sendto)
+
+ if authid:
+ seen_message[authid] = 1
+
+ # now deal with the nosy and cc people who weren't recipients.
+ for userid in cc + db.issue.get(nodeid, whichnosy):
+ if good_recipient(userid):
+ add_recipient(userid, sendto)
+
+ # now deal with bcc people.
+ for userid in bcc:
+ if good_recipient(userid):
+ add_recipient(userid, bcc_sendto)
+
+ # If we have new recipients, update the message's recipients
+ # and send the mail.
+ if sendto or bcc_sendto:
+ if msgid is not None:
+ db.msg.set(msgid, recipients=recipients)
+ db.issue.send_message(nodeid, msgid, note, sendto, from_address,
+ bcc_sendto)
+
+
+def init(db):
+ db.issue.react('set', sendmail)
+ db.issue.react('create', sendmail)
Added: tracker/instances/setuptools/extensions/timestamp.py
==============================================================================
--- (empty file)
+++ tracker/instances/setuptools/extensions/timestamp.py Mon Mar 24 22:53:14 2008
@@ -0,0 +1,28 @@
+import time, struct, base64
+from roundup.cgi.actions import RegisterAction
+from roundup.cgi.exceptions import *
+
+def timestamp():
+ return base64.encodestring(struct.pack("i", time.time())).strip()
+
+def unpack_timestamp(s):
+ return struct.unpack("i",base64.decodestring(s))[0]
+
+class Timestamped:
+ def check(self):
+ try:
+ created = unpack_timestamp(self.form['opaque'].value)
+ except KeyError:
+ raise FormError, "somebody tampered with the form"
+ if time.time() - created < 4:
+ raise FormError, "responding to the form too quickly"
+ return True
+
+class TimestampedRegister(Timestamped, RegisterAction):
+ def permission(self):
+ self.check()
+ RegisterAction.permission(self)
+
+def init(instance):
+ instance.registerUtil('timestamp', timestamp)
+ instance.registerAction('register', TimestampedRegister)
Added: tracker/instances/setuptools/extensions/timezone.py
==============================================================================
--- (empty file)
+++ tracker/instances/setuptools/extensions/timezone.py Mon Mar 24 22:53:14 2008
@@ -0,0 +1,37 @@
+# Utility for replacing the simple input field for the timezone with
+# a select-field that lists the available values.
+
+import cgi
+
+try:
+ import pytz
+except ImportError:
+ pytz = None
+
+
+def tzfield(prop, name, default):
+ if pytz:
+ value = prop.plain()
+ if '' == value:
+ value = default
+ else:
+ try:
+ value = "Etc/GMT%+d" % int(value)
+ except ValueError:
+ pass
+
+ l = ['<select name="%s"' % name]
+ for zone in pytz.all_timezones:
+ s = ' '
+ if zone == value:
+ s = 'selected=selected '
+ z = cgi.escape(zone)
+ l.append('<option %svalue="%s">%s</option>' % (s, z, z))
+ l.append('</select>')
+ return '\n'.join(l)
+
+ else:
+ return prop.field()
+
+def init(instance):
+ instance.registerUtil('tzfield', tzfield)
Modified: tracker/instances/setuptools/html/user.item.html
==============================================================================
--- tracker/instances/setuptools/html/user.item.html (original)
+++ tracker/instances/setuptools/html/user.item.html Mon Mar 24 22:53:14 2008
@@ -104,10 +104,8 @@
<tr tal:condition="python:edit_ok or context.timezone"
tal:define="name string:timezone; label string:Timezone; value context/timezone">
<th metal:use-macro="th_label">Timezone</th>
- <td><input name="timezone" metal:use-macro="normal_input">
- <tal:block tal:condition="edit_ok" i18n:translate="">(this is a numeric hour offset, the default is
- <span tal:replace="db/config/DEFAULT_TIMEZONE" i18n:name="zone"
- />)</tal:block>
+ <td><input tal:replace="structure python:
+ utils.tzfield(context.timezone, 'timezone', db.config.DEFAULT_TIMEZONE)"/>
</td>
</tr>
Modified: tracker/instances/setuptools/html/user.register.html
==============================================================================
--- tracker/instances/setuptools/html/user.register.html (original)
+++ tracker/instances/setuptools/html/user.register.html Mon Mar 24 22:53:14 2008
@@ -11,7 +11,7 @@
<form method="POST" onSubmit="return submit_once()"
enctype="multipart/form-data"
tal:attributes="action context/designator">
-
+<input type="hidden" name="opaque" tal:attributes="value python: utils.timestamp()" />
<table class="form">
<tr>
<th i18n:translate="">Name</th>
More information about the Python-checkins
mailing list