Private
Public Access
1
0

Merge branch 'develop' into feature/ranking

This commit is contained in:
Sander Roosendaal
2018-01-09 09:00:03 +01:00
16 changed files with 4531 additions and 4070 deletions

View File

@@ -56,7 +56,7 @@ import sys
import utils import utils
import datautils import datautils
from utils import lbstoN,myqueue from utils import lbstoN,myqueue,is_ranking_piece
from timezonefinder import TimezoneFinder from timezonefinder import TimezoneFinder
@@ -295,6 +295,12 @@ def clean_df_stats(datadf, workstrokesonly=True, ignorehr=True,
except KeyError: except KeyError:
pass pass
try:
mask = datadf['efficiency'] > 200.
datadf.loc[mask, 'efficiency'] = np.nan
except KeyError:
pass
try: try:
mask = datadf['spm'] < 10 mask = datadf['spm'] < 10
datadf.loc[mask, 'spm'] = np.nan datadf.loc[mask, 'spm'] = np.nan
@@ -921,6 +927,9 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower',
w.startdatetime = timezone.now() w.startdatetime = timezone.now()
w.save() w.save()
if is_ranking_piece(w):
w.rankingpiece = True
w.save()
if privacy == 'visible': if privacy == 'visible':
ts = Team.objects.filter(rower=r) ts = Team.objects.filter(rower=r)
@@ -1557,7 +1566,11 @@ def getsmallrowdata_db(columns, ids=[], doclean=True, workstrokesonly=True):
if extracols and len(ids)==1: if extracols and len(ids)==1:
w = Workout.objects.get(id=ids[0]) w = Workout.objects.get(id=ids[0])
row = rdata(w.csvfilename) row = rdata(w.csvfilename)
f = row.df['TimeStamp (sec)'].diff().mean() try:
f = row.df['TimeStamp (sec)'].diff().mean()
except AttributeError:
f = 0
if f != 0 and not np.isnan(f): if f != 0 and not np.isnan(f):
windowsize = 2 * (int(10. / (f))) + 1 windowsize = 2 * (int(10. / (f))) + 1
else: else:

View File

@@ -582,7 +582,53 @@ def deletecpdata_sql(rower_id,table='cpdata',debug=False):
conn.close() conn.close()
engine.dispose() engine.dispose()
def delete_agegroup_db(age,sex,weightcategory,debug=False):
if debug:
engine = create_engine(database_url_debug, echo=False)
else:
engine = create_engine(database_url, echo=False)
query = sa.text('DELETE from {table} WHERE age={age} and weightcategory = {weightcategory} and sex={sex};'.format(
sex=sex,
age=age,
weightcategory=weightcategory,
table='calcagegrouprecords'
))
with engine.connect() as conn, conn.begin():
try:
result = conn.execute(query)
except:
print "Database locked"
conn.close()
engine.dispose()
def update_agegroup_db(age,sex,weightcategory,wcdurations,wcpower,
debug=False):
delete_agegroup_db(age,sex,weightcategory,debug=debug)
df = pd.DataFrame(
{
'duration':wcdurations,
'power':wcpower,
}
)
df['sex'] = sex
df['age'] = age
df['weightcategory'] = weightcategory
if debug:
engine = create_engine(database_url_debug, echo=False)
else:
engine = create_engine(database_url, echo=False)
table = 'calcagegrouprecords'
with engine.connect() as conn, conn.begin():
df.to_sql(table, engine, if_exists='append', index=False)
conn.close()
engine.dispose()
def updatecpdata_sql(rower_id,delta,cp,table='cpdata',distance=pd.Series([]),debug=False): def updatecpdata_sql(rower_id,delta,cp,table='cpdata',distance=pd.Series([]),debug=False):
deletecpdata_sql(rower_id,table=table,debug=debug) deletecpdata_sql(rower_id,table=table,debug=debug)

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@ import numpy as np
from models import C2WorldClassAgePerformance from models import C2WorldClassAgePerformance
import pandas as pd import pandas as pd
from scipy import optimize from scipy import optimize
from django.utils import timezone
rowingmetrics = ( rowingmetrics = (
('time',{ ('time',{
@@ -64,7 +65,7 @@ rowingmetrics = (
'null':True, 'null':True,
'verbose_name': 'Average Drive Force (N)', 'verbose_name': 'Average Drive Force (N)',
'ax_min': 0, 'ax_min': 0,
'ax_max': 900, 'ax_max': 1200,
'mode':'both', 'mode':'both',
'type': 'pro'}), 'type': 'pro'}),
@@ -73,7 +74,7 @@ rowingmetrics = (
'null':True, 'null':True,
'verbose_name': 'Peak Drive Force (N)', 'verbose_name': 'Peak Drive Force (N)',
'ax_min': 0, 'ax_min': 0,
'ax_max': 900, 'ax_max': 1500,
'mode':'both', 'mode':'both',
'type': 'pro'}), 'type': 'pro'}),
@@ -321,28 +322,36 @@ def calc_trimp(df,sex,hrmax,hrmin):
return trimp return trimp
def getagegrouprecord(age,sex='male',weightcategory='hwt', def getagegrouprecord(age,sex='male',weightcategory='hwt',
distance=2000,duration=None): distance=2000,duration=None,indf=pd.DataFrame()):
if not duration:
df = pd.DataFrame( if not indf.empty:
list( if not duration:
C2WorldClassAgePerformance.objects.filter( df = indf[indf['distance'] == distance]
distance=distance, else:
sex=sex, duration = 60*int(duration)
weightcategory=weightcategory df = indf[indf['duration'] == duration]
).values()
)
)
else: else:
duration=60*int(duration) if not duration:
df = pd.DataFrame( df = pd.DataFrame(
list( list(
C2WorldClassAgePerformance.objects.filter( C2WorldClassAgePerformance.objects.filter(
duration=duration, distance=distance,
sex=sex, sex=sex,
weightcategory=weightcategory weightcategory=weightcategory
).values() ).values()
)
)
else:
duration=60*int(duration)
df = pd.DataFrame(
list(
C2WorldClassAgePerformance.objects.filter(
duration=duration,
sex=sex,
weightcategory=weightcategory
).values()
)
) )
)
if not df.empty: if not df.empty:
ages = df['age'] ages = df['age']
@@ -354,14 +363,21 @@ def getagegrouprecord(age,sex='male',weightcategory='hwt',
p0 = [700,120,700,10,100,100] p0 = [700,120,700,10,100,100]
p1, success = optimize.leastsq(errfunc,p0[:], try:
p1, success = optimize.leastsq(errfunc,p0[:],
args = (ages,powers)) args = (ages,powers))
except:
p1 = p0
success = 0
power = fitfunc(p1, float(age)) if success:
power = fitfunc(p1, float(age))
#power = np.polyval(poly_coefficients,age) #power = np.polyval(poly_coefficients,age)
power = 0.5*(np.abs(power)+power) power = 0.5*(np.abs(power)+power)
else:
power = 0
else: else:
power = 0 power = 0

View File

@@ -214,7 +214,34 @@ def update_records(url=c2url):
except: except:
print record print record
class CalcAgePerformance(models.Model):
weightcategories = (
('hwt','heavy-weight'),
('lwt','light-weight'),
)
sexcategories = (
('male','male'),
('female','female'),
)
weightcategory = models.CharField(default="hwt",
max_length=30,
choices=weightcategories)
sex = models.CharField(default="female",
max_length=30,
choices=sexcategories)
age = models.IntegerField(default=19,verbose_name="Age")
duration = models.FloatField(default=1,blank=True)
power = models.IntegerField(default=200)
class Meta:
db_table = 'calcagegrouprecords'
class C2WorldClassAgePerformance(models.Model): class C2WorldClassAgePerformance(models.Model):
weightcategories = ( weightcategories = (
('hwt','heavy-weight'), ('hwt','heavy-weight'),
@@ -875,9 +902,11 @@ class WorkoutForm(ModelForm):
# Used for the rowing physics calculations # Used for the rowing physics calculations
class AdvancedWorkoutForm(ModelForm): class AdvancedWorkoutForm(ModelForm):
quick_calc = forms.BooleanField(initial=True,required=False)
class Meta: class Meta:
model = Workout model = Workout
fields = ['boattype','weightvalue'] fields = ['boattype','weightvalue','quick_calc']
class RowerExportForm(ModelForm): class RowerExportForm(ModelForm):
class Meta: class Meta:

View File

@@ -6,6 +6,8 @@ import gzip
import shutil import shutil
import numpy as np import numpy as np
from scipy import optimize
import rowingdata import rowingdata
from rowingdata import rowingdata as rdata from rowingdata import rowingdata as rdata
@@ -28,7 +30,8 @@ from utils import deserialize_list
from rowers.dataprepnodjango import ( from rowers.dataprepnodjango import (
update_strokedata, new_workout_from_file, update_strokedata, new_workout_from_file,
getsmallrowdata_db, updatecpdata_sql getsmallrowdata_db, updatecpdata_sql,
update_agegroup_db,
) )
from django.core.mail import send_mail, EmailMessage from django.core.mail import send_mail, EmailMessage
@@ -46,6 +49,96 @@ import longtask
def add(x, y): def add(x, y):
return x + y return x + y
def getagegrouprecord(age,sex='male',weightcategory='hwt',
distance=2000,duration=None,indf=pd.DataFrame()):
if not duration:
df = indf[indf['distance'] == distance]
else:
duration = 60*int(duration)
df = indf[indf['duration'] == duration]
if not df.empty:
ages = df['age']
powers = df['power']
#poly_coefficients = np.polyfit(ages,powers,6)
fitfunc = lambda pars, x: np.abs(pars[0])*(1-x/max(120,pars[1]))-np.abs(pars[2])*np.exp(-x/np.abs(pars[3]))+np.abs(pars[4])*(np.sin(np.pi*x/max(50,pars[5])))
errfunc = lambda pars, x,y: fitfunc(pars,x)-y
p0 = [700,120,700,10,100,100]
p1, success = optimize.leastsq(errfunc,p0[:],
args = (ages,powers))
if success:
power = fitfunc(p1, float(age))
#power = np.polyval(poly_coefficients,age)
power = 0.5*(np.abs(power)+power)
else:
power = 0
else:
power = 0
return power
@app.task(bind=True)
def handle_getagegrouprecords(self,
df,
distances,durations,
age,sex,weightcategory,
**kwargs):
wcdurations = []
wcpower = []
if 'debug' in kwargs:
debug = kwargs['debug']
else:
debug = False
df = pd.read_json(df)
for distance in distances:
worldclasspower = getagegrouprecord(
age,
sex=sex,
distance=distance,
weightcategory=weightcategory,indf=df,
)
velo = (worldclasspower/2.8)**(1./3.)
try:
duration = distance/velo
wcdurations.append(duration)
wcpower.append(worldclasspower)
except ZeroDivisionError:
pass
for duration in durations:
worldclasspower = getagegrouprecord(
age,
sex=sex,
duration=duration,
weightcategory=weightcategory,indf=df
)
try:
velo = (worldclasspower/2.8)**(1./3.)
distance = int(60*duration*velo)
wcdurations.append(60.*duration)
wcpower.append(worldclasspower)
except ValueError:
pass
update_agegroup_db(age,sex,weightcategory,wcdurations,wcpower,
debug=debug)
return 1
@app.task(bind=True) @app.task(bind=True)
def long_test_task(self,aantal,debug=False,job=None,session_key=None): def long_test_task(self,aantal,debug=False,job=None,session_key=None):
@@ -66,7 +159,10 @@ def long_test_task2(self,aantal,**kwargs):
kwargs['jobid'] = job_id kwargs['jobid'] = job_id
return longtask.longtask2(aantal,**kwargs) return longtask.longtask2(aantal,**kwargs)
# create workout # create workout
@app.task @app.task
def handle_new_workout_from_file(r, f2, def handle_new_workout_from_file(r, f2,
@@ -356,10 +452,7 @@ def handle_sendemailcsv(first_name, last_name, email, csvfile,**kwargs):
def handle_otwsetpower(self,f1, boattype, weightvalue, def handle_otwsetpower(self,f1, boattype, weightvalue,
first_name, last_name, email, workoutid, first_name, last_name, email, workoutid,
**kwargs): **kwargs):
# ps=[
# 1, 1, 1, 1],
# ratio=1.0,
# debug=False):
job = self.request job = self.request
job_id = job.id job_id = job.id
@@ -378,7 +471,12 @@ def handle_otwsetpower(self,f1, boattype, weightvalue,
debug = kwargs['debug'] debug = kwargs['debug']
else: else:
debug = False debug = False
if 'quick_calc' in kwargs:
usetable = kwargs['quick_calc']
else:
usetable = False
kwargs['jobid'] = job_id kwargs['jobid'] = job_id
@@ -416,17 +514,25 @@ def handle_otwsetpower(self,f1, boattype, weightvalue,
pass pass
progressurl = SITE_URL progressurl = SITE_URL
siteurl = SITE_URL
if debug: if debug:
progressurl = SITE_URL_DEV progressurl = SITE_URL_DEV
siteurl = SITE_URL_DEV
secret = PROGRESS_CACHE_SECRET secret = PROGRESS_CACHE_SECRET
progressurl += "/rowers/record-progress/" progressurl += "/rowers/record-progress/"
progressurl += job_id progressurl += job_id
# determine cache file name
physics_cache = 'media/'+str(boattype)+'_'+str(int(weightvalue))
rowdata.otw_setpower_silent(skiprows=5, mc=weightvalue, rg=rg, rowdata.otw_setpower(skiprows=5, mc=weightvalue, rg=rg,
powermeasured=powermeasured, powermeasured=powermeasured,
progressurl=progressurl, progressurl=progressurl,
secret=secret secret=secret,
silent=True,
usetable=usetable,storetable=physics_cache,
) )
# save data # save data
@@ -465,7 +571,8 @@ def handle_otwsetpower(self,f1, boattype, weightvalue,
message += "Thank you for using rowsandall.com.\n\n" message += "Thank you for using rowsandall.com.\n\n"
message += "Rowsandall OTW calculations have not been fully implemented yet.\n" message += "Rowsandall OTW calculations have not been fully implemented yet.\n"
message += "We are now running an experimental version for debugging purposes. \n" message += "We are now running an experimental version for debugging purposes. \n"
message += "Your wind/stream corrected plot is available here: http://rowsandall.com/rowers/workout/" message += "Your wind/stream corrected plot is available here: "
message += siteurl+"/rowers/workout/"
message += str(workoutid) message += str(workoutid)
message += "/interactiveotwplot\n\n" message += "/interactiveotwplot\n\n"
message += "Please report any bugs/inconsistencies/unexpected results at rowsandall.slack.com or by reply to this email.\n\n" message += "Please report any bugs/inconsistencies/unexpected results at rowsandall.slack.com or by reply to this email.\n\n"
@@ -482,7 +589,17 @@ def handle_otwsetpower(self,f1, boattype, weightvalue,
def handle_updateergcp(rower_id,workoutfilenames,debug=False,**kwargs): def handle_updateergcp(rower_id,workoutfilenames,debug=False,**kwargs):
therows = [] therows = []
for f1 in workoutfilenames: for f1 in workoutfilenames:
rowdata = rdata(f1) try:
rowdata = rdata(f1)
except IOError:
try:
rowdata = rdata(f1 + '.csv')
except IOError:
try:
rowdata = rdata(f1 + '.gz')
except IOError:
rowdata = 0
if rowdata != 0: if rowdata != 0:
therows.append(rowdata) therows.append(rowdata)

View File

@@ -39,6 +39,11 @@
<h1>Interactive Plot</h1> <h1>Interactive Plot</h1>
<p>This chart shows the <a href="http://www.concept2.com/indoor-rowers/racing/records/world">Indoor Rower World Records</a> for your gender and
weight class. The red dots are the official records, and hovering
over them with your mouse shows you the name of the record holder.
The blue line is a fit to the data, which is used by rowsandall.com
to calculate your performance assessment.</a>
{{ the_div|safe }} {{ the_div|safe }}

View File

@@ -91,9 +91,9 @@
<div class="grid_2 alpha"> <div class="grid_2 alpha">
<p> <p>
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %} {% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<a class="button blue small" href="/rowers/otw-bests">OTW Ranking Pieces</a> <a class="button blue small" href="/rowers/otw-bests">OTW Critical Power</a>
{% else %} {% else %}
<a class="button blue small" href="/rowers/promembership">OTW Ranking Pieces</a> <a class="button blue small" href="/rowers/promembership">OTW Critical Power</a>
{% endif %} {% endif %}
</p> </p>
<p> <p>
@@ -129,9 +129,9 @@
<div class="grid_2 suffix_4 alpha"> <div class="grid_2 suffix_4 alpha">
<p> <p>
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %} {% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<a class="button blue small" href="/rowers/ote-ranking">OTE Ranking Pieces</a> <a class="button blue small" href="/rowers/ote-ranking">OTE Critical Power</a>
{% else %} {% else %}
<a class="button blue small" href="/rowers/promembership">OTE Ranking Pieces</a> <a class="button blue small" href="/rowers/promembership">OTE Critical Power</a>
{% endif %} {% endif %}
</p> </p>
<p> <p>

View File

@@ -4,12 +4,49 @@
{% block title %}Rowsandall Workouts List{% endblock %} {% block title %}Rowsandall Workouts List{% endblock %}
{% block content %} {% block scripts %}
<script> <script>
setTimeout("location.reload(true);",60000); setTimeout("location.reload(true);",60000);
</script> </script>
<script
type='text/javascript'
src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'>
</script>
<script>
$(function() {
$("td.rankingtoggle").click( function() {
var workout_id = $(this).attr('workoutid');
console.log(workout_id);
$.getJSON(window.location.protocol + '//'+window.location.host + '/rowers/workout/'+workout_id+'/toggle-ranking', function(json) {
console.log(JSON.stringify(json));
rankingpiece = json.result;
tdid = "#star"+workout_id;
console.log(rankingpiece);
console.log($(tdid).length);
if (rankingpiece) {
$(tdid).addClass('yellow');
$(tdid).removeClass('notyellow');
$(tdid).html('&starf;');
console.log('adding yellow '+tdid);
} else {
$(tdid).removeClass('yellow');
$(tdid).addClass('notyellow');
$(tdid).html('&star;');
console.log('remove yellow '+tdid);
};
});
});
});
</script>
{% endblock %}
{% block content %}
<style>
#mypointer {
cursor: pointer;
}
</style>
<div class="grid_12"> <div class="grid_12">
@@ -51,6 +88,7 @@
<table width="100%" class="listtable shortpadded"> <table width="100%" class="listtable shortpadded">
<thead> <thead>
<tr> <tr>
<th> R</th>
<th style="width:80"> Date</th> <th style="width:80"> Date</th>
<th> Time</th> <th> Time</th>
<th> Name</th> <th> Name</th>
@@ -76,155 +114,166 @@
{% else %} {% else %}
<tr> <tr>
{% endif %} {% endif %}
<td id="mypointer"
class="rankingtoggle" workoutid="{{ workout.id }}">
{% if workout.rankingpiece %}
<span id="star{{ workout.id }}" class="yellow">&starf;</span>
{% else %}
<span id="star{{ workout.id }}" class="notyellow">&star;</span>
{% endif %}
</td>
<td> {{ workout.date|date:"Y-m-d" }} </td> <td> {{ workout.date|date:"Y-m-d" }} </td>
<td> {{ workout.starttime|date:"H:i" }} </td> <td> {{ workout.starttime|date:"H:i" }} </td>
<td>
{% if workout.user.user == user or user == team.manager %} {% if workout.user.user == user or user == team.manager %}
{% if workout.rankingpiece %}
[RANKING PIECE]
{% endif %}
{% if workout.name != '' %} {% if workout.name != '' %}
<a href={% url rower.defaultlandingpage id=workout.id %}>{{ workout.name }}</a> </td> <td>
{% else %} <a href={% url rower.defaultlandingpage id=workout.id %}>
<a href={% url rower.defaultlandingpage id=workout.id %}>No Name</a> </td> {{ workout.name }}
{% endif %} </a>
{% else %} </td>
{% if workout.name != '' %} {% else %}
<a href="/rowers/workout/{{ workout.id }}/">{{ workout.name }}</a> </td> <td>
{% else %} <a href={% url rower.defaultlandingpage
<a href="/rowers/workout/{{ workout.id }}/">No Name</a> </td> id=workout.id %}>No Name
{% endif %} </a></td>
{% endif %} {% endif %}
<td> {{ workout.workouttype }} </td> {% else %}
<td> {{ workout.distance }}m</td> {% if workout.name != '' %}
<td> {{ workout.duration |durationprint:"%H:%M:%S.%f" }} </td> <td><a href="/rowers/workout/{{ workout.id }}/">{{ workout.name }}</a></td>
<td> {{ workout.averagehr }} </td> {% else %}
<td> {{ workout.maxhr }} </td> <td><a href="/rowers/workout/{{ workout.id }}/">No Name</a> </td>
{% if not team %} {% endif %}
<td> {% endif %}
<a class="small" href="/rowers/workout/{{ workout.id }}/export">Export</a> <td> {{ workout.workouttype }} </td>
</td> <td> {{ workout.distance }}m</td>
<td> {{ workout.duration |durationprint:"%H:%M:%S.%f" }} </td>
<td> {{ workout.averagehr }} </td>
<td> {{ workout.maxhr }} </td>
{% if not team %}
<td>
<a class="small" href="/rowers/workout/{{ workout.id }}/export">Export</a>
</td>
{% else %}
<td colspan="2">
{{ workout.user.user.first_name }} {{ workout.user.user.last_name }}
</td>
{% endif %}
<td> <a class="small" href="/rowers/workout/{{ workout.id }}/flexchart">Flex</a> </td>
<td>
<a class="small" href="/rowers/workout/{{ workout.id }}/deleteconfirm">Delete
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %} {% else %}
<td colspan="2"> <p> No workouts found </p>
{{ workout.user.user.first_name }} {{ workout.user.user.last_name }}
</td>
{% endif %} {% endif %}
<td> <a class="small" href="/rowers/workout/{{ workout.id }}/flexchart">Flex</a> </td>
<td>
<a class="small" href="/rowers/workout/{{ workout.id }}/deleteconfirm">Delete
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p> No workouts found </p>
{% endif %}
</div>
<div class="grid_4 omega">
{% if team %}
<div class="grid_4" id="teambuttons">
<div class="grid_3 alpha">
<p>
&nbsp;
</p>
</div>
</div> </div>
{% endif %}
<div class="grid_4" id="interactiveplot">
<script type="text/javascript" src="/static/js/bokeh-0.12.3.min.js"></script> <div class="grid_4 omega">
<script async="true" type="text/javascript"> {% if team %}
Bokeh.set_log_level("info"); <div class="grid_4" id="teambuttons">
</script> <div class="grid_3 alpha">
<p>
{{ interactiveplot |safe }} &nbsp;
</p>
<script>
// Set things up to resize the plot on a window resize. You can play with
// the arguments of resize_width_height() to change the plot's behavior.
var plot_resize_setup = function () {
var plotid = Object.keys(Bokeh.index)[0]; // assume we have just one plot
var plot = Bokeh.index[plotid];
var plotresizer = function() {
// arguments: use width, use height, maintain aspect ratio
plot.resize_width_height(true, true, true);
};
window.addEventListener('resize', plotresizer);
plotresizer();
};
window.addEventListener('load', plot_resize_setup);
</script>
<style>
/* Need this to get the page in "desktop mode"; not having an infinite height.*/
html, body {height: 100%; margin:5px;}
</style>
{{ the_div |safe }}
</div>
<div class="grid_4" id="announcements">
{% if announcements %}
<h3>What's New?</h3>
{% for a in announcements %}
<div class="site-announcement-box">
<div class="site-announcement">
<i>{{ a.created }}:</i>
{{ a.announcement|urlize }}
</div> </div>
</div> </div>
{% endfor %}
<p>&nbsp;</p>
{% endif %} {% endif %}
</div> <div class="grid_4" id="interactiveplot">
<div class="grid_4" id="about"> <script type="text/javascript" src="/static/js/bokeh-0.12.3.min.js"></script>
<h3>About</h3> <script async="true" type="text/javascript">
<p>This site is a beta site, pioneering rowing data visualization and analysis. No warranties. The site's author is Bokeh.set_log_level("info");
Sander Roosendaal. A Masters rower. </script>
Read his <a href="http://blog.rowsandall.com/">blog</a> {{ interactiveplot |safe }}
</p>
<div style="text-align: right; padding: 2em"> <script>
<a href="http://blog.rowsandall.com/"> // Set things up to resize the plot on a window resize. You can play with
<img src="/static/img/sander.jpg" width="80"></a> // the arguments of resize_width_height() to change the plot's behavior.
var plot_resize_setup = function () {
var plotid = Object.keys(Bokeh.index)[0]; // assume we have just one plot
var plot = Bokeh.index[plotid];
var plotresizer = function() {
// arguments: use width, use height, maintain aspect ratio
plot.resize_width_height(true, true, true);
};
window.addEventListener('resize', plotresizer);
plotresizer();
};
window.addEventListener('load', plot_resize_setup);
</script>
<style>
/* Need this to get the page in "desktop mode"; not having an infinite height.*/
html, body {height: 100%; margin:5px;}
</style>
{{ the_div |safe }}
</div>
<div class="grid_4" id="announcements">
{% if announcements %}
<h3>What's New?</h3>
{% for a in announcements %}
<div class="site-announcement-box">
<div class="site-announcement">
<i>{{ a.created }}:</i>
{{ a.announcement|urlize }}
</div>
</div>
{% endfor %}
<p>&nbsp;</p>
{% endif %}
</div>
<div class="grid_4" id="about">
<h3>About</h3>
<p>This site is a beta site, pioneering rowing data visualization and analysis. No warranties. The site's author is
Sander Roosendaal. A Masters rower.
Read his <a href="http://blog.rowsandall.com/">blog</a>
</p>
<div style="text-align: right; padding: 2em">
<a href="http://blog.rowsandall.com/">
<img src="/static/img/sander.jpg" width="80"></a>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="grid_6 alpha"> <div class="grid_6 alpha">
{% if rankingonly and not team %} {% if rankingonly and not team %}
<div class="grid_2 prefix_1 alpha"> <div class="grid_2 prefix_1 alpha">
<a class="button small green" href="/rowers/list-workouts">All Workouts</a> <a class="button small green" href="/rowers/list-workouts">All Workouts</a>
</div> </div>
{% elif not team %} {% elif not team %}
<div class="grid_2 prefix_1 alpha"> <div class="grid_2 prefix_1 alpha">
<a class="button small green" href="/rowers/list-workouts/ranking">Ranking Pieces Only</a> <a class="button small green" href="/rowers/list-workouts/ranking">Ranking Pieces Only</a>
</div> </div>
{% endif %} {% endif %}
<div class="grid_2 suffix_1 omega"> <div class="grid_2 suffix_1 omega">
<a class="button small gray" href="/rowers/workouts-join-select">Glue Workouts</a> <a class="button small gray" href="/rowers/workouts-join-select">Glue Workouts</a>
</div> </div>
<p>&nbsp;</p> <p>&nbsp;</p>
{% if team %} {% if team %}
<form id="searchform" action="/rowers/list-workouts/team/{{ team.id }}/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}" <form id="searchform" action="/rowers/list-workouts/team/{{ team.id }}/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}"
method="get" accept-charset="utf-8"> method="get" accept-charset="utf-8">
{% else %} {% else %}
<form id="searchform" action="/rowers/list-workouts/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}" <form id="searchform" action="/rowers/list-workouts/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}"
method="get" accept-charset="utf-8"> method="get" accept-charset="utf-8">
{% endif %} {% endif %}
<div class="grid_3 prefix_1 alpha"> <div class="grid_3 prefix_1 alpha">
<input class="searchfield" id="searchbox" name="q" type="text" placeholder="Search"> <input class="searchfield" id="searchbox" name="q" type="text" placeholder="Search">
</div> </div>
<div class="grid_1 omega"> <div class="grid_1 omega">
<button class="button blue small" type="submit"> <button class="button blue small" type="submit">
Search Search
</button> </button>
</div> </div>
</form> </form>
</div> </div>
<div class="grid_2 omega"> <div class="grid_2 omega">
<span class="button gray small"> <span class="button gray small">
@@ -248,5 +297,5 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
</span> </span>
{% endblock %} {% endblock %}

View File

@@ -85,9 +85,9 @@
In other words: How long you can hold that power. In other words: How long you can hold that power.
</p> </p>
<p>When you change the date range, the algorithm calculates new <p>Whenever you load or reload the page, a new calculation is started
parameters in a background process. You may have to reload the as a background process. The page will reload automatically when
page to get an updated prediction.</p> calculation is ready.</p>
<p>At the bottom of the page, you will find predictions derived from the model.</p> <p>At the bottom of the page, you will find predictions derived from the model.</p>
</div> </div>
<div id="form" class="grid_6 omega"> <div id="form" class="grid_6 omega">

View File

@@ -35,6 +35,10 @@
We use FISA minimum boat weight and standard rigging for our calculations. We use FISA minimum boat weight and standard rigging for our calculations.
</p> </p>
<p>The Quick calculation option potentially speeds up the calculation,
at the cost of a slight reduction in accuracy. It is recommended
to keep this option selected.</p>
<form enctype="multipart/form-data" action="{{ formloc }}" method="post"> <form enctype="multipart/form-data" action="{{ formloc }}" method="post">
{% if form.errors %} {% if form.errors %}
<p style="color: red;"> <p style="color: red;">

View File

@@ -5,6 +5,10 @@
{% block title %}Workouts{% endblock %} {% block title %}Workouts{% endblock %}
{% block scripts %} {% block scripts %}
{% include "monitorjobs.html" %} {% include "monitorjobs.html" %}
<script type='text/javascript'
src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'>
</script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@@ -70,7 +74,9 @@
<a href="/rowers/{{ id }}/ote-bests/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}">https://rowsandall.com/rowers/{{ id }}/ote-bests/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}</a> <a href="/rowers/{{ id }}/ote-bests/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}">https://rowsandall.com/rowers/{{ id }}/ote-bests/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}</a>
</p> </p>
<p>The table gives the best efforts achieved on the official Concept2 ranking pieces in the selected date range.</p> <p>The table gives the best efforts achieved on the
<a href="https://log.concept2.com/rankings">official Concept2 ranking pieces</a> in the selected date range. Also the percentile scores on the
chart are based on the Concept2 rankings.</p>
</div> </div>

View File

@@ -123,6 +123,8 @@ urlpatterns = [
url(r'^imports/$', TemplateView.as_view(template_name='imports.html'), name='imports'), url(r'^imports/$', TemplateView.as_view(template_name='imports.html'), name='imports'),
url(r'^agegroupcp/(?P<age>\d+)$',views.agegroupcpview), url(r'^agegroupcp/(?P<age>\d+)$',views.agegroupcpview),
url(r'^agegroupcp/(?P<age>\d+)/(?P<normalize>\d+)$',views.agegroupcpview), url(r'^agegroupcp/(?P<age>\d+)/(?P<normalize>\d+)$',views.agegroupcpview),
url(r'^ajax_agegroup/(?P<age>\d+)/(?P<weightcategory>\w+.*)/(?P<sex>\w+.*)/(?P<userid>\d+)$',
views.ajax_agegrouprecords),
url(r'^agegrouprecords/(?P<sex>\w+.*)/(?P<weightcategory>\w+.*)/(?P<distance>\d+)m$', url(r'^agegrouprecords/(?P<sex>\w+.*)/(?P<weightcategory>\w+.*)/(?P<distance>\d+)m$',
views.agegrouprecordview), views.agegrouprecordview),
url(r'^agegrouprecords/(?P<sex>\w+.*)/(?P<weightcategory>\w+.*)/(?P<duration>\d+)min$', url(r'^agegrouprecords/(?P<sex>\w+.*)/(?P<weightcategory>\w+.*)/(?P<duration>\d+)min$',
@@ -212,6 +214,7 @@ urlpatterns = [
url(r'^graph/(?P<id>\d+)/deleteconfirm$',views.graph_delete_confirm_view), url(r'^graph/(?P<id>\d+)/deleteconfirm$',views.graph_delete_confirm_view),
url(r'^graph/(?P<id>\d+)/delete$',views.graph_delete_view), url(r'^graph/(?P<id>\d+)/delete$',views.graph_delete_view),
url(r'^workout/(?P<id>\d+)/get-thumbnails$',views.get_thumbnails), url(r'^workout/(?P<id>\d+)/get-thumbnails$',views.get_thumbnails),
url(r'^workout/(?P<id>\d+)/toggle-ranking$',views.workout_toggle_ranking),
url(r'^workout/(?P<id>\d+)/get-testscript$',views.get_testscript), url(r'^workout/(?P<id>\d+)/get-testscript$',views.get_testscript),
url(r'^workout/upload/team/$',views.team_workout_upload_view), url(r'^workout/upload/team/$',views.team_workout_upload_view),
url(r'^workout/upload/$',views.workout_upload_view,name='workout_upload_view'), url(r'^workout/upload/$',views.workout_upload_view,name='workout_upload_view'),

View File

@@ -5,6 +5,7 @@ import colorsys
from django.conf import settings from django.conf import settings
import uuid import uuid
import datetime
lbstoN = 4.44822 lbstoN = 4.44822
@@ -127,6 +128,22 @@ palettes = {
'yellow_red':trcolors(255,255,178,189,0,39) 'yellow_red':trcolors(255,255,178,189,0,39)
} }
rankingdistances = [100,500,1000,2000,5000,6000,10000,21097,42195,100000]
rankingdurations = []
rankingdurations.append(datetime.time(minute=1))
rankingdurations.append(datetime.time(minute=4))
rankingdurations.append(datetime.time(minute=30))
rankingdurations.append(datetime.time(hour=1,minute=15))
rankingdurations.append(datetime.time(hour=1))
def is_ranking_piece(workout):
if workout.distance in rankingdistances:
return True
elif workout.duration in rankingdurations:
return True
return False
def range_to_color_hex(groupcols,palette='monochrome_blue'): def range_to_color_hex(groupcols,palette='monochrome_blue'):

View File

@@ -56,6 +56,7 @@ from rowers.models import (
RowerPowerZonesForm,AccountRowerForm,UserForm,StrokeData, RowerPowerZonesForm,AccountRowerForm,UserForm,StrokeData,
Team,TeamForm,TeamInviteForm,TeamInvite,TeamRequest, Team,TeamForm,TeamInviteForm,TeamInvite,TeamRequest,
WorkoutComment,WorkoutCommentForm,RowerExportForm, WorkoutComment,WorkoutCommentForm,RowerExportForm,
CalcAgePerformance
) )
from rowers.models import FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement from rowers.models import FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement
from rowers.metrics import rowingmetrics,defaultfavoritecharts from rowers.metrics import rowingmetrics,defaultfavoritecharts
@@ -108,7 +109,7 @@ from rowers.tasks import (
handle_sendemail_unrecognized,handle_sendemailnewcomment, handle_sendemail_unrecognized,handle_sendemailnewcomment,
handle_sendemailnewresponse, handle_updatedps, handle_sendemailnewresponse, handle_updatedps,
handle_updatecp,long_test_task,long_test_task2, handle_updatecp,long_test_task,long_test_task2,
handle_zip_file handle_zip_file,handle_getagegrouprecords
) )
from scipy.signal import savgol_filter from scipy.signal import savgol_filter
@@ -284,6 +285,7 @@ verbose_job_status = {
'updatecp': 'Critical Power Calculation for Ergometer Workouts', 'updatecp': 'Critical Power Calculation for Ergometer Workouts',
'updatecpwater': 'Critical Power Calculation for OTW Workouts', 'updatecpwater': 'Critical Power Calculation for OTW Workouts',
'otwsetpower': 'Rowing Physics OTW Power Calculation', 'otwsetpower': 'Rowing Physics OTW Power Calculation',
'agegrouprecords': 'Calculate age group records',
'make_plot': 'Create static chart', 'make_plot': 'Create static chart',
'long_test_task': 'Long Test Task', 'long_test_task': 'Long Test Task',
'long_test_task2': 'Long Test Task 2', 'long_test_task2': 'Long Test Task 2',
@@ -711,7 +713,8 @@ def splitstdata(lijst):
from utils import ( from utils import (
geo_distance,serialize_list,deserialize_list,uniqify, geo_distance,serialize_list,deserialize_list,uniqify,
str2bool,range_to_color_hex,absolute,myqueue,get_call, str2bool,range_to_color_hex,absolute,myqueue,get_call,
calculate_age calculate_age,rankingdistances,rankingdurations,
is_ranking_piece
) )
import datautils import datautils
@@ -2956,9 +2959,15 @@ def histo(request,theuser=0,
if 'options' in request.session: if 'options' in request.session:
options = request.session['options'] options = request.session['options']
workouttypes = options['workouttypes'] try:
includereststrokes = options['includereststrokes'] workouttypes = options['workouttypes']
waterboattype = options['waterboattype'] includereststrokes = options['includereststrokes']
waterboattype = options['waterboattype']
except KeyError:
workouttypes = ['water','rower','dynamic','slides']
waterboattype = ['1x','2x','2-','4x','4-','8+']
includereststrokes = False
workstrokesonly = not includereststrokes workstrokesonly = not includereststrokes
@@ -3161,6 +3170,7 @@ def addmanual_view(request):
) )
print duration,'aap'
id,message = dataprep.create_row_df(r, id,message = dataprep.create_row_df(r,
distance, distance,
duration,startdatetime, duration,startdatetime,
@@ -3175,7 +3185,7 @@ def addmanual_view(request):
if id: if id:
w = Workout.objects.get(id=id) w = Workout.objects.get(id=id)
w.rankingpiece = rankingpiece w.rankingpiece = rankingpiece or is_ranking_piece(w)
w.notes = notes w.notes = notes
w.save() w.save()
messages.info(request,'New workout created') messages.info(request,'New workout created')
@@ -3297,29 +3307,22 @@ def rankings_view(request,theuser=0,
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59)) enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
enddate = enddate+datetime.timedelta(days=1) enddate = enddate+datetime.timedelta(days=1)
rankingdistances = [100,500,1000,2000,5000,6000,10000,21097,42195,100000]
rankingdurations = []
rankingdurations.append(datetime.time(minute=1))
rankingdurations.append(datetime.time(minute=4))
rankingdurations.append(datetime.time(minute=30))
rankingdurations.append(datetime.time(hour=1,minute=15))
rankingdurations.append(datetime.time(hour=1))
thedistances = [] thedistances = []
theworkouts = [] theworkouts = []
thesecs = [] thesecs = []
rankingdistances.sort() rankingdistances.sort()
rankingdurations.sort() rankingdurations.sort()
for rankingdistance in rankingdistances: for rankingdistance in rankingdistances:
workouts = Workout.objects.filter(user=r,distance=rankingdistance, workouts = Workout.objects.filter(
workouttype__in=['rower','dynamic','slides'], user=r,distance=rankingdistance,
startdatetime__gte=startdate, workouttype__in=['rower','dynamic','slides'],
startdatetime__lte=enddate).order_by('duration') rankingpiece=True,
startdatetime__gte=startdate,
startdatetime__lte=enddate
).order_by('duration')
if workouts: if workouts:
thedistances.append(rankingdistance) thedistances.append(rankingdistance)
theworkouts.append(workouts[0]) theworkouts.append(workouts[0])
@@ -3333,10 +3336,13 @@ def rankings_view(request,theuser=0,
for rankingduration in rankingdurations: for rankingduration in rankingdurations:
workouts = Workout.objects.filter(user=r,duration=rankingduration, workouts = Workout.objects.filter(
workouttype='rower', user=r,duration=rankingduration,
startdatetime__gte=startdate, workouttype='rower',
startdatetime__lte=enddate).order_by('-distance') rankingpiece=True,
startdatetime__gte=startdate,
startdatetime__lte=enddate
).order_by('-distance')
if workouts: if workouts:
thedistances.append(workouts[0].distance) thedistances.append(workouts[0].distance)
theworkouts.append(workouts[0]) theworkouts.append(workouts[0])
@@ -3511,6 +3517,43 @@ def rankings_view(request,theuser=0,
'teams':get_my_teams(request.user), 'teams':get_my_teams(request.user),
}) })
@login_required()
def ajax_agegrouprecords(request,
age=25,
sex='female',
weightcategory='hwt',
userid=0):
wcdurations = []
wcpower = []
durations = [1,4,30,60]
distances = [100,500,1000,2000,5000,6000,10000,21097,42195]
df = pd.DataFrame(
list(
C2WorldClassAgePerformance.objects.filter(
sex=sex,
weightcategory=weightcategory
).values()
)
)
jsondf = df.to_json()
job = myqueue(queue,
handle_getagegrouprecords,
jsondf,distances,durations,age,sex,weightcategory,
)
return JSONResponse(
{
'job':job.id
}
)
# Show ranking distances including predicted paces # Show ranking distances including predicted paces
@login_required() @login_required()
def rankings_view2(request,theuser=0, def rankings_view2(request,theuser=0,
@@ -3536,48 +3579,78 @@ def rankings_view2(request,theuser=0,
if theuser == 0: if theuser == 0:
theuser = request.user.id theuser = request.user.id
else:
lastupdated = "01-01-1900"
promember=0 promember=0
if not request.user.is_anonymous(): if not request.user.is_anonymous():
r = getrower(request.user) r = getrower(theuser)
wcdurations = [] wcdurations = []
wcpower = [] wcpower = []
lastupdated = "01-01-1900"
userid = 0
if 'options' in request.session:
options = request.session['options']
try:
wcdurations = options['wcdurations']
wcpower = options['wcpower']
lastupdated = options['lastupdated']
except KeyError:
pass
try:
userid = options['userid']
except KeyError:
userid = 0
else:
options = {}
lastupdatedtime = arrow.get(lastupdated).timestamp
current_time = arrow.utcnow().timestamp
deltatime_seconds = current_time - lastupdatedtime
recalc = False
if str(userid) != str(theuser) or deltatime_seconds > 3600:
recalc = True
options['lastupdated'] = arrow.utcnow().isoformat()
else:
recalc = False
options['userid'] = theuser
if r.birthdate: if r.birthdate:
age = calculate_age(r.birthdate) age = calculate_age(r.birthdate)
durations = [1,4,30,60]
distances = [100,500,1000,2000,5000,6000,10000,21097,42195]
for distance in distances:
worldclasspower = metrics.getagegrouprecord(
age,
sex=r.sex,
distance=distance,
weightcategory=r.weightcategory
)
velo = (worldclasspower/2.8)**(1./3.)
try:
duration = distance/velo
wcdurations.append(duration)
wcpower.append(worldclasspower)
except ZeroDivisionError:
pass
for duration in durations:
worldclasspower = metrics.getagegrouprecord(
age,
sex=r.sex,
duration=duration,
weightcategory=r.weightcategory
)
try:
velo = (worldclasspower/2.8)**(1./3.)
distance = int(60*duration*velo)
wcdurations.append(60.*duration)
wcpower.append(worldclasspower)
except ValueError:
pass
else: else:
worldclasspower = None worldclasspower = None
age = 0 age = 0
agerecords = CalcAgePerformance.objects.filter(
age = age,
sex = r.sex,
weightcategory = r.weightcategory)
print len(agerecords),'aap'
if len(agerecords) == 0:
recalc = True
wcpower = []
wcduration = []
else:
wcdurations = []
wcpower = []
for record in agerecords:
wcdurations.append(record.duration)
wcpower.append(record.power)
options['wcpower'] = wcpower
options['wcdurations'] = wcdurations
if theuser:
options['userid'] = theuser
request.session['options'] = options
result = request.user.is_authenticated() and ispromember(request.user) result = request.user.is_authenticated() and ispromember(request.user)
if result: if result:
@@ -3641,13 +3714,6 @@ def rankings_view2(request,theuser=0,
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59)) enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
enddate = enddate+datetime.timedelta(days=1) enddate = enddate+datetime.timedelta(days=1)
rankingdistances = [100,500,1000,2000,5000,6000,10000,21097,42195,100000]
rankingdurations = []
rankingdurations.append(datetime.time(minute=1))
rankingdurations.append(datetime.time(minute=4))
rankingdurations.append(datetime.time(minute=30))
rankingdurations.append(datetime.time(hour=1,minute=15))
rankingdurations.append(datetime.time(hour=1))
thedistances = [] thedistances = []
theworkouts = [] theworkouts = []
@@ -3660,10 +3726,12 @@ def rankings_view2(request,theuser=0,
for rankingdistance in rankingdistances: for rankingdistance in rankingdistances:
workouts = Workout.objects.filter(user=r,distance=rankingdistance, workouts = Workout.objects.filter(
workouttype__in=['rower','dynamic','slides'], user=r,distance=rankingdistance,
startdatetime__gte=startdate, workouttype__in=['rower','dynamic','slides'],
startdatetime__lte=enddate).order_by('duration') rankingpiece=True,
startdatetime__gte=startdate,
startdatetime__lte=enddate).order_by('duration')
if workouts: if workouts:
thedistances.append(rankingdistance) thedistances.append(rankingdistance)
theworkouts.append(workouts[0]) theworkouts.append(workouts[0])
@@ -3677,10 +3745,12 @@ def rankings_view2(request,theuser=0,
for rankingduration in rankingdurations: for rankingduration in rankingdurations:
workouts = Workout.objects.filter(user=r,duration=rankingduration, workouts = Workout.objects.filter(
workouttype='rower', user=r,duration=rankingduration,
startdatetime__gte=startdate, workouttype='rower',
startdatetime__lte=enddate).order_by('-distance') rankingpiece=True,
startdatetime__gte=startdate,
startdatetime__lte=enddate).order_by('-distance')
if workouts: if workouts:
thedistances.append(workouts[0].distance) thedistances.append(workouts[0].distance)
theworkouts.append(workouts[0]) theworkouts.append(workouts[0])
@@ -3841,6 +3911,32 @@ def rankings_view2(request,theuser=0,
'power':int(pwr)} 'power':int(pwr)}
cpredictions.append(a) cpredictions.append(a)
if recalc:
wcdurations = []
wcpower = []
durations = [1,4,30,60]
distances = [100,500,1000,2000,5000,6000,10000,21097,42195]
df = pd.DataFrame(
list(
C2WorldClassAgePerformance.objects.filter(
sex=r.sex,
weightcategory=r.weightcategory
).values()
)
)
jsondf = df.to_json()
job = myqueue(queue,
handle_getagegrouprecords,
jsondf,distances,durations,age,r.sex,r.weightcategory)
try:
request.session['async_tasks'] += [(job.id,'agegrouprecords')]
except KeyError:
request.session['async_tasks'] = [(job.id,'agegrouprecords')]
messages.error(request,message) messages.error(request,message)
return render(request, 'rankings.html', return render(request, 'rankings.html',
@@ -3857,6 +3953,7 @@ def rankings_view2(request,theuser=0,
'theuser':uu, 'theuser':uu,
'age':age, 'age':age,
'sex':r.sex, 'sex':r.sex,
'recalc':recalc,
'weightcategory':r.weightcategory, 'weightcategory':r.weightcategory,
'startdate':startdate, 'startdate':startdate,
'enddate':enddate, 'enddate':enddate,
@@ -3983,12 +4080,6 @@ def otwrankings_view(request,theuser=0,
enddate = enddate+datetime.timedelta(days=1) enddate = enddate+datetime.timedelta(days=1)
rankingdurations = []
rankingdurations.append(datetime.time(minute=1))
rankingdurations.append(datetime.time(minute=4))
rankingdurations.append(datetime.time(minute=30))
rankingdurations.append(datetime.time(hour=1))
rankingdurations.append(datetime.time(hour=1,minute=15))
thedistances = [] thedistances = []
theworkouts = [] theworkouts = []
@@ -4238,14 +4329,6 @@ def oterankings_view(request,theuser=0,
enddate = enddate+datetime.timedelta(days=1) enddate = enddate+datetime.timedelta(days=1)
rankingdurations = []
rankingdurations.append(datetime.time(minute=1))
rankingdurations.append(datetime.time(minute=4))
rankingdurations.append(datetime.time(minute=30))
rankingdurations.append(datetime.time(hour=1))
rankingdurations.append(datetime.time(hour=1,minute=15))
rankingdistances = [100,500,1000,2000,5000,6000,10000,21097,42195,100000]
thedistances = [] thedistances = []
theworkouts = [] theworkouts = []
@@ -5712,15 +5795,25 @@ def boxplot_view_data(request,userid=0,
if 'options' in request.session: if 'options' in request.session:
options = request.session['options'] options = request.session['options']
includereststrokes = options['includereststrokes'] try:
spmmin = options['spmmin'] includereststrokes = options['includereststrokes']
spmmax = options['spmmax'] spmmin = options['spmmin']
workmin = options['workmin'] spmmax = options['spmmax']
workmax = options['workmax'] workmin = options['workmin']
ids = options['ids'] workmax = options['workmax']
userid = options['userid'] ids = options['ids']
plotfield = options['plotfield'] userid = options['userid']
plotfield = options['plotfield']
except KeyError:
includereststrokes = False
spmmin = 15
spmmax = 55
workmin = 0
workmax = 55
ids = []
userid = 0
plotfield = 'spm'
workstrokesonly = not includereststrokes workstrokesonly = not includereststrokes
@@ -5792,8 +5885,13 @@ def boxplot_view(request,userid=0,
if 'options' in request.session: if 'options' in request.session:
options = request.session['options'] options = request.session['options']
try:
includereststrokes = options['includereststrokes']
except KeyError:
includereststrokes = False
options['includereststrokes'] = False
includereststrokes = options['includereststrokes']
workstrokesonly = not includereststrokes workstrokesonly = not includereststrokes
if userid==0: if userid==0:
@@ -5912,7 +6010,6 @@ def workouts_view(request,message='',successmessage='',
except ValueError: except ValueError:
activity_enddate = enddate activity_enddate = enddate
if teamid: if teamid:
try: try:
theteam = Team.objects.get(id=teamid) theteam = Team.objects.get(id=teamid)
@@ -5979,7 +6076,7 @@ def workouts_view(request,message='',successmessage='',
stack='rower' stack='rower'
else: else:
stack='type' stack='type'
script,div = interactive_activitychart(g_workouts, script,div = interactive_activitychart(g_workouts,
activity_startdate, activity_startdate,
activity_enddate, activity_enddate,
@@ -6734,12 +6831,14 @@ def workout_otwsetpower_view(request,id=0,message="",successmessage=""):
form = AdvancedWorkoutForm(request.POST) form = AdvancedWorkoutForm(request.POST)
if form.is_valid(): if form.is_valid():
quick_calc = form.cleaned_data['quick_calc']
boattype = form.cleaned_data['boattype'] boattype = form.cleaned_data['boattype']
weightvalue = form.cleaned_data['weightvalue'] weightvalue = form.cleaned_data['weightvalue']
row.boattype = boattype row.boattype = boattype
row.weightvalue = weightvalue row.weightvalue = weightvalue
row.save() row.save()
# load row data & create power/wind/bearing columns if not set # load row data & create power/wind/bearing columns if not set
f1 = row.csvfilename f1 = row.csvfilename
rowdata = rdata(f1) rowdata = rdata(f1)
@@ -6776,7 +6875,9 @@ def workout_otwsetpower_view(request,id=0,message="",successmessage=""):
weightvalue, weightvalue,
first_name,last_name,emailaddress,id, first_name,last_name,emailaddress,id,
ps=[r.p0,r.p1,r.p2,r.p3], ps=[r.p0,r.p1,r.p2,r.p3],
ratio=r.cpratio) ratio=r.cpratio,
quick_calc = quick_calc,
)
try: try:
request.session['async_tasks'] += [(job.id,'otwsetpower')] request.session['async_tasks'] += [(job.id,'otwsetpower')]
@@ -7312,7 +7413,7 @@ def workout_stats_view(request,id=0,message="",successmessage=""):
# Normalized power & TSS # Normalized power & TSS
duration = datadf['time'].max()-datadf['time'].min() duration = datadf['time'].max()-datadf['time'].min()
duration /= 1.0e3 duration /= 1.0e3
pwr4 = datadf['power']**(4) pwr4 = datadf['power']**(4.0)
normp = (pwr4.mean())**(0.25) normp = (pwr4.mean())**(0.25)
if not np.isnan(normp): if not np.isnan(normp):
ftp = float(r.ftp) ftp = float(r.ftp)
@@ -9304,6 +9405,35 @@ def workout_getc2workout_view(request,c2id):
url = reverse(workout_c2import_view) url = reverse(workout_c2import_view)
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@login_required
def workout_toggle_ranking(request,id=0):
is_ajax = False
if request.is_ajax():
is_ajax = True
try:
# check if valid ID exists (workout exists)
row = Workout.objects.get(id=id)
except Workout.DoesNotExist:
raise Http404("Workout doesn't exist")
if not checkworkoutuser(request.user,row):
message = "You are not allowed to change this workout"
messages.error(request,message)
# we are still here - we own the workout
row.rankingpiece = not row.rankingpiece
row.save()
if is_ajax:
return JSONResponse({'result':row.rankingpiece})
else:
url = reverse(workouts_view)
response = HttpResponseRedirect(url)
return response
# This is the main view for processing uploaded files # This is the main view for processing uploaded files
@login_required() @login_required()
def workout_upload_view(request, def workout_upload_view(request,

View File

@@ -56,6 +56,19 @@ body {
padding-bottom: 20px; padding-bottom: 20px;
} }
.notyellow {
font-size: 1.2em;
height: auto;
padding: 0px;
}
.yellow {
color: #cccc00;
font-size: 1.2em;
height: auto;
padding: 0px;
}
a { a {
/* color: #fff; */ /* color: #fff; */