@@ -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\n TO: %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 )
0 commit comments