Skip to content

Commit c923ee0

Browse files
author
Richard Jones
committed
added CGI :remove:<propname> and :add:<propname>...
...which specify item ids to remove / add in <propname> multilink. (is used in issue message display, allowing removal of messages)
1 parent 8605f41 commit c923ee0

File tree

6 files changed

+122
-39
lines changed

6 files changed

+122
-39
lines changed

TODO.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,12 @@ pending web multilink item removal action (with retirement)
5959
pending web search "refinement" - pre-fill the search page with the
6060
current search parameters
6161
pending web column-heading sort stuff isn't implemented
62-
pending web handle :remove:<propname> and :add:<propname> which specify
63-
item ids to remove / add in <propname> multilink.
6462

6563
active web UNIX init.d script for roundup-server
6664
bug docs need to mention somewhere how sorting works
6765
- it's mentioned in the design doc
6866
- multilink sorting by length is dumb
6967
bug web query editing isn't fully implemented
68+
bug web no testing for parsePropsFromForm
7069
======= ========= =============================================================
7170

doc/announcement.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ looking into fixing it for the next patch release.
1111
Roundup requires python 2.1.3 or later for correct operation.
1212

1313
We've had a good crack at bugs (thanks to all who contributed!):
14+
1415
- fixed filter() with no sort/group (sf bug 618614)
1516
- fixed register with no session (sf bug 618611)
1617
- fixed log / pid file path handling in roundup-server (sf bug 617981)

frontends/ZRoundup/ZRoundup.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1515
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1616
#
17-
# $Id: ZRoundup.py,v 1.15 2002-10-16 06:48:50 richard Exp $
17+
# $Id: ZRoundup.py,v 1.16 2002-10-18 03:34:58 richard Exp $
1818
#
1919
''' ZRoundup module - exposes the roundup web interface to Zope
2020
@@ -38,7 +38,7 @@
3838
from Globals import InitializeClass, HTMLFile
3939
from OFS.SimpleItem import Item
4040
from OFS.PropertyManager import PropertyManager
41-
from Acquisition import Implicit
41+
from Acquisition import Explicit, Implicit
4242
from Persistence import Persistent
4343
from AccessControl import ClassSecurityInfo
4444
from AccessControl import ModuleSecurityInfo
@@ -119,8 +119,8 @@ def __init__(self, id, instance_home):
119119

120120
icon = "misc_/ZRoundup/icon"
121121

122-
security.declarePrivate('_opendb')
123-
def _opendb(self):
122+
security.declarePrivate('roundup_opendb')
123+
def roundup_opendb(self):
124124
'''Open the roundup instance database for a transaction.
125125
'''
126126
instance = roundup.instance.open(self.instance_home)
@@ -131,7 +131,7 @@ def _opendb(self):
131131
url = urlparse.urlparse( self.absolute_url() )
132132
path = url[2]
133133
path_components = path.split( '/' )
134-
134+
135135
# special case when roundup is '/' in this virtual host,
136136
if path == "/" :
137137
env['SCRIPT_NAME'] = "/"
@@ -151,6 +151,7 @@ def _opendb(self):
151151
# It doesn't occur with apache/roundup.cgi, though.
152152
form = FormWrapper(self.REQUEST.form)
153153

154+
print (env['SCRIPT_NAME'], env['PATH_INFO'])
154155
return instance.Client(instance, request, env, form)
155156

156157
security.declareProtected('View', 'index_html')
@@ -170,18 +171,34 @@ def index_html(self):
170171
RESPONSE.setHeader( "Location" , url )
171172
return RESPONSE
172173

173-
client = self._opendb()
174+
client = self.roundup_opendb()
174175
# fake the path that roundup should use
175176
client.split_path = ['index']
176177
return client.main()
177178

178179
def __getitem__(self, item):
179180
'''All other URL accesses are passed throuh to roundup
180181
'''
182+
return PathElement(self, item)
183+
184+
class PathElement(Item, Implicit, Persistent):
185+
def __init__(self, parent, path):
186+
self.parent = parent
187+
self.path = path
188+
189+
def __getitem__(self, item):
190+
''' Get a subitem.
191+
'''
192+
return PathElement(self.path + '/' + item)
193+
194+
def __call__(self, *args, **kw):
195+
''' Actually call through to roundup to handle the request.
196+
'''
197+
print '*****', self.path
181198
try:
182-
client = self._opendb()
199+
client = self.parent.roundup_opendb()
183200
# fake the path that roundup should use
184-
client.path = item
201+
client.path = self.path
185202
# and call roundup to do something
186203
client.main()
187204
return ''
@@ -193,8 +210,6 @@ def __getitem__(self, item):
193210
traceback.print_exc()
194211
# all other exceptions in roundup are valid
195212
raise
196-
raise KeyError, item
197-
198213

199214
InitializeClass(ZRoundup)
200215
modulesecurity.apply(globals())

roundup/cgi/client.py

Lines changed: 82 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# $Id: client.py,v 1.54 2002-10-17 06:11:25 richard Exp $
1+
# $Id: client.py,v 1.55 2002-10-18 03:34:58 richard Exp $
22

33
__doc__ = """
44
WWW request handler (also used in the stand-alone server).
@@ -663,6 +663,11 @@ def editItemAction(self):
663663
:required=property,property,...
664664
The named properties are required to be filled in the form.
665665
666+
:remove:<propname>=id(s)
667+
The ids will be removed from the multilink property.
668+
:add:<propname>=id(s)
669+
The ids will be added to the multilink property.
670+
666671
'''
667672
cl = self.db.classes[self.classname]
668673

@@ -1141,6 +1146,12 @@ def parsePropsFromForm(db, cl, form, nodeid=0, num_re=re.compile('^\d+$')):
11411146
11421147
If a ":required" parameter is supplied, then the names property values
11431148
must be supplied or a ValueError will be raised.
1149+
1150+
Other special form values:
1151+
:remove:<propname>=id(s)
1152+
The ids will be removed from the multilink property.
1153+
:add:<propname>=id(s)
1154+
The ids will be added to the multilink property.
11441155
'''
11451156
required = []
11461157
if form.has_key(':required'):
@@ -1154,19 +1165,37 @@ def parsePropsFromForm(db, cl, form, nodeid=0, num_re=re.compile('^\d+$')):
11541165
keys = form.keys()
11551166
properties = cl.getprops()
11561167
for key in keys:
1157-
if not properties.has_key(key):
1168+
# see if we're performing a special multilink action
1169+
mlaction = 'set'
1170+
if key.startswith(':remove:'):
1171+
propname = key[8:]
1172+
mlaction = 'remove'
1173+
elif key.startswith(':add:'):
1174+
propname = key[5:]
1175+
mlaction = 'add'
1176+
else:
1177+
propname = key
1178+
1179+
1180+
# does the property exist?
1181+
if not properties.has_key(propname):
1182+
if mlaction != 'set':
1183+
raise ValueError, 'You have submitted a remove action for'\
1184+
' the property "%s" which doesn\'t exist'%propname
11581185
continue
1159-
proptype = properties[key]
1186+
proptype = properties[propname]
11601187

11611188
# Get the form value. This value may be a MiniFieldStorage or a list
11621189
# of MiniFieldStorages.
11631190
value = form[key]
11641191

1192+
print (mlaction, propname, value)
1193+
11651194
# make sure non-multilinks only get one value
11661195
if not isinstance(proptype, hyperdb.Multilink):
11671196
if isinstance(value, type([])):
11681197
raise ValueError, 'You have submitted more than one value'\
1169-
' for the %s property'%key
1198+
' for the %s property'%propname
11701199
# we've got a MiniFieldStorage, so pull out the value and strip
11711200
# surrounding whitespace
11721201
value = value.value.strip()
@@ -1180,23 +1209,23 @@ def parsePropsFromForm(db, cl, form, nodeid=0, num_re=re.compile('^\d+$')):
11801209
if not value:
11811210
# ignore empty password values
11821211
continue
1183-
if not form.has_key('%s:confirm'%key):
1212+
if not form.has_key('%s:confirm'%propname):
11841213
raise ValueError, 'Password and confirmation text do not match'
1185-
confirm = form['%s:confirm'%key]
1214+
confirm = form['%s:confirm'%propname]
11861215
if isinstance(confirm, type([])):
11871216
raise ValueError, 'You have submitted more than one value'\
1188-
' for the %s property'%key
1217+
' for the %s property'%propname
11891218
if value != confirm.value:
11901219
raise ValueError, 'Password and confirmation text do not match'
11911220
value = password.Password(value)
11921221
elif isinstance(proptype, hyperdb.Date):
11931222
if value:
1194-
value = date.Date(form[key].value.strip())
1223+
value = date.Date(value.value.strip())
11951224
else:
11961225
continue
11971226
elif isinstance(proptype, hyperdb.Interval):
11981227
if value:
1199-
value = date.Interval(form[key].value.strip())
1228+
value = date.Interval(value.value.strip())
12001229
else:
12011230
continue
12021231
elif isinstance(proptype, hyperdb.Link):
@@ -1211,12 +1240,13 @@ def parsePropsFromForm(db, cl, form, nodeid=0, num_re=re.compile('^\d+$')):
12111240
value = db.classes[link].lookup(value)
12121241
except KeyError:
12131242
raise ValueError, _('property "%(propname)s": '
1214-
'%(value)s not a %(classname)s')%{'propname':key,
1215-
'value': value, 'classname': link}
1243+
'%(value)s not a %(classname)s')%{
1244+
'propname': propname, 'value': value,
1245+
'classname': link}
12161246
except TypeError, message:
12171247
raise ValueError, _('you may only enter ID values '
12181248
'for property "%(propname)s": %(message)s')%{
1219-
'propname':key, 'message': message}
1249+
'propname': propname, 'message': message}
12201250
elif isinstance(proptype, hyperdb.Multilink):
12211251
if isinstance(value, type([])):
12221252
# it's a list of MiniFieldStorages
@@ -1235,37 +1265,66 @@ def parsePropsFromForm(db, cl, form, nodeid=0, num_re=re.compile('^\d+$')):
12351265
except KeyError:
12361266
raise ValueError, _('property "%(propname)s": '
12371267
'"%(value)s" not an entry of %(classname)s')%{
1238-
'propname':key, 'value': entry, 'classname': link}
1268+
'propname': propname, 'value': entry,
1269+
'classname': link}
12391270
except TypeError, message:
12401271
raise ValueError, _('you may only enter ID values '
12411272
'for property "%(propname)s": %(message)s')%{
1242-
'propname':key, 'message': message}
1273+
'propname': propname, 'message': message}
12431274
l.append(entry)
12441275
l.sort()
1245-
value = l
1276+
1277+
# now use that list of ids to modify the multilink
1278+
if mlaction == 'set':
1279+
value = l
1280+
else:
1281+
# we're modifying the list - get the current list of ids
1282+
try:
1283+
existing = cl.get(nodeid, propname)
1284+
except KeyError:
1285+
existing = []
1286+
if mlaction == 'remove':
1287+
# remove - handle situation where the id isn't in the list
1288+
for entry in l:
1289+
try:
1290+
existing.remove(entry)
1291+
except ValueError:
1292+
raise ValueError, _('property "%(propname)s": '
1293+
'"%(value)s" not currently in list')%{
1294+
'propname': propname, 'value': entry}
1295+
else:
1296+
# add - easy, just don't dupe
1297+
for entry in l:
1298+
if entry not in existing:
1299+
existing.append(entry)
1300+
value = existing
1301+
value.sort()
1302+
12461303
elif isinstance(proptype, hyperdb.Boolean):
1247-
props[key] = value = value.lower() in ('yes', 'true', 'on', '1')
1304+
value = value.lower() in ('yes', 'true', 'on', '1')
1305+
props[propname] = value
12481306
elif isinstance(proptype, hyperdb.Number):
1249-
props[key] = value = int(value)
1307+
props[propname] = value = int(value)
12501308

12511309
# register this as received if required?
1252-
if key in required and value is not None:
1253-
required.remove(key)
1310+
if propname in required and value is not None:
1311+
required.remove(propname)
12541312

12551313
# get the old value
12561314
if nodeid:
12571315
try:
1258-
existing = cl.get(nodeid, key)
1316+
existing = cl.get(nodeid, propname)
12591317
except KeyError:
12601318
# this might be a new property for which there is no existing
12611319
# value
1262-
if not properties.has_key(key): raise
1320+
if not properties.has_key(propname):
1321+
raise
12631322

12641323
# if changed, set it
12651324
if value != existing:
1266-
props[key] = value
1325+
props[propname] = value
12671326
else:
1268-
props[key] = value
1327+
props[propname] = value
12691328

12701329
# see if all the required properties have been supplied
12711330
if required:

roundup/templates/classic/html/issue.item

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ You are not allowed to view this page.
4242
<th nowrap>Nosy List</th>
4343
<td>
4444
<span tal:replace="structure context/nosy/field" />
45-
<span tal:replace="structure python:db.user.classhelp('username,realname,address,phone')" />
45+
<span tal:replace="structure
46+
python:db.user.classhelp('username,realname,address,phone')" /><br>
4647
</td>
4748
</tr>
4849

@@ -114,13 +115,15 @@ You are not allowed to view this page.
114115

115116
<table class="messages" tal:condition="context/messages">
116117
<tr><th colspan=4 class="header">Messages</th></tr>
117-
<tr><th>Message</th><th>Author</th><th>Date</th><th>Summary</th></tr>
118118
<tr tal:repeat="msg context/messages/reverse">
119119
<td><a tal:attributes="href string:msg${msg/id}"
120120
tal:content="string:msg${msg/id}"></a></td>
121121
<td tal:content="msg/author">author</td>
122-
<td tal:content="msg/date">date</td>
122+
<td nowrap tal:content="msg/date/pretty">date</td>
123123
<td tal:content="msg/summary">summary</td>
124+
<td>
125+
<a tal:attributes="href string:?:remove:messages=${msg/id}&:action=edit">remove</a>
126+
</td>
124127
</tr>
125128
</table>
126129

test/test_mailsplit.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
# $Id: test_mailsplit.py,v 1.11 2002-09-10 00:19:55 richard Exp $
18+
# $Id: test_mailsplit.py,v 1.12 2002-10-18 03:34:58 richard Exp $
1919

2020
import unittest, cStringIO
2121

@@ -208,6 +208,12 @@ def testIndentationContent(self):
208208
summary, content = parseContent(s, 0, 0)
209209
self.assertEqual(content, s)
210210

211+
def testMultilineSummary(self):
212+
s = 'This is a long sentence that would normally\nbe split. More words.'
213+
summary, content = parseContent(s, 0, 0)
214+
self.assertEqual(summary, 'This is a long sentence that would '
215+
'normally\nbe split.')
216+
211217
def suite():
212218
return unittest.makeSuite(MailsplitTestCase, 'test')
213219

0 commit comments

Comments
 (0)