Skip to content

Commit 03edfdf

Browse files
author
Richard Jones
committed
Fixes for the metakit backend
(removed the cut-n-paste IssueClass, removed a special case for it in testing)
1 parent fe02c5a commit 03edfdf

File tree

2 files changed

+5
-359
lines changed

2 files changed

+5
-359
lines changed

roundup/backends/back_metakit.py

Lines changed: 1 addition & 345 deletions
Original file line numberDiff line numberDiff line change
@@ -931,7 +931,7 @@ def index(self, nodeid):
931931
self.get(nodeid, 'content'), mimetype)
932932

933933
# Yuck - c&p to avoid getting hyperdb.Class
934-
class IssueClass(Class):
934+
class IssueClass(Class, roundupdb.IssueClass):
935935

936936
# Overridden methods:
937937

@@ -952,347 +952,3 @@ def __init__(self, db, classname, **properties):
952952
properties['superseder'] = hyperdb.Multilink(classname)
953953
Class.__init__(self, db, classname, **properties)
954954

955-
# New methods:
956-
957-
def addmessage(self, nodeid, summary, text):
958-
"""Add a message to an issue's mail spool.
959-
960-
A new "msg" node is constructed using the current date, the user that
961-
owns the database connection as the author, and the specified summary
962-
text.
963-
964-
The "files" and "recipients" fields are left empty.
965-
966-
The given text is saved as the body of the message and the node is
967-
appended to the "messages" field of the specified issue.
968-
"""
969-
970-
def nosymessage(self, nodeid, msgid, oldvalues):
971-
"""Send a message to the members of an issue's nosy list.
972-
973-
The message is sent only to users on the nosy list who are not
974-
already on the "recipients" list for the message.
975-
976-
These users are then added to the message's "recipients" list.
977-
"""
978-
users = self.db.user
979-
messages = self.db.msg
980-
981-
# figure the recipient ids
982-
sendto = []
983-
r = {}
984-
recipients = messages.get(msgid, 'recipients')
985-
for recipid in messages.get(msgid, 'recipients'):
986-
r[recipid] = 1
987-
988-
# figure the author's id, and indicate they've received the message
989-
authid = messages.get(msgid, 'author')
990-
991-
# possibly send the message to the author, as long as they aren't
992-
# anonymous
993-
if (self.db.config.MESSAGES_TO_AUTHOR == 'yes' and
994-
users.get(authid, 'username') != 'anonymous'):
995-
sendto.append(authid)
996-
r[authid] = 1
997-
998-
# now figure the nosy people who weren't recipients
999-
nosy = self.get(nodeid, 'nosy')
1000-
for nosyid in nosy:
1001-
# Don't send nosy mail to the anonymous user (that user
1002-
# shouldn't appear in the nosy list, but just in case they
1003-
# do...)
1004-
if users.get(nosyid, 'username') == 'anonymous':
1005-
continue
1006-
# make sure they haven't seen the message already
1007-
if not r.has_key(nosyid):
1008-
# send it to them
1009-
sendto.append(nosyid)
1010-
recipients.append(nosyid)
1011-
1012-
# generate a change note
1013-
if oldvalues:
1014-
note = self.generateChangeNote(nodeid, oldvalues)
1015-
else:
1016-
note = self.generateCreateNote(nodeid)
1017-
1018-
# we have new recipients
1019-
if sendto:
1020-
# map userids to addresses
1021-
sendto = [users.get(i, 'address') for i in sendto]
1022-
1023-
# update the message's recipients list
1024-
messages.set(msgid, recipients=recipients)
1025-
1026-
# send the message
1027-
self.send_message(nodeid, msgid, note, sendto)
1028-
1029-
# XXX backwards compatibility - don't remove
1030-
sendmessage = nosymessage
1031-
1032-
def send_message(self, nodeid, msgid, note, sendto):
1033-
'''Actually send the nominated message from this node to the sendto
1034-
recipients, with the note appended.
1035-
'''
1036-
users = self.db.user
1037-
messages = self.db.msg
1038-
files = self.db.file
1039-
1040-
# determine the messageid and inreplyto of the message
1041-
inreplyto = messages.get(msgid, 'inreplyto')
1042-
messageid = messages.get(msgid, 'messageid')
1043-
1044-
# make up a messageid if there isn't one (web edit)
1045-
if not messageid:
1046-
# this is an old message that didn't get a messageid, so
1047-
# create one
1048-
messageid = "<%s.%s.%s%s@%s>"%(time.time(), random.random(),
1049-
self.classname, nodeid, self.db.config.MAIL_DOMAIN)
1050-
messages.set(msgid, messageid=messageid)
1051-
1052-
# send an email to the people who missed out
1053-
cn = self.classname
1054-
title = self.get(nodeid, 'title') or '%s message copy'%cn
1055-
# figure author information
1056-
authid = messages.get(msgid, 'author')
1057-
authname = users.get(authid, 'realname')
1058-
if not authname:
1059-
authname = users.get(authid, 'username')
1060-
authaddr = users.get(authid, 'address')
1061-
if authaddr:
1062-
authaddr = ' <%s>'%authaddr
1063-
else:
1064-
authaddr = ''
1065-
1066-
# make the message body
1067-
m = ['']
1068-
1069-
# put in roundup's signature
1070-
if self.db.config.EMAIL_SIGNATURE_POSITION == 'top':
1071-
m.append(self.email_signature(nodeid, msgid))
1072-
1073-
# add author information
1074-
if len(self.get(nodeid,'messages')) == 1:
1075-
m.append("New submission from %s%s:"%(authname, authaddr))
1076-
else:
1077-
m.append("%s%s added the comment:"%(authname, authaddr))
1078-
m.append('')
1079-
1080-
# add the content
1081-
m.append(messages.get(msgid, 'content'))
1082-
1083-
# add the change note
1084-
if note:
1085-
m.append(note)
1086-
1087-
# put in roundup's signature
1088-
if self.db.config.EMAIL_SIGNATURE_POSITION == 'bottom':
1089-
m.append(self.email_signature(nodeid, msgid))
1090-
1091-
# encode the content as quoted-printable
1092-
content = cStringIO.StringIO('\n'.join(m))
1093-
content_encoded = cStringIO.StringIO()
1094-
quopri.encode(content, content_encoded, 0)
1095-
content_encoded = content_encoded.getvalue()
1096-
1097-
# get the files for this message
1098-
message_files = messages.get(msgid, 'files')
1099-
1100-
# make sure the To line is always the same (for testing mostly)
1101-
sendto.sort()
1102-
1103-
# create the message
1104-
message = cStringIO.StringIO()
1105-
writer = MimeWriter.MimeWriter(message)
1106-
writer.addheader('Subject', '[%s%s] %s'%(cn, nodeid, title))
1107-
writer.addheader('To', ', '.join(sendto))
1108-
writer.addheader('From', '%s <%s>'%(authname,
1109-
self.db.config.ISSUE_TRACKER_EMAIL))
1110-
writer.addheader('Reply-To', '%s <%s>'%(self.db.config.INSTANCE_NAME,
1111-
self.db.config.ISSUE_TRACKER_EMAIL))
1112-
writer.addheader('MIME-Version', '1.0')
1113-
if messageid:
1114-
writer.addheader('Message-Id', messageid)
1115-
if inreplyto:
1116-
writer.addheader('In-Reply-To', inreplyto)
1117-
1118-
# add a uniquely Roundup header to help filtering
1119-
writer.addheader('X-Roundup-Name', self.db.config.INSTANCE_NAME)
1120-
1121-
# attach files
1122-
if message_files:
1123-
part = writer.startmultipartbody('mixed')
1124-
part = writer.nextpart()
1125-
part.addheader('Content-Transfer-Encoding', 'quoted-printable')
1126-
body = part.startbody('text/plain')
1127-
body.write(content_encoded)
1128-
for fileid in message_files:
1129-
name = files.get(fileid, 'name')
1130-
mime_type = files.get(fileid, 'type')
1131-
content = files.get(fileid, 'content')
1132-
part = writer.nextpart()
1133-
if mime_type == 'text/plain':
1134-
part.addheader('Content-Disposition',
1135-
'attachment;\n filename="%s"'%name)
1136-
part.addheader('Content-Transfer-Encoding', '7bit')
1137-
body = part.startbody('text/plain')
1138-
body.write(content)
1139-
else:
1140-
# some other type, so encode it
1141-
if not mime_type:
1142-
# this should have been done when the file was saved
1143-
mime_type = mimetypes.guess_type(name)[0]
1144-
if mime_type is None:
1145-
mime_type = 'application/octet-stream'
1146-
part.addheader('Content-Disposition',
1147-
'attachment;\n filename="%s"'%name)
1148-
part.addheader('Content-Transfer-Encoding', 'base64')
1149-
body = part.startbody(mime_type)
1150-
body.write(base64.encodestring(content))
1151-
writer.lastpart()
1152-
else:
1153-
writer.addheader('Content-Transfer-Encoding', 'quoted-printable')
1154-
body = writer.startbody('text/plain')
1155-
body.write(content_encoded)
1156-
1157-
# now try to send the message
1158-
if SENDMAILDEBUG:
1159-
open(SENDMAILDEBUG, 'w').write('FROM: %s\nTO: %s\n%s\n'%(
1160-
self.db.config.ADMIN_EMAIL,
1161-
', '.join(sendto),message.getvalue()))
1162-
else:
1163-
try:
1164-
# send the message as admin so bounces are sent there
1165-
# instead of to roundup
1166-
smtp = smtplib.SMTP(self.db.config.MAILHOST)
1167-
smtp.sendmail(self.db.config.ADMIN_EMAIL, sendto,
1168-
message.getvalue())
1169-
except socket.error, value:
1170-
raise MessageSendError, \
1171-
"Couldn't send confirmation email: mailhost %s"%value
1172-
except smtplib.SMTPException, value:
1173-
raise MessageSendError, \
1174-
"Couldn't send confirmation email: %s"%value
1175-
1176-
def email_signature(self, nodeid, msgid):
1177-
''' Add a signature to the e-mail with some useful information
1178-
'''
1179-
web = self.db.config.ISSUE_TRACKER_WEB + 'issue'+ nodeid
1180-
email = '"%s" <%s>'%(self.db.config.INSTANCE_NAME,
1181-
self.db.config.ISSUE_TRACKER_EMAIL)
1182-
line = '_' * max(len(web), len(email))
1183-
return '%s\n%s\n%s\n%s'%(line, email, web, line)
1184-
1185-
def generateCreateNote(self, nodeid):
1186-
"""Generate a create note that lists initial property values
1187-
"""
1188-
cn = self.classname
1189-
cl = self.db.classes[cn]
1190-
props = cl.getprops(protected=0)
1191-
1192-
# list the values
1193-
m = []
1194-
l = props.items()
1195-
l.sort()
1196-
for propname, prop in l:
1197-
value = cl.get(nodeid, propname, None)
1198-
# skip boring entries
1199-
if not value:
1200-
continue
1201-
if isinstance(prop, hyperdb.Link):
1202-
link = self.db.classes[prop.classname]
1203-
if value:
1204-
key = link.labelprop(default_to_id=1)
1205-
if key:
1206-
value = link.get(value, key)
1207-
else:
1208-
value = ''
1209-
elif isinstance(prop, hyperdb.Multilink):
1210-
if value is None: value = []
1211-
l = []
1212-
link = self.db.classes[prop.classname]
1213-
key = link.labelprop(default_to_id=1)
1214-
if key:
1215-
value = [link.get(entry, key) for entry in value]
1216-
value.sort()
1217-
value = ', '.join(value)
1218-
m.append('%s: %s'%(propname, value))
1219-
m.insert(0, '----------')
1220-
m.insert(0, '')
1221-
return '\n'.join(m)
1222-
1223-
def generateChangeNote(self, nodeid, oldvalues):
1224-
"""Generate a change note that lists property changes
1225-
"""
1226-
cn = self.classname
1227-
cl = self.db.classes[cn]
1228-
changed = {}
1229-
props = cl.getprops(protected=0)
1230-
1231-
# determine what changed
1232-
for key in oldvalues.keys():
1233-
if key in ['files','messages']: continue
1234-
new_value = cl.get(nodeid, key)
1235-
# the old value might be non existent
1236-
try:
1237-
old_value = oldvalues[key]
1238-
if type(new_value) is type([]):
1239-
new_value.sort()
1240-
old_value.sort()
1241-
if new_value != old_value:
1242-
changed[key] = old_value
1243-
except:
1244-
changed[key] = new_value
1245-
1246-
# list the changes
1247-
m = []
1248-
l = changed.items()
1249-
l.sort()
1250-
for propname, oldvalue in l:
1251-
prop = props[propname]
1252-
value = cl.get(nodeid, propname, None)
1253-
if isinstance(prop, hyperdb.Link):
1254-
link = self.db.classes[prop.classname]
1255-
key = link.labelprop(default_to_id=1)
1256-
if key:
1257-
if value:
1258-
value = link.get(value, key)
1259-
else:
1260-
value = ''
1261-
if oldvalue:
1262-
oldvalue = link.get(oldvalue, key)
1263-
else:
1264-
oldvalue = ''
1265-
change = '%s -> %s'%(oldvalue, value)
1266-
elif isinstance(prop, hyperdb.Multilink):
1267-
change = ''
1268-
if value is None: value = []
1269-
if oldvalue is None: oldvalue = []
1270-
l = []
1271-
link = self.db.classes[prop.classname]
1272-
key = link.labelprop(default_to_id=1)
1273-
# check for additions
1274-
for entry in value:
1275-
if entry in oldvalue: continue
1276-
if key:
1277-
l.append(link.get(entry, key))
1278-
else:
1279-
l.append(entry)
1280-
if l:
1281-
change = '+%s'%(', '.join(l))
1282-
l = []
1283-
# check for removals
1284-
for entry in oldvalue:
1285-
if entry in value: continue
1286-
if key:
1287-
l.append(link.get(entry, key))
1288-
else:
1289-
l.append(entry)
1290-
if l:
1291-
change += ' -%s'%(', '.join(l))
1292-
else:
1293-
change = '%s -> %s'%(oldvalue, value)
1294-
m.append('%s: %s'%(propname, change))
1295-
if m:
1296-
m.insert(0, '----------')
1297-
m.insert(0, '')
1298-
return '\n'.join(m)

test/test_db.py

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
# $Id: test_db.py,v 1.27 2002-07-14 02:05:54 richard Exp $
18+
# $Id: test_db.py,v 1.28 2002-07-14 02:16:29 richard Exp $
1919

2020
import unittest, os, shutil
2121

@@ -457,19 +457,6 @@ def testTransactions(self):
457457
self.db.file.create(name="test", type="text/plain", content="hi")
458458
self.db.rollback()
459459

460-
def testNewProperty(self):
461-
' make sure a new property is added ok '
462-
self.db.issue.create(title="spam", status='1')
463-
self.db.issue.addprop(fixer=Link("user"))
464-
props = self.db.issue.getprops()
465-
keys = props.keys()
466-
keys.sort()
467-
# metakit _base_ Class has the activity, creation and creator too
468-
self.assertEqual(keys, ['activity', 'creation', 'creator',
469-
'deadline', 'files', 'fixer', 'foo', 'id', 'nosy', 'status',
470-
'title'])
471-
self.assertEqual(self.db.issue.get('1', "fixer"), None)
472-
473460
class metakitReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
474461
def setUp(self):
475462
from roundup.backends import metakit
@@ -517,6 +504,9 @@ def suite():
517504

518505
#
519506
# $Log: not supported by cvs2svn $
507+
# Revision 1.27 2002/07/14 02:05:54 richard
508+
# . all storage-specific code (ie. backend) is now implemented by the backends
509+
#
520510
# Revision 1.26 2002/07/11 01:11:03 richard
521511
# Added metakit backend to the db tests and fixed the more easily fixable test
522512
# failures.

0 commit comments

Comments
 (0)