Private
Public Access
1
0

Merge branch 'release/v1.6'

This commit is contained in:
Sander Roosendaal
2017-03-12 22:34:23 +01:00
14 changed files with 1294 additions and 352 deletions
+495
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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(
+2
View File
@@ -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):
+16 -11
View File
@@ -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:
spm = np.array( spmjson.json()[1]['data'])
except IndexError:
print spmjson.json()
spm = np.array(spmjson.json()[1]['data'])
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))
+13 -2
View File
@@ -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">
+40 -50
View File
@@ -6,14 +6,14 @@
{% block content %}
<script type="text/javascript" src="/static/js/bokeh-0.12.3.min.js"></script>
<script async="true" type="text/javascript">
<script type="text/javascript" src="/static/js/bokeh-0.12.3.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
</script>
{{ interactiveplot |safe }}
{{ interactiveplot |safe }}
<script>
<script>
// Set things up to resize the plot on a window resize. You can play with
// the arguments of resize_width_height() to change the plot's behavior.
var plot_resize_setup = function () {
@@ -27,23 +27,23 @@
plotresizer();
};
window.addEventListener('load', plot_resize_setup);
</script>
<style>
</script>
<style>
/* Need this to get the page in "desktop mode"; not having an infinite height.*/
html, body {height: 100%; margin:5px;}
</style>
</style>
<div id="navigation" class="grid_12 alpha">
{% if user.is_authenticated and mayedit %}
<div class="grid_2 alpha">
<p>
<a class="button gray small" href="/rowers/workout/{{ id }}/edit">Edit Workout</a>
</p>
</p>
</div>
<div class="grid_2 suffix_8 omega">
<p>
<a class="button gray small" href="/rowers/workout/compare/{{ id }}/advanced">Advanced Edit</a>
</p>
</p>
</div>
{% endif %}
@@ -68,60 +68,50 @@
<p>&nbsp;</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>
+4 -4
View File
@@ -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>
+45 -97
View File
@@ -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>
+44
View File
@@ -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 %}
+121
View File
@@ -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>&nbsp;</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 }}">&lt;</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 }}">&gt;</a>
{% endif %}
</span>
</div>
</div>
{% endblock %}
+4
View File
@@ -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
View File
@@ -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,