diff --git a/rowers/dataprep.py b/rowers/dataprep.py
index 0ea6635a..1f6a27ba 100644
--- a/rowers/dataprep.py
+++ b/rowers/dataprep.py
@@ -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',
diff --git a/rowers/datautils.py b/rowers/datautils.py
index 7f8c1af4..c19e9b6b 100644
--- a/rowers/datautils.py
+++ b/rowers/datautils.py
@@ -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
diff --git a/rowers/forms.py b/rowers/forms.py
index 26defb73..6d074114 100644
--- a/rowers/forms.py
+++ b/rowers/forms.py
@@ -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)
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index 376e6c43..14711cc9 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -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,
diff --git a/rowers/models.py b/rowers/models.py
index f38ccc04..8a6663a3 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -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
diff --git a/rowers/templates/analysis.html b/rowers/templates/analysis.html
index 593b55ce..e5f11295 100644
--- a/rowers/templates/analysis.html
+++ b/rowers/templates/analysis.html
@@ -11,20 +11,29 @@
-
+
+ Critical Power
+
+
+
+
+
+
+ Analyse power vs piece duration to make predictions.
+
+
Compare Workouts
{% if team %}
@@ -53,17 +62,6 @@
Plot all strokes in a date range and analyze several parameters (Power, Pace, SPM, Heart Rate).
-
- Histogram
-
-
-
-
-
-
- Plot a histogram chart of one metric for all your strokes over a date range.
-
-
Statistics
@@ -98,17 +96,6 @@
Select workouts and make X-Y charts of averages over various metrics
-
-
- Critical Power
-
-
-
-
-
-
- Analyse power vs piece duration to make predictions.
-
Power Progress
@@ -144,6 +131,17 @@
Analyze your Concept2 ranking pieces over a date range and predict your pace on other pieces.
+
+
+ Histogram
+
+
+
+
+
+
+ Plot a histogram chart of one metric for all your strokes over a date range.
+
diff --git a/rowers/templates/menu_analytics.html b/rowers/templates/menu_analytics.html
index 28cc9eb4..021950b7 100644
--- a/rowers/templates/menu_analytics.html
+++ b/rowers/templates/menu_analytics.html
@@ -26,6 +26,16 @@
Fitness
+
+
+ Performance Manager
+
+
+
+
+ CP Chart
+
+
{% if team %}
@@ -41,11 +51,6 @@
Power Progress
-
-
-
- CP Chart
-
diff --git a/rowers/templates/menu_workout.html b/rowers/templates/menu_workout.html
index cf9e7c26..28f1292c 100644
--- a/rowers/templates/menu_workout.html
+++ b/rowers/templates/menu_workout.html
@@ -308,6 +308,11 @@
OTW Power
+
+
+ Remove Power Data
+
+
{% if 'speedcoach2' in workout.workoutsource %}
diff --git a/rowers/templates/performancemanager.html b/rowers/templates/performancemanager.html
index ddafcfa8..4136e750 100644
--- a/rowers/templates/performancemanager.html
+++ b/rowers/templates/performancemanager.html
@@ -4,72 +4,75 @@
{% block title %}Rowsandall Fitness Progress {% endblock %}
-{% block main %}
-
+{% block scripts %}
+
+
+{% endblock %}
+
+{% block main %}
+
-{{ chartscript |safe }}
+
+ {{ chartscript |safe }}
+
+
-
{% if rower.user %}
Fitness Progress for {{ rower.user.first_name }}
@@ -77,24 +80,68 @@
Fitness Progress for {{ user.first_name }}
{% endif %}
-
- Text Explaining Performance Manager
-
-
@@ -102,6 +149,8 @@
{% endblock %}
+
+
{% block sidebar %}
{% include 'menu_analytics.html' %}
{% endblock %}
diff --git a/rowers/templates/rower_preferences.html b/rowers/templates/rower_preferences.html
index d15b1e69..434336f4 100644
--- a/rowers/templates/rower_preferences.html
+++ b/rowers/templates/rower_preferences.html
@@ -99,11 +99,14 @@
- Range over which Critical Power rolling data (fitness) are calculated
- Use this form to change the number of weeks over which Rowsandall
- keeps track of your Critical Power.
- A shorter range will give you notifications of fitness improvements more often,
- but will also quickly forget those breakthrough workouts.
+ Fitness and Performance Manager Settings
+ Use this form to change the parameters affecting your performance management.
+ 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).
diff --git a/rowers/templates/virtualevents.html b/rowers/templates/virtualevents.html
index e2b06add..9fb5856f 100644
--- a/rowers/templates/virtualevents.html
+++ b/rowers/templates/virtualevents.html
@@ -65,7 +65,7 @@
{% csrf_token %}
-
+
diff --git a/rowers/templates/workout_remove_power_confirm.html b/rowers/templates/workout_remove_power_confirm.html
new file mode 100644
index 00000000..a6b3b9e0
--- /dev/null
+++ b/rowers/templates/workout_remove_power_confirm.html
@@ -0,0 +1,41 @@
+{% extends "newbase.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block title %}Change Workout {% endblock %}
+
+{% block main %}
+ Delete Power?
+
+
+
+{% endblock %}
+
+{% block sidebar %}
+{% include 'menu_workout.html' %}
+{% endblock %}
diff --git a/rowers/tests/test_cpchart.py b/rowers/tests/test_cpchart.py
index 4ab3a2bb..020654c4 100644
--- a/rowers/tests/test_cpchart.py
+++ b/rowers/tests/test_cpchart.py
@@ -195,6 +195,7 @@ class CPChartTest(TestCase):
'duplicate': False,
'avghr': '160',
'avgpwr': 0,
+ 'rpe':4,
'avgspm': 40,
}
diff --git a/rowers/tests/test_emails.py b/rowers/tests/test_emails.py
index eca8a37c..7edbd7e1 100644
--- a/rowers/tests/test_emails.py
+++ b/rowers/tests/test_emails.py
@@ -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)
diff --git a/rowers/tests/test_settings.py b/rowers/tests/test_settings.py
index b9a44653..87b440fb 100644
--- a/rowers/tests/test_settings.py
+++ b/rowers/tests/test_settings.py
@@ -32,6 +32,7 @@ class DataTest(TestCase):
'weightcategory':'lwt',
'adaptiveclass': 'PR1',
'workouttype':'water',
+ 'rpe':1,
'boattype':'1x',
'private':False,
}
diff --git a/rowers/tests/test_units.py b/rowers/tests/test_units.py
index 77827338..df9658cc 100644
--- a/rowers/tests/test_units.py
+++ b/rowers/tests/test_units.py
@@ -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',
diff --git a/rowers/tests/test_uploads.py b/rowers/tests/test_uploads.py
index 21e03df9..d9fbbac5 100644
--- a/rowers/tests/test_uploads.py
+++ b/rowers/tests/test_uploads.py
@@ -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,
diff --git a/rowers/tests/test_urls.py b/rowers/tests/test_urls.py
index e3be6bac..812816ee 100644
--- a/rowers/tests/test_urls.py
+++ b/rowers/tests/test_urls.py
@@ -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 = []
diff --git a/rowers/urls.py b/rowers/urls.py
index 565dde1c..23f431d9 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -437,6 +437,8 @@ urlpatterns = [
re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/stats/$',views.workout_stats_view,name='workout_stats_view'),
re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/data/$',views.workout_data_view,
name='workout_data_view'),
+ re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/zeropower-confirm/$',views.remove_power_confirm_view,
+ name='remove_power_confirm_view'),
re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/zeropower/$',views.remove_power_view,
name='remove_power_view'),
re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/otwsetpower/$',views.workout_otwsetpower_view,name='workout_otwsetpower_view'),
diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py
index 6f28e61e..757444a5 100644
--- a/rowers/views/analysisviews.py
+++ b/rowers/views/analysisviews.py
@@ -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),
})
diff --git a/rowers/views/userviews.py b/rowers/views/userviews.py
index afa15a28..51d2fc4c 100644
--- a/rowers/views/userviews.py
+++ b/rowers/views/userviews.py
@@ -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')
diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py
index 1bd74872..e90b883d 100644
--- a/rowers/views/workoutviews.py
+++ b/rowers/views/workoutviews.py
@@ -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,
diff --git a/static/img/PM.jpg b/static/img/PM.jpg
new file mode 100644
index 00000000..24bd400a
Binary files /dev/null and b/static/img/PM.jpg differ