diff options
Diffstat (limited to 'jfr_playoff')
-rw-r--r-- | jfr_playoff/dto.py | 14 | ||||
-rw-r--r-- | jfr_playoff/generator.py | 240 | ||||
-rw-r--r-- | jfr_playoff/i18n.py | 4 | ||||
-rw-r--r-- | jfr_playoff/matchinfo.py | 62 | ||||
-rw-r--r-- | jfr_playoff/template.py | 32 |
5 files changed, 305 insertions, 47 deletions
diff --git a/jfr_playoff/dto.py b/jfr_playoff/dto.py index a88cd2b..8c04901 100644 --- a/jfr_playoff/dto.py +++ b/jfr_playoff/dto.py @@ -8,8 +8,16 @@ def coalesce(*arg): class Team(object): - name = '' + name = None + possible_name = None score = 0.0 + place = None + known_teams = 0 + + def __init__(self): + self.place = [] + self.name = [] + self.possible_name = [] def __unicode__(self): return u'%s (%.1f)' % (coalesce(self.name, '<None>'), self.score) @@ -25,8 +33,12 @@ class Match(object): link = None winner = None loser = None + possible_winner = None + possible_loser = None winner_matches = None loser_matches = None + winner_place = None + loser_place = None def __repr__(self): return (u'#%d (%s) %s [%s]' % ( diff --git a/jfr_playoff/generator.py b/jfr_playoff/generator.py index d358d86..a620a1b 100644 --- a/jfr_playoff/generator.py +++ b/jfr_playoff/generator.py @@ -1,5 +1,6 @@ from datetime import datetime +from jfr_playoff.dto import coalesce from jfr_playoff.template import PlayoffTemplate from jfr_playoff.data import PlayoffData from jfr_playoff.logger import PlayoffLogger @@ -11,6 +12,7 @@ class PlayoffGenerator(object): self.page = settings.get('page') PlayoffLogger.get('generator').info( 'page settings: %s', self.page) + self.team_box_settings = self.page.get('team_boxes', {}) self.canvas = {} if settings.has_section('canvas'): self.canvas = settings.get('canvas') @@ -50,28 +52,136 @@ class PlayoffGenerator(object): 'PAGE_BODY_FOOTER', datetime.now().strftime('%Y-%m-%d o %H:%M:%S')))) + def __get_team_label(self, team_name, template='MATCH_TEAM_LABEL'): + if not self.team_box_settings.get('predict_teams', None): + # override template if team predictions are not enabled + template = 'MATCH_TEAM_LABEL' + return self.p_temp.get(template, team_name) + + def __shorten_labels(self, labels, limit, separator, ellipsis): + if limit > 0: + current_length = 0 + shortened = [] + for l in range(0, len(labels)): + if current_length + len(labels[l]) > limit: + # current label won't fit within limit, shorten it and stop + shortened.append(labels[l][0:limit-current_length] + ellipsis) + break + else: + # current label fits, add it to output + shortened.append(labels[l]) + current_length += len(labels[l]) + if l < len(labels) - 1: + # if it's not the last label, separator will be added + # if it was the last label, next condition won't run and ellipsis won't be added + current_length += len(separator) + if current_length > limit: + # if separator puts us over the limit, add ellipsis and stop + shortened.append(ellipsis) + break + labels = shortened + return labels + def get_match_table(self, match): rows = '' for team in match.teams: + PlayoffLogger.get('generator').info( + 'generating HTML for team object: %s', team) + # the easy part: team score cell score_html = self.p_temp.get('MATCH_SCORE', team.score) - team_label = ' / '.join([ - self.data.get_shortname(name) for name in - team.name.split('<br />')]) - label_max_length = self.page.get('label_length_limit', 0) - if label_max_length: - team_label = team_label[:label_max_length] + (team_label[label_max_length:] and '(...)') + PlayoffLogger.get('generator').info( + 'score HTML for team object: %s', score_html.strip()) + # the hard part begins here. + team_label = [] # label is what's shown in the table cell + label_separator = self.team_box_settings.get('label_separator', ' / ') + label_placeholder = self.team_box_settings.get('label_placeholder', '??') + label_length_limit = self.team_box_settings.get('label_length_limit', self.page.get('label_length_limit', 0)) + label_ellipsis = self.team_box_settings.get('label_ellipsis', '(...)') + team_name = [] # name is what's shown in the tooltip + name_separator = self.team_box_settings.get('name_separator', '<br />') + name_prefix = self.team_box_settings.get('name_prefix', ' ') # prefix (indent) for team names in the tooltip + if (team.known_teams == 0) and not self.team_box_settings.get('predict_teams', False): + PlayoffLogger.get('generator').info('no eligible teams and predictions are disabled') + # we've got no teams eligible for the match and the prediction option is disabled + team_label = '' + team_name = '' + else: + # predicted teams are not in team.name, they're in tem.possible_name so corresponding spots in team.name are empty + is_label_predicted = [name is None for name in team.name] + # fetch labels (shortnames) for teams in both lists + labels = [self.data.get_shortname(name) if name else None for name in team.name] + PlayoffLogger.get('generator').info('eligible team labels: %s', labels) + predicted_labels = [self.data.get_shortname(name) if name else None for name in team.possible_name] + PlayoffLogger.get('generator').info('predicted team labels: %s', predicted_labels) + for l in range(0, len(labels)): + if labels[l] is None: + if self.team_box_settings.get('predict_teams', False) and (len(predicted_labels) > l): + # fill team labels with either predictions... + labels[l] = predicted_labels[l] + else: + # ...or empty placeholders + labels[l] = label_placeholder + # count how many teams are eligible (how many non-predicted teams are there) + known_teams = len(is_label_predicted) - sum(is_label_predicted) + PlayoffLogger.get('generator').info('detected %d known team(s), predicted mask: %s', known_teams, is_label_predicted) + if self.team_box_settings.get('sort_eligible_first', True): + # sort labels to move eligible teams in front of predicted teams + labels = [label for i, label in enumerate(labels) if not is_label_predicted[i]] \ + + [label for i, label in enumerate(labels) if is_label_predicted[i]] + PlayoffLogger.get('generator').info('team labels: %s', labels) + if len([label for label in labels if label is not None]): + # we have at least one known/predicted team + for l in range(0, len(labels)): + # fill any remaining empty labels (i.e. these which had empty predictions available) with placeholders + labels[l] = coalesce(labels[l], label_placeholder) + # shorten concatenated label to specified combined length + labels = self.__shorten_labels(labels, label_length_limit, label_separator, label_ellipsis) + PlayoffLogger.get('generator').info('shortened team labels: %s', labels) + for l in range(0, len(labels)): + # concatenate labels, assigning appropriate classes to predicted teams + if self.team_box_settings.get('sort_eligible_first', True): + team_label.append(self.__get_team_label( + labels[l], + 'MATCH_PREDICTED_TEAM_LABEL' if l >= known_teams else 'MATCH_TEAM_LABEL')) + else: + team_label.append(self.__get_team_label( + labels[l], + 'MATCH_PREDICTED_TEAM_LABEL' if is_label_predicted[l] else 'MATCH_TEAM_LABEL')) + # team names for tooltip + for name in team.name: + if name: + # every non-empty name gets some indentation + team_name.append(name_prefix + name) + if self.team_box_settings.get('predict_teams', False): + # remember where the list of eligible teams ends + known_teams = len(team_name) + for name in team.possible_name: + # append predicted team names, with indentation as well + if name: + team_name.append(name_prefix + name) + if len(team_name) != known_teams: + # we've added some predicted team names, so we add a header + team_name.insert(known_teams, self.p_temp.get('MATCH_POSSIBLE_TEAM_LIST_HEADER')) + if (len(team_label) > 1) and (match.running == 0): + # and we add a header for matches that haven't started yet and have multiple options for teams + team_name.insert(0, self.p_temp.get('MATCH_TEAM_LIST_HEADER')) + # glue it all together + team_label = label_separator.join(team_label) + PlayoffLogger.get('generator').info('output teams label HTML: %s', team_label) + team_name = name_separator.join(team_name) + PlayoffLogger.get('generator').info('output teams name HTML: %s', team_name) team_html = self.p_temp.get( 'MATCH_TEAM_LINK', - match.link, team.name, team_label) \ + match.link, team_name, team_label) \ if match.link is not None \ else self.p_temp.get( 'MATCH_TEAM_NON_LINK', - team.name, team_label) + team_name, team_label) rows += self.p_temp.get( 'MATCH_TEAM_ROW', ' '.join([ - 'winner' if team.name == match.winner else '', - 'loser' if team.name == match.loser else '' + 'winner' if (match.winner is not None) and (match.winner in team.name) else '', + 'loser' if (match.loser is not None) and (match.loser in team.name) else '' ]).strip(), team_html, self.p_temp.get( @@ -109,33 +219,91 @@ class PlayoffGenerator(object): def get_match_box(self, match, position): if match is not None: + winner_link = [ + str(m) for m in match.winner_matches + ] if match.winner_matches is not None else [] + loser_link = [ + str(m) for m in match.loser_matches + ] if match.loser_matches is not None else [] + place_loser_link = [] + place_winner_link = [] + if self.page.get('starting_position_indicators', None): + for team in match.teams: + if len(team.place) > 0: + place_link = ['place-' + str(pl) for pl in team.place] + if len(team.place) > 1: + place_loser_link += place_link + else: + place_winner_link += place_link return self.p_temp.get( 'MATCH_BOX', position[0], position[1], match.id, - ' '.join([ - str(m) for m in match.winner_matches - ]) if match.winner_matches is not None else '', - ' '.join([ - str(m) for m in match.loser_matches - ]) if match.loser_matches is not None else '', + ' '.join(winner_link), + ' '.join(loser_link), + ' '.join(place_winner_link), + ' '.join(place_loser_link), self.get_match_table(match)) return '' + def get_starting_position_box(self, positions, dimensions): + if 'starting_position_indicators' not in self.page \ + or not self.page['starting_position_indicators']: + return '' + boxes = '' + order = 0 + for place in sorted(positions): + boxes += self.p_temp.get( + 'STARTING_POSITION_BOX', + 0, + self.page['margin'] / 2 + int(float(order) / float(len(positions)) * dimensions[1]), + place, + self.p_temp.get('POSITION_BOX', place)) + order += 1 + return boxes + + def get_finishing_position_box(self, positions, position_info, dimensions, margin): + if 'finishing_position_indicators' not in self.page \ + or not self.page['finishing_position_indicators']: + return '' + boxes = '' + order = 0 + for place in sorted(positions): + boxes += self.p_temp.get( + 'FINISHING_POSITION_BOX', + self.page['margin'] / 2 + int(float(order) / float(len(positions)) * dimensions[1]), + place, + ' '.join([str(p) for p in position_info[place]['winner']]), + ' '.join([str(p) for p in position_info[place]['loser']]), + self.p_temp.get('POSITION_BOX', place)) + order += 1 + return boxes + def get_match_grid(self, dimensions, grid, matches): - canvas_size = ( + canvas_size = [ dimensions[0] * ( self.page['width'] + self.page['margin'] - ) - self.page['margin'], + ) + self.page['margin'], dimensions[1] * ( self.page['height'] + self.page['margin'] - ) - self.page['margin']) + ) - self.page['margin']] + if 'starting_position_indicators' not in self.page \ + or not self.page['starting_position_indicators']: + canvas_size[0] -= self.page['margin'] + if 'finishing_position_indicators' not in self.page \ + or not self.page['finishing_position_indicators']: + canvas_size[0] -= self.page['margin'] PlayoffLogger.get('generator').info( 'canvas size: %s', canvas_size) grid_boxes = '' col_no = 0 + starting_positions = set() + finishing_positions = {} + finishing_places = set() for phase in grid: - grid_x = col_no * (self.page['width'] + self.page['margin']) + grid_x = col_no * self.page['width'] + (col_no + 1) * self.page['margin'] \ + if self.page.get('starting_position_indicators', None) \ + else col_no * (self.page['width'] + self.page['margin']) grid_boxes += self.get_phase_header(phase, grid_x) match_height = canvas_size[1] / len(phase.matches) row_no = 0 @@ -144,11 +312,33 @@ class PlayoffGenerator(object): int(row_no * match_height + 0.5 * (match_height - self.page['height'])) PlayoffLogger.get('generator').info( - 'grid box (%d, %d) position: (%d, %d)', + 'calculated grid box (%d, %d) position: (%d, %d)', col_no, row_no, grid_x, grid_y) + if str(match) in self.canvas.get('box_positioning', {}): + if isinstance(self.canvas['box_positioning'][str(match)], list): + grid_x, grid_y = self.canvas['box_positioning'][str(match)][0:2] + else: + grid_y = float(self.canvas['box_positioning'][str(match)]) + PlayoffLogger.get('generator').info( + 'overridden box #%d position: (%d, %d)', + match, grid_x, grid_y) grid_boxes += self.get_match_box( matches[match] if match is not None else None, (grid_x, grid_y)) + if match is not None: + for team in matches[match].teams: + starting_positions.update(team.place) + for place in matches[match].loser_place + matches[match].winner_place: + if place not in finishing_positions: + finishing_positions[place] = { + 'winner': [], + 'loser': [] + } + finishing_places.add(place) + for place in matches[match].winner_place: + finishing_positions[place]['winner'].append(match) + for place in matches[match].loser_place: + finishing_positions[place]['loser'].append(match) row_no += 1 col_no += 1 return self.p_temp.get( @@ -157,8 +347,12 @@ class PlayoffGenerator(object): canvas_size[0], canvas_size[1], ' '.join(['data-%s="%s"' % ( setting.replace('_', '-'), str(value) - ) for setting, value in self.canvas.iteritems()]), - grid_boxes + ) for setting, value in self.canvas.iteritems() if not isinstance(value, dict)]), + self.get_starting_position_box(starting_positions, canvas_size), + grid_boxes, + self.get_finishing_position_box( + finishing_places, finishing_positions, canvas_size, self.page['margin'] + ) ) def get_leaderboard_row_class(self, position): diff --git a/jfr_playoff/i18n.py b/jfr_playoff/i18n.py index c4134a9..63576db 100644 --- a/jfr_playoff/i18n.py +++ b/jfr_playoff/i18n.py @@ -11,7 +11,9 @@ PLAYOFF_I18N_DEFAULTS = { 'STANDINGS_TEAM': 'drużyna', 'STANDINGS_CAPTIONS': 'legenda', 'FOOTER_GENERATED': 'strona wygenerowana', - 'SWISS_DEFAULT_LABEL': 'Turniej o %d. miejsce' + 'SWISS_DEFAULT_LABEL': 'Turniej o %d. miejsce', + 'DETERMINED_TEAMS': 'Drużyny z pewnym miejscem w tej fazie:', + 'POSSIBLE_TEAMS': 'Drużyny z trwających meczów poprzedniej fazy:' } class PlayoffI18N(object): diff --git a/jfr_playoff/matchinfo.py b/jfr_playoff/matchinfo.py index ab742d4..0d46e59 100644 --- a/jfr_playoff/matchinfo.py +++ b/jfr_playoff/matchinfo.py @@ -32,6 +32,8 @@ class MatchInfo: self.info.loser_matches += self.config['teams'][i]['loser'] self.info.winner_matches = list(set(self.info.winner_matches)) self.info.loser_matches = list(set(self.info.loser_matches)) + self.info.winner_place = self.config.get('winner', []) + self.info.loser_place = self.config.get('loser', []) self.info.teams = [] def __fetch_match_link(self): @@ -58,9 +60,9 @@ class MatchInfo: teams[i].score = self.config['score'][score] try: team_no = int(score) - teams[i].name = self.teams[team_no-1][0] + teams[i].name = [self.teams[team_no-1][0]] except ValueError: - teams[i].name = score + teams[i].name = [score] teams_fetched = True else: teams[i].score = score @@ -77,8 +79,9 @@ class MatchInfo: row = self.database.fetch( self.config['database'], p_sql.MATCH_RESULTS, (self.config['table'], self.config['round'])) - teams[0].name = row[0] - teams[1].name = row[1] + for i in range(0, 2): + teams[i].name = [row[i]] + teams[i].known_teams = 1 if fetch_scores: teams[0].score = row[3] + row[5] teams[1].score = row[4] + row[6] @@ -147,7 +150,8 @@ class MatchInfo: if isinstance(text, unicode)][0].strip(u'\xa0') for link in row.select('a[onmouseover]')] for i in range(0, 2): - teams[i].name = team_names[i] + teams[i].name = [team_names[i]] + teams[i].known_teams = 1 teams[i].score = scores[i] PlayoffLogger.get('matchinfo').info( 'HTML scores for match #%d: %s', @@ -157,30 +161,33 @@ class MatchInfo: def __get_config_teams(self, teams): for i in range(0, 2): match_teams = [] + possible_teams = [] if isinstance(self.config['teams'][i], basestring): - teams[i].name = self.config['teams'][i] + teams[i].name = [self.config['teams'][i]] elif isinstance(self.config['teams'][i], list): - teams[i].name = '<br />'.join(self.config['teams'][i]) + teams[i].name = self.config['teams'][i] else: if 'winner' in self.config['teams'][i]: match_teams += [ MatchInfo.matches[winner_match].winner for winner_match in self.config['teams'][i]['winner']] + possible_teams += [ + MatchInfo.matches[winner_match].possible_winner + for winner_match in self.config['teams'][i]['winner']] if 'loser' in self.config['teams'][i]: match_teams += [ MatchInfo.matches[loser_match].loser for loser_match in self.config['teams'][i]['loser']] + possible_teams += [ + MatchInfo.matches[loser_match].possible_loser + for loser_match in self.config['teams'][i]['loser']] if 'place' in self.config['teams'][i]: match_teams += [ self.teams[place-1][0] for place in self.config['teams'][i]['place']] - known_teams = [team for team in match_teams if team is not None] - if len(known_teams) > 0: - teams[i].name = '<br />'.join([ - team if team is not None - else '??' for team in match_teams]) - else: - teams[i].name = '' + teams[i].name = match_teams + teams[i].possible_name = possible_teams + teams[i].known_teams = len([team for team in match_teams if team is not None]) PlayoffLogger.get('matchinfo').info( 'config scores for match #%d: %s', self.info.id, teams) @@ -212,6 +219,10 @@ class MatchInfo: 'fetching HTML scores for match #%d failed: %s(%s)', self.info.id, type(e).__name__, str(e)) self.info.teams = self.__get_config_teams(self.info.teams) + for team in range(0, len(self.info.teams)): + self.info.teams[team].place = self.config['teams'][team].get( + 'place', self.info.teams[team].place) + def __get_db_board_count(self): towels = self.database.fetch( @@ -341,13 +352,22 @@ class MatchInfo: else boards_played def __determine_outcome(self): - if (self.info.running == -1): - if self.info.teams[0].score > self.info.teams[1].score: - self.info.winner = self.info.teams[0].name - self.info.loser = self.info.teams[1].name - else: - self.info.loser = self.info.teams[0].name - self.info.winner = self.info.teams[1].name + if (self.info.teams[0].known_teams == 1) \ + and (self.info.teams[1].known_teams == 1): + if self.info.running == -1: + if self.info.teams[0].score > self.info.teams[1].score: + self.info.winner = self.info.teams[0].name[0] + self.info.loser = self.info.teams[1].name[0] + else: + self.info.loser = self.info.teams[0].name[0] + self.info.winner = self.info.teams[1].name[0] + elif self.info.running > 0: + if self.info.teams[0].score > self.info.teams[1].score: + self.info.possible_winner = self.info.teams[0].name[0] + self.info.possible_loser = self.info.teams[1].name[0] + elif self.info.teams[0].score < self.info.teams[1].score: + self.info.possible_loser = self.info.teams[0].name[0] + self.info.possible_winner = self.info.teams[1].name[0] def __get_db_running_link(self, prefix, round_no): current_segment = int( diff --git a/jfr_playoff/template.py b/jfr_playoff/template.py index 70c179b..dc9e91e 100644 --- a/jfr_playoff/template.py +++ b/jfr_playoff/template.py @@ -24,6 +24,14 @@ class PlayoffTemplateStrings(object): %.1f ''' + MATCH_TEAM_LABEL = '<span class="team-label">%s</span>' + + MATCH_PREDICTED_TEAM_LABEL = '<span class="team-predicted-label">%s</span>' + + MATCH_TEAM_LIST_HEADER = '{{DETERMINED_TEAMS}}' + + MATCH_POSSIBLE_TEAM_LIST_HEADER = '{{POSSIBLE_TEAMS}}' + MATCH_TEAM_LINK = ''' <a href="%s" onmouseover="Tip('%s')" onmouseout="UnTip()">%s</a> ''' @@ -51,6 +59,8 @@ class PlayoffTemplateStrings(object): <div style="position: relative; width: %dpx; height: %dpx; margin: 10px"> <canvas width="%d" height="%d" id="playoff_canvas" %s></canvas> %s + %s + %s <script src="sklady/playoff.js" type="text/javascript"></script> </div> ''' @@ -77,8 +87,28 @@ class PlayoffTemplateStrings(object): <img src="images/A.gif" /> ''' + STARTING_POSITION_BOX = ''' + <div style="position: absolute; left: %dpx; top: %dpx" class="playoff_matchbox" data-id="place-%d"> + %s + </div> + ''' + + FINISHING_POSITION_BOX = ''' + <div style="position: absolute; right: 0px; top: %dpx" class="playoff_matchbox" data-id="finish-%d" data-finish-winner="%s" data-finish-loser="%s"> + %s + </div> + ''' + + POSITION_BOX = ''' + <table border="0" cellspacing="0"> + <tr> + <td class="bdc12" width="20">%d</td> + </tr> + </table> + ''' + MATCH_BOX = ''' - <div style="text-align: center; position: absolute; left: %dpx; top: %dpx" data-id="%d" data-winner="%s" data-loser="%s" class="playoff_matchbox"> + <div style="text-align: center; position: absolute; left: %dpx; top: %dpx" data-id="%d" data-winner="%s" data-loser="%s" data-place-winner="%s" data-place-loser="%s" class="playoff_matchbox"> %s </div> ''' |