Merge branch 'feature/duplicatesprivate' into develop
This commit is contained in:
+9
-3
@@ -398,7 +398,8 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower',
|
|||||||
notes='',totaldist=0,totaltime=0,
|
notes='',totaldist=0,totaltime=0,
|
||||||
summary='',
|
summary='',
|
||||||
makeprivate=False,
|
makeprivate=False,
|
||||||
oarlength=2.89,inboard=0.88):
|
oarlength=2.89,inboard=0.88,
|
||||||
|
consistencychecks=True):
|
||||||
message = None
|
message = None
|
||||||
powerperc = 100*np.array([r.pw_ut2,
|
powerperc = 100*np.array([r.pw_ut2,
|
||||||
r.pw_ut1,
|
r.pw_ut1,
|
||||||
@@ -417,9 +418,12 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower',
|
|||||||
for key,value in checks.iteritems():
|
for key,value in checks.iteritems():
|
||||||
if not value:
|
if not value:
|
||||||
allchecks = 0
|
allchecks = 0
|
||||||
|
if consistencychecks:
|
||||||
a_messages.error(r.user,'Failed consistency check: '+key+', autocorrected')
|
a_messages.error(r.user,'Failed consistency check: '+key+', autocorrected')
|
||||||
|
else:
|
||||||
|
a_messages.error(r.user,'Failed consistency check: '+key+', not corrected')
|
||||||
|
|
||||||
if not allchecks:
|
if not allchecks and consistencychecks:
|
||||||
# row.repair()
|
# row.repair()
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -529,6 +533,7 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower',
|
|||||||
user=r)
|
user=r)
|
||||||
if (len(ws) != 0):
|
if (len(ws) != 0):
|
||||||
message = "Warning: This workout probably already exists in the database"
|
message = "Warning: This workout probably already exists in the database"
|
||||||
|
privacy = 'private'
|
||||||
|
|
||||||
# checking for inf values
|
# checking for inf values
|
||||||
totaldist = np.nan_to_num(totaldist)
|
totaldist = np.nan_to_num(totaldist)
|
||||||
@@ -781,7 +786,8 @@ def new_workout_from_df(r,df,
|
|||||||
oarlength=oarlength,
|
oarlength=oarlength,
|
||||||
inboard=inboard,
|
inboard=inboard,
|
||||||
makeprivate=makeprivate,
|
makeprivate=makeprivate,
|
||||||
dosmooth=False)
|
dosmooth=False,
|
||||||
|
consistencychecks=False)
|
||||||
|
|
||||||
|
|
||||||
return (id,message)
|
return (id,message)
|
||||||
|
|||||||
@@ -243,6 +243,7 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower',
|
|||||||
user=r)
|
user=r)
|
||||||
if (len(ws) != 0):
|
if (len(ws) != 0):
|
||||||
message = "Warning: This workout probably already exists in the database"
|
message = "Warning: This workout probably already exists in the database"
|
||||||
|
privacy = 'private'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -596,6 +596,106 @@ def googlemap_chart(lat,lon,name=""):
|
|||||||
return [script,div]
|
return [script,div]
|
||||||
|
|
||||||
|
|
||||||
|
def interactive_otwcpchart(powerdf,promember=0):
|
||||||
|
powerdf = powerdf[~(powerdf == 0).any(axis=1)]
|
||||||
|
# plot tools
|
||||||
|
if (promember==1):
|
||||||
|
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair'
|
||||||
|
else:
|
||||||
|
TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
|
||||||
|
|
||||||
|
|
||||||
|
x_axis_type = 'log'
|
||||||
|
y_axis_type = 'linear'
|
||||||
|
|
||||||
|
deltas = powerdf['Delta'].apply(lambda x: timedeltaconv(x))
|
||||||
|
powerdf['ftime'] = niceformat(deltas)
|
||||||
|
|
||||||
|
|
||||||
|
source = ColumnDataSource(
|
||||||
|
data = powerdf
|
||||||
|
)
|
||||||
|
|
||||||
|
# there is no Paul's law for OTW
|
||||||
|
|
||||||
|
# Fit the data to thee parameter CP model
|
||||||
|
fitfunc = lambda pars,x: pars[0]/(1+(x/pars[2])) + pars[1]/(1+(x/pars[3]))
|
||||||
|
errfunc = lambda pars,x,y: fitfunc(pars,x)-y
|
||||||
|
|
||||||
|
p0 = [500,350,10,8000]
|
||||||
|
|
||||||
|
p1 = p0
|
||||||
|
|
||||||
|
thesecs = powerdf['Delta']
|
||||||
|
theavpower = powerdf['CP']
|
||||||
|
|
||||||
|
if len(thesecs)>=4:
|
||||||
|
p1, success = optimize.leastsq(errfunc, p0[:], args = (thesecs,theavpower))
|
||||||
|
else:
|
||||||
|
factor = fitfunc(p0,thesecs.mean())/theavpower.mean()
|
||||||
|
p1 = [p0[0]/factor,p0[1]/factor,p0[2],p0[3]]
|
||||||
|
|
||||||
|
|
||||||
|
fitt = pd.Series(10**(4*np.arange(100)/100.))
|
||||||
|
|
||||||
|
fitpower = fitfunc(p1,fitt)
|
||||||
|
|
||||||
|
message = ""
|
||||||
|
#if len(fitpower[fitpower<0]) > 0:
|
||||||
|
# message = "CP model fit didn't give correct results"
|
||||||
|
|
||||||
|
|
||||||
|
sourcecomplex = ColumnDataSource(
|
||||||
|
data = dict(
|
||||||
|
power = fitpower,
|
||||||
|
duration = fitt
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# making the plot
|
||||||
|
plot = Figure(tools=TOOLS,x_axis_type=x_axis_type,
|
||||||
|
plot_width=900,
|
||||||
|
toolbar_location="above",
|
||||||
|
toolbar_sticky=False)
|
||||||
|
|
||||||
|
# add watermark
|
||||||
|
plot.extra_y_ranges = {"watermark": watermarkrange}
|
||||||
|
|
||||||
|
plot.image_url([watermarkurl],1.8*max(thesecs),watermarky,
|
||||||
|
watermarkw,watermarkh,
|
||||||
|
global_alpha=watermarkalpha,
|
||||||
|
w_units='screen',
|
||||||
|
h_units='screen',
|
||||||
|
anchor=watermarkanchor,
|
||||||
|
dilate=True,
|
||||||
|
y_range_name = "watermark",
|
||||||
|
)
|
||||||
|
|
||||||
|
plot.circle('Delta','CP',source=source,fill_color='red',size=15,
|
||||||
|
legend='Power Data')
|
||||||
|
plot.xaxis.axis_label = "Duration (seconds)"
|
||||||
|
plot.yaxis.axis_label = "Power (W)"
|
||||||
|
|
||||||
|
plot.y_range = Range1d(0,1.5*max(theavpower))
|
||||||
|
plot.x_range = Range1d(1,2*max(thesecs))
|
||||||
|
plot.legend.orientation = "vertical"
|
||||||
|
|
||||||
|
hover = plot.select(dict(type=HoverTool))
|
||||||
|
|
||||||
|
hover.tooltips = OrderedDict([
|
||||||
|
('Duration ','@ftime'),
|
||||||
|
('Power (W)','@CP{int}'),
|
||||||
|
])
|
||||||
|
|
||||||
|
hover.mode = 'mouse'
|
||||||
|
|
||||||
|
plot.line('duration','power',source=sourcecomplex,legend="CP Model",
|
||||||
|
color='green')
|
||||||
|
|
||||||
|
script, div = components(plot)
|
||||||
|
|
||||||
|
return [script,div,p1,message]
|
||||||
|
|
||||||
def interactive_cpchart(thedistances,thesecs,theavpower,
|
def interactive_cpchart(thedistances,thesecs,theavpower,
|
||||||
theworkouts,promember=0):
|
theworkouts,promember=0):
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -425,6 +425,7 @@ class Workout(models.Model):
|
|||||||
summary = models.TextField(blank=True)
|
summary = models.TextField(blank=True)
|
||||||
privacy = models.CharField(default='visible',max_length=30,
|
privacy = models.CharField(default='visible',max_length=30,
|
||||||
choices=privacychoices)
|
choices=privacychoices)
|
||||||
|
rankingpiece = models.BooleanField(default=False,verbose_name='Ranking Piece')
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
|
|
||||||
@@ -552,7 +553,7 @@ class WorkoutForm(ModelForm):
|
|||||||
duration = forms.TimeInput(format='%H:%M:%S.%f')
|
duration = forms.TimeInput(format='%H:%M:%S.%f')
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Workout
|
model = Workout
|
||||||
fields = ['name','date','starttime','duration','distance','workouttype','notes','privacy','boattype']
|
fields = ['name','date','starttime','duration','distance','workouttype','notes','privacy','rankingpiece','boattype']
|
||||||
widgets = {
|
widgets = {
|
||||||
'date': DateInput(),
|
'date': DateInput(),
|
||||||
'notes': forms.Textarea,
|
'notes': forms.Textarea,
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ def get_strava_workout(user,stravaid):
|
|||||||
return [workoutsummary,df]
|
return [workoutsummary,df]
|
||||||
|
|
||||||
# Generate Workout data for Strava (a TCX file)
|
# Generate Workout data for Strava (a TCX file)
|
||||||
def createstravaworkoutdata(w):
|
def createstravaworkoutdata(w,dozip=True):
|
||||||
filename = w.csvfilename
|
filename = w.csvfilename
|
||||||
|
|
||||||
row = rowingdata(filename)
|
row = rowingdata(filename)
|
||||||
@@ -256,6 +256,7 @@ def createstravaworkoutdata(w):
|
|||||||
newnotes = 'from '+w.workoutsource+' via rowsandall.com'
|
newnotes = 'from '+w.workoutsource+' via rowsandall.com'
|
||||||
|
|
||||||
row.exporttotcx(tcxfilename,notes=newnotes)
|
row.exporttotcx(tcxfilename,notes=newnotes)
|
||||||
|
if dozip:
|
||||||
gzfilename = tcxfilename+'.gz'
|
gzfilename = tcxfilename+'.gz'
|
||||||
with file(tcxfilename,'rb') as inF:
|
with file(tcxfilename,'rb') as inF:
|
||||||
s = inF.read()
|
s = inF.read()
|
||||||
@@ -269,6 +270,9 @@ def createstravaworkoutdata(w):
|
|||||||
|
|
||||||
return gzfilename,""
|
return gzfilename,""
|
||||||
|
|
||||||
|
else:
|
||||||
|
return tcxfilename,""
|
||||||
|
|
||||||
|
|
||||||
# Upload the TCX file to Strava and set the workout activity type
|
# Upload the TCX file to Strava and set the workout activity type
|
||||||
# to rowing on Strava
|
# to rowing on Strava
|
||||||
|
|||||||
@@ -77,8 +77,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="grid_12 alpha">
|
||||||
|
<div class="grid_6 alpha">
|
||||||
|
<p> </p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid_6 omega">
|
||||||
|
<div class="grid_2 suffix_4 alpha">
|
||||||
|
<p>
|
||||||
|
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
|
||||||
|
<a class="button blue small" href="/rowers/otw-bests">OTW Ranking Pieces</a>
|
||||||
|
{% else %}
|
||||||
|
<a class="button blue small" href="/rowers/promembership">OTW Ranking Pieces</a>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Analyse power vs piece duration to make predictions.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -147,6 +147,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="grid_1 omega">
|
||||||
|
<a href="/rowers/workout/{{ workout.id }}/emailgpx">
|
||||||
|
<img src="/static/img/gpx.jpg" alt="GPX Export" width="60" height="60"></a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,207 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% load rowerfilters %}
|
||||||
|
|
||||||
|
{% block title %}Workouts{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<script type="text/javascript" src="/static/js/bokeh-0.12.3.min.js"></script>
|
||||||
|
<script async="true" type="text/javascript">
|
||||||
|
Bokeh.set_log_level("info");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{{ interactiveplot |safe }}
|
||||||
|
|
||||||
|
<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, false);
|
||||||
|
};
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="title" class="grid_12 alpha">
|
||||||
|
<div class="grid_10 alpha">
|
||||||
|
{% if theuser %}
|
||||||
|
<h3>{{ theuser.first_name }}'s Ranking Pieces</h3>
|
||||||
|
{% else %}
|
||||||
|
<h3>{{ user.first_name }}'s Ranking Pieces</h3>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="grid_2 omega">
|
||||||
|
{% if user.is_authenticated and user|is_manager %}
|
||||||
|
<div class="grid_2 alpha dropdown">
|
||||||
|
<button class="grid_2 alpha button green small dropbtn">
|
||||||
|
Change Rower
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-content">
|
||||||
|
{% for member in user|team_members %}
|
||||||
|
<a class="button green small" href="/rowers/{{ member.id }}/otw-bests/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}">{{ member.first_name }} {{ member.last_name }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="summary" class="grid_6 alpha">
|
||||||
|
<p>Summary for {{ theuser.first_name }} {{ theuser.last_name }}
|
||||||
|
between {{ startdate|date }} and {{ enddate|date }}</p>
|
||||||
|
|
||||||
|
<p>Direct link for other users:
|
||||||
|
<a href="/rowers/{{ id }}/otw-bests/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}">https://rowsandall.com/rowers/{{ id }}/otw-bests/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>The table gives the OTW efforts you marked as Ranking Piece.
|
||||||
|
The graph shows the best segments from those pieces, plotted as
|
||||||
|
average power (over the segment) vs the duration of the segment/
|
||||||
|
In other words: How long you can hold that power.
|
||||||
|
</p>
|
||||||
|
<p>At the bottom of the page, you will find predictions derived from the model.</p>
|
||||||
|
</div>
|
||||||
|
<div id="form" class="grid_6 omega">
|
||||||
|
<p>Use this form to select a different date range:</p>
|
||||||
|
<p>
|
||||||
|
Select start and end date for a date range:
|
||||||
|
<div class="grid_4 alpha">
|
||||||
|
|
||||||
|
<form enctype="multipart/form-data" action="" method="post">
|
||||||
|
|
||||||
|
<table>
|
||||||
|
{{ dateform.as_table }}
|
||||||
|
</table>
|
||||||
|
{% csrf_token %}
|
||||||
|
</div>
|
||||||
|
<div class="grid_2 omega">
|
||||||
|
<input name='daterange' class="button green" type="submit" value="Submit"> </form>
|
||||||
|
</div>
|
||||||
|
<div class="grid_4 alpha">
|
||||||
|
<form enctype="multipart/form-data" action="" method="post">
|
||||||
|
Or use the last {{ deltaform }} days.
|
||||||
|
</div>
|
||||||
|
<div class="grid_2 omega">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input name='datedelta' class="button green" type="submit" value="Submit">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div id="theplot" class="grid_12 alpha">
|
||||||
|
|
||||||
|
<h2>Critical Power Plot</h2>
|
||||||
|
|
||||||
|
{{ the_div|safe }}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid_12 alpha">
|
||||||
|
|
||||||
|
<h2>Ranking Piece Results</h2>
|
||||||
|
|
||||||
|
{% if rankingworkouts %}
|
||||||
|
|
||||||
|
<table width="70%" class="listtable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th> Distance</th>
|
||||||
|
<th> Duration</th>
|
||||||
|
<th> Avg Power</th>
|
||||||
|
<th> Date</th>
|
||||||
|
<th> Avg HR </th>
|
||||||
|
<th> Max HR </th>
|
||||||
|
<th> Edit</th>
|
||||||
|
<tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for workout in rankingworkouts %}
|
||||||
|
<tr>
|
||||||
|
<td> {{ workout.distance }} m</td>
|
||||||
|
<td> {{ workout.duration |durationprint:"%H:%M:%S.%f" }} </td>
|
||||||
|
<td> {{ avgpower|lookup:workout.id }} W</td>
|
||||||
|
<td> {{ workout.date }} </td>
|
||||||
|
<td> {{ workout.averagehr }} </td>
|
||||||
|
<td> {{ workout.maxhr }} </td>
|
||||||
|
<td>
|
||||||
|
<a href="/rowers/workout/{{ workout.id }}/edit">{{ workout.name }}</a> </td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<p> No ranking workouts found </p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="predictions" class="grid_12 alpha">
|
||||||
|
<h2>Pace predictions for Ranking Pieces</h2>
|
||||||
|
|
||||||
|
<p>Add non-ranking piece using the form. The piece will be added in the prediction tables below. </p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div id="cpmodel" class="grid_6 alpha">
|
||||||
|
<table width="70%" class="listtable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th> Duration</th>
|
||||||
|
<th> Power </th>
|
||||||
|
<tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for pred in cpredictions %}
|
||||||
|
<tr>
|
||||||
|
{% for key, value in pred.items %}
|
||||||
|
{% if key == "power" %}
|
||||||
|
<td> {{ value }} W </td>
|
||||||
|
{% endif %}
|
||||||
|
{% if key == "duration" %}
|
||||||
|
<td> {{ value |deltatimeprint }} </td>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid_3">
|
||||||
|
<form enctype="multipart/form-data" action="{{ formloc }}" method="post">
|
||||||
|
{{ form.value }} {{ form.pieceunit }}
|
||||||
|
|
||||||
|
{% csrf_token %}
|
||||||
|
</div>
|
||||||
|
<div class="grid_1">
|
||||||
|
minutes
|
||||||
|
</div>
|
||||||
|
<div class="grid_2 omega">
|
||||||
|
<input name="piece" class="button green"
|
||||||
|
formaction="/rowers/{{ id }}/otw-bests/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}"
|
||||||
|
type="submit" value="Add">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -135,6 +135,12 @@ urlpatterns = [
|
|||||||
url(r'^ote-bests/(?P<deltadays>\d+)$',views.rankings_view),
|
url(r'^ote-bests/(?P<deltadays>\d+)$',views.rankings_view),
|
||||||
url(r'^ote-bests/$',views.rankings_view),
|
url(r'^ote-bests/$',views.rankings_view),
|
||||||
url(r'^(?P<theuser>\d+)/ote-bests/$',views.rankings_view),
|
url(r'^(?P<theuser>\d+)/ote-bests/$',views.rankings_view),
|
||||||
|
url(r'^(?P<theuser>\d+)/otw-bests/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.otwrankings_view),
|
||||||
|
url(r'^(?P<theuser>\d+)/otw-bests/(?P<deltadays>\d+)$',views.otwrankings_view),
|
||||||
|
url(r'^otw-bests/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.otwrankings_view),
|
||||||
|
url(r'^otw-bests/(?P<deltadays>\d+)$',views.otwrankings_view),
|
||||||
|
url(r'^otw-bests/$',views.otwrankings_view),
|
||||||
|
url(r'^(?P<theuser>\d+)/otw-bests/$',views.otwrankings_view),
|
||||||
url(r'^(?P<theuser>\d+)/flexall/(?P<xparam>\w+.*)/(?P<yparam1>\w+.*)/(?P<yparam2>\w+.*)/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.cum_flex),
|
url(r'^(?P<theuser>\d+)/flexall/(?P<xparam>\w+.*)/(?P<yparam1>\w+.*)/(?P<yparam2>\w+.*)/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.cum_flex),
|
||||||
url(r'^flexall/(?P<xparam>\w+.*)/(?P<yparam1>\w+.*)/(?P<yparam2>\w+.*)/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.cum_flex),
|
url(r'^flexall/(?P<xparam>\w+.*)/(?P<yparam1>\w+.*)/(?P<yparam2>\w+.*)/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.cum_flex),
|
||||||
url(r'^flexall/(?P<xparam>\w+.*)/(?P<yparam1>\w+.*)/(?P<yparam2>\w+.*)$',views.cum_flex),
|
url(r'^flexall/(?P<xparam>\w+.*)/(?P<yparam1>\w+.*)/(?P<yparam2>\w+.*)$',views.cum_flex),
|
||||||
@@ -169,6 +175,7 @@ urlpatterns = [
|
|||||||
url(r'^workout/(?P<id>\d+)/export$',views.workout_export_view),
|
url(r'^workout/(?P<id>\d+)/export$',views.workout_export_view),
|
||||||
url(r'^workout/(?P<id>\d+)/comment$',views.workout_comment_view),
|
url(r'^workout/(?P<id>\d+)/comment$',views.workout_comment_view),
|
||||||
url(r'^workout/(?P<id>\d+)/emailtcx$',views.workout_tcxemail_view),
|
url(r'^workout/(?P<id>\d+)/emailtcx$',views.workout_tcxemail_view),
|
||||||
|
url(r'^workout/(?P<id>\d+)/emailgpx$',views.workout_gpxemail_view),
|
||||||
url(r'^workout/(?P<id>\d+)/emailcsv$',views.workout_csvemail_view),
|
url(r'^workout/(?P<id>\d+)/emailcsv$',views.workout_csvemail_view),
|
||||||
url(r'^workout/(?P<id>\d+)/csvtoadmin$',views.workout_csvtoadmin_view),
|
url(r'^workout/(?P<id>\d+)/csvtoadmin$',views.workout_csvtoadmin_view),
|
||||||
url(r'^workout/compare/(?P<id>\d+)/$',views.workout_comparison_list),
|
url(r'^workout/compare/(?P<id>\d+)/$',views.workout_comparison_list),
|
||||||
|
|||||||
+322
-4
@@ -138,6 +138,8 @@ from scipy.special import lambertw
|
|||||||
|
|
||||||
from dataprep import timedeltaconv
|
from dataprep import timedeltaconv
|
||||||
|
|
||||||
|
from scipy.interpolate import griddata
|
||||||
|
|
||||||
LOCALTIMEZONE = tz('Etc/UTC')
|
LOCALTIMEZONE = tz('Etc/UTC')
|
||||||
USER_LANGUAGE = 'en-US'
|
USER_LANGUAGE = 'en-US'
|
||||||
|
|
||||||
@@ -290,14 +292,11 @@ def iscoachmember(user):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def getrower(user):
|
def getrower(user):
|
||||||
if not user.is_anonymous():
|
|
||||||
try:
|
try:
|
||||||
r = Rower.objects.get(user=user)
|
r = Rower.objects.get(user=user)
|
||||||
except Rower.DoesNotExist:
|
except Rower.DoesNotExist:
|
||||||
r = Rower(user=user)
|
r = Rower(user=user)
|
||||||
r.save()
|
r.save()
|
||||||
else:
|
|
||||||
raise PermissionDenied("You need to log in to use this function")
|
|
||||||
|
|
||||||
return r
|
return r
|
||||||
|
|
||||||
@@ -1086,7 +1085,7 @@ def workout_tcxemail_view(request,id=0):
|
|||||||
raise Http404("Workout doesn't exist")
|
raise Http404("Workout doesn't exist")
|
||||||
if (checkworkoutuser(request.user,w)):
|
if (checkworkoutuser(request.user,w)):
|
||||||
try:
|
try:
|
||||||
tcxfile,tcxmessg = stravastuff.createstravaworkoutdata(w)
|
tcxfile,tcxmessg = stravastuff.createstravaworkoutdata(w,dozip=False)
|
||||||
if tcxfile == 0:
|
if tcxfile == 0:
|
||||||
message = "Something went wrong (TCX export) "+tcxmessg
|
message = "Something went wrong (TCX export) "+tcxmessg
|
||||||
messages.error(request,message)
|
messages.error(request,message)
|
||||||
@@ -1138,6 +1137,51 @@ def workout_tcxemail_view(request,id=0):
|
|||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
# Export workout to GPX and send to user's email address
|
||||||
|
@login_required()
|
||||||
|
def workout_gpxemail_view(request,id=0):
|
||||||
|
message = ""
|
||||||
|
successmessage = ""
|
||||||
|
r = Rower.objects.get(user=request.user)
|
||||||
|
try:
|
||||||
|
w = Workout.objects.get(id=id)
|
||||||
|
except Workout.DoesNotExist:
|
||||||
|
raise Http404("Workout doesn't exist")
|
||||||
|
if (checkworkoutuser(request.user,w)):
|
||||||
|
filename = w.csvfilename
|
||||||
|
row = rdata(filename)
|
||||||
|
gpxfilename = filename[:-4]+'.gpx'
|
||||||
|
row.exporttogpx(gpxfilename)
|
||||||
|
if settings.DEBUG:
|
||||||
|
res = handle_sendemailtcx.delay(r.user.first_name,
|
||||||
|
r.user.last_name,
|
||||||
|
r.user.email,gpxfilename)
|
||||||
|
|
||||||
|
else:
|
||||||
|
res = queuehigh.enqueue(handle_sendemailtcx,r.user.first_name,
|
||||||
|
r.user.last_name,
|
||||||
|
r.user.email,gpxfilename)
|
||||||
|
|
||||||
|
successmessage = "The GPX file was sent to you per email"
|
||||||
|
messages.info(request,successmessage)
|
||||||
|
url = reverse(workout_export_view,
|
||||||
|
kwargs = {
|
||||||
|
'id':str(w.id),
|
||||||
|
})
|
||||||
|
|
||||||
|
response = HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
else:
|
||||||
|
message = "You are not allowed to export this workout"
|
||||||
|
messages.error(request,message)
|
||||||
|
url = reverse(workout_export_view,
|
||||||
|
kwargs = {
|
||||||
|
'id':str(w.id),
|
||||||
|
})
|
||||||
|
response = HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
# Get Workout CSV file and send it to user's email address
|
# Get Workout CSV file and send it to user's email address
|
||||||
@login_required()
|
@login_required()
|
||||||
def workout_csvemail_view(request,id=0):
|
def workout_csvemail_view(request,id=0):
|
||||||
@@ -2730,6 +2774,274 @@ def rankings_view(request,theuser=0,
|
|||||||
'teams':get_my_teams(request.user),
|
'teams':get_my_teams(request.user),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Show ranking distances including predicted paces
|
||||||
|
@login_required()
|
||||||
|
def otwrankings_view(request,theuser=0,
|
||||||
|
startdate=timezone.now()-datetime.timedelta(days=365),
|
||||||
|
enddate=timezone.now(),
|
||||||
|
deltadays=-1,
|
||||||
|
startdatestring="",
|
||||||
|
enddatestring=""):
|
||||||
|
|
||||||
|
if deltadays>0:
|
||||||
|
startdate = enddate-datetime.timedelta(days=int(deltadays))
|
||||||
|
|
||||||
|
if startdatestring != "":
|
||||||
|
startdate = iso8601.parse_date(startdatestring)
|
||||||
|
|
||||||
|
if enddatestring != "":
|
||||||
|
enddate = iso8601.parse_date(enddatestring)
|
||||||
|
|
||||||
|
if enddate < startdate:
|
||||||
|
s = enddate
|
||||||
|
enddate = startdate
|
||||||
|
startdate = s
|
||||||
|
|
||||||
|
if theuser == 0:
|
||||||
|
theuser = request.user.id
|
||||||
|
|
||||||
|
promember=0
|
||||||
|
if not request.user.is_anonymous():
|
||||||
|
r = Rower.objects.get(user=request.user)
|
||||||
|
result = request.user.is_authenticated() and ispromember(request.user)
|
||||||
|
if result:
|
||||||
|
promember=1
|
||||||
|
|
||||||
|
# get all OTW rows in date range
|
||||||
|
|
||||||
|
# process form
|
||||||
|
if request.method == 'POST' and "daterange" in request.POST:
|
||||||
|
dateform = DateRangeForm(request.POST)
|
||||||
|
deltaform = DeltaDaysForm(request.POST)
|
||||||
|
if dateform.is_valid():
|
||||||
|
startdate = dateform.cleaned_data['startdate']
|
||||||
|
enddate = dateform.cleaned_data['enddate']
|
||||||
|
if startdate > enddate:
|
||||||
|
s = enddate
|
||||||
|
enddate = startdate
|
||||||
|
startdate = s
|
||||||
|
elif request.method == 'POST' and "datedelta" in request.POST:
|
||||||
|
deltaform = DeltaDaysForm(request.POST)
|
||||||
|
if deltaform.is_valid():
|
||||||
|
deltadays = deltaform.cleaned_data['deltadays']
|
||||||
|
if deltadays:
|
||||||
|
enddate = timezone.now()
|
||||||
|
startdate = enddate-datetime.timedelta(days=deltadays)
|
||||||
|
if startdate > enddate:
|
||||||
|
s = enddate
|
||||||
|
enddate = startdate
|
||||||
|
startdate = s
|
||||||
|
dateform = DateRangeForm(initial={
|
||||||
|
'startdate': startdate,
|
||||||
|
'enddate': enddate,
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
dateform = DateRangeForm()
|
||||||
|
deltaform = DeltaDaysForm()
|
||||||
|
|
||||||
|
else:
|
||||||
|
dateform = DateRangeForm(initial={
|
||||||
|
'startdate': startdate,
|
||||||
|
'enddate': enddate,
|
||||||
|
})
|
||||||
|
deltaform = DeltaDaysForm()
|
||||||
|
|
||||||
|
# get all 2k (if any) - this rower, in date range
|
||||||
|
try:
|
||||||
|
r = Rower.objects.get(user=theuser)
|
||||||
|
except Rower.DoesNotExist:
|
||||||
|
allergworkouts = []
|
||||||
|
r=0
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
uu = User.objects.get(id=theuser)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
uu = ''
|
||||||
|
|
||||||
|
|
||||||
|
# test to fix bug
|
||||||
|
startdate = datetime.datetime.combine(startdate,datetime.time())
|
||||||
|
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
|
||||||
|
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))
|
||||||
|
|
||||||
|
thedistances = []
|
||||||
|
theworkouts = []
|
||||||
|
thesecs = []
|
||||||
|
|
||||||
|
theworkouts = Workout.objects.filter(user=r,rankingpiece=True,
|
||||||
|
workouttype='water',
|
||||||
|
startdatetime__gte=startdate,
|
||||||
|
startdatetime__lte=enddate)
|
||||||
|
|
||||||
|
|
||||||
|
# get all power data from database (plus workoutid)
|
||||||
|
theids = [w.id for w in theworkouts]
|
||||||
|
columns = ['power','workoutid','time']
|
||||||
|
df = dataprep.getsmallrowdata_db(columns,ids=theids)
|
||||||
|
|
||||||
|
thesecs = []
|
||||||
|
|
||||||
|
for w in theworkouts:
|
||||||
|
timesecs = 3600*w.duration.hour
|
||||||
|
timesecs += 60*w.duration.minute
|
||||||
|
timesecs += w.duration.second
|
||||||
|
timesecs += 1.e-5*w.duration.microsecond
|
||||||
|
|
||||||
|
thesecs.append(timesecs)
|
||||||
|
|
||||||
|
if len(thesecs) != 0:
|
||||||
|
maxt = pd.Series(thesecs).max()
|
||||||
|
else:
|
||||||
|
maxt = 1000.
|
||||||
|
|
||||||
|
maxlog10 = np.log10(maxt)
|
||||||
|
logarr = np.arange(100)*maxlog10/100.
|
||||||
|
logarr = [int(10.**(la)) for la in logarr]
|
||||||
|
logarr = pd.Series(logarr)
|
||||||
|
logarr.drop_duplicates(keep='first',inplace=True)
|
||||||
|
logarr = logarr.values
|
||||||
|
|
||||||
|
delta = []
|
||||||
|
cpvalue = []
|
||||||
|
avgpower = {}
|
||||||
|
|
||||||
|
dfgrouped = df.groupby(['workoutid'])
|
||||||
|
for id,group in dfgrouped:
|
||||||
|
tt = group['time']
|
||||||
|
ww = group['power']
|
||||||
|
try:
|
||||||
|
avgpower[id] = int(ww.mean())
|
||||||
|
except ValueError:
|
||||||
|
avgpower[id] = '---'
|
||||||
|
if not np.isnan(ww.mean()):
|
||||||
|
length = len(ww)
|
||||||
|
dt = []
|
||||||
|
cpw = []
|
||||||
|
for i in range(length-2):
|
||||||
|
w_roll = ww.rolling(i+2,min_periods=2).mean()
|
||||||
|
# now goes with # data points - should be fixed seconds
|
||||||
|
indexmax = w_roll.idxmax(axis=1)
|
||||||
|
try:
|
||||||
|
t_0 = tt.ix[indexmax]
|
||||||
|
t_1 = tt.ix[indexmax-i-2]
|
||||||
|
deltat = 1.0e-3*(t_0-t_1)
|
||||||
|
wmax = w_roll.ix[indexmax]
|
||||||
|
dt.append(deltat)
|
||||||
|
cpw.append(wmax)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
dt = pd.Series(dt)
|
||||||
|
cpw = pd.Series(cpw)
|
||||||
|
cpvalues = griddata(dt.values,
|
||||||
|
cpw.values,
|
||||||
|
logarr,method='linear',fill_value=0)
|
||||||
|
|
||||||
|
for cpv in cpvalues:
|
||||||
|
cpvalue.append(cpv)
|
||||||
|
for d in logarr:
|
||||||
|
delta.append(d)
|
||||||
|
|
||||||
|
print avgpower
|
||||||
|
dt = pd.Series(delta,name='Delta')
|
||||||
|
cpvalue = pd.Series(cpvalue,name='CP')
|
||||||
|
|
||||||
|
|
||||||
|
powerdf = pd.DataFrame({
|
||||||
|
'Delta':delta,
|
||||||
|
'CP':cpvalue,
|
||||||
|
})
|
||||||
|
|
||||||
|
powerdf = powerdf[powerdf['CP']>0]
|
||||||
|
powerdf.dropna(axis=0,inplace=True)
|
||||||
|
powerdf.sort_values(['Delta','CP'],ascending=[1,0],inplace=True)
|
||||||
|
powerdf.drop_duplicates(subset='Delta',keep='first',inplace=True)
|
||||||
|
|
||||||
|
|
||||||
|
# create interactive plot
|
||||||
|
if len(powerdf) !=0 :
|
||||||
|
res = interactive_otwcpchart(powerdf,promember=promember)
|
||||||
|
script = res[0]
|
||||||
|
div = res[1]
|
||||||
|
p1 = res[2]
|
||||||
|
paulslope = 1
|
||||||
|
paulintercept = 1
|
||||||
|
message = res[3]
|
||||||
|
else:
|
||||||
|
script = ''
|
||||||
|
div = '<p>No ranking pieces found.</p>'
|
||||||
|
paulslope = 1
|
||||||
|
paulintercept = 1
|
||||||
|
p1 = [1,1,1,1]
|
||||||
|
message = ""
|
||||||
|
|
||||||
|
|
||||||
|
if request.method == 'POST' and "piece" in request.POST:
|
||||||
|
form = PredictedPieceForm(request.POST)
|
||||||
|
clean = form.is_valid()
|
||||||
|
value = form.cleaned_data['value']
|
||||||
|
hourvalue,value = divmod(value,60)
|
||||||
|
if hourvalue >= 24:
|
||||||
|
hourvalue = 23
|
||||||
|
rankingdurations.append(datetime.time(minute=value,hour=hourvalue))
|
||||||
|
else:
|
||||||
|
form = PredictedPieceForm()
|
||||||
|
|
||||||
|
|
||||||
|
predictions = []
|
||||||
|
cpredictions = []
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for rankingduration in rankingdurations:
|
||||||
|
t = 3600.*rankingduration.hour
|
||||||
|
t += 60.*rankingduration.minute
|
||||||
|
t += rankingduration.second
|
||||||
|
t += rankingduration.microsecond/1.e6
|
||||||
|
|
||||||
|
|
||||||
|
# CP model
|
||||||
|
pwr = p1[0]/(1+t/p1[2])
|
||||||
|
pwr += p1[1]/(1+t/p1[3])
|
||||||
|
|
||||||
|
if pwr <= 0:
|
||||||
|
pwr = 50.
|
||||||
|
|
||||||
|
if not np.isnan(pwr):
|
||||||
|
a = {
|
||||||
|
'duration':timedeltaconv(t),
|
||||||
|
'power':int(pwr)}
|
||||||
|
cpredictions.append(a)
|
||||||
|
|
||||||
|
|
||||||
|
del form.fields["pieceunit"]
|
||||||
|
|
||||||
|
messages.error(request,message)
|
||||||
|
return render(request, 'otwrankings.html',
|
||||||
|
{'rankingworkouts':theworkouts,
|
||||||
|
'interactiveplot':script,
|
||||||
|
'the_div':div,
|
||||||
|
'predictions':predictions,
|
||||||
|
'cpredictions':cpredictions,
|
||||||
|
'avgpower':avgpower,
|
||||||
|
'form':form,
|
||||||
|
'dateform':dateform,
|
||||||
|
'deltaform':deltaform,
|
||||||
|
'id': theuser,
|
||||||
|
'theuser':uu,
|
||||||
|
'startdate':startdate,
|
||||||
|
'enddate':enddate,
|
||||||
|
'teams':get_my_teams(request.user),
|
||||||
|
})
|
||||||
|
|
||||||
# Reload the workout and calculate the summary from the stroke data (lapIDx)
|
# Reload the workout and calculate the summary from the stroke data (lapIDx)
|
||||||
@login_required()
|
@login_required()
|
||||||
def workout_recalcsummary_view(request,id=0):
|
def workout_recalcsummary_view(request,id=0):
|
||||||
@@ -5207,6 +5519,11 @@ def workout_edit_view(request,id=0,message="",successmessage=""):
|
|||||||
privacy = request.POST['privacy']
|
privacy = request.POST['privacy']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
privacy = Workout.objects.get(id=id).privacy
|
privacy = Workout.objects.get(id=id).privacy
|
||||||
|
try:
|
||||||
|
rankingpiece = form.cleaned_data['rankingpiece']
|
||||||
|
except KeyError:
|
||||||
|
rankingpiece =- Workout.objects.get(id=id).rankingpiece
|
||||||
|
|
||||||
startdatetime = (str(date) + ' ' + str(starttime))
|
startdatetime = (str(date) + ' ' + str(starttime))
|
||||||
startdatetime = datetime.datetime.strptime(startdatetime,
|
startdatetime = datetime.datetime.strptime(startdatetime,
|
||||||
"%Y-%m-%d %H:%M:%S")
|
"%Y-%m-%d %H:%M:%S")
|
||||||
@@ -5223,6 +5540,7 @@ def workout_edit_view(request,id=0,message="",successmessage=""):
|
|||||||
row.distance = distance
|
row.distance = distance
|
||||||
row.boattype = boattype
|
row.boattype = boattype
|
||||||
row.privacy = privacy
|
row.privacy = privacy
|
||||||
|
row.rankingpiece = rankingpiece
|
||||||
try:
|
try:
|
||||||
row.save()
|
row.save()
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
Reference in New Issue
Block a user