Code

Lots of little fixes in this update:
authorrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Tue, 10 Dec 2002 00:11:16 +0000 (00:11 +0000)
committerrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Tue, 10 Dec 2002 00:11:16 +0000 (00:11 +0000)
- fixed Date.local()
- email quoted text stripping is controllable again (sf bug 650742)
- extract attachment name from content-disposition if name is missing (sf
  bug 637278)
- removed FILTER_POSITION from bundled configs
- reverse message listing in issue display (reversion of recent change)
- bad entries for multilink editing in cgi don't traceback now (sf bug 640310)

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

12 files changed:
CHANGES.txt
TODO.txt
doc/customizing.txt
roundup/__init__.py
roundup/cgi/client.py
roundup/cgi/templating.py
roundup/mailgw.py
roundup/templates/classic/config.py
roundup/templates/classic/html/issue.item
roundup/templates/minimal/config.py
setup.py
test/test_mailgw.py

index 2077d29efd7ff9e09f6093262f127e0582da558f..6f7e5db8466745bb94d6bd2fea82a47382530bcd 100644 (file)
@@ -9,6 +9,13 @@ are given with the most recent entry first.
 - removed use of string/strop from TAL/TALInterpreter
 - handle KeyboardInterrupt nicely
 - fixed Date and Interval form value handling
+- fixed Date.local()
+- email quoted text stripping is controllable again (sf bug 650742)
+- extract attachment name from content-disposition if name is missing (sf
+  bug 637278)
+- removed FILTER_POSITION from bundled configs
+- reverse message listing in issue display (reversion of recent change)
+- bad entries for multilink editing in cgi don't traceback now (sf bug 640310)
 
 
 2002-11-07 0.5.2
index e37e0a63c20afa17b6592e6b0fd77e1b9b4a490e..88a53357146d0c8da328a1917e843f2377e9aa69 100644 (file)
--- a/TODO.txt
+++ b/TODO.txt
@@ -16,6 +16,8 @@ pending hyperdb   range searching of values (dates in particular).
                   [value, value, ...] implies "in"
 pending hyperdb   migrate "id" property to be Number type
 pending hyperdb   multilink sorting by length is dumb
+pending hyperdb   lastchangedby auto-property giving last user to change an
+                  item
 pending tracker   split instance.open() into open() and login()
 pending mailgw    allow commands (feature request #556996)
                   like "help", "dump issue123" (would send all info about
@@ -33,6 +35,8 @@ pending mailgw    Identification of users should have a configurable degree of
                   strictness (ie. turn off username==address matching)
 pending mailgw    Use in-reply-to for determining message lineage when subject
                   line lets us down
+pending mailgw    Allow different brackets delimiting [issueNNN] in Subject
+pending email     email sig could use a "remove me from this list"
 pending messages  Snarf the first whole sentence, or full first line of
                   messages for the summary - whichever is longer.
 pending project   switch to a Roundup instance for Roundup bug/feature tracking
@@ -56,14 +60,18 @@ pending web       allow multilink selections to select a "none" element to allow
 pending web       automagically link designators
 pending web       add checkbox-based removal/addition for multilink entries
                   (eg "add me"/"remove me" for nosy list)
-pending web       multilink item removal action (with retirement)
 pending web       search "refinement" - pre-fill the search page with the
                   current search parameters
 pending web       column-heading sort stuff isn't implemented
+pending web       multilink item removal action with retirement
+pending web       implement a python dict version of the form values
 
 active  web       UNIX init.d script for roundup-server
 bug     web       query editing isn't fully implemented
 bug     web       no testing for parsePropsFromForm
 active  web       revert to showing entire message in classic issue display
+bug     hyperdb   pysqlite and locks
+                  http://www.hwaci.com/sw/sqlite/c_interface.html
+bug     web       implement the request.url attribute?
 ======= ========= =============================================================
 
index d67e395895341bfa14bb1f1deca2daa94ea0f960..068e9076244f7e1096625e299bebd443f584b10c 100644 (file)
@@ -2,7 +2,7 @@
 Customising Roundup
 ===================
 
-:Version: $Revision: 1.63 $
+:Version: $Revision: 1.64 $
 
 .. This document borrows from the ZopeBook section on ZPT. The original is at:
    http://www.zope.org/Documentation/Books/ZopeBook/current/ZPT.stx
@@ -1486,6 +1486,10 @@ the "status" and "topic" properties, and the table includes columns for the
 Searching Views
 ---------------
 
+Note: if you add a new column to the ``:columns`` form variable potentials
+      then you will need to add the column to the `index view`_
+      template so it is actually displayed.
+
 This is one of the class context views. The template used is typically
 "*classname*.search". The form on this page should have "search" as its
 ``:action`` variable. The "search" action:
@@ -1515,7 +1519,6 @@ action are:
   template schema does not.
 
 
-
 Item Views
 ----------
 
index b5440b9ff9d47a21325025bb8b3a1ded5d48aac9..9935146fe39af1b47adb8e6087e84597cb1ac5da 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.16 2002-11-06 11:38:42 richard Exp $
+# $Id: __init__.py,v 1.17 2002-12-10 00:11:14 richard Exp $
 
 ''' Roundup - issue tracking for knowledge workers.
 
@@ -67,6 +67,6 @@ written by Ka-Ping Yee in the "doc" directory. If nothing else, it has a
 much prettier cake :)
 '''
 
-__version__ = '0.5.2'
+__version__ = '0.5.3'
 
 # vim: set filetype=python ts=4 sw=4 et si
index 77e625d68b080cc32d9944ea4d66c2fc14eff76e..f45b3366a9950c39abe711591c2a3cadaf04a10d 100644 (file)
@@ -1,4 +1,4 @@
-# $Id: client.py,v 1.58 2002-11-28 07:08:39 richard Exp $
+# $Id: client.py,v 1.59 2002-12-10 00:11:15 richard Exp $
 
 __doc__ = """
 WWW request handler (also used in the stand-alone server).
@@ -691,7 +691,7 @@ class Client:
             props = self._changenode(props)
             # handle linked nodes 
             self._post_editnode(self.nodeid)
-        except (ValueError, KeyError), message:
+        except (ValueError, KeyError, IndexError), message:
             self.error_message.append(_('Error: ') + str(message))
             return
 
@@ -771,7 +771,19 @@ class Client:
         try:
             # do the create
             nid = self._createnode(props)
+        except (ValueError, KeyError, IndexError), message:
+            # these errors might just be indicative of user dumbness
+            self.error_message.append(_('Error: ') + str(message))
+            return
+        except:
+            # oops
+            self.db.rollback()
+            s = StringIO.StringIO()
+            traceback.print_exc(None, s)
+            self.error_message.append('<pre>%s</pre>'%cgi.escape(s.getvalue()))
+            return
 
+        try:
             # handle linked nodes 
             self._post_editnode(nid)
 
@@ -783,9 +795,6 @@ class Client:
 
             # and some nice feedback for the user
             message = _('%(classname)s created ok')%self.__dict__ + xtra
-        except (ValueError, KeyError), message:
-            self.error_message.append(_('Error: ') + str(message))
-            return
         except:
             # oops
             self.db.rollback()
index cd315297c16a3485c06b20803cc2cec63ad18c22..35bc234b290eb86615a6dcc2b526c9f65bc3b8c0 100644 (file)
@@ -891,6 +891,12 @@ class DateHTMLProperty(HTMLProperty):
         '''
         return self._value.pretty()
 
+    def local(self, offset):
+        ''' Return the date/time as a local (timezone offset) date/time.
+        '''
+        return DateHTMLProperty(self._client, self._nodeid, self._prop,
+            self._name, self._value.local())
+
 class IntervalHTMLProperty(HTMLProperty):
     def plain(self):
         ''' Render a "plain" representation of the property
@@ -965,6 +971,7 @@ class LinkHTMLProperty(HTMLProperty):
         else:
             s = ''
         l.append(_('<option %svalue="-1">- no selection -</option>')%s)
+        # XXX if the current value is retired, then list it explicitly
         for optionid in options:
             # get the option value, and if it's None use an empty string
             option = linkcl.get(optionid, k) or ''
@@ -1011,6 +1018,7 @@ class LinkHTMLProperty(HTMLProperty):
         else:  
             sort_on = ('+', linkcl.labelprop())
         options = linkcl.filter(None, conditions, sort_on, (None, None))
+        # XXX if the current value is retired, then list it explicitly
         for optionid in options:
             # get the option value, and if it's None use an empty string
             option = linkcl.get(optionid, k) or ''
@@ -1131,6 +1139,7 @@ class MultilinkHTMLProperty(HTMLProperty):
         height = height or min(len(options), 7)
         l = ['<select multiple name="%s" size="%s">'%(self._name, height)]
         k = linkcl.labelprop(1)
+        # XXX if any of the current values are retired, then list them
         for optionid in options:
             # get the option value, and if it's None use an empty string
             option = linkcl.get(optionid, k) or ''
index 8b4f95e6de567160cee5a5f0b85788fb3f455a9e..3f62e415520b3f5a44d69007cd398380a15e51a0 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.99 2002-11-05 22:59:46 richard Exp $
+$Id: mailgw.py,v 1.100 2002-12-10 00:11:15 richard Exp $
 '''
 
 import string, re, os, mimetools, cStringIO, smtplib, socket, binascii, quopri
@@ -723,7 +723,11 @@ Subject was: "%s"
                     attachments.append((name, 'message/rfc822', part.fp.read()))
                 else:
                     # try name on Content-Type
-                    name = part.getparam('name')
+                    name = part.getparam('name').strip()
+                    if not name:
+                        disp = part.getheader('content-disposition', None)
+                        if disp:
+                            name = disp.getparam('filename').strip()
                     # this is just an attachment
                     data = self.get_part_data_decoded(part) 
                     attachments.append((name, part.gettype(), data))
@@ -761,9 +765,9 @@ not find a text/plain part to use.
             content = self.get_part_data_decoded(message) 
  
         # figure how much we should muck around with the email body
-        keep_citations = getattr(self.instance, 'EMAIL_KEEP_QUOTED_TEXT',
+        keep_citations = getattr(self.instance.config, 'EMAIL_KEEP_QUOTED_TEXT',
             'no') == 'yes'
-        keep_body = getattr(self.instance, 'EMAIL_LEAVE_BODY_UNCHANGED',
+        keep_body = getattr(self.instance.config, 'EMAIL_LEAVE_BODY_UNCHANGED',
             'no') == 'yes'
 
         # parse the body of the message, stripping out bits as appropriate
index 9683b40991083fdcfa25fed2b2242f64e2227881..c312b58f97134825fa042ccb7f2fd9371356c9a6 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: config.py,v 1.4 2002-11-07 21:17:44 richard Exp $
+# $Id: config.py,v 1.5 2002-12-10 00:11:16 richard Exp $
 
 import os
 
@@ -47,9 +47,6 @@ TRACKER_WEB = 'http://your.tracker.url.example/'
 # The email address that roundup will complain to if it runs into trouble
 ADMIN_EMAIL = 'roundup-admin@%s'%MAIL_DOMAIN
 
-# Where to place the web filtering HTML on the index page
-FILTER_POSITION = 'bottom'          # one of 'top', 'bottom', 'top and bottom'
-
 # 
 # SECURITY DEFINITIONS
 #
index d4e4e1f9f49299e7d4bcb6f6589b7ac639fd3f3e..dcba3e0646f5620d834c412188a52b63f598b228 100644 (file)
@@ -128,7 +128,7 @@ python:db.user.classhelp('username,realname,address,phone')" /><br>
 
  <table class="messages" tal:condition="context/messages">
   <tr><th colspan="4" class="header">Messages</th></tr>
-  <tal:block tal:repeat="msg context/messages">
+  <tal:block tal:repeat="msg context/messages/reverse">
    <tr>
     <th><a tal:attributes="href string:msg${msg/id}"
            tal:content="string:msg${msg/id}"></a></th>
index 2987ed6ee8a94da07fb25f856231d5641be1365d..94e76e3ab4a349b06d58ef078223b82a91b03503 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: config.py,v 1.2 2002-11-07 21:17:44 richard Exp $
+# $Id: config.py,v 1.3 2002-12-10 00:11:16 richard Exp $
 
 import os
 
@@ -47,9 +47,6 @@ TRACKER_WEB = 'http://your.tracker.url.example/'
 # The email address that roundup will complain to if it runs into trouble
 ADMIN_EMAIL = 'roundup-admin@%s'%MAIL_DOMAIN
 
-# Where to place the web filtering HTML on the index page
-FILTER_POSITION = 'bottom'          # one of 'top', 'bottom', 'top and bottom'
-
 # 
 # SECURITY DEFINITIONS
 #
index 64176b8785e2f45bdcd5189854319d9019e65fc0..800603e61903817a2db4eefc82788b831965b1a8 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.40 2002-10-17 00:22:16 richard Exp $
+# $Id: setup.py,v 1.41 2002-12-10 00:11:13 richard Exp $
 
 from distutils.core import setup, Extension
 from distutils.util import get_platform
@@ -25,6 +25,11 @@ from distutils.command.build_scripts import build_scripts
 import sys, os, string
 from glob import glob
 
+# patch distutils if it can't cope with the "classifiers" keyword
+if sys.version < '2.2.3':
+    from distutils.dist import DistributionMetadata
+    DistributionMetadata.classifiers = None
+
 from roundup.templates.builder import makeHtmlBase
 
 
@@ -180,6 +185,22 @@ if __name__ == '__main__':
         author_email = "richard@users.sourceforge.net",
         url = 'http://sourceforge.net/projects/roundup/',
         packages = packagelist,
+        classifiers = [
+            'Development Status :: 4 - Beta',
+            'Environment :: Console',
+            'Environment :: Web Environment',
+            'Intended Audience :: End Users/Desktop',
+            'Intended Audience :: Developers',
+            'Intended Audience :: System Administrators',
+            'License :: OSI Approved :: Python Software Foundation License',
+            'Operating System :: MacOS :: MacOS X',
+            'Operating System :: Microsoft :: Windows',
+            'Operating System :: POSIX',
+            'Programming Language :: Python',
+            'Topic :: Communications :: Email',
+            'Topic :: Office/Business',
+            'Topic :: Software Development :: Bug Tracking',
+        ],
 
         # Override certain command classes with our own ones
         cmdclass = {
index f8332b618c3f8d9605bb6781302d818ad8fdd8c5..918f431396773b0077205e62d9b2b63796714d64 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.33 2002-11-05 22:59:46 richard Exp $
+# $Id: test_mailgw.py,v 1.34 2002-12-10 00:11:16 richard Exp $
 
 import unittest, cStringIO, tempfile, os, shutil, errno, imp, sys, difflib
 
@@ -119,6 +119,7 @@ This is a test submission of a new issue.
         l = self.db.issue.get(nodeid, 'nosy')
         l.sort()
         self.assertEqual(l, ['3', '4'])
+        return nodeid
 
     def testNewIssue(self):
         self.doNewIssue()
@@ -819,6 +820,53 @@ http://your.tracker.url.example/issue1
 _______________________________________________________________________
 ''')
 
+    def testEmailQuoting(self):
+        self.instance.config.EMAIL_KEEP_QUOTED_TEXT = 'no'
+        self.innerTestQuoting('''This is a followup
+''')
+
+    def testEmailQuotingRemove(self):
+        self.instance.config.EMAIL_KEEP_QUOTED_TEXT = 'yes'
+        self.innerTestQuoting('''Blah blah wrote:
+> Blah bklaskdfj sdf asdf jlaskdf skj sdkfjl asdf
+>  skdjlkjsdfalsdkfjasdlfkj dlfksdfalksd fj
+>
+
+This is a followup
+''')
+
+    def innerTestQuoting(self, expect):
+        nodeid = self.doNewIssue()
+
+        messages = self.db.issue.get(nodeid, 'messages')
+
+        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: Re: [issue1] Testing...
+
+Blah blah wrote:
+> Blah bklaskdfj sdf asdf jlaskdf skj sdkfjl asdf
+>  skdjlkjsdfalsdkfjasdlfkj dlfksdfalksd fj
+>
+
+This is a followup
+''')
+        handler = self.instance.MailGW(self.instance, self.db)
+        handler.trapExceptions = 0
+        handler.main(message)
+
+        # figure the new message id
+        newmessages = self.db.issue.get(nodeid, 'messages')
+        for msg in messages:
+            newmessages.remove(msg)
+        messageid = newmessages[0]
+
+        self.compareStrings(self.db.msg.get(messageid, 'content'), expect)
+
 def suite():
     l = [unittest.makeSuite(MailgwTestCase),
     ]