3 @@TR: This code is pretty much unsupported.
5 MondoReport.py -- Batching module for Python and Cheetah.
7 Version 2001-Nov-18. Doesn't do much practical yet, but the companion
8 testMondoReport.py passes all its tests.
11 TODO: BatchRecord.prev/next/prev_batches/next_batches/query, prev.query,
14 How about Report: .page(), .all(), .summary()? Or PageBreaker.
16 import operator
, types
18 from Cheetah
.NameMapper
import valueForKey
as lookup_func
20 def lookup_func(obj
, name
):
21 if hasattr(obj
, name
):
22 return getattr(obj
, name
)
24 return obj
[name
] # Raises KeyError.
26 ########## CONSTANTS ##############################
28 True, False = (1==1), (1==0)
29 numericTypes
= types
.IntType
, types
.LongType
, types
.FloatType
31 ########## PUBLIC GENERIC FUNCTIONS ##############################
33 class NegativeError(ValueError):
37 return type(v
) in numericTypes
42 raise NegativeError(v
)
48 n
= int(n
) # Raises TypeError.
50 raise ValueError("roman numeral for zero or negative undefined: " + n
)
70 while n
< 5 and n
>= 1:
73 roman
= roman
.replace('DCCCC', 'CM')
74 roman
= roman
.replace('CCCC', 'CD')
75 roman
= roman
.replace('LXXXX', 'XC')
76 roman
= roman
.replace('XXXX', 'XL')
77 roman
= roman
.replace('VIIII', 'IX')
78 roman
= roman
.replace('IIII', 'IV')
83 return reduce(operator
.add
, lis
, 0)
86 """Always returns a floating-point number.
90 return 0.00 # Avoid ZeroDivisionError (not raised for floats anyway)
91 total
= float( sum(lis
) )
92 return total
/ lis_len
97 return lis
[int(len(lis
)/2)]
101 raise NotImplementedError()
104 raise NotImplementedError()
106 def standardDeviation(lis
):
107 raise NotImplementedError()
109 def standardDeviation_n(lis
):
110 raise NotImplementedError()
115 """Eight ways to display a subscript index.
116 ("Fifty ways to leave your lover....")
118 def __init__(self
, index
, item
=None):
120 self
._number
= index
+ 1
132 return self
._number
% 2 == 0
135 return not self
.even()
138 return self
._index
% 2 == 0
141 return not self
.even_i()
144 return self
.Letter().lower()
147 n
= ord('A') + self
._index
151 return self
.Roman().lower()
154 return Roman(self
._number
)
161 ########## PRIVATE CLASSES ##############################
163 class ValuesGetterMixin
:
164 def __init__(self
, origList
):
165 self
._origList
= origList
167 def _getValues(self
, field
=None, criteria
=None):
169 ret
= [lookup_func(elm
, field
) for elm
in self
._origList
]
173 ret
= filter(criteria
, ret
)
177 class RecordStats(IndexFormats
, ValuesGetterMixin
):
178 """The statistics that depend on the current record.
180 def __init__(self
, origList
, index
):
181 record
= origList
[index
] # Raises IndexError.
182 IndexFormats
.__init
__(self
, index
, record
)
183 ValuesGetterMixin
.__init
__(self
, origList
)
186 return len(self
._origList
)
189 return self
._index
== 0
192 return self
._index
>= len(self
._origList
) - 1
194 def _firstOrLastValue(self
, field
, currentIndex
, otherIndex
):
195 currentValue
= self
._origList
[currentIndex
] # Raises IndexError.
197 otherValue
= self
._origList
[otherIndex
]
201 currentValue
= lookup_func(currentValue
, field
)
202 otherValue
= lookup_func(otherValue
, field
)
203 return currentValue
!= otherValue
205 def firstValue(self
, field
=None):
206 return self
._firstOrLastValue
(field
, self
._index
, self
._index
- 1)
208 def lastValue(self
, field
=None):
209 return self
._firstOrLastValue
(field
, self
._index
, self
._index
+ 1)
211 # firstPage and lastPage not implemented. Needed?
213 def percentOfTotal(self
, field
=None, suffix
='%', default
='N/A', decimals
=2):
214 rec
= self
._origList
[self
._index
]
216 val
= lookup_func(rec
, field
)
220 lis
= self
._getValues
(field
, isNumeric
)
221 except NegativeError
:
224 if total
== 0.00: # Avoid ZeroDivisionError.
228 percent
= (val
/ total
) * 100
229 except ZeroDivisionError:
232 percent
= int(percent
)
234 percent
= round(percent
, decimals
)
236 return str(percent
) + suffix
# String.
238 return percent
# Numeric.
240 def __call__(self
): # Overrides IndexFormats.__call__
241 """This instance is not callable, so we override the super method.
243 raise NotImplementedError()
249 length
= self
.length()
250 start
= self
._index
- length
251 return PrevNextPage(self
._origList
, length
, start
)
254 if self
._index
+ self
.length() == self
.length():
257 length
= self
.length()
258 start
= self
._index
+ length
259 return PrevNextPage(self
._origList
, length
, start
)
262 raise NotImplementedError()
265 raise NotImplementedError()
267 prev_batches
= prevPages
268 next_batches
= nextPages
271 raise NotImplementedError()
275 def _prevNextHelper(self
, start
,end
,size
,orphan
,sequence
):
276 """Copied from Zope's DT_InSV.py's "opt" function.
279 if start
> 0 and end
> 0 and end
>= start
:
285 try: sequence
[start
-1]
286 except: start
=len(sequence
)
287 # if start > l: start=l
290 if end
< start
: end
=start
293 try: sequence
[end
+orphan
-1]
294 except: end
=len(sequence
)
295 # if l - end < orphan: end=l
298 except: end
=len(sequence
)
301 if start
- 1 < orphan
: start
=1
305 try: sequence
[end
+orphan
-1]
306 except: end
=len(sequence
)
307 # if l - end < orphan: end=l
308 return start
,end
,size
312 class Summary(ValuesGetterMixin
):
313 """The summary statistics, that don't depend on the current record.
315 def __init__(self
, origList
):
316 ValuesGetterMixin
.__init
__(self
, origList
)
318 def sum(self
, field
=None):
319 lis
= self
._getValues
(field
, isNumeric
)
324 def count(self
, field
=None):
325 lis
= self
._getValues
(field
, isNotNone
)
328 def min(self
, field
=None):
329 lis
= self
._getValues
(field
, isNotNone
)
330 return min(lis
) # Python builtin function min.
332 def max(self
, field
=None):
333 lis
= self
._getValues
(field
, isNotNone
)
334 return max(lis
) # Python builtin function max.
336 def mean(self
, field
=None):
337 """Always returns a floating point number.
339 lis
= self
._getValues
(field
, isNumeric
)
344 def median(self
, field
=None):
345 lis
= self
._getValues
(field
, isNumeric
)
348 def variance(self
, field
=None):
349 raiseNotImplementedError()
351 def variance_n(self
, field
=None):
352 raiseNotImplementedError()
354 def standardDeviation(self
, field
=None):
355 raiseNotImplementedError()
357 def standardDeviation_n(self
, field
=None):
358 raiseNotImplementedError()
362 def __init__(self
, origList
, size
, start
):
364 self
.start
= IndexFormats(start
, origList
[start
])
365 self
.end
= IndexFormats(end
, origList
[end
])
369 ########## MAIN PUBLIC CLASS ##############################
371 _RecordStatsClass
= RecordStats
372 _SummaryClass
= Summary
374 def __init__(self
, origlist
):
375 self
._origList
= origlist
377 def page(self
, size
, start
, overlap
=0, orphan
=0):
378 """Returns list of ($r, $a, $b)
381 raise NotImplementedError("non-zero overlap")
383 raise NotImplementedError("non-zero orphan")
384 origList
= self
._origList
385 origList_len
= len(origList
)
386 start
= max(0, start
)
387 end
= min( start
+ size
, len(self
._origList
) )
388 mySlice
= origList
[start
:end
]
390 for rel
in range(size
):
393 a
= self
._RecordStatsClass
(origList
, abs_
)
394 b
= self
._RecordStatsClass
(mySlice
, rel
)
403 origList_len
= len(self
._origList
)
404 return self
.page(origList_len
, 0, 0, 0)
408 return self
._SummaryClass
(self
._origList
)
411 **********************************
412 Return a pageful of records from a sequence, with statistics.
414 in : origlist, list or tuple. The entire set of records. This is
415 usually a list of objects or a list of dictionaries.
416 page, int >= 0. Which page to display.
417 size, int >= 1. How many records per page.
418 widow, int >=0. Not implemented.
419 orphan, int >=0. Not implemented.
420 base, int >=0. Number of first page (usually 0 or 1).
422 out: list of (o, b) pairs. The records for the current page. 'o' is
423 the original element from 'origlist' unchanged. 'b' is a Batch
424 object containing meta-info about 'o'.
425 exc: IndexError if 'page' or 'size' is < 1. If 'origlist' is empty or
426 'page' is too high, it returns an empty list rather than raising
429 origlist_len = len(origlist)
430 start = (page + base) * size
431 end = min(start + size, origlist_len)
433 # widow, orphan calculation: adjust 'start' and 'end' up and down,
434 # Set 'widow', 'orphan', 'first_nonwidow', 'first_nonorphan' attributes.
435 for i in range(start, end):
437 b = Batch(origlist, size, i)
443 # return a PrevNextPage or None
446 # return a PrevNextPage or None
448 def prev_batches(self):
449 # return a list of SimpleBatch for the previous batches
451 def next_batches(self):
452 # return a list of SimpleBatch for the next batches
454 ########## PUBLIC MIXIN CLASS FOR CHEETAH TEMPLATES ##############
455 class MondoReportMixin:
456 def batch(self, origList, size=None, start=0, overlap=0, orphan=0):
457 bat = MondoReport(origList)
458 return bat.batch(size, start, overlap, orphan)
459 def batchstats(self, origList):
460 bat = MondoReport(origList)
464 # vim: shiftwidth=4 tabstop=4 expandtab textwidth=79