Private
Public Access
1
0
Files
rowsandall/rowers/views.py
Sander Roosendaal 8b7005b3dc small bug fix
2017-01-25 18:18:13 +01:00

4859 lines
142 KiB
Python

import time
import zipfile
import operator
import warnings
from django.views.generic.base import TemplateView
from django.db.models import Q
from django.db import IntegrityError, transaction
from django.shortcuts import render
from django.http import (
HttpResponse, HttpResponseRedirect,
HttpResponseForbidden, HttpResponseNotAllowed,
HttpResponseNotFound,
)
from django.contrib.auth import authenticate, login, logout
from rowers.forms import LoginForm,DocumentsForm,UploadOptionsForm
from django.core.urlresolvers import reverse
from django.template import RequestContext
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.conf import settings
from django.utils.datastructures import MultiValueDictKeyError
from django.utils import timezone,translation
from django.core.mail import send_mail, BadHeaderError
from rowers.forms import EmailForm, RegistrationForm, RegistrationFormTermsOfService,RegistrationFormUniqueEmail,CNsummaryForm,UpdateWindForm,UpdateStreamForm
from rowers.forms import PredictedPieceForm,DateRangeForm,DeltaDaysForm
from rowers.forms import SummaryStringForm,IntervalUpdateForm,StrokeDataForm
from rowers.models import Workout, User, Rower, WorkoutForm,FavoriteChart
from rowers.models import (
RowerPowerForm,RowerForm,GraphImage,AdvancedWorkoutForm,
RowerPowerZonesForm
)
from rowers.models import FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement
from django.forms.formsets import formset_factory
import StringIO
from django.contrib.auth.decorators import login_required,user_passes_test
from time import strftime,strptime,mktime,time,daylight
import os,sys
import datetime
import iso8601
import c2stuff
from c2stuff import C2NoTokenError
from iso8601 import ParseError
import stravastuff
import sporttracksstuff
import ownapistuff
from ownapistuff import TEST_CLIENT_ID, TEST_CLIENT_SECRET, TEST_REDIRECT_URI
from rowsandall_app.settings import C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET, STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET
from rowsandall_app.settings import SPORTTRACKS_CLIENT_ID, SPORTTRACKS_REDIRECT_URI, SPORTTRACKS_CLIENT_SECRET
import requests
import json
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from rowers.rows import handle_uploaded_file
from rowers.tasks import handle_makeplot,handle_otwsetpower,handle_sendemailtcx,handle_sendemailcsv
from rowers.tasks import handle_sendemail_unrecognized
from scipy.signal import savgol_filter
from django.shortcuts import render_to_response
from shutil import copyfile
from rowingdata import rower as rrower
from rowingdata import main as rmain
from rowingdata import rowingdata as rrdata
from rowingdata import TCXParser,RowProParser,ErgDataParser,TCXParserNoHR
from rowingdata import BoatCoachParser,RowPerfectParser,BoatCoachAdvancedParser
from rowingdata import MysteryParser
from rowingdata import painsledDesktopParser,speedcoachParser,ErgStickParser
from rowingdata import SpeedCoach2Parser,FITParser,fitsummarydata
from rowingdata import make_cumvalues
from rowingdata import summarydata,get_file_type
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from pytz import timezone as tz,utc
import dateutil
import mpld3
from mpld3 import plugins
import stravalib
from stravalib.exc import ActivityUploadFailed,TimeoutExceeded
from weather import get_wind_data
from oauth2_provider.models import Application,Grant,AccessToken
import django_rq
queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('low')
from rest_framework_swagger.views import get_swagger_view
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from rest_framework.response import Response
from rowers.serializers import RowerSerializer,WorkoutSerializer
from rest_framework import status,permissions,generics
from rest_framework.decorators import api_view, renderer_classes
from permissions import IsOwnerOrNot
import plots
import mailprocessing
from io import BytesIO
from scipy.special import lambertw
from dataprep import timedeltaconv
LOCALTIMEZONE = tz('Etc/UTC')
USER_LANGUAGE = 'en-US'
from interactiveplots import *
# Define the API documentation
schema_view = get_swagger_view(title='Rowsandall API (Unstable)')
# Custom error pages with Rowsandall headers
def error500_view(request):
response = render_to_response('500.html', {},
context_instance = RequestContext(request))
response.status_code = 500
return response
def error404_view(request):
response = render_to_response('404.html', {},
context_instance = RequestContext(request))
response.status_code = 404
return response
def error400_view(request):
response = render_to_response('400.html', {},
context_instance = RequestContext(request))
response.status_code = 400
return response
def error403_view(request):
response = render_to_response('403.html', {},
context_instance = RequestContext(request))
response.status_code = 403
return response
# Wrapper around the rowingdata call to catch some exceptions
# Checks for CSV file, then for gzipped CSV file, and if all fails, returns 0
def rdata(file,rower=rrower()):
try:
res = rrdata(file,rower=rower)
except IOError, IndexError:
try:
res = rrdata(file+'.gz',rower=rower)
except IOError, IndexError:
res = 0
return res
# Used for the interval editor - translates seconds to a time object
def get_time(second):
if (second<=0) or (second>1e9):
hours = 0
minutes=0
sec=0
microsecond = 0
elif math.isnan(second):
hours = 0
minutes=0
sec=0
microsecond = 0
else:
days = int(second/(24.*3600.)) % (24*3600)
hours = int((second-24.*3600.*days)/3600.) % 24
minutes = int((second-3600.*(hours+24.*days))/60.) % 60
sec = int(second-3600.*(hours+24.*days)-60.*minutes) % 60
microsecond = int(1.0e6*(second-3600.*(hours+24.*days)-60.*minutes-sec))
return datetime.time(hours,minutes,sec,microsecond)
# get the workout ID from the SportTracks URI
def getidfromsturi(uri):
return uri[len(uri)-8:]
# Splits SportTracks data which is one long sequence of
# [t,[lat,lon],t2,[lat2,lon2] ...]
# to [t,t2,t3, ...], [[lat,long],[lat2,long2],...
def splitstdata(lijst):
t = []
latlong = []
while len(lijst)>=2:
t.append(lijst[0])
latlong.append(lijst[1])
lijst = lijst[2:]
return [np.array(t),np.array(latlong)]
from utils import geo_distance
# Check if a user is a Pro member
def promember(user):
r = Rower.objects.get(user=user)
result = user.is_authenticated() and (r.rowerplan=='pro' or r.rowerplan=='coach')
return result
# User registration
def rower_register_view(request):
if request.method == 'POST':
form = RegistrationFormUniqueEmail(request.POST)
if form.is_valid():
first_name = form.cleaned_data['first_name']
last_name = form.cleaned_data['last_name']
email = form.cleaned_data['email']
password = form.cleaned_data['password1']
username = form.cleaned_data['username']
theuser = User.objects.create_user(username,password=password)
theuser.first_name = first_name
theuser.last_name = last_name
theuser.email = email
theuser.save()
therower = Rower(user=theuser)
therower.save()
# Create Sample workout
f = 'media/testdata.csv.gz'
timestr = strftime("%Y%m%d-%H%M%S")
f2 = f[:-7]+timestr+'.csv.gz'
copyfile(f,f2)
response = dataprep.new_workout_from_file(therower,f2,
title='New User Sample Data',
notes='This is an example workout to get you started')
# Create and send email
fullemail = first_name + " " + last_name + " " + "<" + email + ">"
subject = "Thank you for registering on rowsandall.com"
message = "Thank you for registering on rowsandall.com. You can now login using the credentials you provided.\n"
message += "The first thing you might want to do is check and edit the heart rate band values. After logging in, click the button with your first name.\n"
message += "You can also check our videos page at http://rowsandall.com/rowers/videos for some helpful instruction videos.\n\n"
message += "User name:"+username+"\n"
message += "Password :"+password+"\n\n"
message += "For all your questions, just reply to this email.\n\n"
message += "Happy rowing!\n\n\n"
message += "Oh, one more thing. The site is currently in beta and is developing fast. Bear with us. Don't hesitate to contact me if anything is broken or doesn't seem to work as advertised."
send_mail(subject, message,
'Sander Roosendaal <info@rowsandall.com>',
[fullemail])
subject2 = "New User"
message2 = "New user registered.\n"
message2 += fullemail + "\n"
message2 += "User name: "+username
send_mail(subject2, message2,
'Rowsandall Server <info@rowsandall.com>',
['roosendaalsander@gmail.com'])
return HttpResponseRedirect('/rowers/register/thankyou/')
else:
return render(request,
"registration_form.html",
{'form':form})
else:
form = RegistrationFormUniqueEmail()
return render(request,
"registration_form.html",
{'form':form,})
# Shows email form and sends it if submitted
def sendmail(request):
if request.method == 'POST':
form = EmailForm(request.POST)
if form.is_valid():
firstname = form.cleaned_data['firstname']
lastname = form.cleaned_data['lastname']
email = form.cleaned_data['email']
subject = form.cleaned_data['subject']
botcheck = form.cleaned_data['botcheck'].lower()
message = form.cleaned_data['message']
if botcheck == 'yes':
try:
fullemail = firstname + " " + lastname + " " + "<" + email + ">"
send_mail(subject, message, fullemail, ['info@rowsandall.com'])
return HttpResponseRedirect('/rowers/email/thankyou/')
except:
return HttpResponseRedirect('/rowers/email/')
else:
return HttpResponseRedirect('/rowers/email/')
else:
return HttpResponseRedirect('/rowers/email/')
# Check if workout belongs to this user
def checkworkoutuser(user,workout):
try:
r = Rower.objects.get(user=user)
return (workout.user == r)
except Rower.DoesNotExist:
return(False)
# Create workout data from Strava or Concept2
# data and create the associated Workout object and save it
def add_workout_from_strokedata(user,importid,data,strokedata,
source='c2',splitdata=None):
workouttype = data['type']
if workouttype not in [x[0] for x in Workout.workouttypes]:
workouttype = 'water'
try:
comments = data['comments']
except:
comments = ' '
# comments = "Imported data \n %s" % comments
# comments = "Imported data \n"+comments # str(comments)
try:
thetimezone = tz(data['timezone'])
except:
thetimezone = 'UTC'
r = Rower.objects.get(user=user)
try:
rowdatetime = iso8601.parse_date(data['date_utc'])
except KeyError:
rowdatetime = iso8601.parse_date(data['start_date'])
except ParseError:
rowdatetime = iso8601.parse_date(data['date'])
try:
c2intervaltype = data['workout_type']
except KeyError:
c2intervaltype = ''
try:
title = data['name']
except KeyError:
title = ""
try:
t = data['comments'].split('\n', 1)[0]
title += t[:20]
except:
title = 'Imported'
starttimeunix = mktime(rowdatetime.timetuple())
res = make_cumvalues(0.1*strokedata['t'])
cum_time = res[0]
lapidx = res[1]
unixtime = cum_time+starttimeunix
# unixtime[0] = starttimeunix
seconds = 0.1*strokedata.ix[:,'t']
nr_rows = len(unixtime)
try:
latcoord = strokedata.ix[:,'lat']
loncoord = strokedata.ix[:,'lon']
except:
latcoord = np.zeros(nr_rows)
loncoord = np.zeros(nr_rows)
try:
strokelength = strokedata.ix[:,'strokelength']
except:
strokelength = np.zeros(nr_rows)
dist2 = 0.1*strokedata.ix[:,'d']
spm = strokedata.ix[:,'spm']
hr = strokedata.ix[:,'hr']
pace = strokedata.ix[:,'p']/10.
pace = np.clip(pace,0,1e4)
pace = pace.replace(0,300)
velo = 500./pace
power = 2.8*velo**3
# save csv
# Create data frame with all necessary data to write to csv
df = pd.DataFrame({'TimeStamp (sec)':unixtime,
' Horizontal (meters)': dist2,
' Cadence (stokes/min)':spm,
' HRCur (bpm)':hr,
' longitude':loncoord,
' latitude':latcoord,
' Stroke500mPace (sec/500m)':pace,
' Power (watts)':power,
' DragFactor':np.zeros(nr_rows),
' DriveLength (meters)':np.zeros(nr_rows),
' StrokeDistance (meters)':strokelength,
' DriveTime (ms)':np.zeros(nr_rows),
' StrokeRecoveryTime (ms)':np.zeros(nr_rows),
' AverageDriveForce (lbs)':np.zeros(nr_rows),
' PeakDriveForce (lbs)':np.zeros(nr_rows),
' lapIdx':lapidx,
' ElapsedTime (sec)':seconds
})
df.sort_values(by='TimeStamp (sec)',ascending=True)
timestr = strftime("%Y%m%d-%H%M%S")
# auto smoothing
pace = df[' Stroke500mPace (sec/500m)'].values
velo = 500./pace
f = df['TimeStamp (sec)'].diff().mean()
windowsize = 2*(int(10./(f)))+1
if windowsize <= 3:
windowsize = 5
df['originalvelo'] = velo
if windowsize > 3 and windowsize < len(velo):
velo2 = savgol_filter(velo,windowsize,3)
else:
velo2=velo
velo3 = pd.Series(velo2)
velo3 = velo3.replace([-np.inf,np.inf],np.nan)
velo3 = velo3.fillna(method='ffill')
pace2 = 500./abs(velo3)
df[' Stroke500mPace (sec/500m)'] = pace2
df = df.fillna(0)
# end autosmoothing
# Create CSV file name and save data to CSV file
csvfilename ='media/Import_'+str(importid)+'.csv'
res = df.to_csv(csvfilename+'.gz',index_label='index',
compression='gzip')
# make workout
powerperc = 100*np.array([r.pw_ut2,
r.pw_ut1,
r.pw_at,
r.pw_tr,r.pw_an])/r.ftp
rr = rrower(hrmax=r.max,hrut2=r.ut2,
hrut1=r.ut1,hrat=r.at,
hrtr=r.tr,hran=r.an,ftp=r.ftp,
powerperc=powerperc,
powerzones=r.powerzones,
)
row = rdata(csvfilename,rower=rr)
averagehr = row.df[' HRCur (bpm)'].mean()
maxhr = row.df[' HRCur (bpm)'].max()
totaldist = row.df['cum_dist'].max()
totaltime = row.df['TimeStamp (sec)'].max()-row.df['TimeStamp (sec)'].min()
totaltime = totaltime+row.df.ix[0,' ElapsedTime (sec)']
# with Concept2
if source=='c2':
try:
totaldist = data['distance']
totaltime = data['time']/10.
except KeyError:
pass
hours = int(totaltime/3600.)
minutes = int((totaltime - 3600.*hours)/60.)
seconds = int(totaltime - 3600.*hours - 60.*minutes)
tenths = int(10*(totaltime - 3600.*hours - 60.*minutes - seconds))
duration = "%s:%s:%s.%s" % (hours,minutes,seconds,tenths)
summary = row.summary()
summary += '\n'
summary += row.intervalstats()
workoutdate = row.rowdatetime.strftime('%Y-%m-%d')
workoutstarttime = row.rowdatetime.strftime('%H:%M:%S')
# check for duplicate start times
ws = Workout.objects.filter(starttime=workoutstarttime,
user=r)
if (len(ws) != 0):
warnings.warn("Probably a duplicate workout",UserWarning)
# Create the Workout object
w = Workout(user=r,name=title,
date=workoutdate,workouttype=workouttype,
duration=duration,distance=totaldist,
weightcategory=r.weightcategory,
starttime=workoutstarttime,
csvfilename=csvfilename,notes=comments,
uploadedtoc2=0,summary=summary,
averagehr=averagehr,maxhr=maxhr,
startdatetime=rowdatetime)
w.save()
return w.id
# Create workout from SportTracks Data, which are slightly different
# than Strava or Concept2 data
def add_workout_from_stdata(user,importid,data):
workouttype = data['type']
if workouttype not in [x[0] for x in Workout.workouttypes]:
workouttype = 'water'
try:
comments = data['comments']
except:
comments = ''
try:
thetimezone = tz(data['timezone'])
except:
thetimezone = 'UTC'
r = Rower.objects.get(user=user)
try:
rowdatetime = iso8601.parse_date(data['start_time'])
except iso8601.ParseError:
try:
rowdatetime = datetime.datetime.strptime(data['start_time'],"%Y-%m-%d %H:%M:%S")
rowdatetime = thetimezone.localize(rowdatetime).astimezone(utc)
except:
try:
rowdatetime = dateutil.parser.parse(data['start_time'])
rowdatetime = thetimezone.localize(rowdatetime).astimezone(utc)
except:
rowdatetime = datetime.datetime.strptime(data['date'],"%Y-%m-%d %H:%M:%S")
rowdatetime = thetimezone.localize(rowdatetime).astimezone(utc)
# try:
# c2intervaltype = data['workout_type']
# except:
# c2intervaltype = ''
try:
title = data['name']
except:
title = "Imported data"
starttimeunix = mktime(rowdatetime.timetuple())
res = splitstdata(data['distance'])
distance = res[1]
times_distance = res[0]
try:
l = data['location']
res = splitstdata(l)
times_location = res[0]
latlong = res[1]
latcoord = []
loncoord = []
for coord in latlong:
lat = coord[0]
lon = coord[1]
latcoord.append(lat)
loncoord.append(lon)
except:
times_location = times_distance
latcoord = np.zeros(len(times_distance))
loncoord = np.zeros(len(times_distance))
try:
res = splitstdata(data['cadence'])
times_spm = res[0]
spm = res[1]
except KeyError:
times_spm = times_distance
spm = 0*times_distance
try:
res = splitstdata(data['heartrate'])
hr = res[1]
times_hr = res[0]
except KeyError:
times_hr = times_distance
hr = 0*times_distance
# create data series and remove duplicates
distseries = pd.Series(distance,index=times_distance)
distseries = distseries.groupby(distseries.index).first()
latseries = pd.Series(latcoord,index=times_location)
latseries = latseries.groupby(latseries.index).first()
lonseries = pd.Series(loncoord,index=times_location)
lonseries = lonseries.groupby(lonseries.index).first()
spmseries = pd.Series(spm,index=times_spm)
spmseries = spmseries.groupby(spmseries.index).first()
hrseries = pd.Series(hr,index=times_hr)
hrseries = hrseries.groupby(hrseries.index).first()
# Create dicts and big dataframe
d = {
' Horizontal (meters)': distseries,
' latitude': latseries,
' longitude': lonseries,
' Cadence (stokes/min)': spmseries,
' HRCur (bpm)' : hrseries,
}
df = pd.DataFrame(d)
df = df.groupby(level=0).last()
cum_time = df.index.values
df[' ElapsedTime (sec)'] = cum_time
velo = df[' Horizontal (meters)'].diff()/df[' ElapsedTime (sec)'].diff()
df[' Power (watts)'] = 0.0*velo
nr_rows = len(velo.values)
df[' DriveLength (meters)'] = np.zeros(nr_rows)
df[' StrokeDistance (meters)'] = np.zeros(nr_rows)
df[' DriveTime (ms)'] = np.zeros(nr_rows)
df[' StrokeRecoveryTime (ms)'] = np.zeros(nr_rows)
df[' AverageDriveForce (lbs)'] = np.zeros(nr_rows)
df[' PeakDriveForce (lbs)'] = np.zeros(nr_rows)
df[' lapIdx'] = np.zeros(nr_rows)
unixtime = cum_time+starttimeunix
unixtime[0] = starttimeunix
df['TimeStamp (sec)'] = unixtime
dt = np.diff(cum_time).mean()
wsize = round(5./dt)
velo2 = stravastuff.ewmovingaverage(velo,wsize)
df[' Stroke500mPace (sec/500m)'] = 500./velo2
df = df.fillna(0)
df.sort_values(by='TimeStamp (sec)',ascending=True)
timestr = strftime("%Y%m%d-%H%M%S")
# auto smoothing
pace = df[' Stroke500mPace (sec/500m)'].values
velo = 500./pace
f = df['TimeStamp (sec)'].diff().mean()
windowsize = 2*(int(10./(f)))+1
df['originalvelo'] = velo
if windowsize > 3 and windowsize<len(velo):
velo2 = savgol_filter(velo,windowsize,3)
else:
velo2 = velo
velo3 = pd.Series(velo2)
velo3 = velo3.replace([-np.inf,np.inf],np.nan)
velo3 = velo3.fillna(method='ffill')
pace2 = 500./abs(velo3)
df[' Stroke500mPace (sec/500m)'] = pace2
df = df.fillna(0)
# end autosmoothing
csvfilename ='media/Import_'+str(importid)+'.csv'
res = df.to_csv(csvfilename+'.gz',index_label='index',
compression='gzip')
# make workout
powerperc = 100*np.array([r.pw_ut2,
r.pw_ut1,
r.pw_at,
r.pw_tr,r.pw_an])/r.ftp
rr = rrower(hrmax=r.max,hrut2=r.ut2,
hrut1=r.ut1,hrat=r.at,
hrtr=r.tr,hran=r.an,ftp=r.ftp,
powerperc=powerperc,powerzones=r.powerzones)
row = rdata(csvfilename,rower=rr)
averagehr = row.df[' HRCur (bpm)'].mean()
maxhr = row.df[' HRCur (bpm)'].max()
totaldist = row.df['cum_dist'].max()
totaltime = row.df['TimeStamp (sec)'].max()-row.df['TimeStamp (sec)'].min()
totaltime = totaltime+row.df.ix[0,' ElapsedTime (sec)']
hours = int(totaltime/3600.)
minutes = int((totaltime - 3600.*hours)/60.)
seconds = int(totaltime - 3600.*hours - 60.*minutes)
tenths = int(10*(totaltime - 3600.*hours - 60.*minutes - seconds))
duration = "%s:%s:%s.%s" % (hours,minutes,seconds,tenths)
summary = row.summary()
summary += '\n'
summary += row.intervalstats()
workoutdate = row.rowdatetime.strftime('%Y-%m-%d')
workoutstarttime = row.rowdatetime.strftime('%H:%M:%S')
# check for duplicate start times
ws = Workout.objects.filter(starttime=workoutstarttime,
user=r)
if (len(ws) != 0):
print "Warning: This workout probably already exists in the database"
w = Workout(user=r,name=title,
date=workoutdate,workouttype=workouttype,
duration=duration,distance=totaldist,
weightcategory=r.weightcategory,
starttime=workoutstarttime,
csvfilename=csvfilename,notes=comments,
uploadedtoc2=0,summary=summary,
averagehr=averagehr,maxhr=maxhr,
startdatetime=rowdatetime)
w.save()
return w.id
# Checks if user has Concept2 tokens, resets tokens if they are
# expired.
def c2_open(user):
r = Rower.objects.get(user=user)
if (r.c2token == '') or (r.c2token is None):
s = "Token doesn't exist. Need to authorize"
raise C2NoTokenError("User has no token")
else:
if (timezone.now()>r.tokenexpirydate):
res = c2stuff.rower_c2_token_refresh(user)
if res[0] != None:
thetoken = res[0]
else:
thetoken = r.c2token
else:
thetoken = r.c2token
return thetoken
# Checks if user has SportTracks token, renews them if they are expired
def sporttracks_open(user):
r = Rower.objects.get(user=user)
if (r.sporttrackstoken == '') or (r.sporttrackstoken is None):
s = "Token doesn't exist. Need to authorize"
raise SportTracksNoTokenError("User has no token")
else:
if (timezone.now()>r.sporttrackstokenexpirydate):
thetoken = sporttracksstuff.rower_sporttracks_token_refresh(user)
else:
thetoken = r.sporttrackstoken
return thetoken
# Export workout to TCX and send to user's email address
@login_required()
def workout_tcxemail_view(request,id=0):
message = ""
successmessage = ""
r = Rower.objects.get(user=request.user)
w = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,w)):
try:
tcxfile = stravastuff.createstravaworkoutdata(w)
if settings.DEBUG:
res = handle_sendemailtcx.delay(r.user.first_name,
r.user.last_name,
r.user.email,tcxfile)
else:
res = queuehigh.enqueue(handle_sendemailtcx,r.user.first_name,
r.user.last_name,
r.user.email,tcxfile)
successmessage = "The TCX file was sent to you per email"
url = reverse(workout_export_view,
kwargs = {
'id':str(w.id),
'successmessage':successmessage,
})
except:
successmessage = ""
message = "Something went wrong (TCX export) "+str(sys.exc_info()[0])
with open("media/c2errors.log","a") as errorlog:
errorstring = str(sys.exc_info()[0])
timestr = strftime("%Y%m%d-%H%M%S")
errorlog.write(timestr+errorstring+"\r\n")
url = reverse(workout_export_view,
kwargs = {
'id':str(w.id),
'message':message,
})
response = HttpResponseRedirect(url)
else:
message = "You are not allowed to export this workout"
url = reverse(workout_export_view,
kwargs = {
'id':str(w.id),
'message':message,
})
response = HttpResponseRedirect(url)
return response
# Get Workout CSV file and send it to user's email address
@login_required()
def workout_csvemail_view(request,id=0):
message = ""
r = Rower.objects.get(user=request.user)
w = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,w)):
csvfile = w.csvfilename
if settings.DEBUG:
res = handle_sendemailcsv.delay(r.user.first_name,
r.user.last_name,
r.user.email,csvfile)
else:
res = queuehigh.enqueue(handle_sendemailcsv,r.user.first_name,
r.user.last_name,
r.user.email,csvfile)
successmessage = "The CSV file was sent to you per email"
url = reverse(workout_export_view,
kwargs = {
'id':str(w.id),
'successmessage':successmessage,
})
response = HttpResponseRedirect(url)
else:
message = "You are not allowed to export this workout"
url = reverse(workout_export_view,
kwargs = {
'id':str(w.id),
'message':message,
})
response = HttpResponseRedirect(url)
return response
# Send workout to Strava
# abundance of error logging here because there were/are some bugs
@login_required()
def workout_strava_upload_view(request,id=0):
message = ""
r = Rower.objects.get(user=request.user)
res = -1
if (r.stravatoken == '') or (r.stravatoken is None):
s = "Token doesn't exist. Need to authorize"
return HttpResponseRedirect("/rowers/me/stravaauthorize/")
else:
# ready to upload. Hurray
w = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,w)):
try:
tcxfile = stravastuff.createstravaworkoutdata(w)
if tcxfile:
with open(tcxfile,'rb') as f:
try:
res = stravastuff.handle_stravaexport(f,w.name,
r.stravatoken,
description=w.notes)
if res==0:
message = "Strava Upload error: %s" % e
w.uploadedtostrava = -1
w.save()
os.remove(tcxfile)
url = reverse(workout_export_view,
kwargs = {
'id':str(w.id),
})
response = HttpResponseRedirect(url)
return response
except:
with open("media/stravaerrors.log","a") as errorlog:
errorstring = str(sys.exc_info()[0])
timestr = strftime("%Y%m%d-%H%M%S")
errorlog.write(timestr+errorstring+"\r\n")
errorlog.write("views.py line 937\r\n")
message = 'Error: '+errorstring
res = 0
try:
w.uploadedtostrava = res
w.save()
os.remove(tcxfile)
url = "/rowers/workout/"+str(w.id)+"/edit"
successmessage = 'Workout sent to Strava.'
except:
with open("media/stravaerrors.log","a") as errorlog:
errorstring = str(sys.exc_info()[0])
timestr = strftime("%Y%m%d-%H%M%S")
errorlog.write(timestr+errorstring+"\r\n")
errorlog.write("views.py line 952\r\n")
message = 'Error: '+errorstring
else:
message = "Strava Upload error"
w.uploadedtostrava = -1
w.save()
url = reverse(workout_export_view,
kwargs = {
'id':str(w.id),
})
response = HttpResponseRedirect(url)
url = reverse(workout_export_view,
kwargs = {
'id':str(w.id),
}
)
response = HttpResponseRedirect(url)
except ActivityUploadFailed as e:
message = "Strava Upload error: %s" % e
w.uploadedtostrava = -1
w.save()
os.remove(tcxfile)
url = reverse(workout_export_view,
kwargs = {
'id':str(w.id),
})
response = HttpResponseRedirect(url)
# except TimeoutExceeded as e:
# w.uploadedtostrava = -1
# w.save()
# url = reverse(workout_export_view,
# kwargs = {
# 'id':str(w.id),
# 'message':'Strava Upload attempted. No response within 10 seconds. You may be OK. Check on Strava',
# })
# response = HttpResponseRedirect(url)
return response
# Upload workout to Concept2 logbook
@login_required()
def workout_c2_upload_view(request,id=0):
message = ""
try:
thetoken = c2_open(request.user)
except C2NoTokenError:
return HttpResponseRedirect("/rowers/me/c2authorize/")
# ready to upload. Hurray
w = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,w)):
c2userid = c2stuff.get_userid(thetoken)
data = c2stuff.createc2workoutdata(w)
authorizationstring = str('Bearer ' + thetoken)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
import urllib
try:
url = "https://log.concept2.com/api/users/%s/results" % (c2userid)
response = requests.post(url,headers=headers,data=json.dumps(data))
except:
message = "Unexpected Error: "+str(sys.exc_info()[0])
with open("media/c2errors.log","a") as errorlog:
errorstring = str(sys.exc_info()[0])
timestr = strftime("%Y%m%d-%H%M%S")
errorlog.write(timestr+errorstring+"\r\n")
# check for duplicate error first
if (response.status_code == 409 ):
message = "Duplicate error"
w.uploadedtoc2 = -1
w.save()
elif (response.status_code == 201 or response.status_code == 200):
try:
s= json.loads(response.text)
c2id = s['data']['id']
w.uploadedtoc2 = c2id
w.save()
url = "/rowers/workout/"+str(w.id)+"/export"
return HttpResponseRedirect(url)
except:
message = "Something went wrong in workout_c2_upload_view. Response code 200/201 but C2 sync failed: "+response.text
with open("media/c2errors.log","a") as errorlog:
errorstring = str(sys.exc_info()[0])
timestr = strftime("%Y%m%d-%H%M%S")
errorlog.write(timestr+errorstring+"\r\n")
else:
s = response
message = "Something went wrong in workout_c2_upload_view. C2 sync failed."
with open("media/c2errors.log","a") as errorlog:
errorstring = str(sys.exc_info()[0])
timestr = strftime("%Y%m%d-%H%M%S")
errorlog.write(timestr+errorstring+"\r\n")
else:
message = "You are not authorized to upload this workout"
url = reverse(workout_export_view,
kwargs = {
'message':str(message),
'id':str(w.id),
})
return HttpResponseRedirect(url)
# Upload workout to SportTracks
@login_required()
def workout_sporttracks_upload_view(request,id=0):
message = ""
try:
thetoken = sporttracks_open(request.user)
except SportTracksNoTokenError:
return HttpResponseRedirect("/rowers/me/sporttracksauthorize/")
# ready to upload. Hurray
w = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,w)):
data = sporttracksstuff.createsporttracksworkoutdata(w)
if not data:
message = "Data error"
url = reverse(workout_export_view,
kwargs = {
'message':str(message),
'id':str(w.id),
})
return HttpResponseRedirect(url)
authorizationstring = str('Bearer ' + thetoken)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
import urllib
url = "https://api.sporttracks.mobi/api/v2/fitnessActivities.json"
response = requests.post(url,headers=headers,data=json.dumps(data))
# check for duplicate error first
if (response.status_code == 409 ):
message = "Duplicate error"
w.uploadedtosporttracks = -1
w.save()
elif (response.status_code == 201 or response.status_code==200):
s= json.loads(response.text)
sporttracksid = sporttracksstuff.getidfromresponse(response)
w.uploadedtosporttracks = sporttracksid
w.save()
url = "/rowers/workout/"+str(w.id)+"/export"
return HttpResponseRedirect(url)
else:
s = response
message = "Something went wrong in workout_sporttracks_upload_view: %s" % s.reason
else:
message = "You are not authorized to upload this workout"
url = reverse(workout_export_view,
kwargs = {
'message':str(message),
'id':str(w.id),
})
return HttpResponseRedirect(url)
# Concept2 authorization
@login_required()
def rower_c2_authorize(request):
# Generate a random string for the state parameter
# Save it for use later to prevent xsrf attacks
from uuid import uuid4
state = str(uuid4())
scope = "user:read,results:write"
params = {"client_id": C2_CLIENT_ID,
"response_type": "code",
"redirect_uri": C2_REDIRECT_URI}
import urllib
url = "http://log.concept2.com/oauth/authorize?"+ urllib.urlencode(params)
url += "&scope="+scope
return HttpResponseRedirect(url)
# Strava Authorization
@login_required()
def rower_strava_authorize(request):
# Generate a random string for the state parameter
# Save it for use later to prevent xsrf attacks
from uuid import uuid4
state = str(uuid4())
params = {"client_id": STRAVA_CLIENT_ID,
"response_type": "code",
"redirect_uri": STRAVA_REDIRECT_URI,
"scope": "write"}
import urllib
url = "https://www.strava.com/oauth/authorize?"+ urllib.urlencode(params)
return HttpResponseRedirect(url)
# SportTracks Authorization
@login_required()
def rower_sporttracks_authorize(request):
# Generate a random string for the state parameter
# Save it for use later to prevent xsrf attacks
from uuid import uuid4
state = str(uuid4())
params = {"client_id": SPORTTRACKS_CLIENT_ID,
"response_type": "code",
"state": state,
"redirect_uri": SPORTTRACKS_REDIRECT_URI}
import urllib
url = "https://api.sporttracks.mobi/oauth2/authorize?"+ urllib.urlencode(params)
return HttpResponseRedirect(url)
# Concept2 token refresh. URL for manual refresh. Not visible to users
@login_required()
def rower_c2_token_refresh(request):
r = Rower.objects.get(user=request.user)
res = c2stuff.do_refresh_token(r.c2refreshtoken)
if res[0] != None:
access_token = res[0]
expires_in = res[1]
refresh_token = res[2]
expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in)
r = Rower.objects.get(user=request.user)
r.c2token = access_token
r.tokenexpirydate = expirydatetime
r.c2refreshtoken = refresh_token
r.save()
successmessage = "Tokens refreshed. Good to go"
message = ""
else:
successmessage = ""
message = "Something went wrong (refreshing tokens). Please reauthorize:"
return imports_view(request,successmessage=successmessage,message=message)
# SportTracks token refresh. URL for manual refresh. Not visible to users
@login_required()
def rower_sporttracks_token_refresh(request):
r = Rower.objects.get(user=request.user)
res = sporttracksstuff.do_refresh_token(r.sporttracksrefreshtoken)
access_token = res[0]
expires_in = res[1]
refresh_token = res[2]
expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in)
r = Rower.objects.get(user=request.user)
r.sporttrackstoken = access_token
r.sporttrackstokenexpirydate = expirydatetime
r.sporttracksrefreshtoken = refresh_token
r.save()
successmessage = "Tokens refreshed. Good to go"
return imports_view(request,successmessage=successmessage)
# Concept2 Callback
@login_required()
def rower_process_callback(request):
try:
code = request.GET['code']
res = c2stuff.get_token(code)
except MultiValueDictKeyError:
message = "The resource owner or authorization server denied the request"
return imports_view(request,message=message)
access_token = res[0]
expires_in = res[1]
refresh_token = res[2]
expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in)
r = Rower.objects.get(user=request.user)
r.c2token = access_token
r.tokenexpirydate = expirydatetime
r.c2refreshtoken = refresh_token
r.save()
successmessage = "Tokens stored. Good to go"
return imports_view(request,successmessage=successmessage)
# The imports page
@login_required()
def imports_view(request,successmessage="",message=""):
return render(request,"imports.html",
{'successmessage': successmessage,
'message': message,
})
# Just for testing purposes
@login_required()
def test_reverse_view(request):
successmessage = "Tokens stored. Good to go"
return imports_view(request,successmessage=successmessage)
# dummy
@login_required()
def rower_process_twittercallback(request):
return "dummy"
# Process Strava Callback
@login_required()
def rower_process_stravacallback(request):
code = request.GET['code']
res = stravastuff.get_token(code)
access_token = res[0]
r = Rower.objects.get(user=request.user)
r.stravatoken = access_token
r.save()
successmessage = "Tokens stored. Good to go"
return imports_view(request,successmessage=successmessage)
# Process SportTracks callback
@login_required()
def rower_process_sporttrackscallback(request):
code = request.GET['code']
res = sporttracksstuff.get_token(code)
access_token = res[0]
expires_in = res[1]
refresh_token = res[2]
expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in)
r = Rower.objects.get(user=request.user)
r.sporttrackstoken = access_token
r.sporttrackstokenexpirydate = expirydatetime
r.sporttracksrefreshtoken = refresh_token
r.save()
successmessage = "Tokens stored. Good to go"
return imports_view(request,successmessage=successmessage)
# Process Own API callback - for API testing purposes
@login_required()
def rower_process_testcallback(request):
code = request.GET['code']
res = ownapistuff.get_token(code)
access_token = res[0]
expires_in = res[1]
refresh_token = res[2]
expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in)
text = "Access Token:\n"
text += access_token
text += "\n\nRefresh Token:\n"
text += refresh_token
return HttpResponse(text)
# View around the Histogram of all strokes. Not implemented in UI
@login_required()
def histo_all(request,theuser=0):
promember=0
if theuser == 0:
theuser = request.user.id
if not request.user.is_anonymous():
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated() and r.rowerplan=='pro'
if result:
promember=1
if not promember:
return HttpResponseRedirect("/rowers/about/")
# get all indoor rows of past 12 months
ayearago = timezone.now()-datetime.timedelta(days=365)
try:
r2 = Rower.objects.get(user=theuser)
allergworkouts = Workout.objects.filter(user=r2,
workouttype__in=['rower','dynamic','slides'],
startdatetime__gte=ayearago)
except Rower.DoesNotExist:
allergworkouts = []
r2=0
try:
u = User.objects.get(id=theuser)
except User.DoesNotExist:
u = ''
if allergworkouts:
res = interactive_histoall(allergworkouts)
script = res[0]
div = res[1]
else:
script = ''
div = '<p>No erg pieces uploaded yet.</p>'
return render(request, 'histoall.html',
{'interactiveplot':script,
'the_div':div,
'id':theuser,
'theuser':u,
})
# The Flex plot for a large selection of workouts
@login_required()
def cum_flex(request,theuser=0,
xparam='spm',
yparam1='power',
yparam2='None',
startdate=timezone.now()-datetime.timedelta(days=10),
enddate=timezone.now()+datetime.timedelta(days=1),
deltadays=-1,
startdatestring="",
enddatestring=""):
if deltadays>0:
startdate = enddate-datetime.timedelta(days=int(deltadays))
if startdatestring != "":
startdate = iso8601.parse_date(startdatestring)
if enddatestring != "":
enddate = iso8601.parse_date(enddatestring)
if enddate < startdate:
s = enddate
enddate = startdate
startdate = s
promember=0
if theuser == 0:
theuser = request.user.id
if not request.user.is_anonymous():
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated() and r.rowerplan=='pro'
if result:
promember=1
if not promember:
return HttpResponseRedirect("/rowers/about/")
# get all indoor rows of in date range
# process form
if request.method == 'POST' and "daterange" in request.POST:
form = DateRangeForm(request.POST)
deltaform = DeltaDaysForm(request.POST)
if form.is_valid():
startdate = form.cleaned_data['startdate']
enddate = form.cleaned_data['enddate']
if startdate > enddate:
s = enddate
enddate = startdate
startdate = s
elif request.method == 'POST' and "datedelta" in request.POST:
deltaform = DeltaDaysForm(request.POST)
if deltaform.is_valid():
deltadays = deltaform.cleaned_data['deltadays']
if deltadays != 0:
enddate = timezone.now()
startdate = enddate-datetime.timedelta(days=deltadays)
if startdate > enddate:
s = enddate
enddate = startdate
startdate = s
form = DateRangeForm(initial={
'startdate': startdate,
'enddate': enddate,
})
else:
form = DateRangeForm(initial={
'startdate': startdate,
'enddate': enddate,
})
deltaform = DeltaDaysForm()
try:
r2 = Rower.objects.get(user=theuser)
allergworkouts = Workout.objects.filter(user=r2,
workouttype__in=['rower','dynamic','slides'],
startdatetime__gte=startdate,
startdatetime__lte=enddate)
except Rower.DoesNotExist:
allergworkouts = []
r2=0
try:
u = User.objects.get(id=theuser)
except User.DoesNotExist:
u = ''
if allergworkouts:
res = interactive_cum_flex_chart2(allergworkouts,xparam=xparam,
yparam1=yparam1,
yparam2=yparam2,
promember=promember)
script = res[0]
div = res[1]
js_resources = res[2]
css_resources = res[3]
else:
script = ''
div = '<p>No erg pieces uploaded for this date range.</p>'
js_resources = ''
css_resources = ''
return render(request, 'cum_flex.html',
{'interactiveplot':script,
'the_div':div,
'js_res': js_resources,
'css_res':css_resources,
'id':theuser,
'theuser':u,
'startdate':startdate,
'enddate':enddate,
'form':form,
'deltaform':deltaform,
'xparam':xparam,
'yparam1':yparam1,
'yparam2':yparam2,
'promember':promember,
})
# Show the EMpower Oarlock generated Stroke Profile
@user_passes_test(promember,login_url="/",redirect_field_name=None)
def workout_forcecurve_view(request,id=0,workstrokesonly=False):
row = Workout.objects.get(id=id)
promember=0
mayedit=0
if not request.user.is_anonymous():
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated() and r.rowerplan=='pro'
if result:
promember=1
if request.user == row.user.user:
mayedit=1
if not promember:
return HttpResponseRedirect("/rowers/about/")
if request.method == 'POST' and 'workstrokesonly' in request.POST:
workstrokesonly = request.POST['workstrokesonly']
if workstrokesonly == 'True':
workstrokesonly = True
else:
workstrokesonly = False
script,div,js_resources,css_resources = interactive_forcecurve([row],
workstrokesonly=workstrokesonly)
return render(request,
'forcecurve_single.html',
{
'the_script':script,
'the_div':div,
'js_res': js_resources,
'css_res':css_resources,
'id':id,
'mayedit':mayedit,
'workstrokesonly': not workstrokesonly,
})
# Show Stroke power histogram for a workout
@login_required()
def workout_histo_view(request,id=0):
row = Workout.objects.get(id=id)
promember=0
mayedit=0
if not request.user.is_anonymous():
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated() and r.rowerplan=='pro'
if result:
promember=1
if request.user == row.user.user:
mayedit=1
if not promember:
return HttpResponseRedirect("/rowers/about/")
res = interactive_histoall([row])
script = res[0]
div = res[1]
return render(request,
'histo_single.html',
{'interactiveplot':script,
'the_div':div,
'id':id,
'mayedit':mayedit,
})
# Histogram for a date/time range
@login_required()
def histo(request,theuser=0,
startdate=timezone.now()-datetime.timedelta(days=365),
enddate=timezone.now(),
deltadays=-1,
startdatestring="",
enddatestring=""):
if deltadays>0:
startdate = enddate-datetime.timedelta(days=int(deltadays))
if startdatestring != "":
startdate = iso8601.parse_date(startdatestring)
if enddatestring != "":
enddate = iso8601.parse_date(enddatestring)
if enddate < startdate:
s = enddate
enddate = startdate
startdate = s
promember=0
if theuser == 0:
theuser = request.user.id
if not request.user.is_anonymous():
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated() and r.rowerplan=='pro'
if result:
promember=1
if not promember:
return HttpResponseRedirect("/rowers/promembership/")
# get all indoor rows of in date range
# process form
if request.method == 'POST' and "daterange" in request.POST:
form = DateRangeForm(request.POST)
deltaform = DeltaDaysForm(request.POST)
if form.is_valid():
startdate = form.cleaned_data['startdate']
enddate = form.cleaned_data['enddate']
if startdate > enddate:
s = enddate
enddate = startdate
startdate = s
elif request.method == 'POST' and "datedelta" in request.POST:
deltaform = DeltaDaysForm(request.POST)
if deltaform.is_valid():
deltadays = deltaform.cleaned_data['deltadays']
if deltadays != 0:
enddate = timezone.now()
startdate = enddate-datetime.timedelta(days=deltadays)
if startdate > enddate:
s = enddate
enddate = startdate
startdate = s
form = DateRangeForm(initial={
'startdate': startdate,
'enddate': enddate,
})
else:
form = DateRangeForm(initial={
'startdate': startdate,
'enddate': enddate,
})
deltaform = DeltaDaysForm()
try:
r2 = Rower.objects.get(user=theuser)
allergworkouts = Workout.objects.filter(user=r2,
workouttype__in=['rower','dynamic','slides'],
startdatetime__gte=startdate,
startdatetime__lte=enddate)
except Rower.DoesNotExist:
allergworkouts = []
r2=0
try:
u = User.objects.get(id=theuser)
except User.DoesNotExist:
u = ''
if allergworkouts:
res = interactive_histoall(allergworkouts)
script = res[0]
div = res[1]
else:
script = ''
div = '<p>No erg pieces uploaded for this date range.</p>'
return render(request, 'histo.html',
{'interactiveplot':script,
'the_div':div,
'id':theuser,
'theuser':u,
'startdate':startdate,
'enddate':enddate,
'form':form,
'deltaform':deltaform,
})
# Show ranking distances including predicted paces
@login_required()
def rankings_view(request,theuser=0,
startdate=timezone.now()-datetime.timedelta(days=365),
enddate=timezone.now(),
deltadays=-1,
startdatestring="",
enddatestring=""):
if deltadays>0:
startdate = enddate-datetime.timedelta(days=int(deltadays))
if startdatestring != "":
startdate = iso8601.parse_date(startdatestring)
if enddatestring != "":
enddate = iso8601.parse_date(enddatestring)
if enddate < startdate:
s = enddate
enddate = startdate
startdate = s
if theuser == 0:
theuser = request.user.id
promember=0
if not request.user.is_anonymous():
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated() and r.rowerplan=='pro'
if result:
promember=1
# get all indoor rows in date range
# process form
if request.method == 'POST' and "daterange" in request.POST:
dateform = DateRangeForm(request.POST)
deltaform = DeltaDaysForm(request.POST)
if dateform.is_valid():
startdate = dateform.cleaned_data['startdate']
enddate = dateform.cleaned_data['enddate']
if startdate > enddate:
s = enddate
enddate = startdate
startdate = s
elif request.method == 'POST' and "datedelta" in request.POST:
deltaform = DeltaDaysForm(request.POST)
if deltaform.is_valid():
deltadays = deltaform.cleaned_data['deltadays']
if deltadays != 0:
enddate = timezone.now()
startdate = enddate-datetime.timedelta(days=deltadays)
if startdate > enddate:
s = enddate
enddate = startdate
startdate = s
dateform = DateRangeForm(initial={
'startdate': startdate,
'enddate': enddate,
})
else:
dateform = DateRangeForm(initial={
'startdate': startdate,
'enddate': enddate,
})
deltaform = DeltaDaysForm()
# get all 2k (if any) - this rower, in date range
try:
r = Rower.objects.get(user=theuser)
except Rower.DoesNotExist:
allergworkouts = []
r=0
try:
uu = User.objects.get(id=theuser)
except User.DoesNotExist:
uu = ''
# test to fix bug
startdate = datetime.datetime.combine(startdate,datetime.time())
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
enddate = enddate+datetime.timedelta(days=1)
rankingdistances = [100,500,1000,2000,5000,6000,10000,21097,42195,100000]
rankingdurations = []
rankingdurations.append(datetime.time(minute=1))
rankingdurations.append(datetime.time(minute=4))
rankingdurations.append(datetime.time(minute=30))
rankingdurations.append(datetime.time(hour=1))
thedistances = []
theworkouts = []
thesecs = []
rankingdistances.sort()
rankingdurations.sort()
for rankingdistance in rankingdistances:
workouts = Workout.objects.filter(user=r,distance=rankingdistance,
workouttype__in=['rower','dynamic','slides'],
startdatetime__gte=startdate,
startdatetime__lte=enddate).order_by('duration')
if workouts:
thedistances.append(rankingdistance)
theworkouts.append(workouts[0])
timesecs = 3600*workouts[0].duration.hour
timesecs += 60*workouts[0].duration.minute
timesecs += workouts[0].duration.second
timesecs += 1.e-6*workouts[0].duration.microsecond
thesecs.append(timesecs)
for rankingduration in rankingdurations:
workouts = Workout.objects.filter(user=r,duration=rankingduration,
workouttype='rower',
startdatetime__gte=startdate,
startdatetime__lte=enddate).order_by('-distance')
if workouts:
thedistances.append(workouts[0].distance)
theworkouts.append(workouts[0])
timesecs = 3600*workouts[0].duration.hour
timesecs += 60*workouts[0].duration.minute
timesecs += workouts[0].duration.second
timesecs += 1.e-5*workouts[0].duration.microsecond
thesecs.append(timesecs)
thedistances = np.array(thedistances)
thesecs = np.array(thesecs)
thevelos = thedistances/thesecs
theavpower = 2.8*(thevelos**3)
# create interactive plot
if len(thedistances) !=0 :
res = interactive_cpchart(thedistances,thesecs,theavpower,
theworkouts,promember=promember)
script = res[0]
div = res[1]
paulslope = res[2]
paulintercept = res[3]
p1 = res[4]
message = res[5]
else:
script = ''
div = '<p>No ranking pieces found.</p>'
paulslope = 1
paulintercept = 1
p1 = [1,1,1,1]
message = ""
if request.method == 'POST' and "piece" in request.POST:
form = PredictedPieceForm(request.POST)
if form.is_valid():
value = form.cleaned_data['value']
pieceunit = form.cleaned_data['pieceunit']
if pieceunit == 'd':
rankingdistances.append(value)
else:
rankingdurations.append(datetime.time(minute=value))
else:
form = PredictedPieceForm()
rankingdistances.sort()
rankingdurations.sort()
predictions = []
cpredictions = []
for rankingdistance in rankingdistances:
# Paul's model
p = paulslope*np.log10(rankingdistance)+paulintercept
velo = 500./p
t = rankingdistance/velo
pwr = 2.8*(velo**3)
a = {'distance':rankingdistance,
'duration':timedeltaconv(t),
'pace':timedeltaconv(p),
'power':int(pwr)}
predictions.append(a)
# CP model -
pwr2 = p1[0]/(1+t/p1[2])
pwr2 += p1[1]/(1+t/p1[3])
if pwr2 <= 0:
pwr2 = 50.
velo2 = (pwr2/2.8)**(1./3.)
if np.isnan(velo2) or velo2 <= 0:
velo2 = 1.0
t2 = rankingdistance/velo2
pwr3 = p1[0]/(1+t2/p1[2])
pwr3 += p1[1]/(1+t2/p1[3])
if pwr3 <= 0:
pwr3 = 50.
velo3 = (pwr3/2.8)**(1./3.)
if np.isnan(velo3) or velo3 <= 0:
velo3 = 1.0
t3 = rankingdistance/velo3
p3 = 500./velo3
a = {'distance':rankingdistance,
'duration':timedeltaconv(t3),
'pace':timedeltaconv(p3),
'power':int(pwr3)}
cpredictions.append(a)
for rankingduration in rankingdurations:
t = 3600.*rankingduration.hour
t += 60.*rankingduration.minute
t += rankingduration.second
t += rankingduration.microsecond/1.e6
# Paul's model
ratio = paulintercept/paulslope
u = ((2**(2+ratio))*(5.**(3+ratio))*t*np.log(10))/paulslope
d = 500*t*np.log(10.)
d = d/(paulslope*lambertw(u))
d = d.real
velo = d/t
p = 500./velo
pwr = 2.8*(velo**3)
a = {'distance':int(d),
'duration':timedeltaconv(t),
'pace':timedeltaconv(p),
'power':int(pwr)}
predictions.append(a)
# CP model
pwr = p1[0]/(1+t/p1[2])
pwr += p1[1]/(1+t/p1[3])
if pwr <= 0:
pwr = 50.
velo = (pwr/2.8)**(1./3.)
if np.isnan(velo) or velo <=0:
velo = 1.0
d = t*velo
p = 500./velo
a = {'distance':int(d),
'duration':timedeltaconv(t),
'pace':timedeltaconv(p),
'power':int(pwr)}
cpredictions.append(a)
return render(request, 'rankings.html',
{'rankingworkouts':theworkouts,
'interactiveplot':script,
'the_div':div,
'predictions':predictions,
'cpredictions':cpredictions,
'nrdata':len(thedistances),
'form':form,
'dateform':dateform,
'deltaform':deltaform,
'id': theuser,
'theuser':uu,
'message':message,
'startdate':startdate,
'enddate':enddate,
})
# Reload the workout and calculate the summary from the stroke data (lapIDx)
@login_required()
def workout_recalcsummary_view(request,id=0):
row = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,row)==False):
message = "You are not allowed to edit this workout"
url = reverse(workouts_view,args=[str(message)])
return HttpResponseRedirect(url)
filename = row.csvfilename
rowdata = rdata(filename)
row.summary = rowdata.allstats()
row.save()
successmessage = "Summary Updated"
url = reverse(workout_edit_view,
kwargs = {
'id':str(id),
'successmessage':str(successmessage),
})
return HttpResponseRedirect(url)
# List Workouts
@login_required()
def workouts_view(request,message='',successmessage='',
startdatestring="",enddatestring="",
startdate=timezone.now()-datetime.timedelta(days=365),
enddate=timezone.now()+datetime.timedelta(days=1)):
try:
r = Rower.objects.get(user=request.user)
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,
})
startdate = datetime.datetime.combine(startdate,datetime.time())
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
enddate = enddate+datetime.timedelta(days=1)
if startdatestring:
startdate = iso8601.parse_date(startdatestring)
if enddatestring:
enddate = iso8601.parse_date(enddatestring)
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")
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,20) # 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)
today = timezone.now()
announcements = SiteAnnouncement.objects.filter(
expires__gte=today
).order_by(
"-created",
"-id"
)
return render(request, 'list_workouts.html',
{'workouts': workouts,
'message': message,
'successmessage':successmessage,
'dateform':dateform,
'startdate':startdate,
'enddate':enddate,
'announcements':announcements[0:4],
})
except Rower.DoesNotExist:
return HttpResponse("User has no rower instance")
# List of workouts to compare a selected workout to
@user_passes_test(promember,login_url="/",redirect_field_name=None)
def workout_comparison_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)
row = Workout.objects.get(id=id)
return render(request, 'comparison_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:
return HttpResponse("User has no rower instance")
# Basic 'EDIT' view of workout
def workout_view(request,id=0):
try:
# check if valid ID exists (workout exists)
row = Workout.objects.get(id=id)
g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime")
r = Rower.objects.get(id=row.user.id)
u = User.objects.get(id=r.user.id)
# create interactive plot
res = interactive_chart(id)
script = res[0]
div = res[1]
# render page
if (len(g)<=3):
return render(request, 'workout_view.html',
{'workout':row,
'graphs1':g[0:3],
'last_name':u.last_name,
'first_name':u.first_name,
'interactiveplot':script,
'the_div':div})
else:
return render(request, 'workout_view.html',
{'workout':row,
'graphs1':g[0:3],
'graphs2':g[3:6],
'last_name':u.last_name,
'first_name':u.first_name,
'interactiveplot':script,
'the_div':div})
except Workout.DoesNotExist:
return HttpResponseNotFound("Workout doesn't exist")
# Resets stroke data to raw data (pace)
@user_passes_test(promember,login_url="/",redirect_field_name=None)
def workout_undo_smoothenpace_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,row)==False):
message = "You are not allowed to edit this workout"
url = reverse(workouts_view,args=[str(message)])
return HttpResponseRedirect(url)
filename = row.csvfilename
row = rdata(filename)
if row == 0:
return HttpResponse("Error: CSV Data File Not Found")
if 'originalvelo' in row.df:
velo = row.df['originalvelo'].values
row.df[' Stroke500mPace (sec/500m)'] = 500./velo
row.write_csv(filename,gzip=True)
dataprep.update_strokedata(id,row.df)
url = "/rowers/workout/"+str(id)+"/advanced"
return HttpResponseRedirect(url)
# Data smoothing of pace data
@user_passes_test(promember,login_url="/",redirect_field_name=None)
def workout_smoothenpace_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,row)==False):
message = "You are not allowed to edit this workout"
url = reverse(workouts_view,args=[str(message)])
return HttpResponseRedirect(url)
filename = row.csvfilename
row = rdata(filename)
if row == 0:
return HttpResponse("Error: CSV Data File Not Found")
pace = row.df[' Stroke500mPace (sec/500m)'].values
velo = 500./pace
if not 'originalvelo' in row.df:
row.df['originalvelo'] = velo
velo2 = stravastuff.ewmovingaverage(velo,5)
pace2 = 500./abs(velo2)
row.df[' Stroke500mPace (sec/500m)'] = pace2
row.df = row.df.fillna(0)
row.write_csv(filename,gzip=True)
dataprep.update_strokedata(id,row.df)
url = "/rowers/workout/"+str(id)+"/advanced"
return HttpResponseRedirect(url)
# Process CrewNerd Summary CSV and update summary
@user_passes_test(promember,login_url="/",redirect_field_name=None)
def workout_crewnerd_summary_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id)
if request.method == 'POST':
form = CNsummaryForm(request.POST,request.FILES)
if form.is_valid():
f = request.FILES['file']
res = handle_uploaded_file(f)
fname = res[1]
try:
sumd = summarydata(fname)
row.summary = sumd.allstats()
row.save()
os.remove(fname)
successmessage = "CrewNerd summary added"
url = reverse(workout_edit_view,
kwargs = {
'id':str(id),
'successmessage':str(successmessage),
})
return HttpResponseRedirect(url)
except:
os.remove(fname)
message = "Something went wrong (workout_crewnerd_summary_view)"
url = reverse(workout_edit_view,
kwargs = {
'id':str(id),
'message':str(message),
})
return HttpResponseRedirect(url)
else:
return render(request,
"cn_form.html",
{'form':form,
'id':row.id})
else:
form = CNsummaryForm()
return render(request,
"cn_form.html",
{'form':form,
'id':row.id})
# Get weather for given location and date/time
@user_passes_test(promember,login_url="/",redirect_field_name=None)
def workout_downloadwind_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id)
f1 = row.csvfilename
if (checkworkoutuser(request.user,row)==False):
message = "You are not allowed to edit this workout"
url = reverse(workouts_view,args=[str(message)])
return HttpResponseRedirect(url)
# create bearing
rowdata = rdata(f1)
if rowdata == 0:
return HttpResponse("Error: CSV Data File Not Found")
try:
bearing = rowdata.df.ix[:,'bearing'].values
except KeyError:
rowdata.add_bearing()
rowdata.write_csv(f1,gzip=True)
# get wind
try:
avglat = rowdata.df[' latitude'].mean()
avglon = rowdata.df[' longitude'].mean()
avgtime = int(rowdata.df['TimeStamp (sec)'].mean()-rowdata.df.ix[0,'TimeStamp (sec)'])
startdatetime = dateutil.parser.parse("{}, {}".format(row.date,
row.starttime))
starttimeunix = int(mktime(startdatetime.timetuple()))
avgtime = starttimeunix+avgtime
winddata = get_wind_data(avglat,avglon,avgtime)
windspeed = winddata[0]
windbearing = winddata[1]
message = winddata[2]
row.notes += "\n"+message
row.save()
rowdata.add_wind(windspeed,windbearing)
rowdata.write_csv(f1,gzip=True)
kwargs = {'successmessage':str(message),
'id':str(id)}
url = reverse(workout_wind_view,kwargs=kwargs)
response = HttpResponseRedirect(url)
except KeyError:
message = "No latitude/longitude data"
kwargs = {'message':str(message),
'id':str(id)}
url = reverse(workout_wind_view,kwargs=kwargs)
response = HttpResponseRedirect(url)
return response
# Show form to update wind data
@user_passes_test(promember,login_url="/",redirect_field_name=None)
def workout_wind_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,row)==False):
message = "You are not allowed to edit this workout"
url = reverse(workouts_view,args=[str(message)])
return HttpResponseRedirect(url)
# get data
f1 = row.csvfilename
u = request.user
r = Rower.objects.get(user=u)
# create bearing
rowdata = rdata(f1)
if row == 0:
return HttpResponse("Error: CSV Data File Not Found")
hascoordinates = 1
try:
latitude = rowdata.df.ix[:,' latitude']
except KeyError:
hascoordinates = 0
if not latitude.std():
hascoordinates = 0
try:
bearing = rowdata.df.ix[:,'bearing'].values
except KeyError:
rowdata.add_bearing()
rowdata.write_csv(f1,gzip=True)
if request.method == 'POST':
# process form
form = UpdateWindForm(request.POST)
if form.is_valid():
vwind1 = form.cleaned_data['vwind1']
vwind2 = form.cleaned_data['vwind2']
dist1 = form.cleaned_data['dist1']
dist2 = form.cleaned_data['dist2']
winddirection1 = form.cleaned_data['winddirection1']
winddirection2 = form.cleaned_data['winddirection2']
windunit = form.cleaned_data['windunit']
rowdata.update_wind(vwind1,vwind2,
winddirection1,
winddirection2,
dist1,dist2,
units=windunit)
rowdata.write_csv(f1,gzip=True)
else:
message = "Invalid Form"
kwargs = {'message':str(message),
'id':str(id)}
url = reverse(workout_wind_view,kwargs=kwargs)
response = HttpResponseRedirect(url)
else:
form = UpdateWindForm()
# create interactive plot
res = interactive_windchart(id,promember=1)
script = res[0]
div = res[1]
if hascoordinates:
res = googlemap_chart(rowdata.df[' latitude'],
rowdata.df[' longitude'],
row.name)
gmscript = res[0]
gmdiv = res[1]
else:
gmscript = ""
gmdiv = "No GPS data available"
return render(request,
'windedit.html',
{'workout':row,
'message': message,
'successmessage': successmessage,
'interactiveplot':script,
'form':form,
'the_div':div,
'gmap':gmscript,
'gmapdiv':gmdiv})
# Show form to update River stream data (for river dwellers)
@user_passes_test(promember,login_url="/",redirect_field_name=None)
def workout_stream_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,row)==False):
message = "You are not allowed to edit this workout"
url = reverse(workouts_view,args=[str(message)])
return HttpResponseRedirect(url)
# create interactive plot
f1 = row.csvfilename
u = request.user
r = Rower.objects.get(user=u)
rowdata = rdata(f1)
if rowdata == 0:
return HttpResponse("Error: CSV Data File Not Found")
if request.method == 'POST':
# process form
form = UpdateStreamForm(request.POST)
if form.is_valid():
dist1 = form.cleaned_data['dist1']
dist2 = form.cleaned_data['dist2']
stream1 = form.cleaned_data['stream1']
stream2 = form.cleaned_data['stream2']
streamunit = form.cleaned_data['streamunit']
rowdata.update_stream(stream1,stream2,dist1,dist2,
units=streamunit)
rowdata.write_csv(f1,gzip=True)
else:
message = "Invalid Form"
kwargs = {'message':str(message),
'id':str(id)}
url = reverse(workout_wind_view,kwargs=kwargs)
response = HttpResponseRedirect(url)
else:
form = UpdateStreamForm()
# create interactive plot
res = interactive_streamchart(id,promember=1)
script = res[0]
div = res[1]
return render(request,
'streamedit.html',
{'workout':row,
'message': message,
'successmessage': successmessage,
'interactiveplot':script,
'form':form,
'the_div':div})
# Form to set average crew weight and boat type, then run power calcs
@user_passes_test(promember, login_url="/",redirect_field_name=None)
def workout_otwsetpower_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,row)==False):
message = "You are not allowed to edit this workout"
url = reverse(workouts_view,args=[str(message)])
return HttpResponseRedirect(url)
if request.method == 'POST':
# process form
form = AdvancedWorkoutForm(request.POST)
if form.is_valid():
boattype = form.cleaned_data['boattype']
weightvalue = form.cleaned_data['weightvalue']
row.boattype = boattype
row.weightvalue = weightvalue
row.save()
# load row data & create power/wind/bearing columns if not set
f1 = row.csvfilename
rowdata = rdata(f1)
if rowdata == 0:
return HttpResponse("Error: CSV Data File Not Found")
try:
vstream = rowdata.df['vstream']
except KeyError:
rowdata.add_stream(0)
rowdata.write_csv(f1,gzip=True)
try:
bearing = rowdata.df['bearing']
except KeyError:
rowdata.add_bearing()
rowdata.write_csv(f1,gzip=True)
try:
vwind = rowdata.df['vwind']
except KeyError:
rowdata.add_wind(0,0)
rowdata.write_csv(f1,gzip=True)
# do power calculation (asynchronous)
u = request.user
first_name = u.first_name
last_name = u.last_name
emailaddress = u.email
if settings.DEBUG:
res = handle_otwsetpower.delay(f1,boattype,weightvalue,
first_name,last_name,emailaddress,id)
else:
res = queuelow.enqueue(handle_otwsetpower,f1,boattype,
weightvalue,
first_name,last_name,emailaddress,id)
successmessage = "Your calculations have been submitted. You will receive an email when they are done."
kwargs = {'successmessage':str(successmessage),
'id':str(id)}
url = reverse(workout_advanced_view,kwargs=kwargs)
response = HttpResponseRedirect(url)
return response
else:
message = "Invalid Form"
kwargs = {'message':str(message),
'id':str(id)}
url = reverse(workout_otwsetpower_view,kwargs=kwargs)
response = HttpResponseRedirect(url)
else:
form = AdvancedWorkoutForm(instance=row)
return render(request,
'otwsetpower.html',
{'workout':row,
'message': message,
'successmessage': successmessage,
'form':form,
})
# A special Edit page with all the Geeky functionality for the workout
@login_required()
def workout_geeky_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id)
form = WorkoutForm(instance=row)
g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime")
# check if user is owner of this workout
if (checkworkoutuser(request.user,row)==False):
message = "You are not allowed to edit this workout"
url = reverse(workouts_view,args=[str(message)])
return HttpResponseRedirect(url)
# create interactive plot
f1 = row.csvfilename
u = request.user
r = Rower.objects.get(user=u)
# create interactive plot
try:
res = interactive_chart(id,promember=1)
script = res[0]
div = res[1]
except ValueError:
pass
if row.workouttype=='water':
return render(request,
'otwgeeky.html',
{'workout':row,
'message': message,
'successmessage': successmessage,
'interactiveplot':script,
'the_div':div})
else:
return render(request,
'advancededit.html',
{'workout':row,
'message': message,
'successmessage': successmessage,
'interactiveplot':script,
'the_div':div})
# The Advanced edit page
@login_required()
def workout_advanced_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id)
form = WorkoutForm(instance=row)
g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime")
# check if user is owner of this workout
if (checkworkoutuser(request.user,row)==False):
message = "You are not allowed to edit this workout"
url = reverse(workouts_view,args=[str(message)])
return HttpResponseRedirect(url)
# create interactive plot
f1 = row.csvfilename
u = request.user
r = Rower.objects.get(user=u)
# create interactive plot
try:
res = interactive_chart(id,promember=1)
script = res[0]
div = res[1]
except ValueError:
pass
if row.workouttype=='water':
return render(request,
'advancedotw.html',
{'workout':row,
'message': message,
'successmessage': successmessage,
'interactiveplot':script,
'the_div':div})
else:
return render(request,
'advancededit.html',
{'workout':row,
'message': message,
'successmessage': successmessage,
'interactiveplot':script,
'the_div':div})
# The interactive plot comparing two workouts (obsolete version)
def workout_comparison_view(request,id1=0,id2=0,xparam='distance',yparam='spm'):
promember=0
if not request.user.is_anonymous():
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated() and r.rowerplan=='pro'
if result:
promember=1
# create interactive plot
res = interactive_comparison_chart(id1,id2,xparam=xparam,yparam=yparam,
promember=promember)
script = res[0]
div = res[1]
return render(request,
'comparisonchart.html',
{'interactiveplot':script,
'the_div':div,
'id1':id1,
'id2':id2,
'xparam':xparam,
'yparam':yparam,
})
# Updated version of comparison plot
def workout_comparison_view2(request,id1=0,id2=0,xparam='distance',
yparam='spm',plottype='line'):
promember=0
if not request.user.is_anonymous():
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated() and r.rowerplan=='pro'
if result:
promember=1
# create interactive plot
res = interactive_comparison_chart(id1,id2,xparam=xparam,yparam=yparam,
promember=promember,plottype=plottype)
script = res[0]
div = res[1]
return render(request,
'comparisonchart2.html',
{'interactiveplot':script,
'the_div':div,
'id1':id1,
'id2':id2,
'xparam':xparam,
'yparam':yparam,
'plottype':plottype,
'promember':promember,
})
# The famous flex chart
def workout_flexchart3_view(request,*args,**kwargs):
try:
id = kwargs['id']
except KeyError:
return HttpResponse("Invalid workout number")
if 'promember' in kwargs:
promember = kwargs['promember']
else:
promember = 0
try:
favoritenr = int(request.GET['favoritechart'])
except:
favoritenr = 0
row = Workout.objects.get(id=id)
promember=0
mayedit=0
if not request.user.is_anonymous():
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated() and r.rowerplan=='pro'
if result:
promember=1
if request.user == row.user.user:
mayedit=1
workouttype = 'ote'
if row.workouttype == 'water':
workouttype = 'otw'
favorites = FavoriteChart.objects.filter(user=r,
workouttype__in=[workouttype,'both']).order_by("id")
maxfav = len(favorites)-1
# check if favoritenr is not out of range
if favorites:
try:
t = favorites[favoritenr].xparam
except IndexError:
favoritenr=0
if 'xparam' in kwargs:
xparam = kwargs['xparam']
else:
if favorites:
xparam = favorites[favoritenr].xparam
else:
xparam = 'distance'
if 'yparam1' in kwargs:
yparam1 = kwargs['yparam1']
else:
if favorites:
yparam1 = favorites[favoritenr].yparam1
else:
yparam1 = 'pace'
if 'yparam2' in kwargs:
yparam2 = kwargs['yparam2']
if yparam2 == '':
yparam2 = 'None'
else:
if favorites:
yparam2 = favorites[favoritenr].yparam2
if yparam2 == '':
yparam2 = 'None'
else:
yparam2 = 'hr'
if 'plottype' in kwargs:
plottype = kwargs['plottype']
else:
if favorites:
plottype = favorites[favoritenr].plottype
else:
plottype = 'line'
if 'workstrokesonly' in kwargs:
workstrokesonly = kwargs['workstrokesonly']
else:
if favorites:
workstrokesonly = not favorites[favoritenr].reststrokes
else:
workstrokesonly = False
if request.method == 'POST' and 'savefavorite' in request.POST:
workstrokesonly = request.POST['workstrokesonlysave']
reststrokes = not workstrokesonly
f = FavoriteChart(user=r,xparam=xparam,
yparam1=yparam1,yparam2=yparam2,
plottype=plottype,workouttype=workouttype,
reststrokes=reststrokes)
f.save()
if request.method == 'POST' and 'workstrokesonly' in request.POST:
workstrokesonly = request.POST['workstrokesonly']
if workstrokesonly == 'True':
workstrokesonly = True
else:
workstrokesonly = False
# create interactive plot
try:
script,div,js_resources,css_resources = interactive_flex_chart2(id,xparam=xparam,yparam1=yparam1,
yparam2=yparam2,
promember=promember,plottype=plottype,
workstrokesonly=workstrokesonly)
except ValueError:
script,div = interactive_flex_chart2(id,xparam=xparam,yparam1=yparam1,
yparam2=yparam2,
promember=promember,plottype=plottype,
workstrokesonly=workstrokesonly)
js_resources = ""
css_resources = ""
# script = res[0]
# div = res[1]
# js_resources = res[2]
# css_resources = res[3]
if row.workouttype == 'water':
return render(request,
'flexchart3otw.html',
{'the_script':script,
'the_div':div,
'js_res': js_resources,
'css_res':css_resources,
'id':id,
'xparam':xparam,
'yparam1':yparam1,
'yparam2':yparam2,
'plottype':plottype,
'mayedit':mayedit,
'promember':promember,
'workstrokesonly': not workstrokesonly,
'favoritenr':favoritenr,
'maxfav':maxfav,
})
else:
return render(request,
'flexchart3.html',
{'the_script':script,
'the_div':div,
'js_res': js_resources,
'css_res':css_resources,
'id':id,
'xparam':xparam,
'yparam1':yparam1,
'yparam2':yparam2,
'plottype':plottype,
'mayedit':mayedit,
'promember':promember,
'workstrokesonly': not workstrokesonly,
'favoritenr':favoritenr,
'maxfav':maxfav,
})
# The interactive plot with the colored Heart rate zones
def workout_biginteractive_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id)
# check if user is owner of this workout
# create interactive plot
f1 = row.csvfilename
u = request.user
# r = Rower.objects.get(user=u)
promember=0
mayedit=0
if not request.user.is_anonymous():
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated() and r.rowerplan=='pro'
if result:
promember=1
if request.user == row.user.user:
mayedit=1
# create interactive plot
res = interactive_bar_chart(id,promember=promember)
script = res[0]
div = res[1]
return render(request,
'biginteractive1.html',
{'workout':row,
'message': message,
'successmessage': successmessage,
'interactiveplot':script,
'the_div':div,
'promember':promember,
'mayedit':mayedit})
# The interactive plot with wind corrected pace for OTW outings
def workout_otwpowerplot_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id)
# check if user is owner of this workout
# create interactive plot
f1 = row.csvfilename
u = request.user
# r = Rower.objects.get(user=u)
promember=0
mayedit=0
if not request.user.is_anonymous():
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated() and r.rowerplan=='pro'
if result:
promember=1
if request.user == row.user.user:
mayedit=1
# create interactive plot
res = interactive_otw_advanced_pace_chart(id,promember=promember)
script = res[0]
div = res[1]
return render(request,
'otwinteractive.html',
{'workout':row,
'message': message,
'successmessage': successmessage,
'interactiveplot':script,
'the_div':div,
'mayedit':mayedit})
# the page where you can chose where to export this workout
@login_required()
def workout_export_view(request,id=0, message="", successmessage=""):
request.session[translation.LANGUAGE_SESSION_KEY] = USER_LANGUAGE
row = Workout.objects.get(id=id)
form = WorkoutForm(instance=row)
g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime")
# check if user is owner of this workout
if (checkworkoutuser(request.user,row)==False):
message = "You are not allowed to edit this workout"
url = reverse(workouts_view,args=[str(message)])
return HttpResponseRedirect(url)
else:
return render(request,
'export.html',
{'workout':row,
'message':message,
'successmessage':successmessage,
})
# The basic edit page
@login_required()
def workout_edit_view(request,id=0,message="",successmessage=""):
request.session[translation.LANGUAGE_SESSION_KEY] = USER_LANGUAGE
if request.method == 'POST':
# Form was submitted
form = WorkoutForm(request.POST)
if form.is_valid():
# Get values from form
name = form.cleaned_data['name']
date = form.cleaned_data['date']
starttime = form.cleaned_data['starttime']
workouttype = form.cleaned_data['workouttype']
duration = form.cleaned_data['duration']
distance = form.cleaned_data['distance']
notes = form.cleaned_data['notes']
try:
boattype = request.POST['boattype']
except KeyError:
boattype = Workout.objects.get(id=id).boattype
startdatetime = (str(date) + ' ' + str(starttime))
startdatetime = datetime.datetime.strptime(startdatetime,
"%Y-%m-%d %H:%M:%S")
startdatetime = timezone.make_aware(startdatetime)
try:
# check if valid ID exists (workout exists)
row = Workout.objects.get(id=id)
# check if user is owner of this workout
if checkworkoutuser(request.user,row):
row.name = name
row.date = date
row.starttime = starttime
row.startdatetime = startdatetime
row.workouttype = workouttype
row.notes = notes
row.duration = duration
row.distance = distance
row.boattype = boattype
row.save()
# change data in csv file
r = rdata(row.csvfilename)
if r == 0:
return HttpResponse("Error: CSV Data File Not Found")
r.rowdatetime = startdatetime
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),
'successmessage':str(successmessage),
})
response = HttpResponseRedirect(url)
else:
message = "You are not allowed to change this workout"
url = reverse(workouts_view,args=[str(message)])
response = HttpResponseRedirect(url)
except Workout.DoesNotExist:
# create new workout
r = Rower.objects.get(user=request.user)
w = Workout(name=name,date=date,workouttype=workouttype,
user=r)
w.save()
successmessage = "New Workout Created"
url = reverse(workouts_view,
kwargs = {
'successmessage':str(successmessage),
})
response = HttpResponseRedirect(url)
else:
message = "Invalid Form"
url = reverse(workouts_view,args=[str(message)])
response = HttpResponseRedirect(url)
return response
else:
try:
row = Workout.objects.get(id=id)
form = WorkoutForm(instance=row)
g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime")
# check if user is owner of this workout
if (checkworkoutuser(request.user,row)==False):
message = "You are not allowed to edit this workout"
url = reverse(workouts_view,args=[str(message)])
return HttpResponseRedirect(url)
else:
# create interactive plot
f1 = row.csvfilename
u = request.user
r = Rower.objects.get(user=u)
rowdata = rdata(f1)
hascoordinates = 1
if rowdata != 0:
try:
latitude = rowdata.df[' latitude']
if not latitude.std():
hascoordinates = 0
except KeyError,AttributeError:
hascoordinates = 0
else:
hascoordinates = 0
if hascoordinates:
res = googlemap_chart(rowdata.df[' latitude'],
rowdata.df[' longitude'],
row.name)
gmscript = res[0]
gmdiv = res[1]
else:
gmscript = ""
gmdiv = ""
# render page
if (len(g)<=3):
return render(request, 'workout_form.html',
{'form':form,
'workout':row,
'graphs1':g[0:3],
'message': message,
'successmessage': successmessage,
'gmscript': gmscript,
'gmdiv': gmdiv,
})
else:
return render(request, 'workout_form.html',
{'form':form,
'workout':row,
'graphs1':g[0:3],
'graphs2':g[3:6],
'message': message,
'successmessage': successmessage,
'gmscript': gmscript,
'gmdiv': gmdiv,
})
except Workout.DoesNotExist:
message = "workout doesn't exist"
url = reverse(workouts_view,
kwargs = {
'message': str(message)
})
return HttpResponseRedirect(url)
# Create the chart image with wind corrected pace (OTW)
@user_passes_test(promember,login_url="/",redirect_field_name=None)
def workout_add_otw_powerplot_view(request,id):
w = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,w)==False):
return HttpResponse("You are not allowed add plots to this workout")
else:
f1 = w.csvfilename[6:-4]
timestr = strftime("%Y%m%d-%H%M%S")
imagename = f1+timestr+'.png'
fullpathimagename = 'static/plots/'+imagename
u = request.user
r = Rower.objects.get(user=u)
hrpwrdata = {
'hrmax':r.max,
'hrut2':r.ut2,
'hrut1':r.ut1,
'hrat':r.at,
'hrtr':r.tr,
'hran':r.an,
'ftp':r.ftp,
}
# make plot - asynchronous task
plotnr = 9
if (w.workouttype=='water'):
plotnr = plotnr+3
if settings.DEBUG:
res = handle_makeplot.delay(f1,w.csvfilename,
w.name,hrpwrdata,plotnr,imagename)
else:
res = queue.enqueue(handle_makeplot,f1,w.csvfilename,
w.name,hrpwrdata,plotnr,imagename)
# i = GraphImage(workout=w,creationdatetime=datetime.datetime.now(),
# filename=fullpathimagename)
i = GraphImage(workout=w,creationdatetime=timezone.now(),
filename=fullpathimagename)
i.save()
url = "/rowers/workout/"+str(w.id)+"/edit"
return HttpResponseRedirect(url)
# Create the Heart rate zone pie chart
@login_required()
def workout_add_piechart_view(request,id):
w = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,w)==False):
return HttpResponse("You are not allowed add plots to this workout")
else:
f1 = w.csvfilename[6:-4]
timestr = strftime("%Y%m%d-%H%M%S")
imagename = f1+timestr+'.png'
fullpathimagename = 'static/plots/'+imagename
u = request.user
r = Rower.objects.get(user=u)
hrpwrdata = {
'hrmax':r.max,
'hrut2':r.ut2,
'hrut1':r.ut1,
'hrat':r.at,
'hrtr':r.tr,
'hran':r.an,
'ftp':r.ftp,
}
# make plot - asynchronous task
plotnr = 3
if (w.workouttype=='water'):
plotnr = plotnr+3
if settings.DEBUG:
res = handle_makeplot.delay(f1,w.csvfilename,
w.name,hrpwrdata,plotnr,imagename)
else:
res = queue.enqueue(handle_makeplot,f1,w.csvfilename,
w.name,hrpwrdata,plotnr,imagename)
# i = GraphImage(workout=w,creationdatetime=datetime.datetime.now(),
# filename=fullpathimagename)
i = GraphImage(workout=w,creationdatetime=timezone.now(),
filename=fullpathimagename)
i.save()
url = "/rowers/workout/"+str(w.id)+"/edit"
return HttpResponseRedirect(url)
# Create the Power zone pie chart
@login_required()
def workout_add_power_piechart_view(request,id):
w = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,w)==False):
return HttpResponse("You are not allowed add plots to this workout")
else:
f1 = w.csvfilename[6:-4]
timestr = strftime("%Y%m%d-%H%M%S")
imagename = f1+timestr+'.png'
fullpathimagename = 'static/plots/'+imagename
u = request.user
r = Rower.objects.get(user=u)
hrpwrdata = {
'hrmax':r.max,
'hrut2':r.ut2,
'hrut1':r.ut1,
'hrat':r.at,
'hrtr':r.tr,
'hran':r.an,
'ftp':r.ftp,
}
# make plot - asynchronous task
plotnr = 13
if (w.workouttype=='water'):
plotnr = plotnr+3
if settings.DEBUG:
res = handle_makeplot.delay(f1,w.csvfilename,
w.name,hrpwrdata,plotnr,imagename)
else:
res = queue.enqueue(handle_makeplot,f1,w.csvfilename,
w.name,hrpwrdata,plotnr,imagename)
i = GraphImage(workout=w,creationdatetime=timezone.now(),
filename=fullpathimagename)
i.save()
url = "/rowers/workout/"+str(w.id)+"/edit"
return HttpResponseRedirect(url)
# Create the time based summary chart
@login_required()
def workout_add_timeplot_view(request,id):
w = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,w)==False):
return HttpResponse("You are not allowed add plots to this workout")
else:
f1 = w.csvfilename[6:-4]
timestr = strftime("%Y%m%d-%H%M%S")
imagename = f1+timestr+'.png'
fullpathimagename = 'static/plots/'+imagename
u = request.user
r = Rower.objects.get(user=u)
hrpwrdata = {
'hrmax':r.max,
'hrut2':r.ut2,
'hrut1':r.ut1,
'hrat':r.at,
'hrtr':r.tr,
'hran':r.an,
'ftp':r.ftp,
}
# make plot - asynchronous task
plotnr = 1
if (w.workouttype=='water'):
plotnr = plotnr+3
if settings.DEBUG:
res = handle_makeplot.delay(f1,w.csvfilename,
w.name,hrpwrdata,plotnr,imagename)
else:
res = queue.enqueue(handle_makeplot,f1,w.csvfilename,
w.name,hrpwrdata,plotnr,imagename)
i = GraphImage(workout=w,creationdatetime=timezone.now(),
filename=fullpathimagename)
# i = GraphImage(workout=w,creationdatetime=datetime.datetime.now(),
# filename=fullpathimagename)
i.save()
url = "/rowers/workout/"+str(w.id)+"/edit"
return HttpResponseRedirect(url)
# Create the distance based summary chart
@login_required()
def workout_add_distanceplot_view(request,id):
w = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,w)==False):
return HttpResponse("You are not allowed add plots to this workout")
else:
f1 = w.csvfilename[6:-4]
timestr = strftime("%Y%m%d-%H%M%S")
imagename = f1+timestr+'.png'
fullpathimagename = 'static/plots/'+imagename
u = request.user
r = Rower.objects.get(user=u)
hrpwrdata = {
'hrmax':r.max,
'hrut2':r.ut2,
'hrut1':r.ut1,
'hrat':r.at,
'hrtr':r.tr,
'hran':r.an,
'ftp':r.ftp,
}
# make plot - asynchronous task
plotnr = 2
if (w.workouttype=='water'):
plotnr = plotnr+3
if settings.DEBUG:
res = handle_makeplot.delay(f1,w.csvfilename,
w.name,hrpwrdata,plotnr,imagename)
else:
res = queue.enqueue(handle_makeplot,f1,w.csvfilename,
w.name,hrpwrdata,plotnr,imagename)
i = GraphImage(workout=w,creationdatetime=timezone.now(),
filename=fullpathimagename)
i.save()
url = "/rowers/workout/"+str(w.id)+"/edit"
return HttpResponseRedirect(url)
# Create the advanced parameters distance overview chart
@user_passes_test(promember,login_url="/",redirect_field_name=None)
def workout_add_distanceplot2_view(request,id):
w = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,w)==False):
return HttpResponse("You are not allowed add plots to this workout")
else:
f1 = w.csvfilename[6:-4]
timestr = strftime("%Y%m%d-%H%M%S")
imagename = f1+timestr+'.png'
fullpathimagename = 'static/plots/'+imagename
u = request.user
r = Rower.objects.get(user=u)
hrpwrdata = {
'hrmax':r.max,
'hrut2':r.ut2,
'hrut1':r.ut1,
'hrat':r.at,
'hrtr':r.tr,
'hran':r.an,
}
# make plot - asynchronous task
plotnr = 7
if (w.workouttype=='water'):
plotnr = plotnr+3
if settings.DEBUG:
res = handle_makeplot.delay(f1,w.csvfilename,
w.name,hrpwrdata,plotnr,imagename)
else:
res = queue.enqueue(handle_makeplot,f1,w.csvfilename,
w.name,hrpwrdata,plotnr,imagename)
i = GraphImage(workout=w,creationdatetime=timezone.now(),
filename=fullpathimagename)
i.save()
url = "/rowers/workout/"+str(w.id)+"/edit"
return HttpResponseRedirect(url)
# Create the advanced parameters time based overview chart
@user_passes_test(promember,login_url="/",redirect_field_name=None)
def workout_add_timeplot2_view(request,id):
w = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,w)==False):
return HttpResponse("You are not allowed add plots to this workout")
else:
f1 = w.csvfilename[6:-4]
timestr = strftime("%Y%m%d-%H%M%S")
imagename = f1+timestr+'.png'
fullpathimagename = 'static/plots/'+imagename
u = request.user
r = Rower.objects.get(user=u)
hrpwrdata = {
'hrmax':r.max,
'hrut2':r.ut2,
'hrut1':r.ut1,
'hrat':r.at,
'hrtr':r.tr,
'hran':r.an,
}
# make plot - asynchronous task
plotnr = 8
if (w.workouttype=='water'):
plotnr = plotnr+3
if settings.DEBUG:
res = handle_makeplot.delay(f1,w.csvfilename,
w.name,hrpwrdata,plotnr,imagename)
else:
res = queue.enqueue(handle_makeplot,f1,w.csvfilename,
w.name,hrpwrdata,plotnr,imagename)
i = GraphImage(workout=w,creationdatetime=timezone.now(),
filename=fullpathimagename)
i.save()
url = "/rowers/workout/"+str(w.id)+"/edit"
return HttpResponseRedirect(url)
# The page where you select which Strava workout to import
@login_required()
def workout_stravaimport_view(request,message=""):
res = stravastuff.get_strava_workout_list(request.user)
if (res.status_code != 200):
if (res.status_code == 401):
r = Rower.objects.get(user=request.user)
if (r.stravatoken == '') or (r.stravatoken is None):
s = "Token doesn't exist. Need to authorize"
return HttpResponseRedirect("/rowers/me/stravaauthorize/")
message = "Something went wrong in workout_stravaimport_view"
if settings.DEBUG:
return HttpResponse(res)
else:
url = reverse(workouts_view,
kwargs = {
'message': str(message)
})
return HttpResponseRedirect(url)
else:
data = res.json()
return render(request,'strava_list_import.html',
{'data':data,
'message':message,
})
return HttpResponse(res)
# The page where you select which SportTracks page to import
@login_required()
def workout_sporttracksimport_view(request,message=""):
res = sporttracksstuff.get_sporttracks_workout_list(request.user)
if (res.status_code != 200):
if (res.status_code == 401):
r = Rower.objects.get(user=request.user)
if (r.sporttrackstoken == '') or (r.sporttrackstoken is None):
s = "Token doesn't exist. Need to authorize"
return HttpResponseRedirect("/rowers/me/sporttracksauthorize/")
message = "Something went wrong in workout_sporttracksimport_view"
if settings.DEBUG:
return HttpResponse(res)
else:
url = reverse(workouts_view,
kwargs = {
'message': str(message)
})
return HttpResponseRedirect(url)
else:
workouts = []
for item in res.json()['items']:
d = int(float(item['total_distance']))
i = getidfromsturi(item['uri'])
n = item['name']
ttot = str(datetime.timedelta(seconds=int(float(item['duration']))))
s = item['start_time']
r = item['type']
keys = ['id','distance','duration','starttime','type','name']
values = [i,d,ttot,s,r,n]
res = dict(zip(keys,values))
workouts.append(res)
return render(request,'sporttracks_list_import.html',
{'workouts':workouts,
'message':message,
})
return HttpResponse(res)
# List of workouts on Concept2 logbook. This view only used for debugging
@login_required()
def c2listdebug_view(request,message=""):
try:
thetoken = c2_open(request.user)
except C2NoTokenError:
return HttpResponseRedirect("/rowers/me/c2authorize/")
r = Rower.objects.get(user=request.user)
res = c2stuff.get_c2_workout_list(request.user)
if (res.status_code != 200):
message = "Something went wrong in workout_c2import_view (C2 token renewal)"
if settings.DEBUG:
return HttpResponse(res)
else:
url = reverse(workouts_view,
kwargs = {
'message': str(message)
})
return HttpResponseRedirect(url)
else:
workouts = []
for item in res.json()['data']:
d = item['distance']
i = item['id']
ttot = item['time_formatted']
s = item['date']
r = item['type']
s2 = item['source']
c = item['comments']
keys = ['id','distance','duration','starttime','rowtype','source','comment']
values = [i,d,ttot,s,r,s2,c]
res = dict(zip(keys,values))
workouts.append(res)
return render(request,
'c2_list_import2.html',
{'workouts':workouts,
'message':message})
# List of workouts available on Concept2 logbook - for import
@login_required()
def workout_c2import_view(request,message=""):
try:
thetoken = c2_open(request.user)
except C2NoTokenError:
return HttpResponseRedirect("/rowers/me/c2authorize/")
res = c2stuff.get_c2_workout_list(request.user)
if (res.status_code != 200):
message = "Something went wrong in workout_c2import_view (C2 token refresh)"
if settings.DEBUG:
return HttpResponse(res)
else:
url = reverse(workouts_view,
kwargs = {
'message': str(message)
})
return HttpResponseRedirect(url)
else:
workouts = []
for item in res.json()['data']:
d = item['distance']
i = item['id']
ttot = item['time_formatted']
s = item['date']
r = item['type']
s2 = item['source']
c = item['comments']
keys = ['id','distance','duration','starttime','rowtype','source','comment']
values = [i,d,ttot,s,r,s2,c]
res = dict(zip(keys,values))
workouts.append(res)
return render(request,
'c2_list_import2.html',
{'workouts':workouts,
'message':message})
# Import a workout from Strava
@login_required()
def workout_getstravaworkout_view(request,stravaid):
res = stravastuff.get_strava_workout(request.user,stravaid)
strokedata = res[1]
data = res[0]
id = add_workout_from_strokedata(request.user,stravaid,data,strokedata,
source='strava')
w = Workout.objects.get(id=id)
w.uploadedtostrava=stravaid
w.save()
url = "/rowers/workout/"+str(id)+"/edit"
return HttpResponseRedirect(url)
# Imports a workout from SportTracks
@login_required()
def workout_getsporttracksworkout_view(request,sporttracksid):
res = sporttracksstuff.get_sporttracks_workout(request.user,sporttracksid)
data = res.json()
id = add_workout_from_stdata(request.user,sporttracksid,data)
w = Workout.objects.get(id=id)
w.uploadedtosporttracks=sporttracksid
w.save()
url = "/rowers/workout/"+str(id)+"/edit"
return HttpResponseRedirect(url)
# Imports a workout from Concept2
@login_required()
def workout_getc2workout_view(request,c2id):
try:
thetoken = c2_open(request.user)
except C2NoTokenError:
return HttpResponseRedirect("/rowers/me/c2authorize/")
res = c2stuff.get_c2_workout(request.user,c2id)
if (res.status_code == 200):
data = res.json()['data']
splitdata = None
if 'workout' in data:
if 'splits' in data['workout']:
splitdata = data['workout']['splits']
if 'intervals' in data['workout']:
splitdata = data['workout']['intervals']
# Check if workout has stroke data, and get the stroke data
if data['stroke_data']:
res2 = c2stuff.get_c2_workout_strokes(request.user,c2id)
else:
message = "This workout does not have any stroke data associated with it"
url = reverse(workout_c2import_view,
kwargs={
'message':message,
})
return HttpResponseRedirect(url)
# We have stroke data
if res2.status_code == 200:
strokedata = pd.DataFrame.from_dict(res2.json()['data'])
# create the workout
id = add_workout_from_strokedata(request.user,c2id,data,strokedata,
source='c2')
w = Workout.objects.get(id=id)
w.uploadedtoc2=c2id
# If we have split data, update the stroke data so they
# match exactly (some users are anal about this)
if splitdata:
try:
w.summary,sa,results = c2stuff.summaryfromsplitdata(splitdata,data,w.csvfilename)
except:
sa = []
results = []
with open("media/c2splitdata.log","a") as errorlog:
errorstring = str(sys.exc_info()[0])
timestr = strftime("%Y%m%d-%H%M%S")
errorlog.write(timestr+errorstring+"\r\n")
errorlog.write("views.py line 952\r\n")
w.save()
from rowingdata.trainingparser import getlist
# set stroke data in CSV file
if sa:
values = getlist(sa)
units = getlist(sa,sel='unit')
types = getlist(sa,sel='type')
rowdata = rdata(w.csvfilename)
if rowdata:
rowdata.updateintervaldata(values,
units,types,results)
rowdata.write_csv(w.csvfilename,gzip=True)
dataprep.update_strokedata(w.id,rowdata.df)
url = "/rowers/workout/"+str(id)+"/edit"
return HttpResponseRedirect(url)
else:
# message = json.loads(s.text)['message']
message = json.loads(res2.text)['message']
url = reverse(workout_c2import_view,
kwargs={
'message':message,
})
return HttpResponseRedirect(url)
url = reverse(workout_c2import_view,
kwargs={
'message':message,
})
return HttpResponseRedirect(url)
else:
message = "Received error code from Concept2"
if settings.DEBUG:
return HttpResponse(res)
else:
url = reverse(workout_c2import_view,
kwargs={
'message':message,
})
return HttpResponseRedirect(url)
# This is the main view for processing uploaded files
@login_required()
def workout_upload_view(request,message=""):
if request.method == 'POST':
form = DocumentsForm(request.POST,request.FILES)
optionsform = UploadOptionsForm(request.POST)
if form.is_valid():
f = request.FILES['file']
res = handle_uploaded_file(f)
t = form.cleaned_data['title']
workouttype = form.cleaned_data['workouttype']
notes = form.cleaned_data['notes']
make_plot = request.POST.getlist('make_plot')
plottype = request.POST['plottype']
upload_to_c2 = request.POST.getlist('upload_to_C2')
f1 = res[0] # file name
f2 = res[1] # file name incl media directory
# get file type (ErgData, NK, BoatCoach, etc
fileformat = get_file_type(f2)
if len(fileformat)==3 and fileformat[0]=='zip':
f_to_be_deleted = f2
with zipfile.ZipFile(f2) as z:
# for now, we're getting only the first file
# from the NK zip file (issue #69 on bitbucket)
f2 = z.extract(z.namelist()[0],path='media/')
fileformat = fileformat[2]
os.remove(f_to_be_deleted)
# Some people try to upload Concept2 logbook summaries
if fileformat == 'c2log':
message = "This C2 logbook summary does not contain stroke data. Please download the Export Stroke Data file from the workout details on the C2 logbook."
url = reverse(workout_upload_view,
args=[str(message)])
response = HttpResponseRedirect(url)
return response
# Some people try to upload RowPro summary logs
if fileformat == 'rowprolog':
message = "This RowPro logbook summary does not contain stroke data. Please use the Stroke Data CSV file for the individual workout in your log."
url = reverse(workout_upload_view,
args=[str(message)])
response = HttpResponseRedirect(url)
return response
# Sometimes people try an unsupported file type.
# Send an email to info@rowsandall.com with the file attached
# for me to check if it is a bug, or a new file type
# worth supporting
if fileformat == 'unknown':
message = "We couldn't recognize the file type"
url = reverse(workout_upload_view,
args=[str(message)])
response = HttpResponseRedirect(url)
if settings.DEBUG:
res = handle_sendemail_unrecognized.delay(f2,
request.user.email)
else:
res = queuehigh.enqueue(handle_sendemail_unrecognized,
f2,request.user.email)
return response
summary = ''
# handle non-Painsled by converting it to painsled
# compatible CSV
try:
if (fileformat != 'csv'):
# handle RowPro:
if (fileformat == 'rp'):
row = RowProParser(f2)
# handle TCX
if (fileformat == 'tcx'):
row = TCXParser(f2)
# handle Mystery
if (fileformat == 'mystery'):
row = MysteryParser(f2)
# handle RowPerfect
if (fileformat == 'rowperfect3'):
row = RowPerfectParser(f2)
# handle TCX no HR
if (fileformat == 'tcxnohr'):
row = TCXParserNoHR(f2)
# handle ErgData
if (fileformat == 'ergdata'):
row = ErgDataParser(f2)
# handle Mike
if (fileformat == 'bcmike'):
row = BoatCoachAdvancedParser(f2)
# handle BoatCoach
if (fileformat == 'boatcoach'):
row = BoatCoachParser(f2)
# handle painsled desktop
if (fileformat == 'painsleddesktop'):
row = painsledDesktopParser(f2)
# handle speed coach GPS
if (fileformat == 'speedcoach'):
row = speedcoachParser(f2)
# handle speed coach GPS 2
if (fileformat == 'speedcoach2'):
row = SpeedCoach2Parser(f2)
try:
summary = row.allstats()
except:
pass
# handle ErgStick
if (fileformat == 'ergstick'):
row = ErgStickParser(f2)
# handle FIT
if (fileformat == 'fit'):
row = FITParser(f2)
# The FIT files have nice lap/split summaries
# so we make use of it
s = fitsummarydata(f2)
s.setsummary()
summary = s.summarytext
# Save the Painsled compatible CSV file and delete
# the uploaded file
f_to_be_deleted = f2
# should delete file
f2 = f2[:-4]+'o.csv'
row.write_csv(f2,gzip=True)
try:
os.remove(f_to_be_deleted)
except:
os.remove(f_to_be_deleted+'.gz')
# make Workout object and put in database
r = Rower.objects.get(user=request.user)
powerperc = 100*np.array([r.pw_ut2,
r.pw_ut1,
r.pw_at,
r.pw_tr,r.pw_an])/r.ftp
print powerperc
rr = rrower(hrmax=r.max,hrut2=r.ut2,
hrut1=r.ut1,hrat=r.at,
hrtr=r.tr,hran=r.an,ftp=r.ftp,
powerperc=powerperc,powerzones=r.powerzones)
row = rdata(f2,rower=rr)
if row == 0:
return HttpResponse("Error: CSV Data File Not Found")
# auto smoothing
pace = row.df[' Stroke500mPace (sec/500m)'].values
velo = 500./pace
f = row.df['TimeStamp (sec)'].diff().mean()
windowsize = 2*(int(10./(f)))+1
if not 'originalvelo' in row.df:
row.df['originalvelo'] = velo
if windowsize > 3 and windowsize<len(velo):
velo2 = savgol_filter(velo,windowsize,3)
else:
velo2 = velo
velo3 = pd.Series(velo2)
velo3 = velo3.replace([-np.inf,np.inf],np.nan)
velo3 = velo3.fillna(method='ffill')
pace2 = 500./abs(velo3)
row.df[' Stroke500mPace (sec/500m)'] = pace2
row.df = row.df.fillna(0)
row.write_csv(f2,gzip=True)
try:
os.remove(f2)
except:
pass
# recalculate power data
if workouttype == 'rower' or workouttype == 'dynamic' or workouttype == 'slides':
try:
row.erg_recalculatepower()
row.write_csv(f2,gzip=True)
except:
pass
if fileformat != 'fit' and summary == '':
summary = row.summary()
summary += '\n'
summary += row.intervalstats_painsled()
averagehr = row.df[' HRCur (bpm)'].mean()
maxhr = row.df[' HRCur (bpm)'].max()
totaldist = row.df['cum_dist'].max()
totaltime = row.df['TimeStamp (sec)'].max()-row.df['TimeStamp (sec)'].min()
totaltime = totaltime+row.df.ix[0,' ElapsedTime (sec)']
hours = int(totaltime/3600.)
minutes = int((totaltime - 3600.*hours)/60.)
seconds = int(totaltime - 3600.*hours - 60.*minutes)
tenths = int(10*(totaltime - 3600.*hours - 60.*minutes - seconds))
duration = "%s:%s:%s.%s" % (hours,minutes,seconds,tenths)
workoutdate = row.rowdatetime.strftime('%Y-%m-%d')
workoutstarttime = row.rowdatetime.strftime('%H:%M:%S')
workoutstartdatetime = thetimezone.localize(row.rowdatetime).astimezone(utc)
# check for duplicate start times
r = Rower.objects.get(user=request.user)
ws = Workout.objects.filter(starttime=workoutstarttime,
user=r)
if (len(ws) != 0):
message = "Warning: This workout probably already exists in the database"
w = Workout(user=r,name=t,date=workoutdate,
workouttype=workouttype,
duration=duration,distance=totaldist,
weightcategory=r.weightcategory,
starttime=workoutstarttime,
csvfilename=f2,notes=notes,summary=summary,
maxhr=maxhr,averagehr=averagehr,
startdatetime=workoutstartdatetime)
w.save()
# put stroke data in database
res = dataprep.dataprep(row.df,id=w.id,
bands=True,barchart=True,
otwpower=True,empower=True)
# Make Plot
if (make_plot):
imagename = f1[:-4]+'.png'
fullpathimagename = 'static/plots/'+imagename
u = request.user
hrpwrdata = {
'hrmax':r.max,
'hrut2':r.ut2,
'hrut1':r.ut1,
'hrat':r.at,
'hrtr':r.tr,
'hran':r.an,
}
# make plot - asynchronous task
plotnrs = {
'timeplot':1,
'distanceplot':2,
'pieplot':3,
}
plotnr = plotnrs[plottype]
if (workouttype=='water'):
plotnr = plotnr+3
if settings.DEBUG:
res = handle_makeplot.delay(f1,f2,t,
hrpwrdata,plotnr,
imagename)
else:
res = queue.enqueue(handle_makeplot,f1,f2,
t,hrpwrdata,
plotnr,imagename)
i = GraphImage(workout=w,
creationdatetime=timezone.now(),
filename=fullpathimagename)
i.save()
# upload to C2
if (upload_to_c2):
try:
thetoken = c2_open(request.user)
except C2NoTokenError:
return HttpResponseRedirect("/rowers/me/c2authorize/")
try:
c2userid = c2stuff.get_userid(thetoken)
data = c2stuff.createc2workoutdata(w)
authorizationstring = str('Bearer ' + thetoken)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
import urllib
url = "httpvs://log.concept2.com/api/users/%s/results" % (c2userid)
response = requests.post(url,headers=headers,data=json.dumps(data))
# response = c2stuff.workout_c2_upload(request.user,w)
if (response.status_code != 201):
if settings.DEBUG:
return HttpResponse(response)
else:
message = "C2 upload failed"
url = reverse(workout_edit_view,
kwargs={
'message':message,
'id':str(w.id),
})
return HttpResponseRedirect(url)
else:
s= json.loads(response.text)
c2id = s['data']['id']
w.uploadedtoc2 = c2id
w.save()
except:
message = "C2 upload failed"
url = reverse(workout_edit_view,
kwargs={
'message':message,
'id':str(w.id),
})
return HttpResponseRedirect(url)
# redirect to workout edit page
url = "/rowers/workout/"+str(w.id)+"/edit"
return HttpResponseRedirect(url)
except:
if settings.DEBUG:
errorstring = str(sys.exc_info()[0])
print errorstring
message = "something went wrong (workout_upload_view)"
url = reverse(workout_upload_view,
args=[str(message)])
response = HttpResponseRedirect(url)
else:
response = render(request,
'document_form.html',
{'form':form,
'optionsform': optionsform,
'message':message})
return response
else:
form = DocumentsForm()
optionsform = UploadOptionsForm()
return render(request, 'document_form.html',
{'form':form,
'optionsform': optionsform,
'message':message})
# Ask the user if he really wants to delete the workout
@login_required()
def workout_delete_confirm_view(request, id=0):
try:
row = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,row)==False):
return HttpResponse("You are not allowed to delete this workout")
else:
return render(request,'workout_delete_confirm.html',
{'id':id,
'workout':row})
except Workout.DoesNotExist:
return HttpResponseNotFound("Workout doesn't exist")
# Really deleting the workout
@login_required()
def workout_delete_view(request,id=0):
try:
row = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,row)==False):
return HttpResponse("You are not allowed to delete this workout")
else:
# files are removed by pre-delete in models.py
row.delete()
url = reverse(workouts_view,kwargs={
'successmessage': "Workout deleted",
}
)
return HttpResponseRedirect(url)
except Workout.DoesNotExist:
return HttpResponseNotFound("Workout doesn't exist")
# Ask the user to confirm that he wants to delete a chart
@login_required()
def graph_delete_confirm_view(request, id=0):
try:
img = GraphImage.objects.get(id=id)
row = Workout.objects.get(id=img.workout.id)
if (checkworkoutuser(request.user,row)==False):
return HttpResponse("You are not allowed to delete this workout")
else:
return render(request,'graphimage_delete_confirm.html',
{'id':id,
'graph':img})
except Workout.DoesNotExist:
return HttpResponseNotFound("Workout doesn't exist")
# Really deleting the chart
@login_required()
def graph_delete_view(request,id=0):
try:
img = GraphImage.objects.get(id=id)
row = Workout.objects.get(id=img.workout.id)
if (checkworkoutuser(request.user,row)==False):
return HttpResponse("You are not allowed to delete this graph")
else:
img.delete()
url = reverse(workouts_view,kwargs={
'successmessage': "Graph deleted",
}
)
return HttpResponseRedirect(url)
except GraphImage.DoesNotExist:
return HttpResponse("Graph Image doesn't exist")
# A page with all the recent graphs (searchable on workout name)
@login_required()
def graphs_view(request):
try:
r = Rower.objects.get(user=request.user)
workouts = Workout.objects.filter(user=r).order_by("-date", "-starttime")
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))
)
g = GraphImage.objects.filter(workout__in=workouts).order_by("-creationdatetime")
if (len(g)<=5):
return render(request, 'list_graphs.html',
{'graphs1': g[0:4]})
else:
return render(request, 'list_graphs.html',
{'graphs1': g[0:5],
'graphs2': g[5:10]})
except Rower.DoesNotExist:
return HttpResponse("User has no rower instance")
# Show the chart (png image)
def graph_show_view(request,id):
try:
g = GraphImage.objects.get(id=id)
w = Workout.objects.get(id=g.workout.id)
r = Rower.objects.get(id=w.user.id)
return render(request,'show_graph.html',
{'graph':g,
'workout':w,
'rower':r,})
except GraphImage.DoesNotExist:
return HttpResponse("This graph doesn't exist")
# Restore original stroke data and summary
@login_required()
def workout_summary_restore_view(request,id,message="",successmessage=""):
try:
row = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,row)==False):
return HttpResponse("You are not allowed to edit this workout")
except Workout.DoesNotExist:
return HttpResponseNotFound("Workout doesn't exist")
s = ""
# still here - this is a workout we may edit
f1 = row.csvfilename
u = request.user
r = Rower.objects.get(user=u)
powerperc = 100*np.array([r.pw_ut2,
r.pw_ut1,
r.pw_at,
r.pw_tr,r.pw_an])/r.ftp
rr = rrower(hrmax=r.max,hrut2=r.ut2,
hrut1=r.ut1,hrat=r.at,
hrtr=r.tr,hran=r.an,ftp=r.ftp,
powerperc=powerperc,powerzones=r.powerzones)
rowdata = rdata(f1,rower=rr)
if rowdata == 0:
return HttpResponse("Error: CSV Data File Not Found")
rowdata.restoreintervaldata()
rowdata.write_csv(f1,gzip=True)
dataprep.update_strokedata(id,rowdata.df)
intervalstats = rowdata.allstats()
row.summary = intervalstats
row.save()
itime,idist,itype = rowdata.intervalstats_values()
nrintervals = len(idist)
# create interactive plot
try:
res = interactive_chart(id,promember=1)
script = res[0]
div = res[1]
except ValueError:
pass
url = reverse(workout_summary_edit_view,kwargs={
'id':id,
'successmessage': "Original Interval Data Restored",
}
)
return HttpResponseRedirect(url)
# Edit the splits/summary
@login_required()
def workout_summary_edit_view(request,id,message="",successmessage=""
):
try:
row = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,row)==False):
return HttpResponse("You are not allowed to edit this workout")
except Workout.DoesNotExist:
return HttpResponseNotFound("Workout doesn't exist")
s = ""
# still here - this is a workout we may edit
f1 = row.csvfilename
u = request.user
r = Rower.objects.get(user=u)
powerperc = 100*np.array([r.pw_ut2,
r.pw_ut1,
r.pw_at,
r.pw_tr,r.pw_an])/r.ftp
rr = rrower(hrmax=r.max,hrut2=r.ut2,
hrut1=r.ut1,hrat=r.at,
hrtr=r.tr,hran=r.an,ftp=r.ftp,
powerperc=powerperc,powerzones=r.powerzones)
rowdata = rdata(f1,rower=rr)
if rowdata == 0:
return HttpResponse("Error: CSV Data File Not Found")
intervalstats = rowdata.allstats()
itime,idist,itype = rowdata.intervalstats_values()
nrintervals = len(idist)
# create interactive plot
try:
res = interactive_chart(id,promember=1)
script = res[0]
div = res[1]
except ValueError:
pass
savebutton = 'nosavebutton'
# We have submitted the mini language interpreter
if request.method == 'POST' and "intervalstring" in request.POST:
form = SummaryStringForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
s = cd["intervalstring"]
rowdata.updateinterval_string(s)
intervalstats = rowdata.allstats()
itime,idist,itype = rowdata.intervalstats_values()
nrintervals = len(idist)
savebutton = 'savestringform'
# we are saving the results obtained from the mini language interpreter
elif request.method == 'POST' and "savestringform" in request.POST:
s = request.POST["savestringform"]
rowdata.updateinterval_string(s)
intervalstats = rowdata.allstats()
itime,idist,itype = rowdata.intervalstats_values()
nrintervals = len(idist)
row.summary = intervalstats
#intervalstats = rowdata.allstats()
row.notes += "\n"+s
row.save()
rowdata.write_csv(f1,gzip=True)
dataprep.update_strokedata(id,rowdata.df)
data = {'intervalstring':s}
form = SummaryStringForm(initial=data)
savebutton = 'savestringform'
# we are saving the results obtained from the detailed form
elif request.method == 'POST' and "savedetailform" in request.POST:
savebutton = 'savedetailform'
form = SummaryStringForm()
nrintervals = int(request.POST['nrintervals'])
detailform = IntervalUpdateForm(request.POST,aantal=nrintervals)
itime = []
idist = []
itype = []
ivalues = []
iunits = []
itypes = []
iresults = []
for i in range(nrintervals):
try:
t = datetime.datetime.strptime(request.POST['intervalt_%s' % i],"%H:%M:%S.%f")
except ValueError:
t = datetime.datetime.strptime(request.POST['intervalt_%s' % i],"%H:%M:%S")
timesecs = 3600*t.hour+60*t.minute+t.second+t.microsecond/1.e6
itime += [timesecs]
idist += [int(request.POST['intervald_%s' % i])]
itype += [int(request.POST['type_%s' % i])]
if itype[i] == 3: # rest
itypes += ['rest']
ivalues += [timesecs]
iresults += [idist[i]]
iunits += ['seconds']
if itype[i] == 5 or itype[i] == 2: # distance based work
itypes += ['work']
ivalues += [idist[i]]
iresults += [timesecs]
iunits += ['meters']
if itype[i] == 4 or itype[i] == 1: # time based work
itypes += ['work']
ivalues += [timesecs]
iresults += [idist[i]]
iunits += ['seconds']
rowdata.updateintervaldata(ivalues,iunits,itypes,iresults=iresults)
intervalstats = rowdata.allstats()
row.summary = intervalstats
row.notes += "\n"+s
row.save()
rowdata.write_csv(f1,gzip=True)
dataprep.update_strokedata(id,rowdata.df)
form = SummaryStringForm()
# we are processing the details form
elif request.method == 'POST' and "nrintervals" in request.POST:
savebutton = 'savedetailform'
nrintervals = int(request.POST['nrintervals'])
detailform = IntervalUpdateForm(request.POST,aantal=nrintervals)
if detailform.is_valid():
cd = detailform.cleaned_data
itime = []
idist = []
itype = []
ivalues = []
iunits = []
itypes = []
iresults = []
for i in range(nrintervals):
t = cd['intervalt_%s' % i]
timesecs = t.total_seconds()
itime += [timesecs]
idist += [cd['intervald_%s' % i]]
itype += [cd['type_%s' % i]]
if itype[i] == '3': # rest
itypes += ['rest']
ivalues += [timesecs]
iresults += [idist[i]]
iunits += ['seconds']
if itype[i] == '5' or itype[i] == '2': # distance based work
itypes += ['work']
ivalues += [idist[i]]
iresults += [timesecs]
iunits += ['meters']
if itype[i] == '4' or itype[i] == '1': # time based work
itypes += ['work']
ivalues += [timesecs]
iresults += [idist[i]]
iunits += ['seconds']
rowdata.updateintervaldata(ivalues,iunits,
itypes,iresults=iresults)
intervalstats = rowdata.allstats()
form = SummaryStringForm()
else:
form = SummaryStringForm()
initial = {}
for i in range(nrintervals):
initial['intervald_%s' % i] = idist[i]
initial['intervalt_%s' % i] = get_time(itime[i])
initial['type_%s' % i] = itype[i]
detailform = IntervalUpdateForm(aantal=nrintervals,initial=initial)
# render page
return render(request, 'summary_edit.html',
{'form':form,
'detailform':detailform,
'workout':row,
'intervalstats':intervalstats,
'nrintervals':nrintervals,
'message': message,
'successmessage': successmessage,
'interactiveplot':script,
'the_div':div,
'intervalstring':s,
'savebutton':savebutton,
})
# Page where user can manage his favorite charts
@user_passes_test(promember,login_url="/rowers/me/edit",redirect_field_name=None)
def rower_favoritecharts_view(request):
message = ''
successmessage = ''
r = Rower.objects.get(user=request.user)
favorites = FavoriteChart.objects.filter(user=r).order_by('id')
aantal = len(favorites)
favorites_data = [{'yparam1':f.yparam1,
'yparam2':f.yparam2,
'xparam':f.xparam,
'plottype':f.plottype,
'workouttype':f.workouttype,
'reststrokes':f.reststrokes}
for f in favorites]
FavoriteChartFormSet = formset_factory(FavoriteForm,formset=BaseFavoriteFormSet,extra=0)
if aantal==0:
FavoriteChartFormSet = formset_factory(FavoriteForm,formset=BaseFavoriteFormSet,extra=1)
if request.method == 'POST':
favorites_formset = FavoriteChartFormSet(request.POST)
if favorites_formset.is_valid():
new_instances = []
for favorites_form in favorites_formset:
yparam1 = favorites_form.cleaned_data.get('yparam1')
yparam2 = favorites_form.cleaned_data.get('yparam2')
xparam = favorites_form.cleaned_data.get('xparam')
plottype = favorites_form.cleaned_data.get('plottype')
workouttype = favorites_form.cleaned_data.get('workouttype')
reststrokes = favorites_form.cleaned_data.get('reststrokes')
new_instances.append(FavoriteChart(user=r,
yparam1=yparam1,
yparam2=yparam2,
xparam=xparam,
plottype=plottype,
workouttype=workouttype,
reststrokes=reststrokes))
try:
with transaction.atomic():
FavoriteChart.objects.filter(user=r).delete()
FavoriteChart.objects.bulk_create(new_instances)
successmessage = "You have updated your favorites"
if len(new_instances)==0:
FavoriteChartFormSet=formset_factory(FavoriteForm,formset=BaseFavoriteFormSet,extra=1)
favorites_formset = FavoriteChartFormSet()
except IntegrityError:
message = "something went wrong"
else:
favorites_formset = FavoriteChartFormSet(initial=favorites_data)
context = {
'favorites_formset':favorites_formset,
'message':message,
'successmessage':successmessage,
}
return render(request,'favoritecharts.html',context)
# Page where user can set his details
# Add email address to form so user can change his email address
@login_required()
def rower_edit_view(request,message=""):
r = Rower.objects.get(user=request.user)
if request.method == 'POST' and "ut2" in request.POST:
form = RowerForm(request.POST)
if form.is_valid():
# something
cd = form.cleaned_data
hrmax = cd['max']
ut2 = cd['ut2']
ut1 = cd['ut1']
at = cd['at']
tr = cd['tr']
an = cd['an']
rest = cd['rest']
weightcategory = cd['weightcategory']
try:
r = Rower.objects.get(user=request.user)
r.max = max(min(hrmax,250),10)
r.ut2 = max(min(ut2,250),10)
r.ut1 = max(min(ut1,250),10)
r.at = max(min(at,250),10)
r.tr = max(min(tr,250),10)
r.an = max(min(an,250),10)
r.rest = max(min(rest,250),10)
r.weightcategory = weightcategory
r.save()
successmessage = "Your Heart Rate data were changed"
form = RowerForm(instance=r)
powerform = RowerPowerForm(instance=r)
powerzonesform = RowerPowerZonesForm(instance=r)
return render(request, 'rower_form.html',
{'form':form,
'powerzonesform':powerzonesform,
'powerform':powerform,
'rower':r,
'successmessage':successmessage,
})
except Rower.DoesNotExist:
message = "Funny. This user doesn't exist."
url = reverse(workouts_view,args=[str(message)])
response = HttpResponseRedirect(url)
else:
message = HttpResponse("invalid form")
#form = RowerForm(instance=r)
powerform = RowerPowerForm(instance=r)
powerzonesform = RowerPowerZonesForm(instance=r)
return render(request, 'rower_form.html',
{'form':form,
'powerzonesform':powerzonesform,
'powerform':powerform,
'rower':r,
})
# url = reverse(rower_edit_view,args=[str(message)])
# response = HttpResponseRedirect(url)
return response
elif request.method == 'POST' and "ftp" in request.POST:
powerform = RowerPowerForm(request.POST)
if powerform.is_valid():
cd = powerform.cleaned_data
ftp = cd['ftp']
try:
r = Rower.objects.get(user=request.user)
powerfrac = 100*np.array([r.pw_ut2,
r.pw_ut1,
r.pw_at,
r.pw_tr,r.pw_an])/r.ftp
r.ftp = max(min(ftp,650),50)
ut2,ut1,at,tr,an = (r.ftp*powerfrac/100.).astype(int)
r.pw_ut2 = ut2
r.pw_ut1 = ut1
r.pw_at = at
r.pw_tr = tr
r.pw_an = an
r.save()
message = "Functional Threshold Value Changed"
url = reverse(rower_edit_view,args=[str(message)])
response = HttpResponseRedirect(url)
except Rower.DoesNotExist:
message = "Funny. This user doesn't exist."
url = reverse(rower_edit_view,args=[str(message)])
response = HttpResponseRedirect(url)
else:
message = HttpResponse("invalid form")
form = RowerForm(instance=r)
#powerform = RowerPowerForm(instance=r)
powerzonesform = RowerPowerZonesForm(instance=r)
return render(request, 'rower_form.html',
{'form':form,
'powerform':powerform,
'rower':r,
})
return response
elif request.method == 'POST' and "ut3name" in request.POST:
powerzonesform = RowerPowerZonesForm(request.POST)
if powerzonesform.is_valid():
cd = powerzonesform.cleaned_data
pw_ut2 = cd['pw_ut2']
pw_ut1 = cd['pw_ut1']
pw_at = cd['pw_at']
pw_tr = cd['pw_tr']
pw_an = cd['pw_an']
ut3name = cd['ut3name']
ut2name = cd['ut2name']
ut1name = cd['ut1name']
atname = cd['atname']
trname = cd['trname']
anname = cd['anname']
powerzones = [ut3name,ut2name,ut1name,atname,trname,anname]
try:
r = Rower.objects.get(user=request.user)
r.pw_ut2 = pw_ut2
r.pw_ut1 = pw_ut1
r.pw_at = pw_at
r.pw_tr = pw_tr
r.pw_an = pw_an
r.powerzones = powerzones
r.save()
successmessage = "Your Power Zone data were changed"
form = RowerForm(instance=r)
powerform = RowerPowerForm(instance=r)
powerzonesform = RowerPowerZonesForm(instance=r)
return render(request, 'rower_form.html',
{'form':form,
'powerzonesform':powerzonesform,
'powerform':powerform,
'rower':r,
'successmessage':successmessage,
})
except Rower.DoesNotExist:
message = "Funny. This user doesn't exist."
url = reverse(workouts_view,args=[str(message)])
response = HttpResponseRedirect(url)
return response
else:
form = RowerForm(instance=r)
powerform = RowerPowerForm(instance=r)
#powerzonesform = RowerPowerZonesForm(instance=r)
message = HttpResponse("invalid form")
return render(request, 'rower_form.html',
{'form':form,
'powerform':powerform,
'powerzonesform':powerzonesform,
'rower':r,
})
else:
try:
r = Rower.objects.get(user=request.user)
form = RowerForm(instance=r)
powerform = RowerPowerForm(instance=r)
powerzonesform = RowerPowerZonesForm(instance=r)
grants = AccessToken.objects.filter(user=request.user)
return render(request, 'rower_form.html',
{
'form':form,
'powerform':powerform,
'powerzonesform':powerzonesform,
'grants':grants,
'rower':r,
})
except Rower.DoesNotExist:
return HttpResponse("This user doesn't exist")
# Revoke an app that you granted access through the API.
# this views is called when you press a button on the User edit page
# the button is only there when you have granted access to an app
@login_required()
def rower_revokeapp_view(request,id=0):
tokens = AccessToken.objects.filter(user=request.user,application=id)
refreshtokens = AccessToken.objects.filter(user=request.user,application=id)
for token in tokens:
token.revoke()
for token in refreshtokens:
token.revoke()
r = Rower.objects.get(user=request.user)
form = RowerForm(instance=r)
powerform = RowerPowerForm(instance=r)
grants = AccessToken.objects.filter(user=request.user)
return render(request, 'rower_form.html',
{
'form':form,
'powerform':powerform,
'grants':grants,
})
# Utility to get stroke data in a JSON response
class JSONResponse(HttpResponse):
def __init__(self, data, **kwargs):
content = JSONRenderer().render(data)
kwargs['content_type'] = 'application/json'
super(JSONResponse, self).__init__(content, **kwargs)
# Creates unix time stamp from a datetime object
def totimestamp(dt, epoch=datetime.datetime(1970,1,1,tzinfo=tz('UTC'))):
td = dt - epoch
# return td.total_seconds()
return (td.microseconds + (td.seconds + td.days * 86400) * 10**6) / 10**6
# Check if a column of a dataframe has the required (aantal)
# number of elements. Also checks if the column is a numerical type
# Replaces any faulty columns with zeros
def trydf(df,aantal,column):
try:
s = df[column]
if len(s) != aantal:
return np.zeros(aantal)
if not np.issubdtype(s,np.number):
return np.zeros(aantal)
except KeyError:
s = np.zeros(aantal)
return s
# Stroke data form to test API upload
@login_required()
def strokedataform(request,id=0):
if request.method == 'GET':
form = StrokeDataForm()
return render(request, 'strokedata_form.html',
{
'form':form,
'id':id,
})
elif request.method == 'POST':
form = StrokeDataForm()
return render(request, 'strokedata_form.html',
{
'form':form,
'id':id,
})
# Process the POSTed stroke data according to the API definition
# Return the GET stroke data according to the API definition
from rest_framework_swagger.renderers import OpenAPIRenderer, SwaggerUIRenderer
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
@login_required()
@api_view(['GET','POST'])
def strokedatajson(request,id):
"""
POST: Add Stroke data to workout
GET: Get stroke data of workout
"""
try:
row = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,row)==False):
return HttpResponseForbidden("Permission error")
except Workout.DoesNotExist:
return HttpResponseNotFound("Workout doesn't exist")
try:
id = int(id)
except ValueError:
return HttpResponse("Not a valid workout number",status=400)
if request.method == 'GET':
# currently only returns a subset.
columns = ['spm','time','hr','pace','power','distance']
datadf = dataprep.getsmallrowdata_db(columns,ids=[id])
return JSONResponse(datadf)
if request.method == 'POST':
checkdata,r = dataprep.getrowdata_db(id=row.id)
if not checkdata.empty:
return HttpResponse("Duplicate Error",409)
# strokedata = request.POST['strokedata']
# checking/validating and cleaning
try:
strokedata = json.loads(request.POST['strokedata'])
except:
return HttpResponse("No JSON object could be decoded",400)
df = pd.DataFrame(strokedata)
df.index = df.index.astype(int)
df.sort_index(inplace=True)
# time, hr, pace, spm, power, drivelength, distance, drivespeed, dragfactor, strokerecoverytime, averagedriveforce, peakdriveforce, lapidx
time = df['time']/1.e3
aantal = len(time)
pace = df['pace']/1.e3
if len(pace) != aantal:
return HttpResponse("Pace array has incorrect length",status=400)
distance = df['distance']
if len(distance) != aantal:
return HttpResponse("Distance array has incorrect length",status=400)
spm = df['spm']
if len(spm) != aantal:
return HttpResponse("SPM array has incorrect length",status=400)
res = dataprep.testdata(time,distance,pace,spm)
if not res:
return HttpResponse("Data are not numerical",status=400)
power = trydf(df,aantal,'power')
drivelength = trydf(df,aantal,'drivelength')
drivespeed = trydf(df,aantal,'drivespeed')
dragfactor = trydf(df,aantal,'dragfactor')
drivetime = trydf(df,aantal,'drivetime')
strokerecoverytime = trydf(df,aantal,'strokerecoverytime')
averagedriveforce = trydf(df,aantal,'averagedriveforce')
peakdriveforce = trydf(df,aantal,'peakdriveforce')
lapidx = trydf(df,aantal,'lapidx')
hr = trydf(df,aantal,'hr')
starttime = totimestamp(row.startdatetime)+time
unixtime = starttime+time
data = pd.DataFrame({'TimeStamp (sec)':unixtime,
' Horizontal (meters)': distance,
' Cadence (stokes/min)':spm,
' HRCur (bpm)':hr,
' DragFactor':dragfactor,
' Stroke500mPace (sec/500m)':pace,
' Power (watts)':power,
' DriveLength (meters)':drivelength,
' DriveTime (ms)':drivetime,
' StrokeRecoveryTime (ms)':strokerecoverytime,
' AverageDriveForce (lbs)':averagedriveforce,
' PeakDriveForce (lbs)':peakdriveforce,
' lapIdx':lapidx,
' ElapsedTime (sec)':time,
})
timestr = row.startdatetime.strftime("%Y%m%d-%H%M%S")
csvfilename ='media/Import_'+timestr+'.csv'
res = data.to_csv(csvfilename+'.gz',index_label='index',
compression='gzip')
row.csvfilename = csvfilename
row.save()
r = Rower.objects.get(user=request.user)
powerperc = 100*np.array([r.pw_ut2,
r.pw_ut1,
r.pw_at,
r.pw_tr,r.pw_an])/r.ftp
rr = rrower(hrmax=r.max,hrut2=r.ut2,
hrut1=r.ut1,hrat=r.at,
hrtr=r.tr,hran=r.an,ftp=r.ftp,
powerperc=powerperc,powerzones=r.powerzones)
rowdata = rdata(row.csvfilename,rower=rr).df
datadf = dataprep.dataprep(rowdata,id=row.id,bands=True,barchart=True,otwpower=True,empower=True)
# mangling
#
return HttpResponse(row.id,status=201)
#Method not supported
return HttpResponseNotAllowed("Method not supported")