diff --git a/rowers/dataprep.py b/rowers/dataprep.py index b6ace874..dbc7ff7b 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -8,6 +8,7 @@ from pandas import DataFrame,Series import pandas as pd import numpy as np +import itertools from django.conf import settings from sqlalchemy import create_engine @@ -84,9 +85,33 @@ def rdata(file,rower=rrower()): def getrowdata_db(id=0): data = read_df_sql(id) + data['pace'] = data['pace']/1.0e6 + data['ergpace'] = data['ergpace']/1.0e6 + data['nowindpace'] = data['nowindpace']/1.0e6 + data['time'] = data['time']/1.0e6 + data['x_right'] = data['x_right']/1.0e6 if data.empty: rowdata,row = getrowdata(id=id) - data = dataprep(rowdata.df,id=id,bands=True,barchart=True,otwpower=True) + if rowdata: + data = dataprep(rowdata.df,id=id,bands=True,barchart=True,otwpower=True) + else: + data = pd.DataFrame() # returning empty dataframe + else: + row = Workout.objects.get(id=id) + + return data,row + +def getsmallrowdata_db(xparam,yparam1,yparam2,ids=[]): + if yparam2 == 'None': + yparam2 = yparam1 + prepmultipledata(ids) + data = read_cols_df_sql(ids,xparam,yparam1,yparam2) + if xparam == 'time': + data['time'] = data['time']/1.0e6 + if yparam1 == 'pace': + data['pace'] = data['pace']/1.0e6 + if yparam2 == 'pace': + data['pace'] = data['pace']/1.0e6 return data @@ -110,7 +135,34 @@ def getrowdata(id=0): return rowdata,row -# temporary +def prepmultipledata(ids): + query = sa.text('SELECT DISTINCT workoutid FROM strokedata') + with engine.connect() as conn, conn.begin(): + res = conn.execute(query) + res = list(itertools.chain.from_iterable(res.fetchall())) + + res = list(set(ids)-set(res)) + + for id in res: + rowdata,row = getrowdata(id=id) + if rowdata: + data = dataprep(rowdata.df,id=id,bands=True,barchart=True,otwpower=True) + return res + +def read_cols_df_sql(ids,col1,col2,col3): + columns = list(set((col1,col2,col3,'distance','spm'))) + cls = '' + for column in columns: + cls += column+', ' + cls = cls[:-2] + query = sa.text('SELECT {columns} FROM strokedata WHERE workoutid IN {ids}'.format( + columns = cls, + ids = tuple(ids), + )) + df = pd.read_sql_query(query,engine) + return df + + def read_df_sql(id): df = pd.read_sql_query(sa.text('SELECT * FROM strokedata WHERE workoutid={id}'.format( id=id)), engine) @@ -118,6 +170,37 @@ def read_df_sql(id): + + +def smalldataprep(therows,xparam,yparam1,yparam2): + df = pd.DataFrame() + if yparam2 == 'None': + yparam2 = 'power' + df[xparam] = [] + df[yparam1] = [] + df[yparam2] = [] + df['distance'] = [] + df['spm'] = [] + for workout in therows: + f1 = workout.csvfilename + + try: + rowdata = dataprep(rrdata(f1).df) + + rowdata = pd.DataFrame({xparam: rowdata[xparam], + yparam1: rowdata[yparam1], + yparam2: rowdata[yparam2], + 'distance': rowdata['distance'], + 'spm': rowdata['spm'], + } + ) + df = pd.concat([df,rowdata],ignore_index=True) + except IOError: + pass + + return df + + def dataprep(rowdatadf,id=0,bands=False,barchart=False,otwpower=False): rowdatadf.set_index([range(len(rowdatadf))],inplace=True) t = rowdatadf.ix[:,'TimeStamp (sec)'] @@ -134,8 +217,11 @@ def dataprep(rowdatadf,id=0,bands=False,barchart=False,otwpower=False): power = rowdatadf.ix[:,' Power (watts)'] averageforce = rowdatadf.ix[:,' AverageDriveForce (lbs)'] drivelength = rowdatadf.ix[:,' DriveLength (meters)'] - - + try: + workoutstate = rowdatadf.ix[:,' WorkoutState'] + except KeyError: + workoutstate = 0*hr + peakforce = rowdatadf.ix[:,' PeakDriveForce (lbs)'] forceratio = averageforce/peakforce @@ -152,7 +238,10 @@ def dataprep(rowdatadf,id=0,bands=False,barchart=False,otwpower=False): drivelength = savgol_filter(drivelength,windowsize,3) forceratio = savgol_filter(forceratio,windowsize,3) - t2 = t.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) + try: + t2 = t.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) + except TypeError: + t2 = 0*t p2 = p.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) @@ -179,6 +268,7 @@ def dataprep(rowdatadf,id=0,bands=False,barchart=False,otwpower=False): fpace = nicepaceformat(p2), driveenergy=driveenergy, power=power, + workoutstate=workoutstate, averageforce=averageforce, drivelength=drivelength, peakforce=peakforce, diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index e27a6105..d8a80dab 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -5,9 +5,11 @@ from rowingdata import cumcpdata,histodata from rowingdata import rowingdata as rrdata +from django.utils import timezone + from bokeh.plotting import figure, ColumnDataSource, Figure,curdoc from bokeh.models import CustomJS,Slider -from bokeh.charts import Histogram +from bokeh.charts import Histogram,HeatMap from bokeh.resources import CDN,INLINE from bokeh.embed import components from bokeh.layouts import layout,widgetbox @@ -68,14 +70,13 @@ from rowers.dataprep import timedeltaconv def interactive_histoall(theworkouts): TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' - therows = [] - for workout in theworkouts: - f1 = workout.csvfilename - rowdata = rdata(f1) - if rowdata != 0: - therows.append(rowdata) - - histopwr = histodata(therows) + ids = [w.id for w in theworkouts] + rowdata = dataprep.getsmallrowdata_db('power','power','power',ids=ids) + + histopwr = rowdata['power'].values + if len(histopwr) == 0: + return "","CSV file not found","","" + # throw out nans histopwr = histopwr[~np.isinf(histopwr)] histopwr = histopwr[histopwr > 25] @@ -494,18 +495,15 @@ def interactive_chart(id=0,promember=0): else: TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' - rowdata,row = dataprep.getrowdata(id=id) - if rowdata == 0: + + datadf,row = dataprep.getrowdata_db(id=id) + if datadf.empty: return "","CSV Data File Not Found" - datadf = dataprep.dataprep(rowdata.df) - source = ColumnDataSource( datadf ) - - plot = Figure(x_axis_type="datetime",y_axis_type="datetime", plot_width=400, plot_height=400, @@ -552,10 +550,10 @@ def interactive_chart(id=0,promember=0): hover.mode = 'mouse' - plot.extra_y_ranges = {"hr": Range1d(start=100,end=200)} + plot.extra_y_ranges = {"hrax": Range1d(start=100,end=200)} plot.line('time','hr',source=source,color="red", - y_range_name="hr", legend="Heart Rate") - plot.add_layout(LinearAxis(y_range_name="hr",axis_label="HR"),'right') + y_range_name="hrax", legend="Heart Rate") + plot.add_layout(LinearAxis(y_range_name="hrax",axis_label="HR"),'right') plot.legend.location = "bottom_right" @@ -563,23 +561,15 @@ def interactive_chart(id=0,promember=0): return [script,div] -def interactive_cum_flex_chart(theworkouts,promember=0, - xparam='spm', - yparam1='power', - yparam2='hr', - ): - - - therows = [] - for workout in theworkouts: - f1 = workout.csvfilename - rowdata = rdata(f1) - if rowdata != 0: - therows.append(rowdata.df) - - datadf = pd.concat(therows) - +def interactive_cum_flex_chart2(theworkouts,promember=0, + xparam='spm', + yparam1='power', + yparam2='spm'): + # datadf = dataprep.smalldataprep(theworkouts,xparam,yparam1,yparam2) + ids = [w.id for w in theworkouts] + datadf = dataprep.getsmallrowdata_db(xparam,yparam1,yparam2,ids=ids) + axlabels = { 'time': 'Time', 'distance': 'Distance (m)', @@ -626,21 +616,11 @@ def interactive_cum_flex_chart(theworkouts,promember=0, } - datadf = dataprep.dataprep(datadf) - if yparam1 != 'pace' and yparam1 != 'time': - datadf = datadf[datadf[yparam1] > 0] - elif yparam1 == 'time': - datadf = datadf[datadf['timesecs'] > 0] - else: - datadf = datadf[datadf['pseconds']>0] + datadf = datadf[datadf[yparam1] > 0] - if xparam != 'time' and xparam != 'pace': - datadf = datadf[datadf[xparam] > 0] - elif xparam == 'time': - datadf = datadf[datadf['timesecs']>0] - else: - datadf = datadf[datadf['pseconds']>0] + + datadf = datadf[datadf[xparam] > 0] if yparam2 != 'None': datadf = datadf[datadf[yparam2] > 0] @@ -651,9 +631,7 @@ def interactive_cum_flex_chart(theworkouts,promember=0, datadf['x1'] = datadf.ix[:,xparam] - tseconds = datadf.ix[:,'timesecs'] - - + datadf['y1'] = datadf.ix[:,yparam1] if yparam2 != 'None': datadf['y2'] = datadf.ix[:,yparam2] @@ -661,12 +639,7 @@ def interactive_cum_flex_chart(theworkouts,promember=0, datadf['y2'] = datadf['y1'] - if xparam=='time': - xaxmax = tseconds.max() - xaxmin = tseconds.min() - xaxmax = 1.0e3*xaxmax - xaxmin = 1.0e3*xaxmin - elif xparam=='distance': + if xparam=='distance': xaxmax = datadf['x1'].max() xaxmin = datadf['x1'].min() else: @@ -674,27 +647,16 @@ def interactive_cum_flex_chart(theworkouts,promember=0, xaxmin = yaxminima[xparam] # average values - if xparam != 'time': - x1mean = datadf['x1'].mean() - else: - x1mean = 0 + x1mean = datadf['x1'].mean() y1mean = datadf['y1'].mean() y2mean = datadf['y2'].mean() - if xparam != 'time': - xvals = pd.Series(xaxmin+np.arange(100)*(xaxmax-xaxmin)/100.) - else: - xvals = pd.Series(np.arange(100)) + + xvals = pd.Series(xaxmin+np.arange(100)*(xaxmax-xaxmin)/100.) x_axis_type = 'linear' y_axis_type = 'linear' - if xparam == 'time': - x_axis_type = 'datetime' - - if yparam1 == 'pace': - y_axis_type = 'datetime' - y1mean = datadf.ix[:,'pseconds'].mean() datadf['xname'] = xparam datadf['yname1'] = yparam1 @@ -703,6 +665,7 @@ def interactive_cum_flex_chart(theworkouts,promember=0, else: datadf['yname2'] = yparam1 + source = ColumnDataSource( datadf ) @@ -713,9 +676,9 @@ def interactive_cum_flex_chart(theworkouts,promember=0, # Add hover to this comma-separated string and see what changes if (promember==1): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,resize,crosshair' else: - TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' + TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,crosshair' plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type, tools=TOOLS, @@ -735,9 +698,9 @@ def interactive_cum_flex_chart(theworkouts,promember=0, text_color='green', ) - if (xparam != 'time') and (xparam != 'distance'): - plot.add_layout(x1means) - plot.add_layout(xlabel) + + plot.add_layout(x1means) + plot.add_layout(xlabel) plot.add_layout(y1means) @@ -746,8 +709,8 @@ def interactive_cum_flex_chart(theworkouts,promember=0, background_fill_alpha=.7, text_color='blue', ) - if yparam1 != 'time' and yparam1 != 'pace': - plot.add_layout(y1label) + + plot.add_layout(y1label) y2label = y1label plot.circle('x1','y1',source=source2,fill_alpha=0.3,line_color=None, @@ -761,29 +724,9 @@ def interactive_cum_flex_chart(theworkouts,promember=0, yrange1 = Range1d(start=yaxminima[yparam1],end=yaxmaxima[yparam1]) plot.y_range = yrange1 - if (xparam != 'time') and (xparam != 'distance'): - xrange1 = Range1d(start=yaxminima[xparam],end=yaxmaxima[xparam]) - plot.x_range = xrange1 + xrange1 = Range1d(start=yaxminima[xparam],end=yaxmaxima[xparam]) + plot.x_range = xrange1 - if xparam == 'time': - xrange1 = Range1d(start=xaxmin,end=xaxmax) - plot.x_range = xrange1 - plot.xaxis[0].formatter = DatetimeTickFormatter( - hours = ["%H"], - minutes = ["%M"], - seconds = ["%S"], - days = ["0"], - months = [""], - years = [""] - ) - - - - if yparam1 == 'pace': - plot.yaxis[0].formatter = DatetimeTickFormatter( - seconds = ["%S"], - minutes = ["%M"] - ) if yparam2 != 'None': yrange2 = Range1d(start=yaxminima[yparam2],end=yaxmaxima[yparam2]) @@ -809,18 +752,6 @@ def interactive_cum_flex_chart(theworkouts,promember=0, if yparam2 != 'pace' and yparam2 != 'time': plot.add_layout(y2label) - hover = plot.select(dict(type=HoverTool)) - - - hover.tooltips = OrderedDict([ - ('Time','@ftime'), - ('Pace','@fpace'), - ('HR','@hr{int}'), - ('SPM','@spm{1.1}'), - ('Power','@power{int}'), - ]) - - hover.mode = 'mouse' callback = CustomJS(args = dict(source=source,source2=source2, x1means=x1means, @@ -835,11 +766,7 @@ def interactive_cum_flex_chart(theworkouts,promember=0, var y1 = data['y1'] var y2 = data['y2'] var spm1 = data['spm'] - var time1 = data['time'] - var pace1 = data['pace'] - var hr1 = data['hr'] var distance1 = data['distance'] - var power1 = data['power'] var xname = data['xname'][0] var yname1 = data['yname1'][0] var yname2 = data['yname2'][0] @@ -856,11 +783,7 @@ def interactive_cum_flex_chart(theworkouts,promember=0, data2['y1'] = [] data2['y2'] = [] data2['spm'] = [] - data2['time'] = [] - data2['pace'] = [] - data2['hr'] = [] data2['distance'] = [] - data2['power'] = [] data2['x1mean'] = [] data2['y1mean'] = [] data2['y2mean'] = [] @@ -875,11 +798,7 @@ def interactive_cum_flex_chart(theworkouts,promember=0, data2['y1'].push(y1[i]) data2['y2'].push(y2[i]) data2['spm'].push(spm1[i]) - data2['time'].push(time1[i]) - data2['pace'].push(pace1[i]) - data2['hr'].push(hr1[i]) data2['distance'].push(distance1[i]) - data2['power'].push(power1[i]) xm += x1[i] ym1 += y1[i] @@ -944,6 +863,7 @@ def interactive_cum_flex_chart(theworkouts,promember=0, + def interactive_flex_chart2(id=0,promember=0, xparam='time', yparam1='pace', @@ -994,8 +914,8 @@ def interactive_flex_chart2(id=0,promember=0, 'drivespeed':4, } - rowdata,row = dataprep.getrowdata(id=id) - if rowdata == 0: + rowdata,row = dataprep.getrowdata_db(id=id) + if rowdata.empty: return "","CSV Data File Not Found" workoutstateswork = [1,4,5,8,9,6,7] @@ -1004,16 +924,10 @@ def interactive_flex_chart2(id=0,promember=0, if workstrokesonly: try: - rowdata.df = rowdata.df[~rowdata.df[' WorkoutState'].isin(workoutstatesrest)] + rowdata = rowdata[~rowdata['workoutstate'].isin(workoutstatesrest)] except KeyError: pass - rowdata = dataprep.dataprep(rowdata.df) - - - # get user - # u = User.objects.get(id=row.user.id) - rowdata['x1'] = rowdata.ix[:,xparam] rowdata['y1'] = rowdata.ix[:,yparam1] @@ -1336,15 +1250,10 @@ def interactive_flex_chart2(id=0,promember=0, def interactive_bar_chart(id=0,promember=0): # check if valid ID exists (workout exists) - rowdata,row = dataprep.getrowdata(id=id) - if rowdata == 0: + rowdata,row = dataprep.getrowdata_db(id=id) + if rowdata.empty: return "","CSV Data File Not Found" - rowdata = dataprep.dataprep(rowdata.df,bands=True,barchart=True) - - - - # Add hover to this comma-separated string and see what changes if (promember==1): TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' @@ -1355,8 +1264,6 @@ def interactive_bar_chart(id=0,promember=0): rowdata ) - - plot = Figure(x_axis_type="datetime",y_axis_type="datetime", toolbar_sticky=False, plot_width=920, @@ -1453,19 +1360,14 @@ def interactive_comparison_chart(id1=0,id2=0,xparam='distance',yparam='spm', # check if valid ID exists (workout exists) - rowdata1,row1 = dataprep.getrowdata(id=id1) - rowdata2,row2 = dataprep.getrowdata(id=id2) - if rowdata1 == 0: + rowdata1,row1 = dataprep.getrowdata_db(id=id1) + rowdata2,row2 = dataprep.getrowdata_db(id=id2) + if rowdata1.empty: return "","CSV Data File Not Found" - if rowdata2 == 0: + if rowdata2.empty: return "","CSV Data File Not Found" - rowdata1 = dataprep.dataprep(rowdata1.df) - rowdata2 = dataprep.dataprep(rowdata2.df) - - - x1 = rowdata1.ix[:,xparam] x2 = rowdata2.ix[:,xparam] @@ -1601,12 +1503,10 @@ def interactive_comparison_chart(id1=0,id2=0,xparam='distance',yparam='spm', def interactive_otw_advanced_pace_chart(id=0,promember=0): # check if valid ID exists (workout exists) - rowdata,row = dataprep.getrowdata(id=id) - if rowdata == 0: + rowdata,row = dataprep.getrowdata_db(id=id) + if rowdata.empty: return "","CSV Data File Not Found" - rowdata = dataprep.dataprep(rowdata.df,otwpower=True) - # Add hover to this comma-separated string and see what changes if (promember==1): TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' diff --git a/rowers/models.py b/rowers/models.py index 7c025310..4abb804d 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -9,8 +9,31 @@ from django.forms.widgets import SplitDateTimeWidget from datetimewidget.widgets import DateTimeWidget import os -# Create your models here. +from django.conf import settings +from sqlalchemy import create_engine +import sqlalchemy as sa +from sqlite3 import OperationalError +user = settings.DATABASES['default']['USER'] +password = settings.DATABASES['default']['PASSWORD'] +database_name = settings.DATABASES['default']['NAME'] +host = settings.DATABASES['default']['HOST'] +port = settings.DATABASES['default']['PORT'] + +database_url = 'mysql://{user}:{password}@{host}:{port}/{database_name}'.format( + user=user, + password=password, + database_name=database_name, + host=host, + port=port, + ) + +if settings.DEBUG: + database_url = 'sqlite:///db.sqlite3' + +engine = create_engine(database_url, echo=False) + +# Create your models here. class Team(models.Model): name = models.CharField(max_length=150) notes = models.CharField(blank=True,max_length=200) @@ -120,6 +143,17 @@ def auto_delete_file_on_delete(sender, instance, **kwargs): if os.path.isfile(instance.csvfilename): os.remove(instance.csvfilename) +@receiver(models.signals.post_delete,sender=Workout) +def auto_delete_strokedata_on_delete(sender, instance, **kwargs): + if instance.id: + query = sa.text('DELETE FROM strokedata WHERE workoutid={id};'.format( + id=instance.id, + )) + with engine.connect() as conn, conn.begin(): + try: + result = conn.execute(query) + except: + print "Database Locked" class StrokeData(models.Model): class Meta: @@ -130,6 +164,7 @@ class StrokeData(models.Model): timesecs = models.FloatField(null=True) hr = models.IntegerField(null=True) pace = models.TimeField(null=True) + workoutstate = models.IntegerField(null=True,default=1) pseconds = models.FloatField(null=True) spm = models.FloatField(null=True) cumdist = models.FloatField(null=True) diff --git a/rowers/templates/advancededit.html b/rowers/templates/advancededit.html index fc9bbedb..0e40362b 100644 --- a/rowers/templates/advancededit.html +++ b/rowers/templates/advancededit.html @@ -7,115 +7,132 @@ {% block content %}
- Please correct the error{{ form.errors|pluralize }} below. -
- {% endif %} + {% if form.errors %} ++ Please correct the error{{ form.errors|pluralize }} below. +
+ {% endif %} -This is a preview of the page with advanced functionality for Pro users. See the page about Pro membership for more information and to sign up for Pro Membership - {% endif %} -
- Edit Workout -
-- Export -
+This is a preview of the page with advanced functionality for Pro users. + See + the page about Pro membership for more information and to sign up for Pro Membership +{% endif %} +
+ Edit Workout +
++ Export +
-| Date: | {{ workout.date }} | -
|---|---|
| Time: | {{ workout.starttime }} | -
| Distance: | {{ workout.distance }}m | -
| Duration: | {{ workout.duration |durationprint:"%H:%M:%S.%f" }} | -Public link to this workout | -- http://rowsandall.com/rowers/workout/{{ workout.id }} - | - |
| Date: | {{ workout.date }} | +
|---|---|
| Time: | {{ workout.starttime }} | +
| Distance: | {{ workout.distance }}m | +
| Duration: | {{ workout.duration |durationprint:"%H:%M:%S.%f" }} | +Public link to this workout | ++ https://rowsandall.com/rowers/workout/{{ workout.id }} + | + |
- Compare this workout to other workouts. Plot HR, SPM, or pace vs time or distance for the two workouts. -
-- Flexible Interactive plot. Pick your own X and Y axis parameters. -
-+ Compare this workout to other workouts. Plot HR, SPM, or pace vs time or distance for the two workouts. +
++ Flexible Interactive plot. Pick your own X and Y axis parameters. +
+- {% if user.rower.rowerplan == 'pro' %} - Edit Intervals - {% else %} - Edit Intervals - {% endif %} -
- Enter or change the interval and summary data for your workout ++ {% if user.rower.rowerplan == 'pro' %} + Edit Intervals + {% else %} + Edit Intervals + {% endif %} +
+ Enter or change the interval and summary data for your workout --Enter or change the interval and summary data for your workout -
-+ Enter or change the interval and summary data for your workout +
+- {% if user.rower.rowerplan == 'pro' %} - Dist Metrics Plot - {% else %} - Dist Metrics Plot - {% endif %} -
-- Various advanced stroke metrics plotted versus distance. -
-- {% if user.rower.rowerplan == 'pro' %} - Time Metrics Plot - {% else %} - Time Metrics Plot - {% endif %} -
-- Various advanced stroke metrics plotted versus time. -
-- See (and save) the big interactive plot -
-+ {% if user.rower.rowerplan == 'pro' %} + Dist Metrics Plot + {% else %} + Dist Metrics Plot + {% endif %} +
++ Various advanced stroke metrics plotted versus distance. +
++ {% if user.rower.rowerplan == 'pro' %} + Time Metrics Plot + {% else %} + Time Metrics Plot + {% endif %} +
++ Various advanced stroke metrics plotted versus time. +
++ See (and save) the big interactive plot +
++ {% if user.rower.rowerplan == 'pro' %} + Power Histogram + {% else %} + Dist Metrics Plot + {% endif %} +
++ Plot the Power Histogram of this workout +
+| Weight Category: | {{ workout.weightcategory }} |
|---|
+
+ ++
No workouts found
{% endif %} + +Direct link for other Pro users: - http://rowsandall.com/rowers/{{ id }}/histo/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }} + https://rowsandall.com/rowers/{{ id }}/histo/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}
Summary of the past 12 months for {{ theuser.first_name }} {{ theuser.last_name }}
Direct link for other Pro users: - http://rowsandall.com/rowers/{{ id }}/histo-all + https://rowsandall.com/rowers/{{ id }}/histo-all
diff --git a/rowers/templates/list_workouts.html b/rowers/templates/list_workouts.html index 0b4334ef..d3cf848e 100644 --- a/rowers/templates/list_workouts.html +++ b/rowers/templates/list_workouts.html @@ -5,8 +5,27 @@ {% block title %}Workouts{% endblock %} {% block content %} +
| Public link to this workout | - http://rowsandall.com/rowers/workout/{{ workout.id }} + https://rowsandall.com/rowers/workout/{{ workout.id }} |
|---|
Direct link for other users: - http://rowsandall.com/rowers/{{ id }}/ote-bests/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }} + https://rowsandall.com/rowers/{{ id }}/ote-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.
diff --git a/rowers/templates/summary_edit.html b/rowers/templates/summary_edit.html index de042397..5d7c98e3 100644 --- a/rowers/templates/summary_edit.html +++ b/rowers/templates/summary_edit.html @@ -48,12 +48,12 @@