def test_determine_end_of_league(): start_date = dtutil.parse('2017-11-01 00:00:00', '%Y-%m-%d %H:%M:%S', dtutil.WOTC_TZ) end_date = league.determine_end_of_league(start_date) assert dtutil.dt2ts(end_date) == 1512115199 start_date = dtutil.parse('2017-10-31 11:59:59.999', '%Y-%m-%d %H:%M:%S.%f', dtutil.WOTC_TZ) end_date = league.determine_end_of_league(start_date) assert dtutil.dt2ts(end_date) == 1512115199
def next_tournament_info(): now = dtutil.now(dtutil.GATHERLING_TZ) now_ts = dtutil.dt2ts(dtutil.now()) pdm_time = rrule.rrule(rrule.WEEKLY, byhour=19, byminute=0, bysecond=0, dtstart=now, byweekday=rrule.MO)[0] pdt_time = rrule.rrule(rrule.WEEKLY, byhour=19, byminute=0, bysecond=0, dtstart=now, byweekday=rrule.TH)[0] pds_time = rrule.rrule(rrule.WEEKLY, byhour=13, byminute=30, bysecond=0, dtstart=now, byweekday=rrule.SU)[0] if pdm_time < pdt_time and pdm_time < pds_time: next_tournament_name = 'Penny Dreadful Monday' next_time = pdm_time elif pdt_time < pds_time: next_tournament_name = 'Penny Dreadful Thursday' next_time = pdt_time else: next_tournament_name = 'Penny Dreadful Sunday' next_time = pds_time next_tournament_time_precise = dtutil.dt2ts(next_time) - now_ts next_tournament_time = dtutil.display_time(next_tournament_time_precise, granularity=1) return { 'next_tournament_name': next_tournament_name, 'next_tournament_time': next_tournament_time, 'next_tournament_time_precise': next_tournament_time_precise, 'pdm_time': pdm_time, 'pds_time': pds_time, 'pdt_time': pdt_time }
def insert_match(dt: datetime.datetime, left_id: int, left_games: int, right_id: int, right_games: int, round_num: Optional[int] = None, elimination: Optional[int] = None, mtgo_match_id: Optional[int] = None) -> int: if left_games == right_games: raise InvalidDataException('`insert_match` does not support draws.') winner_id = left_id if left_games > right_games else right_id loser_id = left_id if left_games < right_games else right_id db().begin('insert_match') match_id = db().insert( 'INSERT INTO `match` (`date`, `round`, elimination, mtgo_id) VALUES (%s, %s, %s, %s)', [dtutil.dt2ts(dt), round_num, elimination, mtgo_match_id]) sql = 'UPDATE deck_cache SET wins = IFNULL(wins, 0) + 1, active_date = %s WHERE deck_id = %s' db().execute(sql, [dtutil.dt2ts(dt), winner_id]) sql = 'UPDATE deck_cache SET losses = IFNULL(losses, 0) + 1, active_date = %s WHERE deck_id = %s' db().execute(sql, [dtutil.dt2ts(dt), loser_id]) sql = 'INSERT INTO deck_match (deck_id, match_id, games) VALUES (%s, %s, %s)' db().execute(sql, [left_id, match_id, left_games]) if right_id is not None: # Don't insert matches or adjust Elo for the bye. db().execute(sql, [right_id, match_id, right_games]) elo.adjust_elo(winner_id, loser_id) db().commit('insert_match') redis.clear(f'decksite:deck:{left_id}') if right_id is not None: redis.clear(f'decksite:deck:{right_id}') return match_id
def last_switcheroo(self) -> str: last_switcheroo = stats.calc_last_switcheroo() if last_switcheroo: diff = dtutil.dt2ts(dtutil.now()) - dtutil.dt2ts( last_switcheroo.start_time_aware()) return dtutil.display_time(diff) return 'unknown'
def load_news(start_date: datetime.datetime = None, end_date: datetime.datetime = None, max_items: int = sys.maxsize) -> List[Container]: if start_date is None: start_date = dtutil.ts2dt(0) if end_date is None: end_date = dtutil.now() sql = """ SELECT id, `date`, title, url FROM news_item WHERE `date` >= %s AND `date` <= %s ORDER BY `date` DESC LIMIT %s """ results = [ Container(r) for r in db().select( sql, [dtutil.dt2ts(start_date), dtutil.dt2ts(end_date), max_items]) ] for result in results: result.date = dtutil.ts2dt(result.date) result.form_date = dtutil.form_date(result.date, dtutil.WOTC_TZ) result.display_date = dtutil.display_date(result.date) result.type = 'site-news' return results
def last_switcheroo(self) -> str: last_switcheroo = stats.calc_last_switcheroo() if last_switcheroo: start = last_switcheroo.start_time_aware() diff = -1 if start is not None: diff = dtutil.dt2ts(dtutil.now()) - dtutil.dt2ts(start) return dtutil.display_time(diff) return 'unknown'
def tournament_info(time_direction: TimeDirection, units: int = 2) -> Dict[str, Any]: day, time = get_nearest_tournament(time_direction) next_tournament_time_precise = abs(dtutil.dt2ts(time) - dtutil.dt2ts(dtutil.now())) near = next_tournament_time_precise < 18000 # Threshold for near: 5 hours in seconds next_tournament_time = dtutil.display_time(next_tournament_time_precise, units) return { 'next_tournament_name': 'Penny Dreadful {day}'.format(day=day), 'next_tournament_time': next_tournament_time, 'next_tournament_time_precise': next_tournament_time_precise, 'near': near }
def tournament_info(time_direction: TimeDirection, units: int = 2) -> Dict[str, Any]: tournament_id, name, time = get_nearest_tournament(time_direction) next_tournament_time_precise = abs(dtutil.dt2ts(time) - dtutil.dt2ts(dtutil.now())) near = next_tournament_time_precise < 18000 # Threshold for near: 5 hours in seconds next_tournament_time = dtutil.display_time(next_tournament_time_precise, units) info = { 'next_tournament_name': name, 'next_tournament_time': next_tournament_time, 'next_tournament_time_precise': next_tournament_time_precise, 'near': near, } info.update(series_info(tournament_id)) return info
def tournament_winners(start_date: datetime.datetime, end_date: datetime.datetime, max_items: int = sys.maxsize) -> List[Container]: where = 'd.finish = 1 AND d.created_date > {start_date} AND d.created_date <= {end_date}'.format( start_date=sqlescape(dtutil.dt2ts(start_date)), end_date=sqlescape(dtutil.dt2ts(end_date))) ds = deck.load_decks(where, limit=f'LIMIT {max_items}') return [ Container({ 'date': d.active_date, 'title': tournament_winner_headline(d), 'url': url_for('deck', deck_id=d.id), 'type': 'tournament-winner' }) for d in ds ]
def insert_match(dt: datetime.datetime, left_id: int, left_games: int, right_id: int, right_games: int, round_num: Optional[int] = None, elimination: Optional[int] = None, mtgo_match_id: Optional[int] = None) -> int: db().begin('insert_match') match_id = db().insert( 'INSERT INTO `match` (`date`, `round`, elimination, mtgo_id) VALUES (%s, %s, %s, %s)', [dtutil.dt2ts(dt), round_num, elimination, mtgo_match_id]) update_cache(left_id, left_games, right_games, dt=dt) update_cache(right_id, right_games, left_games, dt=dt) sql = 'INSERT INTO deck_match (deck_id, match_id, games) VALUES (%s, %s, %s)' db().execute(sql, [left_id, match_id, left_games]) if right_id is not None: # Don't insert matches or adjust Elo for the bye. db().execute(sql, [right_id, match_id, right_games]) if left_games == right_games: # Don't adjust Elo for a draw. This is not quite right but we have so few it's not important. winner_id = left_id if left_games > right_games else right_id loser_id = left_id if left_games < right_games else right_id elo.adjust_elo(winner_id, loser_id) db().commit('insert_match') redis.clear(f'decksite:deck:{left_id}') if right_id is not None: redis.clear(f'decksite:deck:{right_id}') return match_id
def insert_deck(competition_id: int, date: datetime.datetime, d: GatherlingDeck, fs: FinalStandings, players: List[Player]) -> deck.Deck: finish = fuzzy_get(fs, d.playername) if not finish: raise InvalidDataException( f"I don't have a finish for `{d.playername}`") mtgo_username = find_mtgo_username(d.playername, players) if not mtgo_username: raise InvalidDataException( f"I don't have an MTGO username for `{d.playername}`") raw: deck.RawDeckDescription = { 'name': d.name, 'source': 'Gatherling', 'competition_id': competition_id, 'created_date': dtutil.dt2ts(date), 'mtgo_username': mtgo_username, 'finish': finish, 'url': gatherling_url(f'/deck.php?mode=view&id={d.id}'), 'archetype': d.archetype.value, 'identifier': str(d.id), 'cards': { 'maindeck': d.maindeck, 'sideboard': d.sideboard }, } if len(raw['cards']['maindeck']) + len(raw['cards']['sideboard']) == 0: raise InvalidDataException( f'Unable to add deck with no cards `{d.id}`') decklist.vivify(raw['cards']) if deck.get_deck_id(raw['source'], raw['identifier']): raise InvalidArgumentException( "You asked me to insert a deck that already exists `{raw['source']}`, `{raw['identifier']}`" ) return deck.add_deck(raw)
def times_from_location(q: str, twentyfour: bool) -> Dict[str, List[str]]: api_key = configuration.get('google_maps_api_key') if not api_key: raise NotConfiguredException('No value found for google_maps_api_key') url = 'https://maps.googleapis.com/maps/api/geocode/json?address={q}&key={api_key}&sensor=false'.format( q=fetch_tools.escape(q), api_key=api_key) info = fetch_tools.fetch_json(url) if 'error_message' in info: return info['error_message'] try: location = info['results'][0]['geometry']['location'] except IndexError as e: raise TooFewItemsException(e) from e url = 'https://maps.googleapis.com/maps/api/timezone/json?location={lat},{lng}×tamp={timestamp}&key={api_key}&sensor=false'.format( lat=fetch_tools.escape(str(location['lat'])), lng=fetch_tools.escape(str(location['lng'])), timestamp=fetch_tools.escape(str(dtutil.dt2ts(dtutil.now()))), api_key=api_key) timezone_info = fetch_tools.fetch_json(url) if 'error_message' in timezone_info: return timezone_info['error_message'] if timezone_info['status'] == 'ZERO_RESULTS': raise TooFewItemsException(timezone_info['status']) try: timezone = dtutil.timezone(timezone_info['timeZoneId']) except KeyError as e: raise TooFewItemsException( f'Unable to find a timezone in {timezone_info}') from e return { current_time(timezone, twentyfour): [info['results'][0]['formatted_address']] }
async def update_database_async(new_date: datetime.datetime) -> None: db().begin('update_database') db().execute('DELETE FROM scryfall_version') db().execute( 'SET FOREIGN_KEY_CHECKS=0' ) # Avoid needing to drop _cache_card (which has an FK relationship with card) so that the database continues to function while we perform the update. db().execute(""" DELETE FROM card_color; DELETE FROM card_color_identity; DELETE FROM card_legality; DELETE FROM card_bug; DELETE FROM face; DELETE FROM printing; DELETE FROM card; DELETE FROM `set`; """) for s in await fetcher.all_sets_async(): insert_set(s) every_card_printing = await fetcher.all_cards_async() await insert_cards_async(every_card_printing) await update_pd_legality_async() db().execute('INSERT INTO scryfall_version (last_updated) VALUES (%s)', [dtutil.dt2ts(new_date)]) db().execute( 'SET FOREIGN_KEY_CHECKS=1' ) # OK we are done monkeying with the db put the FK checks back in place and recreate _cache_card. rebuild_cache() db().commit('update_database')
def tournament_deck(cells: ResultSet, competition_id: int, date: datetime.datetime, final: Dict[str, int]) -> Optional[deck.Deck]: d: deck.RawDeckDescription = { 'source': 'Gatherling', 'competition_id': competition_id, 'created_date': dtutil.dt2ts(date) } player = cells[2] username = aliased(player.a.contents[0].string) d['mtgo_username'] = username d['finish'] = final.get(username) link = cells[4].a d['url'] = gatherling_url(link['href']) d['name'] = link.string if cells[5].find('a'): d['archetype'] = cells[5].a.string else: d['archetype'] = cells[5].string gatherling_id = urllib.parse.parse_qs( urllib.parse.urlparse(str(d['url'])).query)['id'][0] d['identifier'] = gatherling_id existing = deck.get_deck_id(d['source'], d['identifier']) if existing is not None: return deck.load_deck(existing) dlist = decklist.parse( fetcher.internal.post(gatherling_url('deckdl.php'), {'id': gatherling_id})) d['cards'] = dlist if len(dlist['maindeck']) + len(dlist['sideboard']) == 0: logger.warning( 'Rejecting deck with id {id} because it has no cards.'.format( id=gatherling_id)) return None return deck.add_deck(d)
def prepare_competitions(self): for c in getattr(self, 'competitions', []): c.competition_url = url_for('competition', competition_id=c.id) c.display_date = dtutil.display_date(c.start_date) c.ends = '' if c.end_date < dtutil.now() else dtutil.display_date( c.end_date) c.date_sort = dtutil.dt2ts(c.start_date)
def fetch() -> None: all_prices, timestamps = {}, [] ch_urls = configuration.get_list('cardhoarder_urls') if ch_urls: for _, url in enumerate(ch_urls): s = fetch_tools.fetch(url) s = ftfy.fix_encoding(s) timestamps.append( dtutil.parse_to_ts( s.split('\n', 1)[0].replace('UPDATED ', ''), '%Y-%m-%dT%H:%M:%S+00:00', dtutil.CARDHOARDER_TZ)) all_prices[url] = parser.parse_cardhoarder_prices(s) url = configuration.get_str('mtgotraders_url') if url: s = fetch_tools.fetch(url) timestamps.append(dtutil.dt2ts(dtutil.now())) all_prices['mtgotraders'] = parser.parse_mtgotraders_prices(s) if not timestamps: raise TooFewItemsException( 'Did not get any prices when fetching {urls} ({all_prices})'. format(urls=itertools.chain( configuration.get_list('cardhoarder_urls'), [configuration.get_str('mtgotraders_url')]), all_prices=all_prices)) count = store(min(timestamps), all_prices) cleanup(count)
def recent_json() -> Response: last_week = dtutil.now() - dtutil.ts2dt(7 * 24 * 60 * 60) val: Dict[str, Any] = {} val['formats'] = {} last_f: Dict[str, int] = {} for m in match.Match.query.filter( match.Match.start_time > last_week).all(): f = m.format if val['formats'].get(f.name, None) is None: val['formats'][f.name] = {} time = dtutil.dt2ts(m.start_time_aware().replace(microsecond=0, second=0, minute=0)) last = last_f.get(f.name, None) if last is not None: while last < time: last = last + 3600 val['formats'][f.name][last] = val['formats'][f.name].get( last, 0) else: last = time last_f[f.name] = last val['formats'][f.name][time] = val['formats'][f.name].get(time, 0) + 1 return return_json(val)
def price_info(c: Card) -> str: try: p = fetcher.card_price(c.name) except FetchException: return 'Price unavailable' if p is None: return 'Not available online' # Currently disabled s = '{price}'.format(price=format_price(p['price'])) try: if float(p['low']) <= 0.05: s += ' (low {low}, high {high}'.format( low=format_price(p['low']), high=format_price(p['high'])) if float(p['low']) <= MAX_PRICE_TIX and not short: s += ', {week}% this week, {month}% this month, {season}% this season'.format( week=round(float(p['week']) * 100.0), month=round(float(p['month']) * 100.0), season=round(float(p['season']) * 100.0)) s += ')' age = dtutil.dt2ts(dtutil.now()) - p['time'] if age > 60 * 60 * 2: s += '\nWARNING: price information is {display} old'.format( display=dtutil.display_time(age, 1)) except TypeError as e: print(f'Unable to get price info string from {p} because of {e}') return 'Price information is incomplete' return s
def add_or_update_news(news_item_id: int, date: datetime.datetime, title: str, url: str) -> None: ts = dtutil.dt2ts(date) if news_item_id is not None: update_news(news_item_id, ts, title, url) return add_news(ts, title, url)
def perfect_league_runs(start_date: datetime.datetime, end_date: datetime.datetime, max_items: int = sys.maxsize) -> List[Container]: where = "ct.name = 'League' AND d.created_date > {start_date} AND d.created_date <= {end_date}".format( start_date=sqlescape(dtutil.dt2ts(start_date)), end_date=sqlescape(dtutil.dt2ts(end_date))) having = 'wins >= 5 AND losses = 0' ds = deck.load_decks(where, having=having, limit=f'LIMIT {max_items}') return [ Container({ 'date': d.active_date, 'title': perfect_league_run_headline(d), 'url': url_for('deck', deck_id=d.id), 'type': 'perfect-league-run' }) for d in ds ]
def __init__(self, notes: Iterable[Container], people: Iterable[Person]) -> None: super().__init__() for n in notes: n.date_sort = dtutil.dt2ts(n.created_date) n.display_date = dtutil.display_date(n.created_date) self.notes = notes self.people = people
def prepare_deck(self, d): set_stars_and_top8(d) if d.get('colors') is not None: d.colors_safe = colors_html(d.colors, d.colored_symbols) d.person_url = '/people/{id}/'.format(id=d.person_id) d.date_sort = dtutil.dt2ts(d.date) d.display_date = dtutil.display_date(d.date) d.show_record = d.wins or d.losses or d.draws if d.competition_id: d.competition_url = '/competitions/{id}/'.format( id=d.competition_id) d.url = '/decks/{id}/'.format(id=d.id) d.export_url = '/export/{id}/'.format(id=d.id) d.cmc_chart_url = '/charts/cmc/{id}-cmc.png'.format(id=d.id) if d.is_in_current_run(): d.active_safe = '<span class="active" title="Active in the current league">⊕</span>' d.stars_safe = '{active} {stars}'.format( active=d.active_safe, stars=d.stars_safe).strip() d.source_sort = '1' d.source_is_external = False if d.source_name == 'League' else True d.comp_row_len = len("{comp_name} (Piloted by {person}".format( comp_name=d.competition_name, person=d.person)) if d.get('archetype_id', None): d.archetype_url = '/archetypes/{id}/'.format(id=d.archetype_id) if d.get('omw') is not None: d.omw = str(int(d.omw)) + '%' else: d.omw = '' d.has_legal_format = len(d.legal_formats) > 0 d.pd_legal = 'Penny Dreadful' in d.legal_formats d.legal_icons = '' sets = multiverse.SEASONS if 'Penny Dreadful' in d.legal_formats: icon = rotation.last_rotation_ex()['code'].lower() n = sets.index(icon.upper()) + 1 d.legal_icons += '<a href="{url}"><i class="ss ss-{code} ss-rare ss-grad">S{n}</i></a>'.format( url='/seasons/{id}/'.format(id=n), code=icon, n=n) past_pd_formats = [ fmt.replace('Penny Dreadful ', '') for fmt in d.legal_formats if 'Penny Dreadful ' in fmt ] past_pd_formats.sort(key=lambda code: -sets.index(code)) for code in past_pd_formats: n = sets.index(code.upper()) + 1 d.legal_icons += '<a href="{url}"><i class="ss ss-{set} ss-common ss-grad">S{n}</i></a>'.format( url='/seasons/{id}/'.format(id=n), set=code.lower(), n=n) if 'Commander' in d.legal_formats: # I think C16 looks the nicest. d.legal_icons += '<i class="ss ss-c16 ss-uncommon ss-grad">CMDR</i>' if session.get('admin') or not d.is_in_current_run(): d.decklist = str(d).replace('\n', '<br>') else: d.decklist = '' total, num_cards = 0, 0 for c in d.maindeck: if 'Land' not in c['card'].type: num_cards += c['n'] total += c['n'] * c['card'].cmc d.average_cmc = round(total / max(1, num_cards), 2)
def cleanup(count: int = 0) -> None: beginning_of_season = rotation.last_rotation() one_month_ago = dtutil.now(dtutil.WOTC_TZ) - datetime.timedelta(31) oldest_needed = min(beginning_of_season, one_month_ago) limit = '' if count > 0: limit = f'LIMIT {count * 2}' execute('DELETE FROM low_price WHERE `time` < %s ' + limit, [dtutil.dt2ts(oldest_needed)])
def test_determine_end_of_league() -> None: next_rotation = dtutil.parse('2018-02-01 00:00:00', '%Y-%m-%d %H:%M:%S', dtutil.WOTC_TZ) start_date = dtutil.parse('2017-11-01 00:00:00', '%Y-%m-%d %H:%M:%S', dtutil.WOTC_TZ) end_date = league.determine_end_of_league(start_date, next_rotation) assert dtutil.dt2ts(end_date) == 1512115199 start_date = dtutil.parse('2017-10-31 11:59:59.999', '%Y-%m-%d %H:%M:%S.%f', dtutil.WOTC_TZ) end_date = league.determine_end_of_league(start_date, next_rotation) assert dtutil.dt2ts(end_date) == 1512115199 next_rotation = dtutil.parse('2018-07-13 00:00:00', '%Y-%m-%d %H:%M:%S', dtutil.WOTC_TZ) start_date = dtutil.parse('2018-05-31 11:04:15', '%Y-%m-%d %H:%M:%S', dtutil.WOTC_TZ) end_date = league.determine_end_of_league(start_date, next_rotation) assert dtutil.dt2ts(end_date) == dtutil.dt2ts(next_rotation) - 1 start_date = dtutil.parse('2018-07-13 00:00:00', '%Y-%m-%d %H:%M:%S', dtutil.WOTC_TZ) end_date = league.determine_end_of_league(start_date, next_rotation) assert end_date == dtutil.parse('2018-07-31 23:59:59', '%Y-%m-%d %H:%M:%S', dtutil.WOTC_TZ) start_date = dtutil.parse('2018-08-01 00:00:00', '%Y-%m-%d %H:%M:%S', dtutil.WOTC_TZ) end_date = league.determine_end_of_league(start_date, next_rotation) assert end_date == dtutil.parse('2018-08-31 23:59:59', '%Y-%m-%d %H:%M:%S', dtutil.WOTC_TZ)
def active_competition_id_query(): return """ SELECT id FROM competition WHERE start_date < {now} AND end_date > {now} AND id IN ({competition_ids_by_type_select}) """.format(now=dtutil.dt2ts(dtutil.now()), competition_ids_by_type_select=query.competition_ids_by_type_select('League'))
def after_request(response: Response) -> Response: requests_until_no_intro = 20 # Typically ten page views because of async requests for the status bar. views = int(request.cookies.get('views', 0)) + 1 response.set_cookie('views', str(views)) if views >= requests_until_no_intro: response.set_cookie('hide_intro', value=str(True), expires=dtutil.dt2ts(dtutil.now()) + 60 * 60 * 24 * 365 * 10) return response
async def post_cards(self, cards, channel, replying_to=None, additional_text=''): await self.client.send_typing(channel) not_pd = configuration.get('not_pd').split(',') disable_emoji = False if channel.id in not_pd: # or (channel.server and channel.server.id in not_pd): disable_emoji = True if len(cards) == 0: if replying_to is not None: text = '{author}: No matches.'.format(author=replying_to.mention) else: text = 'No matches.' await self.client.send_message(channel, text) return cards = command.uniqify_cards(cards) more_text = '' if len(cards) > 10: more_text = ' and ' + str(len(cards) - 4) + ' more.' cards = cards[:4] if len(cards) == 1: card = cards[0] mana = emoji.replace_emoji(''.join(card.mana_cost or []), self.client) legal = ' — ' + emoji.legal_emoji(card, True) if disable_emoji: legal = '' text = '{name} {mana} — {type}{legal}'.format(name=card.name, mana=mana, type=card.type, legal=legal) if card.bug_desc is not None: text += '\n:beetle:{rank} bug: {bug}'.format(bug=card.bug_desc, rank=card.bug_class) now_ts = dtutil.dt2ts(dtutil.now()) if card.bug_last_confirmed < now_ts - 60 * 60 * 24 * 60: text += ' (Last confirmed {time} ago.)'.format(time=dtutil.display_time(now_ts - card.bug_last_confirmed, 1)) else: text = ', '.join('{name} {legal}'.format(name=card.name, legal=(emoji.legal_emoji(card)) if not disable_emoji else '') for card in cards) text += more_text if len(cards) > 10: image_file = None else: image_file = image_fetcher.download_image(cards) if image_file is None: text += '\n\n' if len(cards) == 1: text += emoji.replace_emoji(cards[0].text, self.client) else: text += 'No image available.' text += '\n' + additional_text if image_file is None: await self.client.send_message(channel, text) else: message = await self.client.send_file(channel, image_file, content=text) if message and message.attachments and message.attachments[0]['size'] == 0: print('Message size is zero so resending') await self.client.delete_message(message) await self.client.send_file(channel, image_file, content=text)
def prepare_deck(self, d: Deck) -> None: set_stars_and_top8(d) if d.get('colors') is not None: d.colors_safe = colors_html(d.colors, d.colored_symbols) d.person_url = '/people/{id}/'.format(id=d.person_id) d.date_sort = dtutil.dt2ts(d.active_date) d.display_date = dtutil.display_date(d.active_date) d.show_record = d.wins or d.losses or d.draws if d.competition_id: d.competition_url = '/competitions/{id}/'.format(id=d.competition_id) d.url = '/decks/{id}/'.format(id=d.id) d.export_url = '/export/{id}/'.format(id=d.id) d.cmc_chart_url = '/charts/cmc/{id}-cmc.png'.format(id=d.id) if d.is_in_current_run(): d.active_safe = '<span class="active" title="Active in the current league">⊕</span>' d.stars_safe = '{active} {stars}'.format(active=d.active_safe, stars=d.stars_safe).strip() d.source_sort = '1' d.source_is_external = not d.source_name == 'League' d.comp_row_len = len('{comp_name} (Piloted by {person}'.format(comp_name=d.competition_name, person=d.person)) if d.get('archetype_id', None): d.archetype_url = '/archetypes/{id}/'.format(id=d.archetype_id) # We might be getting '43%'/'' from cache or '43'/None from the db. Cope with all possibilities. # It might be better to use display_omw and omw as separate properties rather than overwriting the numeric value. if d.get('omw') is None or d.omw == '': d.omw = '' elif '%' not in str(d.omw): d.omw = str(int(d.omw)) + '%' d.has_legal_format = len(d.legal_formats) > 0 d.pd_legal = 'Penny Dreadful' in d.legal_formats d.legal_icons = '' sets = rotation.SEASONS if 'Penny Dreadful' in d.legal_formats: icon = rotation.current_season_code().lower() n = sets.index(icon.upper()) + 1 d.legal_icons += '<a href="{url}"><i class="ss ss-{code} ss-rare ss-grad">S{n}</i></a>'.format(url='/seasons/{id}/'.format(id=n), code=icon, n=n) past_pd_formats = [fmt.replace('Penny Dreadful ', '') for fmt in d.legal_formats if 'Penny Dreadful ' in fmt] past_pd_formats.sort(key=lambda code: -sets.index(code)) for code in past_pd_formats: n = sets.index(code.upper()) + 1 d.legal_icons += '<a href="{url}"><i class="ss ss-{set} ss-common ss-grad">S{n}</i></a>'.format(url='/seasons/{id}/'.format(id=n), set=code.lower(), n=n) if 'Commander' in d.legal_formats: # I think C16 looks the nicest. d.legal_icons += '<i class="ss ss-c16 ss-uncommon ss-grad">CMDR</i>' if session.get('admin') or session.get('demimod') or not d.is_in_current_run(): d.decklist = str(d).replace('\n', '<br>') else: d.decklist = '' total, num_cards = 0, 0 for c in d.maindeck: if c.card.cmc is None: c.card.cmc = 0 if 'Land' not in c.card.type_line: num_cards += c['n'] total += c['n'] * c.card.cmc d.average_cmc = round(total / max(1, num_cards), 2)
def prepare_matches(self) -> None: for m in getattr(self, 'matches', []): if m.get('date'): m.display_date = dtutil.display_date(m.date) m.date_sort = dtutil.dt2ts(m.date) if m.get('deck_id'): m.deck_url = url_for('deck', deck_id=m.deck_id) if m.get('opponent'): m.opponent_url = url_for('person', person_id=m.opponent) if m.get('opponent_deck_id'): m.opponent_deck_url = url_for('deck', deck_id=m.opponent_deck_id)
def stats() -> Dict[str, int]: sql = """ SELECT SUM(CASE WHEN FROM_UNIXTIME(`date`) >= NOW() - INTERVAL 1 DAY THEN 1 ELSE 0 END) AS num_matches_today, SUM(CASE WHEN FROM_UNIXTIME(`date`) >= NOW() - INTERVAL 7 DAY THEN 1 ELSE 0 END) AS num_matches_this_week, SUM(CASE WHEN FROM_UNIXTIME(`date`) >= NOW() - INTERVAL 30 DAY THEN 1 ELSE 0 END) AS num_matches_this_month, SUM(CASE WHEN `date` >= %s THEN 1 ELSE 0 END) AS num_matches_this_season, COUNT(*) AS num_matches_all_time FROM `match` """ return db().select(sql, [dtutil.dt2ts(rotation.last_rotation())])[0]