From cc57eb2b821693cebae031989c07d2f7323abb9c Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Wed, 31 May 2017 11:21:47 +0200
Subject: [PATCH 01/11] added workoutsource to email processing
---
rowers/mailprocessing.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/rowers/mailprocessing.py b/rowers/mailprocessing.py
index 2e4c1208..bb346b73 100644
--- a/rowers/mailprocessing.py
+++ b/rowers/mailprocessing.py
@@ -237,6 +237,7 @@ def make_new_workout_from_email(rr,f2,name,cntr=0):
inboard=inboard,
oarlength=oarlength,
title=name,
+ workoutsource=fileformat,
notes='imported through email')
From 79ab50e29a3b61305600ffff14cf05a60b7f31e8 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Thu, 1 Jun 2017 16:19:56 +0200
Subject: [PATCH 02/11] no consistency checks on data fusion
---
rowers/dataprep.py | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/rowers/dataprep.py b/rowers/dataprep.py
index 9744c77a..18516db8 100644
--- a/rowers/dataprep.py
+++ b/rowers/dataprep.py
@@ -398,7 +398,8 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower',
notes='',totaldist=0,totaltime=0,
summary='',
makeprivate=False,
- oarlength=2.89,inboard=0.88):
+ oarlength=2.89,inboard=0.88,
+ consistencychecks=True):
message = None
powerperc = 100*np.array([r.pw_ut2,
r.pw_ut1,
@@ -417,9 +418,12 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower',
for key,value in checks.iteritems():
if not value:
allchecks = 0
- a_messages.error(r.user,'Failed consistency check: '+key+', autocorrected')
+ if consistencychecks:
+ 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()
@@ -780,7 +784,8 @@ def new_workout_from_df(r,df,
oarlength=oarlength,
inboard=inboard,
makeprivate=makeprivate,
- dosmooth=False)
+ dosmooth=False,
+ consistencychecks=False)
return (id,message)
From dae5897b6a69cde3fac2f6bab461ccb1e40858f3 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Thu, 1 Jun 2017 16:21:58 +0200
Subject: [PATCH 03/11] duplicates are marked private
---
rowers/dataprep.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/rowers/dataprep.py b/rowers/dataprep.py
index 18516db8..2cf91786 100644
--- a/rowers/dataprep.py
+++ b/rowers/dataprep.py
@@ -532,6 +532,7 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower',
user=r)
if (len(ws) != 0):
message = "Warning: This workout probably already exists in the database"
+ privacy = 'private'
# checking for inf values
totaldist = np.nan_to_num(totaldist)
From 4e8b7e02b198ee016af602cba62ddf54a5bb3dbb Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Fri, 2 Jun 2017 20:28:12 +0200
Subject: [PATCH 04/11] otw cp stuff not finished
---
gpx.xsd | 1 +
rowers/.#views.py | 1 +
rowers/models.py | 5 +-
rowers/stravastuff.py | 28 ++--
rowers/urls.py | 1 +
rowers/views.py | 348 +++++++++++++++++++++++++++++++++++++++++-
6 files changed, 369 insertions(+), 15 deletions(-)
create mode 100644 gpx.xsd
create mode 100644 rowers/.#views.py
diff --git a/gpx.xsd b/gpx.xsd
new file mode 100644
index 00000000..2237442b
--- /dev/null
+++ b/gpx.xsd
@@ -0,0 +1 @@
+
GPX schema version 1.1 - For more information on GPX and this schema, visit http://www.topografix.com/gpx.asp
GPX uses the following conventions: all coordinates are relative to the WGS84 datum. All measurements are in metric units.
GPX is the root element in the XML file.
GPX documents contain a metadata header, followed by waypoints, routes, and tracks. You can add your own elements
to the extensions section of the GPX document.
Metadata about the file.
A list of waypoints.
A list of routes.
A list of tracks.
You can add extend GPX by adding your own elements from another schema here.
You must include the version number in your GPX document.
You must include the name or URL of the software that created your GPX document. This allows others to
inform the creator of a GPX instance document that fails to validate.
Information about the GPX file, author, and copyright restrictions goes in the metadata section. Providing rich,
meaningful information about your GPX files allows others to search for and use your GPS data.
The name of the GPX file.
A description of the contents of the GPX file.
The person or organization who created the GPX file.
Copyright and license information governing use of the file.
URLs associated with the location described in the file.
The creation date of the file.
Keywords associated with the file. Search engines or databases can use this information to classify the data.
Minimum and maximum coordinates which describe the extent of the coordinates in the file.
You can add extend GPX by adding your own elements from another schema here.
wpt represents a waypoint, point of interest, or named feature on a map.
Elevation (in meters) of the point.
Creation/modification timestamp for element. Date and time in are in Univeral Coordinated Time (UTC), not local time! Conforms to ISO 8601 specification for date/time representation. Fractional seconds are allowed for millisecond timing in tracklogs.
Magnetic variation (in degrees) at the point
Height (in meters) of geoid (mean sea level) above WGS84 earth ellipsoid. As defined in NMEA GGA message.
The GPS name of the waypoint. This field will be transferred to and from the GPS. GPX does not place restrictions on the length of this field or the characters contained in it. It is up to the receiving application to validate the field before sending it to the GPS.
GPS waypoint comment. Sent to GPS as comment.
A text description of the element. Holds additional information about the element intended for the user, not the GPS.
Source of data. Included to give user some idea of reliability and accuracy of data. "Garmin eTrex", "USGS quad Boston North", e.g.
Link to additional information about the waypoint.
Text of GPS symbol name. For interchange with other programs, use the exact spelling of the symbol as displayed on the GPS. If the GPS abbreviates words, spell them out.
Type (classification) of the waypoint.
Type of GPX fix.
Number of satellites used to calculate the GPX fix.
Horizontal dilution of precision.
Vertical dilution of precision.
Position dilution of precision.
Number of seconds since last DGPS update.
ID of DGPS station used in differential correction.
You can add extend GPX by adding your own elements from another schema here.
The latitude of the point. This is always in decimal degrees, and always in WGS84 datum.
The longitude of the point. This is always in decimal degrees, and always in WGS84 datum.
rte represents route - an ordered list of waypoints representing a series of turn points leading to a destination.
GPS name of route.
GPS comment for route.
Text description of route for user. Not sent to GPS.
Source of data. Included to give user some idea of reliability and accuracy of data.
Links to external information about the route.
GPS route number.
Type (classification) of route.
You can add extend GPX by adding your own elements from another schema here.
A list of route points.
trk represents a track - an ordered list of points describing a path.
GPS name of track.
GPS comment for track.
User description of track.
Source of data. Included to give user some idea of reliability and accuracy of data.
Links to external information about track.
GPS track number.
Type (classification) of track.
You can add extend GPX by adding your own elements from another schema here.
A Track Segment holds a list of Track Points which are logically connected in order. To represent a single GPS track where GPS reception was lost, or the GPS receiver was turned off, start a new Track Segment for each continuous span of track data.
You can add extend GPX by adding your own elements from another schema here.
You can add extend GPX by adding your own elements from another schema here.
A Track Segment holds a list of Track Points which are logically connected in order. To represent a single GPS track where GPS reception was lost, or the GPS receiver was turned off, start a new Track Segment for each continuous span of track data.
A Track Point holds the coordinates, elevation, timestamp, and metadata for a single point in a track.
You can add extend GPX by adding your own elements from another schema here.
Information about the copyright holder and any license governing use of this file. By linking to an appropriate license,
you may place your data into the public domain or grant additional usage rights.
Year of copyright.
Link to external file containing license text.
Copyright holder (TopoSoft, Inc.)
A link to an external resource (Web page, digital photo, video clip, etc) with additional information.
Text of hyperlink.
Mime type of content (image/jpeg)
URL of hyperlink.
An email address. Broken into two parts (id and domain) to help prevent email harvesting.
id half of email address (billgates2004)
domain half of email address (hotmail.com)
A person or organization.
Name of person or organization.
Email address.
Link to Web site or other external information about person.
A geographic point with optional elevation and time. Available for use by other schemas.
The elevation (in meters) of the point.
The time that the point was recorded.
The latitude of the point. Decimal degrees, WGS84 datum.
The latitude of the point. Decimal degrees, WGS84 datum.
An ordered sequence of points. (for polygons or polylines, e.g.)
Ordered list of geographic points.
Two lat/lon pairs defining the extent of an element.
The minimum latitude.
The minimum longitude.
The maximum latitude.
The maximum longitude.
The latitude of the point. Decimal degrees, WGS84 datum.
The longitude of the point. Decimal degrees, WGS84 datum.
Used for bearing, heading, course. Units are decimal degrees, true (not magnetic).
Type of GPS fix. none means GPS had no fix. To signify "the fix info is unknown, leave out fixType entirely. pps = military signal used
Represents a differential GPS station.
\ No newline at end of file
diff --git a/rowers/.#views.py b/rowers/.#views.py
new file mode 100644
index 00000000..1666c3af
--- /dev/null
+++ b/rowers/.#views.py
@@ -0,0 +1 @@
+E408191@CZ27LT9RCGN72.7808:1496304801
\ No newline at end of file
diff --git a/rowers/models.py b/rowers/models.py
index 528c28b9..05e27344 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -425,7 +425,8 @@ class Workout(models.Model):
summary = models.TextField(blank=True)
privacy = models.CharField(default='visible',max_length=30,
choices=privacychoices)
-
+ rankingpiece = models.BooleanField(default=False,verbose_name='Ranking Piece')
+
def __unicode__(self):
date = self.date
@@ -552,7 +553,7 @@ class WorkoutForm(ModelForm):
duration = forms.TimeInput(format='%H:%M:%S.%f')
class Meta:
model = Workout
- fields = ['name','date','starttime','duration','distance','workouttype','notes','privacy','boattype']
+ fields = ['name','date','starttime','duration','distance','workouttype','notes','privacy','rankingpiece','boattype']
widgets = {
'date': DateInput(),
'notes': forms.Textarea,
diff --git a/rowers/stravastuff.py b/rowers/stravastuff.py
index 28900411..eff56909 100644
--- a/rowers/stravastuff.py
+++ b/rowers/stravastuff.py
@@ -245,7 +245,7 @@ def get_strava_workout(user,stravaid):
return [workoutsummary,df]
# Generate Workout data for Strava (a TCX file)
-def createstravaworkoutdata(w):
+def createstravaworkoutdata(w,dozip=True):
filename = w.csvfilename
row = rowingdata(filename)
@@ -256,18 +256,22 @@ def createstravaworkoutdata(w):
newnotes = 'from '+w.workoutsource+' via rowsandall.com'
row.exporttotcx(tcxfilename,notes=newnotes)
- gzfilename = tcxfilename+'.gz'
- with file(tcxfilename,'rb') as inF:
- s = inF.read()
- with gzip.GzipFile(gzfilename,'wb') as outF:
- outF.write(s)
+ if dozip:
+ gzfilename = tcxfilename+'.gz'
+ with file(tcxfilename,'rb') as inF:
+ s = inF.read()
+ with gzip.GzipFile(gzfilename,'wb') as outF:
+ outF.write(s)
- try:
- os.remove(tcxfilename)
- except WindowError:
- pass
-
- return gzfilename,""
+ try:
+ os.remove(tcxfilename)
+ except WindowError:
+ pass
+
+ return gzfilename,""
+
+ else:
+ return tcxfilename,""
# Upload the TCX file to Strava and set the workout activity type
diff --git a/rowers/urls.py b/rowers/urls.py
index 33f953f7..68495427 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -169,6 +169,7 @@ urlpatterns = [
url(r'^workout/(?P\d+)/export$',views.workout_export_view),
url(r'^workout/(?P\d+)/comment$',views.workout_comment_view),
url(r'^workout/(?P\d+)/emailtcx$',views.workout_tcxemail_view),
+ url(r'^workout/(?P\d+)/emailgpx$',views.workout_gpxemail_view),
url(r'^workout/(?P\d+)/emailcsv$',views.workout_csvemail_view),
url(r'^workout/(?P\d+)/csvtoadmin$',views.workout_csvtoadmin_view),
url(r'^workout/compare/(?P\d+)/$',views.workout_comparison_list),
diff --git a/rowers/views.py b/rowers/views.py
index b8abbc77..8e15110d 100644
--- a/rowers/views.py
+++ b/rowers/views.py
@@ -1064,7 +1064,7 @@ def workout_tcxemail_view(request,id=0):
raise Http404("Workout doesn't exist")
if (checkworkoutuser(request.user,w)):
try:
- tcxfile,tcxmessg = stravastuff.createstravaworkoutdata(w)
+ tcxfile,tcxmessg = stravastuff.createstravaworkoutdata(w,dozip=False)
if tcxfile == 0:
message = "Something went wrong (TCX export) "+tcxmessg
messages.error(request,message)
@@ -1116,6 +1116,51 @@ def workout_tcxemail_view(request,id=0):
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
@login_required()
def workout_csvemail_view(request,id=0):
@@ -2645,6 +2690,301 @@ def rankings_view(request,theuser=0,
+ for rankingduration in rankingdurations:
+ t = 3600.*rankingduration.hour
+ t += 60.*rankingduration.minute
+ t += rankingduration.second
+ t += rankingduration.microsecond/1.e6
+
+ # Paul's model
+ ratio = paulintercept/paulslope
+
+ u = ((2**(2+ratio))*(5.**(3+ratio))*t*np.log(10))/paulslope
+
+ d = 500*t*np.log(10.)
+ d = d/(paulslope*lambertw(u))
+ d = d.real
+
+ velo = d/t
+ p = 500./velo
+ pwr = 2.8*(velo**3)
+ a = {'distance':int(d),
+ 'duration':timedeltaconv(t),
+ 'pace':timedeltaconv(p),
+ 'power':int(pwr)}
+ predictions.append(a)
+
+ # CP model
+ pwr = p1[0]/(1+t/p1[2])
+ pwr += p1[1]/(1+t/p1[3])
+
+ if pwr <= 0:
+ pwr = 50.
+
+ velo = (pwr/2.8)**(1./3.)
+
+ if np.isnan(velo) or velo <=0:
+ velo = 1.0
+
+ d = t*velo
+ p = 500./velo
+ a = {'distance':int(d),
+ 'duration':timedeltaconv(t),
+ 'pace':timedeltaconv(p),
+ 'power':int(pwr)}
+ cpredictions.append(a)
+
+
+ messages.error(request,message)
+ return render(request, 'rankings.html',
+ {'rankingworkouts':theworkouts,
+ 'interactiveplot':script,
+ 'the_div':div,
+ 'predictions':predictions,
+ 'cpredictions':cpredictions,
+ 'nrdata':len(thedistances),
+ 'form':form,
+ 'dateform':dateform,
+ 'deltaform':deltaform,
+ 'id': theuser,
+ 'theuser':uu,
+ 'startdate':startdate,
+ 'enddate':enddate,
+ '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 = []
+
+
+
+ rankingdistances.sort()
+ rankingdurations.sort()
+
+
+
+ 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)
+
+ dfgrouped = df.groupby(['workoutid'])
+ for id in theids:
+ tt = df['time']
+
+ for w in theworkouts:
+
+ thedistances.append(w.distance)
+
+ timesecs = 3600*workouts[0].duration.hour
+ timesecs += 60*workouts[0].duration.minute
+ timesecs += workouts[0].duration.second
+ timesecs += 1.e-5*workouts[0].duration.microsecond
+
+ thesecs.append(timesecs)
+
+
+
+ thesecs = np.array(thesecs)
+
+ theavpower = 2.8*(thevelos**3)
+
+
+ # create interactive plot
+ if len(thedistances) !=0 :
+ res = interactive_cpchart(thedistances,thesecs,theavpower,
+ theworkouts,promember=promember)
+ script = res[0]
+ div = res[1]
+ paulslope = res[2]
+ paulintercept = res[3]
+ p1 = res[4]
+ message = res[5]
+ else:
+ script = ''
+ div = 'No ranking pieces found.
'
+ paulslope = 1
+ paulintercept = 1
+ p1 = [1,1,1,1]
+ message = ""
+
+
+ if request.method == 'POST' and "piece" in request.POST:
+ form = PredictedPieceForm(request.POST)
+ if form.is_valid():
+ value = form.cleaned_data['value']
+ hourvalue,value = divmod(value,60)
+ if hourvalue >= 24:
+ hourvalue = 23
+ pieceunit = form.cleaned_data['pieceunit']
+ if pieceunit == 'd':
+ rankingdistances.append(value)
+ else:
+ rankingdurations.append(datetime.time(minute=value,hour=hourvalue))
+ else:
+ form = PredictedPieceForm()
+
+ rankingdistances.sort()
+ rankingdurations.sort()
+
+
+ predictions = []
+ cpredictions = []
+
+
+ for rankingdistance in rankingdistances:
+ # Paul's model
+ p = paulslope*np.log10(rankingdistance)+paulintercept
+ velo = 500./p
+ t = rankingdistance/velo
+ pwr = 2.8*(velo**3)
+ a = {'distance':rankingdistance,
+ 'duration':timedeltaconv(t),
+ 'pace':timedeltaconv(p),
+ 'power':int(pwr)}
+ predictions.append(a)
+
+ # CP model -
+ pwr2 = p1[0]/(1+t/p1[2])
+ pwr2 += p1[1]/(1+t/p1[3])
+
+ if pwr2 <= 0:
+ pwr2 = 50.
+
+ velo2 = (pwr2/2.8)**(1./3.)
+
+ if np.isnan(velo2) or velo2 <= 0:
+ velo2 = 1.0
+
+ t2 = rankingdistance/velo2
+
+ pwr3 = p1[0]/(1+t2/p1[2])
+ pwr3 += p1[1]/(1+t2/p1[3])
+
+ if pwr3 <= 0:
+ pwr3 = 50.
+
+ velo3 = (pwr3/2.8)**(1./3.)
+ if np.isnan(velo3) or velo3 <= 0:
+ velo3 = 1.0
+
+ t3 = rankingdistance/velo3
+ p3 = 500./velo3
+
+ a = {'distance':rankingdistance,
+ 'duration':timedeltaconv(t3),
+ 'pace':timedeltaconv(p3),
+ 'power':int(pwr3)}
+ cpredictions.append(a)
+
+
+
+
for rankingduration in rankingdurations:
t = 3600.*rankingduration.hour
t += 60.*rankingduration.minute
@@ -5187,6 +5527,11 @@ def workout_edit_view(request,id=0,message="",successmessage=""):
privacy = request.POST['privacy']
except KeyError:
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 = datetime.datetime.strptime(startdatetime,
"%Y-%m-%d %H:%M:%S")
@@ -5203,6 +5548,7 @@ def workout_edit_view(request,id=0,message="",successmessage=""):
row.distance = distance
row.boattype = boattype
row.privacy = privacy
+ row.rankingpiece = rankingpiece
try:
row.save()
except IntegrityError:
From 39eee9c79012d6fcef3be43a78c9987e7ba3a625 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Sun, 4 Jun 2017 19:55:33 +0200
Subject: [PATCH 05/11] gpx export on export page
---
rowers/.#views.py | 1 -
rowers/templates/export.html | 4 ++++
static/img/gpx.jpg | Bin 0 -> 27965 bytes
3 files changed, 4 insertions(+), 1 deletion(-)
delete mode 100644 rowers/.#views.py
create mode 100644 static/img/gpx.jpg
diff --git a/rowers/.#views.py b/rowers/.#views.py
deleted file mode 100644
index 1666c3af..00000000
--- a/rowers/.#views.py
+++ /dev/null
@@ -1 +0,0 @@
-E408191@CZ27LT9RCGN72.7808:1496304801
\ No newline at end of file
diff --git a/rowers/templates/export.html b/rowers/templates/export.html
index 52f53480..3953b013 100644
--- a/rowers/templates/export.html
+++ b/rowers/templates/export.html
@@ -147,6 +147,10 @@
{% endif %}
+
+
+
+
diff --git a/static/img/gpx.jpg b/static/img/gpx.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b81a5ef4d0bad2765824ee469562887aee75f4fa
GIT binary patch
literal 27965
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
literal 0
HcmV?d00001
From c2207ccdd482af7b19a4ba94e743e8ac81bd1b84 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Sun, 4 Jun 2017 20:42:48 +0200
Subject: [PATCH 06/11] otwpower untested, unfinished
---
rowers/views.py | 57 ++++++++++++++++++++++++++++++++++++++++---------
1 file changed, 47 insertions(+), 10 deletions(-)
diff --git a/rowers/views.py b/rowers/views.py
index 8e15110d..1ac5a4ab 100644
--- a/rowers/views.py
+++ b/rowers/views.py
@@ -138,6 +138,8 @@ from scipy.special import lambertw
from dataprep import timedeltaconv
+from scipy.interpolate import griddata
+
LOCALTIMEZONE = tz('Etc/UTC')
USER_LANGUAGE = 'en-US'
@@ -2873,15 +2875,10 @@ def otwrankings_view(request,theuser=0,
columns = ['power','workoutid','time']
df = dataprep.getsmallrowdata_db(columns,ids=theids)
- dfgrouped = df.groupby(['workoutid'])
- for id in theids:
- tt = df['time']
-
+ thesecs = []
+
for w in theworkouts:
-
- thedistances.append(w.distance)
-
- timesecs = 3600*workouts[0].duration.hour
+ timesecs = 3600*workouts[0].duration.hour
timesecs += 60*workouts[0].duration.minute
timesecs += workouts[0].duration.second
timesecs += 1.e-5*workouts[0].duration.microsecond
@@ -2889,11 +2886,51 @@ def otwrankings_view(request,theuser=0,
thesecs.append(timesecs)
+ maxt = pd.Series(thesecs).max()
- thesecs = np.array(thesecs)
+ maxlog10 = np.log10(maxt)
+ logarr = np.arange(100)*maxlog10/100.
+ logarr = 10.**(logarr)
- theavpower = 2.8*(thevelos**3)
+ delta = []
+ cpvalue = []
+
+ dfgrouped = df.groupby(['workoutid'])
+ for id,group in dfgrouped:
+ tt = group['time']
+ ww = group['power']
+ length = len(ww)
+ dt = []
+ cpw = []
+ for i in range(length):
+ w_roll = ww.rolling(i)
+ # now goes with # data points - should be fixed seconds
+ t_0 = tt.ix[w_roll.idxmax(axis=1)]
+ t_1 = tt.ix[w_roll.idxmax(axis=1)-i]
+ deltat = t_1-t_0
+ wmax = w_roll.ix[w_roll.idxmax(axis=1)]
+ dt.append(deltat)
+ cpw.append(wmax)
+ cpvalues = griddata(dt,cpw,logarr,method='linear',fill_value=0)
+
+ for cpv in cpvalues:
+ cpvalue.append(cpv)
+ for d in logarr:
+ delta.append(d)
+
+ dt = pd.Series(delta,name='Delta')
+ cpvalue = pd.Series(cpvalue,name='CP')
+
+
+ powerdf = pd.DataFrame({
+ 'Delta':delta,
+ 'CP':cpvalue,
+ })
+
+ powerdf.sort_value(['Delta','CP'],ascending=[1,0]
+ powerdf.drop_duplicates(subset='Delta',keep='first')
+
# create interactive plot
if len(thedistances) !=0 :
From 5e0d99b72099d0e9bae79e946abc0fa90950b9da Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 5 Jun 2017 14:52:54 +0200
Subject: [PATCH 07/11] set privacy to private on email imported duplicates
---
rowers/dataprepnodjango.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/rowers/dataprepnodjango.py b/rowers/dataprepnodjango.py
index 95616d9e..40aa638d 100644
--- a/rowers/dataprepnodjango.py
+++ b/rowers/dataprepnodjango.py
@@ -242,6 +242,7 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower',
user=r)
if (len(ws) != 0):
message = "Warning: This workout probably already exists in the database"
+ privacy = 'private'
From 0449ff432fddf2de74532e8e6714d6db97edb450 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 5 Jun 2017 15:54:25 +0200
Subject: [PATCH 08/11] got to point where chart is generated (L2936)
---
rowers/urls.py | 6 +++
rowers/views.py | 117 +++++++++++++++---------------------------------
2 files changed, 42 insertions(+), 81 deletions(-)
diff --git a/rowers/urls.py b/rowers/urls.py
index 68495427..e850a4c6 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -135,6 +135,12 @@ urlpatterns = [
url(r'^ote-bests/(?P\d+)$',views.rankings_view),
url(r'^ote-bests/$',views.rankings_view),
url(r'^(?P\d+)/ote-bests/$',views.rankings_view),
+ url(r'^(?P\d+)/otw-bests/(?P\w+.*)/(?P\w+.*)$',views.otwrankings_view),
+ url(r'^(?P\d+)/otw-bests/(?P\d+)$',views.otwrankings_view),
+ url(r'^otw-bests/(?P\w+.*)/(?P\w+.*)$',views.otwrankings_view),
+ url(r'^otw-bests/(?P\d+)$',views.otwrankings_view),
+ url(r'^otw-bests/$',views.otwrankings_view),
+ url(r'^(?P\d+)/otw-bests/$',views.otwrankings_view),
url(r'^(?P\d+)/flexall/(?P\w+.*)/(?P\w+.*)/(?P\w+.*)/(?P\w+.*)/(?P\w+.*)$',views.cum_flex),
url(r'^flexall/(?P\w+.*)/(?P\w+.*)/(?P\w+.*)/(?P\w+.*)/(?P\w+.*)$',views.cum_flex),
url(r'^flexall/(?P\w+.*)/(?P\w+.*)/(?P\w+.*)$',views.cum_flex),
diff --git a/rowers/views.py b/rowers/views.py
index 1ac5a4ab..922ba101 100644
--- a/rowers/views.py
+++ b/rowers/views.py
@@ -2856,13 +2856,6 @@ def otwrankings_view(request,theuser=0,
thedistances = []
theworkouts = []
thesecs = []
-
-
-
- rankingdistances.sort()
- rankingdurations.sort()
-
-
theworkouts = Workout.objects.filter(user=r,rankingpiece=True,
workouttype='water',
@@ -2878,10 +2871,10 @@ def otwrankings_view(request,theuser=0,
thesecs = []
for w in theworkouts:
- timesecs = 3600*workouts[0].duration.hour
- timesecs += 60*workouts[0].duration.minute
- timesecs += workouts[0].duration.second
- timesecs += 1.e-5*workouts[0].duration.microsecond
+ timesecs = 3600*w.duration.hour
+ timesecs += 60*w.duration.minute
+ timesecs += w.duration.second
+ timesecs += 1.e-5*w.duration.microsecond
thesecs.append(timesecs)
@@ -2890,7 +2883,10 @@ def otwrankings_view(request,theuser=0,
maxlog10 = np.log10(maxt)
logarr = np.arange(100)*maxlog10/100.
- logarr = 10.**(logarr)
+ logarr = [int(10.**(la)) for la in logarr]
+ logarr = pd.Series(logarr)
+ logarr.drop_duplicates(keep='first',inplace=True)
+ logarr = logarr.values
delta = []
cpvalue = []
@@ -2899,25 +2895,34 @@ def otwrankings_view(request,theuser=0,
for id,group in dfgrouped:
tt = group['time']
ww = group['power']
- length = len(ww)
- dt = []
- cpw = []
- for i in range(length):
- w_roll = ww.rolling(i)
- # now goes with # data points - should be fixed seconds
- t_0 = tt.ix[w_roll.idxmax(axis=1)]
- t_1 = tt.ix[w_roll.idxmax(axis=1)-i]
- deltat = t_1-t_0
- wmax = w_roll.ix[w_roll.idxmax(axis=1)]
- dt.append(deltat)
- cpw.append(wmax)
+ 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
- cpvalues = griddata(dt,cpw,logarr,method='linear',fill_value=0)
+ 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)
+ for cpv in cpvalues:
+ cpvalue.append(cpv)
+ for d in logarr:
+ delta.append(d)
dt = pd.Series(delta,name='Delta')
cpvalue = pd.Series(cpvalue,name='CP')
@@ -2928,9 +2933,9 @@ def otwrankings_view(request,theuser=0,
'CP':cpvalue,
})
- powerdf.sort_value(['Delta','CP'],ascending=[1,0]
+ powerdf.sort_values(['Delta','CP'],ascending=[1,0])
powerdf.drop_duplicates(subset='Delta',keep='first')
-
+
# create interactive plot
if len(thedistances) !=0 :
@@ -2966,61 +2971,11 @@ def otwrankings_view(request,theuser=0,
else:
form = PredictedPieceForm()
- rankingdistances.sort()
- rankingdurations.sort()
-
predictions = []
cpredictions = []
- for rankingdistance in rankingdistances:
- # Paul's model
- p = paulslope*np.log10(rankingdistance)+paulintercept
- velo = 500./p
- t = rankingdistance/velo
- pwr = 2.8*(velo**3)
- a = {'distance':rankingdistance,
- 'duration':timedeltaconv(t),
- 'pace':timedeltaconv(p),
- 'power':int(pwr)}
- predictions.append(a)
-
- # CP model -
- pwr2 = p1[0]/(1+t/p1[2])
- pwr2 += p1[1]/(1+t/p1[3])
-
- if pwr2 <= 0:
- pwr2 = 50.
-
- velo2 = (pwr2/2.8)**(1./3.)
-
- if np.isnan(velo2) or velo2 <= 0:
- velo2 = 1.0
-
- t2 = rankingdistance/velo2
-
- pwr3 = p1[0]/(1+t2/p1[2])
- pwr3 += p1[1]/(1+t2/p1[3])
-
- if pwr3 <= 0:
- pwr3 = 50.
-
- velo3 = (pwr3/2.8)**(1./3.)
- if np.isnan(velo3) or velo3 <= 0:
- velo3 = 1.0
-
- t3 = rankingdistance/velo3
- p3 = 500./velo3
-
- a = {'distance':rankingdistance,
- 'duration':timedeltaconv(t3),
- 'pace':timedeltaconv(p3),
- 'power':int(pwr3)}
- cpredictions.append(a)
-
-
-
for rankingduration in rankingdurations:
t = 3600.*rankingduration.hour
From 4e94a9e2e5d73d257bb68aee29ceb8987bfabbd4 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Tue, 6 Jun 2017 20:54:24 -0500
Subject: [PATCH 09/11] otw-bests somehow working
---
rowers/interactiveplots.py | 88 +++++++++++++
rowers/templates/otwrankings.html | 199 ++++++++++++++++++++++++++++++
rowers/views.py | 48 ++-----
3 files changed, 298 insertions(+), 37 deletions(-)
create mode 100644 rowers/templates/otwrankings.html
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index 7f640cee..0fdb77c1 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -596,6 +596,94 @@ def googlemap_chart(lat,lon,name=""):
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'
+
+ 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')
+ 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"
+
+
+ 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,
theworkouts,promember=0):
diff --git a/rowers/templates/otwrankings.html b/rowers/templates/otwrankings.html
new file mode 100644
index 00000000..35b35865
--- /dev/null
+++ b/rowers/templates/otwrankings.html
@@ -0,0 +1,199 @@
+{% extends "base.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block title %}Workouts{% endblock %}
+
+{% block content %}
+
+
+
+
+ {{ interactiveplot |safe }}
+
+
+
+
+
+
+
+ {% if theuser %}
+
{{ theuser.first_name }}'s Ranking Pieces
+ {% else %}
+ {{ user.first_name }}'s Ranking Pieces
+ {% endif %}
+
+
+ {% if user.is_authenticated and user|is_manager %}
+
+
+
+
+
+
+
+
+
Ranking Piece Results
+
+ {% if rankingworkouts %}
+
+
+
+
+ Distance
+ Duration
+ Date
+ Avg HR
+ Max HR
+ Edit
+
+
+
+ {% for workout in rankingworkouts %}
+
+ {{ workout.distance }}
+ {{ workout.duration |durationprint:"%H:%M:%S.%f" }}
+ {{ workout.date }}
+ {{ workout.averagehr }}
+ {{ workout.maxhr }}
+
+ {{ workout.name }}
+
+
+
+ {% endfor %}
+
+
+ {% else %}
+
No ranking workouts found
+ {% endif %}
+
+
+
+
+
+
+
Critical Power Plot
+
+ {{ the_div|safe }}
+
+
+
+
+
Pace predictions for Ranking Pieces
+
+
Add non-ranking piece using the form. The piece will be added in the prediction tables below.
+
+
+
+
+
+
+
+
+
+
+ No Paul Data
+
+
+
CP Model
+
+
+
+ Duration
+ Power
+
+
+
+ {% for pred in cpredictions %}
+
+ {% for key, value in pred.items %}
+ {% if key == "power" %}
+ {{ value }} W
+ {% endif %}
+ {% if key == "duration" %}
+ {{ value |deltatimeprint }}
+ {% endif %}
+ {% endfor %}
+
+ {% endfor %}
+
+
+
+
+
+
+{% endblock %}
diff --git a/rowers/views.py b/rowers/views.py
index 922ba101..fc15ea5e 100644
--- a/rowers/views.py
+++ b/rowers/views.py
@@ -2938,15 +2938,14 @@ def otwrankings_view(request,theuser=0,
# create interactive plot
- if len(thedistances) !=0 :
- res = interactive_cpchart(thedistances,thesecs,theavpower,
- theworkouts,promember=promember)
+ if len(powerdf) !=0 :
+ res = interactive_otwcpchart(powerdf)
script = res[0]
div = res[1]
- paulslope = res[2]
- paulintercept = res[3]
- p1 = res[4]
- message = res[5]
+ p1 = res[2]
+ paulslope = 1
+ paulintercept = 1
+ message = res[3]
else:
script = ''
div = '
No ranking pieces found.
'
@@ -2983,23 +2982,6 @@ def otwrankings_view(request,theuser=0,
t += rankingduration.second
t += rankingduration.microsecond/1.e6
- # Paul's model
- ratio = paulintercept/paulslope
-
- u = ((2**(2+ratio))*(5.**(3+ratio))*t*np.log(10))/paulslope
-
- d = 500*t*np.log(10.)
- d = d/(paulslope*lambertw(u))
- d = d.real
-
- velo = d/t
- p = 500./velo
- pwr = 2.8*(velo**3)
- a = {'distance':int(d),
- 'duration':timedeltaconv(t),
- 'pace':timedeltaconv(p),
- 'power':int(pwr)}
- predictions.append(a)
# CP model
pwr = p1[0]/(1+t/p1[2])
@@ -3008,28 +2990,20 @@ def otwrankings_view(request,theuser=0,
if pwr <= 0:
pwr = 50.
- velo = (pwr/2.8)**(1./3.)
-
- if np.isnan(velo) or velo <=0:
- velo = 1.0
-
- d = t*velo
- p = 500./velo
- a = {'distance':int(d),
- 'duration':timedeltaconv(t),
- 'pace':timedeltaconv(p),
- 'power':int(pwr)}
+ a = {
+ 'duration':timedeltaconv(t),
+ 'power':int(pwr)}
cpredictions.append(a)
+ print cpredictions
messages.error(request,message)
- return render(request, 'rankings.html',
+ return render(request, 'otwrankings.html',
{'rankingworkouts':theworkouts,
'interactiveplot':script,
'the_div':div,
'predictions':predictions,
'cpredictions':cpredictions,
- 'nrdata':len(thedistances),
'form':form,
'dateform':dateform,
'deltaform':deltaform,
From 2cb4841e8ceec211fb71339415b153edcd0f3440 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Wed, 7 Jun 2017 17:52:00 -0700
Subject: [PATCH 10/11] creates Rower object if user doesn't have it
---
rowers/views.py | 222 ++++++++++++++++++++++++++----------------------
1 file changed, 121 insertions(+), 101 deletions(-)
diff --git a/rowers/views.py b/rowers/views.py
index b8abbc77..1eaa47b8 100644
--- a/rowers/views.py
+++ b/rowers/views.py
@@ -277,17 +277,39 @@ from rowers.models import checkworkoutuser
# Check if a user is a Coach member
def iscoachmember(user):
if not user.is_anonymous():
- r = Rower.objects.get(user=user)
+ try:
+ r = Rower.objects.get(user=user)
+ except Rower.DoesNotExist:
+ r = Rower(user=user)
+ r.save()
+
result = user.is_authenticated() and (r.rowerplan=='coach')
else:
result = False
return result
+def getrower(user):
+ if not user.is_anonymous():
+ try:
+ r = Rower.objects.get(user=user)
+ except Rower.DoesNotExist:
+ r = Rower(user=user)
+ r.save()
+ else:
+ raise PermissionDenied("You need to log in to use this function")
+
+ return r
+
# Check if a user is a Pro member
def ispromember(user):
if not user.is_anonymous():
- r = Rower.objects.get(user=user)
+ try:
+ r = Rower.objects.get(user=user)
+ except Rower.DoesNotExists:
+ r = Rower(user=user)
+ r.save()
+
result = user.is_authenticated() and (r.rowerplan=='pro' or r.rowerplan=='coach')
else:
result = False
@@ -406,7 +428,7 @@ def add_workout_from_strokedata(user,importid,data,strokedata,
except:
thetimezone = 'UTC'
- r = Rower.objects.get(user=user)
+ r = getrower(user)
try:
rowdatetime = iso8601.parse_date(data['date_utc'])
except KeyError:
@@ -570,7 +592,7 @@ def add_workout_from_runkeeperdata(user,importid,data):
except:
utcoffset = 0
- r = Rower.objects.get(user=user)
+ r = getrower(user)
try:
rowdatetime = iso8601.parse_date(data['start_time'])
@@ -734,7 +756,7 @@ def add_workout_from_stdata(user,importid,data):
except:
thetimezone = 'UTC'
- r = Rower.objects.get(user=user)
+ r = getrower(user)
try:
rowdatetime = iso8601.parse_date(data['start_time'])
except iso8601.ParseError:
@@ -897,7 +919,7 @@ def add_workout_from_underarmourdata(user,importid,data):
except:
thetimezone = 'UTC'
- r = Rower.objects.get(user=user)
+ r = getrower(user)
try:
rowdatetime = iso8601.parse_date(data['start_datetime'])
except iso8601.ParseError:
@@ -1057,7 +1079,7 @@ def add_workout_from_underarmourdata(user,importid,data):
def workout_tcxemail_view(request,id=0):
message = ""
successmessage = ""
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
try:
w = Workout.objects.get(id=id)
except Workout.DoesNotExist:
@@ -1120,7 +1142,7 @@ def workout_tcxemail_view(request,id=0):
@login_required()
def workout_csvemail_view(request,id=0):
message = ""
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
try:
w = Workout.objects.get(id=id)
except Workout.DoesNotExist:
@@ -1160,7 +1182,7 @@ def workout_csvemail_view(request,id=0):
@login_required()
def workout_csvtoadmin_view(request,id=0):
message = ""
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
try:
w = Workout.objects.get(id=id)
except Workout.DoesNotExist:
@@ -1195,7 +1217,7 @@ def workout_csvtoadmin_view(request,id=0):
@login_required()
def workout_tp_upload_view(request,id=0):
message = ""
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
res = -1
try:
thetoken = tp_open(r.user)
@@ -1253,7 +1275,7 @@ def workout_tp_upload_view(request,id=0):
@login_required()
def workout_strava_upload_view(request,id=0):
message = ""
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
res = -1
if (r.stravatoken == '') or (r.stravatoken is None):
s = "Token doesn't exist. Need to authorize"
@@ -1674,14 +1696,14 @@ def rower_tp_authorize(request):
# Concept2 token refresh. URL for manual refresh. Not visible to users
@login_required()
def rower_c2_token_refresh(request):
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
res = c2stuff.do_refresh_token(r.c2refreshtoken)
if res[0] != None:
access_token = res[0]
expires_in = res[1]
refresh_token = res[2]
expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in)
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
r.c2token = access_token
r.tokenexpirydate = expirydatetime
r.c2refreshtoken = refresh_token
@@ -1699,7 +1721,7 @@ def rower_c2_token_refresh(request):
# Underarmour token refresh. URL for manual refresh. Not visible to users
@login_required()
def rower_underarmour_token_refresh(request):
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
res = underarmourstuff.do_refresh_token(
r.underarmourrefreshtoken,
r.underarmourtoken
@@ -1709,7 +1731,7 @@ def rower_underarmour_token_refresh(request):
refresh_token = res[2]
expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in)
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
r.underarmourtoken = access_token
r.underarmourtokenexpirydate = expirydatetime
r.underarmourrefreshtoken = refresh_token
@@ -1724,7 +1746,7 @@ def rower_underarmour_token_refresh(request):
# TrainingPeaks token refresh. URL for manual refresh. Not visible to users
@login_required()
def rower_tp_token_refresh(request):
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
res = tpstuff.do_refresh_token(
r.tprefreshtoken,
)
@@ -1733,7 +1755,7 @@ def rower_tp_token_refresh(request):
refresh_token = res[2]
expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in)
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
r.tptoken = access_token
r.tptokenexpirydate = expirydatetime
r.tprefreshtoken = refresh_token
@@ -1748,7 +1770,7 @@ def rower_tp_token_refresh(request):
# SportTracks token refresh. URL for manual refresh. Not visible to users
@login_required()
def rower_sporttracks_token_refresh(request):
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
res = sporttracksstuff.do_refresh_token(
r.sporttracksrefreshtoken,
)
@@ -1757,7 +1779,7 @@ def rower_sporttracks_token_refresh(request):
refresh_token = res[2]
expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in)
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
r.sporttrackstoken = access_token
r.sporttrackstokenexpirydate = expirydatetime
r.sporttracksrefreshtoken = refresh_token
@@ -1791,7 +1813,7 @@ def rower_process_callback(request):
refresh_token = res[2]
expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in)
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
r.c2token = access_token
r.tokenexpirydate = expirydatetime
r.c2refreshtoken = refresh_token
@@ -1843,7 +1865,7 @@ def rower_process_stravacallback(request):
if res[0]:
access_token = res[0]
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
r.stravatoken = access_token
r.save()
@@ -1862,7 +1884,7 @@ def rower_process_runkeepercallback(request):
code = request.GET['code']
access_token = runkeeperstuff.get_token(code)
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
r.runkeepertoken = access_token
r.save()
@@ -1883,7 +1905,7 @@ def rower_process_sporttrackscallback(request):
refresh_token = res[2]
expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in)
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
r.sporttrackstoken = access_token
r.sporttrackstokenexpirydate = expirydatetime
r.sporttracksrefreshtoken = refresh_token
@@ -1906,7 +1928,7 @@ def rower_process_underarmourcallback(request):
refresh_token = res[2]
expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in)
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
r.underarmourtoken = access_token
r.underarmourtokenexpirydate = expirydatetime
r.underarmourrefreshtoken = refresh_token
@@ -1929,7 +1951,7 @@ def rower_process_tpcallback(request):
refresh_token = res[2]
expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in)
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
r.tptoken = access_token
r.tptokenexpirydate = expirydatetime
r.tprefreshtoken = refresh_token
@@ -1968,7 +1990,7 @@ def histo_all(request,theuser=0):
theuser = request.user.id
if not request.user.is_anonymous():
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
@@ -1979,7 +2001,7 @@ def histo_all(request,theuser=0):
# get all indoor rows of past 12 months
ayearago = timezone.now()-datetime.timedelta(days=365)
try:
- r2 = Rower.objects.get(user=theuser)
+ r2 = getrower(theuser)
allergworkouts = Workout.objects.filter(user=r2,
workouttype__in=['rower','dynamic','slides'],
startdatetime__gte=ayearago)
@@ -2052,7 +2074,7 @@ def cum_flex(request,theuser=0,
theuser = request.user.id
if not request.user.is_anonymous():
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
@@ -2126,7 +2148,7 @@ def cum_flex(request,theuser=0,
deltaform = DeltaDaysForm()
optionsform = StatsOptionsForm()
try:
- r2 = Rower.objects.get(user=theuser)
+ r2 = getrower(theuser)
allworkouts = Workout.objects.filter(user=r2,
workouttype__in=workouttypes,
startdatetime__gte=startdate,
@@ -2210,7 +2232,7 @@ def workout_forcecurve_view(request,id=0,workstrokesonly=False):
promember=0
mayedit=0
if not request.user.is_anonymous():
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
@@ -2271,7 +2293,7 @@ def workout_histo_view(request,id=0):
promember=0
mayedit=0
if not request.user.is_anonymous():
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
@@ -2324,7 +2346,7 @@ def histo(request,theuser=0,
theuser = request.user.id
if not request.user.is_anonymous():
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
@@ -2371,7 +2393,7 @@ def histo(request,theuser=0,
deltaform = DeltaDaysForm()
try:
- r2 = Rower.objects.get(user=theuser)
+ r2 = getrower(theuser)
allergworkouts = Workout.objects.filter(user=r2,
workouttype__in=['rower','dynamic','slides'],
startdatetime__gte=startdate,
@@ -2435,7 +2457,7 @@ def rankings_view(request,theuser=0,
promember=0
if not request.user.is_anonymous():
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
@@ -2481,7 +2503,7 @@ def rankings_view(request,theuser=0,
# get all 2k (if any) - this rower, in date range
try:
- r = Rower.objects.get(user=theuser)
+ r = getrower(theuser)
except Rower.DoesNotExist:
allergworkouts = []
r=0
@@ -2762,7 +2784,7 @@ def workout_makepublic_view(request,id,
row.privacy = 'visible'
row.save()
- rr = Rower.objects.get(user=request.user)
+ rr = getrower(request.user)
teams = rr.team.all()
for team in teams:
@@ -2821,7 +2843,7 @@ def team_comparison_select(request,
teamid=0):
try:
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
except Rower.DoesNotExist:
raise Http404("Rower doesn't exist")
@@ -2906,7 +2928,7 @@ def team_comparison_select(request,
def multi_compare_view(request):
promember=0
if not request.user.is_anonymous():
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
@@ -2997,10 +3019,8 @@ def user_boxplot_select(request,
else:
user = User.objects.get(id=userid)
- try:
- r = Rower.objects.get(user=user)
- except Rower.DoesNotExist:
- raise Http404("Rower doesn't exist")
+
+ r = getrower(user)
if 'startdate' in request.session:
startdate = iso8601.parse_date(request.session['startdate'])
@@ -3259,7 +3279,7 @@ def workouts_view(request,message='',successmessage='',
enddate=timezone.now()+datetime.timedelta(days=1),
teamid=0):
try:
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
except Rower.DoesNotExist:
raise Http404("Rower doesn't exist")
@@ -3360,7 +3380,7 @@ def workout_comparison_list(request,id=0,message='',successmessage='',
enddate=timezone.now()):
try:
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
u = User.objects.get(id=r.user.id)
if request.method == 'POST':
dateform = DateRangeForm(request.POST)
@@ -3440,7 +3460,7 @@ def workout_fusion_list(request,id=0,message='',successmessage='',
enddate=timezone.now()):
try:
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
u = User.objects.get(id=r.user.id)
if request.method == 'POST':
dateform = DateRangeForm(request.POST)
@@ -3840,7 +3860,7 @@ def workout_wind_view(request,id=0,message="",successmessage=""):
# get data
f1 = row.csvfilename
u = row.user.user
- r = Rower.objects.get(user=u)
+ r = getrower(u)
# create bearing
rowdata = rdata(f1)
@@ -3961,7 +3981,7 @@ def workout_stream_view(request,id=0,message="",successmessage=""):
# create interactive plot
f1 = row.csvfilename
u = row.user.user
- r = Rower.objects.get(user=u)
+ r = getrower(u)
rowdata = rdata(f1)
if rowdata == 0:
@@ -4128,7 +4148,7 @@ def workout_geeky_view(request,id=0,message="",successmessage=""):
# create interactive plot
f1 = row.csvfilename
u = row.user.user
- r = Rower.objects.get(user=u)
+ r = getrower(u)
# create interactive plot
try:
@@ -4198,7 +4218,7 @@ def cumstats(request,theuser=0,
theuser = request.user.id
if not request.user.is_anonymous():
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
@@ -4266,7 +4286,7 @@ def cumstats(request,theuser=0,
optionsform = StatsOptionsForm()
try:
- r2 = Rower.objects.get(user=theuser)
+ r2 = getrower(theuser)
allergworkouts = Workout.objects.filter(user=r2,
workouttype__in=workouttypes,
startdatetime__gte=startdate,
@@ -4407,7 +4427,7 @@ def cumstats(request,theuser=0,
@login_required()
def workout_stats_view(request,id=0,message="",successmessage=""):
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
try:
w = Workout.objects.get(id=id)
except Workout.DoesNotExist:
@@ -4562,7 +4582,7 @@ def workout_advanced_view(request,id=0,message="",successmessage=""):
# create interactive plot
f1 = row.csvfilename
u = row.user.user
- r = Rower.objects.get(user=u)
+ r = getrower(u)
# create interactive plot
try:
@@ -4595,7 +4615,7 @@ def workout_advanced_view(request,id=0,message="",successmessage=""):
def workout_comparison_view(request,id1=0,id2=0,xparam='distance',yparam='spm'):
promember=0
if not request.user.is_anonymous():
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
@@ -4630,7 +4650,7 @@ def workout_comparison_view2(request,id1=0,id2=0,xparam='distance',
yparam='spm',plottype='line'):
promember=0
if not request.user.is_anonymous():
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
@@ -4706,7 +4726,7 @@ def workout_flexchart3_view(request,*args,**kwargs):
promember=0
mayedit=0
if not request.user.is_anonymous():
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
@@ -4781,7 +4801,7 @@ def workout_flexchart3_view(request,*args,**kwargs):
if not request.user.is_anonymous():
workstrokesonly = request.POST['workstrokesonlysave']
reststrokes = not workstrokesonly
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
f = FavoriteChart(user=r,xparam=xparam,
yparam1=yparam1,yparam2=yparam2,
plottype=plottype,workouttype=workouttype,
@@ -4889,12 +4909,12 @@ def workout_biginteractive_view(request,id=0,message="",successmessage=""):
# create interactive plot
f1 = row.csvfilename
u = row.user.user
- # r = Rower.objects.get(user=u)
+ # r = getrower(u)
promember=0
mayedit=0
if not request.user.is_anonymous():
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
@@ -4933,12 +4953,12 @@ def workout_otwpowerplot_view(request,id=0,message="",successmessage=""):
# create interactive plot
f1 = row.csvfilename
u = row.user.user
- # r = Rower.objects.get(user=u)
+ # r = getrower(u)
promember=0
mayedit=0
if not request.user.is_anonymous():
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
@@ -5247,7 +5267,7 @@ def workout_edit_view(request,id=0,message="",successmessage=""):
# create interactive plot
f1 = row.csvfilename
u = row.user.user
- r = Rower.objects.get(user=u)
+ r = getrower(u)
rowdata = rdata(f1)
hascoordinates = 1
if rowdata != 0:
@@ -5314,7 +5334,7 @@ def workout_add_otw_powerplot_view(request,id):
imagename = f1+timestr+'.png'
fullpathimagename = 'static/plots/'+imagename
u = w.user.user
- r = Rower.objects.get(user=u)
+ r = getrower(u)
powerperc = 100*np.array([r.pw_ut2,
r.pw_ut1,
r.pw_at,
@@ -5375,7 +5395,7 @@ def workout_add_piechart_view(request,id):
imagename = f1+timestr+'.png'
fullpathimagename = 'static/plots/'+imagename
u = w.user.user
- r = Rower.objects.get(user=u)
+ r = getrower(u)
powerperc = 100*np.array([r.pw_ut2,
r.pw_ut1,
@@ -5436,7 +5456,7 @@ def workout_add_power_piechart_view(request,id):
imagename = f1+timestr+'.png'
fullpathimagename = 'static/plots/'+imagename
u = w.user.user
- r = Rower.objects.get(user=u)
+ r = getrower(u)
powerperc = 100*np.array([r.pw_ut2,
r.pw_ut1,
@@ -5495,7 +5515,7 @@ def workout_add_timeplot_view(request,id):
imagename = f1+timestr+'.png'
fullpathimagename = 'static/plots/'+imagename
u = w.user.user
- r = Rower.objects.get(user=u)
+ r = getrower(u)
powerperc = 100*np.array([r.pw_ut2,
r.pw_ut1,
r.pw_at,
@@ -5555,7 +5575,7 @@ def workout_add_distanceplot_view(request,id):
imagename = f1+timestr+'.png'
fullpathimagename = 'static/plots/'+imagename
u = w.user.user
- r = Rower.objects.get(user=u)
+ r = getrower(u)
powerperc = 100*np.array([r.pw_ut2,
r.pw_ut1,
r.pw_at,
@@ -5613,7 +5633,7 @@ def workout_add_distanceplot2_view(request,id):
imagename = f1+timestr+'.png'
fullpathimagename = 'static/plots/'+imagename
u = w.user.user
- r = Rower.objects.get(user=u)
+ r = getrower(u)
powerperc = 100*np.array([r.pw_ut2,
r.pw_ut1,
r.pw_at,
@@ -5673,7 +5693,7 @@ def workout_add_timeplot2_view(request,id):
imagename = f1+timestr+'.png'
fullpathimagename = 'static/plots/'+imagename
u = w.user.user
- r = Rower.objects.get(user=u)
+ r = getrower(u)
powerperc = 100*np.array([r.pw_ut2,
r.pw_ut1,
r.pw_at,
@@ -5722,7 +5742,7 @@ def workout_stravaimport_view(request,message=""):
res = stravastuff.get_strava_workout_list(request.user)
if (res.status_code != 200):
if (res.status_code == 401):
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
if (r.stravatoken == '') or (r.stravatoken is None):
s = "Token doesn't exist. Need to authorize"
return HttpResponseRedirect("/rowers/me/stravaauthorize/")
@@ -5735,7 +5755,7 @@ def workout_stravaimport_view(request,message=""):
return HttpResponseRedirect(url)
else:
workouts = []
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
stravaids = [int(item['id']) for item in res.json()]
knownstravaids = uniqify([
w.uploadedtostrava for w in Workout.objects.filter(user=r)
@@ -5772,7 +5792,7 @@ def workout_runkeeperimport_view(request,message=""):
res = runkeeperstuff.get_runkeeper_workout_list(request.user)
if (res.status_code != 200):
if (res.status_code == 401):
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
if (r.runkeepertoken == '') or (r.runkeepertoken is None):
s = "Token doesn't exist. Need to authorize"
return HttpResponseRedirect("/rowers/me/runkeeperauthorize/")
@@ -5809,7 +5829,7 @@ def workout_underarmourimport_view(request,message=""):
res = underarmourstuff.get_underarmour_workout_list(request.user)
if (res.status_code != 200):
if (res.status_code == 401):
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
if (r.underarmourtoken == '') or (r.underarmourtoken is None):
s = "Token doesn't exist. Need to authorize"
return HttpResponseRedirect("/rowers/me/underarmourauthorize/")
@@ -5857,7 +5877,7 @@ def workout_sporttracksimport_view(request,message=""):
res = sporttracksstuff.get_sporttracks_workout_list(request.user)
if (res.status_code != 200):
if (res.status_code == 401):
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
if (r.sporttrackstoken == '') or (r.sporttrackstoken is None):
s = "Token doesn't exist. Need to authorize"
return HttpResponseRedirect("/rowers/me/sporttracksauthorize/")
@@ -5870,7 +5890,7 @@ def workout_sporttracksimport_view(request,message=""):
return HttpResponseRedirect(url)
else:
workouts = []
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
stids = [int(getidfromsturi(item['uri'])) for item in res.json()['items']]
knownstids = uniqify([
w.uploadedtosporttracks for w in Workout.objects.filter(user=r)
@@ -5906,7 +5926,7 @@ def c2listdebug_view(request,message=""):
except C2NoTokenError:
return HttpResponseRedirect("/rowers/me/c2authorize/")
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
res = c2stuff.get_c2_workout_list(request.user)
@@ -5954,7 +5974,7 @@ def workout_getc2workout_all(request,message=""):
message = "Something went wrong in workout_c2import_view (C2 token refresh)"
messages.error(request,message)
else:
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
c2ids = [item['id'] for item in res.json()['data']]
knownc2ids = uniqify([
w.uploadedtoc2 for w in Workout.objects.filter(user=r)
@@ -6011,7 +6031,7 @@ def workout_c2import_view(request,message=""):
return HttpResponseRedirect(url)
else:
workouts = []
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
c2ids = [item['id'] for item in res.json()['data']]
knownc2ids = uniqify([
w.uploadedtoc2 for w in Workout.objects.filter(user=r)
@@ -6141,7 +6161,7 @@ def workout_getsporttracksworkout_view(request,sporttracksid):
def workout_getsporttracksworkout_all(request):
res = sporttracksstuff.get_sporttracks_workout_list(request.user)
if (res.status_code == 200):
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
stids = [int(getidfromsturi(item['uri'])) for item in res.json()['items']]
knownstids = uniqify([
w.uploadedtosporttracks for w in Workout.objects.filter(user=r)
@@ -6170,7 +6190,7 @@ def workout_getsporttracksworkout_all(request):
def workout_getstravaworkout_all(request):
res = stravastuff.get_strava_workout_list(request.user)
if (res.status_code == 200):
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
stravaids = [int(item['id']) for item in res.json()]
knownstravaids = uniqify([
w.uploadedtostrava for w in Workout.objects.filter(user=r)
@@ -6357,7 +6377,7 @@ def workout_upload_view(request,
except KeyError:
upload_totp = False
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
if request.method == 'POST':
form = DocumentsForm(request.POST,request.FILES)
optionsform = UploadOptionsForm(request.POST)
@@ -6431,7 +6451,7 @@ def workout_upload_view(request,
imagename = f1[:-4]+'.png'
fullpathimagename = 'static/plots/'+imagename
u = request.user
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
powerperc = 100*np.array([r.pw_ut2,
r.pw_ut1,
r.pw_at,
@@ -6605,7 +6625,7 @@ def team_workout_upload_view(request,message="",
make_plot = uploadoptions['make_plot']
plottype = uploadoptions['plottype']
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
if request.method == 'POST':
form = DocumentsForm(request.POST,request.FILES)
optionsform = TeamUploadOptionsForm(request.POST)
@@ -6620,7 +6640,7 @@ def team_workout_upload_view(request,message="",
if rowerform.is_valid():
u = rowerform.cleaned_data['user']
if u:
- r = Rower.objects.get(user=u)
+ r = getrower(u)
else:
message = 'Please select a rower'
messages.error(request,message)
@@ -6834,7 +6854,7 @@ def graph_delete_view(request,id=0):
@login_required()
def graphs_view(request):
try:
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
workouts = Workout.objects.filter(user=r).order_by("-date", "-starttime")
query = request.GET.get('q')
if query:
@@ -6892,7 +6912,7 @@ def workout_summary_restore_view(request,id,message="",successmessage=""):
# still here - this is a workout we may edit
f1 = row.csvfilename
u = row.user.user
- r = Rower.objects.get(user=u)
+ r = getrower(u)
powerperc = 100*np.array([r.pw_ut2,
r.pw_ut1,
r.pw_at,
@@ -7010,7 +7030,7 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
# still here - this is a workout we may edit
f1 = row.csvfilename
u = row.user.user
- r = Rower.objects.get(user=u)
+ r = getrower(u)
powerperc = 100*np.array([r.pw_ut2,
r.pw_ut1,
r.pw_at,
@@ -7208,7 +7228,7 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
def rower_favoritecharts_view(request):
message = ''
successmessage = ''
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
favorites = FavoriteChart.objects.filter(user=r).order_by('id')
aantal = len(favorites)
favorites_data = [{'yparam1':f.yparam1,
@@ -7272,7 +7292,7 @@ def rower_favoritecharts_view(request):
# Add email address to form so user can change his email address
@login_required()
def rower_edit_view(request,message=""):
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
if request.method == 'POST' and "ut2" in request.POST:
form = RowerForm(request.POST)
if form.is_valid():
@@ -7286,7 +7306,7 @@ def rower_edit_view(request,message=""):
an = cd['an']
rest = cd['rest']
try:
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
r.max = max(min(hrmax,250),10)
r.ut2 = max(min(ut2,250),10)
r.ut1 = max(min(ut1,250),10)
@@ -7343,7 +7363,7 @@ def rower_edit_view(request,message=""):
ftp = cd['ftp']
otwslack = cd['otwslack']
try:
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
powerfrac = 100*np.array([r.pw_ut2,
r.pw_ut1,
r.pw_at,
@@ -7401,7 +7421,7 @@ def rower_edit_view(request,message=""):
anname = cd['anname']
powerzones = [ut3name,ut2name,ut1name,atname,trname,anname]
try:
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
r.pw_ut2 = pw_ut2
r.pw_ut1 = pw_ut1
r.pw_at = pw_at
@@ -7465,7 +7485,7 @@ def rower_edit_view(request,message=""):
if len(email):
u.email = email
u.save()
- r = Rower.objects.get(user=u)
+ r = getrower(u)
r.weightcategory = weightcategory
r.save()
form = RowerForm(instance=r)
@@ -7501,7 +7521,7 @@ def rower_edit_view(request,message=""):
else:
try:
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
form = RowerForm(instance=r)
powerform = RowerPowerForm(instance=r)
@@ -7536,7 +7556,7 @@ def rower_revokeapp_view(request,id=0):
for token in refreshtokens:
token.revoke()
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
form = RowerForm(instance=r)
powerform = RowerPowerForm(instance=r)
grants = AccessToken.objects.filter(user=request.user)
@@ -7700,7 +7720,7 @@ def strokedatajson(request,id):
row.csvfilename = csvfilename
row.save()
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
powerperc = 100*np.array([r.pw_ut2,
r.pw_ut1,
r.pw_at,
@@ -7733,7 +7753,7 @@ import teams
def team_view(request,id=0):
ismember = 0
hasrequested = 0
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
myteams = Team.objects.filter(manager=request.user)
try:
@@ -7799,7 +7819,7 @@ def team_leaveconfirm_view(request,id=0):
@login_required()
def team_leave_view(request,id=0):
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
teams.remove_member(id,r)
url = reverse(rower_teams_view)
@@ -7822,7 +7842,7 @@ def rower_teams_view(request,message='',successmessage=''):
else:
form = TeamInviteCodeForm()
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
ts = Team.objects.filter(rower=r)
myteams = Team.objects.filter(manager=request.user)
otherteams = Team.objects.filter(private='open').exclude(rower=r).exclude(manager=request.user).order_by('name')
@@ -8050,7 +8070,7 @@ def team_create_view(request):
@user_passes_test(iscoachmember,login_url="/",redirect_field_name=None)
def team_deleteconfirm_view(request,id):
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
try:
t = Team.objects.get(id=id)
except Team.DoesNotExist:
@@ -8066,7 +8086,7 @@ def team_deleteconfirm_view(request,id):
@user_passes_test(iscoachmember,login_url="/",redirect_field_name=None)
def team_delete_view(request,id):
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
try:
t = Team.objects.get(id=id)
except Team.DoesNotExist:
@@ -8082,7 +8102,7 @@ def team_delete_view(request,id):
@user_passes_test(iscoachmember,login_url="/",redirect_field_name=None)
def team_members_stats_view(request,id):
- r = Rower.objects.get(user=request.user)
+ r = getrower(request.user)
try:
t = Team.objects.get(id=id)
except Team.DoesNotExist:
From 832d6f88786471a0534290cf4e3269722cd36400 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Thu, 8 Jun 2017 14:19:21 +0200
Subject: [PATCH 11/11] OTW power CP graph working!
---
rowers/interactiveplots.py | 14 +++-
rowers/templates/analysis.html | 20 +++++
rowers/templates/otwrankings.html | 118 ++++++++++++++++--------------
rowers/views.py | 62 +++++++++-------
4 files changed, 130 insertions(+), 84 deletions(-)
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index 0fdb77c1..0e2e4d23 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -608,6 +608,10 @@ def interactive_otwcpchart(powerdf,promember=0):
x_axis_type = 'log'
y_axis_type = 'linear'
+ deltas = powerdf['Delta'].apply(lambda x: timedeltaconv(x))
+ powerdf['ftime'] = niceformat(deltas)
+
+
source = ColumnDataSource(
data = powerdf
)
@@ -668,7 +672,7 @@ def interactive_otwcpchart(powerdf,promember=0):
)
plot.circle('Delta','CP',source=source,fill_color='red',size=15,
- legend='Power')
+ legend='Power Data')
plot.xaxis.axis_label = "Duration (seconds)"
plot.yaxis.axis_label = "Power (W)"
@@ -676,6 +680,14 @@ def interactive_otwcpchart(powerdf,promember=0):
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')
diff --git a/rowers/templates/analysis.html b/rowers/templates/analysis.html
index 2dc2cf43..9fd83289 100644
--- a/rowers/templates/analysis.html
+++ b/rowers/templates/analysis.html
@@ -77,8 +77,28 @@
+
+
+
+
+
+ {% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
+ OTW Ranking Pieces
+ {% else %}
+ OTW Ranking Pieces
+ {% endif %}
+
+
+ Analyse power vs piece duration to make predictions.
+
+
+
+
+
{% endblock %}
diff --git a/rowers/templates/otwrankings.html b/rowers/templates/otwrankings.html
index 35b35865..a78330e4 100644
--- a/rowers/templates/otwrankings.html
+++ b/rowers/templates/otwrankings.html
@@ -67,9 +67,12 @@
https://rowsandall.com/rowers/{{ id }}/otw-bests/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}
-The table gives the best efforts achieved on the official Concept2 ranking pieces in the selected date range.
-
-This page will evolve and try to give you guidance on where to improve.
+ 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.
+
+At the bottom of the page, you will find predictions derived from the model.
+
+
+
+
+
Critical Power Plot
+
+ {{ the_div|safe }}
+
+
+
Ranking Piece Results
@@ -109,6 +122,7 @@
Distance
Duration
+ Avg Power
Date
Avg HR
Max HR
@@ -118,8 +132,9 @@
{% for workout in rankingworkouts %}
- {{ workout.distance }}
+ {{ workout.distance }} m
{{ workout.duration |durationprint:"%H:%M:%S.%f" }}
+ {{ avgpower|lookup:workout.id }} W
{{ workout.date }}
{{ workout.averagehr }}
{{ workout.maxhr }}
@@ -137,63 +152,56 @@
-
-
-
-
Critical Power Plot
-
- {{ the_div|safe }}
-
-
-
-
Pace predictions for Ranking Pieces
+
Pace predictions for Ranking Pieces
-
Add non-ranking piece using the form. The piece will be added in the prediction tables below.
-
-
-
-
-
-
+
Add non-ranking piece using the form. The piece will be added in the prediction tables below.
-
- No Paul Data
-
-
-
CP Model
-
-
-
- Duration
- Power
-
-
-
- {% for pred in cpredictions %}
-
- {% for key, value in pred.items %}
- {% if key == "power" %}
- {{ value }} W
- {% endif %}
- {% if key == "duration" %}
- {{ value |deltatimeprint }}
- {% endif %}
- {% endfor %}
-
- {% endfor %}
-
-
+
+
+
+
+ Duration
+ Power
+
+
+
+ {% for pred in cpredictions %}
+
+ {% for key, value in pred.items %}
+ {% if key == "power" %}
+ {{ value }} W
+ {% endif %}
+ {% if key == "duration" %}
+ {{ value |deltatimeprint }}
+ {% endif %}
+ {% endfor %}
+
+ {% endfor %}
+
+
-
+
+
+
+
+
+ minutes
+
+
+
+
+
+
+
{% endblock %}
diff --git a/rowers/views.py b/rowers/views.py
index 85f321aa..0c5ae939 100644
--- a/rowers/views.py
+++ b/rowers/views.py
@@ -292,14 +292,11 @@ def iscoachmember(user):
return result
def getrower(user):
- if not user.is_anonymous():
- try:
- r = Rower.objects.get(user=user)
- except Rower.DoesNotExist:
- r = Rower(user=user)
- r.save()
- else:
- raise PermissionDenied("You need to log in to use this function")
+ try:
+ r = Rower.objects.get(user=user)
+ except Rower.DoesNotExist:
+ r = Rower(user=user)
+ r.save()
return r
@@ -2900,8 +2897,10 @@ def otwrankings_view(request,theuser=0,
thesecs.append(timesecs)
-
- maxt = pd.Series(thesecs).max()
+ if len(thesecs) != 0:
+ maxt = pd.Series(thesecs).max()
+ else:
+ maxt = 1000.
maxlog10 = np.log10(maxt)
logarr = np.arange(100)*maxlog10/100.
@@ -2912,11 +2911,16 @@ def otwrankings_view(request,theuser=0,
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 = []
@@ -2946,6 +2950,7 @@ def otwrankings_view(request,theuser=0,
for d in logarr:
delta.append(d)
+ print avgpower
dt = pd.Series(delta,name='Delta')
cpvalue = pd.Series(cpvalue,name='CP')
@@ -2955,13 +2960,15 @@ def otwrankings_view(request,theuser=0,
'CP':cpvalue,
})
- powerdf.sort_values(['Delta','CP'],ascending=[1,0])
- powerdf.drop_duplicates(subset='Delta',keep='first')
+ 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)
+ res = interactive_otwcpchart(powerdf,promember=promember)
script = res[0]
div = res[1]
p1 = res[2]
@@ -2979,16 +2986,12 @@ def otwrankings_view(request,theuser=0,
if request.method == 'POST' and "piece" in request.POST:
form = PredictedPieceForm(request.POST)
- if form.is_valid():
- value = form.cleaned_data['value']
- hourvalue,value = divmod(value,60)
- if hourvalue >= 24:
- hourvalue = 23
- pieceunit = form.cleaned_data['pieceunit']
- if pieceunit == 'd':
- rankingdistances.append(value)
- else:
- rankingdurations.append(datetime.time(minute=value,hour=hourvalue))
+ 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()
@@ -3012,13 +3015,15 @@ def otwrankings_view(request,theuser=0,
if pwr <= 0:
pwr = 50.
- a = {
- 'duration':timedeltaconv(t),
- 'power':int(pwr)}
- cpredictions.append(a)
+ if not np.isnan(pwr):
+ a = {
+ 'duration':timedeltaconv(t),
+ 'power':int(pwr)}
+ cpredictions.append(a)
- print cpredictions
+ del form.fields["pieceunit"]
+
messages.error(request,message)
return render(request, 'otwrankings.html',
{'rankingworkouts':theworkouts,
@@ -3026,6 +3031,7 @@ def otwrankings_view(request,theuser=0,
'the_div':div,
'predictions':predictions,
'cpredictions':cpredictions,
+ 'avgpower':avgpower,
'form':form,
'dateform':dateform,
'deltaform':deltaform,