Home | Trees | Indices | Help |
|
---|
|
1 # 2 # Copyright (C) 2008 Gaudenz Steinlin <gaudenz@soziologie.ch> 3 # 4 # This program is free software: you can redistribute it and/or modify 5 # it under the terms of the GNU General Public License as published by 6 # the Free Software Foundation, either version 3 of the License, or 7 # (at your option) any later version. 8 # 9 # This program is distributed in the hope that it will be useful, 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 # GNU General Public License for more details. 13 # 14 # You should have received a copy of the GNU General Public License 15 # along with this program. If not, see <http://www.gnu.org/licenses/>. 16 """ 17 formatter.py - Classes to to format rankings 18 """ 19 20 import pkg_resources 21 import json 22 23 from mako.lookup import TemplateLookup 24 25 from reportlab.lib import colors, pagesizes 26 from reportlab.platypus import * 27 from reportlab.lib.styles import getSampleStyleSheet 28 29 from datetime import datetime 30 from StringIO import StringIO 31 from csv import writer 32 33 from ranking import Validator, ValidationError, UnscoreableException 34 from course import SIStation, Control 35 from run import Punch, Run 3638 (hours, seconds) = divmod(delta.seconds, 3600) 39 (minutes, seconds) = divmod(seconds, 60) 40 if hours > 0: 41 return "%i:%02i:%02i" % (hours, minutes, seconds) 42 else: 43 return "%i:%02i" % (minutes, seconds)4446 47 validation_codes = {Validator.OK : 'OK', 48 Validator.NOT_COMPLETED : 'not yet finished', 49 Validator.MISSING_CONTROLS : 'missing controls', 50 Validator.DID_NOT_FINISH : 'did not finish', 51 Validator.DISQUALIFIED : 'disqualified', 52 Validator.DID_NOT_START : 'did not start'}5355 """Formats a ranking. str(rankingRormatter) returns the formatted ranking.""" 567058 """ 59 @param ranking: the ranking to format 60 @type ranking: generator or list as returned by L{Rankable}s ranking method. 61 """ 62 63 self.rankings = rankings6472 """Uses the Mako Templating Engine to format a ranking as HTML.""" 739575 """ 76 @type rankings: list of dicts with keys 'ranking' and 'info' 77 the value of the 'ranking' key is an object of 78 class Ranking 79 @param template_file: File name for the template 80 @param template_dir: template directory (inside the bosco module) 81 @param header: gerneral information for the ranking header 82 @type header: dict 83 """ 84 super(type(self), self).__init__(rankings) 85 lookup = TemplateLookup(directories=[pkg_resources.resource_filename('bosco', template_dir)]) 86 self._template = lookup.get_template(template_file) 87 self._header = header8890 91 return self._template.render_unicode(header = self._header, 92 validation_codes = self.validation_codes, 93 now = datetime.now().strftime('%c'), 94 rankings = self.rankings)97 """Formats the Ranking for exporting to the SOLV result site.""" 98 99 validation_codes = {Validator.OK : 'OK', 100 Validator.NOT_COMPLETED : '', 101 Validator.MISSING_CONTROLS : 'fehl', 102 Validator.DID_NOT_FINISH : 'aufg', 103 Validator.DISQUALIFIED : 'disq', 104 Validator.DID_NOT_START : 'n. gest'} 105143 144106 - def __init__(self, ranking, reftime, encoding = 'utf-8', control_replacements = None, control_exclude = None, 107 lineterminator = '\n'):108 """ 109 @reftime: reference time for the event (usually first starttime) 110 """ 111 AbstractRankingFormatter.__init__(self, ranking) 112 self._reftime = reftime 113 self._encoding = encoding 114 self._control_replacements = control_replacements if control_replacements is not None else {} 115 self._control_exclude = control_exclude if control_exclude is not None else () 116 self._lineterminator = lineterminator117119 if r['validation']['status'] == Validator.OK: 120 return str(r['scoreing']['score']) 121 else: 122 return self.validation_codes[r['validation']['status']]123 126128 try: 129 return self._control_replacements[control.code] 130 except KeyError: 131 return control.code132134 self._outstr = StringIO() 135 return writer(self._outstr, delimiter=';', 136 lineterminator=self._lineterminator)137 140146 """ 147 Formatting a course ranking to be uploaded to the SOLV website 148 Format: Rank;Name;Firstname;YearOfBirth;SexMF;FedNr;Zip;Town;Club;NationIOF;Start Nr;eCardNr;RunTime;StartTime;FinishTime;CtrlCode;SplitTime 149 """207 226151 152 output = self._writer() 153 for ranking in self.rankings: 154 output.writerow([str(ranking.rankable), 155 ranking.rankable.length, 156 ranking.rankable.climb, 157 ranking.rankable.controlcount() 158 ]) 159 for r in ranking: 160 line = [r['rank'] or '', 161 self._encode(r['item'].sicard.runner.surname), 162 self._encode(r['item'].sicard.runner.given_name), 163 r['item'].sicard.runner.dateofbirth and r['item'].sicard.runner.dateofbirth.strftime('%y') or '', 164 r['item'].sicard.runner.sex, 165 '', # FedNR (?) 166 '', # Zip 167 '', # Town 168 r['item'].sicard.runner.team and self._encode(r['item'].sicard.runner.team.name) or '', # Club 169 '', # NationIOF 170 '', # Start Nr 171 '', # eCardNr 172 self._print_score(r), 173 ] 174 try: 175 line.append(r['scoreing']['start'] - self._reftime) 176 except (TypeError, KeyError): 177 line.append('') 178 try: 179 line.append(r['scoreing']['finish'] - self._reftime) 180 except (TypeError, KeyError): 181 line.append('') 182 183 try: 184 punchlist = r['validation']['reordered_punchlist'] 185 except KeyError: 186 punchlist = r['validation']['punchlist'] 187 for status, p in punchlist: 188 if status == 'missing' and not p.code in self._control_exclude: 189 # p is an object of class Control 190 line.extend([self._encode(self._control_code(p)), '']) 191 if not status == 'ok': 192 # ignore additional punches 193 continue 194 if (not p.sistation.control is None 195 and p.sistation.id > SIStation.SPECIAL_MAX 196 and not p.sistation.control.code in self._control_exclude): 197 try: 198 line.extend([self._encode(self._control_code(p.sistation.control)), 199 p.punchtime - r['scoreing']['start']]) 200 except (TypeError, KeyError): 201 line.extend([self._encode(self._control_code(p.sistation.control)), 202 '']) 203 204 output.writerow(line) 205 206 return self._output()228 """ 229 As there is no real documenation for the SOLV ranking format this is modeled 230 after the file for "Osterstaffel 2012" made with ORWare. 231 """ 232274234 235 output = self._writer() 236 for ranking in self.rankings: 237 output.writerow([str(ranking.rankable)]) 238 239 for r in ranking: 240 line = [r['rank'] or '', 241 self._encode(r['item'].number), 242 self._encode(r['item']), 243 '', # TODO: put nation of team into database 244 self._print_score(r), 245 r['scoreing']['behind'] or '', 246 '', # RelayAltStart, whatever that could be... 247 len(r['runs']), 248 ] 249 for leg in range(len(r['runs'])): 250 leg_run = r['runs'][leg] 251 leg_split = r['splits'][leg] 252 if leg_run is not None: 253 run = leg_run['item'] 254 runner = run.sicard.runner 255 line.extend([self._encode(runner.surname) or '', 256 self._encode(runner.given_name) or '', 257 runner.dateofbirth and runner.dateofbirth.year or '', 258 self._encode(run.course.code), 259 leg_run['rank'] or '', 260 self._print_score(leg_run), 261 leg_run['scoreing']['behind'] or '', 262 ]) 263 else: 264 # No valid run on this leg 265 line.extend([''] * 7) 266 line.extend(['', # LegAltStart, whatever that could be... 267 leg_split['rank'] or '', 268 self._print_score(leg_split), 269 leg_split['scoreing']['behind'] or '', 270 ]) 271 output.writerow(line) 272 273 return self._output()276307 308278 279 output = self._writer() 280 281 for ranking in self.rankings: 282 lines = [] 283 for r in ranking: 284 if type(r['item']) == Run: 285 runner = r['item'].sicard.runner 286 run = r['item'] 287 else: 288 runner = r['item'] 289 run = r['item'].run 290 number = runner and runner.number or 0 291 lines.append([r['rank'] or '', 292 run.sicard.id, 293 self._encode(runner and runner.category or u''), 294 self._encode(number), # change index below if position of this element changes 295 self._encode(runner and runner.given_name or u''), 296 self._encode(runner and runner.surname or u''), 297 298 self._print_score(r), 299 ]) 300 301 # reorder by number instead of rank 302 lines.sort(key=lambda x: int(x[3])) 303 output.writerow([str(ranking.rankable)]) 304 output.writerows(lines) 305 306 return self._output()310374 375312 results = { 313 'name': '', 314 'map': '', 315 'date': '', 316 'startTime': str(self._reftime), 317 'categories': [], 318 } 319 320 for ranking in self.rankings: 321 cat = { 322 'name': str(ranking.rankable), 323 'distance': ranking.rankable.length, 324 'ascent': ranking.rankable.climb, 325 'controls': ranking.rankable.controlcount(), 326 'runners': [], 327 } 328 329 for r in ranking: 330 run_dict = { 331 'fullName': '%s %s' % (r['item'].sicard.runner.given_name, r['item'].sicard.runner.surname), 332 'yearOfBirth': r['item'].sicard.runner.dateofbirth and r['item'].sicard.runner.dateofbirth.strftime('%y') or '', 333 'sex': r['item'].sicard.runner.sex, 334 'club': r['item'].sicard.runner.team and r['item'].sicard.runner.team.name or '', 335 'city': '', 336 'nation': '', 337 'time': self._print_score(r), 338 'ecard': r['item'].sicard.id, 339 } 340 341 try: 342 run_dict['startTime'] = str(r['scoreing']['start'] - self._reftime) 343 except (TypeError, KeyError): 344 run_dict['startTime'] = '' 345 346 try: 347 punchlist = r['validation']['reordered_punchlist'] 348 except KeyError: 349 punchlist = r['validation']['punchlist'] 350 splits = [] 351 for status, p in punchlist: 352 if status == 'missing' and not p.code in self._control_exclude: 353 # p is an object of class Control 354 splits.append([self._control_code(p), '']) 355 if not status == 'ok': 356 # ignore additional punches 357 continue 358 if (not p.sistation.control is None 359 and p.sistation.id > SIStation.SPECIAL_MAX 360 and not p.sistation.control.code in self._control_exclude): 361 try: 362 splits.append([self._control_code(p.sistation.control), 363 str(p.punchtime - r['scoreing']['start'])]) 364 except (TypeError, KeyError): 365 splits.append([self._control_code(p.sistation.control), '']) 366 run_dict['splits'] = splits 367 run_dict['course'] = ','.join([s[0] for s in splits]) 368 369 cat['runners'].append(run_dict) 370 371 results['categories'].append(cat) 372 373 return json.dumps(results)377 """Formats a Run.""" 378459380 """ 381 @param run run to format 382 @param header header information 383 @type header dict 384 @param event event this run belongs to 385 """ 386 self._run = run 387 self._header = header 388 self._event = event389 395397 try: 398 punchlist = self._event.validate(self._run)['punchlist'] 399 except ValidationError: 400 # create pseudo validation result 401 punchlist = [ ('ignored', p) for p in self._run.punches ] 402 403 return punchlist404406 407 raw_punchlist = self._raw_punchlist() 408 punchlist = [] 409 try: 410 lastpunch = start = self._event.score(self._run)['start'] 411 except UnscoreableException: 412 lastpunch = start = self._run.start_time or raw_punchlist[0][1].punchtime 413 for code, p in raw_punchlist: 414 if type(p) == Punch: 415 punchtime = p.manual_punchtime or p.card_punchtime 416 punchlist.append((p.sequence and str(p.sequence) or '', 417 p.sistation.control and p.sistation.control.code or '', 418 str(p.sistation.id), 419 p.card_punchtime and str(p.card_punchtime) or '', 420 p.manual_punchtime and str(p.manual_punchtime) or '', 421 punchtime and format_timedelta(punchtime - start) or '', 422 punchtime and format_timedelta(punchtime - lastpunch) or '', 423 str(int(p.ignore)), 424 str(code))) 425 if code == 'ok': 426 lastpunch = punchtime 427 elif type(p) == Control: 428 punchlist.append(('', 429 p.code, 430 '', 431 '', 432 '', 433 '', 434 '', 435 str(int(False)), 436 code)) 437 elif type(p) == SIStation: 438 punchlist.append(('', 439 '', 440 str(p.id), 441 '', 442 '', 443 '', 444 '', 445 str(int(False)), 446 code)) 447 if with_finish: 448 punchtime = self._run.manual_finish_time or self._run.card_finish_time 449 punchlist.append(('', 450 'Finish', 451 '', 452 self._run.card_finish_time and str(self._run.card_finish_time) or '', 453 self._run.manual_finish_time and str(self._run.manual_finish_time) or '', 454 punchtime and format_timedelta(punchtime - start) or '', 455 punchtime and format_timedelta(punchtime - lastpunch) or '', 456 str(int(False)), 457 'finish')) 458 return punchlist461561463 try: 464 validation = self._event.validate(self._run) 465 except ValidationError, validation_error: 466 validation = None 467 try: 468 score = self._event.score(self._run) 469 except UnscoreableException: 470 score = None 471 472 io = StringIO() 473 doc = SimpleDocTemplate(io, 474 pagesize=pagesizes.landscape(pagesizes.A5), 475 leftMargin = 20, rightMargin = 20, topMargin = 20, bottomMargin = 20) 476 477 styles = getSampleStyleSheet() 478 elements = [] 479 480 elements.append(Paragraph(("%(event)s / %(map)s / %(place)s / %(date)s / %(organiser)s" % 481 self._header), 482 styles['Normal'])) 483 elements.append(Spacer(0,10)) 484 485 runner = self._run.sicard.runner 486 elements.append(Paragraph("%s %s" % (unicode(runner), runner.number and 487 ("(%s)" % runner.number) or ''), 488 styles['Heading1'])) 489 elements.append(Paragraph("SI-Card: %s" % str(self._run.sicard.id), 490 styles['Normal'])) 491 course = self._run.course 492 elements.append(Paragraph("<b>%s</b>" % (course and course.code or 'unknown course'), 493 styles['Normal'])) 494 elements.append(Spacer(0,10)) 495 496 if validation and validation['status'] == Validator.OK: 497 elements.append(Paragraph('<b>Laufzeit %s</b>' % score['score'], styles['Normal'])) 498 elif validation: 499 elements.append(Paragraph('<b>%s</b>' % AbstractFormatter.validation_codes[validation['status']], 500 styles['Normal'])) 501 else: 502 elements.append(Paragraph('<b>Validation error: %s</b>' % validation_error.message, 503 styles['Normal'])) 504 elements.append(Spacer(0,10)) 505 506 (punchtable, tablestyles) = self._format_punchlist() 507 508 tablestyles = [('ALIGN', (0,0), (-1,-1), 'RIGHT'), 509 ('FONT', (0,0), (-1,-1), 'Helvetica'), 510 ('TOPPADDING', (0,0), (-1,-1), 0), 511 ('BOTTOMPADDING', (0,0), (-1,-1), 0), 512 ] + tablestyles 513 t = Table(punchtable, ) 514 t.setStyle(TableStyle(tablestyles)) 515 t.hAlign = 'LEFT' 516 elements.append(t) 517 elements.append(Spacer(0,20)) 518 519 elements.append(Paragraph("printed by Bosco, Free Orienteering Software, " 520 "http://bosco.durcheinandertal.ch", styles['Normal'])) 521 522 doc.build(elements) 523 524 return io.getvalue()525527 punchlist = self._punchlist(with_finish = True) 528 529 # format into triples (control, time, time_to_last) 530 punch_triples = [] 531 ctrl_nr = 1 532 for i,p in enumerate(punchlist): 533 if p[8] in ('ok', 'missing'): 534 control = "%i (%s)" % (ctrl_nr, p[1]) 535 ctrl_nr += 1 536 elif p[8] == 'finish': 537 control = "Finish" 538 elif p[8] in ('additional', 'ignored'): 539 control = "+ (%s)" % (p[1] != '' and p[1] or p[2]) 540 punch_triples.append((control, p[5] or 'missing', p[6])) 541 542 # rearrange punch_triples by row for the output table 543 i = 0 544 table = [] 545 styles = [] 546 row_items = ([], [], []) 547 while i < len(punchlist): 548 if i > 0 and i % cols == 0: 549 # new row 550 table.extend(row_items) 551 row_items = ([],[],[]) 552 rowcount = len(table) 553 styles.append(('TOPPADDING', (0,rowcount), (-1,rowcount), 20)) 554 555 for j in range(3): 556 row_items[j].append(punch_triples[i][j]) 557 i += 1 558 # add last row 559 table.extend(row_items) 560 return (table, styles)
Home | Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Fri Nov 3 23:56:09 2023 | http://epydoc.sourceforge.net |