Skip to content

Commit f247c96

Browse files
committed
issue2551331 - Fix repeat first/last methods.
The first() and last() methods for a variable defined by tal:repeat now work as documented. There is an undocumented same_part() method for repeat. It is called by first/last and can cause them to return true when not at an end of the Iterator sequence. I wrote a treatise on that function and what it does. I have no idea why it does what it does. Added tests for roundup/cgi/ZTUtils/Iterator.py Also fixes issue with roman() found while writing tests. lower wasn't being called and it was printing the lower() method signature. Doc updates in references.txt to come in a future checkin. Clarifying the repeat methods led to finding/fixing this.
1 parent 8519ae8 commit f247c96

File tree

4 files changed

+164
-4
lines changed

4 files changed

+164
-4
lines changed

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ Fixed:
118118
- issue2551264 - REST X-Total-Count header and @total_size count
119119
incorrect when paginated - correct values are now returned.
120120
(John Rouillard)
121+
- issue2551331 - Fix repeat first/last methods. (John Rouillard)
121122

122123

123124
Features:

roundup/cgi/ZTUtils/Iterator.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,42 @@ def Roman(self, rnvalues=(
8585
return s
8686

8787
def roman(self, lower=lambda x:x.lower):
88-
return lower(self.Roman())
88+
return self.Roman().lower()
8989

9090
def first(self, name=None):
9191
if self.start: return 1
92-
return not self.same_part(name, self._last, self.item)
92+
return self.same_part(name, self._last, self.item)
9393

9494
def last(self, name=None):
9595
if self.end: return 1
96-
return not self.same_part(name, self.item, self._next)
96+
return self.same_part(name, self.item, self._next)
9797

9898
def same_part(self, name, ob1, ob2):
99+
"""I have no idea what this does.
100+
101+
It returns True for first()/last() when there are more items
102+
in the sequence. Called by first() to compare the previous
103+
item in sequence and the current item. Caled by last() to
104+
compare the current item and next item in sequence.
105+
106+
Accepts a string version of the name of an attribute as 'name'.
107+
108+
If no attribute name is provided, return True if the two items:
109+
110+
are equal (==) - duplicate strings/integer/same object
111+
112+
else False.
113+
114+
If a non-existent attribute name is provided return False.
115+
116+
If the attribute is present and the named attributes compare
117+
equal (==) return True else False.
118+
119+
No idea what use case this handles. Maybe this has
120+
something to do with batching and first/last returning True
121+
triggers a new group?
122+
"""
123+
99124
if name is None:
100125
return ob1 == ob2
101126
no = []

roundup/cgi/templating.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2143,7 +2143,7 @@ def field(self, labelfirst=False, y_label=None, n_label=None,
21432143
If not editable, just display the value via plain().
21442144
21452145
In addition to being able to set arbitrary html properties
2146-
using prop=val arguments, the thre arguments:
2146+
using prop=val arguments, the three arguments:
21472147
21482148
y_label, n_label, u_label let you control the labels
21492149
associated with the yes, no (and optionally unknown/empty)

test/test_templating.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from roundup.anypy.cgi_ import FieldStorage, MiniFieldStorage
66
from roundup.cgi.templating import *
7+
from roundup.cgi.ZTUtils.Iterator import Iterator
78
from .test_actions import MockNull, true
89
from .html_norm import NormalizingHtmlParser
910

@@ -1119,6 +1120,139 @@ def test_string_stext(self):
11191120
self.assertEqual(p.stext(), u2s(u'A string with <a href="mailto:[email protected]">[email protected]</a> *embedded* \u00df'))
11201121

11211122

1123+
class ZUtilsTestcase(TemplatingTestCase):
1124+
1125+
def test_Iterator(self):
1126+
"""Test all the iterator functions and properties.
1127+
"""
1128+
sequence = ['one', 'two', '3', 4]
1129+
i = Iterator(sequence)
1130+
for j in [ # element, item, 1st, last, even, odd, number,
1131+
# letter, Letter, roman, Roman
1132+
(1, "one", 1, 0, True, 0, 1, 'a', 'A', 'i', 'I'),
1133+
(1, "two", 0, 0, False, 1, 2, 'b', 'B', 'ii', 'II'),
1134+
(1, "3", 0, 0, True, 0, 3, 'c', 'C', 'iii', 'III'),
1135+
(1, 4, 0, 1, False, 1, 4, 'd', 'D', 'iv', 'IV'),
1136+
# next() fails with 0 when past end of sequence
1137+
# everything else is left at end of sequence
1138+
(0, 4, 0, 1, False, 1, 4, 'd', 'D', 'iv', 'IV'),
1139+
1140+
1141+
]:
1142+
element = i.next() # returns 1 if next item else 0
1143+
print(i.item)
1144+
self.assertEqual(element, j[0])
1145+
self.assertEqual(i.item, j[1])
1146+
self.assertEqual(i.first(), j[2])
1147+
self.assertEqual(i.start, j[2])
1148+
self.assertEqual(i.last(), j[3])
1149+
self.assertEqual(i.end, j[3])
1150+
self.assertIs(i.even(), j[4])
1151+
self.assertEqual(i.odd(), j[5])
1152+
self.assertEqual(i.number(), j[6])
1153+
self.assertEqual(i.index, j[6] - 1)
1154+
self.assertEqual(i.nextIndex, j[6])
1155+
self.assertEqual(i.letter(), j[7])
1156+
self.assertEqual(i.Letter(), j[8])
1157+
self.assertEqual(i.roman(), j[9])
1158+
self.assertEqual(i.Roman(), j[10])
1159+
1160+
class I:
1161+
def __init__(self, name, data):
1162+
self.name = name
1163+
self.data = data
1164+
1165+
sequence = [I('Al', 'd'),
1166+
I('Bob', 'e'),
1167+
I('Bob', 'd'),
1168+
I('Chip', 'd')
1169+
]
1170+
1171+
iterator = iter(sequence)
1172+
1173+
# Iterator is supposed take both sequence and Python iterator.
1174+
for source in [sequence, iterator]:
1175+
i = Iterator(source)
1176+
1177+
element = i.next() # returns 1 if next item else 0
1178+
item1 = i.item
1179+
1180+
# note these can trigger calls by first/last to same_part().
1181+
# It can return true for first/last even when there are more
1182+
# items in the sequence. I am just testing the current
1183+
# implementation. Woe to the person who tries to change
1184+
# Iterator.py.
1185+
1186+
self.assertEqual(element, 1)
1187+
# i.start == 1, so it bypasses name check
1188+
self.assertEqual(i.first(name='namea'), 1)
1189+
self.assertEqual(i.first(name='name'), 1)
1190+
# i.end == 0 so it uses name check in object
1191+
self.assertEqual(i.last(name='namea'), 0)
1192+
self.assertEqual(i.last(name='name'), 0)
1193+
1194+
element = i.next() # returns 1 if next item else 0
1195+
item2 = i.item
1196+
self.assertEqual(element, 1)
1197+
# i.start == 0 so it uses name check
1198+
# between item1 and item2
1199+
self.assertEqual(i.first(name='namea'), 0)
1200+
self.assertEqual(i.first(name='name'), 0)
1201+
# i.end == 0 so it uses name check in object
1202+
# between item2 and the next item item3
1203+
self.assertEqual(i.last(name='namea'), 0)
1204+
self.assertEqual(i.last(name='name'), True)
1205+
1206+
element = i.next() # returns 1 if next item else 0
1207+
item3 = i.item
1208+
self.assertEqual(element, 1)
1209+
# i.start == 0 so it uses name check
1210+
self.assertEqual(i.first(name='namea'), 0)
1211+
self.assertEqual(i.first(name='name'), 1)
1212+
# i.end == 0 so it uses name check in object
1213+
# between item3 and the next item item4
1214+
self.assertEqual(i.last(name='namea'), 0)
1215+
self.assertEqual(i.last(name='name'), 0)
1216+
1217+
element = i.next() # returns 1 if next item else 0
1218+
item4 = i.item
1219+
self.assertEqual(element, 1)
1220+
# i.start == 0 so it uses name check
1221+
self.assertEqual(i.first(name='namea'), 0)
1222+
self.assertEqual(i.first(name='name'), 0)
1223+
# i.end == 0 so it uses name check in object
1224+
# last two object have same name (1)
1225+
self.assertEqual(i.last(name='namea'), 1)
1226+
self.assertEqual(i.last(name='name'), 1)
1227+
1228+
element = i.next() # returns 1 if next item else 0
1229+
self.assertEqual(element, 0)
1230+
1231+
# this is the underlying call for first/last
1232+
# when i.start/i.end are 0
1233+
# use non-existing attribute name, same item
1234+
self.assertIs(i.same_part('namea', item2, item2), False)
1235+
# use correct attribute name
1236+
self.assertIs(i.same_part('name', item2, item2), True)
1237+
# use no attribute name
1238+
self.assertIs(i.same_part(None, item2, item2), True)
1239+
1240+
# use non-existing attribute name, different item
1241+
# non-matching names
1242+
self.assertIs(i.same_part('namea', item1, item2), False)
1243+
# use correct attribute name
1244+
self.assertIs(i.same_part('name', item1, item2), False)
1245+
# use no attribute name
1246+
self.assertIs(i.same_part(None, item1, item2), False)
1247+
1248+
# use non-existing attribute name, different item
1249+
# matching names
1250+
self.assertIs(i.same_part('namea', item2, item3), False)
1251+
# use correct attribute name
1252+
self.assertIs(i.same_part('name', item2, item3), True)
1253+
# use no attribute name
1254+
self.assertIs(i.same_part(None, item2, item3), False)
1255+
11221256
r'''
11231257
class HTMLPermissions:
11241258
def is_edit_ok(self):

0 commit comments

Comments
 (0)