comparison roundup/mailgw.py @ 753:938edfdeac6e

Sorry about this huge checkin! It's fixing a lot of related stuff in one go though. . [SF#541941] changing multilink properties by mail . [SF#526730] search for messages capability . [SF#505180] split MailGW.handle_Message - also changed cgi client since it was duplicating the functionality . build htmlbase if tests are run using CVS checkout (removed note from installation.txt) . don't create an empty message on email issue creation if the email is empty
author Richard Jones <richard@users.sourceforge.net>
date Wed, 29 May 2002 01:16:17 +0000
parents b0d887310f7a
children 34eacaa7e313 c06dc334d81d
comparison
equal deleted inserted replaced
752:a721f4e7ebbc 753:938edfdeac6e
71 set() method to add the message to the item's spool; in the second case we 71 set() method to add the message to the item's spool; in the second case we
72 are calling the create() method to create a new node). If an auditor raises 72 are calling the create() method to create a new node). If an auditor raises
73 an exception, the original message is bounced back to the sender with the 73 an exception, the original message is bounced back to the sender with the
74 explanatory message given in the exception. 74 explanatory message given in the exception.
75 75
76 $Id: mailgw.py,v 1.73 2002-05-22 04:12:05 richard Exp $ 76 $Id: mailgw.py,v 1.74 2002-05-29 01:16:17 richard Exp $
77 ''' 77 '''
78 78
79 79
80 import string, re, os, mimetools, cStringIO, smtplib, socket, binascii, quopri 80 import string, re, os, mimetools, cStringIO, smtplib, socket, binascii, quopri
81 import time, random 81 import time, random
342 title = title.strip() 342 title = title.strip()
343 else: 343 else:
344 title = '' 344 title = ''
345 345
346 # but we do need either a title or a nodeid... 346 # but we do need either a title or a nodeid...
347 if not nodeid and not title: 347 if nodeid is None and not title:
348 raise MailUsageError, ''' 348 raise MailUsageError, '''
349 I cannot match your message to a node in the database - you need to either 349 I cannot match your message to a node in the database - you need to either
350 supply a full node identifier (with number, eg "[issue123]" or keep the 350 supply a full node identifier (with number, eg "[issue123]" or keep the
351 previous subject title intact so I can match that. 351 previous subject title intact so I can match that.
352 352
353 Subject was: "%s" 353 Subject was: "%s"
354 '''%subject 354 '''%subject
355
356 # extract the args
357 subject_args = m.group('args')
358 355
359 # If there's no nodeid, check to see if this is a followup and 356 # If there's no nodeid, check to see if this is a followup and
360 # maybe someone's responded to the initial mail that created an 357 # maybe someone's responded to the initial mail that created an
361 # entry. Try to find the matching nodes with the same title, and 358 # entry. Try to find the matching nodes with the same title, and
362 # use the _last_ one matched (since that'll _usually_ be the most 359 # use the _last_ one matched (since that'll _usually_ be the most
363 # recent...) 360 # recent...)
364 if not nodeid and m.group('refwd'): 361 if nodeid is None and m.group('refwd'):
365 l = cl.stringFind(title=title) 362 l = cl.stringFind(title=title)
366 if l: 363 if l:
367 nodeid = l[-1] 364 nodeid = l[-1]
368 365
369 # start of the props 366 # if a nodeid was specified, make sure it's valid
367 if nodeid is not None and not cl.hasnode(nodeid):
368 raise MailUsageError, '''
369 The node specified by the designator in the subject of your message ("%s")
370 does not exist.
371
372 Subject was: "%s"
373 '''%(nodeid, subject)
374
375 #
376 # extract the args
377 #
378 subject_args = m.group('args')
379
380 #
381 # handle the subject argument list
382 #
383 # figure what the properties of this Class are
370 properties = cl.getprops() 384 properties = cl.getprops()
371 props = {} 385 props = {}
372
373 # handle the args
374 args = m.group('args') 386 args = m.group('args')
375 if args: 387 if args:
388 errors = []
376 for prop in string.split(args, ';'): 389 for prop in string.split(args, ';'):
377 # extract the property name and value 390 # extract the property name and value
378 try: 391 try:
379 key, value = prop.split('=') 392 propname, value = prop.split('=')
380 except ValueError, message: 393 except ValueError, message:
381 raise MailUsageError, ''' 394 errors.append('not of form [arg=value,'
382 Subject argument list not of form [arg=value,value,...;arg=value,value...] 395 'value,...;arg=value,value...]')
383 (specific exception message was "%s") 396 break
384
385 Subject was: "%s"
386 '''%(message, subject)
387 397
388 # ensure it's a valid property name 398 # ensure it's a valid property name
389 key = key.strip() 399 propname = propname.strip()
390 try: 400 try:
391 proptype = properties[key] 401 proptype = properties[propname]
392 except KeyError: 402 except KeyError:
393 raise MailUsageError, ''' 403 errors.append('refers to an invalid property: '
394 Subject argument list refers to an invalid property: "%s" 404 '"%s"'%propname)
395 405 continue
396 Subject was: "%s"
397 '''%(key, subject)
398 406
399 # convert the string value to a real property value 407 # convert the string value to a real property value
400 if isinstance(proptype, hyperdb.String): 408 if isinstance(proptype, hyperdb.String):
401 props[key] = value.strip() 409 props[propname] = value.strip()
402 if isinstance(proptype, hyperdb.Password): 410 if isinstance(proptype, hyperdb.Password):
403 props[key] = password.Password(value.strip()) 411 props[propname] = password.Password(value.strip())
404 elif isinstance(proptype, hyperdb.Date): 412 elif isinstance(proptype, hyperdb.Date):
405 try: 413 try:
406 props[key] = date.Date(value.strip()) 414 props[propname] = date.Date(value.strip())
407 except ValueError, message: 415 except ValueError, message:
408 raise UsageError, ''' 416 errors.append('contains an invalid date for '
409 Subject argument list contains an invalid date for %s. 417 '%s.'%propname)
410
411 Error was: %s
412 Subject was: "%s"
413 '''%(key, message, subject)
414 elif isinstance(proptype, hyperdb.Interval): 418 elif isinstance(proptype, hyperdb.Interval):
415 try: 419 try:
416 props[key] = date.Interval(value) # no strip needed 420 props[propname] = date.Interval(value)
417 except ValueError, message: 421 except ValueError, message:
418 raise UsageError, ''' 422 errors.append('contains an invalid date interval'
419 Subject argument list contains an invalid date interval for %s. 423 'for %s.'%propname)
420
421 Error was: %s
422 Subject was: "%s"
423 '''%(key, message, subject)
424 elif isinstance(proptype, hyperdb.Link): 424 elif isinstance(proptype, hyperdb.Link):
425 linkcl = self.db.classes[proptype.classname] 425 linkcl = self.db.classes[proptype.classname]
426 propkey = linkcl.labelprop(default_to_id=1) 426 propkey = linkcl.labelprop(default_to_id=1)
427 try: 427 try:
428 props[key] = linkcl.lookup(value) 428 props[propname] = linkcl.lookup(value)
429 except KeyError, message: 429 except KeyError, message:
430 raise MailUsageError, ''' 430 errors.append('"%s" is not a value for %s.'%(value,
431 Subject argument list contains an invalid value for %s. 431 propname))
432
433 Error was: %s
434 Subject was: "%s"
435 '''%(key, message, subject)
436 elif isinstance(proptype, hyperdb.Multilink): 432 elif isinstance(proptype, hyperdb.Multilink):
437 # get the linked class 433 # get the linked class
438 linkcl = self.db.classes[proptype.classname] 434 linkcl = self.db.classes[proptype.classname]
439 propkey = linkcl.labelprop(default_to_id=1) 435 propkey = linkcl.labelprop(default_to_id=1)
436 if nodeid:
437 curvalue = cl.get(nodeid, propname)
438 else:
439 curvalue = []
440
441 # handle each add/remove in turn
440 for item in value.split(','): 442 for item in value.split(','):
441 item = item.strip() 443 item = item.strip()
444
445 # handle +/-
446 remove = 0
447 if item.startswith('-'):
448 remove = 1
449 item = item[1:]
450 elif item.startswith('+'):
451 item = item[1:]
452
453 # look up the value
442 try: 454 try:
443 item = linkcl.lookup(item) 455 item = linkcl.lookup(item)
444 except KeyError, message: 456 except KeyError, message:
445 raise MailUsageError, ''' 457 errors.append('"%s" is not a value for %s.'%(item,
446 Subject argument list contains an invalid value for %s. 458 propname))
447 459 continue
448 Error was: %s 460
461 # perform the add/remove
462 if remove:
463 try:
464 curvalue.remove(item)
465 except ValueError:
466 errors.append('"%s" is not currently in '
467 'for %s.'%(item, propname))
468 continue
469 else:
470 if item not in curvalue:
471 curvalue.append(item)
472
473 # that's it, set the new Multilink property value
474 props[propname] = curvalue
475
476 # handle any errors parsing the argument list
477 if errors:
478 errors = '\n- '.join(errors)
479 raise MailUsageError, '''
480 There were problems handling your subject line argument list:
481 - %s
482
449 Subject was: "%s" 483 Subject was: "%s"
450 '''%(key, message, subject) 484 '''%(errors, subject)
451 if props.has_key(key):
452 props[key].append(item)
453 else:
454 props[key] = [item]
455 485
456 # 486 #
457 # handle the users 487 # handle the users
458 # 488 #
459 489
622 if not name: 652 if not name:
623 name = "unnamed" 653 name = "unnamed"
624 files.append(self.db.file.create(type=mime_type, name=name, 654 files.append(self.db.file.create(type=mime_type, name=name,
625 content=data)) 655 content=data))
626 656
627 # 657 #
628 # now handle the db stuff 658 # create the message if there's a message body (content)
629 # 659 #
630 if nodeid: 660 if content:
631 # If an item designator (class name and id number) is found there,
632 # the newly created "msg" node is added to the "messages" property
633 # for that item, and any new "file" nodes are added to the "files"
634 # property for the item.
635
636 # if the message is currently 'unread' or 'resolved', then set
637 # it to 'chatting'
638 if properties.has_key('status'):
639 try:
640 # determine the id of 'unread', 'resolved' and 'chatting'
641 unread_id = self.db.status.lookup('unread')
642 resolved_id = self.db.status.lookup('resolved')
643 chatting_id = self.db.status.lookup('chatting')
644 except KeyError:
645 pass
646 else:
647 current_status = cl.get(nodeid, 'status')
648 if (not props.has_key('status') and
649 current_status == unread_id or
650 current_status == resolved_id):
651 props['status'] = chatting_id
652
653 # update the nosy list
654 current = {}
655 for nid in cl.get(nodeid, 'nosy'):
656 current[nid] = 1
657 self.updateNosy(cl, props, author, recipients, current)
658
659 # create the message
660 message_id = self.db.msg.create(author=author, 661 message_id = self.db.msg.create(author=author,
661 recipients=recipients, date=date.Date('.'), summary=summary, 662 recipients=recipients, date=date.Date('.'), summary=summary,
662 content=content, files=files, messageid=messageid, 663 content=content, files=files, messageid=messageid,
663 inreplyto=inreplyto) 664 inreplyto=inreplyto)
664 try: 665
666 # attach the message to the node
667 if nodeid:
668 # add the message to the node's list
665 messages = cl.get(nodeid, 'messages') 669 messages = cl.get(nodeid, 'messages')
666 except IndexError: 670 messages.append(message_id)
667 raise MailUsageError, ''' 671 props['messages'] = messages
668 The node specified by the designator in the subject of your message ("%s") 672 else:
669 does not exist. 673 # pre-load the messages list
670 674 props['messages'] = [message_id]
671 Subject was: "%s" 675
672 '''%(nodeid, subject) 676 # set the title to the subject
673 messages.append(message_id) 677 if properties.has_key('title') and not props.has_key('title'):
674 props['messages'] = messages 678 props['title'] = title
675 679
676 # now apply the changes 680 #
677 try: 681 # perform the node change / create
682 #
683 try:
684 if nodeid:
678 cl.set(nodeid, **props) 685 cl.set(nodeid, **props)
679 except (TypeError, IndexError, ValueError), message: 686 else:
680 raise MailUsageError, ''' 687 nodeid = cl.create(**props)
688 except (TypeError, IndexError, ValueError), message:
689 raise MailUsageError, '''
681 There was a problem with the message you sent: 690 There was a problem with the message you sent:
682 %s 691 %s
683 '''%message 692 '''%message
684 # commit the changes to the DB 693
685 self.db.commit() 694 # commit the changes to the DB
686 else: 695 self.db.commit()
687 # If just an item class name is found there, we attempt to create a
688 # new item of that class with its "messages" property initialized to
689 # contain the new "msg" node and its "files" property initialized to
690 # contain any new "file" nodes.
691 message_id = self.db.msg.create(author=author,
692 recipients=recipients, date=date.Date('.'), summary=summary,
693 content=content, files=files, messageid=messageid,
694 inreplyto=inreplyto)
695
696 # pre-set the issue to unread
697 if properties.has_key('status') and not props.has_key('status'):
698 try:
699 # determine the id of 'unread'
700 unread_id = self.db.status.lookup('unread')
701 except KeyError:
702 pass
703 else:
704 props['status'] = '1'
705
706 # set the title to the subject
707 if properties.has_key('title') and not props.has_key('title'):
708 props['title'] = title
709
710 # pre-load the messages list
711 props['messages'] = [message_id]
712
713 # set up (clean) the nosy list
714 self.updateNosy(cl, props, author, recipients)
715
716 # and attempt to create the new node
717 try:
718 nodeid = cl.create(**props)
719 except (TypeError, IndexError, ValueError), message:
720 raise MailUsageError, '''
721 There was a problem with the message you sent:
722 %s
723 '''%message
724
725 # commit the new node(s) to the DB
726 self.db.commit()
727 696
728 return nodeid 697 return nodeid
729
730 def updateNosy(self, cl, props, author, recipients, current=None):
731 '''Determine what the nosy list should be given:
732
733 props: properties specified on the subject line of the message
734 author: the sender of the message
735 recipients: the recipients (to, cc) of the message
736 current: if the issue already exists, this is the current nosy
737 list, as a dictionary.
738 '''
739 if current is None:
740 current = {}
741 ok = ('new', 'yes')
742 else:
743 ok = ('yes',)
744
745 # add nosy in arguments to issue's nosy list
746 nosy = props.get('nosy', [])
747 for value in nosy:
748 if not self.db.hasnode('user', value):
749 continue
750 if not current.has_key(value):
751 current[value] = 1
752
753 # add the author to the nosy list
754 if getattr(self.instance, 'ADD_AUTHOR_TO_NOSY', 'new') in ok:
755 if not current.has_key(author):
756 current[author] = 1
757
758 # add on the recipients of the message
759 if getattr(self.instance, 'ADD_RECIPIENTS_TO_NOSY', 'new') in ok:
760 for recipient in recipients:
761 if not current.has_key(recipient):
762 current[recipient] = 1
763
764 # add assignedto(s) to the nosy list
765 if props.has_key('assignedto'):
766 propdef = cl.getprops()
767 if isinstance(propdef['assignedto'], hyperdb.Link):
768 assignedto_ids = [props['assignedto']]
769 elif isinstance(propdef['assignedto'], hyperdb.Multilink):
770 assignedto_ids = props['assignedto']
771 for assignedto_id in assignedto_ids:
772 if not current.has_key(assignedto_id):
773 current[assignedto_id] = 1
774
775 props['nosy'] = current.keys()
776 698
777 def parseContent(content, keep_citations, keep_body, 699 def parseContent(content, keep_citations, keep_body,
778 blank_line=re.compile(r'[\r\n]+\s*[\r\n]+'), 700 blank_line=re.compile(r'[\r\n]+\s*[\r\n]+'),
779 eol=re.compile(r'[\r\n]+'), 701 eol=re.compile(r'[\r\n]+'),
780 signature=re.compile(r'^[>|\s]*[-_]+\s*$'), 702 signature=re.compile(r'^[>|\s]*[-_]+\s*$'),
841 content = '\n\n'.join(l) 763 content = '\n\n'.join(l)
842 return summary, content 764 return summary, content
843 765
844 # 766 #
845 # $Log: not supported by cvs2svn $ 767 # $Log: not supported by cvs2svn $
768 # Revision 1.73 2002/05/22 04:12:05 richard
769 # . applied patch #558876 ] cgi client customization
770 # ... with significant additions and modifications ;)
771 # - extended handling of ML assignedto to all places it's handled
772 # - added more NotFound info
773 #
846 # Revision 1.72 2002/05/22 01:24:51 richard 774 # Revision 1.72 2002/05/22 01:24:51 richard
847 # Added note to MIGRATION about new config vars. Also made us more resilient 775 # Added note to MIGRATION about new config vars. Also made us more resilient
848 # for upgraders. Reinstated list header style (oops) 776 # for upgraders. Reinstated list header style (oops)
849 # 777 #
850 # Revision 1.71 2002/05/08 02:40:55 richard 778 # Revision 1.71 2002/05/08 02:40:55 richard

Roundup Issue Tracker: http://roundup-tracker.org/