Private
Public Access
1
0

Merge branch 'release/v14.72'

This commit is contained in:
Sander Roosendaal
2020-12-03 19:07:06 +01:00
23 changed files with 478 additions and 179 deletions

View File

@@ -1242,7 +1242,7 @@ def fetchcp(rower,theworkouts,table='cpdata'):
def create_row_df(r,distance,duration,startdatetime,workouttype='rower',
avghr=None,avgpwr=None,avgspm=None,
rankingpiece = False,
duplicate=False,
duplicate=False,rpe=-1,
title='Manual entry',notes='',weightcategory='hwt',
adaptiveclass='None'):
@@ -1351,6 +1351,7 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower',
workoutsource='unknown',
notes='', totaldist=0, totaltime=0,
rankingpiece=False,
rpe=-1,
duplicate=False,
summary='',
makeprivate=False,
@@ -1583,6 +1584,7 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower',
workoutsource=workoutsource,
rankingpiece=rankingpiece,
forceunit=forceunit,
rpe=rpe,
csvfilename=f2, notes=notes, summary=summary,
maxhr=maxhr, averagehr=averagehr,
startdatetime=workoutstartdatetime,
@@ -1810,6 +1812,7 @@ def new_workout_from_file(r, f2,
workoutsource=None,
title='Workout',
boattype='1x',
rpe=-1,
makeprivate=False,
notes='',
uploadoptions={'boattype':'1x','workouttype':'rower'}):
@@ -1944,6 +1947,7 @@ def new_workout_from_file(r, f2,
dosummary=dosummary,
workoutsource=workoutsource,
summary=summary,
rpe=rpe,
inboard=inboard, oarlength=oarlength,
title=title,
forceunit='N',

View File

@@ -13,6 +13,20 @@ from rowers.mytypes import otwtypes,otetypes,rowtypes
#p0 = [500,350,10,8000]
p0 = [190,200,33,16000]
# RPE to TSS
rpetotss = {
1:20,
2:30,
3:40,
4:50,
5:60,
6:70,
7:80,
8:100,
9:120,
10:140,
}
def updatecp(delta,cpvalues,r,workouttype='water'):
if workouttype in otwtypes:
p0 = r.p0

View File

@@ -245,17 +245,23 @@ class StandardsForm(forms.Form):
# The form used for uploading files
class DocumentsForm(forms.Form):
rpechoices = Workout.rpechoices
rpechoices = tuple([(-1,'---')]+list(rpechoices))
title = forms.CharField(required=False)
file = forms.FileField(required=False,
validators=[validate_file_extension])
workouttype = forms.ChoiceField(required=True,
choices=Workout.workouttypes)
choices=Workout.workouttypes,
label='Workout Type')
boattype = forms.ChoiceField(required=True,
choices=mytypes.boattypes,
label = "Boat Type")
rpe = forms.ChoiceField(required=False,
choices=rpechoices,
label='Rate of Perceived Exertion',initial=-1)
notes = forms.CharField(required=False,
widget=forms.Textarea)

View File

@@ -97,7 +97,7 @@ import rowers.c2stuff as c2stuff
from rowers.metrics import axes,axlabels,yaxminima,yaxmaxima,get_yaxminima,get_yaxmaxima
from rowers.utils import lbstoN
from rowers.datautils import p0
from rowers.datautils import p0,rpetotss
import rowers.datautils as datautils
from pandas.core.groupby.groupby import DataError
@@ -1650,18 +1650,30 @@ def getfatigues(
if metricchoice == 'rscore':
factor = 2.0
for i in range(nrdays):
for i in range(nrdays+1):
date = startdate+datetime.timedelta(days=i)
ws = Workout.objects.filter(user=user.rower,date=date,duplicate=False)
weight = 0
for w in ws:
weight += factor*getattr(w,metricchoice)
if getattr(w,metricchoice) == 0:
if metricchoice == 'rscore' and w.hrtss != 0:
if getattr(w,metricchoice) > 0:
weight += factor*getattr(w,metricchoice)
if getattr(w,metricchoice) <= 0:
if metricchoice == 'rscore' and w.hrtss > 0:
weight+= factor*w.hrtss
else:
elif metricchoice == 'rscore' and w.hrtss <= 0:
trimp,hrtss = dataprep.workout_trimp(w)
rscore,normp = dataprep.workout_rscore(w)
if w.rpe and w.rpe > 0:
dd = 3600*w.duration.hour+60*w.duration.minute+w.duration.second
dd = dd/3600
weight += factor*rpetotss[w.rpe]*dd
elif metricchoice == 'trimp' and w.trimp <= 0:
trimp,hrtss = dataprep.workout_trimp(w)
rscore,normp = dataprep.workout_rscore(w)
if w.rpe and w.rpe > 0:
dd = 3600*w.duration.hour+60*w.duration.minute+w.duration.second
dd = dd/3600
weight += 2*rpetotss[w.rpe]*dd
impulses.append(weight)
@@ -1680,6 +1692,7 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
metricchoice='trimp',doform=False,dofatigue=False):
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
TOOLS2 = 'box_zoom,hover'
fatigues = []
@@ -1713,7 +1726,9 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
'impulse':impulses,
})
endfitness = fitnesses[-1]
endfatigue = fatigues[-1]
endform = endfitness-endfatigue
if modelchoice == 'banister':
df['fatigue'] = k2*df['fatigue']
@@ -1777,18 +1792,18 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
fitlabel = 'PTE (fitness)'
fatiguelabel = 'NTE (fatigue)'
formlabel = 'Performance'
rightaxlabel = 'NTE'
if doform:
yaxlabel = 'PTE/Performance'
rightaxlabel = 'Performance'
if dofatigue:
yaxlabel = 'PTE/NTE'
else:
yaxlabel = 'PTE'
else:
fitlabel = 'Fitness'
fatiguelabel = 'Fatigue'
formlabel = 'Freshness'
rightaxlabel = 'Fatigue'
if doform:
yaxlabel = 'Fitness/Freshness'
rightaxlabel = 'Freshness'
if dofatigue:
yaxlabel = 'Fitness/Fatigue'
else:
yaxlabel = 'Fitness'
@@ -1829,13 +1844,6 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
plot.legend.location = "top_left"
#plot.xaxis.formatter = DatetimeTickFormatter(
# days=["%d %B %Y"],
# months=["%d %B %Y"],
# years=["%d %B %Y"],
# )
#plot.xaxis.major_label_orientation = pi/4
plot.sizing_mode = 'scale_both'
#plot.y_range = Range1d(0,1.5*max(df['testpower']))
@@ -1854,19 +1862,26 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
hover = plot.select(dict(type=HoverTool))
linked_crosshair = CrosshairTool(dimensions='height')
hover.tooltips = OrderedDict([
#(legend_label,'@testpower'),
('Date','@fdate'),
(fitlabel,'@fitness'),
(fatiguelabel,'@fatigue'),
(formlabel,'@form')
(fitlabel,'@fitness{int}'),
(fatiguelabel,'@fatigue{int}'),
(formlabel,'@form{int}'),
('Impulse','@impulse{int}')
])
plot2 = Figure(tools=TOOLS,x_axis_type='datetime',
plot2 = Figure(tools=TOOLS2,x_axis_type='datetime',
plot_width=900,plot_height=150,
toolbar_location=None,
toolbar_sticky=False)
plot2.x_range = xrange
plot2.y_range = Range1d(0,df['impulse'].max())
@@ -1876,6 +1891,9 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
plot2.yaxis.axis_label = 'Impulse'
plot2.xaxis.axis_label = 'Date'
plot.add_tools(linked_crosshair)
plot2.add_tools(linked_crosshair)
layout = layoutcolumn([plot,plot2])
layout.sizing_mode = 'stretch_both'
@@ -1892,7 +1910,7 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
)
)
return [script,div]
return [script,div,endfitness,endfatigue,endform]
def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None,

View File

@@ -881,11 +881,17 @@ class Rower(models.Model):
ep3 = models.FloatField(default=1.0,verbose_name="erg CP p4")
ecpratio = models.FloatField(default=1.0,verbose_name="erg CP fit ratio")
cprange = models.IntegerField(default=42,verbose_name="Range for calculation breakthrough workouts and fitness (CP)",
cprange = models.IntegerField(default=42,verbose_name="Range for calculation of breakthrough workouts and fitness (CP)",
choices=cppresets)
otwslack = models.IntegerField(default=0,verbose_name="OTW Power slack")
# performance manager stuff
kfit = models.IntegerField(default=42,verbose_name='Fitness Time Decay Constant (days)')
kfatigue = models.IntegerField(default=7,verbose_name='Fatigue Time Decay Constant (days)')
showfit = models.BooleanField(default=False)
showfresh = models.BooleanField(default=False)
pw_ut2 = models.IntegerField(default=124,verbose_name="UT2 Power")
pw_ut1 = models.IntegerField(default=171,verbose_name="UT1 Power")
pw_at = models.IntegerField(default=203,verbose_name="AT Power")
@@ -2898,6 +2904,19 @@ class Workout(models.Model):
privacychoices = mytypes.privacychoices
adaptivetypes = mytypes.adaptivetypes
boatbrands = mytypes.boatbrands
rpechoices = (
(0,'Not Specified'),
(1,'1 Very Easy (a walk in the park)'), # 20 TSS / hour
(2,'2 Easy (You breathe normally, it feels comfortable)'), # 30 TSS / hour
(3,'3 Somewhat easy (You can talk easily but did you notice the beautiful clouds?)'),
(4,'4 Moderate (You can talk in short spurts, breathing more labored, this feels just right)'), # 50 TSS/hour
(5,"5 (It's not that painful, you just don't want to be here all day.)"),
(6,'6 Somewhat Hard (You can say a few words if you need to)'), # 70 TSS / hour
(7,'7 Vigorous (This is starting to get painful)'),
(8,"8 Hard (You can barely talk, breathing heavily, hoping you won't have to this that long)"), # 100 TSS / hour
(9,'9 Very Hard (My goodness, please make it stop)'), # 120 TSS / hour
(10,'10 Max Effort (You can barely remember your name, you would rather rip out your toenails than go through this)') # 140 TSS / hour
)
user = models.ForeignKey(Rower,on_delete=models.CASCADE)
team = models.ManyToManyField(Team,blank=True)
@@ -2925,12 +2944,18 @@ class Workout(models.Model):
distance = models.IntegerField(default=0,blank=True)
duration = models.TimeField(blank=True)
dragfactor = models.IntegerField(default=0,blank=True)
# scores
trimp = models.IntegerField(default=-1,blank=True)
rscore = models.IntegerField(default=-1,blank=True)
hrtss = models.IntegerField(default=-1,blank=True)
normp = models.IntegerField(default=-1,blank=True)
normv = models.FloatField(default=-1,blank=True)
normw = models.FloatField(default=-1,blank=True)
goldmedalstandard = models.FloatField(default=-1,blank=True,verbose_name='Gold Medal Standard')
rpe = models.IntegerField(default=0,blank=True,choices=rpechoices,
verbose_name='Rate of Perceived Exertion')
weightcategory = models.CharField(
default="hwt",
max_length=10,
@@ -2958,7 +2983,6 @@ class Workout(models.Model):
inboard = models.FloatField(default=0.88)
oarlength = models.FloatField(default=2.89)
notes = models.CharField(blank=True,null=True,max_length=1000)
summary = models.TextField(blank=True)
privacy = models.CharField(default='visible',max_length=30,
@@ -3541,6 +3565,7 @@ class WorkoutForm(ModelForm):
'dragfactor',
'weightcategory',
'adaptiveclass',
'rpe',
'notes',
'rankingpiece',
'duplicate',
@@ -3618,7 +3643,7 @@ class RowerPowerForm(ModelForm):
class RowerCPForm(ModelForm):
class Meta:
model = Rower
fields = ['cprange']
fields = ['cprange','kfit','kfatigue']
# Form to set rower's Power zones, including test routines
# to enable consistency

View File

@@ -11,20 +11,29 @@
<ul class="main-content">
<!--
<li class="rounder">
<h2>Performance Manager</h2>
<a href="/rowers/performancemanager/">
<div class="vignet">
<img src="/static/img/perfmanager.png"
<img src="/static/img/PM.jpg"
alt="Performance Manager">
</div>
</a>
<p>
Manager Fitness, Fatigue and Freshness
Manage Fitness, Fatigue and Freshness
</p>
</li>
-->
<li class="rounder">
<h2>Critical Power</h2>
<a href="/rowers/user-analysis-select/cp/">
<div class="vignet">
<img src="/static/img/otwcp.png" alt="Critical Power">
</div>
</a>
<p>
Analyse power vs piece duration to make predictions.
</p>
</li>
<li class="rounder">
<h2>Compare Workouts</h2>
{% if team %}
@@ -53,17 +62,6 @@
Plot all strokes in a date range and analyze several parameters (Power, Pace, SPM, Heart Rate).
</p>
</li>
<li class="rounder">
<h2>Histogram</h2>
<a href="/rowers/user-analysis-select/histo/">
<div class="vignet">
<img src="/static/img/histogram.png" alt="Power Histogram">
</div>
</a>
<p>
Plot a histogram chart of one metric for all your strokes over a date range.
</p>
</li>
<li class="rounder">
<h2>Statistics</h2>
<a href="/rowers/user-analysis-select/stats/">
@@ -98,17 +96,6 @@
<p>
Select workouts and make X-Y charts of averages over various metrics
</p>
</li>
<li class="rounder">
<h2>Critical Power</h2>
<a href="/rowers/user-analysis-select/cp/">
<div class="vignet">
<img src="/static/img/otwcp.png" alt="Critical Power">
</div>
</a>
<p>
Analyse power vs piece duration to make predictions.
</p>
</li>
<li class="rounder">
<h2>Power Progress</h2>
@@ -144,6 +131,17 @@
<p>
Analyze your Concept2 ranking pieces over a date range and predict your pace on other pieces.
</p>
</li>
<li class="rounder">
<h2>Histogram</h2>
<a href="/rowers/user-analysis-select/histo/">
<div class="vignet">
<img src="/static/img/histogram.png" alt="Power Histogram">
</div>
</a>
<p>
Plot a histogram chart of one metric for all your strokes over a date range.
</p>
</li>
</ul>

View File

@@ -26,6 +26,16 @@
<i class="fas fa-watch-fitness fa-fw"></i>&nbsp;Fitness
</label>
<ul>
<li id="performancemanager">
<a href="/rowers/performancemanager/">
<i class="far fa-battery-bolt fa-fw"></i>&nbsp;Performance Manager
</a>
</li>
<li id="fitness-otecp">
<a href="/rowers/user-analysis-select/cp/">
<i class="fas fa-user-chart fa-fw"></i>&nbsp;CP Chart
</a>
</li>
<li id="compare">
{% if team %}
<a href="/rowers/user-analysis-select/compare/team/{{ team.id }}/">
@@ -41,11 +51,6 @@
<a href="/rowers/fitness-progress/">
<i class="far fa-watch-fitness fa-fw"></i>&nbsp;Power Progress
</a>
</li>
<li id="fitness-otecp">
<a href="/rowers/user-analysis-select/cp/">
<i class="fas fa-user-chart fa-fw"></i>&nbsp;CP Chart
</a>
</li>
<li id="fitness-ranking">
<a href="/rowers/ote-bests2/">

View File

@@ -308,6 +308,11 @@
<i class="fas fa-calculator-alt fa-fw"></i>&nbsp;OTW Power
</a>
</li>
<li id="advanced-otwpower">
<a href="/rowers/workout/{{ workout.id|encode }}/zeropower-confirm/">
<i class="fas fa-eraser fa-fw"></i>&nbsp;Remove Power Data
</a>
</li>
{% if 'speedcoach2' in workout.workoutsource %}
<li id="advanced-usegps">
<a href="/rowers/workout/{{ workout.id|encode }}/otwusegps/">

View File

@@ -4,72 +4,75 @@
{% block title %}Rowsandall Fitness Progress {% endblock %}
{% block main %}
<script src="https://code.jquery.com/jquery-1.9.1.min.js"></script>
{% block scripts %}
<script type='text/javascript'
src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'>
</script>
<script>
$(function() {
// Get the form fields and hidden div
var checkbox = $("#id_water");
var hidden = $("#id_waterboattype");
function submit_form() {
console.log("form changed");
var frm = $("#performanceform");
var data = new FormData(frm[0]);
// Hide the fields.
// Use JS to do this in case the user doesn't have JS
// enabled.
$.ajax({
url:"/rowers/performancemanager/user/{{ rower.user.id }}/",
type: "POST",
contentType: false,
processData: false,
data: data,
dataType: 'json',
hidden.hide();
// Setup an event listener for when the state of the
// checkbox changes.
checkbox.change(function() {
// Check to see if the checkbox is checked.
// If it is, show the fields and populate the input.
// If not, hide the fields.
if (checkbox.is(':checked')) {
// Show the hidden fields.
hidden.show();
} else {
// Make sure that the hidden fields are indeed
// hidden.
hidden.hide();
// You may also want to clear the value of the
// hidden fields here. Just in case somebody
// shows the fields, enters data to them and then
// unticks the checkbox.
//
// This would do the job:
//
// $("#hidden_field").val("");
success: function(data) {
console.log(data);
// var parsedJSON = $.parseJSON(data); //
$("#id_script").replaceWith('<div id="id_script">'+data.script+'</d'+'iv>');
$("#id_chart").replaceWith('<div id="id_chart">'+data.div+'</d'+'iv>');
$("#endfitness").html(data.endfitness)
$("#endfatigue").html(data.endfatigue)
$("#endform").html(data.endform)
console.log('done');
}
});
};
$(document).ready(function() {
var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
console.log("CSRF token",csrftoken);
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
$("#performanceform").on('change', function(evt) {
submit_form();
});
});
</script>
</script>
{% endblock %}
{% block main %}
<script src="https://cdn.pydata.org/bokeh/release/bokeh-2.2.3.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{{ chartscript |safe }}
<div id="id_script">
{{ chartscript |safe }}
</div>
<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, false, false);
};
window.addEventListener('resize', plotresizer);
plotresizer();
};
window.addEventListener('load', plot_resize_setup);
</script>
{% if rower.user %}
<h1>Fitness Progress for {{ rower.user.first_name }} </h1>
@@ -77,24 +80,68 @@
<h1>Fitness Progress for {{ user.first_name }} </h1>
{% endif %}
<p>
Text Explaining Performance Manager
</p>
<ul class="main-content">
<li class="grid_2">
<form enctype="multipart/form-data" action="/rowers/performancemanager/user/{{ rower.user.id }}/" method="post">
<li class="grid_4">
<div id="id_chart">
{{ the_div|safe }}
</div>
</li>
<li class="grid_1">
<form id="performanceform" enctype="multipart/form-data" method="post">
<table>
{{ form.as_table }}
</table>
{% csrf_token %}
<input name='daterange' class="button green" type="submit" value="Submit">
<input name='form' class="button" type="submit" value="Submit">
</form>
</li>
<li class="grid_4">
{{ the_div|safe }}
<li class="grid_2">
<p>
The Performance Manager on this page is based on scientific literature
on modeling human performance. A good description can be found
<a href="https://fellrnr.com/wiki/Modeling_Human_Performance" target="_">here</a>.
Every person is different. This statement has implications for training and modeling of
training impact. Each person differs in their response to training, diet, rest, or
other factors. You are an experiment of one, a unique person and all models are
wrong (but some are useful). Be prepared to learn from this chart, to experiment
and perhaps to go against established advice.
</p>
<p>
The chart models your performance over a time period that you can set with the form
on the left. The model balances out after a few weeks of regular training, so don't
make this chart shorter than a few months.
</p>
<p>
For this chart to reflect your fitness and freshness, it is important to have all workouts on
Rowsandall.com. You can automatically import workouts from other fitness platforms. Change
your <a href="/rowers/me/exportsettings/">Import and Export Settings here.</a>
</p>
<p>
The time constants used in generating this performance chart were
a fitness decay constant of {{ rower.kfit }} days
and a fatigue decay constant of {{ rower.kfatigue }} days.
You can change these values in your <a href="/rowers/me/preferences/">Profile Settings</a>.
</p>
</li>
<li class="grid_1">
<div class="rounder">
<p>
<table width="100%">
<tbody>
<tr>
<td><h2>Fitness</h2></td><td><h2><span id="endfitness">{{ endfitness }}</span></h2></td>
</tr>
<tr>
<td><h2>Fatigue</h2></td><td><h2><span id="endfatigue"> {{ endfatigue }}</span></h2></td>
</tr>
<tr>
<td><h2>Freshness</h2></td><td><h2><span id="endform"> {{ endform }}</span></h2></td>
</tr>
</tbody>
</table>
</p>
</div>
</li>
</ul>
@@ -102,6 +149,8 @@
{% endblock %}
{% block sidebar %}
{% include 'menu_analytics.html' %}
{% endblock %}

View File

@@ -99,11 +99,14 @@
</li>
<li class="grid_2">
<form enctype="multipart/form-data" action="" method="post">
<h2>Range over which Critical Power rolling data (fitness) are calculated</h2>
<p>Use this form to change the number of weeks over which Rowsandall
keeps track of your Critical Power. </p>
<p>A shorter range will give you notifications of fitness improvements more often,
but will also quickly forget those breakthrough workouts.</p>
<h2>Fitness and Performance Manager Settings</h2>
<p>Use this form to change the parameters affecting your performance management.</p>
<p>A shorter range for the CP calculations
will give you notifications of fitness improvements more often,
but will also quickly forget those breakthrough workouts. Shorter decay
time constants for the performance manager will model a quicker building up
of fitness and a faster recovery. Recommended values are 42 days (fitness)
and 7 days (fatigue).</p>
<table>
{{ cpform.as_table }}
</table>

View File

@@ -65,7 +65,7 @@
{% csrf_token %}
</p>
<p>
<input name='form' class='green button' type='submit' value="Submit">
<input name='form' class='button' type='submit' value="Submit">
</form>
</p>
</li>

View File

@@ -0,0 +1,41 @@
{% extends "newbase.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}Change Workout {% endblock %}
{% block main %}
<h1>Delete Power?</h1>
<ul class="main-content">
<li class="grid_2">
<p>
This will delete the power data for the following workout:
</p>
<table width=100%>
<tr>
<th>Name:</th><td>{{ workout.name }}</td>
</tr><tr>
<th>Date:</th><td>{{ workout.date }}</td>
</tr><tr>
<th>Time:</th><td>{{ workout.starttime }}</td>
</tr><tr>
<th>Distance:</th><td>{{ workout.distance }}m</td>
</tr><tr>
<th>Duration:</th><td>{{ workout.duration |durationprint:"%H:%M:%S.%f" }}</td>
</tr>
</table>
</li>
<li class="grid_2">
<form action="/rowers/workout/{{ workout.id|encode }}/zeropower/" method="get">
{% csrf_token %}
<input type="submit" value="Confirm">
</form>
</li>
</ul>
{% endblock %}
{% block sidebar %}
{% include 'menu_workout.html' %}
{% endblock %}

View File

@@ -195,6 +195,7 @@ class CPChartTest(TestCase):
'duplicate': False,
'avghr': '160',
'avgpwr': 0,
'rpe':4,
'avgspm': 40,
}

View File

@@ -4,6 +4,7 @@ from __future__ import print_function
from __future__ import unicode_literals
#from __future__ import print_function
from .statements import *
from django.db import transaction
@override_settings(TESTING=True)
class EmailUpload(TestCase):
@@ -62,6 +63,7 @@ workout run
'workouttype':'rower',
'boattype': '1x',
'notes': 'aap noot mies',
'rpe':1,
'make_plot': False,
'upload_to_C2': False,
'plottype': 'timeplot',
@@ -84,27 +86,29 @@ workout run
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db)
def test_uploadapi2(self,mocked_sqlalchemy,mocked_getsmallrowdata_db):
form_data = {
'title': 'test',
'workouttype':'rower',
'boattype': '1x',
'notes': 'aap noot mies',
'make_plot': False,
'upload_to_C2': False,
'plottype': 'timeplot',
'file': 'media/mailbox_attachments/colin3.csv',
'secret': settings.UPLOAD_SERVICE_SECRET,
'useremail': 'sander2@ds.nl',
}
with transaction.atomic():
form_data = {
'title': 'test',
'workouttype':'rower',
'boattype': '1x',
'notes': 'aap noot mies',
'make_plot': False,
'upload_to_C2': False,
'plottype': 'timeplot',
'rpe':4,
'file': 'media/mailbox_attachments/colin3.csv',
'secret': settings.UPLOAD_SERVICE_SECRET,
'useremail': 'sander2@ds.nl',
}
url = reverse('workout_upload_api')
response = self.c.post(url,form_data,HTTP_HOST='127.0.0.1:4533')
self.assertEqual(response.status_code,200)
url = reverse('workout_upload_api')
response = self.c.post(url,form_data,HTTP_HOST='127.0.0.1:4533')
self.assertEqual(response.status_code,200)
# should also test if workout is created
w = Workout.objects.get(id=1)
self.assertEqual(w.name,'test')
self.assertEqual(w.notes,'aap noot mies')
# should also test if workout is created
w = Workout.objects.get(id=1)
self.assertEqual(w.name,'test')
self.assertEqual(w.notes,'aap noot mies')
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db)

View File

@@ -32,6 +32,7 @@ class DataTest(TestCase):
'weightcategory':'lwt',
'adaptiveclass': 'PR1',
'workouttype':'water',
'rpe':1,
'boattype':'1x',
'private':False,
}

View File

@@ -55,6 +55,7 @@ class ForceUnits(TestCase):
'make_plot':False,
'upload_to_c2':False,
'plottype':'timeplot',
'rpe': 1,
'file': f,
}
@@ -100,6 +101,7 @@ class ForceUnits(TestCase):
'boattype':'1x',
'notes':'aap noot mies',
'make_plot':False,
'rpe': 1,
'upload_to_c2':False,
'plottype':'timeplot',
'file': f,
@@ -131,6 +133,7 @@ class ForceUnits(TestCase):
file_data = {'file': f}
form_data = {
'title':'test',
'rpe':1,
'workouttype':'rower',
'boattype':'1x',
'notes':'aap noot mies',

View File

@@ -6,6 +6,7 @@ from __future__ import unicode_literals
#from __future__ import print_function
from .statements import *
nu = datetime.datetime.now()
from django.db import transaction
from rowers.views import add_defaultfavorites
@@ -50,7 +51,9 @@ class ViewTest(TestCase):
'workouttype':'rower',
'boattype':'1x',
'notes':'aap noot mies',
'rpe':4,
'make_plot':False,
'rpe':6,
'upload_to_c2':False,
'plottype':'timeplot',
'file': f,
@@ -100,6 +103,7 @@ class ViewTest(TestCase):
'adaptiveclass':'PR1',
'workouttype':'rower',
'boattype':'1x',
'rpe':4,
'dragfactor':'112',
'private':True,
'notes':'noot mies',
@@ -141,6 +145,7 @@ class ViewTest(TestCase):
'workouttype':'rower',
'boattype':'1x',
'notes':'aap noot mies',
'rpe':6,
'make_plot':False,
'upload_to_C2':False,
'upload_to_Strava':False,
@@ -193,6 +198,7 @@ class ViewTest(TestCase):
'upload_to_c2':False,
'plottype':'timeplot',
'file': f,
'rpe':6,
}
form = DocumentsForm(form_data,file_data)
@@ -229,6 +235,7 @@ class ViewTest(TestCase):
'upload_to_c2':False,
'plottype':'timeplot',
'file': f,
'rpe':6,
}
form = DocumentsForm(form_data,file_data)
@@ -263,6 +270,7 @@ class ViewTest(TestCase):
'upload_to_c2':False,
'plottype':'timeplot',
'file': f,
'rpe':6,
}
form = DocumentsForm(form_data,file_data)
@@ -313,6 +321,7 @@ class ViewTest(TestCase):
'upload_to_c2':False,
'plottype':'timeplot',
'file': f,
'rpe':6,
}
form = DocumentsForm(form_data,file_data)
@@ -350,6 +359,7 @@ class ViewTest(TestCase):
'upload_to_c2':False,
'plottype':'timeplot',
'file': f,
'rpe':6,
}
form = DocumentsForm(form_data,file_data)
@@ -389,6 +399,7 @@ class ViewTest(TestCase):
'upload_to_c2':False,
'plottype':'timeplot',
'file': f,
'rpe':6,
}
form = DocumentsForm(form_data,file_data)
@@ -424,6 +435,7 @@ class ViewTest(TestCase):
'make_plot':False,
'upload_to_c2':False,
'plottype':'timeplot',
'rpe':1,
'file': f,
}
@@ -459,6 +471,7 @@ class ViewTest(TestCase):
'boattype':'1x',
'notes':'aap noot mies',
'make_plot':False,
'rpe':1,
'upload_to_c2':False,
'plottype':'timeplot',
'file': f,
@@ -531,6 +544,7 @@ class ViewTest(TestCase):
file_data = {'file': f}
form_data = {
'title':'test',
'rpe':1,
'workouttype':'water',
'boattype':'1x',
'notes':'aap noot mies',
@@ -570,6 +584,7 @@ class ViewTest(TestCase):
'make_plot':False,
'upload_to_c2':False,
'plottype':'timeplot',
'rpe':4,
'file': f,
}
@@ -623,35 +638,37 @@ class ViewTest(TestCase):
@patch('rowers.dataprep.create_engine')
def test_upload_view_RP_interval(self, mocked_sqlalchemy):
self.c.login(username='john',password='koeinsloot')
with transaction.atomic():
self.c.login(username='john',password='koeinsloot')
filename = 'rowers/tests/testdata/RP_interval.csv'
f = open(filename,'rb')
file_data = {'file': f}
form_data = {
'title':'test',
'workouttype':'rower',
'boattype':'1x',
'notes':'aap noot mies',
'make_plot':False,
'upload_to_c2':False,
'plottype':'timeplot',
'file': f,
}
filename = 'rowers/tests/testdata/RP_interval.csv'
f = open(filename,'rb')
file_data = {'file': f}
form_data = {
'title':'test',
'workouttype':'rower',
'boattype':'1x',
'notes':'aap noot mies',
'make_plot':False,
'upload_to_c2':False,
'plottype':'timeplot',
'rpe':1,
'file': f,
}
form = DocumentsForm(form_data,file_data)
form = DocumentsForm(form_data,file_data)
response = self.c.post('/rowers/workout/upload/', form_data, follow=True)
self.assertRedirects(response, expected_url='/rowers/workout/'+encoded1+'/edit/',
response = self.c.post('/rowers/workout/upload/', form_data, follow=True)
self.assertRedirects(response, expected_url='/rowers/workout/'+encoded1+'/edit/',
status_code=302,target_status_code=200)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.status_code, 200)
w = Workout.objects.get(id=1)
f_to_be_deleted = w.csvfilename
try:
os.remove(f_to_be_deleted+'.gz')
except (FileNotFoundError,OSError):
pass
w = Workout.objects.get(id=1)
f_to_be_deleted = w.csvfilename
try:
os.remove(f_to_be_deleted+'.gz')
except (FileNotFoundError,OSError):
pass
@@ -667,6 +684,7 @@ class ViewTest(TestCase):
'workouttype':'rower',
'boattype':'1x',
'notes':'aap noot mies',
'rpe':4,
'make_plot':False,
'upload_to_c2':False,
'plottype':'timeplot',
@@ -699,6 +717,7 @@ class ViewTest(TestCase):
'workouttype':'rower',
'boattype':'1x',
'notes':'aap noot mies',
'rpe':4,
'make_plot':False,
'upload_to_c2':False,
'plottype':'timeplot',
@@ -731,6 +750,7 @@ class ViewTest(TestCase):
'workouttype':'rower',
'boattype':'1x',
'notes':'aap noot mies',
'rpe':4,
'make_plot':False,
'upload_to_c2':False,
'plottype':'timeplot',
@@ -762,6 +782,7 @@ class ViewTest(TestCase):
'title':'test',
'workouttype':'rower',
'boattype':'1x',
'rpe':4,
'notes':'aap noot mies',
'make_plot':False,
'upload_to_c2':False,

View File

@@ -10,7 +10,9 @@ nu = datetime.datetime.now()
tested = [
'/rowers/me/delete/'
'/rowers/me/delete/',
'/rowers/performancemanager/'
]
#@pytest.mark.django_db
@@ -76,7 +78,7 @@ class URLTests(TestCase):
'/rowers/agegroupcp/30/1/',
'/rowers/agegrouprecords/male/hwt/',
'/rowers/agegrouprecords/male/hwt/2000m/',
'/rowers/agegrouprecords/male/hwt/2000min/',
'/rowers/agegrouprecords/male/hwt/30min/',
'/rowers/ajax_agegroup/45/hwt/male/1/',
'/rowers/analysis/',
'/rowers/analysis/user/1/',
@@ -240,6 +242,8 @@ class URLTests(TestCase):
# '/rowers/workouts-join-select/2016-01-01/2016-12-31/',
]
# urlstotest = ['/rowers/createplan/user/1/']
lijst = []

View File

@@ -437,6 +437,8 @@ urlpatterns = [
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/stats/$',views.workout_stats_view,name='workout_stats_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/data/$',views.workout_data_view,
name='workout_data_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/zeropower-confirm/$',views.remove_power_confirm_view,
name='remove_power_confirm_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/zeropower/$',views.remove_power_view,
name='remove_power_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/otwsetpower/$',views.workout_otwsetpower_view,name='workout_otwsetpower_view'),

View File

@@ -6,6 +6,7 @@ from __future__ import unicode_literals, absolute_import
from rowers.views.statements import *
import collections
import simplejson
from jinja2 import Template,Environment,FileSystemLoader
def floatformat(x,prec=2):
@@ -1547,17 +1548,21 @@ def performancemanager_view(request,userid=0,mode='rower',
startdate=timezone.now()-timezone.timedelta(days=365),
enddate=timezone.now()):
is_ajax = False
if request.is_ajax():
is_ajax = True
therower = getrequestrower(request,userid=userid)
theuser = therower.user
kfitness = 42
kfatigue = 7
kfitness = therower.kfit
kfatigue = therower.kfatigue
fitnesstest = 20
metricchoice = 'trimp'
modelchoice = 'tsb'
usefitscore = False
doform = False
dofatigue = False
doform = therower.showfresh
dofatigue = therower.showfit
if request.method == 'POST':
form = PerformanceManagerForm(request.POST)
@@ -1567,10 +1572,13 @@ def performancemanager_view(request,userid=0,mode='rower',
metricchoice = form.cleaned_data['metricchoice']
dofatigue = form.cleaned_data['dofatigue']
doform = form.cleaned_data['doform']
therower.showfresh = doform
therower.showfatigue = dofatigue
therower.save()
else:
form = PerformanceManagerForm()
script, thediv = performance_chart(
script, thediv, endfitness, endfatigue, endform = performance_chart(
theuser,startdate=startdate,enddate=enddate,
kfitness = kfitness,
kfatigue = kfatigue,
@@ -1590,6 +1598,17 @@ def performancemanager_view(request,userid=0,mode='rower',
}
]
if is_ajax:
response = json.dumps({
'script':script,
'div':thediv,
'endform':int(endform),
'endfitness': int(endfitness),
'endfatigue': int(endfatigue),
})
return(HttpResponse(response,content_type='application/json'))
return render(request,'performancemanager.html',
{
@@ -1600,6 +1619,9 @@ def performancemanager_view(request,userid=0,mode='rower',
'the_div':thediv,
'mode':mode,
'form':form,
'endfitness':int(endfitness),
'endfatigue':int(endfatigue),
'endform':int(endform),
})

View File

@@ -570,9 +570,13 @@ def rower_prefs_view(request,userid=0,message=""):
if cpform.is_valid():
cd = cpform.cleaned_data
cprange = cd['cprange']
kfit = cd['kfit']
kfatigue = cd['kfatigue']
r.cprange = cprange
r.kfit = kfit
r.kfatigue = kfatigue
r.save()
messages.info(request,'Updated CP range value')
messages.info(request,'Updated CP range and time decay constants')
success = dataprep.update_rolling_cp(r,mytypes.otwtypes,'water')
success = dataprep.update_rolling_cp(r,mytypes.otetypes,'erg')

View File

@@ -585,12 +585,19 @@ def addmanual_view(request,raceid=0):
weightcategory = form.cleaned_data['weightcategory']
adaptiveclass = form.cleaned_data['adaptiveclass']
distance = form.cleaned_data['distance']
try:
rpe = form.cleaned_data['rpe']
if not rpe:
rpe = -1
except KeyError:
rpe = -1
notes = form.cleaned_data['notes']
thetimezone = form.cleaned_data['timezone']
private = form.cleaned_data['private']
avghr = metricsform.cleaned_data['avghr']
avgpwr = metricsform.cleaned_data['avgpwr']
avgspm = metricsform.cleaned_data['avgspm']
try:
ps = form.cleaned_data['plannedsession']
except KeyError:
@@ -632,6 +639,7 @@ def addmanual_view(request,raceid=0):
id,message = dataprep.create_row_df(r,
distance,
duration,startdatetime,
rpe=rpe,
weightcategory=weightcategory,
adaptiveclass=adaptiveclass,
avghr=avghr,
@@ -657,6 +665,7 @@ def addmanual_view(request,raceid=0):
w.notes = notes
w.plannedsession = ps
w.name = name
w.rpe = rpe
w.workouttype = workouttype
w.boattype = boattype
w.save()
@@ -1274,6 +1283,36 @@ def workouts_join_select(request,
'teams':get_my_teams(request.user),
})
@login_required()
def remove_power_confirm_view(request,id=0):
r = getrower(request.user)
workout = get_workout_by_opaqueid(request,id)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,encoder.encode_hex(workout.id)),
'name': encoder.encode_hex(workout.id)
},
{ 'url':reverse('remove_power_confirm_view',
kwargs={'id':encoder.encode_hex(workout.id)}),
'name': 'Delete'
}
]
return render(request,
'workout_remove_power_confirm.html',
{
'workout':workout,
'rower':r,
'breadcrumbs':breadcrumbs,
})
@login_required()
def remove_power_view(request,id=0):
r = getrower(request.user)
@@ -1299,6 +1338,11 @@ def remove_power_view(request,id=0):
res = dataprep.dataprep(row.df, id=workout.id)
cpdf,delta,cpvalues = dataprep.setcp(workout)
workout.normp = 0
workout.rscore = 0
workout.save()
dataprep.initiate_cp(r)
@@ -4320,6 +4364,12 @@ def workout_edit_view(request,id=0,message="",successmessage=""):
notes = form.cleaned_data['notes']
newdragfactor = form.cleaned_data['dragfactor']
thetimezone = form.cleaned_data['timezone']
try:
rpe = form.cleaned_data['rpe']
if not rpe:
rpe = -1
except KeyError:
rpe = -1
try:
ps = form.cleaned_data['plannedsession']
@@ -4386,6 +4436,7 @@ def workout_edit_view(request,id=0,message="",successmessage=""):
row.weightcategory = weightcategory
row.adaptiveclass = adaptiveclass
row.notes = notes
row.rpe = rpe
row.duration = duration
row.distance = distance
row.boattype = boattype
@@ -4829,6 +4880,10 @@ def workout_upload_api(request):
t = form.cleaned_data['title']
boattype = form.cleaned_data['boattype']
workouttype = form.cleaned_data['workouttype']
try:
rpe = form.cleaned_data['rpe']
except KeyError:
rpe = -1
if rowerform.is_valid():
u = rowerform.cleaned_data['user']
r = getrower(u)
@@ -4884,6 +4939,7 @@ def workout_upload_api(request):
boattype=boattype,
makeprivate=makeprivate,
title = t,
rpe=rpe,
notes=notes,
uploadoptions=post_data,
)
@@ -5004,6 +5060,13 @@ def workout_upload_view(request,
except KeyError:
boattype = '1x'
try:
rpe = docformoptions['rpe']
if not rpe:
rpe = -1
except KeyError:
rpe = -1
try:
notes = docformoptions['notes']
except KeyError:
@@ -5081,6 +5144,10 @@ def workout_upload_view(request,
t = form.cleaned_data['title']
workouttype = form.cleaned_data['workouttype']
boattype = form.cleaned_data['boattype']
try:
rpe = form.cleaned_data['rpe']
except KeyError:
rpe = -1
request.session['docformoptions'] = {
'workouttype':workouttype,
@@ -5121,6 +5188,7 @@ def workout_upload_view(request,
'upload_to_TrainingPeaks':upload_to_tp,
'landingpage':landingpage,
'boattype': boattype,
'rpe':rpe,
'workouttype': workouttype,
}
@@ -5136,6 +5204,7 @@ def workout_upload_view(request,
workouttype=workouttype,
workoutsource=workoutsource,
boattype=boattype,
rpe=rpe,
makeprivate=makeprivate,
title = t,
notes=notes,

BIN
static/img/PM.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB