Mercurial > p > roundup > code
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 |
