diff --git a/aacstats.py b/aacstats.py index 0088178..09d5a25 100644 --- a/aacstats.py +++ b/aacstats.py @@ -1,53 +1,91 @@ -from flask import Flask, request, render_template, url_for +from flask import Flask, abort, request, render_template, url_for import math import MySQLdb import MySQLdb.cursors import datetime as dt app = Flask(__name__) +PAGE_SIZE=20 +# TODO: Search + +def now(): + # TODO: If it's January and no races have been run this year, return last year + return dt.datetime.now() @app.route('/') -def index(): +@app.route('/list') +@app.route('/list/') # title = { races, rankings, runners, licence } +@app.route('/list/<title>/<int:year>') +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' + if title not in ( 'races', 'rankings', 'runners', 'licence', ): + abort(404) start = int(request.args.get('start', '0')) - limit = int(request.args.get('limit', '10')) - results = read_db(start, limit) - total = int(total_entries()) - return render_template('index.html', ltype='index', request=request, start=start, limit=limit, results=results, total=total) + limit = int(request.args.get('limit', PAGE_SIZE)) + results = read_db(start, limit, listing=title, year=year) + return render_template('list-'+title+'.html', ltype='listing', title=title, year=year, + results=results, start=start, limit=limit, + request=request, now=now(), PAGE_SIZE=PAGE_SIZE) +@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): + start = int(request.args.get('start', '0')) + limit = int(request.args.get('limit', PAGE_SIZE)) + results = read_db(start, limit, year=year) + return render_template('index.html', ltype='index', year=year, + results=results, start=start, limit=limit, + request=request, now=now(), PAGE_SIZE=PAGE_SIZE) -@app.route('/race/<year>/<title>') -def race(start=0, year=None, title=None): + +@app.route('/race/<int:year>/<title>') +def race(year=None, title=None): start = int(request.args.get('start', '0')) - limit = int(request.args.get('limit', '10')) - total = int(total_entries(event=title, year=year)) + limit = int(request.args.get('limit', PAGE_SIZE)) results = read_db(start, limit, event=title, year=year) - return render_template('index.html', ltype='race', title=title, year=year, request=request, start=start, limit=limit, results=results, total=total) + return render_template('index.html', ltype='race', title=title, year=year, + results=results, start=start, limit=limit, + request=request, now=now(), PAGE_SIZE=PAGE_SIZE) @app.route('/person/<title>') -@app.route('/person/<title>/<year>') -def person(start=0, title=None, year=None): +@app.route('/person/<title>/<int:year>') +def person(title=None, year=None): start = int(request.args.get('start', '0')) - limit = int(request.args.get('limit', '10')) - total = int(total_entries(person=title, year=year)) + limit = int(request.args.get('limit', PAGE_SIZE)) results = read_db(start, limit, person=title, year=year) - return render_template('index.html', ltype='person', title=title, year=year, request=request, start=start, limit=limit, results=results, total=total) + return render_template('index.html', ltype='person', title=title, year=year, + results=results, start=start, limit=limit, + request=request, now=now(), PAGE_SIZE=PAGE_SIZE) -@app.route('/licence/<year>/<title>') -def licence(start=0, year=dt.datetime.now().year, title=None): +@app.route('/licence/<int:year>') +@app.route('/licence/<int:year>/<title>') +def licence(year=now().year, title=None): start = int(request.args.get('start', '0')) - limit = int(request.args.get('limit', '10')) - total = int(total_entries(licence=title, year=year)) + limit = int(request.args.get('limit', PAGE_SIZE)) results = read_db(start, limit, licence=title, year=year) - return render_template('index.html', ltype='licence', title=title, year=year, request=request, start=start, limit=limit, results=results, total=total) + return render_template('index.html', ltype='licence', title=title, year=year, + results=results, start=start, limit=limit, + request=request, now=now(), PAGE_SIZE=PAGE_SIZE) -def read_db(start=0, limit=10, person=None, licence=None, event=None, year=None): +def read_db(start=0, limit=PAGE_SIZE, listing=None, event=None, person=None, licence=None, year=None): db = MySQLdb.connect(user = 'aac', passwd = 'saOAcCWHg4LaoSSA', db = 'AAC', cursorclass = MySQLdb.cursors.DictCursor) c = db.cursor() - where = 'club LIKE "AAC"' - if person: - where += ' AND CONCAT_WS(" ", name, surname) LIKE "{}"'.format(person) + + count = 0 + + select = '*' + where = 'WHERE club LIKE "AAC"' + group = '' + order = 'date DESC, event, position' + close = '' if event: where += ' AND event LIKE "{}"'.format(event) + if person: + where += ' AND CONCAT_WS(" ", name, surname) LIKE "{}"'.format(person) if licence: where += ' AND licence LIKE "{}"'.format(licence) if year: @@ -56,51 +94,77 @@ def read_db(start=0, limit=10, person=None, licence=None, event=None, year=None) firstdate = firstdate.replace(year=int(year)) lastdate = lastdate.replace(year=int(year)) where += ' AND date > "{}" AND date < "{}"'.format(firstdate, lastdate) - sql = 'SELECT * FROM `results` WHERE {} ORDER BY date DESC, event, position LIMIT {},{};'.format(where, start, limit) - c.execute(sql) - results = c.fetchall() - return results -@app.template_filter('total_entries') -def total_entries(person=None, licence=None, event=None, year=None): - db = MySQLdb.connect(user = 'aac', passwd = 'saOAcCWHg4LaoSSA', db = 'AAC', cursorclass = MySQLdb.cursors.DictCursor) - c = db.cursor() - where = 'club LIKE "AAC"' - if person: - where += ' AND CONCAT_WS(" ", name, surname) LIKE "{}"'.format(person) - if event: - where += ' AND event LIKE "{}"'.format(event) - if licence: - where += ' AND licence LIKE "{}"'.format(licence) - if year: - 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) - sql = 'SELECT COUNT(*) FROM `results` WHERE {}'.format(where) + if listing: + if listing == 'races': + 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' + + sql = 'SELECT {} FROM `results` {} {} ORDER BY {} LIMIT {},{} {};'.format(select, where, group, order, start, limit, close) + #app.logger.debug(sql) c.execute(sql) - for x in c.fetchone().values(): - return x + queryresults = c.fetchall() + + select = 'COUNT(*)' + if listing: + if listing == 'races': + select = 'COUNT(*) FROM ( SELECT COUNT(event)' + close = ') races' + 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) + c.execute(sql) + countresult = c.fetchone() + for x in countresult.keys(): + count = countresult[x] + app.logger.debug(count) + + return { 'count': int(count), 'rows': queryresults } + @app.template_filter('pace') def pace(time): return (dt.datetime(1,1,1) + time).strftime('%M:%S') -@app.template_filter('clean_date') +@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('year') -def year(time): - return time.strftime('%Y') - @app.template_filter('ordinal') def ordinal(n): return "%d%s" % (n,"tsnrhtdd"[(math.floor(n/10)%10!=1)*(n%10<4)*n%10::4]) + if __name__ == '__main__': app.run(debug=True) @@ -112,9 +176,15 @@ if __name__ == '__main__': # by race # - and by gender -# default: list of races (sorted by recent) -# tabs: list of [races (sorted by recent), people (sorted by total kms for the year), licences for the year (sorted by number of races), podiums/winners (people sorted by total_position/total_races), ] -# click to expand by [race (all AAC members by position), person (races by recent), person (races by pace)] +# 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) # SEARCH # /?sort={distance,pace}&sex={m,f} @@ -122,6 +192,4 @@ if __name__ == '__main__': # /person/Timothy Allen?sort={pace,date} # /person/Timothy Allen/2018 # /license/2018/4356 -# /license/2018/4356/2018 -# -# TODO LIMIT/pagination +# /license/2018/4356 diff --git a/static/AAC_224x224.png b/static/AAC_224x224.png new file mode 100644 index 0000000..0d7abd5 Binary files /dev/null and b/static/AAC_224x224.png differ diff --git a/static/AAC_224x95.png b/static/AAC_224x95.png new file mode 100644 index 0000000..0c34863 Binary files /dev/null and b/static/AAC_224x95.png differ diff --git a/static/style.css b/static/style.css index 33295ea..d06f674 100644 --- a/static/style.css +++ b/static/style.css @@ -1,9 +1,16 @@ body { - margin: 0 auto; + margin: 0 auto 15px; + padding: 0 8px; max-width: 800px; font-family: 'Roboto Condensed', sans-serif; font-size: 11pt; } +a { + color: #154996; +} +h1 { + text-align: center; +} table { border-collapse: collapse; width: 100%; @@ -22,11 +29,45 @@ table tr td:last-child { table tr td.nowrap { white-space: nowrap; } -nav.nextprev { +nav { + display: flex; + background-color: #154996; text-align: center; +} +nav span { + flex: 1 1 auto; +} +nav span { +} +nav span a { + display: block; + color: #FFF; +} +nav.nextprev { margin-top: 10px; + padding: 0 15px; } -nav.nextprev span.prev { -} +nav.nextprev span.first, +nav.nextprev span.last, +nav.nextprev span.prev, nav.nextprev span.next { + padding: 0 2px; +} +nav.nextprev span.plain, +nav.nextprev span a { + display: block; + padding: 10px 5px; +} +nav.tabs { +} +nav.tabs span { + border-right: 1px solid #FFF; +} +nav.tabs span:last-child { + border-right: none; +} +nav.tabs span a { + padding: 15px 10px; + font-size: 11pt; + font-weight: bold; } diff --git a/templates/head.html b/templates/head.html new file mode 100644 index 0000000..d153e9c --- /dev/null +++ b/templates/head.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html lang="en-ZA"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width"> + <title>Atlantic Athletic Club + + + +{%- set ns.limit = limit -%} +{%- if ns.limit == 0 or ns.limit == PAGE_SIZE -%} + {%- set ns.limit = None -%} +{%- endif -%} + +{% include 'tabs.html' with context %} diff --git a/templates/index.html b/templates/index.html index 7cd5cd8..c11cdee 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,29 +1,13 @@ - - - - - - AAC Statistics - - - -{%- set ns = namespace() -%} +{% set ns = namespace() -%} -{%- set ns.limit = limit -%} -{%- if ns.limit == 0 or ns.limit == 10 -%} - {%- set ns.limit = None -%} -{%- endif -%} - -{%- set ns.year = year -%} -{%- if ns.year == 0 -%} - {%- set ns.year = None -%} -{%- endif -%} - - - +{% include 'head.html' with context %}
-

AAC Statistics {% if title %}: {{ title }}{% endif %}

-{%- if results -%} +

AAC Statistics {% if title %}: {{ title }}{% endif %}{% if year %} {{ year }}{% endif %}

+{% if results -%} + {%- set ns.total = 0 -%} + {%- if 'count' in results -%} + {%- set ns.total = results['count'] -%} + {%- endif -%} @@ -42,7 +26,7 @@ - {%- for row in results -%} + {%- for row in results['rows'] -%} {%- set person='{} {}'.format(row.name, row.surname) -%} {%- if distance %}{# set total_km += row.distance #}{% endif -%} @@ -56,67 +40,20 @@ {%- if ltype != 'event' -%} {%- endif -%} - + {%- endfor -%}
{{ row.event }} ({{ row.distance }} KM){{ row.date | clean_date }}{{ row.date | cleandate }} - {% if row.sex and row.sexposition and row.sexposition <= 100 %}{{ row.sexposition | ordinal }} {{ row.sex.lower() }}{% endif %} - {% if row.sexposition and row.catposition %}/{% endif %} - {% if row.catposition and row.catposition <= 100 %}{{ row.catposition | ordinal }} in category{% endif %} + {%- if row.sex and row.sexposition and row.sexposition <= 100 %}{{ row.sexposition | ordinal }} {{ row.sex.lower() }}{% endif -%} + {%- if row.sexposition and row.catposition %}/{% endif -%} + {%- if row.catposition and row.catposition <= 100 %}{{ row.catposition | ordinal }} in category{% endif -%}
-{%- endif -%} - +{%- endif %}
+ diff --git a/templates/list-licence.html b/templates/list-licence.html new file mode 100644 index 0000000..351b7af --- /dev/null +++ b/templates/list-licence.html @@ -0,0 +1,34 @@ +{% set ns = namespace() -%} + +{% include 'head.html' with context %} +
+

AAC Statistics: {% if year %} {{ year }}{% endif %}{% if title %} {{ title | title }}{% endif %}

+{% if results -%} + {%- set ns.total = 0 -%} + {%- if 'count' in results -%} + {%- set ns.total = results['count'] -%} + {%- endif -%} + + + + + + + + + {%- for row in results['rows'] -%} + + + + + + {%- endfor -%} + +
LicenceName
{{ row.licence }}{{ row.person }}{{ row.date | year }}
+{%- endif %} +
+ + + diff --git a/templates/list-licences.html b/templates/list-licences.html new file mode 100644 index 0000000..c0ce7e4 --- /dev/null +++ b/templates/list-licences.html @@ -0,0 +1,33 @@ +{% set ns = namespace() -%} + +{% include 'head.html' with context %} +
+

AAC Statistics: {% if year %} {{ year }}{% endif %}{% if title %} {{ title | title }}{% endif %}

+{% if results -%} + {%- set ns.total = 0 -%} + {%- if 'count' in results -%} + {%- set ns.total = results['count'] -%} + {%- endif -%} + + + + + + + + + {%- for row in results['rows'] -%} + + + + + {%- endfor -%} + +
LicenceName
{{ row.licence }}{{ row.person }}
+{%- endif %} +
+ + + diff --git a/templates/list-races.html b/templates/list-races.html new file mode 100644 index 0000000..967b9f0 --- /dev/null +++ b/templates/list-races.html @@ -0,0 +1,33 @@ +{% set ns = namespace() -%} + +{% include 'head.html' with context %} +
+

AAC Statistics: {% if year %} {{ year }}{% endif %}{% if title %} {{ title | title }}{% endif %}

+{% if results -%} + {%- set ns.total = 0 -%} + {%- if 'count' in results -%} + {%- set ns.total = results['count'] -%} + {%- endif -%} + + + + + + + + + {%- for row in results['rows'] -%} + + + + + {%- endfor -%} + +
EventDate
{{ row.event }}{{ row.date | cleandate }}
+{%- endif %} +
+ + + diff --git a/templates/list-rankings.html b/templates/list-rankings.html new file mode 100644 index 0000000..951cfeb --- /dev/null +++ b/templates/list-rankings.html @@ -0,0 +1,37 @@ +{% set ns = namespace() -%} + +{% include 'head.html' with context %} +
+

AAC Statistics: {% if year %} {{ year }}{% endif %}{% if title %} {{ title | title }}{% endif %}

+{% if results -%} + {%- set ns.total = 0 -%} + {%- if 'count' in results -%} + {%- set ns.total = results['count'] -%} + {%- endif -%} + + + + + + + + + + + {%- for row in results['rows'] -%} + + + + + + + {%- endfor -%} + +
NameAverage positionSum of race positionsNumber of races
{{ row.person }}{{ row.score }}{{ row.positions }}{{ row.races }}
+{%- endif %} +
+ + + diff --git a/templates/list-runners.html b/templates/list-runners.html new file mode 100644 index 0000000..05b6bd9 --- /dev/null +++ b/templates/list-runners.html @@ -0,0 +1,33 @@ +{% set ns = namespace() -%} + +{% include 'head.html' with context %} +
+

AAC Statistics: {% if year %} {{ year }}{% endif %} Top Runners by Race Mileage

+{% if results -%} + {%- set ns.total = 0 -%} + {%- if 'count' in results -%} + {%- set ns.total = results['count'] -%} + {%- endif -%} + + + + + + + + + {%- for row in results['rows'] -%} + + + + + {%- endfor -%} + +
NameDistance
{{ row.person }}{{ row.total }}
+{%- endif %} +
+ + + diff --git a/templates/prevnext.html b/templates/prevnext.html new file mode 100644 index 0000000..ef7be75 --- /dev/null +++ b/templates/prevnext.html @@ -0,0 +1,55 @@ + diff --git a/templates/tabs.html b/templates/tabs.html new file mode 100644 index 0000000..ba34592 --- /dev/null +++ b/templates/tabs.html @@ -0,0 +1,7 @@ +