diff --git a/aacstats.py b/aacstats.py index 2d3a7a6..f600324 100644 --- a/aacstats.py +++ b/aacstats.py @@ -4,24 +4,70 @@ import math import MySQLdb import MySQLdb.cursors import re +import urllib.parse app = Flask(__name__) PAGE_SIZE=20 MIN_MONTHS_FOR_LISTINGS=3 +def now(): + return dt.datetime.now() -def read_db(start=0, limit=PAGE_SIZE, listing=None, event=None, person=None, licence=None, search=dict(), year=None): +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]) + +def read_db(listing=None, event=None, person=None, licence=None, search=dict(), year=None): db = MySQLdb.connect(user='aac', passwd='saOAcCWHg4LaoSSA', db='AAC', use_unicode=True, charset="utf8", cursorclass=MySQLdb.cursors.DictCursor) c = db.cursor() - count = 0 + start = getstart() + show = getshow() select = '*' + close = '' where = 'WHERE club LIKE "AAC"' group = '' order = 'date DESC, event, position' - close = '' + limit = 'LIMIT {},{}'.format(start, show) + if show == -1: + limit = '' + if event: if isinstance(event, str): event = db.escape_string(event).decode() @@ -76,7 +122,7 @@ def read_db(start=0, limit=PAGE_SIZE, listing=None, event=None, person=None, lic pass if listing: - if listing == 'races': + if listing == 'race': select = 'event, date' group = 'GROUP BY event' elif listing == 'runners': @@ -93,17 +139,18 @@ def read_db(start=0, limit=PAGE_SIZE, listing=None, event=None, person=None, lic 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) + sql = 'SELECT {} FROM `results` {} {} ORDER BY {} {} {};'.format(select, where, group, order, limit, close) app.logger.debug(sql) c.execute(sql) queryresults = c.fetchall() select = 'COUNT(*)' if listing: - if listing == 'races': + if listing == 'race': select = 'COUNT(*) FROM ( SELECT COUNT(event)' - close = ') races' + close = ') race' elif listing == 'runners': select = 'COUNT(*) FROM ( SELECT COUNT(name)' group = 'GROUP BY CONCAT_WS(" ", name, surname)' @@ -116,49 +163,27 @@ def read_db(start=0, limit=PAGE_SIZE, listing=None, event=None, person=None, lic pass sql = 'SELECT {} FROM `results` {} {} {};'.format(select, where, group, close) - #app.logger.debug(sql) + app.logger.debug(sql) c.execute(sql) countresult = c.fetchone() for x in countresult.keys(): count = countresult[x] - #app.logger.debug(count) + app.logger.debug(count) return { 'count': int(count), 'rows': queryresults } -def now(): - return dt.datetime.now() -@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]) - @app.route('/') -@app.route('/list') -@app.route('/list/') # title = { races, rankings, runners, licence } -@app.route('/list/<title>/<int:year>') +@app.route('/<title>') +@app.route('/<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', ): + title = urllib.parse.unquote_plus(title) + if title not in ( 'race', 'rankings', 'runners', 'licence', ): abort(404) ''' In early January, we'll be left with blank pages in listings, since there won't @@ -172,61 +197,56 @@ def list(title=None, year=None): lastyear = date - dt.timedelta(days=(MIN_MONTHS_FOR_LISTINGS * 31)) year = lastyear.year - start = int(request.args.get('start', '0')) - 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) + 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) + @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) - + 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) @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', PAGE_SIZE)) - results = read_db(start, limit, event=title, year=year) - 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) + 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) @app.route('/person/<title>') @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', PAGE_SIZE)) - results = read_db(start, limit, person=title, year=year) - 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) + 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) -@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', PAGE_SIZE)) - results = read_db(start, limit, licence=title, year=year) - 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) + 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) @app.route('/search') def search(): - start = int(request.args.get('start', '0')) - limit = int(request.args.get('limit', PAGE_SIZE)) - results = read_db(start, limit, search=request.args) - return render_template('search.html', ltype='index', year=None, - results=results, start=start, limit=limit, - request=request, now=now(), PAGE_SIZE=PAGE_SIZE) + 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) if __name__ == '__main__': diff --git a/static/style.css b/static/style.css index 73999ee..9edf7a2 100644 --- a/static/style.css +++ b/static/style.css @@ -5,7 +5,7 @@ body { font-size: 11pt; } a { - color: #154996; + color: rgba(21, 73, 150, 100%); } h1 { text-align: center; @@ -13,27 +13,11 @@ h1 { article { padding: 0 8px; } -table { - border-collapse: collapse; - width: 100%; -} -table th { - text-align: left; - font-size: 10pt; -} -table tr td { - padding-right: 10px; - border-bottom: 1px solid grey; -} -table tr td:last-child { - padding: 0; -} -table tr td.nowrap { - white-space: nowrap; -} + +/* Navigation elements */ nav { display: flex; - background-color: #154996; + background-color: rgba(21, 73, 150, 100%); text-align: center; } nav span { @@ -46,6 +30,20 @@ nav span a { display: block; color: #FFF; } +nav.tabs { +} +nav.tabs span { + border-right: 1px solid #FFF; +} +nav.tabs span:last-child { + border-right: none; +} +nav.tabs span a { + padding-top: 15px; + padding-bottom: 15px; + font-size: 10pt; + font-weight: bold; +} nav.nextprev { margin-top: 10px; padding: 0 15px; @@ -61,19 +59,84 @@ nav.nextprev span a { display: block; padding: 10px 5px; } -nav.tabs { + +/* Results lists */ +table { + border-collapse: collapse; + width: 100%; + overflow-x: auto; } -nav.tabs span { - border-right: 1px solid #FFF; +table thead, +table tbody { } -nav.tabs span:last-child { - border-right: none; -} -nav.tabs span a { - padding: 15px 10px; +table thead th { + text-align: left; font-size: 10pt; - font-weight: bold; } +table tr { +} +table tr td { + padding-right: 10px; + border-bottom: 1px solid grey; +} +table tr td:last-child { + padding-right: 0; +} +table tr td.nowrap { + white-space: nowrap; +} +table tr td span.label { + display: none; +} + +@media +only screen and (max-width: 600px) { + /* Force table to not be like tables anymore */ + table.wide, + table.wide thead, + table.wide tbody, + table.wide thead th, + table.wide tr, + table.wide tr td { + display: block; + + } + table.wide thead tr { + display: none; + } + table.wide tr:nth-of-type(even) { + border: 1px solid rgba(21, 73, 150, 60%); + } + table.wide tr:nth-of-type(odd) { + background-color: rgba(21, 73, 150, 60%); + } + table.wide tr:nth-of-type(odd) a { + color: #FFF; + } + table.wide tr td { + display: flex; + align-items: center; + /* Behave like a "row" */ + border: none; + border-bottom: 1px solid rgba(255, 255, 255, 10%); + } + table.wide tr td span { + flex: 2; + padding-right: 10px; + } + table.wide tr td span.label { + display: initial; + flex: 1; + text-align: right; + } + table.wide tr td, + table.wide tr td:last-child { + padding-right: 10px; + } +} + + +/* Search box */ div.search { margin: 0 10px 15px; display: flex; @@ -81,7 +144,7 @@ div.search { align-items: stretch; } div.search > div { - flex: 1 1 300px; + flex: 1 1 250px; padding: 5px; display: flex; align-items: center; diff --git a/templates/head.html b/templates/head.html index d153e9c..6b77056 100644 --- a/templates/head.html +++ b/templates/head.html @@ -3,13 +3,21 @@ <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width"> - <title>Atlantic Athletic Club + Atlantic Athletic Club Results -{%- set ns.limit = limit -%} -{%- if ns.limit == 0 or ns.limit == PAGE_SIZE -%} - {%- set ns.limit = None -%} +{%- set ns.start = getstart -%} +{%- set ns.show = getshow -%} + +{# Reset arguments, so as not to display standard arguments in the query part of the URL #} +{%- if ns.start == 0 -%} + {%- set ns.start = None -%} {%- endif -%} + +{%- if ns.show == 0 or ns.show == PAGE_SIZE -%} + {%- set ns.show = None -%} +{%- endif -%} + {% include 'tabs.html' with context %} diff --git a/templates/index.html b/templates/index.html index be85535..f3d9dd6 100644 --- a/templates/index.html +++ b/templates/index.html @@ -2,13 +2,17 @@ {% include 'head.html' with context %}
-

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

+

AAC Results{% if title %}: {% if ltype == 'licence' %}Licence {% endif %}{{ title }}{% endif %}{% if year %} ({{ year }}){% endif %}

+ +{% if ltype == 'search' -%} + {% include 'search.html' with context %} +{%- endif -%} {% if results -%} {%- set ns.total = 0 -%} {%- if 'count' in results -%} {%- set ns.total = results['count'] -%} {%- endif -%} - +
@@ -19,7 +23,7 @@ {% if ltype != 'event' %} - + {% endif %} @@ -27,24 +31,22 @@ {%- for row in results['rows'] -%} - {%- set person='{} {}'.format(row.name, row.surname) -%} - {%- if distance %}{# set total_km += row.distance #}{% endif -%} + {%- set person='{} {}'.format(row.name|e, row.surname|e) -%} - - {%- if ltype != 'person' -%} - - - {%- endif -%} - - + + + + + {%- if ltype != 'event' -%} - + {%- endif -%} - - + {%- endfor -%} diff --git a/templates/list-licence.html b/templates/list-licence.html index 351b7af..e801325 100644 --- a/templates/list-licence.html +++ b/templates/list-licence.html @@ -2,25 +2,26 @@ {% include 'head.html' with context %}
-

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

+

AAC Results: Licences{% if year %} for {{ year }}{% endif %}

{% if results -%} {%- set ns.total = 0 -%} {%- if 'count' in results -%} {%- set ns.total = results['count'] -%} {%- endif -%} -
PositionTime Average PaceEventRaceDate Notes
{{ row.position }}{{ person }}{{ row.licence }}{{ row.time }}{% if row.distance is number %}{{ (row.time / row.distance) | pace }} min/KM{% endif %}Position {{ row.position|e }}Name {{ person }}Licence {{ row.licence|e }}Time {{ row.time|e }}Average Pace {% if row.distance is number and row.distance|int != 0 %}{{ (row.time / row.distance) | pace }} min/KM{% endif %}{{ row.event }} ({{ row.distance }} KM)Race {{ row.event|e }} ({{ row.distance|e }} KM){{ row.date | cleandate }} - {%- if row.sex and row.sexposition and row.sexposition | int <= 100 %}{{ row.sexposition | ordinal }} {{ row.sex.lower() }}{% endif -%} + Date {{ row.date|cleandate|e }}Notes + {%- if row.sex and row.sexposition and row.sexposition | int <= 100 %}{{ row.sexposition|ordinal|e }} {{ row.sex|lower|e }}{% endif -%} {%- if row.sexposition and row.sexposition | int <= 100 and row.catposition and row.catposition | int <= 100 %} and {% endif -%} - {%- if row.catposition and row.catposition | int <= 100 %}{{ row.catposition | ordinal }} in category{% endif -%} + {%- if row.catposition and row.catposition | int <= 100 %}{{ row.catposition|ordinal|e }} in category{% endif -%} +
+
+ {%- for row in results['rows'] -%} - - - + + + {%- endfor -%} diff --git a/templates/list-race.html b/templates/list-race.html new file mode 100644 index 0000000..e4e549d --- /dev/null +++ b/templates/list-race.html @@ -0,0 +1,33 @@ +{% set ns = namespace() -%} + +{% include 'head.html' with context %} +
+

AAC Results: Races {% if year %} in {{ year }}{% endif %}

+{% if results -%} + {%- set ns.total = 0 -%} + {%- if 'count' in results -%} + {%- set ns.total = results['count'] -%} + {%- endif -%} +
Licence NameYear
{{ row.licence }}{{ row.person }}{{ row.date | year }}Licence {{ row.licence|e }}Name {{ row.person|e }}Year {{ row.date | year }}
+ + + + + + + + {%- for row in results['rows'] -%} + + + + + {%- endfor -%} + +
RaceDate
Race {{ row.event|e }}Date {{ row.date | cleandate }}
+{%- endif %} +
+ + + diff --git a/templates/list-rankings.html b/templates/list-rankings.html index 951cfeb..32863a2 100644 --- a/templates/list-rankings.html +++ b/templates/list-rankings.html @@ -2,28 +2,28 @@ {% include 'head.html' with context %}
-

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

+

AAC Results: Rankings{% if year %} for {{ year }}{% endif %}

{% if results -%} {%- set ns.total = 0 -%} {%- if 'count' in results -%} {%- set ns.total = results['count'] -%} {%- endif -%} - +
- - - + + + {%- for row in results['rows'] -%} - - - - + + + + {%- endfor -%} diff --git a/templates/list-runners.html b/templates/list-runners.html index 05b6bd9..15f609b 100644 --- a/templates/list-runners.html +++ b/templates/list-runners.html @@ -2,13 +2,13 @@ {% include 'head.html' with context %}
-

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

+

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

{% if results -%} {%- set ns.total = 0 -%} {%- if 'count' in results -%} {%- set ns.total = results['count'] -%} {%- endif -%} -
NameAverage positionSum of race positionsNumber of racesAverage PositionRaces
{{ row.person }}{{ row.score }}{{ row.positions }}{{ row.races }}Name {{ row.person|e }}Average Position {{ row.score|e }}Number of Races {{ row.races|e }}
+
@@ -18,8 +18,8 @@ {%- for row in results['rows'] -%} - - + + {%- endfor -%} diff --git a/templates/prevnext.html b/templates/prevnext.html index ef7be75..96e1b19 100644 --- a/templates/prevnext.html +++ b/templates/prevnext.html @@ -1,39 +1,29 @@ diff --git a/templates/search.html b/templates/search.html index c761246..9127edf 100644 --- a/templates/search.html +++ b/templates/search.html @@ -1,8 +1,3 @@ -{% set ns = namespace() -%} - -{% include 'head.html' with context %} -
-

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

Name
{{ row.person }}{{ row.total }}Name {{ row.person|e }}Distance {{ row.total|e }}
- - - - - - - - - - - - - - {%- for row in results['rows'] -%} - {%- set person='{} {}'.format(row.name, row.surname) -%} - {%- if distance %}{# set total_km += row.distance #}{% endif -%} - - - - - - - - - - - {%- endfor -%} - -
PositionNameLicenceTimeAverage PaceEventDateNotes
{{ row.position }}{{ person }}{{ row.licence }}{{ row.time }}{% if row.distance is number %}{{ (row.time / row.distance) | pace }} min/KM{% endif %}{{ row.event }} ({{ row.distance }} KM){{ row.date | cleandate }} - {%- if row.sex and row.sexposition and row.sexposition | int <= 100 %}{{ row.sexposition | ordinal }} {{ row.sex.lower() }}{% endif -%} - {%- if row.sexposition and row.sexposition | int <= 100 and row.catposition and row.catposition | int <= 100 %} and {% endif -%} - {%- if row.catposition and row.catposition | int <= 100 %}{{ row.catposition | ordinal }} in category{% endif -%} -
-{%- endif %} -
- - - diff --git a/templates/tabs.html b/templates/tabs.html index 0abbc70..62d0ab2 100644 --- a/templates/tabs.html +++ b/templates/tabs.html @@ -1,8 +1,8 @@