Merge branch 'release/v1.6'
This commit is contained in:
@@ -0,0 +1,495 @@
|
||||
from celery import Celery,app
|
||||
import os
|
||||
import time
|
||||
import gc
|
||||
import gzip
|
||||
import shutil
|
||||
import numpy as np
|
||||
|
||||
import rowingdata
|
||||
from rowingdata import main as rmain
|
||||
from rowingdata import rowingdata as rdata
|
||||
import rowingdata
|
||||
|
||||
from matplotlib.backends.backend_agg import FigureCanvas
|
||||
#from matplotlib.backends.backend_cairo import FigureCanvasCairo as FigureCanvas
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib import figure
|
||||
|
||||
import stravalib
|
||||
|
||||
from utils import serialize_list,deserialize_list
|
||||
|
||||
from rowers.dataprepnodjango import update_strokedata
|
||||
|
||||
|
||||
from django.core.mail import send_mail, BadHeaderError,EmailMessage
|
||||
|
||||
# testing task
|
||||
@app.task
|
||||
def add(x, y):
|
||||
return x + y
|
||||
|
||||
# send email to me when an unrecognized file is uploaded
|
||||
@app.task
|
||||
def handle_sendemail_unrecognized(unrecognizedfile,useremail):
|
||||
|
||||
# send email with attachment
|
||||
fullemail = 'roosendaalsander@gmail.com'
|
||||
subject = "Unrecognized file from Rowsandall.com"
|
||||
message = "Dear Sander,\n\n"
|
||||
message += "Please find attached a file that someone tried to upload to rowsandall.com. The file was not recognized as a valid file type.\n\n"
|
||||
message += "User Email "+useremail+"\n\n"
|
||||
message += "Best Regards, the Rowsandall Team"
|
||||
|
||||
email = EmailMessage(subject, message,
|
||||
'Rowsandall <info@rowsandall.com>',
|
||||
[fullemail])
|
||||
|
||||
|
||||
email.attach_file(unrecognizedfile)
|
||||
|
||||
res = email.send()
|
||||
|
||||
# remove tcx file
|
||||
os.remove(unrecognizedfile)
|
||||
return 1
|
||||
|
||||
|
||||
# Send email with TCX attachment
|
||||
@app.task
|
||||
def handle_sendemailtcx(first_name,last_name,email,tcxfile):
|
||||
|
||||
# send email with attachment
|
||||
fullemail = first_name + " " + last_name + " " + "<" + email + ">"
|
||||
subject = "File from Rowsandall.com"
|
||||
message = "Dear "+first_name+",\n\n"
|
||||
message += "Please find attached the requested file for your workout.\n\n"
|
||||
message += "Best Regards, the Rowsandall Team"
|
||||
|
||||
email = EmailMessage(subject, message,
|
||||
'Rowsandall <info@rowsandall.com>',
|
||||
[fullemail])
|
||||
|
||||
|
||||
email.attach_file(tcxfile)
|
||||
|
||||
res = email.send()
|
||||
|
||||
# remove tcx file
|
||||
os.remove(tcxfile)
|
||||
return 1
|
||||
|
||||
# Send email with CSV attachment
|
||||
@app.task
|
||||
def handle_sendemailcsv(first_name,last_name,email,csvfile):
|
||||
|
||||
# send email with attachment
|
||||
fullemail = first_name + " " + last_name + " " + "<" + email + ">"
|
||||
subject = "File from Rowsandall.com"
|
||||
message = "Dear "+first_name+",\n\n"
|
||||
message += "Please find attached the requested file for your workout.\n\n"
|
||||
message += "Best Regards, the Rowsandall Team"
|
||||
|
||||
email = EmailMessage(subject, message,
|
||||
'Rowsandall <info@rowsandall.com>',
|
||||
[fullemail])
|
||||
|
||||
|
||||
if os.path.isfile(csvfile):
|
||||
email.attach_file(csvfile)
|
||||
else:
|
||||
csvfile2 = csvfile
|
||||
with gzip.open(csvfile+'.gz','rb') as f_in, open(csvfile2,'wb') as f_out:
|
||||
shutil.copyfileobj(f_in, f_out)
|
||||
|
||||
email.attach_file(csvfile2)
|
||||
os.remove(csvfile2)
|
||||
|
||||
res = email.send()
|
||||
|
||||
return 1
|
||||
|
||||
# Calculate wind and stream corrections for OTW rowing
|
||||
@app.task
|
||||
def handle_otwsetpower(f1,boattype,weightvalue,
|
||||
first_name,last_name,email,workoutid,
|
||||
debug=False):
|
||||
try:
|
||||
rowdata = rdata(f1)
|
||||
except IOError:
|
||||
try:
|
||||
rowdata = rdata(f1+'.csv')
|
||||
except IOError:
|
||||
rowdata = rdata(f1+'.gz')
|
||||
|
||||
weightvalue = float(weightvalue)
|
||||
|
||||
# do something with boat type
|
||||
boatfile = {
|
||||
'1x':'static/rigging/1x.txt',
|
||||
'2x':'static/rigging/2x.txt',
|
||||
'2-':'static/rigging/2-.txt',
|
||||
'4x':'static/rigging/4x.txt',
|
||||
'4-':'static/rigging/4-.txt',
|
||||
'8+':'static/rigging/8+.txt',
|
||||
}
|
||||
try:
|
||||
rg = rowingdata.getrigging(boatfile[boattype])
|
||||
except KeyError:
|
||||
rg = rowingdata.getrigging('static/rigging/1x.txt')
|
||||
|
||||
# do calculation, but do not overwrite NK Empower Power data
|
||||
powermeasured = False
|
||||
try:
|
||||
w = rowdata.df['wash']
|
||||
powermeasured = True
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
rowdata.otw_setpower_silent(skiprows=5,mc=weightvalue,rg=rg,
|
||||
powermeasured=powermeasured)
|
||||
|
||||
# save data
|
||||
rowdata.write_csv(f1)
|
||||
update_strokedata(workoutid,rowdata.df,debug=debug)
|
||||
|
||||
# send email
|
||||
fullemail = first_name + " " + last_name + " " + "<" + email + ">"
|
||||
subject = "Your Rowsandall OTW calculations are ready"
|
||||
message = "Dear "+first_name+",\n\n"
|
||||
message += "Your Rowsandall OTW calculations are ready.\n"
|
||||
message += "Thank you for using rowsandall.com.\n\n"
|
||||
message += "Rowsandall OTW calculations have not been fully implemented yet.\n"
|
||||
message += "We are now running an experimental version for debugging purposes. \n"
|
||||
message += "Your wind/stream corrected plot is available here: http://rowsandall.com/rowers/workout/"
|
||||
message += str(workoutid)
|
||||
message +="/interactiveotwplot\n\n"
|
||||
message += "Please report any bugs/inconsistencies/unexpected results at rowsandall.slack.com or by reply to this email.\n\n"
|
||||
message += "Best Regards, The Rowsandall Physics Department."
|
||||
|
||||
send_mail(subject, message,
|
||||
'Rowsandall Physics Department <info@rowsandall.com>',
|
||||
[fullemail])
|
||||
|
||||
return 1
|
||||
|
||||
# This function generates all the static (PNG image) plots
|
||||
@app.task
|
||||
def handle_makeplot(f1,f2,t,hrdata,plotnr,imagename):
|
||||
|
||||
hrmax = hrdata['hrmax']
|
||||
hrut2 = hrdata['hrut2']
|
||||
hrut1 = hrdata['hrut1']
|
||||
hrat = hrdata['hrat']
|
||||
hrtr = hrdata['hrtr']
|
||||
hran = hrdata['hran']
|
||||
ftp = hrdata['ftp']
|
||||
powerzones = deserialize_list(hrdata['powerzones'])
|
||||
powerperc = np.array(deserialize_list(hrdata['powerperc'])).astype(int)
|
||||
|
||||
rr = rowingdata.rower(hrmax=hrmax,hrut2=hrut2,
|
||||
hrut1=hrut1,hrat=hrat,
|
||||
hrtr=hrtr,hran=hran,
|
||||
ftp=ftp,powerperc=powerperc,
|
||||
powerzones=powerzones)
|
||||
try:
|
||||
row = rdata(f2,rower=rr)
|
||||
except IOError:
|
||||
row = rdata(f2+'.gz',rower=rr)
|
||||
|
||||
|
||||
haspower = row.df[' Power (watts)'].mean() > 50
|
||||
|
||||
nr_rows = len(row.df)
|
||||
if (plotnr in [1,2,4,5,8,11,9,12]) and (nr_rows > 1200):
|
||||
bin = int(nr_rows/1200.)
|
||||
df = row.df.groupby(lambda x:x/bin).mean()
|
||||
row.df = df
|
||||
nr_rows = len(row.df)
|
||||
if (plotnr==1):
|
||||
fig1 = row.get_timeplot_erg(t)
|
||||
elif (plotnr==2):
|
||||
fig1 = row.get_metersplot_erg(t)
|
||||
elif (plotnr==3):
|
||||
fig1 = row.get_piechart(t)
|
||||
elif (plotnr==4):
|
||||
if haspower:
|
||||
fig1 = row.get_timeplot_otwempower(t)
|
||||
else:
|
||||
fig1 = row.get_timeplot_otw(t)
|
||||
elif (plotnr==5):
|
||||
if haspower:
|
||||
fig1 = row.get_metersplot_otwempower(t)
|
||||
else:
|
||||
fig1 = row.get_metersplot_otw(t)
|
||||
elif (plotnr==6):
|
||||
fig1 = row.get_piechart(t)
|
||||
elif (plotnr==7) or (plotnr==10):
|
||||
fig1 = row.get_metersplot_erg2(t)
|
||||
elif (plotnr==8) or (plotnr==11):
|
||||
fig1 = row.get_timeplot_erg2(t)
|
||||
elif (plotnr==9) or (plotnr==12):
|
||||
fig1 = row.get_time_otwpower(t)
|
||||
elif (plotnr==13) or (plotnr==16):
|
||||
fig1 = row.get_power_piechart(t)
|
||||
|
||||
canvas = FigureCanvas(fig1)
|
||||
|
||||
# plt.savefig('static/plots/'+imagename,format='png')
|
||||
canvas.print_figure('static/plots/'+imagename)
|
||||
# plt.imsave(fname='static/plots/'+imagename)
|
||||
plt.close(fig1)
|
||||
fig1.clf()
|
||||
gc.collect()
|
||||
return imagename
|
||||
|
||||
# Team related remote tasks
|
||||
|
||||
@app.task
|
||||
def handle_sendemail_invite(email,name,code,teamname,manager):
|
||||
fullemail = name+' <'+email+'>'
|
||||
subject = 'Invitation to join team '+teamname
|
||||
message = 'Dear '+name+',\n\n'
|
||||
message += manager+' is inviting you to join his team '+teamname
|
||||
message += ' on rowsandall.com\n\n'
|
||||
message += 'By accepting the invite, you will have access to your'
|
||||
message += " team's workouts on rowsandall.com and your workouts will "
|
||||
message += " be visible to "
|
||||
message += "the members of the team.\n\n"
|
||||
message += 'If you already have an account on rowsandall.com, you can login to the site and you will find the invitation here on the Teams page:\n'
|
||||
message += ' https://rowsandall.com/rowers/me/teams \n\n'
|
||||
message += 'You can also click the direct link: \n'
|
||||
message += 'https://rowsandall.com/rowers/me/invitation/'+code+' \n\n'
|
||||
message += 'If you are not yet registered to rowsandall.com, '
|
||||
message += 'you can register for free at https://rowsandall.com/rowers/register\n'
|
||||
message += 'After you set up your account, you can use the direct link: '
|
||||
message += 'https://rowsandall.com/rowers/me/invitation/'+code+' \n\n'
|
||||
|
||||
message += 'You can also manually accept your team membership with the code.\n'
|
||||
message += 'You will need to do this if you registered under a different email address than this one.\n'
|
||||
message += 'Code: '+code+'\n'
|
||||
message += 'Link to manually accept your team membership: '
|
||||
message += 'https://rowsandall.com/rowers/me/invitation\n\n'
|
||||
message += "Best Regards, the Rowsandall Team"
|
||||
|
||||
email = EmailMessage(subject, message,
|
||||
'Rowsandall <info@rowsandall.com>',
|
||||
[fullemail])
|
||||
|
||||
|
||||
res = email.send()
|
||||
|
||||
return 1
|
||||
|
||||
@app.task
|
||||
def handle_sendemailnewresponse(first_name,last_name,
|
||||
email,
|
||||
commenter_first_name,
|
||||
commenter_last_name,
|
||||
comment,
|
||||
workoutname,workoutid,commentid):
|
||||
fullemail = first_name+' '+last_name+' <'+email+'>'
|
||||
subject = 'New comment on workout '+workoutname
|
||||
message = 'Dear '+first_name+',\n\n'
|
||||
message += commenter_first_name+' '+commenter_last_name
|
||||
message += ' has written a new comment on the workout '
|
||||
message += workoutname+'\n\n'
|
||||
message += comment
|
||||
message += '\n\n'
|
||||
message += 'You can read the comment here:\n'
|
||||
message += 'https://rowsandall.com/rowers/workout/'+str(workoutid)+'/comment'
|
||||
message += '\n\n'
|
||||
message += 'You are receiving this email because you are subscribed '
|
||||
message += 'to comments on this workout. To unsubscribe, follow this link:\n'
|
||||
message += 'https://rowsandall.com/rowers/workout/'+str(workoutid)+'/unsubscribe'
|
||||
|
||||
email = EmailMessage(subject, message,
|
||||
'Rowsandall <info@rowsandall.com>',
|
||||
[fullemail])
|
||||
|
||||
|
||||
res = email.send()
|
||||
|
||||
return 1
|
||||
|
||||
@app.task
|
||||
def handle_sendemailnewcomment(first_name,
|
||||
last_name,
|
||||
email,
|
||||
commenter_first_name,
|
||||
commenter_last_name,
|
||||
comment,workoutname,
|
||||
workoutid):
|
||||
fullemail = first_name+' '+last_name+' <'+email+'>'
|
||||
subject = 'New comment on workout '+workoutname
|
||||
message = 'Dear '+first_name+',\n\n'
|
||||
message += commenter_first_name+' '+commenter_last_name
|
||||
message += ' has written a new comment on your workout '
|
||||
message += workoutname+'\n\n'
|
||||
message += comment
|
||||
message += '\n\n'
|
||||
message += 'You can read the comment here:\n'
|
||||
message += 'https://rowsandall.com/rowers/workout/'+str(workoutid)+'/comment'
|
||||
|
||||
email = EmailMessage(subject, message,
|
||||
'Rowsandall <info@rowsandall.com>',
|
||||
[fullemail])
|
||||
|
||||
|
||||
res = email.send()
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
@app.task
|
||||
def handle_sendemail_request(email,name,code,teamname,requestor,id):
|
||||
fullemail = name+' <'+email+'>'
|
||||
subject = 'Request to join team '+teamname
|
||||
message = 'Dear '+name+',\n\n'
|
||||
message += requestor+' is requesting admission to your team '+teamname
|
||||
message += ' on rowsandall.com\n\n'
|
||||
message += 'Click the direct link to accept: \n'
|
||||
message += 'https://rowsandall.com/rowers/me/request/'+code+' \n\n'
|
||||
message += 'Click the following link to reject the request: \n'
|
||||
message += 'https://rowsandall.com/rowers/me/request/'+str(id)+' \n\n'
|
||||
message += 'You can find all pending requests on your team management page:\n'
|
||||
message += 'https://rowsandall.com/rowers/me/teams\n\n'
|
||||
message += "Best Regards, the Rowsandall Team"
|
||||
|
||||
email = EmailMessage(subject, message,
|
||||
'Rowsandall <info@rowsandall.com>',
|
||||
[fullemail])
|
||||
|
||||
|
||||
res = email.send()
|
||||
|
||||
return 1
|
||||
|
||||
@app.task
|
||||
def handle_sendemail_request_accept(email,name,teamname,managername):
|
||||
fullemail = name+' <'+email+'>'
|
||||
subject = 'Welcome to '+teamname
|
||||
message = 'Dear '+name+',\n\n'
|
||||
message += managername
|
||||
message += ' has accepted your request to be part of the team '
|
||||
message += teamname
|
||||
message += '\n\n'
|
||||
message += "Best Regards, the Rowsandall Team"
|
||||
|
||||
email = EmailMessage(subject, message,
|
||||
'Rowsandall <info@rowsandall.com>',
|
||||
[fullemail])
|
||||
|
||||
|
||||
res = email.send()
|
||||
|
||||
return 1
|
||||
|
||||
@app.task
|
||||
def handle_sendemail_request_reject(email,name,teamname,managername):
|
||||
fullemail = name+' <'+email+'>'
|
||||
subject = 'Your application to '+teamname+' was rejected'
|
||||
message = 'Dear '+name+',\n\n'
|
||||
message += 'Unfortunately, '
|
||||
message += managername
|
||||
message += ' has rejected your request to be part of the team '
|
||||
message += teamname
|
||||
message += '\n\n'
|
||||
message += "Best Regards, the Rowsandall Team"
|
||||
|
||||
email = EmailMessage(subject, message,
|
||||
'Rowsandall <info@rowsandall.com>',
|
||||
[fullemail])
|
||||
|
||||
|
||||
res = email.send()
|
||||
|
||||
return 1
|
||||
|
||||
@app.task
|
||||
def handle_sendemail_member_dropped(email,name,teamname,managername):
|
||||
fullemail = name+' <'+email+'>'
|
||||
subject = 'You were removed from '+teamname
|
||||
message = 'Dear '+name+',\n\n'
|
||||
message += 'Unfortunately, '
|
||||
message += managername
|
||||
message += ' has removed you from the team '
|
||||
message += teamname
|
||||
message += '\n\n'
|
||||
message += "Best Regards, the Rowsandall Team"
|
||||
|
||||
email = EmailMessage(subject, message,
|
||||
'Rowsandall <info@rowsandall.com>',
|
||||
[fullemail])
|
||||
|
||||
|
||||
res = email.send()
|
||||
|
||||
return 1
|
||||
|
||||
@app.task
|
||||
def handle_sendemail_team_removed(email,name,teamname,managername):
|
||||
fullemail = name+' <'+email+'>'
|
||||
subject = 'Team '+teamname+' was deleted'
|
||||
message = 'Dear '+name+',\n\n'
|
||||
message += managername
|
||||
message += ' has decided to delete the team '
|
||||
message += teamname
|
||||
message += '\n\n'
|
||||
message += 'The '+teamname+' tag has been removed from all your '
|
||||
message += 'workouts on rowsandall.com.\n\n'
|
||||
message += "Best Regards, the Rowsandall Team"
|
||||
|
||||
email = EmailMessage(subject, message,
|
||||
'Rowsandall <info@rowsandall.com>',
|
||||
[fullemail])
|
||||
|
||||
|
||||
res = email.send()
|
||||
|
||||
return 1
|
||||
|
||||
@app.task
|
||||
def handle_sendemail_invite_reject(email,name,teamname,managername):
|
||||
fullemail = managername+' <'+email+'>'
|
||||
subject = 'Your invitation to '+name+' was rejected'
|
||||
message = 'Dear '+managername+',\n\n'
|
||||
message += 'Unfortunately, '
|
||||
message += name
|
||||
message += ' has rejected your invitation to be part of the team '
|
||||
message += teamname
|
||||
message += '\n\n'
|
||||
message += "Best Regards, the Rowsandall Team"
|
||||
|
||||
email = EmailMessage(subject, message,
|
||||
'Rowsandall <info@rowsandall.com>',
|
||||
[fullemail])
|
||||
|
||||
|
||||
res = email.send()
|
||||
|
||||
return 1
|
||||
|
||||
@app.task
|
||||
def handle_sendemail_invite_accept(email,name,teamname,managername):
|
||||
fullemail = managername+' <'+email+'>'
|
||||
subject = 'Your invitation to '+name+' was accepted'
|
||||
message = 'Dear '+managername+',\n\n'
|
||||
message += name+' has accepted your invitation to be part of the team '+teamname+'\n\n'
|
||||
message += "Best Regards, the Rowsandall Team"
|
||||
|
||||
email = EmailMessage(subject, message,
|
||||
'Rowsandall <info@rowsandall.com>',
|
||||
[fullemail])
|
||||
|
||||
|
||||
res = email.send()
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
|
||||
# Another simple task for debugging purposes
|
||||
def add2(x,y):
|
||||
return x+y
|
||||
+109
-3
@@ -12,7 +12,8 @@ from rowingdata import get_file_type,get_empower_rigging
|
||||
|
||||
from pandas import DataFrame,Series
|
||||
from pytz import timezone as tz,utc
|
||||
|
||||
from django.utils import timezone
|
||||
from time import strftime,strptime,mktime,time,daylight
|
||||
from django.utils.timezone import get_current_timezone
|
||||
thetimezone = get_current_timezone()
|
||||
from rowingdata import (
|
||||
@@ -81,6 +82,7 @@ columndict = {
|
||||
'peakforceangle':'peakforceangle',
|
||||
'wash':'wash',
|
||||
'slip':'wash',
|
||||
'workoutstate':' WorkoutState',
|
||||
}
|
||||
|
||||
from scipy.signal import savgol_filter
|
||||
@@ -621,6 +623,62 @@ def new_workout_from_file(r,f2,
|
||||
|
||||
return (id,message,f2)
|
||||
|
||||
# Create new workout from data frame and store it in the database
|
||||
# This routine should be used everywhere in views.py and mailprocessing.py
|
||||
# Currently there is code duplication
|
||||
def new_workout_from_df(r,df,
|
||||
title='New Workout',
|
||||
parent=None):
|
||||
|
||||
message = None
|
||||
|
||||
summary = ''
|
||||
if parent:
|
||||
oarlength = parent.oarlength
|
||||
inboard = parent.inboard
|
||||
workouttype = parent.workouttype
|
||||
notes=parent.notes
|
||||
summary=parent.summary
|
||||
makeprivate=parent.privacy
|
||||
startdatetime=parent.startdatetime
|
||||
else:
|
||||
oarlength = 2.89
|
||||
inboard = 0.88
|
||||
workouttype = 'rower'
|
||||
notes=''
|
||||
summary=''
|
||||
makeprivate=False
|
||||
startdatetime = timezone.now()
|
||||
|
||||
timestr = strftime("%Y%m%d-%H%M%S")
|
||||
|
||||
csvfilename ='media/Fusion_'+timestr+'.csv'
|
||||
|
||||
df.rename(columns = columndict,inplace=True)
|
||||
starttimeunix = mktime(startdatetime.utctimetuple())
|
||||
df[' ElapsedTime (sec)'] = df['TimeStamp (sec)']
|
||||
df['TimeStamp (sec)'] = df['TimeStamp (sec)']+starttimeunix
|
||||
|
||||
row = rrdata(df=df)
|
||||
row.write_csv(csvfilename,gzip=True)
|
||||
|
||||
#res = df.to_csv(csvfilename+'.gz',index_label='index',
|
||||
# compression='gzip')
|
||||
|
||||
id,message = save_workout_database(csvfilename,r,
|
||||
workouttype=workouttype,
|
||||
title=title,
|
||||
notes=notes,
|
||||
oarlength=oarlength,
|
||||
inboard=inboard,
|
||||
makeprivate=makeprivate,
|
||||
dosmooth=False)
|
||||
|
||||
|
||||
return (id,message)
|
||||
|
||||
|
||||
|
||||
# Compare the data from the CSV file and the database
|
||||
# Currently only calculates number of strokes. To be expanded with
|
||||
# more elaborate testing if needed
|
||||
@@ -696,10 +754,10 @@ def repair_data(verbose=False):
|
||||
# A wrapper around the rowingdata class, with some error catching
|
||||
def rdata(file,rower=rrower()):
|
||||
try:
|
||||
res = rrdata(file,rower=rower)
|
||||
res = rrdata(csvfile=file,rower=rower)
|
||||
except IOError,IndexError:
|
||||
try:
|
||||
res = rrdata(file+'.gz',rower=rower)
|
||||
res = rrdata(csvfile=file+'.gz',rower=rower)
|
||||
except IOError,IndexError:
|
||||
res = 0
|
||||
|
||||
@@ -898,6 +956,47 @@ def smalldataprep(therows,xparam,yparam1,yparam2):
|
||||
|
||||
return df
|
||||
|
||||
# data fusion
|
||||
def datafusion(id1,id2,columns,offset):
|
||||
df1,w1 = getrowdata_db(id=id1)
|
||||
df1 = df1.drop([#'cumdist',
|
||||
'hr_ut2',
|
||||
'hr_ut1',
|
||||
'hr_at',
|
||||
'hr_tr',
|
||||
'hr_an',
|
||||
'hr_max',
|
||||
'ftime',
|
||||
'fpace',
|
||||
'workoutid',
|
||||
'id'],
|
||||
1,errors='ignore')
|
||||
|
||||
df2 = getsmallrowdata_db(['time']+columns,ids=[id2],doclean=False)
|
||||
offsetmillisecs = offset.seconds*1000+offset.microseconds/1000.
|
||||
offsetmillisecs += offset.days*(3600*24*1000)
|
||||
df2['time'] = df2['time']+offsetmillisecs
|
||||
|
||||
keep1 = {c:c for c in set(df1.columns)}
|
||||
for c in columns:
|
||||
keep1.pop(c)
|
||||
|
||||
for c in df1.columns:
|
||||
if not c in keep1:
|
||||
df1 = df1.drop(c,1,errors='ignore')
|
||||
|
||||
df = pd.concat([df1,df2],ignore_index=True)
|
||||
df = df.sort_values(['time'])
|
||||
df = df.interpolate(method='linear',axis=0,limit_direction='both',
|
||||
limit=10)
|
||||
df.fillna(method='bfill',inplace=True)
|
||||
|
||||
df['time'] = df['time']/1000.
|
||||
df['pace'] = df['pace']/1000.
|
||||
df['cum_dist'] = df['cumdist']
|
||||
|
||||
return df
|
||||
|
||||
# This is the main routine.
|
||||
# it reindexes, sorts, filters, and smooths the data, then
|
||||
# saves it to the stroke_data table in the database
|
||||
@@ -1021,7 +1120,10 @@ def dataprep(rowdatadf,id=0,bands=True,barchart=True,otwpower=True,
|
||||
drivelength = arclength
|
||||
else:
|
||||
drivelength = driveenergy/(averageforce*4.44822)
|
||||
|
||||
slip = rowdatadf.ix[:,'slip']
|
||||
totalangle = finish-catch
|
||||
effectiveangle = finish-wash-catch-slip
|
||||
if windowsize > 3 and windowsize<len(slip):
|
||||
wash = savgol_filter(wash,windowsize,3)
|
||||
slip = savgol_filter(slip,windowsize,3)
|
||||
@@ -1030,6 +1132,8 @@ def dataprep(rowdatadf,id=0,bands=True,barchart=True,otwpower=True,
|
||||
peakforceangle = savgol_filter(peakforceangle,windowsize,3)
|
||||
driveenergy = savgol_filter(driveenergy,windowsize,3)
|
||||
drivelength = savgol_filter(drivelength,windowsize,3)
|
||||
totalangle = savgol_filter(totalangle,windowsize,3)
|
||||
effectiveangle = savgol_filter(effectiveangle,windowsize,3)
|
||||
data['wash'] = wash
|
||||
data['catch'] = catch
|
||||
data['slip'] = slip
|
||||
@@ -1037,6 +1141,8 @@ def dataprep(rowdatadf,id=0,bands=True,barchart=True,otwpower=True,
|
||||
data['peakforceangle'] = peakforceangle
|
||||
data['driveenergy'] = driveenergy
|
||||
data['drivelength'] = drivelength
|
||||
data['totalangle'] = totalangle
|
||||
data['effectiveangle'] = effectiveangle
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
+46
-4
@@ -6,6 +6,8 @@ from django.contrib.auth.models import User
|
||||
from django.contrib.admin.widgets import AdminDateWidget
|
||||
from django.forms.extras.widgets import SelectDateWidget
|
||||
from django.utils import timezone,translation
|
||||
from django.forms import ModelForm
|
||||
import dataprep
|
||||
|
||||
import datetime
|
||||
|
||||
@@ -259,8 +261,9 @@ class WorkoutMultipleCompareForm(forms.Form):
|
||||
|
||||
from rowers.interactiveplots import axlabels
|
||||
|
||||
axlabels.pop('None')
|
||||
axlabels = list(axlabels.items())
|
||||
formaxlabels = axlabels.copy()
|
||||
formaxlabels.pop('None')
|
||||
parchoices = list(sorted(formaxlabels.items(), key = lambda x:x[1]))
|
||||
|
||||
|
||||
class ChartParamChoiceForm(forms.Form):
|
||||
@@ -268,7 +271,46 @@ class ChartParamChoiceForm(forms.Form):
|
||||
('line','Line Plot'),
|
||||
('scatter','Scatter Plot'),
|
||||
)
|
||||
xparam = forms.ChoiceField(choices=axlabels,initial='distance')
|
||||
yparam = forms.ChoiceField(choices=axlabels,initial='hr')
|
||||
xparam = forms.ChoiceField(choices=parchoices,initial='distance')
|
||||
yparam = forms.ChoiceField(choices=parchoices,initial='hr')
|
||||
plottype = forms.ChoiceField(choices=plotchoices,initial='scatter')
|
||||
teamid = forms.IntegerField(widget=forms.HiddenInput())
|
||||
|
||||
formaxlabels.pop('time')
|
||||
metricchoices = list(sorted(formaxlabels.items(), key = lambda x:x[1]))
|
||||
|
||||
class FusionMetricChoiceForm(ModelForm):
|
||||
class Meta:
|
||||
model = Workout
|
||||
fields = []
|
||||
|
||||
posneg = (
|
||||
('pos','Workout 2 starts after Workout 1'),
|
||||
('neg','Workout 2 starts before Workout 1'),
|
||||
)
|
||||
columns = forms.MultipleChoiceField(choices=metricchoices,
|
||||
initial=[],
|
||||
widget=forms.CheckboxSelectMultiple())
|
||||
posneg = forms.ChoiceField(choices=posneg,initial='pos')
|
||||
offset = forms.DurationField(label='Time Offset',initial=datetime.timedelta())
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(FusionMetricChoiceForm, self).__init__(*args, **kwargs)
|
||||
# need to add code to remove "empty" fields
|
||||
|
||||
if self.instance.id is not None:
|
||||
id = self.instance.id
|
||||
df = dataprep.getrowdata_db(id=id)[0]
|
||||
|
||||
labeldict = {key:value for key,value in self.fields['columns'].choices}
|
||||
|
||||
for label in labeldict:
|
||||
if df.ix[:,label].std() == 0:
|
||||
try:
|
||||
formaxlabels.pop(label)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
metricchoices = list(sorted(formaxlabels.items(), key = lambda x:x[1]))
|
||||
self.fields['columns'].choices = metricchoices
|
||||
|
||||
|
||||
+51
-72
@@ -1,4 +1,3 @@
|
||||
|
||||
from rowers.models import Workout, User, Rower, WorkoutForm,RowerForm,GraphImage
|
||||
from rowingdata import rower as rrower
|
||||
from rowingdata import main as rmain
|
||||
@@ -51,66 +50,36 @@ import stravastuff
|
||||
from rowers.dataprep import rdata
|
||||
import rowers.dataprep as dataprep
|
||||
|
||||
axlabels = {
|
||||
'time': 'Time',
|
||||
'distance': 'Distance (m)',
|
||||
'cumdist': 'Cumulative Distance (m)',
|
||||
'hr': 'Heart Rate (bpm)',
|
||||
'spm': 'Stroke Rate (spm)',
|
||||
'pace': 'Pace (/500m)',
|
||||
'power': 'Power (Watt)',
|
||||
'averageforce': 'Average Drive Force (lbs)',
|
||||
'drivelength': 'Drive Length (m)',
|
||||
'peakforce': 'Peak Drive Force (lbs)',
|
||||
'forceratio': 'Average/Peak Drive Force Ratio',
|
||||
'driveenergy': 'Work per Stroke (J)',
|
||||
'drivespeed': 'Drive Speed (m/s)',
|
||||
'slip': 'Slip (degrees)',
|
||||
'catch': 'Catch (degrees)',
|
||||
'finish': 'Finish (degrees)',
|
||||
'wash': 'Wash (degrees)',
|
||||
'peakforceangle': 'Peak Force Angle (degrees)',
|
||||
'rhythm': 'Stroke Rhythm (%)',
|
||||
'None': '',
|
||||
}
|
||||
axes = (
|
||||
('time','Time',0,1e5,'basic'),
|
||||
('distance', 'Distance (m)',0,1e5,'basic'),
|
||||
('cumdist', 'Cumulative Distance (m)',0,1e5,'basic'),
|
||||
('hr','Heart Rate (bpm)',100,200,'basic'),
|
||||
('spm', 'Stroke Rate (spm)',15,45,'basic'),
|
||||
('pace', 'Pace (/500m)',1.0e3*210,1.0e3*75,'basic'),
|
||||
('power', 'Power (Watt)',0,600,'basic'),
|
||||
('averageforce', 'Average Drive Force (lbs)',0,200,'pro'),
|
||||
('drivelength', 'Drive Length (m)',0.5,2.0,'pro'),
|
||||
('peakforce', 'Peak Drive Force (lbs)',0,400,'pro'),
|
||||
('forceratio', 'Average/Peak Force Ratio',0,1,'pro'),
|
||||
('driveenergy', 'Work per Stroke (J)',0,1000,'pro'),
|
||||
('drivespeed', 'Drive Speed (m/s)',0,4,'pro'),
|
||||
('slip', 'Slip (degrees)',0,20,'pro'),
|
||||
('catch', 'Catch (degrees)',-40,-75,'pro'),
|
||||
('finish', 'Finish (degrees)',20,55,'pro'),
|
||||
('wash', 'Wash (degrees)',0,30,'pro'),
|
||||
('peakforceangle', 'Peak Force Angle (degrees)',-20,20,'pro'),
|
||||
('totalangle', 'Drive Length (deg)',40,140,'pro'),
|
||||
('effectiveangle', 'Effective Drive Length (deg)',40,140,'pro'),
|
||||
('rhythm', 'Stroke Rhythm (%)',20,55,'pro'),
|
||||
('None', 'None',0,1,'basic'),
|
||||
)
|
||||
|
||||
yaxminima = {
|
||||
'hr':100,
|
||||
'spm':15,
|
||||
'pace': 1.0e3*210,
|
||||
'power': 0,
|
||||
'averageforce': 0,
|
||||
'peakforce': 0,
|
||||
'forceratio':0,
|
||||
'drivelength':0.5,
|
||||
'driveenergy': 0,
|
||||
'drivespeed': 0,
|
||||
'slip': 0,
|
||||
'catch': -40,
|
||||
'finish': 20,
|
||||
'wash': 0,
|
||||
'peakforceangle': -20,
|
||||
'rhythm':20,
|
||||
}
|
||||
axlabels = {ax[0]:ax[1] for ax in axes}
|
||||
|
||||
yaxmaxima = {
|
||||
'hr':200,
|
||||
'spm':45,
|
||||
'pace': 1.0e3*75,
|
||||
'power': 600,
|
||||
'averageforce':200,
|
||||
'peakforce':400,
|
||||
'forceratio':1,
|
||||
'drivelength':2.0,
|
||||
'driveenergy': 1000,
|
||||
'drivespeed':4,
|
||||
'slip': 15,
|
||||
'catch': -75,
|
||||
'finish': 55,
|
||||
'wash': 30,
|
||||
'peakforceangle': 20,
|
||||
'rhythm':55,
|
||||
}
|
||||
yaxminima = {ax[0]:ax[2] for ax in axes}
|
||||
|
||||
yaxmaxima = {ax[0]:ax[3] for ax in axes}
|
||||
|
||||
def tailwind(bearing,vwind,winddir):
|
||||
""" Calculates head-on head/tailwind in direction of rowing
|
||||
@@ -996,7 +965,7 @@ def interactive_cum_flex_chart2(theworkouts,promember=0,
|
||||
line_dash=[6,6],line_width=2)
|
||||
y2means = y1means
|
||||
|
||||
xlabel = Label(x=370,y=130,x_units='screen',y_units='screen',
|
||||
xlabel = Label(x=100,y=130,x_units='screen',y_units='screen',
|
||||
text=xparam+": {x1mean:6.2f}".format(x1mean=x1mean),
|
||||
background_fill_alpha=.7,
|
||||
text_color='green',
|
||||
@@ -1008,7 +977,7 @@ def interactive_cum_flex_chart2(theworkouts,promember=0,
|
||||
|
||||
plot.add_layout(y1means)
|
||||
|
||||
y1label = Label(x=370,y=100,x_units='screen',y_units='screen',
|
||||
y1label = Label(x=100,y=100,x_units='screen',y_units='screen',
|
||||
text=yparam1+": {y1mean:6.2f}".format(y1mean=y1mean),
|
||||
background_fill_alpha=.7,
|
||||
text_color='blue',
|
||||
@@ -1048,8 +1017,8 @@ def interactive_cum_flex_chart2(theworkouts,promember=0,
|
||||
|
||||
|
||||
plot.add_layout(y2means)
|
||||
y2label = Label(x=370,y=70,x_units='screen',y_units='screen',
|
||||
text=yparam2+": {y2mean:6.2f}".format(y2mean=y2mean),
|
||||
y2label = Label(x=100,y=70,x_units='screen',y_units='screen',
|
||||
text=axlabels[yparam2]+": {y2mean:6.2f}".format(y2mean=y2mean),
|
||||
background_fill_alpha=.7,
|
||||
text_color='red',
|
||||
)
|
||||
@@ -1196,7 +1165,7 @@ def interactive_flex_chart2(id=0,promember=0,
|
||||
columns = [xparam,yparam1,yparam2,
|
||||
'ftime','distance','fpace',
|
||||
'power','hr','spm','driveenergy',
|
||||
'time','pace','workoutstate']
|
||||
'time','pace','workoutstate','time']
|
||||
|
||||
rowdata = dataprep.getsmallrowdata_db(columns,ids=[id],doclean=True,
|
||||
workstrokesonly=workstrokesonly)
|
||||
@@ -1252,7 +1221,10 @@ def interactive_flex_chart2(id=0,promember=0,
|
||||
|
||||
# average values
|
||||
if xparam != 'time':
|
||||
try:
|
||||
x1mean = rowdata['x1'].mean()
|
||||
except TypeError:
|
||||
x1mean = 0
|
||||
else:
|
||||
x1mean = 0
|
||||
|
||||
@@ -1318,8 +1290,8 @@ def interactive_flex_chart2(id=0,promember=0,
|
||||
line_dash=[6,6],line_width=2)
|
||||
y2means = y1means
|
||||
|
||||
xlabel = Label(x=370,y=130,x_units='screen',y_units='screen',
|
||||
text=xparam+": {x1mean:6.2f}".format(x1mean=x1mean),
|
||||
xlabel = Label(x=100,y=130,x_units='screen',y_units='screen',
|
||||
text=axlabels[xparam]+": {x1mean:6.2f}".format(x1mean=x1mean),
|
||||
background_fill_alpha=.7,
|
||||
text_color='green',
|
||||
)
|
||||
@@ -1331,8 +1303,8 @@ def interactive_flex_chart2(id=0,promember=0,
|
||||
|
||||
plot.add_layout(y1means)
|
||||
|
||||
y1label = Label(x=370,y=100,x_units='screen',y_units='screen',
|
||||
text=yparam1+": {y1mean:6.2f}".format(y1mean=y1mean),
|
||||
y1label = Label(x=100,y=100,x_units='screen',y_units='screen',
|
||||
text=axlabels[yparam1]+": {y1mean:6.2f}".format(y1mean=y1mean),
|
||||
background_fill_alpha=.7,
|
||||
text_color='blue',
|
||||
)
|
||||
@@ -1353,6 +1325,7 @@ def interactive_flex_chart2(id=0,promember=0,
|
||||
|
||||
plot.title.text = row.name
|
||||
plot.title.text_font_size=value("1.0em")
|
||||
|
||||
plot.xaxis.axis_label = axlabels[xparam]
|
||||
plot.yaxis.axis_label = axlabels[yparam1]
|
||||
|
||||
@@ -1406,8 +1379,8 @@ def interactive_flex_chart2(id=0,promember=0,
|
||||
|
||||
|
||||
plot.add_layout(y2means)
|
||||
y2label = Label(x=370,y=70,x_units='screen',y_units='screen',
|
||||
text=yparam2+": {y2mean:6.2f}".format(y2mean=y2mean),
|
||||
y2label = Label(x=100,y=70,x_units='screen',y_units='screen',
|
||||
text=axlabels[yparam2]+": {y2mean:6.2f}".format(y2mean=y2mean),
|
||||
background_fill_alpha=.7,
|
||||
text_color='red',
|
||||
)
|
||||
@@ -1739,12 +1712,15 @@ def interactive_multiple_compare_chart(ids,xparam,yparam,plottype='line',
|
||||
group = datadf[datadf['workoutid']==int(id)].copy()
|
||||
group.sort_values(by='time',ascending=True,inplace=True)
|
||||
group['x'] = group[xparam]
|
||||
try:
|
||||
group['y'] = group[yparam]
|
||||
except KeyError:
|
||||
group['y'] = 0.0*group['x']
|
||||
|
||||
ymean = group['y'].mean()
|
||||
ylabel = Label(x=100,y=70+20*cntr,
|
||||
x_units='screen',y_units='screen',
|
||||
text=yparam+": {ymean:6.2f}".format(ymean=ymean),
|
||||
text=axlabels[yparam]+": {ymean:6.2f}".format(ymean=ymean),
|
||||
background_fill_alpha=.7,
|
||||
text_color=color,
|
||||
)
|
||||
@@ -1857,11 +1833,14 @@ def interactive_comparison_chart(id1=0,id2=0,xparam='distance',yparam='spm',
|
||||
else:
|
||||
rowdata2.sort_values(by='time',ascending=True,inplace=True)
|
||||
|
||||
try:
|
||||
x1 = rowdata1.ix[:,xparam]
|
||||
x2 = rowdata2.ix[:,xparam]
|
||||
|
||||
y1 = rowdata1.ix[:,yparam]
|
||||
y2 = rowdata2.ix[:,yparam]
|
||||
except KeyError:
|
||||
return "","No valid Data Available"
|
||||
|
||||
x_axis_type = 'linear'
|
||||
y_axis_type = 'linear'
|
||||
@@ -1984,7 +1963,7 @@ def interactive_comparison_chart(id1=0,id2=0,xparam='distance',yparam='spm',
|
||||
plot.title.text = row1.name+' vs '+row2.name
|
||||
plot.title.text_font_size=value("1.2em")
|
||||
plot.xaxis.axis_label = axlabels[xparam]
|
||||
|
||||
plot.yaxis.axis_label = axlabels[yparam]
|
||||
|
||||
if xparam == 'time':
|
||||
plot.xaxis[0].formatter = DatetimeTickFormatter(
|
||||
|
||||
@@ -498,6 +498,8 @@ class StrokeData(models.Model):
|
||||
wash = models.FloatField(default=0,null=True,verbose_name='Wash')
|
||||
peakforceangle = models.FloatField(default=0,null=True,verbose_name='Peak Force Angle')
|
||||
rhythm = models.FloatField(default=1.0,null=True,verbose_name='Rhythm')
|
||||
totalangle = models.FloatField(default=0.0,null=True,verbose_name='Total Stroke Length (deg)')
|
||||
effectiveangle = models.FloatField(default=0.0,null=True,verbose_name='Effective Stroke Length (deg)')
|
||||
|
||||
# A wrapper around the png files
|
||||
class GraphImage(models.Model):
|
||||
|
||||
+15
-10
@@ -139,6 +139,7 @@ def get_strava_workout(user,stravaid):
|
||||
else:
|
||||
# ready to fetch. Hurray
|
||||
fetchresolution = 'high'
|
||||
series_type = 'time'
|
||||
authorizationstring = str('Bearer ' + r.stravatoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
@@ -150,39 +151,43 @@ def get_strava_workout(user,stravaid):
|
||||
workoutsummary['timezone'] = "Etc/UTC"
|
||||
startdatetime = workoutsummary['start_date']
|
||||
|
||||
url = "https://www.strava.com/api/v3/activities/"+str(stravaid)+"/streams/cadence?resolution="+fetchresolution
|
||||
url = "https://www.strava.com/api/v3/activities/"+str(stravaid)+"/streams/cadence?resolution="+fetchresolution+"&series_type="+series_type
|
||||
spmjson = requests.get(url,headers=headers)
|
||||
url = "https://www.strava.com/api/v3/activities/"+str(stravaid)+"/streams/heartrate?resolution="+fetchresolution
|
||||
url = "https://www.strava.com/api/v3/activities/"+str(stravaid)+"/streams/heartrate?resolution="+fetchresolution+"&series_type="+series_type
|
||||
hrjson = requests.get(url,headers=headers)
|
||||
url = "https://www.strava.com/api/v3/activities/"+str(stravaid)+"/streams/time?resolution="+fetchresolution
|
||||
url = "https://www.strava.com/api/v3/activities/"+str(stravaid)+"/streams/time?resolution="+fetchresolution+"&series_type="+series_type
|
||||
print url
|
||||
timejson = requests.get(url,headers=headers)
|
||||
url = "https://www.strava.com/api/v3/activities/"+str(stravaid)+"/streams/velocity_smooth?resolution="+fetchresolution
|
||||
url = "https://www.strava.com/api/v3/activities/"+str(stravaid)+"/streams/velocity_smooth?resolution="+fetchresolution+"&series_type="+series_type
|
||||
velojson = requests.get(url,headers=headers)
|
||||
url = "https://www.strava.com/api/v3/activities/"+str(stravaid)+"/streams/distance?resolution="+fetchresolution
|
||||
url = "https://www.strava.com/api/v3/activities/"+str(stravaid)+"/streams/distance?resolution="+fetchresolution+"&series_type="+series_type
|
||||
distancejson = requests.get(url,headers=headers)
|
||||
url = "https://www.strava.com/api/v3/activities/"+str(stravaid)+"/streams/latlng?resolution="+fetchresolution
|
||||
url = "https://www.strava.com/api/v3/activities/"+str(stravaid)+"/streams/latlng?resolution="+fetchresolution+"&series_type="+series_type
|
||||
latlongjson = requests.get(url,headers=headers)
|
||||
|
||||
try:
|
||||
t = np.array(timejson.json()[0]['data'])
|
||||
d = np.array(distancejson.json()[0]['data'])
|
||||
nr_rows = len(t)
|
||||
if nr_rows == 0:
|
||||
return (0,"Error: Time data had zero length")
|
||||
except KeyError:
|
||||
return (0,"something went wrong with the Strava import")
|
||||
|
||||
try:
|
||||
print spmjson.json()
|
||||
spm = np.array(spmjson.json()[1]['data'])
|
||||
except IndexError:
|
||||
except:
|
||||
spm = np.zeros(nr_rows)
|
||||
|
||||
try:
|
||||
hr = np.array(hrjson.json()[1]['data'])
|
||||
except IndexError:
|
||||
except IndexError,KeyError:
|
||||
hr = np.zeros(nr_rows)
|
||||
|
||||
try:
|
||||
velo = np.array(velojson.json()[1]['data'])
|
||||
except IndexError:
|
||||
except IndexError,KeyError:
|
||||
velo = np.zeros(nr_rows)
|
||||
|
||||
dt = np.diff(t).mean()
|
||||
@@ -193,7 +198,7 @@ def get_strava_workout(user,stravaid):
|
||||
try:
|
||||
lat = coords[:,0]
|
||||
lon = coords[:,1]
|
||||
except IndexError:
|
||||
except IndexError,KeyError:
|
||||
lat = np.zeros(len(t))
|
||||
lon = np.zeros(len(t))
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
</div>
|
||||
<div class="grid_6 alpha">
|
||||
|
||||
<div class="grid_2 suffix_4 alpha">
|
||||
<div class="grid_2 alpha">
|
||||
<p>
|
||||
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
|
||||
<a class="button blue small" href="/rowers/workout/{{ workout.id }}/histo">Power Histogram</a>
|
||||
@@ -133,9 +133,20 @@
|
||||
Plot the Power Histogram of this workout
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid_2 suffix_2 omega">
|
||||
<p>
|
||||
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
|
||||
<a class="button blue small" href="/rowers/workout/fusion/{{ workout.id }}/">Sensor Fusion</a>
|
||||
{% else %}
|
||||
<a class="button blue small" href="/rowers/promembership">Dist Metrics Plot</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>
|
||||
Merge data from another source into this workout
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="advancedplots" class="grid_6 omega">
|
||||
<div class="grid_6 alpha">
|
||||
|
||||
|
||||
@@ -68,60 +68,50 @@
|
||||
<p> </p>
|
||||
|
||||
<div id="plotbuttons" class="grid_12 alpha">
|
||||
|
||||
|
||||
<div id="x-axis" class="grid_6 alpha">
|
||||
<div class="grid_2 alpha dropdown">
|
||||
<button class="grid_2 alpha button blue small dropbtn">X-axis</button>
|
||||
<div class="dropdown-content">
|
||||
<a class="button blue small alpha" href="/rowers/workout/compare/{{ id1 }}/{{ id2 }}/time/{{ yparam }}/{{ plottype }}">Time</a>
|
||||
<a class="button blue small alpha" href="/rowers/workout/compare/{{ id1 }}/{{ id2 }}/distance/{{ yparam }}/{{ plottype }}">Distance</a>
|
||||
{% for key, value in axchoicesbasic.items %}
|
||||
{% if key != 'None' %}
|
||||
<a class="button blue small alpha" href="/rowers/workout/compare/{{ id1}}/{{ id2 }}/{{ key }}/{{ yparam }}/{{ plottype }}">{{ value }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if promember %}
|
||||
<a class="button blue small alpha" href="/rowers/workout/compare/{{ id1 }}/{{ id2 }}/power/{{ yparam }}/scatter">Power</a>
|
||||
<a class="button blue small alpha" href="/rowers/workout/compare/{{ id1 }}/{{ id2 }}/hr/{{ yparam }}/scatter">HR</a>
|
||||
<a class="button blue small alpha" href="/rowers/workout/compare/{{ id1 }}/{{ id2 }}/spm/{{ yparam }}/scatter">SPM</a>
|
||||
<a class="button blue small alpha" href="/rowers/workout/compare/{{ id1 }}/{{ id2 }}/peakforce/{{ yparam }}/scatter">Peak Force</a>
|
||||
<a class="button blue small alpha" href="/rowers/workout/compare/{{ id1 }}/{{ id2 }}/averageforce/{{ yparam }}/scatter">Average Force</a>
|
||||
<a class="button blue small alpha" href="/rowers/workout/compare/{{ id1 }}/{{ id2 }}/forceratio/{{ yparam }}/scatter">Average/Peak Force Ratio</a>
|
||||
<a class="button blue small alpha" href="/rowers/workout/compare/{{ id1 }}/{{ id2 }}/drivelength/{{ yparam }}/scatter">Drive Length</a>
|
||||
<a class="button blue small alpha" href="/rowers/workout/compare/{{ id1 }}/{{ id2 }}/driveenergy/{{ yparam }}/scatter">Work per Stroke</a>
|
||||
<a class="button blue small alpha" href="/rowers/workout/compare/{{ id1 }}/{{ id2 }}/drivespeed/{{ yparam }}/scatter">Drive Speed</a>
|
||||
{% for key, value in axchoicespro.items %}
|
||||
<a class="button blue small alpha" href="/rowers/workout/compare/{{ id1 }}/{{ id2 }}/{{ key }}/{{ yparam }}/{{ plottype }}">{{ value }}</a>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<a class="button rosy small" href="/rowers/promembership">Power (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">HR (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">SPM (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Peak Force (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Average Force (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Average/Peak Force Ratio (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Drive Length (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Work per Stroke (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Drive Speed (Pro)</a>
|
||||
|
||||
{% for key, value in axchoicespro.items %}
|
||||
<a class="button rosy small" href="/rowers/promembership">{{ value }}</a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="grid_2 suffix_2 omega dropdown">
|
||||
<button class="grid_2 alpha button blue small dropbtn">Y-axis</button>
|
||||
<div class="dropdown-content">
|
||||
<a class="button blue small" href="/rowers/workout/compare/{{ id1 }}/{{ id2 }}/{{ xparam }}/pace/{{ plottype }}">Pace</a>
|
||||
<a class="button blue small" href="/rowers/workout/compare/{{ id1 }}/{{ id2 }}/{{ xparam }}/hr/{{ plottype }}">HR</a>
|
||||
<a class="button blue small" href="/rowers/workout/compare/{{ id1 }}/{{ id2 }}/{{ xparam }}/spm/{{ plottype }}">SPM</a>
|
||||
<a class="button blue small" href="/rowers/workout/compare/{{ id1 }}/{{ id2 }}/{{ xparam }}/power/{{ plottype }}">Power</a>
|
||||
{% if promember %}
|
||||
<a class="button blue small" href="/rowers/workout/compare/{{ id1 }}/{{ id2 }}/{{ xparam }}/peakforce/{{ plottype }}">Peak Force</a>
|
||||
<a class="button blue small" href="/rowers/workout/compare/{{ id1 }}/{{ id2 }}/{{ xparam }}/averageforce/{{ plottype }}">Average Force</a>
|
||||
<a class="button blue small" href="/rowers/workout/compare/{{ id1 }}/{{ id2 }}/{{ xparam }}/forceratio/{{ plottype }}">Average/Peak Force Ratio</a>
|
||||
<a class="button blue small" href="/rowers/workout/compare/{{ id1 }}/{{ id2 }}/{{ xparam }}/drivelength/{{ plottype }}">Drive Length</a>
|
||||
<a class="button blue small" href="/rowers/workout/compare/{{ id1 }}/{{ id2 }}/{{ xparam }}/driveenergy/{{ plottype }}">Work per Stroke</a>
|
||||
<a class="button blue small" href="/rowers/workout/compare/{{ id1 }}/{{ id2 }}/{{ xparam }}/drivespeed/{{ plottype }}">Drive Speed</a>
|
||||
{% else %}
|
||||
<a class="button rosy small" href="/rowers/promembership">Peak Force (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Average Force (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Average/Peak Force Ratio (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Drive Length (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Work per Stroke (Pro)</a>
|
||||
{% for key, value in axchoicesbasic.items %}
|
||||
{% if key not in noylist and key != 'None' %}
|
||||
<a class="button blue small" href="/rowers/workout/compare/{{ id1 }}/{{ id2 }}/{{ xparam }}/{{ key }}/{{ plottype }}">{{ value }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if promember %}
|
||||
{% for key, value in axchoicespro.items %}
|
||||
{% if key not in noylist %}
|
||||
<a class="button blue small" href="/rowers/workout/compare/{{ id1 }}/{{ id2 }}/{{ xparam }}/{{ key }}/{{ plottype }}">{{ value }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% for key, value in axchoicespro.items %}
|
||||
{% if key not in noylist %}
|
||||
<a class="button rosy small" href="/rowers/promembership">{{ value }} (Pro)</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -47,13 +47,13 @@
|
||||
<ul>
|
||||
<li>Advantages
|
||||
<ul>
|
||||
<li>It may take up to five minutes for the workout to show up
|
||||
on the site.</li>
|
||||
<li>It's a simple process, which can be automated.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Disadvantages
|
||||
<ul>
|
||||
<li>It's a simple process, which can be automated.</li>
|
||||
<li>It may take up to five minutes for the workout to show up
|
||||
on the site.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -86,7 +86,7 @@
|
||||
<li>The API is not stable and not fully tested yet.</li>
|
||||
<li>You need to register your app with us. We can revoke your
|
||||
permissions if you misuse them.</li>
|
||||
<li>The first time user must grant permissions to your app.</li>
|
||||
<li>The user user must grant permissions to your app.</li>
|
||||
<li>You need to manage authorization tokens.</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
@@ -51,121 +51,69 @@
|
||||
<div class="grid_2 alpha dropdown">
|
||||
<button class="grid_2 alpha button blue small dropbtn">X-axis</button>
|
||||
<div class="dropdown-content">
|
||||
<a class="button blue small alpha" href="/rowers/workout/{{ id }}/flexchart/time/{{ yparam1 }}/{{ yparam2 }}/{{ plottype }}">Time</a>
|
||||
<a class="button blue small alpha" href="/rowers/workout/{{ id }}/flexchart/distance/{{ yparam1 }}/{{ yparam2 }}/{{ plottype }}">Distance</a>
|
||||
{% for key, value in axchoicesbasic.items %}
|
||||
{% if key != 'None' %}
|
||||
<a class="button blue small alpha" href="/rowers/workout/{{ id }}/flexchart/{{ key }}/{{ yparam1 }}/{{ yparam2 }}/{{ plottype }}">{{ value }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if promember %}
|
||||
<a class="button blue small alpha" href="/rowers/workout/{{ id }}/flexchart/power/{{ yparam1 }}/{{ yparam2 }}/scatter">Power</a>
|
||||
<a class="button blue small alpha" href="/rowers/workout/{{ id }}/flexchart/hr/{{ yparam1 }}/{{ yparam2 }}/scatter">HR</a>
|
||||
<a class="button blue small alpha" href="/rowers/workout/{{ id }}/flexchart/spm/{{ yparam1 }}/{{ yparam2 }}/scatter">SPM</a>
|
||||
<a class="button blue small alpha" href="/rowers/workout/{{ id }}/flexchart/peakforce/{{ yparam1 }}/{{ yparam2 }}/scatter">Peak Force</a>
|
||||
<a class="button blue small alpha" href="/rowers/workout/{{ id }}/flexchart/averageforce/{{ yparam1 }}/{{ yparam2 }}/scatter">Average Force</a>
|
||||
<a class="button blue small alpha" href="/rowers/workout/{{ id }}/flexchart/forceratio/{{ yparam1 }}/{{ yparam2 }}/scatter">Average/Peak force ratio</a>
|
||||
<a class="button blue small alpha" href="/rowers/workout/{{ id }}/flexchart/drivelength/{{ yparam1 }}/{{ yparam2 }}/scatter">Drive Length</a>
|
||||
<a class="button blue small alpha" href="/rowers/workout/{{ id }}/flexchart/driveenergy/{{ yparam1 }}/{{ yparam2 }}/scatter">Work per Stroke</a>
|
||||
<a class="button blue small alpha" href="/rowers/workout/{{ id }}/flexchart/drivespeed/{{ yparam1 }}/{{ yparam2 }}/scatter">Drive Speed</a>
|
||||
|
||||
<a class="button blue small alpha" href="/rowers/workout/{{ id }}/flexchart/catch/{{ yparam1 }}/{{ yparam2 }}/scatter">Catch Angle</a>
|
||||
<a class="button blue small alpha" href="/rowers/workout/{{ id }}/flexchart/finish/{{ yparam1 }}/{{ yparam2 }}/scatter">Finish Angle</a>
|
||||
<a class="button blue small alpha" href="/rowers/workout/{{ id }}/flexchart/slip/{{ yparam1 }}/{{ yparam2 }}/scatter">Slip</a>
|
||||
<a class="button blue small alpha" href="/rowers/workout/{{ id }}/flexchart/wash/{{ yparam1 }}/{{ yparam2 }}/scatter">Wash</a>
|
||||
<a class="button blue small alpha" href="/rowers/workout/{{ id }}/flexchart/peakforceangle/{{ yparam1 }}/{{ yparam2 }}/scatter">Peak Force Angle</a>
|
||||
{% for key, value in axchoicespro.items %}
|
||||
<a class="button blue small alpha" href="/rowers/workout/{{ id }}/flexchart/{{ key }}/{{ yparam1 }}/{{ yparam2 }}/scatter">{{ value }}</a>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<a class="button rosy small" href="/rowers/promembership">Power (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">HR (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">SPM (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Peak Force (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Average Force (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Average/Peak Force Ratio (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Drive Length (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Work per Stroke (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Drive Speed (Pro)</a>
|
||||
|
||||
<a class="button rosy small" href="/rowers/promembership">Catch Angle (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Finish Angle (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Slip (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Wash (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Peak Force Angle (Pro)</a>
|
||||
|
||||
{% for key, value in axchoicespro.items %}
|
||||
<a class="button rosy small" href="/rowers/promembership">{{ value }}</a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid_2 dropdown">
|
||||
<div id="left-y" class="grid_2 dropdown">
|
||||
<button class="grid_2 alpha button blue small dropbtn">Left</button>
|
||||
<div class="dropdown-content">
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/pace/{{ yparam2 }}/{{ plottype }}">Pace</a>
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/hr/{{ yparam2 }}/{{ plottype }}">HR</a>
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/spm/{{ yparam2 }}/{{ plottype }}">SPM</a>
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/power/{{ yparam2 }}/{{ plottype }}">Power</a>
|
||||
{% for key, value in axchoicesbasic.items %}
|
||||
{% if key not in noylist and key != 'None' %}
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/{{ key }}/{{ yparam2 }}/{{ plottype }}">{{ value }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if promember %}
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/peakforce/{{ yparam2 }}/{{ plottype }}">Peak Force</a>
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/averageforce/{{ yparam2 }}/{{ plottype }}">Average Force</a>
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/forceratio/{{ yparam2 }}/{{ plottype }}">Average/Peak Force Ratio</a>
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/drivelength/{{ yparam2 }}/{{ plottype }}">Drive Length</a>
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/driveenergy/{{ yparam2 }}/{{ plottype }}">Work per Stroke</a>
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/drivespeed/{{ yparam2 }}/{{ plottype }}">Drive Speed</a>
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/rhythm/{{ yparam2 }}/{{ plottype }}">Drive Rhythm</a>
|
||||
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/catch/{{ yparam2 }}/{{ plottype }}">Catch Angle</a>
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/finish/{{ yparam2 }}/{{ plottype }}">Finish Angle</a>
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/slip/{{ yparam2 }}/{{ plottype }}">Slip</a>
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/wash/{{ yparam2 }}/{{ plottype }}">Wash</a>
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/peakforceangle/{{ yparam2 }}/{{ plottype }}">Peak Force Angle</a>
|
||||
{% for key, value in axchoicespro.items %}
|
||||
{% if key not in noylist %}
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/{{ key }}/{{ yparam2 }}/{{ plottype }}">{{ value }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<a class="button rosy small" href="/rowers/promembership">Peak Force (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Average Force (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Average/Peak Force Ratio (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Drive Length (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Work per Stroke (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Drive Speed (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Drive Rhythm (Pro)</a>
|
||||
|
||||
<a class="button rosy small" href="/rowers/promembership">Catch Angle (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Finish Angle(Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Wash (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Slip (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Peak Force Angle (Pro)</a>
|
||||
{% for key, value in axchoicespro.items %}
|
||||
{% if key not in noylist %}
|
||||
<a class="button rosy small" href="/rowers/promembership">{{ value }} (Pro)</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid_2 dropdown omega">
|
||||
<div id="right-y" class="grid_2 dropdown omega">
|
||||
<button class="grid_2 alpha button blue small dropbtn">Right</button>
|
||||
<div class="dropdown-content">
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/{{ yparam1 }}/hr/{{ plottype }}">HR</a>
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/{{ yparam1 }}/spm/{{ plottype }}">SPM</a>
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/{{ yparam1 }}/power/{{ plottype }}">Power</a>
|
||||
{% if promember %}
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/{{ yparam1 }}/peakforce/{{ plottype }}">Peak Force</a>
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/{{ yparam1 }}/averageforce/{{ plottype }}">Average Force</a>
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/{{ yparam1 }}/forceratio/{{ plottype }}">Average/Peak Force Ratio</a>
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/{{ yparam1 }}/drivelength/{{ plottype }}">Drive Length</a>
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/{{ yparam1 }}/driveenergy/{{ plottype }}">Work per Stroke</a>
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/{{ yparam1 }}/drivespeed/{{ plottype }}">Drive Speed</a>
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/{{ yparam1 }}/rhythm/{{ plottype }}">Drive Rhythm</a>
|
||||
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/{{ yparam1 }}/catch/{{ plottype }}">Catch Angle</a>
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/{{ yparam1 }}/finish/{{ plottype }}">Finish Angle</a>
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/{{ yparam1 }}/slip/{{ plottype }}">Slip</a>
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/{{ yparam1 }}/wash/{{ plottype }}">Wash</a>
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/{{ yparam1 }}/peakforceangle/{{ plottype }}">Peak Force Angle</a>
|
||||
{% else %}
|
||||
<a class="button rosy small" href="/rowers/promembership">Peak Force (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Average Force (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Average/Peak Force Ratio (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Drive Length (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Work per Stroke (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Drive Speed (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Drive Rhythm (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Catch Angle (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Finish Angle (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Slip (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Wash (Pro)</a>
|
||||
<a class="button rosy small" href="/rowers/promembership">Peak Force Angle (Pro)</a>
|
||||
{% for key, value in axchoicesbasic.items %}
|
||||
{% if key not in noylist %}
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/{{ yparam1 }}/{{ key }}/{{ plottype }}">{{ value }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if promember %}
|
||||
{% for key, value in axchoicespro.items %}
|
||||
{% if key not in noylist %}
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/{{ yparam1 }}/{{ key }}/{{ plottype }}">{{ value }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% for key, value in axchoicespro.items %}
|
||||
{% if key not in noylist %}
|
||||
<a class="button rosy small" href="/rowers/promembership">{{ value }} (Pro)</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart/{{ xparam }}/{{ yparam1 }}/None/{{ plottype }}">None</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load rowerfilters %}
|
||||
|
||||
{% block title %}Workouts{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
||||
<div class="grid_12 alpha">
|
||||
<h3>Fusion Editor</h3>
|
||||
</div>
|
||||
<div class="grid_12 alpha">
|
||||
<div class="grid_6 alpha">
|
||||
<p>
|
||||
Adding sensor data from workout {{ workout2.id }} into workout {{ workout1.id }}.
|
||||
This will create a new workout. After you submit the form, you will be
|
||||
taken to the newly created workout. If you are happy with the result, you
|
||||
can delete the two original workouts manually.
|
||||
</p>
|
||||
<p>
|
||||
Workout 1: {{ workout1.name }}
|
||||
</p>
|
||||
<p>
|
||||
Workout 2: {{ workout2.name }}
|
||||
</p>
|
||||
<p>On the right hand side, please select the columns from workout 2 that
|
||||
you want to replace the equivalent columns in workout 1. </p>
|
||||
</div>
|
||||
<div class="grid_4">
|
||||
|
||||
<form enctype="multipart/form-data" action="" method="post">
|
||||
|
||||
<table>
|
||||
{{ form.as_table }}
|
||||
</table>
|
||||
{% csrf_token %}
|
||||
</div>
|
||||
<div class="grid_2 omega">
|
||||
<input name='fusion' class="button green" type="submit" value="Submit"> </form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,121 @@
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load rowerfilters %}
|
||||
|
||||
{% block title %}Workouts{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="workouts" class="grid_4 alpha">
|
||||
<div class="grid_4 alpha">
|
||||
<h1>Workout {{ id }}</h1>
|
||||
<table width=100%>
|
||||
<tr>
|
||||
<th>Rower:</th><td>{{ first_name }} {{ last_name }}</td>
|
||||
</tr><tr>
|
||||
<tr>
|
||||
<th>Name:</th><td>{{ workout.name }}</td>
|
||||
</tr><tr>
|
||||
<tr>
|
||||
<th>Date:</th><td>{{ workout.date }}</td>
|
||||
</tr><tr>
|
||||
<th>Time:</th><td>{{ workout.starttime }}</td>
|
||||
</tr><tr>
|
||||
<th>Distance:</th><td>{{ workout.distance }}m</td>
|
||||
</tr><tr>
|
||||
<th>Duration:</th><td>{{ workout.duration |durationprint:"%H:%M:%S.%f" }}</td>
|
||||
</tr><tr>
|
||||
<th>Type:</th><td>{{ workout.workouttype }}</td>
|
||||
</tr><tr>
|
||||
<th>Weight Category:</th><td>{{ workout.weightcategory }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="grid_4 alpha">
|
||||
<p>
|
||||
<form id="searchform" action=""
|
||||
method="get" accept-charset="utf-8">
|
||||
<button class="button blue small" type="submit">
|
||||
Search
|
||||
</button>
|
||||
<input class="searchfield" id="searchbox" name="q" type="text" placeholder="Search">
|
||||
</form>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Select start and end date for a date range:
|
||||
<div class="grid_4 alpha">
|
||||
<p>
|
||||
<form enctype="multipart/form-data" action="/rowers/workout/fusion/{{ id }}/" method="post">
|
||||
|
||||
<table>
|
||||
{{ dateform.as_table }}
|
||||
</table>
|
||||
{% csrf_token %}
|
||||
</div>
|
||||
<div class="grid_2 suffix_2 omega">
|
||||
<input name='daterange' class="button green" type="submit" value="Submit"> </form>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div id="fusion" class="grid_8 omega">
|
||||
<h1>Fuse this workout with data from:</h1>
|
||||
{% if workouts %}
|
||||
<table width="100%" class="listtable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th> Date</th>
|
||||
<th> Time</th>
|
||||
<th> Name</th>
|
||||
<th> Type</th>
|
||||
<th> Distance </th>
|
||||
<th> Duration </th>
|
||||
<th> Avg HR </th>
|
||||
<th> Max HR </th>
|
||||
<th> Fusion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</tbody>
|
||||
{% for cworkout in workouts %}
|
||||
<tr>
|
||||
<td> {{ cworkout.date }} </td>
|
||||
<td> {{ cworkout.starttime }} </td>
|
||||
<td> <a href="/rowers/workout/{{ workout.id }}/edit">{{ cworkout.name }}</a> </td>
|
||||
<td> {{ cworkout.workouttype }} </td>
|
||||
<td> {{ cworkout.distance }}m</td>
|
||||
<td> {{ cworkout.duration |durationprint:"%H:%M:%S.%f" }} </td>
|
||||
<td> {{ cworkout.averagehr }} </td>
|
||||
<td> {{ cworkout.maxhr }} </td>
|
||||
{% if id == cworkout.id %}
|
||||
<td> </td>
|
||||
{% else %}
|
||||
<td> <a class="button blue small" href="/rowers/workout/fusion/{{ id }}/{{ cworkout.id }}">Fusion</a> </td>
|
||||
{% endif %}
|
||||
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p> No workouts found </p>
|
||||
{% endif %}
|
||||
|
||||
<div class="grid_2 prefix_5 suffix_1 omega">
|
||||
<span class="button gray small">
|
||||
{% if workouts.has_previous %}
|
||||
<a class="wh" href="/rowers/workout/fusion/{{ id }}/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}?page={{ workouts.previous_page_number }}"><</a>
|
||||
{% endif %}
|
||||
|
||||
<span>
|
||||
Page {{ workouts.number }} of {{ workouts.paginator.num_pages }}.
|
||||
</span>
|
||||
|
||||
{% if workouts.has_next %}
|
||||
<a class="wh" href="/rowers/workout/fusion/{{ id }}/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}?page={{ workouts.next_page_number }}">></a>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -189,6 +189,10 @@ urlpatterns = [
|
||||
url(r'^workout/(\d+)/interactiveplot$',views.workout_biginteractive_view),
|
||||
url(r'^workout/(\d+)/view$',views.workout_view),
|
||||
url(r'^workout/(\d+)$',views.workout_view),
|
||||
url(r'^workout/fusion/(?P<id1>\d+)/(?P<id2>\d+)$',views.workout_fusion_view),
|
||||
url(r'^workout/fusion/(\d+)/$',views.workout_fusion_list),
|
||||
url(r'^workout/fusion/(?P<id>\d+)/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\w+.*)$',views.workout_fusion_list),
|
||||
|
||||
url(r'^physics$',TemplateView.as_view(template_name='physics.html'),name='physics'),
|
||||
url(r'^workout/(\d+)/$',views.workout_view),
|
||||
url(r'^workout/(\d+)/addtimeplot$',views.workout_add_timeplot_view),
|
||||
|
||||
+221
-26
@@ -26,7 +26,8 @@ from rowers.forms import (
|
||||
StatsOptionsForm,PredictedPieceForm,DateRangeForm,DeltaDaysForm,
|
||||
EmailForm, RegistrationForm, RegistrationFormTermsOfService,
|
||||
RegistrationFormUniqueEmail,CNsummaryForm,UpdateWindForm,
|
||||
UpdateStreamForm,WorkoutMultipleCompareForm,ChartParamChoiceForm
|
||||
UpdateStreamForm,WorkoutMultipleCompareForm,ChartParamChoiceForm,
|
||||
FusionMetricChoiceForm,
|
||||
)
|
||||
from rowers.models import Workout, User, Rower, WorkoutForm,FavoriteChart
|
||||
from rowers.models import (
|
||||
@@ -231,7 +232,6 @@ def rower_register_view(request):
|
||||
theuser.email = email
|
||||
theuser.save()
|
||||
|
||||
|
||||
therower = Rower(user=theuser)
|
||||
|
||||
therower.save()
|
||||
@@ -2320,6 +2320,85 @@ def workout_comparison_list(request,id=0,message='',successmessage='',
|
||||
except Rower.DoesNotExist:
|
||||
raise Http404("User has no rower instance")
|
||||
|
||||
# List of workouts to compare a selected workout to
|
||||
@user_passes_test(ispromember,login_url="/",redirect_field_name=None)
|
||||
def workout_fusion_list(request,id=0,message='',successmessage='',
|
||||
startdatestring="",enddatestring="",
|
||||
startdate=timezone.now()-datetime.timedelta(days=365),
|
||||
enddate=timezone.now()):
|
||||
|
||||
try:
|
||||
r = Rower.objects.get(user=request.user)
|
||||
u = User.objects.get(id=r.user.id)
|
||||
if request.method == 'POST':
|
||||
dateform = DateRangeForm(request.POST)
|
||||
if dateform.is_valid():
|
||||
startdate = dateform.cleaned_data['startdate']
|
||||
enddate = dateform.cleaned_data['enddate']
|
||||
else:
|
||||
dateform = DateRangeForm(initial={
|
||||
'startdate':startdate,
|
||||
'enddate':enddate,
|
||||
})
|
||||
|
||||
if startdatestring:
|
||||
startdate = iso8601.parse_date(startdatestring)
|
||||
if enddatestring:
|
||||
enddate = iso8601.parse_date(enddatestring)
|
||||
|
||||
startdate = datetime.datetime.combine(startdate,datetime.time())
|
||||
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
|
||||
enddate = enddate+datetime.timedelta(days=1)
|
||||
|
||||
if enddate < startdate:
|
||||
s = enddate
|
||||
enddate = startdate
|
||||
startdate = s
|
||||
|
||||
workouts = Workout.objects.filter(user=r,
|
||||
startdatetime__gte=startdate,
|
||||
startdatetime__lte=enddate).order_by("-date", "-starttime").exclude(id=id)
|
||||
|
||||
query = request.GET.get('q')
|
||||
if query:
|
||||
query_list = query.split()
|
||||
workouts = workouts.filter(
|
||||
reduce(operator.and_,
|
||||
(Q(name__icontains=q) for q in query_list)) |
|
||||
reduce(operator.and_,
|
||||
(Q(notes__icontains=q) for q in query_list))
|
||||
)
|
||||
|
||||
paginator = Paginator(workouts,15) # show 25 workouts per page
|
||||
page = request.GET.get('page')
|
||||
|
||||
try:
|
||||
workouts = paginator.page(page)
|
||||
except PageNotAnInteger:
|
||||
workouts = paginator.page(1)
|
||||
except EmptyPage:
|
||||
workouts = paginator.page(paginator.num_pages)
|
||||
try:
|
||||
row = Workout.objects.get(id=id)
|
||||
except Workout.DoesNotExist:
|
||||
raise Http404("Workout doesn't exist")
|
||||
|
||||
|
||||
return render(request, 'fusion_list.html',
|
||||
{'id':id,
|
||||
'workout':row,
|
||||
'workouts': workouts,
|
||||
'last_name':u.last_name,
|
||||
'first_name':u.first_name,
|
||||
'message': message,
|
||||
'successmessage':successmessage,
|
||||
'dateform':dateform,
|
||||
'startdate':startdate,
|
||||
'enddate':enddate,
|
||||
})
|
||||
except Rower.DoesNotExist:
|
||||
raise Http404("User has no rower instance")
|
||||
|
||||
# Basic 'EDIT' view of workout
|
||||
def workout_view(request,id=0):
|
||||
try:
|
||||
@@ -2627,7 +2706,7 @@ def workout_wind_view(request,id=0,message="",successmessage=""):
|
||||
|
||||
# get data
|
||||
f1 = row.csvfilename
|
||||
u = request.user
|
||||
u = row.user.user
|
||||
r = Rower.objects.get(user=u)
|
||||
|
||||
# create bearing
|
||||
@@ -2742,7 +2821,7 @@ def workout_stream_view(request,id=0,message="",successmessage=""):
|
||||
|
||||
# create interactive plot
|
||||
f1 = row.csvfilename
|
||||
u = request.user
|
||||
u = row.user.user
|
||||
r = Rower.objects.get(user=u)
|
||||
|
||||
rowdata = rdata(f1)
|
||||
@@ -2902,7 +2981,7 @@ def workout_geeky_view(request,id=0,message="",successmessage=""):
|
||||
|
||||
# create interactive plot
|
||||
f1 = row.csvfilename
|
||||
u = request.user
|
||||
u = row.user.user
|
||||
r = Rower.objects.get(user=u)
|
||||
|
||||
# create interactive plot
|
||||
@@ -3163,10 +3242,6 @@ def workout_stats_view(request,id=0,message="",successmessage=""):
|
||||
fieldlist,fielddict = dataprep.getstatsfields()
|
||||
fielddict.pop('workoutstate')
|
||||
|
||||
print "aap"
|
||||
print datadf['catch'].mean()
|
||||
print "noot"
|
||||
|
||||
for field,verbosename in fielddict.iteritems():
|
||||
thedict = {
|
||||
'mean':datadf[field].mean(),
|
||||
@@ -3223,7 +3298,7 @@ def workout_advanced_view(request,id=0,message="",successmessage=""):
|
||||
|
||||
# create interactive plot
|
||||
f1 = row.csvfilename
|
||||
u = request.user
|
||||
u = row.user.user
|
||||
r = Rower.objects.get(user=u)
|
||||
|
||||
# create interactive plot
|
||||
@@ -3266,6 +3341,10 @@ def workout_comparison_view(request,id1=0,id2=0,xparam='distance',yparam='spm'):
|
||||
script = res[0]
|
||||
div = res[1]
|
||||
|
||||
axchoicesbasic = {ax[0]:ax[1] for ax in axes if ax[4]=='basic'}
|
||||
axchoicespro = {ax[0]:ax[1] for ax in axes if ax[4]=='pro'}
|
||||
noylist = ["time","distance"]
|
||||
axchoicesbasic.pop("cumdist")
|
||||
|
||||
return render(request,
|
||||
'comparisonchart.html',
|
||||
@@ -3273,6 +3352,9 @@ def workout_comparison_view(request,id1=0,id2=0,xparam='distance',yparam='spm'):
|
||||
'the_div':div,
|
||||
'id1':id1,
|
||||
'id2':id2,
|
||||
'axchoicesbasic':axchoicesbasic,
|
||||
'axchoicespro':axchoicespro,
|
||||
'noylist':noylist,
|
||||
'xparam':xparam,
|
||||
'yparam':yparam,
|
||||
})
|
||||
@@ -3296,12 +3378,33 @@ def workout_comparison_view2(request,id1=0,id2=0,xparam='distance',
|
||||
script = res[0]
|
||||
div = res[1]
|
||||
|
||||
axchoicesbasic = {ax[0]:ax[1] for ax in axes if ax[4]=='basic'}
|
||||
axchoicespro = {ax[0]:ax[1] for ax in axes if ax[4]=='pro'}
|
||||
noylist = ["time","distance"]
|
||||
axchoicesbasic.pop("cumdist")
|
||||
|
||||
row1 = Workout.objects.get(id=id1)
|
||||
row2 = Workout.objects.get(id=id2)
|
||||
|
||||
if row1.workouttype != 'water' or row2.workouttype != 'water':
|
||||
axchoicespro.pop('slip')
|
||||
axchoicespro.pop('wash')
|
||||
axchoicespro.pop('catch')
|
||||
axchoicespro.pop('finish')
|
||||
axchoicespro.pop('totalangle')
|
||||
axchoicespro.pop('effectiveangle')
|
||||
axchoicespro.pop('peakforceangle')
|
||||
|
||||
|
||||
return render(request,
|
||||
'comparisonchart2.html',
|
||||
{'interactiveplot':script,
|
||||
'the_div':div,
|
||||
'id1':id1,
|
||||
'id2':id2,
|
||||
'axchoicesbasic':axchoicesbasic,
|
||||
'axchoicespro':axchoicespro,
|
||||
'noylist':noylist,
|
||||
'xparam':xparam,
|
||||
'yparam':yparam,
|
||||
'plottype':plottype,
|
||||
@@ -3443,6 +3546,12 @@ def workout_flexchart3_view(request,*args,**kwargs):
|
||||
# js_resources = res[2]
|
||||
# css_resources = res[3]
|
||||
|
||||
|
||||
axchoicesbasic = {ax[0]:ax[1] for ax in axes if ax[4]=='basic'}
|
||||
axchoicespro = {ax[0]:ax[1] for ax in axes if ax[4]=='pro'}
|
||||
noylist = ["time","distance"]
|
||||
axchoicesbasic.pop("cumdist")
|
||||
|
||||
if row.workouttype == 'water':
|
||||
return render(request,
|
||||
'flexchart3otw.html',
|
||||
@@ -3457,13 +3566,25 @@ def workout_flexchart3_view(request,*args,**kwargs):
|
||||
'plottype':plottype,
|
||||
'mayedit':mayedit,
|
||||
'promember':promember,
|
||||
'axchoicesbasic':axchoicesbasic,
|
||||
'axchoicespro':axchoicespro,
|
||||
'noylist':noylist,
|
||||
'workstrokesonly': not workstrokesonly,
|
||||
'favoritenr':favoritenr,
|
||||
'maxfav':maxfav,
|
||||
})
|
||||
else:
|
||||
axchoicespro.pop('slip')
|
||||
axchoicespro.pop('wash')
|
||||
axchoicespro.pop('catch')
|
||||
axchoicespro.pop('finish')
|
||||
axchoicespro.pop('totalangle')
|
||||
axchoicespro.pop('effectiveangle')
|
||||
axchoicespro.pop('peakforceangle')
|
||||
|
||||
|
||||
return render(request,
|
||||
'flexchart3.html',
|
||||
'flexchart3otw.html',
|
||||
{'the_script':script,
|
||||
'the_div':div,
|
||||
'js_res': js_resources,
|
||||
@@ -3473,6 +3594,9 @@ def workout_flexchart3_view(request,*args,**kwargs):
|
||||
'yparam1':yparam1,
|
||||
'yparam2':yparam2,
|
||||
'plottype':plottype,
|
||||
'axchoicesbasic':axchoicesbasic,
|
||||
'axchoicespro':axchoicespro,
|
||||
'noylist':noylist,
|
||||
'mayedit':mayedit,
|
||||
'promember':promember,
|
||||
'workstrokesonly': not workstrokesonly,
|
||||
@@ -3493,7 +3617,7 @@ def workout_biginteractive_view(request,id=0,message="",successmessage=""):
|
||||
|
||||
# create interactive plot
|
||||
f1 = row.csvfilename
|
||||
u = request.user
|
||||
u = row.user.user
|
||||
# r = Rower.objects.get(user=u)
|
||||
|
||||
promember=0
|
||||
@@ -3534,7 +3658,7 @@ def workout_otwpowerplot_view(request,id=0,message="",successmessage=""):
|
||||
|
||||
# create interactive plot
|
||||
f1 = row.csvfilename
|
||||
u = request.user
|
||||
u = row.user.user
|
||||
# r = Rower.objects.get(user=u)
|
||||
|
||||
promember=0
|
||||
@@ -3615,7 +3739,7 @@ def workout_unsubscribe_view(request,id=0):
|
||||
user=request.user).order_by("created")
|
||||
|
||||
for c in comments:
|
||||
c.notify = False
|
||||
c.notification = False
|
||||
c.save()
|
||||
|
||||
form = WorkoutCommentForm()
|
||||
@@ -3703,12 +3827,26 @@ def workout_comment_view(request,id=0):
|
||||
|
||||
form = WorkoutCommentForm()
|
||||
|
||||
g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime")
|
||||
|
||||
if (len(g)<=3):
|
||||
return render(request,
|
||||
'workout_comments.html',
|
||||
{'workout':w,
|
||||
'graphs1':g[0:3],
|
||||
'comments':comments,
|
||||
'form':form,
|
||||
})
|
||||
else:
|
||||
return render(request,
|
||||
'workout_comments.html',
|
||||
{'workout':w,
|
||||
'graphs1':g[0:3],
|
||||
'graphs1':g[3:6],
|
||||
'comments':comments,
|
||||
'form':form,
|
||||
})
|
||||
|
||||
|
||||
# The basic edit page
|
||||
@login_required()
|
||||
@@ -3718,6 +3856,7 @@ def workout_edit_view(request,id=0,message="",successmessage=""):
|
||||
try:
|
||||
# check if valid ID exists (workout exists)
|
||||
row = Workout.objects.get(id=id)
|
||||
form = WorkoutForm(instance=row)
|
||||
except Workout.DoesNotExist:
|
||||
raise Http404("Workout doesn't exist")
|
||||
|
||||
@@ -3767,7 +3906,6 @@ def workout_edit_view(request,id=0,message="",successmessage=""):
|
||||
r.write_csv(row.csvfilename,gzip=True)
|
||||
dataprep.update_strokedata(id,r.df)
|
||||
successmessage = "Changes saved"
|
||||
url = "/rowers/workout/"+str(row.id)+"/edit"
|
||||
url = reverse(workout_edit_view,
|
||||
kwargs = {
|
||||
'id':str(row.id),
|
||||
@@ -3797,7 +3935,7 @@ def workout_edit_view(request,id=0,message="",successmessage=""):
|
||||
|
||||
# create interactive plot
|
||||
f1 = row.csvfilename
|
||||
u = request.user
|
||||
u = row.user.user
|
||||
r = Rower.objects.get(user=u)
|
||||
rowdata = rdata(f1)
|
||||
hascoordinates = 1
|
||||
@@ -3866,7 +4004,7 @@ def workout_add_otw_powerplot_view(request,id):
|
||||
timestr = strftime("%Y%m%d-%H%M%S")
|
||||
imagename = f1+timestr+'.png'
|
||||
fullpathimagename = 'static/plots/'+imagename
|
||||
u = request.user
|
||||
u = w.user.user
|
||||
r = Rower.objects.get(user=u)
|
||||
powerperc = 100*np.array([r.pw_ut2,
|
||||
r.pw_ut1,
|
||||
@@ -3923,7 +4061,7 @@ def workout_add_piechart_view(request,id):
|
||||
timestr = strftime("%Y%m%d-%H%M%S")
|
||||
imagename = f1+timestr+'.png'
|
||||
fullpathimagename = 'static/plots/'+imagename
|
||||
u = request.user
|
||||
u = w.user.user
|
||||
r = Rower.objects.get(user=u)
|
||||
|
||||
powerperc = 100*np.array([r.pw_ut2,
|
||||
@@ -3981,7 +4119,7 @@ def workout_add_power_piechart_view(request,id):
|
||||
timestr = strftime("%Y%m%d-%H%M%S")
|
||||
imagename = f1+timestr+'.png'
|
||||
fullpathimagename = 'static/plots/'+imagename
|
||||
u = request.user
|
||||
u = w.user.user
|
||||
r = Rower.objects.get(user=u)
|
||||
|
||||
powerperc = 100*np.array([r.pw_ut2,
|
||||
@@ -4037,7 +4175,7 @@ def workout_add_timeplot_view(request,id):
|
||||
timestr = strftime("%Y%m%d-%H%M%S")
|
||||
imagename = f1+timestr+'.png'
|
||||
fullpathimagename = 'static/plots/'+imagename
|
||||
u = request.user
|
||||
u = w.user.user
|
||||
r = Rower.objects.get(user=u)
|
||||
powerperc = 100*np.array([r.pw_ut2,
|
||||
r.pw_ut1,
|
||||
@@ -4094,7 +4232,7 @@ def workout_add_distanceplot_view(request,id):
|
||||
timestr = strftime("%Y%m%d-%H%M%S")
|
||||
imagename = f1+timestr+'.png'
|
||||
fullpathimagename = 'static/plots/'+imagename
|
||||
u = request.user
|
||||
u = w.user.user
|
||||
r = Rower.objects.get(user=u)
|
||||
powerperc = 100*np.array([r.pw_ut2,
|
||||
r.pw_ut1,
|
||||
@@ -4149,7 +4287,7 @@ def workout_add_distanceplot2_view(request,id):
|
||||
timestr = strftime("%Y%m%d-%H%M%S")
|
||||
imagename = f1+timestr+'.png'
|
||||
fullpathimagename = 'static/plots/'+imagename
|
||||
u = request.user
|
||||
u = w.user.user
|
||||
r = Rower.objects.get(user=u)
|
||||
powerperc = 100*np.array([r.pw_ut2,
|
||||
r.pw_ut1,
|
||||
@@ -4206,7 +4344,7 @@ def workout_add_timeplot2_view(request,id):
|
||||
timestr = strftime("%Y%m%d-%H%M%S")
|
||||
imagename = f1+timestr+'.png'
|
||||
fullpathimagename = 'static/plots/'+imagename
|
||||
u = request.user
|
||||
u = w.user.user
|
||||
r = Rower.objects.get(user=u)
|
||||
powerperc = 100*np.array([r.pw_ut2,
|
||||
r.pw_ut1,
|
||||
@@ -4404,7 +4542,7 @@ def workout_c2import_view(request,message=""):
|
||||
def workout_getstravaworkout_view(request,stravaid):
|
||||
res = stravastuff.get_strava_workout(request.user,stravaid)
|
||||
if not res[0]:
|
||||
message = "Something went wrong in Strava import"
|
||||
message = res[1]
|
||||
return imports_view(request,message=message)
|
||||
|
||||
strokedata = res[1]
|
||||
@@ -4900,7 +5038,7 @@ def workout_summary_restore_view(request,id,message="",successmessage=""):
|
||||
s = ""
|
||||
# still here - this is a workout we may edit
|
||||
f1 = row.csvfilename
|
||||
u = request.user
|
||||
u = row.user.user
|
||||
r = Rower.objects.get(user=u)
|
||||
powerperc = 100*np.array([r.pw_ut2,
|
||||
r.pw_ut1,
|
||||
@@ -4940,6 +5078,63 @@ def workout_summary_restore_view(request,id,message="",successmessage=""):
|
||||
)
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
# Fuse two workouts
|
||||
@user_passes_test(ispromember,login_url="/",redirect_field_name=None)
|
||||
def workout_fusion_view(request,id1=0,id2=1):
|
||||
try:
|
||||
w1 = Workout.objects.get(id=id1)
|
||||
w2 = Workout.objects.get(id=id2)
|
||||
r = w1.user
|
||||
if (checkworkoutuser(request.user,w1)==False) or \
|
||||
(checkworkoutuser(request.user,w2)==False):
|
||||
raise PermissionDenied("You are not allowed to use these workouts")
|
||||
except Workout.DoesNotExist:
|
||||
raise Http404("One of the workouts doesn't exist")
|
||||
|
||||
if request.method == 'POST':
|
||||
form = FusionMetricChoiceForm(request.POST,instance=w2)
|
||||
if form.is_valid():
|
||||
cd = form.cleaned_data
|
||||
columns = cd['columns']
|
||||
timeoffset = cd['offset']
|
||||
posneg = cd['posneg']
|
||||
if posneg == 'neg':
|
||||
timeoffset = -timeoffset
|
||||
df = dataprep.datafusion(id1,id2,columns,timeoffset)
|
||||
idnew,message = dataprep.new_workout_from_df(r,df,
|
||||
title='Fused data',
|
||||
parent=w1)
|
||||
if message != None:
|
||||
url = reverse(workout_edit_view,
|
||||
kwargs={
|
||||
'message':message,
|
||||
'id':idnew,
|
||||
})
|
||||
else:
|
||||
successmessage = 'Data fused'
|
||||
url = reverse(workout_edit_view,
|
||||
kwargs={
|
||||
'successmessage':successmessage,
|
||||
'id':idnew,
|
||||
})
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
else:
|
||||
return render(request, 'fusion.html',
|
||||
{'form':form,
|
||||
'workout1':w1,
|
||||
'workout2':w2,
|
||||
})
|
||||
|
||||
|
||||
form = FusionMetricChoiceForm(instance=w2)
|
||||
|
||||
return render(request, 'fusion.html',
|
||||
{'form':form,
|
||||
'workout1':w1,
|
||||
'workout2':w2,
|
||||
})
|
||||
|
||||
|
||||
# Edit the splits/summary
|
||||
@login_required()
|
||||
@@ -4955,7 +5150,7 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
|
||||
s = ""
|
||||
# still here - this is a workout we may edit
|
||||
f1 = row.csvfilename
|
||||
u = request.user
|
||||
u = row.user.user
|
||||
r = Rower.objects.get(user=u)
|
||||
powerperc = 100*np.array([r.pw_ut2,
|
||||
r.pw_ut1,
|
||||
|
||||
Reference in New Issue
Block a user