JSLint.
[Plinn.git] / PloneMisc.py
1 ##
2 ## The following code comes from Plone project http://plone.org
3 ##
4 """ Useful utilities function from the Plone project like Batch.
5
6
7
8 """
9
10
11 from __future__ import nested_scopes
12
13 from ZTUtils.Batch import Batch as ZTUBatch
14 from ZTUtils import make_query
15 from ExtensionClass import Base
16
17 # These have to be duplicated from ZTUtils.Batch to use the correct Batch
18 # class, otherwise orphans will come out wrong in the 'show x next messages'.
19 class LazyPrevBatch(Base):
20 def __of__(self, parent):
21 return Batch(parent._sequence, parent._size,
22 parent.first - parent._size + parent.overlap, 0,
23 parent.orphan, parent.overlap)
24
25 class LazyNextBatch(Base):
26 def __of__(self, parent):
27 try: parent._sequence[parent.end]
28 except IndexError: return None
29 return Batch(parent._sequence, parent._size,
30 parent.end - parent.overlap, 0,
31 parent.orphan, parent.overlap)
32
33 class LazySequenceLength(Base):
34 def __of__(self, parent):
35 parent.sequence_length = l = len(parent._sequence)
36 return l
37
38 class Batch(ZTUBatch):
39 """Create a sequence batch"""
40 __allow_access_to_unprotected_subobjects__ = 1
41
42 previous = LazyPrevBatch()
43 next = LazyNextBatch()
44 sequence_length = LazySequenceLength()
45
46 size = first= start = end = orphan = overlap = navlist = None
47 numpages = pagenumber = pagerange = pagerangeend = pagerangestart = pagenumber = quantumleap = None
48
49 def __init__( self
50 , sequence
51 , size
52 , start=0
53 , end=0
54 , orphan=0
55 , overlap=0
56 , pagerange=7
57 , quantumleap=0
58 , b_start_str='b_start'
59 , before_getitem=lambda item: item
60 , ):
61 """ Encapsulate sequence in batches of size
62 sequence - the data to batch.
63 size - the number of items in each batch. This will be computed if left out.
64 start - the first element of sequence to include in batch (0-index)
65 end - the last element of sequence to include in batch (0-index, optional)
66 orphan - the next page will be combined with the current page if it does not contain more than orphan elements
67 overlap - the number of overlapping elements in each batch
68 pagerange - the number of pages to display in the navigation
69 quantumleap - 0 or 1 to indicate if bigger increments should be used in the navigation list for big results.
70 b_start_str - the request variable used for start, default 'b_start'
71 before_getitem - function that compute the item before getting it
72 """
73 start = start + 1
74
75 start,end,sz = opt(start,end,size,orphan,sequence)
76
77 self._sequence = sequence
78 self.size = sz
79 self._size = size
80 self.start = start
81 self.end = end
82 self.orphan = orphan
83 self.overlap = overlap
84 self.first = max(start - 1, 0)
85 self.length = self.end - self.first
86
87 self.b_start_str = b_start_str
88
89 self.last = self.sequence_length - size
90
91 # Set up next and previous
92 if self.first == 0:
93 self.previous = None
94
95 # Set up the total number of pages
96 self.numpages = calculate_pagenumber(self.sequence_length - self.orphan, self.size, self.overlap)
97
98 # Set up the current page number
99 self.pagenumber = calculate_pagenumber(self.start, self.size, self.overlap)
100
101 # Set up pagerange for the navigation quick links
102 self.pagerange, self.pagerangestart, self.pagerangeend = calculate_pagerange(self.pagenumber,self.numpages,pagerange)
103
104 # Set up the lists for the navigation: 4 5 [6] 7 8
105 # navlist is the complete list, including pagenumber
106 # prevlist is the 4 5 in the example above
107 # nextlist is 7 8 in the example above
108 self.navlist = self.prevlist = self.nextlist = []
109 if self.pagerange and self.numpages >= 1:
110 self.navlist = range(self.pagerangestart, self.pagerangeend)
111 self.prevlist = range(self.pagerangestart, self.pagenumber)
112 self.nextlist = range(self.pagenumber + 1, self.pagerangeend)
113
114 # QuantumLeap - faster navigation for big result sets
115 self.quantumleap = quantumleap
116 self.leapback = self.leapforward = []
117 if self.quantumleap:
118 self.leapback = calculate_leapback(self.pagenumber, self.numpages, self.pagerange)
119 self.leapforward = calculate_leapforward(self.pagenumber, self.numpages, self.pagerange)
120
121 # comptute item before getting it
122 self.before_getitem = before_getitem
123
124 def __getitem__(self, index):
125 if index < 0:
126 if index + self.end < self.first: raise IndexError, index
127 item = self._sequence[index + self.end]
128 return self.before_getitem(item)
129
130 if index >= self.length: raise IndexError, index
131 item = self._sequence[index+self.first]
132 return self.before_getitem(item)
133
134 def pageurl(self, formvariables, pagenumber=-1):
135 """ Makes the url for a given page """
136 if pagenumber == -1:
137 pagenumber = self.pagenumber
138 b_start = pagenumber * (self.size - self.overlap) - self.size
139 return make_query(formvariables, {self.b_start_str:b_start})
140
141 def navurls(self, formvariables, navlist=[]):
142 """ Returns the page number and url for the navigation quick links """
143 if not navlist: navlist = self.navlist
144 return map(lambda x, formvariables = formvariables: (x, self.pageurl(formvariables, x)), navlist)
145
146 def prevurls(self, formvariables):
147 """ Helper method to get prev navigation list from templates """
148 return self.navurls(formvariables, self.prevlist)
149
150 def nexturls(self, formvariables):
151 """ Helper method to get next navigation list from templates """
152 return self.navurls(formvariables, self.nextlist)
153
154 # Calculate start, end, batchsize
155 # This is copied from ZTUtils.Batch.py because orphans were not correct there.
156 # 04/16/04 modified by Danny Bloemendaal (_ender_). Removed try/except structs because
157 # in some situations they cause some unexpected problems. Also fixed some problems with the orphan stuff. Seems to work now.
158 def opt(start,end,size,orphan,sequence):
159 length = len(sequence)
160 if size < 1:
161 if start > 0 and end > 0 and end >= start:
162 size = end + 1 - start
163 else: size = 25
164 if start > 0:
165 if start>length:
166 start = length
167 if end > 0:
168 if end < start: end = start
169 else:
170 end = start + size - 1
171 if (end+orphan)>=length:
172 end = length
173 elif end > 0:
174 if (end)>length:
175 end = length
176 start = end + 1 - size
177 if start - 1 < orphan: start = 1
178 else:
179 start = 1
180 end = start + size - 1
181 if (end+orphan)>=length:
182 end = length
183 return start,end,size
184
185 def calculate_pagenumber(elementnumber, batchsize, overlap=0):
186 """ Calculate the pagenumber for the navigation """
187 # To find first element in a page,
188 # elementnumber = pagenumber * (size - overlap) - size (- orphan?)
189 try:
190 pagenumber,remainder = divmod(elementnumber, batchsize - overlap)
191 except ZeroDivisionError:
192 pagenumber, remainder = divmod(elementnumber, 1)
193 if remainder > overlap:
194 pagenumber = pagenumber + 1
195 pagenumber = max(pagenumber, 1)
196 return pagenumber
197
198 def calculate_pagerange(pagenumber, numpages, pagerange):
199 """ Calculate the pagerange for the navigation quicklinks """
200 # Pagerange is the number of pages linked to in the navigation, odd number
201 pagerange = max(0 , pagerange + pagerange % 2 - 1)
202 # Making sure the list will not start with negative values
203 pagerangestart = max ( 1, pagenumber - (pagerange - 1 ) / 2 )
204 # Making sure the list does not expand beyond the last page
205 pagerangeend = min ( pagenumber + (pagerange - 1 ) / 2, numpages) + 1
206 return pagerange, pagerangestart, pagerangeend
207
208 def calculate_quantum_leap_gap(numpages, pagerange):
209 """ Find the QuantumLeap gap. Current width of list is 6 clicks (30/5) """
210 return int(max(1,round(float(numpages - pagerange)/30))*5)
211
212 def calculate_leapback(pagenumber, numpages, pagerange):
213 """ Check the distance between start and 0 and add links as necessary """
214 leapback = []
215 quantum_leap_gap = calculate_quantum_leap_gap(numpages, pagerange)
216 num_back_leaps = max(0,min(3, int(round(float(pagenumber - pagerange)/quantum_leap_gap) - 0.3)))
217 if num_back_leaps:
218 pagerange, pagerangestart, pagerangeend = calculate_pagerange( pagenumber, numpages, pagerange)
219 leapback = range(pagerangestart - num_back_leaps * quantum_leap_gap, pagerangestart, quantum_leap_gap)
220 return leapback
221
222 def calculate_leapforward(pagenumber, numpages, pagerange):
223 """ Check the distance between end and length and add links as necessary """
224 leapforward = []
225 quantum_leap_gap = calculate_quantum_leap_gap(numpages, pagerange)
226 num_forward_leaps = max(0,min(3, int(round(float(numpages - pagenumber - pagerange)/quantum_leap_gap) - 0.3)))
227 if num_forward_leaps:
228 pagerange, pagerangestart, pagerangeend = calculate_pagerange( pagenumber, numpages, pagerange)
229 leapforward = range(pagerangeend-1 + quantum_leap_gap, pagerangeend-1 + (num_forward_leaps+1) * quantum_leap_gap, quantum_leap_gap)
230 return leapforward
231
232
233 class IndexIterator:
234 __allow_access_to_unprotected_subobjects__ = 1
235
236 def __init__(self, upper=100000, pos=0):
237 self.upper=upper
238 self.pos=pos
239
240 def next(self):
241 if self.pos <= self.upper:
242 self.pos += 1
243 return self.pos
244 raise KeyError, 'Reached upper bounds'