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//')
+@app.route('/')
+@app.route('//')
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/')
@app.route('/all//') # 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//')
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/')
@app.route('/person//')
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/')
@app.route('/licence//')
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 @@
- 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 -%}
-
+
Position
@@ -19,7 +23,7 @@
Time
Average Pace
{% if ltype != 'event' %}
- Event
+ Race
{% endif %}
Date
Notes
@@ -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) -%}
- {{ row.position }}
- {%- if ltype != 'person' -%}
- {{ person }}
- {{ row.licence }}
- {%- endif -%}
- {{ 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 %}
{%- if ltype != 'event' -%}
- {{ row.event }} ({{ row.distance }} KM)
+ Race {{ row.event|e }} ({{ row.distance|e }} KM)
{%- endif -%}
- {{ 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 -%}
+
{%- 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 -%}
-
+
Licence
Name
+ Year
{%- for row in results['rows'] -%}
- {{ row.licence }}
- {{ row.person }}
- {{ row.date | year }}
+ Licence {{ row.licence|e }}
+ Name {{ row.person|e }}
+ Year {{ row.date | year }}
{%- 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 -%}
+
+
+
+ Race
+ Date
+
+
+
+ {%- for row in results['rows'] -%}
+
+ Race {{ row.event|e }}
+ Date {{ row.date | cleandate }}
+
+ {%- endfor -%}
+
+
+{%- endif %}
+
+
+{% include 'prevnext.html' with context %}
+
+
+