Skip to content

Commit 8972c59

Browse files
author
Richard Jones
committed
revert StringHTMLProperty to not hyperlink text by default (added doc too)
1 parent be30491 commit 8972c59

File tree

5 files changed

+262
-30
lines changed

5 files changed

+262
-30
lines changed

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ are given with the most recent entry first.
66
- fixed rdbms searching by ID (sf bug 666615)
77
- detect corrupted index and raise semi-useful exception (sf bug 666767)
88
- open server logfile unbuffered
9+
- revert StringHTMLProperty to not hyperlink text by default
910

1011

1112
2003-01-10 0.5.4

doc/customizing.txt

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Customising Roundup
33
===================
44

5-
:Version: $Revision: 1.68 $
5+
:Version: $Revision: 1.68.2.1 $
66

77
.. This document borrows from the ZopeBook section on ZPT. The original is at:
88
http://www.zope.org/Documentation/Books/ZopeBook/current/ZPT.stx
@@ -1219,32 +1219,55 @@ _value the value of the property if any - this is the actual value
12191219

12201220
There are several methods available on these wrapper objects:
12211221

1222-
=========== =================================================================
1223-
Method Description
1224-
=========== =================================================================
1225-
plain render a "plain" representation of the property
1226-
field render an appropriate form edit field for the property - for most
1227-
types this is a text entry box, but for Booleans it's a tri-state
1228-
yes/no/neither selection.
1229-
stext only on String properties - render the value of the
1230-
property as StructuredText (requires the StructureText module
1231-
to be installed separately)
1232-
multiline only on String properties - render a multiline form edit
1233-
field for the property
1234-
email only on String properties - render the value of the
1235-
property as an obscured email address
1236-
confirm only on Password properties - render a second form edit field for
1237-
the property, used for confirmation that the user typed the
1238-
password correctly. Generates a field with name "name:confirm".
1239-
reldate only on Date properties - render the interval between the
1240-
date and now
1241-
pretty only on Interval properties - render the interval in a
1242-
pretty format (eg. "yesterday")
1243-
menu only on Link and Multilink properties - render a form select
1244-
list for this property
1245-
reverse only on Multilink properties - produce a list of the linked
1246-
items in reverse order
1247-
=========== =================================================================
1222+
========= =====================================================================
1223+
Method Description
1224+
========= =====================================================================
1225+
plain render a "plain" representation of the property. This method may
1226+
take two arguments:
1227+
1228+
escape
1229+
If true, escape the text so it is HTML safe (default: no). The
1230+
reason this defaults to off is that text is usually escaped
1231+
at a later stage by the TAL commands, unless the "structure"
1232+
option is used in the template. The following are equivalent::
1233+
1234+
<p tal:content="structure python:msg.content.plain(escape=1)" />
1235+
<p tal:content="python:msg.content.plain()" />
1236+
<p tal:content="msg/content/plain" />
1237+
<p tal:content="msg/content" />
1238+
1239+
hyperlink
1240+
If true, turn URLs, email addresses and hyperdb item designators
1241+
in the text into hyperlinks (default: no). Note that you'll need
1242+
to use the "structure" TAL option if you want to use this::
1243+
1244+
<p tal:content="structure python:msg.content.plain(hyperlink=1)" />
1245+
1246+
Note also that the text is automatically HTML-escape before the
1247+
hyperlinking transformation.
1248+
1249+
field render an appropriate form edit field for the property - for most
1250+
types this is a text entry box, but for Booleans it's a tri-state
1251+
yes/no/neither selection.
1252+
stext only on String properties - render the value of the
1253+
property as StructuredText (requires the StructureText module
1254+
to be installed separately)
1255+
multiline only on String properties - render a multiline form edit
1256+
field for the property
1257+
email only on String properties - render the value of the
1258+
property as an obscured email address
1259+
confirm only on Password properties - render a second form edit field for
1260+
the property, used for confirmation that the user typed the
1261+
password correctly. Generates a field with name "name:confirm".
1262+
reldate only on Date properties - render the interval between the
1263+
date and now
1264+
pretty only on Interval properties - render the interval in a
1265+
pretty format (eg. "yesterday")
1266+
menu only on Link and Multilink properties - render a form select
1267+
list for this property
1268+
reverse only on Multilink properties - produce a list of the linked
1269+
items in reverse order
1270+
========= =====================================================================
12481271

12491272
The request variable
12501273
~~~~~~~~~~~~~~~~~~~~

roundup/cgi/templating.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -754,7 +754,7 @@ def _designator_repl(self, match):
754754
except KeyError:
755755
return '%s%s'%(s1, s2)
756756

757-
def plain(self, escape=0, hyperlink=1):
757+
def plain(self, escape=0, hyperlink=0):
758758
''' Render a "plain" representation of the property
759759
760760
"escape" turns on/off HTML quoting
@@ -766,8 +766,10 @@ def plain(self, escape=0, hyperlink=1):
766766
if escape:
767767
s = cgi.escape(str(self._value))
768768
else:
769-
s = self._value
769+
s = str(self._value)
770770
if hyperlink:
771+
if not escape:
772+
s = cgi.escape(s)
771773
s = self.url_re.sub(self._url_repl, s)
772774
s = self.email_re.sub(self._email_repl, s)
773775
s = self.designator_re.sub(self._designator_repl, s)

roundup/templates/classic/html/issue.item

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ python:db.user.classhelp('username,realname,address,phone')" /><br>
140140
</tr>
141141
<tr>
142142
<td colspan="4" class="content">
143-
<pre tal:content="structure msg/content">content</pre>
143+
<pre tal:content="msg/content">content</pre>
144144
</td>
145145
</tr>
146146
</tal:block>

test/test_cgi.py

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
#
2+
# Copyright (c) 2003 Richard Jones, [email protected]
3+
# This module is free software, and you may redistribute it and/or modify
4+
# under the same terms as Python, so long as this copyright message and
5+
# disclaimer are retained in their original form.
6+
#
7+
# This module is distributed in the hope that it will be useful,
8+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
9+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10+
#
11+
# $Id: test_cgi.py,v 1.4 2003-01-15 11:14:01 richard Exp $
12+
13+
import unittest, os, shutil, errno, sys, difflib, cgi
14+
15+
from roundup.cgi import client
16+
from roundup import init, instance, password
17+
18+
def makeForm(args):
19+
form = cgi.FieldStorage()
20+
for k,v in args.items():
21+
if type(v) is type([]):
22+
[form.list.append(cgi.MiniFieldStorage(k, x)) for x in v]
23+
else:
24+
form.list.append(cgi.MiniFieldStorage(k, v))
25+
return form
26+
27+
class FormTestCase(unittest.TestCase):
28+
def setUp(self):
29+
self.dirname = '_test_cgi_form'
30+
try:
31+
shutil.rmtree(self.dirname)
32+
except OSError, error:
33+
if error.errno not in (errno.ENOENT, errno.ESRCH): raise
34+
# create the instance
35+
init.install(self.dirname, 'classic', 'anydbm')
36+
init.initialise(self.dirname, 'sekrit')
37+
# check we can load the package
38+
self.instance = instance.open(self.dirname)
39+
# and open the database
40+
self.db = self.instance.open('admin')
41+
self.db.user.create(username='Chef', address='[email protected]',
42+
realname='Bork, Chef', roles='User')
43+
self.db.user.create(username='mary', address='mary@test',
44+
roles='User', realname='Contrary, Mary')
45+
46+
def tearDown(self):
47+
self.db.close()
48+
try:
49+
shutil.rmtree(self.dirname)
50+
except OSError, error:
51+
if error.errno not in (errno.ENOENT, errno.ESRCH): raise
52+
53+
#
54+
# Empty form
55+
#
56+
def testNothing(self):
57+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.issue,
58+
makeForm({})), {})
59+
60+
def testNothingWithRequired(self):
61+
form = makeForm({':required': 'title'})
62+
self.assertRaises(ValueError, client.parsePropsFromForm, self.db,
63+
self.db.issue, form)
64+
65+
#
66+
# String
67+
#
68+
def testEmptyString(self):
69+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.issue,
70+
makeForm({'title': ''})), {})
71+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.issue,
72+
makeForm({'title': ' '})), {})
73+
self.assertRaises(ValueError, client.parsePropsFromForm, self.db,
74+
self.db.issue, makeForm({'title': ['', '']}))
75+
76+
def testSetString(self):
77+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.issue,
78+
makeForm({'title': 'foo'})), {'title': 'foo'})
79+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.issue,
80+
makeForm({'title': 'a\r\nb\r\n'})), {'title': 'a\nb'})
81+
82+
def testEmptyStringSet(self):
83+
nodeid = self.db.issue.create(title='foo')
84+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.issue,
85+
makeForm({'title': ''}), nodeid), {'title': ''})
86+
nodeid = self.db.issue.create(title='foo')
87+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.issue,
88+
makeForm({'title': ' '}), nodeid), {'title': ''})
89+
90+
#
91+
# Multilink
92+
#
93+
def testEmptyMultilink(self):
94+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.issue,
95+
makeForm({'nosy': ''})), {})
96+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.issue,
97+
makeForm({'nosy': ' '})), {})
98+
99+
def testSetMultilink(self):
100+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.issue,
101+
makeForm({'nosy': '1'})), {'nosy': ['1']})
102+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.issue,
103+
makeForm({'nosy': 'admin'})), {'nosy': ['1']})
104+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.issue,
105+
makeForm({'nosy': ['1','2']})), {'nosy': ['1','2']})
106+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.issue,
107+
makeForm({'nosy': '1,2'})), {'nosy': ['1','2']})
108+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.issue,
109+
makeForm({'nosy': 'admin,2'})), {'nosy': ['1','2']})
110+
111+
def testEmptyMultilinkSet(self):
112+
nodeid = self.db.issue.create(nosy=['1','2'])
113+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.issue,
114+
makeForm({'nosy': ''}), nodeid), {'nosy': []})
115+
nodeid = self.db.issue.create(nosy=['1','2'])
116+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.issue,
117+
makeForm({'nosy': ' '}), nodeid), {'nosy': []})
118+
119+
def testInvalidMultilinkValue(self):
120+
# XXX This is not the current behaviour - should we enforce this?
121+
# self.assertRaises(IndexError, client.parsePropsFromForm, self.db,
122+
# self.db.issue, makeForm({'nosy': '4'}))
123+
self.assertRaises(ValueError, client.parsePropsFromForm, self.db,
124+
self.db.issue, makeForm({'nosy': 'frozzle'}))
125+
self.assertRaises(ValueError, client.parsePropsFromForm, self.db,
126+
self.db.issue, makeForm({'nosy': '1,frozzle'}))
127+
# XXX need a test for the TypeError (where the ML class doesn't define a key
128+
129+
def testMultilinkAdd(self):
130+
nodeid = self.db.issue.create(nosy=['1'])
131+
# do nothing
132+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.issue,
133+
makeForm({':add:nosy': ''}), nodeid), {})
134+
135+
# do something ;)
136+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.issue,
137+
makeForm({':add:nosy': '2'}), nodeid), {'nosy': ['1','2']})
138+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.issue,
139+
makeForm({':add:nosy': '2,mary'}), nodeid), {'nosy': ['1','2','4']})
140+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.issue,
141+
makeForm({':add:nosy': ['2','3']}), nodeid), {'nosy': ['1','2','3']})
142+
143+
def testMultilinkAddNew(self):
144+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.issue,
145+
makeForm({':add:nosy': ['2','3']})), {'nosy': ['2','3']})
146+
147+
def testMultilinkRemove(self):
148+
nodeid = self.db.issue.create(nosy=['1','2'])
149+
# do nothing
150+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.issue,
151+
makeForm({':remove:nosy': ''}), nodeid), {})
152+
153+
# do something ;)
154+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.issue,
155+
makeForm({':remove:nosy': '1'}), nodeid), {'nosy': ['2']})
156+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.issue,
157+
makeForm({':remove:nosy': 'admin,2'}), nodeid), {'nosy': []})
158+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.issue,
159+
makeForm({':remove:nosy': ['1','2']}), nodeid), {'nosy': []})
160+
161+
# remove one that doesn't exist?
162+
self.assertRaises(ValueError, client.parsePropsFromForm, self.db,
163+
self.db.issue, makeForm({':remove:nosy': '4'}), nodeid)
164+
165+
#
166+
# Password
167+
#
168+
def testEmptyPassword(self):
169+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.user,
170+
makeForm({'password': ''})), {})
171+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.user,
172+
makeForm({'password': ''})), {})
173+
self.assertRaises(ValueError, client.parsePropsFromForm, self.db,
174+
self.db.user, makeForm({'password': ['', '']}))
175+
self.assertRaises(ValueError, client.parsePropsFromForm, self.db,
176+
self.db.user, makeForm({'password': 'foo',
177+
'password:confirm': ['', '']}))
178+
179+
def testSetPassword(self):
180+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.user,
181+
makeForm({'password': 'foo', 'password:confirm': 'foo'})),
182+
{'password': 'foo'})
183+
184+
def testSetPasswordConfirmBad(self):
185+
self.assertRaises(ValueError, client.parsePropsFromForm, self.db,
186+
self.db.user, makeForm({'password': 'foo'}))
187+
self.assertRaises(ValueError, client.parsePropsFromForm, self.db,
188+
self.db.user, makeForm({'password': 'foo',
189+
'password:confirm': 'bar'}))
190+
191+
def testEmptyPasswordNOTSet(self):
192+
nodeid = self.db.user.create(username='1', password=password.Password('foo'))
193+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.user,
194+
makeForm({'password': ''}), nodeid), {})
195+
nodeid = self.db.user.create(username='2', password=password.Password('foo'))
196+
self.assertEqual(client.parsePropsFromForm(self.db, self.db.user,
197+
makeForm({'password': '', 'password:confirm': ''}), nodeid), {})
198+
199+
200+
def suite():
201+
l = [unittest.makeSuite(FormTestCase),
202+
]
203+
return unittest.TestSuite(l)
204+
205+
206+
# vim: set filetype=python ts=4 sw=4 et si

0 commit comments

Comments
 (0)