2018-08-14 08:55:34 +00:00
from flask import Flask , abort , request , render_template , url_for
2018-08-14 08:57:55 +00:00
import datetime as dt
2018-08-14 08:54:02 +00:00
import math
import MySQLdb
import MySQLdb . cursors
2018-08-14 08:57:55 +00:00
import re
2018-08-14 08:59:12 +00:00
import urllib . parse
2018-08-14 08:54:02 +00:00
app = Flask ( __name__ )
2018-08-14 08:55:34 +00:00
PAGE_SIZE = 20
2018-08-14 08:57:55 +00:00
MIN_MONTHS_FOR_LISTINGS = 3
2018-08-14 08:55:34 +00:00
2018-08-14 08:59:12 +00:00
@app.template_filter ( ' urlescape ' )
def urlescape ( string ) :
2018-08-14 09:01:26 +00:00
if string is None :
return ' '
2018-08-14 08:59:12 +00:00
return urllib . parse . quote_plus ( string )
@app.template_filter ( ' pace ' )
def pace ( time ) :
return ( dt . datetime ( 1 , 1 , 1 ) + time ) . strftime ( ' % M: % S ' )
@app.template_filter ( ' year ' )
def year ( time ) :
return time . strftime ( ' % Y ' )
@app.template_filter ( ' cleandate ' )
2018-08-14 09:01:26 +00:00
def cleandate ( time ) :
2018-08-14 08:59:12 +00:00
if time . month == 1 and time . day == 1 :
return time . strftime ( ' % Y ' )
return time . strftime ( ' % Y- % m- %d ' )
@app.template_filter ( ' ordinal ' )
def ordinal ( n ) :
if not isinstance ( n , int ) or n < 1 :
return
return " %d %s " % ( n , " tsnrhtdd " [ ( math . floor ( n / 10 ) % 10 != 1 ) * ( n % 10 < 4 ) * n % 10 : : 4 ] )
2018-08-14 08:54:02 +00:00
2018-08-14 09:01:26 +00:00
@app.template_filter ( ' cleandict ' )
def cleandict ( dict ) :
''' Prevent duplication of existing query strings when calling url_for(..., **request.args) '''
newdict = { }
for key , value in dict . items ( ) :
if key not in ( ' title ' , ' year ' , ' start ' , ' show ' ) and value not in ( None , ' ' , ) :
newdict [ key ] = value
return newdict
def now ( ) :
return dt . datetime . now ( )
def getstart ( ) :
start = request . args . get ( ' start ' , ' 0 ' )
if not isinstance ( start , ( int , float ) ) :
try :
return int ( start )
except :
return 0
return start
def getshow ( ) :
show = request . args . get ( ' show ' , PAGE_SIZE )
if show == ' all ' :
return - 1
if not isinstance ( show , ( int , float ) ) :
try :
return int ( show )
except :
return PAGE_SIZE
return show
def read_db ( listing = None , event = None , person = None , licence = None , search = dict ( ) , year = None , finishers = False ) :
2018-08-14 08:57:55 +00:00
db = MySQLdb . connect ( user = ' aac ' , passwd = ' saOAcCWHg4LaoSSA ' , db = ' AAC ' ,
use_unicode = True , charset = " utf8 " , cursorclass = MySQLdb . cursors . DictCursor )
2018-08-14 08:54:02 +00:00
c = db . cursor ( )
2018-08-14 08:55:34 +00:00
count = 0
2018-08-14 08:59:12 +00:00
start = getstart ( )
show = getshow ( )
2018-08-14 08:55:34 +00:00
select = ' * '
2018-08-14 09:01:26 +00:00
close = ' '
where = ' WHERE club LIKE " AAC " '
group = ' '
2018-08-14 09:03:45 +00:00
order = ' date DESC, event, distance DESC, position '
2018-08-14 09:01:26 +00:00
limit = ' LIMIT {} , {} ' . format ( start , show )
2018-08-14 08:59:12 +00:00
if show == - 1 :
limit = ' '
2018-08-14 08:54:02 +00:00
if event :
2018-08-14 08:57:55 +00:00
if isinstance ( event , str ) :
event = db . escape_string ( event ) . decode ( )
where + = ' AND event LIKE " {} " ' . format ( event . lower ( ) )
2018-08-14 08:54:02 +00:00
if person :
2018-08-14 08:57:55 +00:00
if isinstance ( person , str ) :
person = db . escape_string ( person ) . decode ( )
where + = ' AND CONCAT_WS( " " , name, surname) LIKE " {} " ' . format ( person . lower ( ) )
2018-08-14 08:54:02 +00:00
if licence :
2018-08-14 08:57:55 +00:00
if isinstance ( licence , str ) :
licence = db . escape_string ( licence ) . decode ( )
where + = ' AND licence LIKE " {} " ' . format ( licence . lower ( ) )
2018-08-14 08:54:02 +00:00
if year :
2018-08-14 08:57:55 +00:00
if not isinstance ( year , ( int , float ) ) :
year = int ( db . escape_string ( year ) . decode ( ) )
2018-08-14 08:54:02 +00:00
firstdate = dt . datetime . min
lastdate = dt . datetime . max
firstdate = firstdate . replace ( year = int ( year ) )
lastdate = lastdate . replace ( year = int ( year ) )
where + = ' AND date > " {} " AND date < " {} " ' . format ( firstdate , lastdate )
2018-08-14 09:01:26 +00:00
''' This statement is expensive but doesn ' t increase the count, so don ' t change the count statement '''
if finishers :
select = ' total.finishers, query.* FROM( SELECT * '
close = ' ) AS query INNER JOIN (SELECT event, date, distance, COUNT(event) as finishers FROM `results` GROUP BY event, distance, date) AS total ON total.event=query.event AND total.date=query.date AND total.distance=query.distance '
2018-08-14 08:55:34 +00:00
2018-08-14 08:57:55 +00:00
for column in search . keys ( ) :
if isinstance ( column , str ) :
column = db . escape_string ( column ) . decode ( )
if isinstance ( search [ column ] , str ) :
query = db . escape_string ( search [ column ] ) . decode ( )
if not query :
next
if column in ( ' licence ' , ' event ' , ) :
where + = ' AND {} LIKE " % {} % " ' . format ( column , query . lower ( ) )
elif column in ( ' name ' , ) :
where + = ' AND ( CONCAT_WS( " " , name, surname) LIKE " % {} % " ) ' . format ( query . lower ( ) )
elif column in ( ' position ' , ) :
where + = ' AND {} LIKE " {} " ' . format ( column , query )
elif column == ' after ' :
try :
after = dt . datetime . strptime ( query , ' % Y- % m- %d ' )
date = dt . datetime . min
date = date . replace ( year = after . year , month = after . month , day = after . day )
where + = ' AND date > " {} " ' . format ( date )
except :
pass
elif column == ' before ' :
try :
before = dt . datetime . strptime ( query , ' % Y- % m- %d ' )
date = dt . datetime . max
date = date . replace ( year = before . year , month = before . month , day = before . day )
where + = ' AND date < " {} " ' . format ( date )
except :
pass
else :
pass
2018-08-14 08:55:34 +00:00
if listing :
2018-08-14 09:02:56 +00:00
if listing == ' races ' :
2018-08-14 08:55:34 +00:00
select = ' event, date '
2018-08-14 09:03:45 +00:00
group = ' GROUP BY event, date '
order = ' date DESC, event '
2018-08-14 08:55:34 +00:00
elif listing == ' runners ' :
select = ' CONCAT_WS( " " , name, surname) person, FORMAT(SUM(distance),0) total '
2018-08-14 09:03:45 +00:00
where + = ' AND CONCAT_WS( " " , name, surname) NOT LIKE " % no return % " '
where + = ' AND CONCAT_WS( " " , name, surname) NOT LIKE " % no card % " '
where + = ' AND CONCAT_WS( " " , name, surname) NOT LIKE " % blank card % " '
where + = ' AND CONCAT_WS( " " , name, surname) NOT LIKE " %d isqualified % " '
2018-08-14 08:55:34 +00:00
group = ' GROUP BY CONCAT_WS( " " , name, surname) '
2018-08-14 09:03:45 +00:00
order = ' SUM(distance) DESC, CONCAT_WS( " " , name, surname) '
2018-08-14 08:55:34 +00:00
elif listing == ' rankings ' :
select = ' CONCAT_WS( " " , name, surname) person, SUM(position) positions, COUNT(event) races, SUM(position)/COUNT(event) podiums, FORMAT(SUM(position)/COUNT(event),1) score '
2018-08-14 09:03:45 +00:00
where + = ' AND CONCAT_WS( " " , name, surname) NOT LIKE " % no return % " '
where + = ' AND CONCAT_WS( " " , name, surname) NOT LIKE " % no card % " '
where + = ' AND CONCAT_WS( " " , name, surname) NOT LIKE " % blank card % " '
where + = ' AND CONCAT_WS( " " , name, surname) NOT LIKE " %d isqualified % " '
2018-08-14 08:55:34 +00:00
group = ' GROUP BY CONCAT_WS( " " , name, surname) '
order = ' podiums, races DESC '
elif listing == ' licence ' :
select = ' licence, date, CONCAT_WS( " " , name, surname) person '
2018-08-14 09:03:45 +00:00
group = ' GROUP BY licence, CONCAT_WS( " " , name, surname) '
order = ' CONCAT_WS( " " , name, surname), date DESC '
2018-08-14 08:59:12 +00:00
2018-08-14 08:55:34 +00:00
2018-08-14 08:59:12 +00:00
sql = ' SELECT {} FROM `results` {} {} ORDER BY {} {} {} ; ' . format ( select , where , group , order , limit , close )
2018-08-14 08:57:55 +00:00
app . logger . debug ( sql )
2018-08-14 08:55:34 +00:00
c . execute ( sql )
queryresults = c . fetchall ( )
select = ' COUNT(*) '
2018-08-14 09:01:26 +00:00
close = ' '
2018-08-14 08:55:34 +00:00
if listing :
2018-08-14 09:02:56 +00:00
if listing == ' races ' :
2018-08-14 08:55:34 +00:00
select = ' COUNT(*) FROM ( SELECT COUNT(event) '
2018-08-14 09:02:56 +00:00
close = ' ) AS races '
2018-08-14 08:55:34 +00:00
elif listing == ' runners ' :
select = ' COUNT(*) FROM ( SELECT COUNT(name) '
2018-08-14 09:02:56 +00:00
close = ' ) AS runners '
2018-08-14 08:55:34 +00:00
elif listing == ' rankings ' :
select = ' COUNT(*) FROM ( SELECT COUNT(name) '
2018-08-14 09:02:56 +00:00
close = ' ) AS rankings '
2018-08-14 08:55:34 +00:00
elif listing == ' licence ' :
2018-08-14 09:02:56 +00:00
select = ' COUNT(*) FROM ( SELECT COUNT(licence) '
close = ' ) AS licence '
2018-08-14 08:55:34 +00:00
sql = ' SELECT {} FROM `results` {} {} {} ; ' . format ( select , where , group , close )
2018-08-14 08:59:12 +00:00
app . logger . debug ( sql )
2018-08-14 08:54:02 +00:00
c . execute ( sql )
2018-08-14 08:55:34 +00:00
countresult = c . fetchone ( )
for x in countresult . keys ( ) :
count = countresult [ x ]
2018-08-14 08:59:12 +00:00
app . logger . debug ( count )
2018-08-14 08:55:34 +00:00
return { ' count ' : int ( count ) , ' rows ' : queryresults }
2018-08-14 08:54:02 +00:00
2018-08-14 08:57:55 +00:00
@app.route ( ' / ' )
2018-08-14 08:59:12 +00:00
@app.route ( ' /<title> ' )
@app.route ( ' /<title>/<int:year> ' )
2018-08-14 08:57:55 +00:00
def list ( title = None , year = None ) :
''' Set defaults for the index page '''
if year is None and title is None :
year = now ( ) . year
title = ' runners '
2018-08-14 08:59:12 +00:00
title = urllib . parse . unquote_plus ( title )
2018-08-14 09:02:56 +00:00
if title not in ( ' races ' , ' rankings ' , ' runners ' , ' licence ' , ) :
2018-08-14 08:57:55 +00:00
abort ( 404 )
''' In early January, we ' ll be left with blank pages in listings, since there won ' t
be any results yet , so hack it to return last year ' s info ' ' '
date = dt . datetime . now ( )
if date . year == year and date . month < = MIN_MONTHS_FOR_LISTINGS :
results = read_db ( year = date . year )
app . logger . debug ( results [ ' count ' ] )
if results [ ' count ' ] < 1 :
''' get an approximate time about two months ago (at the end of the previous year) '''
lastyear = date - dt . timedelta ( days = ( MIN_MONTHS_FOR_LISTINGS * 31 ) )
year = lastyear . year
2018-08-14 08:59:12 +00:00
results = read_db ( listing = title , year = year )
return render_template ( ' list- ' + title + ' .html ' , ltype = ' listing ' , title = title ,
results = results , year = year ,
request = request , getstart = getstart ( ) , getshow = getshow ( ) , now = now ( ) , PAGE_SIZE = PAGE_SIZE )
2018-08-14 08:57:55 +00:00
@app.route ( ' /all ' )
@app.route ( ' /all/<int:year> ' )
def index ( title = None , year = None ) :
2018-08-14 08:59:12 +00:00
if title is not None :
title = urllib . parse . unquote_plus ( title )
results = read_db ( year = year )
2018-08-14 09:02:56 +00:00
return render_template ( ' index.html ' , ltype = ' index ' , title = title ,
results = results , year = year ,
2018-08-14 08:59:12 +00:00
request = request , getstart = getstart ( ) , getshow = getshow ( ) , now = now ( ) , PAGE_SIZE = PAGE_SIZE )
2018-08-14 08:57:55 +00:00
2018-08-14 09:02:56 +00:00
@app.route ( ' /races/<int:year>/<title> ' )
def races ( year = None , title = None ) :
2018-08-14 08:59:12 +00:00
if title is not None :
title = urllib . parse . unquote_plus ( title )
results = read_db ( event = title , year = year )
2018-08-14 09:02:56 +00:00
return render_template ( ' index.html ' , ltype = ' races ' , title = title ,
2018-08-14 08:59:12 +00:00
results = results , year = year ,
request = request , getstart = getstart ( ) , getshow = getshow ( ) , now = now ( ) , PAGE_SIZE = PAGE_SIZE )
2018-08-14 08:57:55 +00:00
@app.route ( ' /person/<title> ' )
@app.route ( ' /person/<title>/<int:year> ' )
def person ( title = None , year = None ) :
2018-08-14 08:59:12 +00:00
if title is not None :
title = urllib . parse . unquote_plus ( title )
2018-08-14 09:01:26 +00:00
results = read_db ( person = title , year = year , finishers = True )
2018-08-14 08:59:12 +00:00
return render_template ( ' index.html ' , ltype = ' person ' , title = title ,
results = results , year = year ,
request = request , getstart = getstart ( ) , getshow = getshow ( ) , now = now ( ) , PAGE_SIZE = PAGE_SIZE )
2018-08-14 08:57:55 +00:00
@app.route ( ' /licence/<int:year>/<title> ' )
def licence ( year = now ( ) . year , title = None ) :
2018-08-14 08:59:12 +00:00
if title is not None :
title = urllib . parse . unquote_plus ( title )
2018-08-14 09:01:26 +00:00
results = read_db ( licence = title , year = year , finishers = True )
2018-08-14 08:59:12 +00:00
return render_template ( ' index.html ' , ltype = ' licence ' , title = title ,
results = results , year = year ,
request = request , getstart = getstart ( ) , getshow = getshow ( ) , now = now ( ) , PAGE_SIZE = PAGE_SIZE )
2018-08-14 08:57:55 +00:00
@app.route ( ' /search ' )
def search ( ) :
2018-08-14 08:59:12 +00:00
results = read_db ( search = request . args )
2018-08-14 09:02:56 +00:00
return render_template ( ' index.html ' , ltype = ' search ' , title = None ,
2018-08-14 08:59:12 +00:00
results = results , year = None ,
request = request , getstart = getstart ( ) , getshow = getshow ( ) , now = now ( ) , PAGE_SIZE = PAGE_SIZE )
2018-08-14 08:57:55 +00:00
2018-08-14 08:55:34 +00:00
2018-08-14 08:54:02 +00:00
if __name__ == ' __main__ ' :
app . run ( debug = True )
# most race KMs in the year, by distance
# fastest race pace over the year (time / KMs)
# individual KMs, and race results (race, position, time) by name
# - and by race number and year
# by race
# - and by gender
2018-08-14 08:55:34 +00:00
# tabs:
# list of races (sorted by recent)
# list of people (sorted by total kms for the year)
# list of licences for the year (sorted by number of races)
# list of podiums/rankings (people sorted by total_position/total_races)
# click to expand by
# person (races by recent)
# person (races by pace)]
# race (all AAC members by position)
2018-08-14 08:54:02 +00:00
# SEARCH
# /?sort={distance,pace}&sex={m,f}
# /race/2018/HEWAT ETC?sort={position,pace}&sex={m,f}
# /person/Timothy Allen?sort={pace,date}
# /person/Timothy Allen/2018
# /license/2018/4356
2018-08-14 08:55:34 +00:00
# /license/2018/4356