Code

Sorry about this huge checkin! It's fixing a lot of related stuff in one go
authorrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Wed, 29 May 2002 01:16:17 +0000 (01:16 +0000)
committerrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Wed, 29 May 2002 01:16:17 +0000 (01:16 +0000)
though.

. #541941 ] changing multilink properties by mail
. #526730 ] search for messages capability
. #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

git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@766 57a73879-2fb5-44c3-a270-3262357dd7e2

17 files changed:
CHANGES.txt
MANIFEST.in
MIGRATION.txt
doc/installation.txt
doc/user_guide.txt
roundup/cgi_client.py
roundup/mailgw.py
roundup/roundupdb.py
roundup/templates/classic/detectors/nosyreaction.py
roundup/templates/classic/detectors/statusauditor.py [new file with mode: 0644]
roundup/templates/extended/detectors/__init__.py
roundup/templates/extended/detectors/nosyreaction.py
roundup/templates/extended/detectors/statusauditor.py [new file with mode: 0644]
run_tests
setup.py
test/__init__.py
test/test_mailgw.py

index ec62d75cef6a1d634dd4f36e3507c6d2a237d456..e781a45b3b08d68dc124e1db7b217791a6a974fb 100644 (file)
@@ -32,7 +32,11 @@ Feature:
  . applied patch #558876 ] cgi client customization
  . split instance initialisation into two steps, allowing config changes
    before the database is initialised.
+ . don't create an empty message on email issue creation if the email is empty
+ . #541941 ] changing multilink properties by mail
  . #526730 ] search for messages capability
+ . #505180 ] split MailGW.handle_Message
+   - also changed cgi client since it was duplicating the functionality
 
 Fixed:
  . stop sending blank (whitespace-only) notes
@@ -58,6 +62,7 @@ Fixed:
  . cleaned out the template stylesheets, removing a bunch of junk that really
    wasn't necessary (font specs, styles never used) and added a style for 
    message content
+ . build htmlbase if tests are run using CVS checkout
 
 2002-03-25 - 0.4.1
 Feature:
index 05ca55e06658b4a191f46873cec3aebda68a3fe3..0fc4e8f4f211837c205656eade00342496325421 100644 (file)
@@ -5,5 +5,5 @@ recursive-include test *.py *.txt
 recursive-include doc *.html *.png *.txt
 exclude doc/announcement.txt
 include roundup-*
-include *.txt
+include run_tests *.txt
 
index 7730c360e46b72e95a5322541805f397ed4cfba4..ca793cd598b3bc45d57c79337c416d44b9736fbb 100644 (file)
@@ -9,18 +9,7 @@ This file contains information for users upgrading from:
   0.3.x -> 0.4.x
   0.2.x -> 0.3.x
 
-From CVS
-========
-
-Files storage
--------------
-
-Messages and files from newly created issues will be put into subdierectories
-in thousands e.g. msg123 will be put into files/msg/0/msg123, file2003
-will go into files/file/2/file2003. Previous messages are still found, but
-could be put into this structure.
-
-Migrating from 0.4.0 to 0.4.1
+Migrating from 0.4.1 to 0.4.2
 =============================
 
 Configuration
@@ -38,9 +27,30 @@ The new configuration variables are:
 - ADD_RECIPIENTS_TO_NOSY
 
 
+Mail Gateway
+------------
+
+You will need to copy the detectors from the distribution into your instance
+home detectors directory. The schema-specific code has been removed from the
+mail gateway and made into auditors:
+
+- nosyreactor.py has now got an updatenosy auditor which updates the nosy
+  list with author, recipient and assignedto information.
+- statusauditor.py makes the unread or resolved -> chatting changes and
+  presets the status of an issue to unread.
+
+
 Migrating from 0.4.0 to 0.4.1
 =============================
 
+Files storage
+-------------
+
+Messages and files from newly created issues will be put into subdierectories
+in thousands e.g. msg123 will be put into files/msg/0/msg123, file2003
+will go into files/file/2/file2003. Previous messages are still found, but
+could be put into this structure.
+
 Configuration
 -------------
 
@@ -68,8 +78,6 @@ the appropriate variables in instance_config.py.
 The extended schema has similar variables added too - see the source for more
 info.
 
-
-
 Alternate E-Mail Addresses
 --------------------------
 
index 908cd59fb8ad2ce0d8964eaa8bc0f83411676004..e61279a3187233daedad9f183adb9af5e403a88b 100644 (file)
@@ -2,7 +2,7 @@
 Installing Roundup
 ==================
 
-:Version: $Revision: 1.10 $
+:Version: $Revision: 1.11 $
 
 .. contents::
 
@@ -69,10 +69,6 @@ Testing your Python
 Run ``"python ./run_tests"`` and make sure there
 are no errors.  If there are errors, please let us know!
 
-Note: if you're installing from the CVS, you will need to run "python setup.py
-build" before the tests will work, as there are modules that must be
-generated that are not stored in the CVS.
-
 
 Getting Roundup
 ===============
index 1c1cc3a00f4050695bf209c489a2384c3c009772..87c73739d7568fcf94cc48474add45f6fb4297f7 100644 (file)
@@ -2,7 +2,7 @@
 User Guide
 ==========
 
-:Version: $Revision: 1.3 $
+:Version: $Revision: 1.4 $
 
 .. contents::
 
@@ -222,7 +222,22 @@ Setting Properties
 The e-mail interface also provides a simple way to set properties on items. At
 the end of the subject line, propname=value pairs can be specified in square
 brackets, using the same conventions as for the roundup set shell command.
-explanatory message given in the exception.
+
+For example,
+
+- setting the priority of an issue::
+
+   Subject: Re: [issue1] the coffee machine is broken! [priority=urgent]
+
+- adding yourself to a nosy list (both of these are equivalent)::
+
+   Subject: Re: [issue2] we're out of widgets [nosy=+richard]
+   Subject: Re: [issue2] we're out of widgets [nosy=richard]
+
+- removing yourself from a nosy list::
+
+   Subject: Re: [issue2] we're out of widgets [nosy=-richard]
+
 
 Message content
 ~~~~~~~~~~~~~~~
index 8fb4139db0fef69117a94675c1dda625d705a0a5..ae013fcb2915fb00848b3000e0e5808117c55ab5 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: cgi_client.py,v 1.125 2002-05-25 07:16:24 rochecompaan Exp $
+# $Id: cgi_client.py,v 1.126 2002-05-29 01:16:17 richard Exp $
 
 __doc__ = """
 WWW request handler (also used in the stand-alone server).
@@ -604,86 +604,10 @@ function help_window(helpurl, width, height) {
     showmsg = shownode
     searchissue = searchnode
 
-    def _add_author_to_nosy(self, props):
-        ''' add the author value from the props to the nosy list
-        '''
-        if not props.has_key('author'):
-            return
-        author_id = props['author']
-        if not props.has_key('nosy'):
-            # load current nosy
-            if self.nodeid:
-                cl = self.db.classes[self.classname]
-                l = cl.get(self.nodeid, 'nosy')
-                if author_id in l:
-                    return
-                props['nosy'] = l
-            else:
-                props['nosy'] = []
-        if author_id not in props['nosy']:
-            props['nosy'].append(author_id)
-
-    def _add_assignedto_to_nosy(self, props):
-        ''' add the assignedto value from the props to the nosy list
-        '''
-        # get the properties definition and make some checks
-        if not props.has_key('assignedto'):
-            return
-        cl = self.db.classes[self.classname]
-        propdef = cl.getprops()
-        if not propdef.has_key('assignedto') or not propdef.has_key('nosy'):
-            return
-
-        # get the assignedto(s)
-        if isinstance(propdef['assignedto'], hyperdb.Link):
-            assignedto_ids = [props['assignedto']]
-        elif isinstance(propdef['assignedto'], hyperdb.Multilink):
-            assignedto_ids = props['assignedto']
-        else:
-            return
-
-        # ok, now get the nosy list value
-        if not props.has_key('nosy'):
-            # load current nosy
-            if self.nodeid:
-                props['nosy'] = cl.get(self.nodeid, 'nosy')
-            else:
-                props['nosy'] = []
-
-        # and update for assignedto id(s)
-        for assignedto_id in assignedto_ids:
-            if assignedto_id not in props['nosy']:
-                props['nosy'].append(assignedto_id)
-
     def _changenode(self, props):
         ''' change the node based on the contents of the form
         '''
         cl = self.db.classes[self.classname]
-        # set status to chatting if 'unread' or 'resolved'
-        try:
-            # determine the id of 'unread','resolved' and 'chatting'
-            unread_id = self.db.status.lookup('unread')
-            resolved_id = self.db.status.lookup('resolved')
-            chatting_id = self.db.status.lookup('chatting')
-            current_status = cl.get(self.nodeid, 'status')
-            if props.has_key('status'):
-                new_status = props['status']
-            else:
-                # apparently there's a chance that some browsers don't
-                # send status...
-                new_status = current_status
-        except KeyError:
-            pass
-        else:
-            if new_status == unread_id or (new_status == resolved_id
-                    and current_status == resolved_id):
-                props['status'] = chatting_id
-
-        self._add_assignedto_to_nosy(props)
-
-        # possibly add the author of the change to the nosy list
-        if self.db.config.ADD_AUTHOR_TO_NOSY == 'yes':
-            self._add_author_to_nosy(props)
 
         # create the message
         message, files = self._handle_message()
@@ -701,22 +625,6 @@ function help_window(helpurl, width, height) {
         cl = self.db.classes[self.classname]
         props = parsePropsFromForm(self.db, cl, self.form)
 
-        # set status to 'unread' if not specified - a status of '- no
-        # selection -' doesn't make sense
-        if not props.has_key('status') and cl.getprops().has_key('status'):
-            try:
-                unread_id = self.db.status.lookup('unread')
-            except KeyError:
-                pass
-            else:
-                props['status'] = unread_id
-
-        self._add_assignedto_to_nosy(props)
-
-        # possibly add the author of the new node to the nosy list
-        if self.db.config.ADD_AUTHOR_TO_NOSY in ('new', 'yes'):
-            self._add_author_to_nosy(props)
-
         # check for messages and files
         message, files = self._handle_message()
         if message:
@@ -1454,6 +1362,9 @@ def parsePropsFromForm(db, cl, form, nodeid=0):
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.125  2002/05/25 07:16:24  rochecompaan
+# Merged search_indexing-branch with HEAD
+#
 # Revision 1.124  2002/05/24 02:09:24  richard
 # Nothing like a live demo to show up the bugs ;)
 #
index 4c99afb0a22de18a65b7283dbb4032c2a92a10e6..53dc401d20dea4d9c4ba0613aa34621586ec9f67 100644 (file)
@@ -73,7 +73,7 @@ are calling the create() method to create a new node). If an auditor raises
 an exception, the original message is bounced back to the sender with the
 explanatory message given in the exception. 
 
-$Id: mailgw.py,v 1.73 2002-05-22 04:12:05 richard Exp $
+$Id: mailgw.py,v 1.74 2002-05-29 01:16:17 richard Exp $
 '''
 
 
@@ -344,7 +344,7 @@ Subject was: "%s"
             title = ''
 
         # but we do need either a title or a nodeid...
-        if not nodeid and not title:
+        if nodeid is None and not title:
             raise MailUsageError, '''
 I cannot match your message to a node in the database - you need to either
 supply a full node identifier (with number, eg "[issue123]" or keep the
@@ -353,105 +353,135 @@ previous subject title intact so I can match that.
 Subject was: "%s"
 '''%subject
 
-        # extract the args
-        subject_args = m.group('args')
-
         # If there's no nodeid, check to see if this is a followup and
         # maybe someone's responded to the initial mail that created an
         # entry. Try to find the matching nodes with the same title, and
         # use the _last_ one matched (since that'll _usually_ be the most
         # recent...)
-        if not nodeid and m.group('refwd'):
+        if nodeid is None and m.group('refwd'):
             l = cl.stringFind(title=title)
             if l:
                 nodeid = l[-1]
 
-        # start of the props
+        # if a nodeid was specified, make sure it's valid
+        if nodeid is not None and not cl.hasnode(nodeid):
+            raise MailUsageError, '''
+The node specified by the designator in the subject of your message ("%s")
+does not exist.
+
+Subject was: "%s"
+'''%(nodeid, subject)
+
+        #
+        # extract the args
+        #
+        subject_args = m.group('args')
+
+        #
+        # handle the subject argument list
+        #
+        # figure what the properties of this Class are
         properties = cl.getprops()
         props = {}
-
-        # handle the args
         args = m.group('args')
         if args:
+            errors = []
             for prop in string.split(args, ';'):
                 # extract the property name and value
                 try:
-                    key, value = prop.split('=')
+                    propname, value = prop.split('=')
                 except ValueError, message:
-                    raise MailUsageError, '''
-Subject argument list not of form [arg=value,value,...;arg=value,value...]
-   (specific exception message was "%s")
-
-Subject was: "%s"
-'''%(message, subject)
+                    errors.append('not of form [arg=value,'
+                        'value,...;arg=value,value...]')
+                    break
 
                 # ensure it's a valid property name
-                key = key.strip()
+                propname = propname.strip()
                 try:
-                    proptype =  properties[key]
+                    proptype =  properties[propname]
                 except KeyError:
-                    raise MailUsageError, '''
-Subject argument list refers to an invalid property: "%s"
-
-Subject was: "%s"
-'''%(key, subject)
+                    errors.append('refers to an invalid property: '
+                        '"%s"'%propname)
+                    continue
 
                 # convert the string value to a real property value
                 if isinstance(proptype, hyperdb.String):
-                    props[key] = value.strip()
+                    props[propname] = value.strip()
                 if isinstance(proptype, hyperdb.Password):
-                    props[key] = password.Password(value.strip())
+                    props[propname] = password.Password(value.strip())
                 elif isinstance(proptype, hyperdb.Date):
                     try:
-                        props[key] = date.Date(value.strip())
+                        props[propname] = date.Date(value.strip())
                     except ValueError, message:
-                        raise UsageError, '''
-Subject argument list contains an invalid date for %s.
-
-Error was: %s
-Subject was: "%s"
-'''%(key, message, subject)
+                        errors.append('contains an invalid date for '
+                            '%s.'%propname)
                 elif isinstance(proptype, hyperdb.Interval):
                     try:
-                        props[key] = date.Interval(value) # no strip needed
+                        props[propname] = date.Interval(value)
                     except ValueError, message:
-                        raise UsageError, '''
-Subject argument list contains an invalid date interval for %s.
-
-Error was: %s
-Subject was: "%s"
-'''%(key, message, subject)
+                        errors.append('contains an invalid date interval'
+                            'for %s.'%propname)
                 elif isinstance(proptype, hyperdb.Link):
                     linkcl = self.db.classes[proptype.classname]
                     propkey = linkcl.labelprop(default_to_id=1)
                     try:
-                        props[key] = linkcl.lookup(value)
+                        props[propname] = linkcl.lookup(value)
                     except KeyError, message:
-                        raise MailUsageError, '''
-Subject argument list contains an invalid value for %s.
-
-Error was: %s
-Subject was: "%s"
-'''%(key, message, subject)
+                        errors.append('"%s" is not a value for %s.'%(value,
+                            propname))
                 elif isinstance(proptype, hyperdb.Multilink):
                     # get the linked class
                     linkcl = self.db.classes[proptype.classname]
                     propkey = linkcl.labelprop(default_to_id=1)
+                    if nodeid:
+                        curvalue = cl.get(nodeid, propname)
+                    else:
+                        curvalue = []
+
+                    # handle each add/remove in turn
                     for item in value.split(','):
                         item = item.strip()
+
+                        # handle +/-
+                        remove = 0
+                        if item.startswith('-'):
+                            remove = 1
+                            item = item[1:]
+                        elif item.startswith('+'):
+                            item = item[1:]
+
+                        # look up the value
                         try:
                             item = linkcl.lookup(item)
                         except KeyError, message:
-                            raise MailUsageError, '''
-Subject argument list contains an invalid value for %s.
+                            errors.append('"%s" is not a value for %s.'%(item,
+                                propname))
+                            continue
+
+                        # perform the add/remove
+                        if remove:
+                            try:
+                                curvalue.remove(item)
+                            except ValueError:
+                                errors.append('"%s" is not currently in '
+                                    'for %s.'%(item, propname))
+                                continue
+                        else:
+                            if item not in curvalue:
+                                curvalue.append(item)
+
+                    # that's it, set the new Multilink property value
+                    props[propname] = curvalue
+
+            # handle any errors parsing the argument list
+            if errors:
+                errors = '\n- '.join(errors)
+                raise MailUsageError, '''
+There were problems handling your subject line argument list:
+- %s
 
-Error was: %s
 Subject was: "%s"
-'''%(key, message, subject)
-                        if props.has_key(key):
-                            props[key].append(item)
-                        else:
-                            props[key] = [item]
+'''%(errors, subject)
 
         #
         # handle the users
@@ -624,156 +654,48 @@ not find a text/plain part to use.
             files.append(self.db.file.create(type=mime_type, name=name,
                 content=data))
 
+        # 
+        # create the message if there's a message body (content)
         #
-        # now handle the db stuff
-        #
-        if nodeid:
-            # If an item designator (class name and id number) is found there,
-            # the newly created "msg" node is added to the "messages" property
-            # for that item, and any new "file" nodes are added to the "files" 
-            # property for the item. 
-
-            # if the message is currently 'unread' or 'resolved', then set
-            # it to 'chatting'
-            if properties.has_key('status'):
-                try:
-                    # determine the id of 'unread', 'resolved' and 'chatting'
-                    unread_id = self.db.status.lookup('unread')
-                    resolved_id = self.db.status.lookup('resolved')
-                    chatting_id = self.db.status.lookup('chatting')
-                except KeyError:
-                    pass
-                else:
-                    current_status = cl.get(nodeid, 'status')
-                    if (not props.has_key('status') and
-                            current_status == unread_id or
-                            current_status == resolved_id):
-                        props['status'] = chatting_id
-
-            # update the nosy list
-            current = {}
-            for nid in cl.get(nodeid, 'nosy'):
-                current[nid] = 1
-            self.updateNosy(cl, props, author, recipients, current)
-
-            # create the message
+        if content:
             message_id = self.db.msg.create(author=author,
                 recipients=recipients, date=date.Date('.'), summary=summary,
                 content=content, files=files, messageid=messageid,
                 inreplyto=inreplyto)
-            try:
+
+            # attach the message to the node
+            if nodeid:
+                # add the message to the node's list
                 messages = cl.get(nodeid, 'messages')
-            except IndexError:
-                raise MailUsageError, '''
-The node specified by the designator in the subject of your message ("%s")
-does not exist.
+                messages.append(message_id)
+                props['messages'] = messages
+            else:
+                # pre-load the messages list
+                props['messages'] = [message_id]
 
-Subject was: "%s"
-'''%(nodeid, subject)
-            messages.append(message_id)
-            props['messages'] = messages
+                # set the title to the subject
+                if properties.has_key('title') and not props.has_key('title'):
+                    props['title'] = title
 
-            # now apply the changes
-            try:
+        #
+        # perform the node change / create
+        #
+        try:
+            if nodeid:
                 cl.set(nodeid, **props)
-            except (TypeError, IndexError, ValueError), message:
-                raise MailUsageError, '''
-There was a problem with the message you sent:
-   %s
-'''%message
-            # commit the changes to the DB
-            self.db.commit()
-        else:
-            # If just an item class name is found there, we attempt to create a
-            # new item of that class with its "messages" property initialized to
-            # contain the new "msg" node and its "files" property initialized to
-            # contain any new "file" nodes. 
-            message_id = self.db.msg.create(author=author,
-                recipients=recipients, date=date.Date('.'), summary=summary,
-                content=content, files=files, messageid=messageid,
-                inreplyto=inreplyto)
-
-            # pre-set the issue to unread
-            if properties.has_key('status') and not props.has_key('status'):
-                try:
-                    # determine the id of 'unread'
-                    unread_id = self.db.status.lookup('unread')
-                except KeyError:
-                    pass
-                else:
-                    props['status'] = '1'
-
-            # set the title to the subject
-            if properties.has_key('title') and not props.has_key('title'):
-                props['title'] = title
-
-            # pre-load the messages list
-            props['messages'] = [message_id]
-
-            # set up (clean) the nosy list
-            self.updateNosy(cl, props, author, recipients)
-
-            # and attempt to create the new node
-            try:
+            else:
                 nodeid = cl.create(**props)
-            except (TypeError, IndexError, ValueError), message:
-                raise MailUsageError, '''
+        except (TypeError, IndexError, ValueError), message:
+            raise MailUsageError, '''
 There was a problem with the message you sent:
    %s
 '''%message
 
-            # commit the new node(s) to the DB
-            self.db.commit()
+        # commit the changes to the DB
+        self.db.commit()
 
         return nodeid
 
-    def updateNosy(self, cl, props, author, recipients, current=None):
-        '''Determine what the nosy list should be given:
-
-            props:      properties specified on the subject line of the message
-            author:     the sender of the message
-            recipients: the recipients (to, cc) of the message
-            current:    if the issue already exists, this is the current nosy
-                        list, as a dictionary.
-        '''
-        if current is None:
-            current = {}
-            ok = ('new', 'yes')
-        else:
-            ok = ('yes',)
-
-        # add nosy in arguments to issue's nosy list
-        nosy = props.get('nosy', [])
-        for value in nosy:
-            if not self.db.hasnode('user', value):
-                continue
-            if not current.has_key(value):
-                current[value] = 1
-
-        # add the author to the nosy list
-        if getattr(self.instance, 'ADD_AUTHOR_TO_NOSY', 'new') in ok:
-            if not current.has_key(author):
-                current[author] = 1
-
-        # add on the recipients of the message
-        if getattr(self.instance, 'ADD_RECIPIENTS_TO_NOSY', 'new') in ok:
-            for recipient in recipients:
-                if not current.has_key(recipient):
-                    current[recipient] = 1
-
-        # add assignedto(s) to the nosy list
-        if props.has_key('assignedto'):
-            propdef = cl.getprops()
-            if isinstance(propdef['assignedto'], hyperdb.Link):
-                assignedto_ids = [props['assignedto']]
-            elif isinstance(propdef['assignedto'], hyperdb.Multilink):
-                assignedto_ids = props['assignedto']
-            for assignedto_id in assignedto_ids:
-                if not current.has_key(assignedto_id):
-                    current[assignedto_id] = 1
-
-        props['nosy'] = current.keys()
-
 def parseContent(content, keep_citations, keep_body,
         blank_line=re.compile(r'[\r\n]+\s*[\r\n]+'),
         eol=re.compile(r'[\r\n]+'), 
@@ -843,6 +765,12 @@ def parseContent(content, keep_citations, keep_body,
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.73  2002/05/22 04:12:05  richard
+#  . applied patch #558876 ] cgi client customization
+#    ... with significant additions and modifications ;)
+#    - extended handling of ML assignedto to all places it's handled
+#    - added more NotFound info
+#
 # Revision 1.72  2002/05/22 01:24:51  richard
 # Added note to MIGRATION about new config vars. Also made us more resilient
 # for upgraders. Reinstated list header style (oops)
index f874db8cbc6336b89e84608d66201f10de4eafe6..9f1d81db9b171117a2c685180c6a41292426ef82 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: roundupdb.py,v 1.53 2002-05-25 07:16:24 rochecompaan Exp $
+# $Id: roundupdb.py,v 1.54 2002-05-29 01:16:17 richard Exp $
 
 __doc__ = """
 Extending hyperdb with types specific to issue-tracking.
@@ -115,11 +115,9 @@ class Class(hyperdb.Class):
         """
         if propvalues.has_key('creation') or propvalues.has_key('activity'):
             raise KeyError, '"creation" and "activity" are reserved'
-        for audit in self.auditors['create']:
-            audit(self.db, self, None, propvalues)
+        self.fireAuditors('create', None, propvalues)
         nodeid = hyperdb.Class.create(self, **propvalues)
-        for react in self.reactors['create']:
-            react(self.db, self, nodeid, None)
+        self.fireReactors('create', nodeid, None)
         return nodeid
 
     def set(self, nodeid, **propvalues):
@@ -128,8 +126,7 @@ class Class(hyperdb.Class):
         """
         if propvalues.has_key('creation') or propvalues.has_key('activity'):
             raise KeyError, '"creation" and "activity" are reserved'
-        for audit in self.auditors['set']:
-            audit(self.db, self, nodeid, propvalues)
+        self.fireAuditors('set', nodeid, propvalues)
         # Take a copy of the node dict so that the subsequent set
         # operation doesn't modify the oldvalues structure.
         try:
@@ -141,18 +138,15 @@ class Class(hyperdb.Class):
             # with no intervening commit()
             oldvalues = copy.deepcopy(self.db.getnode(self.classname, nodeid))
         hyperdb.Class.set(self, nodeid, **propvalues)
-        for react in self.reactors['set']:
-            react(self.db, self, nodeid, oldvalues)
+        self.fireReactors('set', nodeid, oldvalues)
 
     def retire(self, nodeid):
         """These operations trigger detectors and can be vetoed.  Attempts
         to modify the "creation" or "activity" properties cause a KeyError.
         """
-        for audit in self.auditors['retire']:
-            audit(self.db, self, nodeid, None)
+        self.fireAuditors('retire', nodeid, None)
         hyperdb.Class.retire(self, nodeid)
-        for react in self.reactors['retire']:
-            react(self.db, self, nodeid, None)
+        self.fireReactors('retire', nodeid, None)
 
     def get(self, nodeid, propname, default=_marker, cache=1):
         """Attempts to get the "creation" or "activity" properties should
@@ -208,6 +202,12 @@ class Class(hyperdb.Class):
         if detector not in l:
             self.auditors[event].append(detector)
 
+    def fireAuditors(self, action, nodeid, newvalues):
+        """Fire all registered auditors.
+        """
+        for audit in self.auditors[action]:
+            audit(self.db, self, nodeid, newvalues)
+
     def react(self, event, detector):
         """Register a detector
         """
@@ -215,6 +215,11 @@ class Class(hyperdb.Class):
         if detector not in l:
             self.reactors[event].append(detector)
 
+    def fireReactors(self, action, nodeid, oldvalues):
+        """Fire all registered reactors.
+        """
+        for react in self.reactors[action]:
+            react(self.db, self, nodeid, oldvalues)
 
 class FileClass(Class):
     def create(self, **propvalues):
@@ -297,7 +302,7 @@ class IssueClass(Class):
         appended to the "messages" field of the specified issue.
         """
 
-    def nosymessage(self, nodeid, msgid, change_note):
+    def nosymessage(self, nodeid, msgid, oldvalues):
         """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
@@ -318,9 +323,6 @@ class IssueClass(Class):
         # figure the author's id, and indicate they've received the message
         authid = messages.get(msgid, 'author')
 
-        # get the current nosy list, we'll need it
-        nosy = self.get(nodeid, 'nosy')
-
         # possibly send the message to the author, as long as they aren't
         # anonymous
         if (self.db.config.MESSAGES_TO_AUTHOR == 'yes' and
@@ -329,6 +331,7 @@ class IssueClass(Class):
         r[authid] = 1
 
         # now figure the nosy people who weren't recipients
+        nosy = self.get(nodeid, 'nosy')
         for nosyid in nosy:
             # Don't send nosy mail to the anonymous user (that user
             # shouldn't appear in the nosy list, but just in case they
@@ -341,6 +344,12 @@ class IssueClass(Class):
                 sendto.append(nosyid)
                 recipients.append(nosyid)
 
+        # generate a change note
+        if oldvalues:
+            note = self.generateChangeNote(nodeid, oldvalues)
+        else:
+            note = self.generateCreateNote(nodeid)
+
         # we have new recipients
         if sendto:
            # map userids to addresses
@@ -350,7 +359,7 @@ class IssueClass(Class):
             messages.set(msgid, recipients=recipients)
 
             # send the message
-            self.send_message(nodeid, msgid, change_note, sendto)
+            self.send_message(nodeid, msgid, note, sendto)
 
     # XXX backwards compatibility - don't remove
     sendmessage = nosymessage
@@ -625,6 +634,9 @@ class IssueClass(Class):
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.53  2002/05/25 07:16:24  rochecompaan
+# Merged search_indexing-branch with HEAD
+#
 # Revision 1.52  2002/05/15 03:27:16  richard
 #  . fixed SCRIPT_NAME in ZRoundup for instances not at top level of Zope
 #    (thanks dman)
index c5c818ebb47f671dd6e49be3edd2dba79214c02d..4490b49a3df850ffc20818c504a25ae5580e32de 100644 (file)
@@ -15,9 +15,9 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-#$Id: nosyreaction.py,v 1.11 2002-01-14 22:21:38 richard Exp $
+#$Id: nosyreaction.py,v 1.12 2002-05-29 01:16:17 richard Exp $
 
-from roundup import roundupdb
+from roundup import roundupdb, hyperdb
 
 def nosyreaction(db, cl, nodeid, oldvalues):
     ''' A standard detector is provided that watches for additions to the
@@ -34,12 +34,21 @@ def nosyreaction(db, cl, nodeid, oldvalues):
         The journal recorded by the hyperdatabase on the "recipients" property
         then provides a log of when the message was sent to whom. 
     '''
+    # send a copy of all new messages to the nosy list
+    for msgid in determineNewMessages(cl, nodeid, oldvalues):
+        try:
+            cl.nosymessage(nodeid, msgid, oldvalues)
+        except roundupdb.MessageSendError, message:
+            raise roundupdb.DetectorError, message
+
+def determineNewMessages(cl, nodeid, oldvalues):
+    ''' Figure a list of the messages that are being added to the given
+        node in this transaction.
+    '''
     messages = []
-    change_note = ''
     if oldvalues is None:
         # the action was a create, so use all the messages in the create
         messages = cl.get(nodeid, 'messages')
-       change_note = cl.generateCreateNote(nodeid)
     elif oldvalues.has_key('messages'):
         # the action was a set (so adding new messages to an existing issue)
         m = {}
@@ -50,24 +59,91 @@ def nosyreaction(db, cl, nodeid, oldvalues):
         for msgid in cl.get(nodeid, 'messages'):
             if not m.has_key(msgid):
                 messages.append(msgid)
-        if messages:
-            change_note = cl.generateChangeNote(nodeid, oldvalues)
-    if not messages:
-        return
+    return messages
 
-    # send a copy to the nosy list
-    for msgid in messages:
-        try:
-            cl.sendmessage(nodeid, msgid, change_note)
-        except roundupdb.MessageSendError, message:
-            raise roundupdb.DetectorError, message
+def updatenosy(db, cl, nodeid, newvalues):
+    '''Update the nosy list for changes to the assignedto
+    '''
+    # nodeid will be None if this is a new node
+    current = {}
+    if nodeid is None:
+        ok = ('new', 'yes')
+    else:
+        ok = ('yes',)
+        # old node, get the current values from the node if they haven't
+        # changed
+        if not newvalues.has_key('nosy'):
+            nosy = cl.get(nodeid, 'nosy')
+            for value in nosy:
+                if not current.has_key(value):
+                    current[value] = 1
+
+    # if the nosy list changed in this transaction, init from the new value
+    if newvalues.has_key('nosy'):
+        nosy = newvalues.get('nosy', [])
+        for value in nosy:
+            if not db.hasnode('user', value):
+                continue
+            if not current.has_key(value):
+                current[value] = 1
+
+    # add assignedto(s) to the nosy list
+    if newvalues.has_key('assignedto'):
+        propdef = cl.getprops()
+        if isinstance(propdef['assignedto'], hyperdb.Link):
+            assignedto_ids = [newvalues['assignedto']]
+        elif isinstance(propdef['assignedto'], hyperdb.Multilink):
+            assignedto_ids = newvalues['assignedto']
+        for assignedto_id in assignedto_ids:
+            if not current.has_key(assignedto_id):
+                current[assignedto_id] = 1
+
+    # see if there's any new messages - if so, possibly add the author and
+    # recipient to the nosy
+    if newvalues.has_key('messages'):
+        if nodeid is None:
+            ok = ('new', 'yes')
+            messages = newvalues['messages']
+        else:
+            ok = ('yes',)
+            # figure which of the messages now on the issue weren't
+            # there before - make sure we don't get a cached version!
+            oldmessages = cl.get(nodeid, 'messages', cache=0)
+            messages = []
+            for msgid in newvalues['messages']:
+                if msgid not in oldmessages:
+                    messages.append(msgid)
+
+        # configs for nosy modifications
+        add_author = getattr(db.config, 'ADD_AUTHOR_TO_NOSY', 'new')
+        add_recips = getattr(db.config, 'ADD_RECIPIENTS_TO_NOSY', 'new')
+
+        # now for each new message:
+        msg = db.msg
+        for msgid in messages:
+            if add_author in ok:
+                authid = msg.get(msgid, 'author')
+                current[authid] = 1
+
+            # add on the recipients of the message
+            if add_recips in ok:
+                for recipient in msg.get(msgid, 'recipients'):
+                    current[recipient] = 1
+
+    # that's it, save off the new nosy list
+    newvalues['nosy'] = current.keys()
 
 def init(db):
     db.issue.react('create', nosyreaction)
     db.issue.react('set', nosyreaction)
+    db.issue.audit('create', updatenosy)
+    db.issue.audit('set', updatenosy)
 
 #
 #$Log: not supported by cvs2svn $
+#Revision 1.11  2002/01/14 22:21:38  richard
+##503353 ] setting properties in initial email
+#
 #Revision 1.10  2002/01/11 23:22:29  richard
 # . #502437 ] rogue reactor and unittest
 #   in short, the nosy reactor was modifying the nosy list. That code had
diff --git a/roundup/templates/classic/detectors/statusauditor.py b/roundup/templates/classic/detectors/statusauditor.py
new file mode 100644 (file)
index 0000000..b935706
--- /dev/null
@@ -0,0 +1,68 @@
+# Copyright (c) 2002 ekit.com Inc (http://www.ekit-inc.com/)
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+#   The above copyright notice and this permission notice shall be included in
+#   all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+#$Id: statusauditor.py,v 1.1 2002-05-29 01:16:17 richard Exp $
+
+def chatty(db, cl, nodeid, newvalues):
+    ''' If the issue is currently 'unread' or 'resolved', then set
+        it to 'chatting'
+    '''
+    # don't fire if there's no new message (ie. chat)
+    if not newvalues.has_key('messages'):
+        return
+    if newvalues['messages'] == cl.get(nodeid, 'messages', cache=0):
+        return
+
+    # determine the id of 'unread', 'resolved' and 'chatting'
+    unread_id = db.status.lookup('unread')
+    resolved_id = db.status.lookup('resolved')
+    chatting_id = db.status.lookup('chatting')
+
+    # get the current value
+    current_status = cl.get(nodeid, 'status')
+
+    # see if there's an explicit change in this transaction
+    if newvalues.has_key('status') and newvalues['status'] != current_status:
+        # yep, skip
+        return
+
+    # ok, there's no explicit change, so do it manually
+    if current_status in (unread_id, resolved_id):
+        newvalues['status'] = chatting_id
+
+
+def presetunread(db, cl, nodeid, newvalues):
+    ''' Make sure the status is set on new issues
+    '''
+    if newvalues.has_key('status'):
+        return
+
+    # ok, do it
+    newvalues['status'] = db.status.lookup('unread')
+
+
+def init(db):
+    # fire before changes are made
+    db.issue.audit('set', chatty)
+    db.issue.audit('create', presetunread)
+
+#
+#$Log: not supported by cvs2svn $
+#
index 2a705cf39b12a12d7e646cbbf186ec22759f65a1..78f532fd0d7c5b36a07214c8a2d07825e4c8eb71 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-#$Id: __init__.py,v 1.3 2001-08-07 00:24:43 richard Exp $
+#$Id: __init__.py,v 1.4 2002-05-29 01:16:17 richard Exp $
 
 def init(db):
     ''' execute the init functions of all the modules in this directory
@@ -35,10 +35,16 @@ def init(db):
 
 #
 #$Log: not supported by cvs2svn $
+#Revision 1.3  2001/08/07 00:24:43  richard
+#stupid typo
+#
 #Revision 1.2  2001/08/07 00:15:51  richard
 #Added the copyright/license notice to (nearly) all files at request of
 #Bizar Software.
 #
+#Revision 1.1  2001/07/23 23:29:10  richard
+#Adding the classic template
+#
 #Revision 1.1  2001/07/23 03:50:47  anthonybaxter
 #moved templates to proper location
 #
index b3030435a491db0a27eb71acf67e13cbbc37da05..4490b49a3df850ffc20818c504a25ae5580e32de 100644 (file)
@@ -15,9 +15,9 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-#$Id: nosyreaction.py,v 1.11 2002-01-14 22:21:38 richard Exp $
+#$Id: nosyreaction.py,v 1.12 2002-05-29 01:16:17 richard Exp $
 
-from roundup import roundupdb
+from roundup import roundupdb, hyperdb
 
 def nosyreaction(db, cl, nodeid, oldvalues):
     ''' A standard detector is provided that watches for additions to the
@@ -34,12 +34,21 @@ def nosyreaction(db, cl, nodeid, oldvalues):
         The journal recorded by the hyperdatabase on the "recipients" property
         then provides a log of when the message was sent to whom. 
     '''
+    # send a copy of all new messages to the nosy list
+    for msgid in determineNewMessages(cl, nodeid, oldvalues):
+        try:
+            cl.nosymessage(nodeid, msgid, oldvalues)
+        except roundupdb.MessageSendError, message:
+            raise roundupdb.DetectorError, message
+
+def determineNewMessages(cl, nodeid, oldvalues):
+    ''' Figure a list of the messages that are being added to the given
+        node in this transaction.
+    '''
     messages = []
-    change_note = ''
     if oldvalues is None:
         # the action was a create, so use all the messages in the create
         messages = cl.get(nodeid, 'messages')
-       change_note = cl.generateCreateNote(nodeid)
     elif oldvalues.has_key('messages'):
         # the action was a set (so adding new messages to an existing issue)
         m = {}
@@ -50,25 +59,91 @@ def nosyreaction(db, cl, nodeid, oldvalues):
         for msgid in cl.get(nodeid, 'messages'):
             if not m.has_key(msgid):
                 messages.append(msgid)
-        if messages:
-            change_note = cl.generateChangeNote(nodeid, oldvalues)
-    if not messages:
-        return
+    return messages
 
-    # send a copy to the nosy list
-    for msgid in messages:
-        try:
-            cl.sendmessage(nodeid, msgid, change_note)
-        except roundupdb.MessageSendError, message:
-            raise roundupdb.DetectorError, message
+def updatenosy(db, cl, nodeid, newvalues):
+    '''Update the nosy list for changes to the assignedto
+    '''
+    # nodeid will be None if this is a new node
+    current = {}
+    if nodeid is None:
+        ok = ('new', 'yes')
+    else:
+        ok = ('yes',)
+        # old node, get the current values from the node if they haven't
+        # changed
+        if not newvalues.has_key('nosy'):
+            nosy = cl.get(nodeid, 'nosy')
+            for value in nosy:
+                if not current.has_key(value):
+                    current[value] = 1
 
+    # if the nosy list changed in this transaction, init from the new value
+    if newvalues.has_key('nosy'):
+        nosy = newvalues.get('nosy', [])
+        for value in nosy:
+            if not db.hasnode('user', value):
+                continue
+            if not current.has_key(value):
+                current[value] = 1
+
+    # add assignedto(s) to the nosy list
+    if newvalues.has_key('assignedto'):
+        propdef = cl.getprops()
+        if isinstance(propdef['assignedto'], hyperdb.Link):
+            assignedto_ids = [newvalues['assignedto']]
+        elif isinstance(propdef['assignedto'], hyperdb.Multilink):
+            assignedto_ids = newvalues['assignedto']
+        for assignedto_id in assignedto_ids:
+            if not current.has_key(assignedto_id):
+                current[assignedto_id] = 1
+
+    # see if there's any new messages - if so, possibly add the author and
+    # recipient to the nosy
+    if newvalues.has_key('messages'):
+        if nodeid is None:
+            ok = ('new', 'yes')
+            messages = newvalues['messages']
+        else:
+            ok = ('yes',)
+            # figure which of the messages now on the issue weren't
+            # there before - make sure we don't get a cached version!
+            oldmessages = cl.get(nodeid, 'messages', cache=0)
+            messages = []
+            for msgid in newvalues['messages']:
+                if msgid not in oldmessages:
+                    messages.append(msgid)
+
+        # configs for nosy modifications
+        add_author = getattr(db.config, 'ADD_AUTHOR_TO_NOSY', 'new')
+        add_recips = getattr(db.config, 'ADD_RECIPIENTS_TO_NOSY', 'new')
+
+        # now for each new message:
+        msg = db.msg
+        for msgid in messages:
+            if add_author in ok:
+                authid = msg.get(msgid, 'author')
+                current[authid] = 1
+
+            # add on the recipients of the message
+            if add_recips in ok:
+                for recipient in msg.get(msgid, 'recipients'):
+                    current[recipient] = 1
+
+    # that's it, save off the new nosy list
+    newvalues['nosy'] = current.keys()
 
 def init(db):
     db.issue.react('create', nosyreaction)
     db.issue.react('set', nosyreaction)
+    db.issue.audit('create', updatenosy)
+    db.issue.audit('set', updatenosy)
 
 #
 #$Log: not supported by cvs2svn $
+#Revision 1.11  2002/01/14 22:21:38  richard
+##503353 ] setting properties in initial email
+#
 #Revision 1.10  2002/01/11 23:22:29  richard
 # . #502437 ] rogue reactor and unittest
 #   in short, the nosy reactor was modifying the nosy list. That code had
@@ -119,6 +194,9 @@ def init(db):
 #Added the copyright/license notice to (nearly) all files at request of
 #Bizar Software.
 #
+#Revision 1.1  2001/07/23 23:29:10  richard
+#Adding the classic template
+#
 #Revision 1.1  2001/07/23 03:50:47  anthonybaxter
 #moved templates to proper location
 #
diff --git a/roundup/templates/extended/detectors/statusauditor.py b/roundup/templates/extended/detectors/statusauditor.py
new file mode 100644 (file)
index 0000000..b935706
--- /dev/null
@@ -0,0 +1,68 @@
+# Copyright (c) 2002 ekit.com Inc (http://www.ekit-inc.com/)
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+#   The above copyright notice and this permission notice shall be included in
+#   all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+#$Id: statusauditor.py,v 1.1 2002-05-29 01:16:17 richard Exp $
+
+def chatty(db, cl, nodeid, newvalues):
+    ''' If the issue is currently 'unread' or 'resolved', then set
+        it to 'chatting'
+    '''
+    # don't fire if there's no new message (ie. chat)
+    if not newvalues.has_key('messages'):
+        return
+    if newvalues['messages'] == cl.get(nodeid, 'messages', cache=0):
+        return
+
+    # determine the id of 'unread', 'resolved' and 'chatting'
+    unread_id = db.status.lookup('unread')
+    resolved_id = db.status.lookup('resolved')
+    chatting_id = db.status.lookup('chatting')
+
+    # get the current value
+    current_status = cl.get(nodeid, 'status')
+
+    # see if there's an explicit change in this transaction
+    if newvalues.has_key('status') and newvalues['status'] != current_status:
+        # yep, skip
+        return
+
+    # ok, there's no explicit change, so do it manually
+    if current_status in (unread_id, resolved_id):
+        newvalues['status'] = chatting_id
+
+
+def presetunread(db, cl, nodeid, newvalues):
+    ''' Make sure the status is set on new issues
+    '''
+    if newvalues.has_key('status'):
+        return
+
+    # ok, do it
+    newvalues['status'] = db.status.lookup('unread')
+
+
+def init(db):
+    # fire before changes are made
+    db.issue.audit('set', chatty)
+    db.issue.audit('create', presetunread)
+
+#
+#$Log: not supported by cvs2svn $
+#
index b9976d983668e546558ac49d8087b3ed2de93ff0..ee73ce7cee3b0b9b5df98d441323865785dce6ef 100755 (executable)
--- a/run_tests
+++ b/run_tests
@@ -9,7 +9,14 @@
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 #
-# $Id: run_tests,v 1.6 2002-05-25 07:24:29 rochecompaan Exp $
+# $Id: run_tests,v 1.7 2002-05-29 01:16:16 richard Exp $
+
+# make sure we have the htmlbase
+try:
+    from roundup.templates.classic import htmlbase
+except ImportError:
+    import setup
+    setup.buildTemplates()
 
 from test import go
 import sys
@@ -20,6 +27,9 @@ else:
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.6  2002/05/25 07:24:29  rochecompaan
+# oops
+#
 # Revision 1.4  2002/02/14 23:38:12  richard
 # Fixed the unit tests for the mailgw re: the x-roundup-name header.
 # Also made the test runner more user-friendly:
index ca2832c9ad08bd270d610fd2a8746c8f780148fc..7c64ffe210a57f2475b7fa46387f3a61cede159a 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -16,7 +16,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: setup.py,v 1.33 2002-04-03 05:53:03 richard Exp $
+# $Id: setup.py,v 1.34 2002-05-29 01:16:16 richard Exp $
 
 from distutils.core import setup, Extension
 from distutils.util import get_platform
@@ -112,8 +112,6 @@ def scriptname(path):
         script = script + ".bat"
     return script
 
-# build list of scripts from their implementation modules
-roundup_scripts = map(scriptname, glob('roundup/scripts/[!_]*.py'))
 
 
 #############################################################################
@@ -124,55 +122,75 @@ def isTemplateDir(dir):
     return dir[0] != '.' and dir != 'CVS' and os.path.isdir(dir) \
         and os.path.isfile(os.path.join(dir, '__init__.py'))
 
+# use that function to list all the templates
 templates = map(os.path.basename, filter(isTemplateDir,
     glob(os.path.join('roundup', 'templates', '*'))))
-packagelist = [
-    'roundup',
-    'roundup.backends',
-    'roundup.scripts',
-    'roundup.templates'
-]
-installdatafiles = [
-    ('share/roundup/cgi-bin', ['cgi-bin/roundup.cgi']),
-] 
-
-for template in templates:
-    tdir = os.path.join('roundup', 'templates', template)
-    makeHtmlBase(tdir)
-
-    # add the template package and subpackage
-    packagelist.append('roundup.templates.%s' % template)
-    packagelist.append('roundup.templates.%s.detectors' % template)
-
-    # scan for data files
-    tfiles = glob(os.path.join(tdir, 'html', '*'))
-    tfiles = filter(os.path.isfile, tfiles)
-    installdatafiles.append(
-        ('share/roundup/templates/%s/html' % template, tfiles)
-    )
-
-
-setup(
-    name = "roundup", 
-    version = "0.4.1",
-    description = "Roundup issue tracking system.",
-    author = "Richard Jones",
-    author_email = "richard@users.sourceforge.net",
-    url = 'http://sourceforge.net/projects/roundup/',
-    packages = packagelist,
 
-    # Override certain command classes with our own ones
-    cmdclass = {
-        'build_scripts': build_scripts_roundup,
-    },
-    scripts = roundup_scripts,
-
-    data_files =  installdatafiles
-)
+def buildTemplates():
+    for template in templates:
+        tdir = os.path.join('roundup', 'templates', template)
+        makeHtmlBase(tdir)
+
+if __name__ == '__main__':
+    # build list of scripts from their implementation modules
+    roundup_scripts = map(scriptname, glob('roundup/scripts/[!_]*.py'))
+
+    # template munching
+    templates = map(os.path.basename, filter(isTemplateDir,
+        glob(os.path.join('roundup', 'templates', '*'))))
+    packagelist = [
+        'roundup',
+        'roundup.backends',
+        'roundup.scripts',
+        'roundup.templates'
+    ]
+    installdatafiles = [
+        ('share/roundup/cgi-bin', ['cgi-bin/roundup.cgi']),
+    ] 
+
+    # munge the template HTML into the htmlbase module
+    buildTemplates()
+
+    # add the templates to the setup packages and data files lists
+    for template in templates:
+        tdir = os.path.join('roundup', 'templates', template)
+
+        # add the template package and subpackage
+        packagelist.append('roundup.templates.%s' % template)
+        packagelist.append('roundup.templates.%s.detectors' % template)
+
+        # scan for data files
+        tfiles = glob(os.path.join(tdir, 'html', '*'))
+        tfiles = filter(os.path.isfile, tfiles)
+        installdatafiles.append(
+            ('share/roundup/templates/%s/html' % template, tfiles)
+        )
+
+    # perform the setup action
+    setup(
+        name = "roundup", 
+        version = "0.4.1",
+        description = "Roundup issue tracking system.",
+        author = "Richard Jones",
+        author_email = "richard@users.sourceforge.net",
+        url = 'http://sourceforge.net/projects/roundup/',
+        packages = packagelist,
+
+        # Override certain command classes with our own ones
+        cmdclass = {
+            'build_scripts': build_scripts_roundup,
+        },
+        scripts = roundup_scripts,
+
+        data_files =  installdatafiles
+    )
 
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.33  2002/04/03 05:53:03  richard
+# Didn't get around to committing these after the last release.
+#
 # Revision 1.32  2002/03/27 23:47:58  jhermann
 # Fix for scripts running under CMD.EXE
 #
index 749ba6777c10323966c23165d9e22aaf38406b1a..92d9f6fcf44f22c73f96c20755393e5ea821e5fc 100644 (file)
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: __init__.py,v 1.16 2002-02-14 23:38:12 richard Exp $
+# $Id: __init__.py,v 1.17 2002-05-29 01:16:17 richard Exp $
 
 import os, tempfile, unittest, shutil
-os.environ['SENDMAILDEBUG'] = tempfile.mktemp()
+import roundup.roundupdb
+roundup.roundupdb.SENDMAILDEBUG=os.environ['SENDMAILDEBUG']=tempfile.mktemp()
 
 # figure all the modules available
 dir = os.path.split(__file__)[0]
@@ -39,6 +40,13 @@ def go(tests=all_tests):
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.16  2002/02/14 23:38:12  richard
+# Fixed the unit tests for the mailgw re: the x-roundup-name header.
+# Also made the test runner more user-friendly:
+#   ./run_tests            - detect all tests in test/test_<name>.py and run them
+#   ./run_tests <name>     - run only test/test_<name>.py
+# eg ./run_tests mailgw    - run the mailgw test from test/test_mailgw.py
+#
 # Revision 1.15  2002/01/22 00:12:20  richard
 # oops
 #
index 5431488765fb7de430c863ecbb43a3d9045a1522..93a1f8fb5b955affa47a0ed2c928b764fa0defb6 100644 (file)
@@ -8,7 +8,7 @@
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 #
-# $Id: test_mailgw.py,v 1.19 2002-05-23 04:26:05 richard Exp $
+# $Id: test_mailgw.py,v 1.20 2002-05-29 01:16:17 richard Exp $
 
 import unittest, cStringIO, tempfile, os, shutil, errno, imp, sys, difflib
 
@@ -86,7 +86,7 @@ class MailgwTestCase(unittest.TestCase, DiffHelper):
         except OSError, error:
             if error.errno not in (errno.ENOENT, errno.ESRCH): raise
 
-    def testNewIssue(self):
+    def doNewIssue(self):
         message = cStringIO.StringIO('''Content-Type: text/plain;
   charset="iso-8859-1"
 From: Chef <chef@bork.bork.bork
@@ -106,6 +106,9 @@ This is a test submission of a new issue.
         l.sort()
         self.assertEqual(l, ['2', '3'])
 
+    def testNewIssue(self):
+        self.doNewIssue()
+
     def testNewIssueNosy(self):
         self.instance.ADD_AUTHOR_TO_NOSY = 'yes'
         message = cStringIO.StringIO('''Content-Type: text/plain;
@@ -218,28 +221,27 @@ _________________________________________________________________________
 
     # BUG should test some binary attamchent too.
 
-    def testFollowup(self):
-        self.testNewIssue()
+    def testSimpleFollowup(self):
+        self.doNewIssue()
         message = cStringIO.StringIO('''Content-Type: text/plain;
   charset="iso-8859-1"
-From: richard <richard@test>
+From: mary <mary@test>
 To: issue_tracker@your.tracker.email.domain.example
 Message-Id: <followup_dummy_id>
 In-Reply-To: <dummy_test_message_id>
-Subject: [issue1] Testing... [assignedto=mary; nosy=john]
+Subject: [issue1] Testing...
 
-This is a followup
+This is a second followup
 ''')
         handler = self.instance.MailGW(self.instance, self.db)
         handler.main(message)
-
         self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
 '''FROM: roundup-admin@your.tracker.email.domain.example
-TO: chef@bork.bork.bork, john@test, mary@test
+TO: chef@bork.bork.bork, richard@test
 Content-Type: text/plain
 Subject: [issue1] Testing...
-To: chef@bork.bork.bork, john@test, mary@test
-From: richard <issue_tracker@your.tracker.email.domain.example>
+To: chef@bork.bork.bork, richard@test
+From: mary <issue_tracker@your.tracker.email.domain.example>
 Reply-To: Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
 MIME-Version: 1.0
 Message-Id: <followup_dummy_id>
@@ -248,14 +250,12 @@ X-Roundup-Name: Roundup issue tracker
 Content-Transfer-Encoding: quoted-printable
 
 
-richard <richard@test> added the comment:
+mary <mary@test> added the comment:
 
-This is a followup
+This is a second followup
 
 
 ----------
-assignedto:  -> mary
-nosy: +mary, john
 status: unread -> chatting
 _________________________________________________________________________
 "Roundup issue tracker" <issue_tracker@your.tracker.email.domain.example>
@@ -263,27 +263,32 @@ http://your.tracker.url.example/issue1
 _________________________________________________________________________
 ''')
 
-    def testFollowup2(self):
-        self.testNewIssue()
+    def testFollowup(self):
+        self.doNewIssue()
+
         message = cStringIO.StringIO('''Content-Type: text/plain;
   charset="iso-8859-1"
-From: mary <mary@test>
+From: richard <richard@test>
 To: issue_tracker@your.tracker.email.domain.example
 Message-Id: <followup_dummy_id>
 In-Reply-To: <dummy_test_message_id>
-Subject: [issue1] Testing...
+Subject: [issue1] Testing... [assignedto=mary; nosy=john]
 
-This is a second followup
+This is a followup
 ''')
         handler = self.instance.MailGW(self.instance, self.db)
         handler.main(message)
+        l = self.db.issue.get('1', 'nosy')
+        l.sort()
+        self.assertEqual(l, ['2', '3', '4', '5'])
+
         self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
 '''FROM: roundup-admin@your.tracker.email.domain.example
-TO: chef@bork.bork.bork, richard@test
+TO: chef@bork.bork.bork, john@test, mary@test
 Content-Type: text/plain
 Subject: [issue1] Testing...
-To: chef@bork.bork.bork, richard@test
-From: mary <issue_tracker@your.tracker.email.domain.example>
+To: chef@bork.bork.bork, john@test, mary@test
+From: richard <issue_tracker@your.tracker.email.domain.example>
 Reply-To: Roundup issue tracker <issue_tracker@your.tracker.email.domain.example>
 MIME-Version: 1.0
 Message-Id: <followup_dummy_id>
@@ -292,12 +297,14 @@ X-Roundup-Name: Roundup issue tracker
 Content-Transfer-Encoding: quoted-printable
 
 
-mary <mary@test> added the comment:
+richard <richard@test> added the comment:
 
-This is a second followup
+This is a followup
 
 
 ----------
+assignedto:  -> mary
+nosy: +mary, john
 status: unread -> chatting
 _________________________________________________________________________
 "Roundup issue tracker" <issue_tracker@your.tracker.email.domain.example>
@@ -306,7 +313,7 @@ _________________________________________________________________________
 ''')
 
     def testFollowupTitleMatch(self):
-        self.testNewIssue()
+        self.doNewIssue()
         message = cStringIO.StringIO('''Content-Type: text/plain;
   charset="iso-8859-1"
 From: richard <richard@test>
@@ -351,8 +358,8 @@ _________________________________________________________________________
 ''')
 
     def testFollowupNosyAuthor(self):
-        self.testNewIssue()
-        self.instance.ADD_AUTHOR_TO_NOSY = 'yes'
+        self.doNewIssue()
+        self.db.config.ADD_AUTHOR_TO_NOSY = self.instance.ADD_AUTHOR_TO_NOSY = 'yes'
         message = cStringIO.StringIO('''Content-Type: text/plain;
   charset="iso-8859-1"
 From: john@test
@@ -397,8 +404,8 @@ _________________________________________________________________________
 ''')
 
     def testFollowupNosyRecipients(self):
-        self.testNewIssue()
-        self.instance.ADD_RECIPIENTS_TO_NOSY = 'yes'
+        self.doNewIssue()
+        self.db.config.ADD_RECIPIENTS_TO_NOSY = self.instance.ADD_RECIPIENTS_TO_NOSY = 'yes'
         message = cStringIO.StringIO('''Content-Type: text/plain;
   charset="iso-8859-1"
 From: richard@test
@@ -444,8 +451,8 @@ _________________________________________________________________________
 ''')
 
     def testFollowupNosyAuthorAndCopy(self):
-        self.testNewIssue()
-        self.instance.ADD_AUTHOR_TO_NOSY = 'yes'
+        self.doNewIssue()
+        self.db.config.ADD_AUTHOR_TO_NOSY = self.instance.ADD_AUTHOR_TO_NOSY = 'yes'
         self.db.config.MESSAGES_TO_AUTHOR = 'yes'
         message = cStringIO.StringIO('''Content-Type: text/plain;
   charset="iso-8859-1"
@@ -491,7 +498,7 @@ _________________________________________________________________________
 ''')
 
     def testFollowupNoNosyAuthor(self):
-        self.testNewIssue()
+        self.doNewIssue()
         self.instance.ADD_AUTHOR_TO_NOSY = 'no'
         message = cStringIO.StringIO('''Content-Type: text/plain;
   charset="iso-8859-1"
@@ -536,7 +543,7 @@ _________________________________________________________________________
 ''')
 
     def testFollowupNoNosyRecipients(self):
-        self.testNewIssue()
+        self.doNewIssue()
         self.instance.ADD_RECIPIENTS_TO_NOSY = 'no'
         message = cStringIO.StringIO('''Content-Type: text/plain;
   charset="iso-8859-1"
@@ -581,8 +588,29 @@ _________________________________________________________________________
 
 ''')
 
+    def testNosyRemove(self):
+        self.doNewIssue()
+
+        message = cStringIO.StringIO('''Content-Type: text/plain;
+  charset="iso-8859-1"
+From: richard <richard@test>
+To: issue_tracker@your.tracker.email.domain.example
+Message-Id: <followup_dummy_id>
+In-Reply-To: <dummy_test_message_id>
+Subject: [issue1] Testing... [nosy=-richard]
+
+''')
+        handler = self.instance.MailGW(self.instance, self.db)
+        handler.main(message)
+        l = self.db.issue.get('1', 'nosy')
+        l.sort()
+        self.assertEqual(l, ['2'])
+
+        # NO NOSY MESSAGE SHOULD BE SENT!
+        self.assert_(not os.path.exists(os.environ['SENDMAILDEBUG']))
+
     def testEnc01(self):
-        self.testNewIssue()
+        self.doNewIssue()
         message = cStringIO.StringIO('''Content-Type: text/plain;
   charset="iso-8859-1"
 From: mary <mary@test>
@@ -628,7 +656,7 @@ _________________________________________________________________________
 
 
     def testMultipartEnc01(self):
-        self.testNewIssue()
+        self.doNewIssue()
         message = cStringIO.StringIO('''Content-Type: text/plain;
   charset="iso-8859-1"
 From: mary <mary@test>
@@ -691,6 +719,9 @@ def suite():
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.19  2002/05/23 04:26:05  richard
+# 'I must run unit tests before committing\n' * 100
+#
 # Revision 1.18  2002/05/15 03:27:16  richard
 #  . fixed SCRIPT_NAME in ZRoundup for instances not at top level of Zope
 #    (thanks dman)