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/dataprep.py b/rowers/dataprep.py index c244c7af..b76ed1a2 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,10 +418,13 @@ 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: - #row.repair() + if not allchecks and consistencychecks: + # row.repair() pass @@ -529,6 +533,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) @@ -781,7 +786,8 @@ def new_workout_from_df(r,df, oarlength=oarlength, inboard=inboard, makeprivate=makeprivate, - dosmooth=False) + dosmooth=False, + consistencychecks=False) return (id,message) diff --git a/rowers/dataprepnodjango.py b/rowers/dataprepnodjango.py index fd8c6d55..78d3903b 100644 --- a/rowers/dataprepnodjango.py +++ b/rowers/dataprepnodjango.py @@ -243,6 +243,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' diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 7f640cee..0e2e4d23 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -596,6 +596,106 @@ 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' + + deltas = powerdf['Delta'].apply(lambda x: timedeltaconv(x)) + powerdf['ftime'] = niceformat(deltas) + + + source = ColumnDataSource( + data = powerdf + ) + + # there is no Paul's law for OTW + + # Fit the data to thee parameter CP model + fitfunc = lambda pars,x: pars[0]/(1+(x/pars[2])) + pars[1]/(1+(x/pars[3])) + errfunc = lambda pars,x,y: fitfunc(pars,x)-y + + p0 = [500,350,10,8000] + + p1 = p0 + + thesecs = powerdf['Delta'] + theavpower = powerdf['CP'] + + if len(thesecs)>=4: + p1, success = optimize.leastsq(errfunc, p0[:], args = (thesecs,theavpower)) + else: + factor = fitfunc(p0,thesecs.mean())/theavpower.mean() + p1 = [p0[0]/factor,p0[1]/factor,p0[2],p0[3]] + + + fitt = pd.Series(10**(4*np.arange(100)/100.)) + + fitpower = fitfunc(p1,fitt) + + message = "" + #if len(fitpower[fitpower<0]) > 0: + # message = "CP model fit didn't give correct results" + + + sourcecomplex = ColumnDataSource( + data = dict( + power = fitpower, + duration = fitt + ) + ) + + # making the plot + plot = Figure(tools=TOOLS,x_axis_type=x_axis_type, + plot_width=900, + toolbar_location="above", + toolbar_sticky=False) + + # add watermark + plot.extra_y_ranges = {"watermark": watermarkrange} + + plot.image_url([watermarkurl],1.8*max(thesecs),watermarky, + watermarkw,watermarkh, + global_alpha=watermarkalpha, + w_units='screen', + h_units='screen', + anchor=watermarkanchor, + dilate=True, + y_range_name = "watermark", + ) + + plot.circle('Delta','CP',source=source,fill_color='red',size=15, + legend='Power Data') + plot.xaxis.axis_label = "Duration (seconds)" + plot.yaxis.axis_label = "Power (W)" + + plot.y_range = Range1d(0,1.5*max(theavpower)) + plot.x_range = Range1d(1,2*max(thesecs)) + plot.legend.orientation = "vertical" + + hover = plot.select(dict(type=HoverTool)) + + hover.tooltips = OrderedDict([ + ('Duration ','@ftime'), + ('Power (W)','@CP{int}'), + ]) + + hover.mode = 'mouse' + + plot.line('duration','power',source=sourcecomplex,legend="CP Model", + color='green') + + script, div = components(plot) + + return [script,div,p1,message] + def interactive_cpchart(thedistances,thesecs,theavpower, theworkouts,promember=0): 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') 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/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/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 %} +
+ + GPX Export +
diff --git a/rowers/templates/otwrankings.html b/rowers/templates/otwrankings.html new file mode 100644 index 00000000..a78330e4 --- /dev/null +++ b/rowers/templates/otwrankings.html @@ -0,0 +1,207 @@ +{% 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 %} + +
+ +
+

Summary for {{ theuser.first_name }} {{ theuser.last_name }} + between {{ startdate|date }} and {{ enddate|date }}

+ +

Direct link for other users: + https://rowsandall.com/rowers/{{ id }}/otw-bests/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }} +

+ +

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.

+
+
+

Use this form to select a different date range:

+

+ Select start and end date for a date range: +

+ +
+ + + {{ dateform.as_table }} +
+ {% csrf_token %} +
+
+ +
+
+
+ Or use the last {{ deltaform }} days. +
+
+ {% csrf_token %} + + +
+
+ + + +
+ +

Critical Power Plot

+ + {{ the_div|safe }} + +
+ +
+ +

Ranking Piece Results

+ + {% if rankingworkouts %} + + + + + + + + + + + + + + + {% for workout in rankingworkouts %} + + + + + + + + + + + + {% endfor %} + +
Distance Duration Avg Power Date Avg HR Max HR Edit
{{ workout.distance }} m {{ workout.duration |durationprint:"%H:%M:%S.%f" }} {{ avgpower|lookup:workout.id }} W {{ workout.date }} {{ workout.averagehr }} {{ workout.maxhr }} + {{ workout.name }}
+ {% else %} +

No ranking workouts found

+ {% endif %} + +
+ +
+

Pace predictions for Ranking Pieces

+ +

Add non-ranking piece using the form. The piece will be added in the prediction tables below.

+ + + +
+ + + + + + + + + {% for pred in cpredictions %} + + {% for key, value in pred.items %} + {% if key == "power" %} + + {% endif %} + {% if key == "duration" %} + + {% endif %} + {% endfor %} + + {% endfor %} + +
Duration Power
{{ value }} W {{ value |deltatimeprint }}
+ +
+ +
+
+ {{ form.value }} {{ form.pieceunit }} + + {% csrf_token %} +
+
+ minutes +
+
+ + +
+ + +
+ +{% endblock %} diff --git a/rowers/urls.py b/rowers/urls.py index 33f953f7..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), @@ -169,6 +175,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..0c5ae939 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' @@ -277,17 +279,36 @@ 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): + try: + r = Rower.objects.get(user=user) + except Rower.DoesNotExist: + r = Rower(user=user) + r.save() + + 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 +427,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 +591,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 +755,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 +918,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,14 +1078,14 @@ 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: 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,11 +1137,56 @@ 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): message = "" - r = Rower.objects.get(user=request.user) + r = getrower(request.user) try: w = Workout.objects.get(id=id) except Workout.DoesNotExist: @@ -1160,7 +1226,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 +1261,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 +1319,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 +1740,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 +1765,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 +1775,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 +1790,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 +1799,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 +1814,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 +1823,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 +1857,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 +1909,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 +1928,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 +1949,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 +1972,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 +1995,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 +2034,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 +2045,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 +2118,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 +2192,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 +2276,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 +2337,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 +2390,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 +2437,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 +2501,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 +2547,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 @@ -2708,6 +2774,274 @@ def rankings_view(request,theuser=0, 'teams':get_my_teams(request.user), }) +# Show ranking distances including predicted paces +@login_required() +def otwrankings_view(request,theuser=0, + startdate=timezone.now()-datetime.timedelta(days=365), + enddate=timezone.now(), + deltadays=-1, + startdatestring="", + enddatestring=""): + + if deltadays>0: + startdate = enddate-datetime.timedelta(days=int(deltadays)) + + if startdatestring != "": + startdate = iso8601.parse_date(startdatestring) + + if enddatestring != "": + enddate = iso8601.parse_date(enddatestring) + + if enddate < startdate: + s = enddate + enddate = startdate + startdate = s + + if theuser == 0: + theuser = request.user.id + + promember=0 + if not request.user.is_anonymous(): + r = Rower.objects.get(user=request.user) + result = request.user.is_authenticated() and ispromember(request.user) + if result: + promember=1 + + # get all OTW rows in date range + + # process form + if request.method == 'POST' and "daterange" in request.POST: + dateform = DateRangeForm(request.POST) + deltaform = DeltaDaysForm(request.POST) + if dateform.is_valid(): + startdate = dateform.cleaned_data['startdate'] + enddate = dateform.cleaned_data['enddate'] + if startdate > enddate: + s = enddate + enddate = startdate + startdate = s + elif request.method == 'POST' and "datedelta" in request.POST: + deltaform = DeltaDaysForm(request.POST) + if deltaform.is_valid(): + deltadays = deltaform.cleaned_data['deltadays'] + if deltadays: + enddate = timezone.now() + startdate = enddate-datetime.timedelta(days=deltadays) + if startdate > enddate: + s = enddate + enddate = startdate + startdate = s + dateform = DateRangeForm(initial={ + 'startdate': startdate, + 'enddate': enddate, + }) + else: + dateform = DateRangeForm() + deltaform = DeltaDaysForm() + + else: + dateform = DateRangeForm(initial={ + 'startdate': startdate, + 'enddate': enddate, + }) + deltaform = DeltaDaysForm() + + # get all 2k (if any) - this rower, in date range + try: + r = Rower.objects.get(user=theuser) + except Rower.DoesNotExist: + allergworkouts = [] + r=0 + + + try: + uu = User.objects.get(id=theuser) + except User.DoesNotExist: + uu = '' + + + # test to fix bug + startdate = datetime.datetime.combine(startdate,datetime.time()) + enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59)) + enddate = enddate+datetime.timedelta(days=1) + + + rankingdurations = [] + rankingdurations.append(datetime.time(minute=1)) + rankingdurations.append(datetime.time(minute=4)) + rankingdurations.append(datetime.time(minute=30)) + rankingdurations.append(datetime.time(hour=1)) + + thedistances = [] + theworkouts = [] + thesecs = [] + + theworkouts = Workout.objects.filter(user=r,rankingpiece=True, + workouttype='water', + startdatetime__gte=startdate, + startdatetime__lte=enddate) + + + # get all power data from database (plus workoutid) + theids = [w.id for w in theworkouts] + columns = ['power','workoutid','time'] + df = dataprep.getsmallrowdata_db(columns,ids=theids) + + thesecs = [] + + for w in theworkouts: + timesecs = 3600*w.duration.hour + timesecs += 60*w.duration.minute + timesecs += w.duration.second + timesecs += 1.e-5*w.duration.microsecond + + thesecs.append(timesecs) + + if len(thesecs) != 0: + maxt = pd.Series(thesecs).max() + else: + maxt = 1000. + + maxlog10 = np.log10(maxt) + logarr = np.arange(100)*maxlog10/100. + logarr = [int(10.**(la)) for la in logarr] + logarr = pd.Series(logarr) + logarr.drop_duplicates(keep='first',inplace=True) + logarr = logarr.values + + delta = [] + cpvalue = [] + avgpower = {} + + dfgrouped = df.groupby(['workoutid']) + for id,group in dfgrouped: + tt = group['time'] + ww = group['power'] + try: + avgpower[id] = int(ww.mean()) + except ValueError: + avgpower[id] = '---' + if not np.isnan(ww.mean()): + length = len(ww) + dt = [] + cpw = [] + for i in range(length-2): + w_roll = ww.rolling(i+2,min_periods=2).mean() + # now goes with # data points - should be fixed seconds + indexmax = w_roll.idxmax(axis=1) + try: + t_0 = tt.ix[indexmax] + t_1 = tt.ix[indexmax-i-2] + deltat = 1.0e-3*(t_0-t_1) + wmax = w_roll.ix[indexmax] + dt.append(deltat) + cpw.append(wmax) + except KeyError: + pass + + dt = pd.Series(dt) + cpw = pd.Series(cpw) + cpvalues = griddata(dt.values, + cpw.values, + logarr,method='linear',fill_value=0) + + for cpv in cpvalues: + cpvalue.append(cpv) + for d in logarr: + delta.append(d) + + print avgpower + dt = pd.Series(delta,name='Delta') + cpvalue = pd.Series(cpvalue,name='CP') + + + powerdf = pd.DataFrame({ + 'Delta':delta, + 'CP':cpvalue, + }) + + powerdf = powerdf[powerdf['CP']>0] + powerdf.dropna(axis=0,inplace=True) + powerdf.sort_values(['Delta','CP'],ascending=[1,0],inplace=True) + powerdf.drop_duplicates(subset='Delta',keep='first',inplace=True) + + + # create interactive plot + if len(powerdf) !=0 : + res = interactive_otwcpchart(powerdf,promember=promember) + script = res[0] + div = res[1] + p1 = res[2] + paulslope = 1 + paulintercept = 1 + message = res[3] + else: + script = '' + div = '

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) + clean = form.is_valid() + value = form.cleaned_data['value'] + hourvalue,value = divmod(value,60) + if hourvalue >= 24: + hourvalue = 23 + rankingdurations.append(datetime.time(minute=value,hour=hourvalue)) + else: + form = PredictedPieceForm() + + + predictions = [] + cpredictions = [] + + + + for rankingduration in rankingdurations: + t = 3600.*rankingduration.hour + t += 60.*rankingduration.minute + t += rankingduration.second + t += rankingduration.microsecond/1.e6 + + + # CP model + pwr = p1[0]/(1+t/p1[2]) + pwr += p1[1]/(1+t/p1[3]) + + if pwr <= 0: + pwr = 50. + + if not np.isnan(pwr): + a = { + 'duration':timedeltaconv(t), + 'power':int(pwr)} + cpredictions.append(a) + + + del form.fields["pieceunit"] + + messages.error(request,message) + return render(request, 'otwrankings.html', + {'rankingworkouts':theworkouts, + 'interactiveplot':script, + 'the_div':div, + 'predictions':predictions, + 'cpredictions':cpredictions, + 'avgpower':avgpower, + 'form':form, + 'dateform':dateform, + 'deltaform':deltaform, + 'id': theuser, + 'theuser':uu, + 'startdate':startdate, + 'enddate':enddate, + 'teams':get_my_teams(request.user), + }) + # Reload the workout and calculate the summary from the stroke data (lapIDx) @login_required() def workout_recalcsummary_view(request,id=0): @@ -2762,7 +3096,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 +3155,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 +3240,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 +3331,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 +3591,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 +3692,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 +3772,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 +4172,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 +4293,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 +4460,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 +4530,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 +4598,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 +4739,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 +4894,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 +4927,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 +4962,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 +5038,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 +5113,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 +5221,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 +5265,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 @@ -5187,6 +5519,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 +5540,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: @@ -5247,7 +5585,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 +5652,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 +5713,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 +5774,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 +5833,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 +5893,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 +5951,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 +6011,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 +6060,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 +6073,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 +6110,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 +6147,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 +6195,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 +6208,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 +6244,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 +6292,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 +6349,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 +6479,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 +6508,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 +6695,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 +6769,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 +6943,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 +6958,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 +7172,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 +7230,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 +7348,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 +7546,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 +7610,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 +7624,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 +7681,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 +7739,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 +7803,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 +7839,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 +7874,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 +8038,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 +8071,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 +8137,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 +8160,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 +8388,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 +8404,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 +8420,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: diff --git a/static/img/gpx.jpg b/static/img/gpx.jpg new file mode 100644 index 00000000..b81a5ef4 Binary files /dev/null and b/static/img/gpx.jpg differ