[3] | 1 | MondoReport Documentation |
---|
| 2 | Version 0.01 alpha 24-Nov-2001. iron@mso.oz.net or mso@oz.net. |
---|
| 3 | Copyright (c) 2001 Mike Orr. License: same as Python or Cheetah. |
---|
| 4 | |
---|
| 5 | * * * * * |
---|
| 6 | STATUS: previous/next batches and query string are not implemented yet. |
---|
| 7 | Sorting not designed yet. Considering "click on this column header to sort by |
---|
| 8 | this field" and multiple ascending/descending sort fields for a future version. |
---|
| 9 | |
---|
| 10 | Tested with Python 2.2b1. May work with Python 2.1 or 2.0. |
---|
| 11 | |
---|
| 12 | * * * * * |
---|
| 13 | OVERVIEW |
---|
| 14 | |
---|
| 15 | MondoReport -- provide information about a list that is useful in generating |
---|
| 16 | any kind of report. The module consists of one main public class, and some |
---|
| 17 | generic functions you may find useful in other programs. This file contains an |
---|
| 18 | overview, syntax reference and examples. The module is designed both for |
---|
| 19 | standalone use and for integration with the Cheetah template system |
---|
| 20 | (http://www.cheetahtemplate.org/), so the examples are in both Python and |
---|
| 21 | Cheetah. The main uses of MondoReport are: |
---|
| 22 | |
---|
| 23 | (A) to iterate through a list. In this sense MR is a for-loop enhancer, |
---|
| 24 | providing information that would be verbose to calculate otherwise. |
---|
| 25 | |
---|
| 26 | (B) to separate a list into equal-size "pages" (or "batches"--the two terms are |
---|
| 27 | interchangeable) and only display the current page, plus limited information |
---|
| 28 | about the previous and next pages. |
---|
| 29 | |
---|
| 30 | (C) to extract summary statistics about a certain column ("field") in the list. |
---|
| 31 | |
---|
| 32 | * * * * * |
---|
| 33 | MAIN PUBLIC CLASS |
---|
| 34 | |
---|
| 35 | To create a MondoReport instance, supply a list to operate on. |
---|
| 36 | |
---|
| 37 | mr = MondoReport(origList) |
---|
| 38 | |
---|
| 39 | The list may be a list of anything, but if you use the 'field' argument in any |
---|
| 40 | of the methods below, the elements must be instances or dictionaries. |
---|
| 41 | |
---|
| 42 | MondoReport assumes it's operating on an unchanging list. Do not modify the |
---|
| 43 | list or any of its elements until you are completely finished with the |
---|
| 44 | ModoReport object and its sub-objects. Otherwise, you may get an exception or |
---|
| 45 | incorrect results. |
---|
| 46 | |
---|
| 47 | MondoReport instances have three methods: |
---|
| 48 | |
---|
| 49 | .page(size, start, overlap=0, orphan=0 |
---|
| 50 | sort=None, reverse=False) => list of (r, a, b). |
---|
| 51 | |
---|
| 52 | 'size' is an integer >= 1. 'start', 'overlap' and 'orphan' are integers >= 0. |
---|
| 53 | The list returned contains one triple for each record in the current page. 'r' |
---|
| 54 | is the original record. 'a' is a BatchRecord instance for the current record |
---|
| 55 | in relation to all records in the origList. 'b' is a BatchRecord instance for |
---|
| 56 | the current record in relation to all the records in that batch/page. (There |
---|
| 57 | is a .batch method that's identical to .page.) |
---|
| 58 | |
---|
| 59 | The other options aren't implemented yet, but 'overlap' duplicates this many |
---|
| 60 | records on adjacent batches. 'orphan' moves this many records or fewer, if |
---|
| 61 | they are on a page alone, onto the neighboring page. 'sort' (string) specifies |
---|
| 62 | a field to sort the records by. It may be suffixed by ":desc" to sort in |
---|
| 63 | descending order. 'reverse' (boolean) reverses the sort order. If both |
---|
| 64 | ":desc" and 'reverse' are specified, they will cancel each other out. This |
---|
| 65 | sorting/reversal happens on a copy of the origList, and all objects returned |
---|
| 66 | by this method use the sorted list, except when resorting the next time. |
---|
| 67 | To do more complicated sorting, such as a hierarchy of columns, do it to the |
---|
| 68 | original list before creating the ModoReport object. |
---|
| 69 | |
---|
| 70 | .all(sort=None, reverse=False) => list of (r, a). |
---|
| 71 | |
---|
| 72 | Same, but the current page spans the entire origList. |
---|
| 73 | |
---|
| 74 | .summary() => Summary instance. |
---|
| 75 | |
---|
| 76 | Summary statistics for the entire origList. |
---|
| 77 | |
---|
| 78 | In Python, use .page or .all in a for loop: |
---|
| 79 | |
---|
| 80 | from Cheetah.Tools.MondoReport import MondoReport |
---|
| 81 | mr = MondoReport(myList) |
---|
| 82 | for r, a, b in mr.page(20, 40): |
---|
| 83 | # Do something with r, a and b. The current page is the third page, |
---|
| 84 | # with twenty records corresponding to origList[40:60]. |
---|
| 85 | if not myList: |
---|
| 86 | # Warn the user there are no records in the list. |
---|
| 87 | |
---|
| 88 | It works the same way in Cheetah, just convert to Cheetah syntax. This example |
---|
| 89 | assumes the template doubles as a Webware servlet, so we use the servlet's |
---|
| 90 | '$request' method to look up the CGI parameter 'start'. The default value is 0 |
---|
| 91 | for the first page. |
---|
| 92 | |
---|
| 93 | #from Cheetah.Tools.MondoReport import MondoReport |
---|
| 94 | #set $mr = $MondoReport($bigList) |
---|
| 95 | #set $start = $request.field("start", 0) |
---|
| 96 | #for $o, $a, $b in $mr.page(20, $start) |
---|
| 97 | ... do something with $o, $a and $b ... |
---|
| 98 | #end for |
---|
| 99 | #unless $bigList |
---|
| 100 | This is displayed if the original list has no elements. |
---|
| 101 | It's equivalent to the "else" part Zope DTML's <dtml-in>. |
---|
| 102 | #end unless |
---|
| 103 | |
---|
| 104 | * * * * * |
---|
| 105 | USING 'r' RECORDS |
---|
| 106 | |
---|
| 107 | Use 'r' just as you would the original element. For instance: |
---|
| 108 | |
---|
| 109 | print r.attribute # If r is an instance. |
---|
| 110 | print r['key'] # If r is a dictionary. |
---|
| 111 | print r # If r is numeric or a string. |
---|
| 112 | |
---|
| 113 | In Cheetah, you can take advantage of Universal Dotted Notation and autocalling: |
---|
| 114 | |
---|
| 115 | $r.name ## 'name' may be an attribute or key of 'r'. If 'r' and/or |
---|
| 116 | ## 'name' is a function or method, it will be called without |
---|
| 117 | ## arguments. |
---|
| 118 | $r.attribute |
---|
| 119 | $r['key'] |
---|
| 120 | $r |
---|
| 121 | $r().attribute()['key']() |
---|
| 122 | |
---|
| 123 | If origList is a list of name/value pairs (2-tuples or 2-lists), you may |
---|
| 124 | prefer to do this: |
---|
| 125 | |
---|
| 126 | for (key, value), a, b in mr.page(20, 40): |
---|
| 127 | print key, "=>", value |
---|
| 128 | |
---|
| 129 | #for ($key, $value), $a, $b in $mr.page(20, $start) |
---|
| 130 | $key => $value |
---|
| 131 | #end for |
---|
| 132 | |
---|
| 133 | * * * * * |
---|
| 134 | STATISTICS METHODS AND FIELD VALUES |
---|
| 135 | |
---|
| 136 | Certain methods below have an optional argument 'field'. If specified, |
---|
| 137 | MondoReport will look up that field in each affected record and use its value |
---|
| 138 | in the calculation. MondoReport uses Cheetah's NameMapper if available, |
---|
| 139 | otherwise it uses a minimal NameMapper substitute that looks for an attribute |
---|
| 140 | or dictionary key called "field". You'll get an exception if any record is a |
---|
| 141 | type without attributes or keys, or if one or more records is missing that |
---|
| 142 | attribute/key. |
---|
| 143 | |
---|
| 144 | If 'field' is None, MondoReport will use the entire record in its |
---|
| 145 | calculation. This makes sense mainly if the records are a numeric type. |
---|
| 146 | |
---|
| 147 | All statistics methods filter out None values from their calculations, and |
---|
| 148 | reduce the number of records accordingly. Most filter out non-numeric fields |
---|
| 149 | (or records). Some raise NegativeError if a numeric field (or record) is |
---|
| 150 | negative. |
---|
| 151 | |
---|
| 152 | |
---|
| 153 | * * * * * |
---|
| 154 | BatchRecord METHODS |
---|
| 155 | |
---|
| 156 | The 'a' and 'b' objects of MondoReport.page() and MondoReport.all() provide |
---|
| 157 | these methods. |
---|
| 158 | |
---|
| 159 | .index() |
---|
| 160 | |
---|
| 161 | The current subscript. For 'a', this is the true subscript into origList. |
---|
| 162 | For 'b', this is relative to the current page, so the first record will be 0. |
---|
| 163 | Hint: In Cheetah, use autocalling to skip the parentheses: '$b.index'. |
---|
| 164 | |
---|
| 165 | .number() |
---|
| 166 | |
---|
| 167 | The record's position starting from 1. This is always '.index() + 1'. |
---|
| 168 | |
---|
| 169 | .Letter() |
---|
| 170 | |
---|
| 171 | The letter ("A", "B", "C") corresponding to .number(). Undefined if .number() |
---|
| 172 | > 26. The current implementation just adds the offset to 'a' and returns |
---|
| 173 | whatever character it happens to be. |
---|
| 174 | |
---|
| 175 | To make a less dumb implementation (e.g., "Z, AA, BB" or "Z, A1, B1"): |
---|
| 176 | 1) Subclass BatchRecord and override the .Letter method. |
---|
| 177 | 2) Subclass MondoReport and set the class variable .BatchRecordClass to your |
---|
| 178 | new improved class. |
---|
| 179 | |
---|
| 180 | .letter() |
---|
| 181 | |
---|
| 182 | Same but lower case. |
---|
| 183 | |
---|
| 184 | .Roman() |
---|
| 185 | |
---|
| 186 | The Roman numeral corresponding to .number(). |
---|
| 187 | |
---|
| 188 | .roman() |
---|
| 189 | |
---|
| 190 | Same but lower case. |
---|
| 191 | |
---|
| 192 | .even() |
---|
| 193 | |
---|
| 194 | True if .number() is even. |
---|
| 195 | |
---|
| 196 | .odd() |
---|
| 197 | |
---|
| 198 | True if .number() is odd. |
---|
| 199 | |
---|
| 200 | .even_i() |
---|
| 201 | |
---|
| 202 | True if .index() is even. |
---|
| 203 | |
---|
| 204 | .odd_i() |
---|
| 205 | |
---|
| 206 | True if .index() is odd. |
---|
| 207 | |
---|
| 208 | .length() |
---|
| 209 | |
---|
| 210 | For 'a', number of records in origList. For 'b', number of records on this |
---|
| 211 | page. |
---|
| 212 | |
---|
| 213 | .item() |
---|
| 214 | |
---|
| 215 | The record itself. You don't need this in the normal case since it's the same |
---|
| 216 | as 'r', but it's useful for previous/next batches. |
---|
| 217 | |
---|
| 218 | .size() |
---|
| 219 | |
---|
| 220 | The 'size' argument used when this BatchRecord was created. |
---|
| 221 | 'a.size() == b.size()'. |
---|
| 222 | |
---|
| 223 | .first() |
---|
| 224 | |
---|
| 225 | True if this is the first record. |
---|
| 226 | |
---|
| 227 | .last() |
---|
| 228 | |
---|
| 229 | True if this is the last record. |
---|
| 230 | |
---|
| 231 | .firstValue(field=None) |
---|
| 232 | |
---|
| 233 | True if there is no previous record, or if the previous field/record has a |
---|
| 234 | different value. Used for to print section headers. For instance, if you |
---|
| 235 | are printing addresses by country, this will be true at the first occurrance |
---|
| 236 | of each country. Or for indexes, you can have a non-printing field showing |
---|
| 237 | which letter of the alphablet this entry starts with, and then print a "B" |
---|
| 238 | header before printing the first record starting with "B". |
---|
| 239 | |
---|
| 240 | .lastValue(field=None) |
---|
| 241 | |
---|
| 242 | True if this is the last record containing the current value in the |
---|
| 243 | field/record. |
---|
| 244 | |
---|
| 245 | .percentOfTotal(field=None, suffix="%", default="N/A", decimals=2) |
---|
| 246 | |
---|
| 247 | Returns the percent that the current field/record is of all fields/records. |
---|
| 248 | If 'suffix' is None, returns a number; otherwise it returns a string with |
---|
| 249 | 'suffix' suffixed. If the current value is non-numeric, returns 'default' |
---|
| 250 | instead (without 'suffix'). 'decimals' tells the number of decimal places to |
---|
| 251 | return; if 0, there will be no decimal point. |
---|
| 252 | |
---|
| 253 | .prev() |
---|
| 254 | |
---|
| 255 | Returns a PrevNextBatch instance for the previous page. If there is no |
---|
| 256 | previous page, returns None. [Not implemented yet.] |
---|
| 257 | |
---|
| 258 | .next() |
---|
| 259 | |
---|
| 260 | Returns a PrevNextBatch instance for the next page. If there is no next page, |
---|
| 261 | returns None. [Not implemented yet.] |
---|
| 262 | |
---|
| 263 | .prevPages() |
---|
| 264 | |
---|
| 265 | Returns a list of PrevNextPage instances for every previous page, or [] if no |
---|
| 266 | previous pages. [Not implemented yet.] |
---|
| 267 | |
---|
| 268 | .nextPages() |
---|
| 269 | |
---|
| 270 | Returns a list of PrevNextPage instances for every next page, or [] if no next |
---|
| 271 | pages. [Not implemented yet.] |
---|
| 272 | |
---|
| 273 | .query(start=None, label=None, attribName="start", attribs=[]) |
---|
| 274 | |
---|
| 275 | [Not implemented yet.] |
---|
| 276 | |
---|
| 277 | With no arguments, returns the HTML query string with start value removed (so |
---|
| 278 | you can append a new start value in your hyperlink). The query string is taken |
---|
| 279 | from the 'QUERY_STRING' environmental variable, or "" if missing. (This is |
---|
| 280 | Webware compatible.) |
---|
| 281 | |
---|
| 282 | With 'start' (an integer >= 0), returns the query string with an updated start |
---|
| 283 | value, normally for the next or previous batch. |
---|
| 284 | |
---|
| 285 | With 'label' (a string), returns a complete HTML hyperlink: |
---|
| 286 | '<A HREF="?new_query_string">label</A>'. You'll get a TypeError if you specify |
---|
| 287 | 'label' but not 'start'. |
---|
| 288 | |
---|
| 289 | With 'attribName' (a string), uses this attribute name rather than "start". |
---|
| 290 | Useful if you have another CGI parameter "start" that's used for something |
---|
| 291 | else. |
---|
| 292 | |
---|
| 293 | With 'attribs' (a dictionary), adds these attributes to the hyperlink. |
---|
| 294 | For instance, 'attribs={"target": "_blank"}'. Ignored unless 'label' is |
---|
| 295 | specified too. |
---|
| 296 | |
---|
| 297 | This method assumes the start parameter is a GET variable, not a POST variable. |
---|
| 298 | |
---|
| 299 | .summary() |
---|
| 300 | |
---|
| 301 | Returns a Summary instance. 'a.summary()' refers to all records in the |
---|
| 302 | origList, so it's the same as MondoReport.summary(). 'b.summary()' refers only |
---|
| 303 | to the records on the current page. [Not implemented yet.] |
---|
| 304 | |
---|
| 305 | * * * * * |
---|
| 306 | PrevNextPage INSTANCES |
---|
| 307 | |
---|
| 308 | [Not implemented yet.] |
---|
| 309 | |
---|
| 310 | PrevNextPage instances have the following methods: |
---|
| 311 | |
---|
| 312 | .start() |
---|
| 313 | |
---|
| 314 | The index (true index of origList) that that page starts at. You may also use |
---|
| 315 | '.start().index()', '.start().number()', etc. Also |
---|
| 316 | '.start().item(field=None)'. (Oh, so *that*'s what .item is for!) |
---|
| 317 | |
---|
| 318 | .end() |
---|
| 319 | |
---|
| 320 | The index (true index of origList) that that page ends at. You may also use |
---|
| 321 | '.end().index()', '.end().number()', etc. Also |
---|
| 322 | '.end().item(field=None)'. |
---|
| 323 | |
---|
| 324 | .length() |
---|
| 325 | |
---|
| 326 | Number of records on that page. |
---|
| 327 | |
---|
| 328 | .query(label=None, attribName="start", attribs={}, before="", after="") |
---|
| 329 | |
---|
| 330 | [Not implemented yet.] |
---|
| 331 | |
---|
| 332 | Similar to 'a.query()' and 'b.query()', but automatically calculates the start |
---|
| 333 | value for the appropriate page. |
---|
| 334 | |
---|
| 335 | For fancy HTML formatting, 'before' is prepended to the returned text and |
---|
| 336 | 'after' is appended. (There was an argument 'else_' for if there is no such |
---|
| 337 | batch, but it was removed because you can't even get to this method at all in |
---|
| 338 | that case.) |
---|
| 339 | |
---|
| 340 | * * * * * * |
---|
| 341 | SUMMARY STATISTICS |
---|
| 342 | |
---|
| 343 | These methods are supported by the Summary instances returned by |
---|
| 344 | MondoReport.Summary(): |
---|
| 345 | |
---|
| 346 | .sum(field=None) |
---|
| 347 | |
---|
| 348 | Sum of all numeric values in a field, or sum of all records. |
---|
| 349 | |
---|
| 350 | .total(field=None) |
---|
| 351 | |
---|
| 352 | Same. |
---|
| 353 | |
---|
| 354 | .count(field=None) |
---|
| 355 | |
---|
| 356 | Number of fields/records with non-None values. |
---|
| 357 | |
---|
| 358 | .min(field=None) |
---|
| 359 | |
---|
| 360 | Minimum value in that field/record. Ignores None values. |
---|
| 361 | |
---|
| 362 | .max(field=None) |
---|
| 363 | |
---|
| 364 | Maximum value in that field/record. Ignores None values. |
---|
| 365 | |
---|
| 366 | .mean(field=None) |
---|
| 367 | |
---|
| 368 | The mean (=average) of all numeric values in that field/record. |
---|
| 369 | |
---|
| 370 | .average(field=None) |
---|
| 371 | |
---|
| 372 | Same. |
---|
| 373 | |
---|
| 374 | .median(field=None) |
---|
| 375 | |
---|
| 376 | The median of all numeric values in that field/record. This is done by sorting |
---|
| 377 | the values and taking the middle value. |
---|
| 378 | |
---|
| 379 | .variance(field=None), .variance_n(field=None) |
---|
| 380 | .standardDeviation(field=None), .standardDeviation_n(field=None) |
---|
| 381 | |
---|
| 382 | [Not implemented yet.] |
---|
| 383 | |
---|
| 384 | |
---|
| 385 | * * * * * |
---|
| 386 | To run the regression tests (requires unittest.py, which is standard with |
---|
| 387 | Python 2.2), run MondoReportTest.py from the command line. The regression test |
---|
| 388 | double as usage examples. |
---|
| 389 | |
---|
| 390 | |
---|
| 391 | # vim: shiftwidth=4 tabstop=4 expandtab textwidth=79 |
---|