AACResults/aacstats.py

280 lines
10 KiB
Python
Raw Normal View History

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
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
def now():
return dt.datetime.now()
def getstart():
start = request.args.get('start', '0')
if not isinstance(start, (int, float)):
return 0
return start
def getshow():
show = request.args.get('show', PAGE_SIZE)
if show == 'all':
return -1
if not isinstance(show, (int, float)):
return PAGE_SIZE
return show
@app.template_filter('urlescape')
def urlescape(string):
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')
def clean_date(time):
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
def read_db(listing=None, event=None, person=None, licence=None, search=dict(), year=None):
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
start = getstart()
show = getshow()
2018-08-14 08:55:34 +00:00
select = '*'
close = ''
2018-08-14 08:55:34 +00:00
where = 'WHERE club LIKE "AAC"'
group = ''
order = 'date DESC, event, position'
limit = 'LIMIT {},{}'.format(start, show)
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 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:
if listing == 'race':
2018-08-14 08:55:34 +00:00
select = 'event, date'
group = 'GROUP BY event'
elif listing == 'runners':
select = 'CONCAT_WS(" ", name, surname) person, FORMAT(SUM(distance),0) total'
where += ' AND CONCAT_WS(" ", name, surname) NOT LIKE "%NO RETURN%"'
where += ' AND CONCAT_WS(" ", name, surname) NOT LIKE "%BLANK CARD%"'
group = 'GROUP BY CONCAT_WS(" ", name, surname)'
order = 'SUM(distance) DESC, surname'
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'
group = 'GROUP BY CONCAT_WS(" ", name, surname)'
order = 'podiums, races DESC'
elif listing == 'licence':
select = 'licence, date, CONCAT_WS(" ", name, surname) person'
group = 'GROUP BY licence'
order = 'surname, date DESC'
2018-08-14 08:55:34 +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(*)'
if listing:
if listing == 'race':
2018-08-14 08:55:34 +00:00
select = 'COUNT(*) FROM ( SELECT COUNT(event)'
close = ') race'
2018-08-14 08:55:34 +00:00
elif listing == 'runners':
select = 'COUNT(*) FROM ( SELECT COUNT(name)'
group = 'GROUP BY CONCAT_WS(" ", name, surname)'
close = ') runners'
elif listing == 'rankings':
select = 'COUNT(*) FROM ( SELECT COUNT(name)'
group = 'GROUP BY CONCAT_WS(" ", name, surname)'
close = ') rankings'
elif listing == 'licence':
pass
sql = 'SELECT {} FROM `results` {} {} {};'.format(select, where, group, close)
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]
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('/')
@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'
title = urllib.parse.unquote_plus(title)
if title not in ( 'race', '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
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>')
@app.route('/all/<int:year>/<title>') # this does nothing, and is simply here to prevent title from being added to the query string
def index(title=None, year=None):
if title is not None:
title = urllib.parse.unquote_plus(title)
results = read_db(year=year)
return render_template('index.html', ltype='index',
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('/race/<int:year>/<title>')
def race(year=None, title=None):
if title is not None:
title = urllib.parse.unquote_plus(title)
results = read_db(event=title, year=year)
return render_template('index.html', ltype='race', 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('/person/<title>')
@app.route('/person/<title>/<int:year>')
def person(title=None, year=None):
if title is not None:
title = urllib.parse.unquote_plus(title)
results = read_db(person=title, year=year)
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):
if title is not None:
title = urllib.parse.unquote_plus(title)
results = read_db(licence=title, year=year)
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():
results = read_db(search=request.args)
return render_template('index.html', ltype='search', title='Search',
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