diff --git a/aacstats.py b/aacstats.py
index 09d5a25..2d3a7a6 100644
--- a/aacstats.py
+++ b/aacstats.py
@@ -1,17 +1,154 @@
from flask import Flask, abort, request, render_template, url_for
+import datetime as dt
import math
import MySQLdb
import MySQLdb.cursors
-import datetime as dt
+import re
app = Flask(__name__)
PAGE_SIZE=20
-# TODO: Search
+MIN_MONTHS_FOR_LISTINGS=3
+
+
+def read_db(start=0, limit=PAGE_SIZE, 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
+
+ select = '*'
+ where = 'WHERE club LIKE "AAC"'
+ group = ''
+ order = 'date DESC, event, position'
+ close = ''
+ if event:
+ if isinstance(event, str):
+ event = db.escape_string(event).decode()
+ where += ' AND event LIKE "{}"'.format(event.lower())
+ if person:
+ if isinstance(person, str):
+ person = db.escape_string(person).decode()
+ where += ' AND CONCAT_WS(" ", name, surname) LIKE "{}"'.format(person.lower())
+ if licence:
+ if isinstance(licence, str):
+ licence = db.escape_string(licence).decode()
+ where += ' AND licence LIKE "{}"'.format(licence.lower())
+ if year:
+ if not isinstance(year, (int, float)):
+ year = int(db.escape_string(year).decode())
+ 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)
+
+ 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
+
+ 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)
+ 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 }
def now():
- # TODO: If it's January and no races have been run this year, return last year
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 }
@@ -23,6 +160,18 @@ def list(title=None, year=None):
title = 'runners'
if title not in ( 'races', 'rankings', 'runners', 'licence', ):
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
+
start = int(request.args.get('start', '0'))
limit = int(request.args.get('limit', PAGE_SIZE))
results = read_db(start, limit, listing=title, year=year)
@@ -70,99 +219,14 @@ def licence(year=now().year, title=None):
results=results, start=start, limit=limit,
request=request, now=now(), PAGE_SIZE=PAGE_SIZE)
-
-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()
-
- 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:
- 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)
-
- 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)
- 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('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):
- return "%d%s" % (n,"tsnrhtdd"[(math.floor(n/10)%10!=1)*(n%10<4)*n%10::4])
+@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)
if __name__ == '__main__':
diff --git a/load_spreadsheet.py b/load_spreadsheet.py
index d724296..b2cf825 100755
--- a/load_spreadsheet.py
+++ b/load_spreadsheet.py
@@ -154,7 +154,8 @@ def load_into_db(rows):
if rows is None or len(rows) < 1:
log.warning("No data found in spreadsheet")
else:
- db = MySQLdb.connect(user='aac', passwd='saOAcCWHg4LaoSSA', db='AAC', use_unicode=True, charset="utf8")
+ db = MySQLdb.connect(user='aac', passwd='saOAcCWHg4LaoSSA', db='AAC',
+ use_unicode=True, charset="utf8")
c = db.cursor()
''' Check for duplicate values by DATE and POSITION and RACE and EVENT '''
diff --git a/static/style.css b/static/style.css
index d06f674..73999ee 100644
--- a/static/style.css
+++ b/static/style.css
@@ -1,6 +1,5 @@
body {
- margin: 0 auto 15px;
- padding: 0 8px;
+ margin: 0 auto;
max-width: 800px;
font-family: 'Roboto Condensed', sans-serif;
font-size: 11pt;
@@ -11,6 +10,9 @@ a {
h1 {
text-align: center;
}
+article {
+ padding: 0 8px;
+}
table {
border-collapse: collapse;
width: 100%;
@@ -38,6 +40,7 @@ nav span {
flex: 1 1 auto;
}
nav span {
+ white-space: nowrap;
}
nav span a {
display: block;
@@ -68,6 +71,33 @@ nav.tabs span:last-child {
}
nav.tabs span a {
padding: 15px 10px;
- font-size: 11pt;
+ font-size: 10pt;
font-weight: bold;
}
+div.search {
+ margin: 0 10px 15px;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: stretch;
+}
+div.search > div {
+ flex: 1 1 300px;
+ padding: 5px;
+ display: flex;
+ align-items: center;
+}
+div.search label {
+ flex: 1;
+ text-align: right;
+ padding-right: 10px;
+}
+div.search input {
+ flex: 2;
+ font-family: 'Roboto Condensed', sans-serif; /* Override firefox's occassional monospace weirdness */
+ padding-top: 5px; /* Override Firefox weirdness */
+ padding-bottom: 5px; /* Override Firefox weirdness */
+}
+div.search input#searchsubmit {
+ flex: none;
+ margin-left: auto;
+}
diff --git a/templates/index.html b/templates/index.html
index c11cdee..be85535 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -42,9 +42,9 @@
{%- endif -%}
{{ 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 | 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 -%}
{%- endfor -%}
diff --git a/templates/search.html b/templates/search.html
new file mode 100644
index 0000000..c761246
--- /dev/null
+++ b/templates/search.html
@@ -0,0 +1,80 @@
+{% set ns = namespace() -%}
+
+{% include 'head.html' with context %}
+
+
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 -%}
+
+
+
+
Position
+
Name
+
Licence
+
Time
+
Average Pace
+
Event
+
Date
+
Notes
+
+
+
+ {%- for row in results['rows'] -%}
+ {%- set person='{} {}'.format(row.name, row.surname) -%}
+ {%- if distance %}{# set total_km += row.distance #}{% endif -%}
+
+ {%- 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 -%}
+