summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 7a761dc)
raw | patch | inline | side by side (parent: 7a761dc)
author | richard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Fri, 18 Oct 2002 03:34:58 +0000 (03:34 +0000) | ||
committer | richard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Fri, 18 Oct 2002 03:34:58 +0000 (03:34 +0000) |
remove / add in <propname> multilink.
(is used in issue message display, allowing removal of messages)
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1365 57a73879-2fb5-44c3-a270-3262357dd7e2
(is used in issue message display, allowing removal of messages)
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1365 57a73879-2fb5-44c3-a270-3262357dd7e2
diff --git a/TODO.txt b/TODO.txt
index ea5809c9fcc80ad0d40fdcd60f6670b95f10170a..dbe7c133aa9050b55eafa7fac38284c7e09cecec 100644 (file)
--- a/TODO.txt
+++ b/TODO.txt
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 handle :remove:<propname> and :add:<propname> which specify
- item ids to remove / add in <propname> multilink.
active web UNIX init.d script for roundup-server
bug docs need to mention somewhere how sorting works
- it's mentioned in the design doc
- multilink sorting by length is dumb
bug web query editing isn't fully implemented
+bug web no testing for parsePropsFromForm
======= ========= =============================================================
diff --git a/doc/announcement.txt b/doc/announcement.txt
index ed65f008baf07336aa4af9d68910a7ca3baa906f..213ddf6fbdc4681e5b64609db745cb25c5345446 100644 (file)
--- a/doc/announcement.txt
+++ b/doc/announcement.txt
Roundup requires python 2.1.3 or later for correct operation.
We've had a good crack at bugs (thanks to all who contributed!):
+
- fixed filter() with no sort/group (sf bug 618614)
- fixed register with no session (sf bug 618611)
- fixed log / pid file path handling in roundup-server (sf bug 617981)
index b0780ccf821781b2a289690437752ddd3a2cc0ee..bcde6b1b017b2e2fb859ed6a25967806376c3757 100644 (file)
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: ZRoundup.py,v 1.15 2002-10-16 06:48:50 richard Exp $
+# $Id: ZRoundup.py,v 1.16 2002-10-18 03:34:58 richard Exp $
#
''' ZRoundup module - exposes the roundup web interface to Zope
from Globals import InitializeClass, HTMLFile
from OFS.SimpleItem import Item
from OFS.PropertyManager import PropertyManager
-from Acquisition import Implicit
+from Acquisition import Explicit, Implicit
from Persistence import Persistent
from AccessControl import ClassSecurityInfo
from AccessControl import ModuleSecurityInfo
icon = "misc_/ZRoundup/icon"
- security.declarePrivate('_opendb')
- def _opendb(self):
+ security.declarePrivate('roundup_opendb')
+ def roundup_opendb(self):
'''Open the roundup instance database for a transaction.
'''
instance = roundup.instance.open(self.instance_home)
url = urlparse.urlparse( self.absolute_url() )
path = url[2]
path_components = path.split( '/' )
-
+
# special case when roundup is '/' in this virtual host,
if path == "/" :
env['SCRIPT_NAME'] = "/"
# It doesn't occur with apache/roundup.cgi, though.
form = FormWrapper(self.REQUEST.form)
+ print (env['SCRIPT_NAME'], env['PATH_INFO'])
return instance.Client(instance, request, env, form)
security.declareProtected('View', 'index_html')
RESPONSE.setHeader( "Location" , url )
return RESPONSE
- client = self._opendb()
+ client = self.roundup_opendb()
# fake the path that roundup should use
client.split_path = ['index']
return client.main()
def __getitem__(self, item):
'''All other URL accesses are passed throuh to roundup
'''
+ return PathElement(self, item)
+
+class PathElement(Item, Implicit, Persistent):
+ def __init__(self, parent, path):
+ self.parent = parent
+ self.path = path
+
+ def __getitem__(self, item):
+ ''' Get a subitem.
+ '''
+ return PathElement(self.path + '/' + item)
+
+ def __call__(self, *args, **kw):
+ ''' Actually call through to roundup to handle the request.
+ '''
+ print '*****', self.path
try:
- client = self._opendb()
+ client = self.parent.roundup_opendb()
# fake the path that roundup should use
- client.path = item
+ client.path = self.path
# and call roundup to do something
client.main()
return ''
traceback.print_exc()
# all other exceptions in roundup are valid
raise
- raise KeyError, item
-
InitializeClass(ZRoundup)
modulesecurity.apply(globals())
diff --git a/roundup/cgi/client.py b/roundup/cgi/client.py
index 03eaf1102c14436c9cc85723a02b60bf95d037b9..10fd44897bd8e423e252cc1e8304f868725af3e4 100644 (file)
--- a/roundup/cgi/client.py
+++ b/roundup/cgi/client.py
-# $Id: client.py,v 1.54 2002-10-17 06:11:25 richard Exp $
+# $Id: client.py,v 1.55 2002-10-18 03:34:58 richard Exp $
__doc__ = """
WWW request handler (also used in the stand-alone server).
:required=property,property,...
The named properties are required to be filled in the form.
+ :remove:<propname>=id(s)
+ The ids will be removed from the multilink property.
+ :add:<propname>=id(s)
+ The ids will be added to the multilink property.
+
'''
cl = self.db.classes[self.classname]
If a ":required" parameter is supplied, then the names property values
must be supplied or a ValueError will be raised.
+
+ Other special form values:
+ :remove:<propname>=id(s)
+ The ids will be removed from the multilink property.
+ :add:<propname>=id(s)
+ The ids will be added to the multilink property.
'''
required = []
if form.has_key(':required'):
keys = form.keys()
properties = cl.getprops()
for key in keys:
- if not properties.has_key(key):
+ # see if we're performing a special multilink action
+ mlaction = 'set'
+ if key.startswith(':remove:'):
+ propname = key[8:]
+ mlaction = 'remove'
+ elif key.startswith(':add:'):
+ propname = key[5:]
+ mlaction = 'add'
+ else:
+ propname = key
+
+
+ # does the property exist?
+ if not properties.has_key(propname):
+ if mlaction != 'set':
+ raise ValueError, 'You have submitted a remove action for'\
+ ' the property "%s" which doesn\'t exist'%propname
continue
- proptype = properties[key]
+ proptype = properties[propname]
# Get the form value. This value may be a MiniFieldStorage or a list
# of MiniFieldStorages.
value = form[key]
+ print (mlaction, propname, value)
+
# make sure non-multilinks only get one value
if not isinstance(proptype, hyperdb.Multilink):
if isinstance(value, type([])):
raise ValueError, 'You have submitted more than one value'\
- ' for the %s property'%key
+ ' for the %s property'%propname
# we've got a MiniFieldStorage, so pull out the value and strip
# surrounding whitespace
value = value.value.strip()
if not value:
# ignore empty password values
continue
- if not form.has_key('%s:confirm'%key):
+ if not form.has_key('%s:confirm'%propname):
raise ValueError, 'Password and confirmation text do not match'
- confirm = form['%s:confirm'%key]
+ confirm = form['%s:confirm'%propname]
if isinstance(confirm, type([])):
raise ValueError, 'You have submitted more than one value'\
- ' for the %s property'%key
+ ' for the %s property'%propname
if value != confirm.value:
raise ValueError, 'Password and confirmation text do not match'
value = password.Password(value)
elif isinstance(proptype, hyperdb.Date):
if value:
- value = date.Date(form[key].value.strip())
+ value = date.Date(value.value.strip())
else:
continue
elif isinstance(proptype, hyperdb.Interval):
if value:
- value = date.Interval(form[key].value.strip())
+ value = date.Interval(value.value.strip())
else:
continue
elif isinstance(proptype, hyperdb.Link):
value = db.classes[link].lookup(value)
except KeyError:
raise ValueError, _('property "%(propname)s": '
- '%(value)s not a %(classname)s')%{'propname':key,
- 'value': value, 'classname': link}
+ '%(value)s not a %(classname)s')%{
+ 'propname': propname, 'value': value,
+ 'classname': link}
except TypeError, message:
raise ValueError, _('you may only enter ID values '
'for property "%(propname)s": %(message)s')%{
- 'propname':key, 'message': message}
+ 'propname': propname, 'message': message}
elif isinstance(proptype, hyperdb.Multilink):
if isinstance(value, type([])):
# it's a list of MiniFieldStorages
except KeyError:
raise ValueError, _('property "%(propname)s": '
'"%(value)s" not an entry of %(classname)s')%{
- 'propname':key, 'value': entry, 'classname': link}
+ 'propname': propname, 'value': entry,
+ 'classname': link}
except TypeError, message:
raise ValueError, _('you may only enter ID values '
'for property "%(propname)s": %(message)s')%{
- 'propname':key, 'message': message}
+ 'propname': propname, 'message': message}
l.append(entry)
l.sort()
- value = l
+
+ # now use that list of ids to modify the multilink
+ if mlaction == 'set':
+ value = l
+ else:
+ # we're modifying the list - get the current list of ids
+ try:
+ existing = cl.get(nodeid, propname)
+ except KeyError:
+ existing = []
+ if mlaction == 'remove':
+ # remove - handle situation where the id isn't in the list
+ for entry in l:
+ try:
+ existing.remove(entry)
+ except ValueError:
+ raise ValueError, _('property "%(propname)s": '
+ '"%(value)s" not currently in list')%{
+ 'propname': propname, 'value': entry}
+ else:
+ # add - easy, just don't dupe
+ for entry in l:
+ if entry not in existing:
+ existing.append(entry)
+ value = existing
+ value.sort()
+
elif isinstance(proptype, hyperdb.Boolean):
- props[key] = value = value.lower() in ('yes', 'true', 'on', '1')
+ value = value.lower() in ('yes', 'true', 'on', '1')
+ props[propname] = value
elif isinstance(proptype, hyperdb.Number):
- props[key] = value = int(value)
+ props[propname] = value = int(value)
# register this as received if required?
- if key in required and value is not None:
- required.remove(key)
+ if propname in required and value is not None:
+ required.remove(propname)
# get the old value
if nodeid:
try:
- existing = cl.get(nodeid, key)
+ existing = cl.get(nodeid, propname)
except KeyError:
# this might be a new property for which there is no existing
# value
- if not properties.has_key(key): raise
+ if not properties.has_key(propname):
+ raise
# if changed, set it
if value != existing:
- props[key] = value
+ props[propname] = value
else:
- props[key] = value
+ props[propname] = value
# see if all the required properties have been supplied
if required:
index b795939f530149b3fa979d26343eec425007155d..3215703e28111ef2ecddec2dd7b18527f7b75870 100644 (file)
<th nowrap>Nosy List</th>
<td>
<span tal:replace="structure context/nosy/field" />
- <span tal:replace="structure python:db.user.classhelp('username,realname,address,phone')" />
+ <span tal:replace="structure
+python:db.user.classhelp('username,realname,address,phone')" /><br>
</td>
</tr>
<table class="messages" tal:condition="context/messages">
<tr><th colspan=4 class="header">Messages</th></tr>
- <tr><th>Message</th><th>Author</th><th>Date</th><th>Summary</th></tr>
<tr tal:repeat="msg context/messages/reverse">
<td><a tal:attributes="href string:msg${msg/id}"
tal:content="string:msg${msg/id}"></a></td>
<td tal:content="msg/author">author</td>
- <td tal:content="msg/date">date</td>
+ <td nowrap tal:content="msg/date/pretty">date</td>
<td tal:content="msg/summary">summary</td>
+ <td>
+ <a tal:attributes="href string:?:remove:messages=${msg/id}&:action=edit">remove</a>
+ </td>
</tr>
</table>
diff --git a/test/test_mailsplit.py b/test/test_mailsplit.py
index ccfd84f2f4dfe6582d3213d512e10e8050c6d8f4..f4cef60d9ad99446ba65ac3d5668ce18b17a7f17 100644 (file)
--- a/test/test_mailsplit.py
+++ b/test/test_mailsplit.py
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: test_mailsplit.py,v 1.11 2002-09-10 00:19:55 richard Exp $
+# $Id: test_mailsplit.py,v 1.12 2002-10-18 03:34:58 richard Exp $
import unittest, cStringIO
summary, content = parseContent(s, 0, 0)
self.assertEqual(content, s)
+ def testMultilineSummary(self):
+ s = 'This is a long sentence that would normally\nbe split. More words.'
+ summary, content = parseContent(s, 0, 0)
+ self.assertEqual(summary, 'This is a long sentence that would '
+ 'normally\nbe split.')
+
def suite():
return unittest.makeSuite(MailsplitTestCase, 'test')