Merge branch 'develop' into feature/stravadevice
This commit is contained in:
@@ -4,6 +4,7 @@ from rowers.models import Workout, User, Rower,StrokeData
|
||||
from rowingdata import rowingdata as rrdata
|
||||
|
||||
from rowers.tasks import handle_sendemail_unrecognized
|
||||
from rowers.tasks import handle_zip_file
|
||||
|
||||
from rowingdata import rower as rrower
|
||||
from rowingdata import main as rmain
|
||||
@@ -44,7 +45,7 @@ import sys
|
||||
import django_rq
|
||||
queue = django_rq.get_queue('default')
|
||||
queuelow = django_rq.get_queue('low')
|
||||
queuehigh = django_rq.get_queue('low')
|
||||
queuehigh = django_rq.get_queue('default')
|
||||
|
||||
|
||||
user = settings.DATABASES['default']['USER']
|
||||
@@ -362,7 +363,10 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower',
|
||||
velo = 500./pace
|
||||
|
||||
f = row.df['TimeStamp (sec)'].diff().mean()
|
||||
windowsize = 2*(int(10./(f)))+1
|
||||
if f !=0:
|
||||
windowsize = 2*(int(10./(f)))+1
|
||||
else:
|
||||
windowsize = 1
|
||||
if not 'originalvelo' in row.df:
|
||||
row.df['originalvelo'] = velo
|
||||
|
||||
@@ -569,12 +573,21 @@ def new_workout_from_file(r,f2,
|
||||
inboard = 0.88
|
||||
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)
|
||||
title = os.path.basename(f2)
|
||||
if settings.DEBUG:
|
||||
res = handle_zip_file.delay(
|
||||
r.user.email,title,f2
|
||||
)
|
||||
|
||||
else:
|
||||
res = queuelow.enqueue(
|
||||
handle_zip_file,
|
||||
r.user.email,
|
||||
title,
|
||||
f2
|
||||
)
|
||||
|
||||
return -1,message,f2
|
||||
|
||||
# Some people try to upload Concept2 logbook summaries
|
||||
if fileformat == 'c2log':
|
||||
@@ -770,6 +783,8 @@ def rdata(file,rower=rrower()):
|
||||
res = rrdata(csvfile=file+'.gz',rower=rower)
|
||||
except IOError,IndexError:
|
||||
res = 0
|
||||
except:
|
||||
res = 0
|
||||
|
||||
return res
|
||||
|
||||
@@ -1047,7 +1062,10 @@ def dataprep(rowdatadf,id=0,bands=True,barchart=True,otwpower=True,
|
||||
rhythm = 0.0*forceratio
|
||||
|
||||
f = rowdatadf['TimeStamp (sec)'].diff().mean()
|
||||
windowsize = 2*(int(10./(f)))+1
|
||||
if f != 0:
|
||||
windowsize = 2*(int(10./(f)))+1
|
||||
else:
|
||||
windowsize = 1
|
||||
if windowsize <= 3:
|
||||
windowsize = 5
|
||||
|
||||
|
||||
@@ -91,6 +91,311 @@ def rdata(file,rower=rrower()):
|
||||
|
||||
return res
|
||||
|
||||
# Processes painsled CSV file to database
|
||||
def save_workout_database(f2,r,dosmooth=True,workouttype='rower',
|
||||
dosummary=True,title='Workout',
|
||||
notes='',totaldist=0,totaltime=0,
|
||||
summary='',
|
||||
makeprivate=False,
|
||||
oarlength=2.89,inboard=0.88):
|
||||
message = None
|
||||
powerperc = 100*np.array([r.pw_ut2,
|
||||
r.pw_ut1,
|
||||
r.pw_at,
|
||||
r.pw_tr,r.pw_an])/r.ftp
|
||||
|
||||
# make workout and put in database
|
||||
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 (0,'Error: CSV data file not found')
|
||||
|
||||
if dosmooth:
|
||||
# auto smoothing
|
||||
pace = row.df[' Stroke500mPace (sec/500m)'].values
|
||||
velo = 500./pace
|
||||
|
||||
f = row.df['TimeStamp (sec)'].diff().mean()
|
||||
if f !=0:
|
||||
windowsize = 2*(int(10./(f)))+1
|
||||
else:
|
||||
windowsize = 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
|
||||
|
||||
averagehr = row.df[' HRCur (bpm)'].mean()
|
||||
maxhr = row.df[' HRCur (bpm)'].max()
|
||||
|
||||
if totaldist == 0:
|
||||
totaldist = row.df['cum_dist'].max()
|
||||
if totaltime == 0:
|
||||
totaltime = row.df['TimeStamp (sec)'].max()-row.df['TimeStamp (sec)'].min()
|
||||
totaltime = totaltime+row.df.ix[0,' ElapsedTime (sec)']
|
||||
|
||||
|
||||
|
||||
hours = int(totaltime/3600.)
|
||||
if hours>23:
|
||||
message = 'Warning: The workout duration was longer than 23 hours. '
|
||||
hours = 23
|
||||
|
||||
minutes = int((totaltime - 3600.*hours)/60.)
|
||||
if minutes>59:
|
||||
minutes = 59
|
||||
if not message:
|
||||
message = 'Warning: there is something wrong with the workout duration'
|
||||
|
||||
seconds = int(totaltime - 3600.*hours - 60.*minutes)
|
||||
if seconds > 59:
|
||||
seconds = 59
|
||||
if not message:
|
||||
message = 'Warning: there is something wrong with the workout duration'
|
||||
|
||||
tenths = int(10*(totaltime - 3600.*hours - 60.*minutes - seconds))
|
||||
if tenths > 9:
|
||||
tenths = 9
|
||||
if not message:
|
||||
message = 'Warning: there is something wrong with the workout duration'
|
||||
|
||||
duration = "%s:%s:%s.%s" % (hours,minutes,seconds,tenths)
|
||||
|
||||
if dosummary:
|
||||
summary = row.summary()
|
||||
summary += '\n'
|
||||
summary += row.intervalstats()
|
||||
|
||||
workoutdate = row.rowdatetime.strftime('%Y-%m-%d')
|
||||
workoutstarttime = row.rowdatetime.strftime('%H:%M:%S')
|
||||
workoutstartdatetime = thetimezone.localize(row.rowdatetime).astimezone(utc)
|
||||
|
||||
if makeprivate:
|
||||
privacy = 'private'
|
||||
else:
|
||||
privacy = 'visible'
|
||||
|
||||
# check for duplicate start times
|
||||
ws = Workout.objects.filter(startdatetime=workoutstartdatetime,
|
||||
user=r)
|
||||
if (len(ws) != 0):
|
||||
message = "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=f2,notes=notes,summary=summary,
|
||||
maxhr=maxhr,averagehr=averagehr,
|
||||
startdatetime=workoutstartdatetime,
|
||||
inboard=inboard,oarlength=oarlength,
|
||||
privacy=privacy)
|
||||
|
||||
|
||||
w.save()
|
||||
|
||||
if privacy == 'visible':
|
||||
ts = Team.objects.filter(rower=r)
|
||||
for t in ts:
|
||||
w.team.add(t)
|
||||
|
||||
# put stroke data in database
|
||||
res = dataprep(row.df,id=w.id,bands=True,
|
||||
barchart=True,otwpower=True,empower=True,inboard=inboard)
|
||||
|
||||
return (w.id,message)
|
||||
|
||||
def handle_nonpainsled(f2,fileformat,summary=''):
|
||||
oarlength = 2.89
|
||||
inboard = 0.88
|
||||
# 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 TCX no HR
|
||||
if (fileformat == 'tcxnohr'):
|
||||
row = TCXParserNoHR(f2)
|
||||
|
||||
# handle RowPerfect
|
||||
if (fileformat == 'rowperfect3'):
|
||||
row = RowPerfectParser(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:
|
||||
oarlength,inboard = get_empower_rigging(f2)
|
||||
summary = row.allstats()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
# handle ErgStick
|
||||
if (fileformat == 'ergstick'):
|
||||
row = ErgStickParser(f2)
|
||||
|
||||
# handle FIT
|
||||
if (fileformat == 'fit'):
|
||||
row = FITParser(f2)
|
||||
s = fitsummarydata(f2)
|
||||
s.setsummary()
|
||||
summary = s.summarytext
|
||||
|
||||
|
||||
f_to_be_deleted = f2
|
||||
# should delete file
|
||||
f2 = f2[:-4]+'o.csv'
|
||||
row.write_csv(f2,gzip=True)
|
||||
|
||||
#os.remove(f2)
|
||||
try:
|
||||
os.remove(f_to_be_deleted)
|
||||
except:
|
||||
os.remove(f_to_be_deleted+'.gz')
|
||||
|
||||
return (f2,summary,oarlength,inboard)
|
||||
|
||||
# Create new workout from file and store it in the database
|
||||
# This routine should be used everywhere in views.py and mailprocessing.py
|
||||
# Currently there is code duplication
|
||||
def new_workout_from_file(r,f2,
|
||||
workouttype='rower',
|
||||
title='Workout',
|
||||
makeprivate=False,
|
||||
notes=''):
|
||||
message = None
|
||||
fileformat = get_file_type(f2)
|
||||
summary = ''
|
||||
oarlength = 2.89
|
||||
inboard = 0.88
|
||||
if len(fileformat)==3 and fileformat[0]=='zip':
|
||||
f_to_be_deleted = f2
|
||||
with zipfile.ZipFile(f2) as z:
|
||||
for fname in z.namelist():
|
||||
f3 = z.extract(fname,path='media/')
|
||||
id,message,f2 = new_workout_from_file(r,f3,
|
||||
workouttype=workouttype,
|
||||
makeprivate=makeprivate,
|
||||
title = title,
|
||||
notes='')
|
||||
os.remove(f_to_be_deleted)
|
||||
return id,message,f2
|
||||
|
||||
# Some people try to upload Concept2 logbook summaries
|
||||
if fileformat == 'c2log':
|
||||
os.remove(f2)
|
||||
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."
|
||||
return (0,message,f2)
|
||||
|
||||
if fileformat == 'nostrokes':
|
||||
os.remove(f2)
|
||||
message = "It looks like this file doesn't contain stroke data."
|
||||
return (0,message,f2)
|
||||
|
||||
# Some people try to upload RowPro summary logs
|
||||
if fileformat == 'rowprolog':
|
||||
os.remove(f2)
|
||||
message = "This RowPro logbook summary does not contain stroke data. Please use the Stroke Data CSV file for the individual workout in your log."
|
||||
return (0,message,f2)
|
||||
|
||||
# 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"
|
||||
if settings.DEBUG:
|
||||
res = handle_sendemail_unrecognized.delay(f2,
|
||||
r.user.email)
|
||||
|
||||
else:
|
||||
res = queuehigh.enqueue(handle_sendemail_unrecognized,
|
||||
f2,r.user.email)
|
||||
return (0,message,f2)
|
||||
|
||||
# handle non-Painsled by converting it to painsled compatible CSV
|
||||
if (fileformat != 'csv'):
|
||||
try:
|
||||
f2,summary,oarlength,inboard = handle_nonpainsled(f2,
|
||||
fileformat,
|
||||
summary=summary)
|
||||
except:
|
||||
errorstring = str(sys.exc_info()[0])
|
||||
message = 'Something went wrong: '+errorstring
|
||||
return (0,message,'')
|
||||
|
||||
|
||||
|
||||
dosummary = (fileformat != 'fit')
|
||||
id,message = save_workout_database(f2,r,
|
||||
workouttype=workouttype,
|
||||
makeprivate=makeprivate,
|
||||
dosummary=dosummary,
|
||||
summary=summary,
|
||||
inboard=inboard,oarlength=oarlength,
|
||||
title=title)
|
||||
|
||||
return (id,message,f2)
|
||||
|
||||
def delete_strokedata(id,debug=True):
|
||||
if debug:
|
||||
engine = create_engine(database_url_debug, echo=False)
|
||||
|
||||
@@ -90,6 +90,24 @@ class UploadOptionsForm(forms.Form):
|
||||
class Meta:
|
||||
fields = ['make_plot','plottype','upload_toc2','makeprivate']
|
||||
|
||||
# The form to indicate additional actions to be performed immediately
|
||||
# after a successful upload. This version allows the Team manager to select
|
||||
# a team member
|
||||
class TeamUploadOptionsForm(forms.Form):
|
||||
plotchoices = (
|
||||
('timeplot','Time Plot'),
|
||||
('distanceplot','Distance Plot'),
|
||||
('pieplot','Pie Chart'),
|
||||
)
|
||||
make_plot = forms.BooleanField(initial=False,required=False)
|
||||
plottype = forms.ChoiceField(required=False,
|
||||
choices=plotchoices,
|
||||
initial='timeplot',
|
||||
label='Plot Type')
|
||||
|
||||
class Meta:
|
||||
fields = ['make_plot','plottype']
|
||||
|
||||
# This form is used on the Analysis page to add a custom distance/time
|
||||
# trial and predict the pace
|
||||
class PredictedPieceForm(forms.Form):
|
||||
|
||||
@@ -63,12 +63,21 @@ class Command(BaseCommand):
|
||||
z = zipfile.ZipFile(a.document)
|
||||
for f in z.namelist():
|
||||
f2 = z.extract(f,path='media/')
|
||||
title = os.path.basename(f2)
|
||||
wid = [
|
||||
make_new_workout_from_email(rr,f2[6:],name)
|
||||
make_new_workout_from_email(rr,f2[6:],title)
|
||||
]
|
||||
res += wid
|
||||
link = 'http://rowsandall.com/rowers/workout/'+str(wid[0])+'/edit'
|
||||
dd = send_confirm(rr.user,name,link)
|
||||
try:
|
||||
dd = send_confirm(rr.user,title,link)
|
||||
time.sleep(10)
|
||||
except:
|
||||
try:
|
||||
time.sleep(10)
|
||||
dd = send_confirm(rr.user,title,link)
|
||||
except:
|
||||
pass
|
||||
|
||||
else:
|
||||
# move attachment and make workout
|
||||
@@ -87,6 +96,7 @@ class Command(BaseCommand):
|
||||
donotdelete = 1
|
||||
try:
|
||||
dd = send_confirm(rr.user,name,link)
|
||||
time.sleep(10)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@@ -195,6 +195,10 @@ class Rower(models.Model):
|
||||
sporttrackstokenexpirydate = models.DateTimeField(blank=True,null=True)
|
||||
sporttracksrefreshtoken = models.CharField(default='',max_length=200,
|
||||
blank=True,null=True)
|
||||
underarmourtoken = models.CharField(default='',max_length=200,blank=True,null=True)
|
||||
underarmourtokenexpirydate = models.DateTimeField(blank=True,null=True)
|
||||
underarmourrefreshtoken = models.CharField(default='',max_length=200,
|
||||
blank=True,null=True)
|
||||
stravatoken = models.CharField(default='',max_length=200,blank=True,null=True)
|
||||
|
||||
runkeepertoken = models.CharField(default='',max_length=200,
|
||||
@@ -360,6 +364,7 @@ class Workout(models.Model):
|
||||
maxhr = models.IntegerField(blank=True,null=True)
|
||||
uploadedtostrava = models.IntegerField(default=0)
|
||||
uploadedtosporttracks = models.IntegerField(default=0)
|
||||
uploadedtounderarmour = models.IntegerField(default=0)
|
||||
uploadedtorunkeeper = models.IntegerField(default=0)
|
||||
|
||||
# empower stuff
|
||||
|
||||
@@ -21,7 +21,7 @@ import stravalib
|
||||
from utils import serialize_list,deserialize_list
|
||||
|
||||
from rowers.dataprepnodjango import update_strokedata
|
||||
|
||||
from rowers.dataprepnodjango import new_workout_from_file
|
||||
|
||||
from django.core.mail import send_mail, BadHeaderError,EmailMessage
|
||||
|
||||
@@ -30,6 +30,16 @@ from django.core.mail import send_mail, BadHeaderError,EmailMessage
|
||||
def add(x, y):
|
||||
return x + y
|
||||
|
||||
# create workout
|
||||
@app.task
|
||||
def handle_new_workout_from_file(r,f2,
|
||||
workouttype='rower',
|
||||
title='Workout',
|
||||
makeprivate=False,
|
||||
notes=''):
|
||||
return new_workout_from_file(r,f2,workouttype,
|
||||
title,makeprivate,notes)
|
||||
|
||||
# send email to me when an unrecognized file is uploaded
|
||||
@app.task
|
||||
def handle_sendemail_unrecognized(unrecognizedfile,useremail):
|
||||
@@ -80,6 +90,17 @@ def handle_sendemailtcx(first_name,last_name,email,tcxfile):
|
||||
os.remove(tcxfile)
|
||||
return 1
|
||||
|
||||
@app.task
|
||||
def handle_zip_file(emailfrom,subject,file):
|
||||
message = "... zip processing ... "
|
||||
email = EmailMessage(subject,message,
|
||||
emailfrom,
|
||||
['workouts@rowsandall.com'])
|
||||
email.attach_file(file)
|
||||
res = email.send()
|
||||
time.sleep(60)
|
||||
return 1
|
||||
|
||||
# Send email with CSV attachment
|
||||
@app.task
|
||||
def handle_sendemailcsv(first_name,last_name,email,csvfile):
|
||||
|
||||
@@ -159,7 +159,7 @@ def create_request(team,user):
|
||||
if r2 in Rower.objects.filter(team=team):
|
||||
return (0,'Already a member of that team')
|
||||
|
||||
if count_club_members(team.manager)+count_invites(team.manager) < r.clubsize:
|
||||
if count_club_members(team.manager)+count_invites(team.manager) <= r.clubsize:
|
||||
codes = [i.code for i in TeamRequest.objects.all()]
|
||||
code = uuid.uuid4().hex[:10].upper()
|
||||
# prevent duplicates
|
||||
@@ -200,7 +200,7 @@ def create_invite(team,manager,user=None,email=''):
|
||||
except Rower.MultipleObjectsReturned:
|
||||
return (0,'There is more than one user with that email address')
|
||||
|
||||
if count_club_members(team.manager)+count_invites(team.manager) < r.clubsize:
|
||||
if count_club_members(team.manager)+count_invites(team.manager) <= r.clubsize:
|
||||
codes = [i.code for i in TeamInvite.objects.all()]
|
||||
code = uuid.uuid4().hex[:10].upper()
|
||||
# prevent duplicates
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "bases.html" %}
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load rowerfilters %}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "bases.html" %}
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load rowerfilters %}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "bases.html" %}
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load rowerfilters %}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "bases.html" %}
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load rowerfilters %}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{% load cookielaw_tags %}
|
||||
{% load analytical %}
|
||||
{% load rowerfilters %}
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -143,13 +144,17 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="grid_1 tooltip">
|
||||
{% if user.is_authenticated and teams %}
|
||||
{% if user.is_authenticated and user|has_teams %}
|
||||
<div class="grid_1 alpha dropdown">
|
||||
<button class="grid_1 alpha button gray small dropbtn">
|
||||
Teams
|
||||
Teams
|
||||
</button>
|
||||
<div class="dropdown-content">
|
||||
{% for t in teams %}
|
||||
<a class="button gray small" href="/rowers/me/teams/">Manage Teams</a>
|
||||
{% if user|is_manager %}
|
||||
<a class="button gray small" href="/rowers/workout/upload/team/">Upload Team Member Workout</a>
|
||||
{% endif %}
|
||||
{% for t in user|user_teams %}
|
||||
<a class="button gray small" href="/rowers/list-workouts/team/{{ t.id }}/">{{ t.name }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -214,7 +219,7 @@
|
||||
<p id="footer">
|
||||
<a href="/rowers/legal">Legal</a></p>
|
||||
</div>
|
||||
<div class="grid_2">
|
||||
<div class="grid_1">
|
||||
<p id="footer">
|
||||
<a href="/rowers/partners">Partners</a></p>
|
||||
</div>
|
||||
@@ -226,9 +231,9 @@
|
||||
<p id="footer">
|
||||
<a href="http://analytics.rowsandall.com/">Rowing Analytics BLOG</a></p>
|
||||
</div>
|
||||
<div class="grid_1 omega">
|
||||
<div class="grid_2 omega">
|
||||
<p id="footer">
|
||||
<a href="/rowers/email">Contact</a></p>
|
||||
<a href="https://www.facebook.com/groups/rowsandall/">Facebook group</a></p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{% load cookielaw_tags %}
|
||||
{% load analytical %}
|
||||
{% load rowerfilters %}
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -31,7 +32,6 @@
|
||||
<style>
|
||||
.splash {
|
||||
background-color: transparent;
|
||||
background-image: url("/static/img/landing1.jpg");
|
||||
-webkit-background-size: cover;
|
||||
-moz-background-size: cover;
|
||||
-o-background-size: cover;
|
||||
@@ -41,11 +41,13 @@
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.container_12 {background-color: rgba(255,255,255,0.7);}
|
||||
.container_12 {background-color: rgba(255,255,255,0.0);}
|
||||
.container_top {background-color: rgba(255,255,255,0.7);}
|
||||
</style>
|
||||
|
||||
<div class="splash">
|
||||
<div id="bgpic" class="splash">
|
||||
{% analytical_body_top %}
|
||||
<div class="container_top">
|
||||
<div class="container_12">
|
||||
<div class="grid_12">
|
||||
|
||||
@@ -161,13 +163,17 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="grid_1 tooltip">
|
||||
{% if user.is_authenticated and teams %}
|
||||
{% if user.is_authenticated and user|user_teams %}
|
||||
<div class="grid_1 alpha dropdown">
|
||||
<button class="grid_1 alpha button gray small dropbtn">
|
||||
Teams
|
||||
</button>
|
||||
<div class="dropdown-content">
|
||||
{% for t in teams %}
|
||||
<a class="button gray small" href="/rowers/me/teams/">Manage Teams</a>
|
||||
{% if user|is_manager %}
|
||||
<a class="button gray small" href="/rowers/workout/upload/team/">Upload Team Member Workout</a>
|
||||
{% endif %}
|
||||
{% for t in user|user_teams %}
|
||||
<a class="button gray small" href="/rowers/list-workouts/team/{{ t.id }}/">{{ t.name }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -232,7 +238,7 @@
|
||||
<p id="footer">
|
||||
<a href="/rowers/legal">Legal</a></p>
|
||||
</div>
|
||||
<div class="grid_2">
|
||||
<div class="grid_1">
|
||||
<p id="footer">
|
||||
<a href="/rowers/partners">Partners</a></p>
|
||||
</div>
|
||||
@@ -244,15 +250,27 @@
|
||||
<p id="footer">
|
||||
<a href="http://analytics.rowsandall.com/">Rowing Analytics BLOG</a></p>
|
||||
</div>
|
||||
<div class="grid_1 omega">
|
||||
<div class="grid_2 omega">
|
||||
<p id="footer">
|
||||
<a href="/rowers/email">Contact</a></p>
|
||||
<a href="https://www.facebook.com/groups/rowsandall/">Facebook group</a></p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% cookielaw_banner %}
|
||||
</div>
|
||||
</div>
|
||||
<!-- end container -->
|
||||
{% analytical_body_bottom %}
|
||||
<script type="text/javascript">
|
||||
var num = (Math.floor(Math.random()*4));
|
||||
|
||||
var array = ['one', 'two', 'three', 'four'];
|
||||
|
||||
|
||||
var elem = document.getElementById('bgpic');
|
||||
console.log(elem);
|
||||
elem.classList.add(array[num]);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load rowerfilters %}
|
||||
|
||||
{% block title %}File loading{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form enctype="multipart/form-data" action="{{ formloc }}" method="post">
|
||||
<div id="left" class="grid_6 alpha">
|
||||
<h1>Upload Workout File</h1>
|
||||
<h1>Upload Workout File</h1>
|
||||
{% if user.is_authenticated and user|is_manager %}
|
||||
<p>Looking for <a href="/rowers/workout/upload/team/">Team Manager
|
||||
Upload?</a></p>
|
||||
{% endif %}
|
||||
{% if form.errors %}
|
||||
<p style="color: red;">
|
||||
Please correct the error{{ form.errors|pluralize }} below.
|
||||
|
||||
@@ -68,6 +68,14 @@ Bug reports and feature requests can be done through our BitBucket page. Please
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<h1>Facebook Group</h1>
|
||||
|
||||
<p>We run a facebook group where you can post questions and report problems,
|
||||
especially if you think the wider user community benefits from the answers.</p>
|
||||
<ul>
|
||||
<li><a href="https://www.facebook.com/groups/rowsandall/">https://www.facebook.com/groups/rowsandall/</a></li>
|
||||
</ul>
|
||||
|
||||
<h1>Twitter</h1>
|
||||
|
||||
<p>You can also check me on Twitter:
|
||||
@@ -79,4 +87,4 @@ When the site is down, this is the appropriate channel to look for apologies, up
|
||||
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
|
||||
|
||||
@@ -111,6 +111,24 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if workout.uploadedtounderarmour == 0 %}
|
||||
{% if user.rower.underarmourtoken == None or user.rower.underarmourtoken == '' %}
|
||||
<div class="grid_1">
|
||||
<a href="/rowers/me/underarmourauthorize">
|
||||
<img src="/static/img/uagray.png" alt="Underarmour icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="grid_1">
|
||||
<a href="/rowers/workout/{{ workout.id }}/underarmouruploadw"><img src="/static/img/uasquare.png" alt="Underarmour icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="grid_1">
|
||||
<a href="https://www.mapmyfitness.com/workout/{{ workout.uploadedtounderarmour }}">
|
||||
<img src="/static/img/uachecked.png" alt="Underarmour icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -138,8 +156,11 @@
|
||||
|
||||
</div>
|
||||
<div class="grid_6">
|
||||
<div class="grid_2 alpha suffix_4">
|
||||
<p><a href="/rowers/me/runkeeperauthorize/"><img src="/static/img/rk-logo.png" alt="connect with RunKeeper" width="120"></a></p>
|
||||
<div class="grid_2 alpha">
|
||||
<p><a href="/rowers/me/runkeeperauthorize/"><img src="/static/img/rk-logo.png" alt="connect with Runkeeper" width="120"></a></p>
|
||||
</div>
|
||||
<div class="grid_2">
|
||||
<p><a href="/rowers/me/underarmourauthorize/"><img src="/static/img/UAbtn.png" alt="connect with Under Armour" width="120"></a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -43,12 +43,22 @@
|
||||
<p>Import workouts from RunKeeper</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid_6">
|
||||
<div class="grid_3 alpha">
|
||||
<p>
|
||||
<a href="/rowers/workout/underarmourimport"><img src="/static/img/UAbtn.png" alt="Under Armour logo" width="140"></a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid_3 omega">
|
||||
<p>Import workouts from MapMyFitness/UnderArmour</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid_6 omega">
|
||||
<h3>Connect</h3>
|
||||
|
||||
<div class="grid_6">
|
||||
<div class="grid_6 alpha">
|
||||
<p>Click one of the below logos to connect to the service of your choice.
|
||||
You only need to do this once. After that, the site will have access until you
|
||||
revoke the authorization for the "rowingdata" app.</p>
|
||||
@@ -67,10 +77,13 @@
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="grid_6">
|
||||
<div class="grid_2 alpha suffix_4">
|
||||
<div class="grid_6 alpha">
|
||||
<div class="grid_2 alpha">
|
||||
<p><a href="/rowers/me/runkeeperauthorize/"><img src="/static/img/rk-logo.png" alt="connect with RunKeeper" width="120"></a></p>
|
||||
</div>
|
||||
<div class="grid_2 suffix_2 omega">
|
||||
<p><a href="/rowers/me/underarmourauthorize/"><img src="/static/img/UAbtn.png" alt="connect with Under Armour" width="120"></a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -35,7 +35,9 @@
|
||||
|
||||
<div id="workouts_table" class="grid_8 alpha">
|
||||
{% if team %}
|
||||
{% include "teambuttons.html" with teamid=team.id %}
|
||||
<div class="grid_8 alpha">
|
||||
{% include "teambuttons.html" with teamid=team.id %}
|
||||
</div>
|
||||
<h3>{{ team.name }} Team Workouts</h3>
|
||||
{% else %}
|
||||
<h3>My Workouts</h3>
|
||||
|
||||
55
rowers/templates/team_document_form.html
Normal file
55
rowers/templates/team_document_form.html
Normal file
@@ -0,0 +1,55 @@
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}File loading{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form enctype="multipart/form-data" action="{{ formloc }}" method="post">
|
||||
<div id="left" class="grid_6 alpha">
|
||||
<h1>Upload Workout File</h1>
|
||||
{% if form.errors %}
|
||||
<p style="color: red;">
|
||||
Please correct the error{{ form.errors|pluralize }} below.
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<table>
|
||||
{{ rowerform.as_table }}
|
||||
{{ form.as_table }}
|
||||
</table>
|
||||
{% csrf_token %}
|
||||
<div id="formbutton" class="grid_1 prefix_4 suffix_1">
|
||||
<input class="button green" type="submit" value="Submit">
|
||||
</div>
|
||||
</div>
|
||||
<div id="right" class="grid_6 omega">
|
||||
<h1>Optional extra actions</h1>
|
||||
<p>
|
||||
<table>
|
||||
{{ optionsform.as_table }}
|
||||
</table>
|
||||
</p>
|
||||
<p>
|
||||
You can select one static plot to be generated immediately for this workout. You can select to upload to Concept2 automatically. If you check "make private", this workout will not be visible to your followers and will not show up in your teams' workouts list.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Valid file types are:
|
||||
<ul>
|
||||
<li>Painsled iOS Stroke Export (CSV)</li>
|
||||
<li>Painsled desktop version Stroke Export (CSV)</li>
|
||||
<li>A TCX file with location data (lat,long) - with or without Heart Rate value, for example from RiM or CrewNerd</li>
|
||||
<li>RowPro CSV export</li>
|
||||
<li>SpeedCoach GPS and SpeedCoach GPS 2 CSV export</li>
|
||||
<li>ErgData CSV export</li>
|
||||
<li>ErgStick CSV export</li>
|
||||
<li>BoatCoach CSV export</li>
|
||||
<li>A FIT file with location data (experimental)</li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -1,3 +1,4 @@
|
||||
{% load rowerfilters %}
|
||||
<div class="grid_2 alpha">
|
||||
<p>
|
||||
<a class="button gray small" href="/rowers/list-workouts/team/{{ teamid }}/">Team Workouts</a>
|
||||
@@ -8,8 +9,17 @@
|
||||
<a class="button gray small" href="/rowers/team-compare-select/team/{{ teamid }}/">Multi Compare</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid_2 suffix_2 omega">
|
||||
<div class="grid_2">
|
||||
<p>
|
||||
<a class="button gray small" href="/rowers/team/{{ teamid }}/">Team Page</a>
|
||||
</p>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid_2 omega">
|
||||
{% if user|is_manager and user|has_teams %}
|
||||
<p>
|
||||
<a class="button gray small" href="/rowers/workout/upload/team/">Upload Workout</a>
|
||||
</p>
|
||||
{% else %}
|
||||
<p> </p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -36,10 +36,76 @@
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="grid_6 omega">
|
||||
{% if otherteams %}
|
||||
<h2>Other Teams</h2>
|
||||
<table width="70%" class="listtable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Manager</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for team in otherteams %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/rowers/team/{{ team.id }}/">{{ team.name }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ team.manager.first_name }} {{ team.manager.last_name }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% else %}
|
||||
<p> </p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid_12 alpha">
|
||||
<div class="grid_6 alpha">
|
||||
{% if user.rower.rowerplan == 'coach' %}
|
||||
<h2>Teams I manage</h2>
|
||||
<p>Number of members: {{ clubsize }}</p>
|
||||
<p>Maximum club size: {{ max_clubsize }}</p>
|
||||
{% if myteams %}
|
||||
<table width="70%" class="listtable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Manager</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for team in myteams %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/rowers/team/{{ team.id }}/">{{ team.name }}</a>
|
||||
</td>
|
||||
<td>
|
||||
<div class="grid_1">
|
||||
<a class="button small red" href="/rowers/team/{{ team.id }}/deleteconfirm">Delete</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
<div class="grid_2 suffix_4 alpha">
|
||||
<a class="button green" href="/rowers/team/create">New Team</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<p> </p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="grid_6 omega">
|
||||
{% if invites or requests or myrequests or myinvites %}
|
||||
<p>
|
||||
<h2>Invitations and Requests</h2>
|
||||
<table width="90%" class="listtable">
|
||||
<thead>
|
||||
@@ -126,70 +192,5 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid_12 alpha">
|
||||
<div class="grid_6 alpha">
|
||||
{% if user.rower.rowerplan == 'coach' %}
|
||||
<h2>Teams I manage</h2>
|
||||
{% if myteams %}
|
||||
<table width="70%" class="listtable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Manager</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for team in myteams %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/rowers/team/{{ team.id }}/">{{ team.name }}</a>
|
||||
</td>
|
||||
<td>
|
||||
<div class="grid_1">
|
||||
<a class="button small red" href="/rowers/team/{{ team.id }}/deleteconfirm">Delete</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
<div class="grid_2 suffix_4 alpha">
|
||||
<a class="button green" href="/rowers/team/create">New Team</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<p> </p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="grid_6 omega">
|
||||
{% if otherteams %}
|
||||
<h2>Other Teams</h2>
|
||||
<table width="70%" class="listtable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Manager</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for team in otherteams %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/rowers/team/{{ team.id }}/">{{ team.name }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ team.manager.first_name }} {{ team.manager.last_name }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% else %}
|
||||
<p> </p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
37
rowers/templates/underarmour_list_import.html
Normal file
37
rowers/templates/underarmour_list_import.html
Normal file
@@ -0,0 +1,37 @@
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load rowerfilters %}
|
||||
|
||||
{% block title %}Workouts{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Available on MapMyFitness (UnderArmour)</h1>
|
||||
{% if workouts %}
|
||||
<table width="70%" class="listtable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th> Import </th>
|
||||
<th> Date/Time </th>
|
||||
<th> Duration </th>
|
||||
<th> Total Distance</th>
|
||||
<th> Type</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for workout in workouts %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/rowers/workout/underarmourimport/{{ workout|ualookup:'id' }}/">Import</a></td>
|
||||
<td>{{ workout|ualookup:'starttime' }}</td>
|
||||
<td>{{ workout|ualookup:'duration' }} </td>
|
||||
<td>{{ workout|ualookup:'distance' }} m</td>
|
||||
<td>{{ workout|ualookup:'type' }}</td>
|
||||
</tr>
|
||||
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p> No workouts found. We only list workouts with time data series. </p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -1,5 +1,6 @@
|
||||
from django import template
|
||||
from time import strftime
|
||||
import dateutil.parser
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@@ -27,6 +28,17 @@ def strfdeltah(tdelta):
|
||||
|
||||
return res
|
||||
|
||||
def secondstotimestring(tdelta):
|
||||
hours, rest = divmod(tdelta,3600)
|
||||
minutes,seconds = divmod(rest,60)
|
||||
res = "{hours:0>2}:{minutes:0>2}:{seconds:0>2}".format(
|
||||
hours=hours,
|
||||
minutes=minutes,
|
||||
seconds=seconds,
|
||||
)
|
||||
|
||||
return res
|
||||
|
||||
@register.filter
|
||||
def durationprint(d,dstring):
|
||||
if (d == None):
|
||||
@@ -57,6 +69,22 @@ def lookup(dict, key):
|
||||
s = s[:22]
|
||||
return s
|
||||
|
||||
@register.filter
|
||||
def ualookup(dict, key):
|
||||
s = dict.get(key)
|
||||
|
||||
if key=='distance':
|
||||
s = int(float(s))
|
||||
|
||||
if key=='duration':
|
||||
s = secondstotimestring(int(s))
|
||||
|
||||
|
||||
if key=='starttime':
|
||||
s = dateutil.parser.parse(s)
|
||||
|
||||
return s
|
||||
|
||||
@register.filter(name='times')
|
||||
def times(number):
|
||||
return range(number)
|
||||
@@ -65,3 +93,35 @@ def times(number):
|
||||
def get_field_id(id,s,form):
|
||||
field_name = s+str(id)
|
||||
return form.__getitem__(field_name)
|
||||
|
||||
from rowers.models import Rower,Team
|
||||
|
||||
@register.filter
|
||||
def is_manager(user):
|
||||
r = Rower.objects.get(user=user)
|
||||
return r.rowerplan == 'coach'
|
||||
|
||||
@register.filter
|
||||
def user_teams(user):
|
||||
try:
|
||||
therower = Rower.objects.get(user=user)
|
||||
teams1 = therower.team.all()
|
||||
teams2 = Team.objects.filter(manager=user)
|
||||
teams = list(set(teams1).union(set(teams2)))
|
||||
except TypeError:
|
||||
teams = []
|
||||
|
||||
return teams
|
||||
|
||||
@register.filter
|
||||
def has_teams(user):
|
||||
try:
|
||||
therower = Rower.objects.get(user=user)
|
||||
teams1 = therower.team.all()
|
||||
teams2 = Team.objects.filter(manager=user)
|
||||
teams = list(set(teams1).union(set(teams2)))
|
||||
return True
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
@@ -702,7 +702,7 @@ class ViewTest(TestCase):
|
||||
|
||||
f.close()
|
||||
|
||||
self.assertRedirects(response, expected_url='/rowers/workout/upload/This%20C2%20logbook%20summary%20does%20not%20contain%20stroke%20data.%20Please%20download%20the%20Export%20Stroke%20Data%20file%20from%20the%20workout%20details%20on%20the%20C2%20logbook.',
|
||||
self.assertRedirects(response, expected_url='/rowers/workout/upload/c/This%20C2%20logbook%20summary%20does%20not%20contain%20stroke%20data.%20Please%20download%20the%20Export%20Stroke%20Data%20file%20from%20the%20workout%20details%20on%20the%20C2%20logbook.',
|
||||
status_code=302,target_status_code=200)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
385
rowers/underarmourstuff.py
Normal file
385
rowers/underarmourstuff.py
Normal file
@@ -0,0 +1,385 @@
|
||||
# All the functionality needed to connect to Runkeeper
|
||||
|
||||
# Python
|
||||
import oauth2 as oauth
|
||||
import cgi
|
||||
import requests
|
||||
import requests.auth
|
||||
import json
|
||||
from django.utils import timezone
|
||||
from datetime import datetime
|
||||
import numpy as np
|
||||
from dateutil import parser
|
||||
import time
|
||||
import math
|
||||
from math import sin,cos,atan2,sqrt
|
||||
import os,sys
|
||||
import urllib
|
||||
|
||||
# Django
|
||||
from django.shortcuts import render_to_response
|
||||
from django.http import HttpResponseRedirect, HttpResponse,JsonResponse
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import authenticate, login, logout
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
# Project
|
||||
# from .models import Profile
|
||||
from rowingdata import rowingdata
|
||||
import pandas as pd
|
||||
from rowers.models import Rower,Workout
|
||||
|
||||
from rowsandall_app.settings import (
|
||||
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
|
||||
STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET,
|
||||
UNDERARMOUR_CLIENT_ID, UNDERARMOUR_CLIENT_SECRET,
|
||||
UNDERARMOUR_REDIRECT_URI,UNDERARMOUR_CLIENT_KEY,
|
||||
)
|
||||
|
||||
# Custom error class - to raise a NoTokenError
|
||||
class UnderArmourNoTokenError(Exception):
|
||||
def __init__(self,value):
|
||||
self.value=value
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.value)
|
||||
|
||||
# Exponentially weighted moving average
|
||||
# Used for data smoothing of the jagged data obtained by Strava
|
||||
# See bitbucket issue 72
|
||||
def ewmovingaverage(interval,window_size):
|
||||
# Experimental code using Exponential Weighted moving average
|
||||
|
||||
try:
|
||||
intervaldf = pd.DataFrame({'v':interval})
|
||||
idf_ewma1 = intervaldf.ewm(span=window_size)
|
||||
idf_ewma2 = intervaldf[::-1].ewm(span=window_size)
|
||||
|
||||
i_ewma1 = idf_ewma1.mean().ix[:,'v']
|
||||
i_ewma2 = idf_ewma2.mean().ix[:,'v']
|
||||
|
||||
interval2 = np.vstack((i_ewma1,i_ewma2[::-1]))
|
||||
interval2 = np.mean( interval2, axis=0) # average
|
||||
except ValueError:
|
||||
interval2 = interval
|
||||
|
||||
return interval2
|
||||
|
||||
from utils import geo_distance
|
||||
|
||||
|
||||
# Custom exception handler, returns a 401 HTTP message
|
||||
# with exception details in the json data
|
||||
def custom_exception_handler(exc,message):
|
||||
|
||||
response = {
|
||||
"errors": [
|
||||
{
|
||||
"code": str(exc),
|
||||
"detail": message,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
res = HttpResponse(message)
|
||||
res.status_code = 401
|
||||
res.json = json.dumps(response)
|
||||
|
||||
return res
|
||||
|
||||
# Refresh ST token using refresh token
|
||||
def do_refresh_token(refreshtoken,access_token):
|
||||
client_auth = requests.auth.HTTPBasicAuth(UNDERARMOUR_CLIENT_KEY, UNDERARMOUR_CLIENT_SECRET)
|
||||
post_data = {"grant_type": "refresh_token",
|
||||
"client_secret": UNDERARMOUR_CLIENT_SECRET,
|
||||
"client_id":UNDERARMOUR_CLIENT_KEY,
|
||||
"refresh_token": refreshtoken,
|
||||
}
|
||||
headers = {'user-agent': 'sanderroosendaal',
|
||||
"Api-Key":UNDERARMOUR_CLIENT_KEY,
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'authorization': 'Bearer %s' % access_token}
|
||||
|
||||
url = "https://api.ua.com/v7.1/oauth2/access_token/"
|
||||
|
||||
response = requests.post(url,
|
||||
data=post_data,
|
||||
headers=headers)
|
||||
|
||||
token_json = response.json()
|
||||
thetoken = token_json['access_token']
|
||||
expires_in = token_json['expires_in']
|
||||
try:
|
||||
refresh_token = token_json['refresh_token']
|
||||
except KeyError:
|
||||
refresh_token = refreshtoken
|
||||
|
||||
return [thetoken,expires_in,refresh_token]
|
||||
|
||||
# Exchange access code for long-lived access token
|
||||
def get_token(code):
|
||||
client_auth = requests.auth.HTTPBasicAuth(UNDERARMOUR_CLIENT_KEY, UNDERARMOUR_CLIENT_SECRET)
|
||||
post_data = {
|
||||
"grant_type": "authorization_code",
|
||||
"code": code,
|
||||
"client_secret": UNDERARMOUR_CLIENT_SECRET,
|
||||
"client_id":UNDERARMOUR_CLIENT_KEY,
|
||||
}
|
||||
headers = {
|
||||
'user-agent': 'sanderroosendaal',
|
||||
"Api-Key":UNDERARMOUR_CLIENT_KEY,
|
||||
}
|
||||
|
||||
response = requests.post("https://api.ua.com/v7.1/oauth2/access_token/",
|
||||
data=post_data,
|
||||
headers=headers)
|
||||
try:
|
||||
token_json = response.json()
|
||||
thetoken = token_json['access_token']
|
||||
expires_in = token_json['expires_in']
|
||||
refresh_token = token_json['refresh_token']
|
||||
except KeyError:
|
||||
thetoken = 0
|
||||
|
||||
return thetoken,expires_in,refresh_token
|
||||
|
||||
# Make authorization URL including random string
|
||||
def make_authorization_url(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": UNDERARMOUR_CLIENT_KEY,
|
||||
"response_type": "code",
|
||||
"redirect_uri": UNDERARMOUR_REDIRECT_URI,
|
||||
}
|
||||
url = "https://www.mapmyfitness.com/v7.1/oauth2/uacf/authorize/" +urllib.urlencode(params)
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
# Get list of workouts available on Underarmour
|
||||
def get_underarmour_workout_list(user):
|
||||
r = Rower.objects.get(user=user)
|
||||
if (r.underarmourtoken == '') or (r.underarmourtoken is None):
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
return custom_exception_handler(401,s)
|
||||
else:
|
||||
# ready to fetch. Hurray
|
||||
authorizationstring = str('Bearer ' + r.underarmourtoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'Api-Key': UNDERARMOUR_CLIENT_KEY,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
url = "https://api.ua.com/v7.1/workout/?user="+str(get_userid(r.underarmourtoken))
|
||||
|
||||
s = requests.get(url,headers=headers)
|
||||
|
||||
return s
|
||||
|
||||
# Get workout summary data by Underarmour ID
|
||||
def get_underarmour_workout(user,underarmourid):
|
||||
r = Rower.objects.get(user=user)
|
||||
if (r.underarmourtoken == '') or (r.underarmourtoken is None):
|
||||
return custom_exception_handler(401,s)
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
else:
|
||||
# ready to fetch. Hurray
|
||||
authorizationstring = str('Bearer ' + r.underarmourtoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'Api-Key': UNDERARMOUR_CLIENT_KEY,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
url = "https://api.ua.com/v7.1/workout/"+str(underarmourid)+"/?field_set=time_series"
|
||||
s = requests.get(url,headers=headers)
|
||||
|
||||
return s
|
||||
|
||||
# Create Workout Data for upload to Underarmour
|
||||
def createunderarmourworkoutdata(w):
|
||||
filename = w.csvfilename
|
||||
try:
|
||||
row = rowingdata(filename)
|
||||
except:
|
||||
return 0
|
||||
|
||||
averagehr = int(row.df[' HRCur (bpm)'].mean())
|
||||
maxhr = int(row.df[' HRCur (bpm)'].max())
|
||||
duration = w.duration.hour*3600
|
||||
duration += w.duration.minute*60
|
||||
duration += w.duration.second
|
||||
duration += +1.0e-6*w.duration.microsecond
|
||||
name = w.name
|
||||
notes = w.notes
|
||||
|
||||
# adding diff, trying to see if this is valid
|
||||
#t = row.df.ix[:,'TimeStamp (sec)'].values-10*row.df.ix[0,'TimeStamp (sec)']
|
||||
t = row.df.ix[:,'TimeStamp (sec)'].values-row.df.ix[0,'TimeStamp (sec)']
|
||||
t[0] = t[1]
|
||||
|
||||
d = row.df.ix[:,'cum_dist'].values
|
||||
d[0] = d[1]
|
||||
t = t.astype(int)
|
||||
d = d.astype(int)
|
||||
spm = row.df[' Cadence (stokes/min)'].astype(int)
|
||||
spm[0] = spm[1]
|
||||
hr = row.df[' HRCur (bpm)'].astype(int)
|
||||
|
||||
haslatlon=1
|
||||
|
||||
try:
|
||||
lat = row.df[' latitude'].values
|
||||
lon = row.df[' longitude'].values
|
||||
if not lat.std() and not lon.std():
|
||||
haslatlon = 0
|
||||
except KeyError:
|
||||
haslatlon = 0
|
||||
|
||||
# path data
|
||||
if haslatlon:
|
||||
locdata = []
|
||||
for e in zip(t,lat,lon):
|
||||
point = {
|
||||
'lat':e[1],
|
||||
'lng':e[2],
|
||||
'elevation':0,
|
||||
}
|
||||
locdata.append([e[0],point])
|
||||
|
||||
hrdata = []
|
||||
for e in zip(t,hr):
|
||||
point = [e[0],
|
||||
e[1]
|
||||
]
|
||||
hrdata.append(point)
|
||||
|
||||
distancedata = []
|
||||
for e in zip(t,d):
|
||||
point = [e[0],
|
||||
e[1]
|
||||
]
|
||||
distancedata.append(point)
|
||||
|
||||
spmdata = []
|
||||
for e in zip(t,spm):
|
||||
spmdata.append([e[0],e[1]])
|
||||
|
||||
start_time = w.startdatetime.isoformat()
|
||||
|
||||
timeseries = {
|
||||
"distance": distancedata,
|
||||
"heartrate": hrdata,
|
||||
"cadence": spmdata,
|
||||
}
|
||||
|
||||
aggregrates = {
|
||||
"elapsed_time_total": int(duration),
|
||||
"distance_total": int(max(d)),
|
||||
"heartrate_avg": averagehr,
|
||||
"heart_rate_min": int(min(hr)),
|
||||
"heart_rate_max": int(max(hr)),
|
||||
}
|
||||
|
||||
|
||||
# if haslatlon:
|
||||
# timeseries["position"] = locdata
|
||||
|
||||
data = {
|
||||
"name": name,
|
||||
"start_datetime": start_time,
|
||||
"time_series": timeseries,
|
||||
"start_locale_timezone": "Etc/UTC",
|
||||
"activity_type": "/v7.1/activity_type/128/",
|
||||
"notes": notes,
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
# Obtain Underarmour Workout ID and activity type
|
||||
def get_idfromuri(user,links):
|
||||
id = links['self'][0]['id']
|
||||
typeid = links['activity_type'][0]['id']
|
||||
|
||||
|
||||
typename = get_typefromid(typeid,user)
|
||||
|
||||
return id,typename
|
||||
|
||||
def getidfromresponse(response):
|
||||
t = json.loads(response.text)
|
||||
|
||||
links = t["_links"]
|
||||
|
||||
id = links["self"][0]["id"]
|
||||
|
||||
return int(id)
|
||||
|
||||
def refresh_ua_actlist(user):
|
||||
r = Rower.objects.get(user=user)
|
||||
authorizationstring = str('Bearer ' + r.underarmourtoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'Api-Key': UNDERARMOUR_CLIENT_KEY,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
url = "https://api.ua.com/v7.1/activity_type/"
|
||||
response = requests.get(url,headers=headers)
|
||||
|
||||
me_json = response.json()
|
||||
types = me_json["_embedded"]["activity_types"]
|
||||
w = {int(t["_links"]["self"][0]["id"]):t["name"] for t in types}
|
||||
wdf = pd.Series(w,name='Name')
|
||||
wdf.to_csv('static/rigging/ua2.csv',index_label='id',header=True)
|
||||
return w
|
||||
|
||||
try:
|
||||
activities = pd.read_csv('static/rigging/ua2.csv',index_col='id')
|
||||
actdict = activities.to_dict()['Name']
|
||||
except:
|
||||
actdict = {}
|
||||
|
||||
|
||||
def get_typefromid(typeid,user):
|
||||
r = Rower.objects.get(user=user)
|
||||
try:
|
||||
res = actdict[int(typeid)]
|
||||
except KeyError:
|
||||
authorizationstring = str('Bearer ' + r.underarmourtoken)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'Api-Key': UNDERARMOUR_CLIENT_KEY,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
url = "https://api.ua.com/v7.1/activity_type/"+str(typeid)
|
||||
response = requests.get(url,headers=headers)
|
||||
|
||||
me_json = response.json()
|
||||
|
||||
try:
|
||||
res = me_json['name']
|
||||
except KeyError:
|
||||
res = 0
|
||||
|
||||
return res
|
||||
|
||||
|
||||
|
||||
# Get user id, having access token
|
||||
# Handy for checking if the API access is working
|
||||
def get_userid(access_token):
|
||||
authorizationstring = str('Bearer ' + access_token)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'Api-Key': UNDERARMOUR_CLIENT_KEY,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json'}
|
||||
url = "https://api.ua.com/v7.1/user/self"
|
||||
response = requests.get(url,headers=headers)
|
||||
|
||||
me_json = response.json()
|
||||
|
||||
try:
|
||||
res = me_json['id']
|
||||
except KeyError:
|
||||
res = 0
|
||||
|
||||
return res
|
||||
@@ -157,8 +157,12 @@ urlpatterns = [
|
||||
url(r'^graph/(\d+)/$',views.graph_show_view),
|
||||
url(r'^graph/(\d+)/deleteconfirm$',views.graph_delete_confirm_view),
|
||||
url(r'^graph/(\d+)/delete$',views.graph_delete_view),
|
||||
url(r'^workout/upload/team/s/(?P<successmessage>\w+.*)/c/(?P<message>\w+.*)/$',views.team_workout_upload_view),
|
||||
url(r'^workout/upload/team/c/(?P<message>\w+.*)/$',views.team_workout_upload_view),
|
||||
url(r'^workout/upload/team/s/(?P<successmessage>\w+.*)/$',views.team_workout_upload_view),
|
||||
url(r'^workout/upload/team/$',views.team_workout_upload_view),
|
||||
url(r'^workout/upload/$',views.workout_upload_view),
|
||||
url(r'^workout/upload/(.+.*)$',views.workout_upload_view),
|
||||
url(r'^workout/upload/c/(?P<message>\w+.*)$',views.workout_upload_view),
|
||||
url(r'^workout/(?P<id>\d+)/histo$',views.workout_histo_view),
|
||||
url(r'^workout/(?P<id>\d+)/forcecurve$',views.workout_forcecurve_view),
|
||||
url(r'^workout/(?P<id>\d+)/unsubscribe$',views.workout_unsubscribe_view),
|
||||
@@ -225,12 +229,15 @@ urlpatterns = [
|
||||
url(r'^workout/sporttracksimport/(\d+)/$',views.workout_getsporttracksworkout_view),
|
||||
url(r'^workout/runkeeperimport/$',views.workout_runkeeperimport_view),
|
||||
url(r'^workout/runkeeperimport/(\d+)/$',views.workout_getrunkeeperworkout_view),
|
||||
url(r'^workout/underarmourimport/$',views.workout_underarmourimport_view),
|
||||
url(r'^workout/underarmourimport/(\d+)/$',views.workout_getunderarmourworkout_view),
|
||||
url(r'^workout/(\d+)/deleteconfirm$',views.workout_delete_confirm_view),
|
||||
url(r'^workout/(\d+)/c2uploadw/$',views.workout_c2_upload_view),
|
||||
url(r'^workout/(\d+)/stravauploadw/$',views.workout_strava_upload_view),
|
||||
url(r'^workout/(\d+)/recalcsummary/$',views.workout_recalcsummary_view),
|
||||
url(r'^workout/(\d+)/sporttracksuploadw/$',views.workout_sporttracks_upload_view),
|
||||
url(r'^workout/(\d+)/runkeeperuploadw/$',views.workout_runkeeper_upload_view),
|
||||
url(r'^workout/(\d+)/underarmouruploadw/$',views.workout_underarmour_upload_view),
|
||||
url(r'^multi-compare$',views.multi_compare_view),
|
||||
url(r'^me/teams/c/(?P<message>\w+.*)/s/(?P<successmessage>\w+.*)$',views.rower_teams_view),
|
||||
url(r'^me/teams/s/(?P<successmessage>\w+.*)$',views.rower_teams_view),
|
||||
@@ -265,8 +272,10 @@ urlpatterns = [
|
||||
url(r'^me/revokeapp/(\d+)$',views.rower_revokeapp_view),
|
||||
url(r'^me/stravaauthorize/$',views.rower_strava_authorize),
|
||||
url(r'^me/sporttracksauthorize/$',views.rower_sporttracks_authorize),
|
||||
url(r'^me/underarmourauthorize/$',views.rower_underarmour_authorize),
|
||||
url(r'^me/runkeeperauthorize/$',views.rower_runkeeper_authorize),
|
||||
url(r'^me/sporttracksrefresh/$',views.rower_sporttracks_token_refresh),
|
||||
url(r'^me/underarmourrefresh/$',views.rower_underarmour_token_refresh),
|
||||
url(r'^me/c2refresh/$',views.rower_c2_token_refresh),
|
||||
url(r'^me/favoritecharts/$',views.rower_favoritecharts_view),
|
||||
url(r'^email/send/$', views.sendmail),
|
||||
|
||||
656
rowers/views.py
656
rowers/views.py
@@ -14,7 +14,10 @@ from django.http import (
|
||||
HttpResponseNotFound,Http404
|
||||
)
|
||||
from django.contrib.auth import authenticate, login, logout
|
||||
from rowers.forms import LoginForm,DocumentsForm,UploadOptionsForm
|
||||
from rowers.forms import (
|
||||
LoginForm,DocumentsForm,UploadOptionsForm,
|
||||
TeamUploadOptionsForm,
|
||||
)
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.template import RequestContext
|
||||
@@ -53,6 +56,7 @@ from sporttracksstuff import SportTracksNoTokenError
|
||||
from iso8601 import ParseError
|
||||
import stravastuff
|
||||
import sporttracksstuff
|
||||
import underarmourstuff
|
||||
import runkeeperstuff
|
||||
import ownapistuff
|
||||
from ownapistuff import TEST_CLIENT_ID, TEST_CLIENT_SECRET, TEST_REDIRECT_URI
|
||||
@@ -61,6 +65,8 @@ from rowsandall_app.settings import (
|
||||
STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET,
|
||||
SPORTTRACKS_CLIENT_ID, SPORTTRACKS_REDIRECT_URI,
|
||||
SPORTTRACKS_CLIENT_SECRET,
|
||||
UNDERARMOUR_CLIENT_ID, UNDERARMOUR_REDIRECT_URI,
|
||||
UNDERARMOUR_CLIENT_SECRET,UNDERARMOUR_CLIENT_KEY,
|
||||
RUNKEEPER_CLIENT_ID,RUNKEEPER_REDIRECT_URI,RUNKEEPER_CLIENT_SECRET,
|
||||
)
|
||||
|
||||
@@ -208,6 +214,15 @@ def get_time(second):
|
||||
def getidfromsturi(uri,length=8):
|
||||
return uri[len(uri)-length:]
|
||||
|
||||
def splituadata(lijst):
|
||||
t = []
|
||||
y = []
|
||||
for d in lijst:
|
||||
t.append(d[0])
|
||||
y.append(d[1])
|
||||
|
||||
return np.array(t),np.array(y)
|
||||
|
||||
def splitrunkeeperlatlongdata(lijst,tname,latname,lonname):
|
||||
t = []
|
||||
lat = []
|
||||
@@ -810,6 +825,173 @@ def add_workout_from_stdata(user,importid,data):
|
||||
|
||||
|
||||
|
||||
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")
|
||||
|
||||
csvfilename ='media/Import_'+str(importid)+'.csv'
|
||||
|
||||
res = df.to_csv(csvfilename+'.gz',index_label='index',
|
||||
compression='gzip')
|
||||
|
||||
id,message = dataprep.save_workout_database(csvfilename,r,
|
||||
workouttype=workouttype,
|
||||
title=title,
|
||||
notes=comments)
|
||||
|
||||
return (id,message)
|
||||
|
||||
# Create workout from SportTracks Data, which are slightly different
|
||||
# than Strava or Concept2 data
|
||||
def add_workout_from_underarmourdata(user,importid,data):
|
||||
workouttype = 'water'
|
||||
|
||||
try:
|
||||
comments = data['notes']
|
||||
except:
|
||||
comments = ''
|
||||
|
||||
try:
|
||||
thetimezone = tz(data['start_locale_timezone'])
|
||||
except:
|
||||
thetimezone = 'UTC'
|
||||
|
||||
r = Rower.objects.get(user=user)
|
||||
try:
|
||||
rowdatetime = iso8601.parse_date(data['start_datetime'])
|
||||
except iso8601.ParseError:
|
||||
try:
|
||||
rowdatetime = datetime.datetime.strptime(data['start_datetime'],"%Y-%m-%d %H:%M:%S")
|
||||
rowdatetime = thetimezone.localize(rowdatetime).astimezone(utc)
|
||||
except:
|
||||
try:
|
||||
rowdatetime = dateutil.parser.parse(data['start_datetime'])
|
||||
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)
|
||||
starttimeunix = mktime(rowdatetime.utctimetuple())
|
||||
|
||||
|
||||
try:
|
||||
title = data['name']
|
||||
except:
|
||||
title = "Imported data"
|
||||
|
||||
timeseries = data['time_series']
|
||||
|
||||
# position, distance, speed, cadence, power,
|
||||
|
||||
res = splituadata(timeseries['distance'])
|
||||
|
||||
distance = res[1]
|
||||
|
||||
times_distance = res[0]
|
||||
|
||||
print distance[0:5]
|
||||
print times_distance[0:5]
|
||||
|
||||
try:
|
||||
l = timeseries['position']
|
||||
|
||||
res = splituadata(l)
|
||||
times_location = res[0]
|
||||
latlong = res[1]
|
||||
latcoord = []
|
||||
loncoord = []
|
||||
|
||||
for coord in latlong:
|
||||
lat = coord['lat']
|
||||
lon = coord['lng']
|
||||
latcoord.append(lat)
|
||||
loncoord.append(lon)
|
||||
except:
|
||||
times_location = times_distance
|
||||
latcoord = np.zeros(len(times_distance))
|
||||
loncoord = np.zeros(len(times_distance))
|
||||
if workouttype == 'water':
|
||||
workouttype = 'rower'
|
||||
|
||||
try:
|
||||
res = splituadata(timeseries['cadence'])
|
||||
times_spm = res[0]
|
||||
spm = res[1]
|
||||
except KeyError:
|
||||
times_spm = times_distance
|
||||
spm = 0*times_distance
|
||||
|
||||
try:
|
||||
res = splituadata(timeseries['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
|
||||
|
||||
@@ -875,6 +1057,20 @@ def sporttracks_open(user):
|
||||
|
||||
return thetoken
|
||||
|
||||
# Checks if user has UnderArmour token, renews them if they are expired
|
||||
def underarmour_open(user):
|
||||
r = Rower.objects.get(user=user)
|
||||
if (r.underarmourtoken == '') or (r.underarmourtoken is None):
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
raise UnderarmourNoTokenError("User has no token")
|
||||
else:
|
||||
if (timezone.now()>r.underarmourtokenexpirydate):
|
||||
thetoken = underarmourstuff.rower_underarmour_token_refresh(user)
|
||||
else:
|
||||
thetoken = r.underarmourtoken
|
||||
|
||||
return thetoken
|
||||
|
||||
# Checks if user has SportTracks token, renews them if they are expired
|
||||
def runkeeper_open(user):
|
||||
r = Rower.objects.get(user=user)
|
||||
@@ -1004,6 +1200,7 @@ def workout_strava_upload_view(request,id=0):
|
||||
# ready to upload. Hurray
|
||||
try:
|
||||
w = Workout.objects.get(id=id)
|
||||
r = w.user
|
||||
except Workout.DoesNotExist:
|
||||
raise Http404("Workout doesn't exist")
|
||||
if (checkworkoutuser(request.user,w)):
|
||||
@@ -1086,16 +1283,18 @@ def workout_strava_upload_view(request,id=0):
|
||||
@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
|
||||
try:
|
||||
w = Workout.objects.get(id=id)
|
||||
r = w.user
|
||||
except Workout.DoesNotExist:
|
||||
raise Http404("Workout doesn't exist")
|
||||
|
||||
try:
|
||||
thetoken = c2_open(r.user)
|
||||
except C2NoTokenError:
|
||||
return HttpResponseRedirect("/rowers/me/c2authorize/")
|
||||
|
||||
if (checkworkoutuser(request.user,w)):
|
||||
c2userid = c2stuff.get_userid(thetoken)
|
||||
if not c2userid:
|
||||
@@ -1172,15 +1371,17 @@ def workout_c2_upload_view(request,id=0):
|
||||
def workout_runkeeper_upload_view(request,id=0):
|
||||
message = ""
|
||||
try:
|
||||
thetoken = runkeeper_open(request.user)
|
||||
w = Workout.objects.get(id=id)
|
||||
r = w.user
|
||||
except Workout.DoesNotExist:
|
||||
raise Http404("Workout doesn't exist")
|
||||
|
||||
try:
|
||||
thetoken = runkeeper_open(r.user)
|
||||
except RunKeeperNoTokenError:
|
||||
return HttpResponseRedirect("/rowers/me/runkeeperauthorize/")
|
||||
|
||||
# ready to upload. Hurray
|
||||
try:
|
||||
w = Workout.objects.get(id=id)
|
||||
except Workout.DoesNotExist:
|
||||
raise Http404("Workout doesn't exist")
|
||||
|
||||
if (checkworkoutuser(request.user,w)):
|
||||
data = runkeeperstuff.createrunkeeperworkoutdata(w)
|
||||
@@ -1229,21 +1430,89 @@ def workout_runkeeper_upload_view(request,id=0):
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
# Upload workout to Underarmour
|
||||
@login_required()
|
||||
def workout_underarmour_upload_view(request,id=0):
|
||||
message = ""
|
||||
try:
|
||||
w = Workout.objects.get(id=id)
|
||||
r = w.user
|
||||
except Workout.DoesNotExist:
|
||||
raise Http404("Workout doesn't exist")
|
||||
|
||||
try:
|
||||
thetoken = underarmour_open(r.user)
|
||||
except UnderarmourNoTokenError:
|
||||
return HttpResponseRedirect("/rowers/me/underarmourauthorize/")
|
||||
|
||||
# ready to upload. Hurray
|
||||
|
||||
if (checkworkoutuser(request.user,w)):
|
||||
data = underarmourstuff.createunderarmourworkoutdata(w)
|
||||
# return HttpResponse(json.dumps(data))
|
||||
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,
|
||||
'Api-Key': UNDERARMOUR_CLIENT_KEY,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
|
||||
import urllib
|
||||
url = "https://api.ua.com/v7.1/workout/"
|
||||
response = requests.post(url,headers=headers,data=json.dumps(data))
|
||||
|
||||
# check for duplicate error first
|
||||
if (response.status_code == 409 ):
|
||||
message = "Duplicate error"
|
||||
w.uploadedtounderarmour = -1
|
||||
w.save()
|
||||
elif (response.status_code == 201 or response.status_code==200):
|
||||
underarmourid = underarmourstuff.getidfromresponse(response)
|
||||
w.uploadedtounderarmour = underarmourid
|
||||
w.save()
|
||||
url = "/rowers/workout/"+str(w.id)+"/export"
|
||||
return HttpResponseRedirect(url)
|
||||
else:
|
||||
s = response
|
||||
message = "Something went wrong in workout_underarmour_upload_view: %s - %s" % (s.reason,s.text)
|
||||
|
||||
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
|
||||
try:
|
||||
w = Workout.objects.get(id=id)
|
||||
r = w.user
|
||||
except Workout.DoesNotExist:
|
||||
raise Http404("Workout doesn't exist")
|
||||
|
||||
try:
|
||||
thetoken = sporttracks_open(r.user)
|
||||
except SportTracksNoTokenError:
|
||||
return HttpResponseRedirect("/rowers/me/sporttracksauthorize/")
|
||||
|
||||
|
||||
if (checkworkoutuser(request.user,w)):
|
||||
data = sporttracksstuff.createsporttracksworkoutdata(w)
|
||||
if not data:
|
||||
@@ -1363,6 +1632,23 @@ def rower_sporttracks_authorize(request):
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
# Underarmour Authorization
|
||||
@login_required()
|
||||
def rower_underarmour_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())
|
||||
|
||||
redirect_uri = UNDERARMOUR_REDIRECT_URI
|
||||
|
||||
url = 'https://www.mapmyfitness.com/v7.1/oauth2/authorize/?' \
|
||||
'client_id={0}&response_type=code&redirect_uri={1}'.format(
|
||||
UNDERARMOUR_CLIENT_KEY, redirect_uri
|
||||
)
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
# Concept2 token refresh. URL for manual refresh. Not visible to users
|
||||
@login_required()
|
||||
def rower_c2_token_refresh(request):
|
||||
@@ -1388,11 +1674,37 @@ def rower_c2_token_refresh(request):
|
||||
|
||||
return imports_view(request,successmessage=successmessage,message=message)
|
||||
|
||||
# Underarmour token refresh. URL for manual refresh. Not visible to users
|
||||
@login_required()
|
||||
def rower_underarmour_token_refresh(request):
|
||||
r = Rower.objects.get(user=request.user)
|
||||
res = underarmourstuff.do_refresh_token(
|
||||
r.underarmourrefreshtoken,
|
||||
r.underarmourtoken
|
||||
)
|
||||
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.underarmourtoken = access_token
|
||||
r.underarmourtokenexpirydate = expirydatetime
|
||||
r.underarmourrefreshtoken = refresh_token
|
||||
|
||||
r.save()
|
||||
|
||||
successmessage = "Tokens refreshed. Good to go"
|
||||
return imports_view(request,successmessage=successmessage)
|
||||
|
||||
|
||||
# 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)
|
||||
res = sporttracksstuff.do_refresh_token(
|
||||
r.sporttracksrefreshtoken,
|
||||
)
|
||||
access_token = res[0]
|
||||
expires_in = res[1]
|
||||
refresh_token = res[2]
|
||||
@@ -1525,6 +1837,28 @@ def rower_process_sporttrackscallback(request):
|
||||
successmessage = "Tokens stored. Good to go"
|
||||
return imports_view(request,successmessage=successmessage)
|
||||
|
||||
# Process Underarmour callback
|
||||
@login_required()
|
||||
def rower_process_underarmourcallback(request):
|
||||
code = request.GET['code']
|
||||
res = underarmourstuff.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.underarmourtoken = access_token
|
||||
r.underarmourtokenexpirydate = expirydatetime
|
||||
r.underarmourrefreshtoken = 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):
|
||||
@@ -3866,6 +4200,7 @@ def workout_flexchart3_view(request,*args,**kwargs):
|
||||
if request.method == 'POST' and 'savefavorite' in request.POST:
|
||||
workstrokesonly = request.POST['workstrokesonlysave']
|
||||
reststrokes = not workstrokesonly
|
||||
r = Rower.objects.get(user=request.user)
|
||||
f = FavoriteChart(user=r,xparam=xparam,
|
||||
yparam1=yparam1,yparam2=yparam2,
|
||||
plottype=plottype,workouttype=workouttype,
|
||||
@@ -4829,6 +5164,57 @@ def workout_runkeeperimport_view(request,message=""):
|
||||
|
||||
return HttpResponse(res)
|
||||
|
||||
# The page where you select which RunKeeper workout to import
|
||||
@login_required()
|
||||
def workout_underarmourimport_view(request,message=""):
|
||||
res = underarmourstuff.get_underarmour_workout_list(request.user)
|
||||
if (res.status_code != 200):
|
||||
if (res.status_code == 401):
|
||||
r = Rower.objects.get(user=request.user)
|
||||
if (r.underarmourtoken == '') or (r.underarmourtoken is None):
|
||||
s = "Token doesn't exist. Need to authorize"
|
||||
return HttpResponseRedirect("/rowers/me/underarmourauthorize/")
|
||||
message = "Something went wrong in workout_underarmourimport_view"
|
||||
if settings.DEBUG:
|
||||
return HttpResponse(res)
|
||||
else:
|
||||
url = reverse(workouts_view,
|
||||
kwargs = {
|
||||
'message': str(message)
|
||||
})
|
||||
return HttpResponseRedirect(url)
|
||||
else:
|
||||
workouts = []
|
||||
items = res.json()['_embedded']['workouts']
|
||||
for item in items:
|
||||
if 'has_time_series' in item:
|
||||
if item['has_time_series']:
|
||||
s = item['start_datetime']
|
||||
i,r = underarmourstuff.get_idfromuri(request.user,item['_links'])
|
||||
n = item['name']
|
||||
try:
|
||||
d = item['aggregates']['distance_total']
|
||||
except KeyError:
|
||||
d = 0
|
||||
try:
|
||||
ttot = item['aggregates']['active_time_total']
|
||||
except KeyError:
|
||||
ttot = 0
|
||||
|
||||
keys = ['id','distance','duration','starttime','type']
|
||||
values = [i,d,ttot,s,r]
|
||||
thedict = dict(zip(keys,values))
|
||||
|
||||
workouts.append(thedict)
|
||||
|
||||
return render(request,'underarmour_list_import.html',
|
||||
{'workouts':workouts,
|
||||
'teams':get_my_teams(request.user),
|
||||
'message':message,
|
||||
})
|
||||
|
||||
return HttpResponse(res)
|
||||
|
||||
# The page where you select which SportTracks workout to import
|
||||
@login_required()
|
||||
def workout_sporttracksimport_view(request,message=""):
|
||||
@@ -5011,6 +5397,29 @@ def workout_getrunkeeperworkout_view(request,runkeeperid):
|
||||
})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
# Imports a workout from Underarmour
|
||||
@login_required()
|
||||
def workout_getunderarmourworkout_view(request,underarmourid):
|
||||
res = underarmourstuff.get_underarmour_workout(request.user,underarmourid)
|
||||
data = res.json()
|
||||
|
||||
id,message = add_workout_from_underarmourdata(request.user,underarmourid,data)
|
||||
w = Workout.objects.get(id=id)
|
||||
w.uploadedtounderarmour=underarmourid
|
||||
w.save()
|
||||
if message:
|
||||
url = reverse(workout_edit_view,
|
||||
kwargs = {
|
||||
'id':id,
|
||||
'message':message,
|
||||
})
|
||||
else:
|
||||
url = reverse(workout_edit_view,
|
||||
kwargs = {
|
||||
'id':id,
|
||||
})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
|
||||
# Imports a workout from SportTracks
|
||||
@@ -5159,11 +5568,24 @@ def workout_upload_view(request,message="",
|
||||
request.session['uploadoptions'] = uploadoptions
|
||||
|
||||
|
||||
|
||||
makeprivate = uploadoptions['makeprivate']
|
||||
make_plot = uploadoptions['make_plot']
|
||||
plottype = uploadoptions['plottype']
|
||||
upload_toc2 = uploadoptions['upload_to_C2']
|
||||
try:
|
||||
makeprivate = uploadoptions['makeprivate']
|
||||
except KeyError:
|
||||
makeprivate = False
|
||||
try:
|
||||
make_plot = uploadoptions['make_plot']
|
||||
except KeyError:
|
||||
make_plot = False
|
||||
|
||||
try:
|
||||
plottype = uploadoptions['plottype']
|
||||
except KeyError:
|
||||
plottype = 'timeplot'
|
||||
|
||||
try:
|
||||
upload_toc2 = uploadoptions['upload_to_C2']
|
||||
except KeyError:
|
||||
upload_toc2 = False
|
||||
|
||||
r = Rower.objects.get(user=request.user)
|
||||
if request.method == 'POST':
|
||||
@@ -5207,7 +5629,12 @@ def workout_upload_view(request,message="",
|
||||
args=[str(message)])
|
||||
response = HttpResponseRedirect(url)
|
||||
return response
|
||||
|
||||
elif id == -1:
|
||||
message = 'The zip archive will be processed in the background. The files in the archive will only be uploaded without the extra actions. You will receive email when the workouts are ready.'
|
||||
url = reverse(workout_upload_view,
|
||||
args=[str(message)])
|
||||
response = HttpResponseRedirect(url)
|
||||
return response
|
||||
else:
|
||||
if message:
|
||||
url = reverse(workout_edit_view,
|
||||
@@ -5350,6 +5777,183 @@ def workout_upload_view(request,message="",
|
||||
'optionsform': optionsform,
|
||||
'message':message})
|
||||
|
||||
# This is the main view for processing uploaded files
|
||||
@user_passes_test(iscoachmember,login_url="/",redirect_field_name=None)
|
||||
def team_workout_upload_view(request,message="",
|
||||
successmessage="",
|
||||
uploadoptions={
|
||||
'make_plot':False,
|
||||
'plottype':'timeplot',
|
||||
}):
|
||||
|
||||
if 'uploadoptions' in request.session:
|
||||
uploadoptions = request.session['uploadoptions']
|
||||
else:
|
||||
request.session['uploadoptions'] = uploadoptions
|
||||
|
||||
|
||||
myteams = Team.objects.filter(manager=request.user)
|
||||
|
||||
make_plot = uploadoptions['make_plot']
|
||||
plottype = uploadoptions['plottype']
|
||||
|
||||
r = Rower.objects.get(user=request.user)
|
||||
if request.method == 'POST':
|
||||
form = DocumentsForm(request.POST,request.FILES)
|
||||
optionsform = TeamUploadOptionsForm(request.POST)
|
||||
|
||||
rowerform = TeamInviteForm(request.POST)
|
||||
rowerform.fields.pop('email')
|
||||
rowerform.fields['user'].queryset = User.objects.filter(rower__isnull=False,rower__team__in=myteams).distinct()
|
||||
if form.is_valid():
|
||||
f = request.FILES['file']
|
||||
res = handle_uploaded_file(f)
|
||||
t = form.cleaned_data['title']
|
||||
if rowerform.is_valid():
|
||||
u = rowerform.cleaned_data['user']
|
||||
if u:
|
||||
r = Rower.objects.get(user=u)
|
||||
else:
|
||||
message = 'Please select a rower'
|
||||
response = render(request,
|
||||
'team_document_form.html',
|
||||
{'form':form,
|
||||
'teams':get_my_teams(request.user),
|
||||
'optionsform': optionsform,
|
||||
'rowerform': rowerform,
|
||||
'message':message,
|
||||
'successmessage':successmessage,
|
||||
})
|
||||
|
||||
return response
|
||||
|
||||
workouttype = form.cleaned_data['workouttype']
|
||||
|
||||
notes = form.cleaned_data['notes']
|
||||
|
||||
if optionsform.is_valid():
|
||||
make_plot = optionsform.cleaned_data['make_plot']
|
||||
plottype = optionsform.cleaned_data['plottype']
|
||||
|
||||
uploadoptions = {
|
||||
'makeprivate':False,
|
||||
'make_plot':make_plot,
|
||||
'plottype':plottype,
|
||||
'upload_to_C2':False,
|
||||
}
|
||||
|
||||
|
||||
request.session['uploadoptions'] = uploadoptions
|
||||
|
||||
f1 = res[0] # file name
|
||||
f2 = res[1] # file name incl media directory
|
||||
|
||||
|
||||
id,message,f2 = dataprep.new_workout_from_file(r,f2,
|
||||
workouttype=workouttype,
|
||||
makeprivate=False,
|
||||
title = t,
|
||||
notes='')
|
||||
if not id:
|
||||
url = reverse(team_workout_upload_view,
|
||||
args=[str(message)])
|
||||
response = HttpResponseRedirect(url)
|
||||
return response
|
||||
|
||||
else:
|
||||
if message:
|
||||
successmessage = "The workout was added to the user's account"
|
||||
url = reverse(team_workout_upload_view,
|
||||
kwargs = {
|
||||
'message':message,
|
||||
'successmessage':successmessage,
|
||||
})
|
||||
else:
|
||||
successmessage = "The workout was added to the user's account"
|
||||
url = reverse(team_workout_upload_view,
|
||||
kwargs = {
|
||||
'successmessage':successmessage,
|
||||
})
|
||||
|
||||
response = HttpResponseRedirect(url)
|
||||
w = Workout.objects.get(id=id)
|
||||
|
||||
if (make_plot):
|
||||
imagename = f1[:-4]+'.png'
|
||||
fullpathimagename = 'static/plots/'+imagename
|
||||
powerperc = 100*np.array([r.pw_ut2,
|
||||
r.pw_ut1,
|
||||
r.pw_at,
|
||||
r.pw_tr,r.pw_an])/r.ftp
|
||||
|
||||
hrpwrdata = {
|
||||
'hrmax':r.max,
|
||||
'hrut2':r.ut2,
|
||||
'hrut1':r.ut1,
|
||||
'hrat':r.at,
|
||||
'hrtr':r.tr,
|
||||
'hran':r.an,
|
||||
'ftp':r.ftp,
|
||||
'powerperc':serialize_list(powerperc),
|
||||
'powerzones':serialize_list(r.powerzones),
|
||||
}
|
||||
|
||||
# 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()
|
||||
|
||||
|
||||
|
||||
else:
|
||||
response = render(request,
|
||||
'team_document_form.html',
|
||||
{'form':form,
|
||||
'teams':get_my_teams(request.user),
|
||||
'optionsform': optionsform,
|
||||
'rowerform': rowerform,
|
||||
'message':message,
|
||||
'successmessage':successmessage,
|
||||
})
|
||||
|
||||
return response
|
||||
else:
|
||||
form = DocumentsForm()
|
||||
optionsform = TeamUploadOptionsForm(initial=uploadoptions)
|
||||
rowerform = TeamInviteForm()
|
||||
rowerform.fields.pop('email')
|
||||
rowerform.fields['user'].queryset = User.objects.filter(rower__isnull=False,rower__team__in=myteams).distinct()
|
||||
return render(request, 'team_document_form.html',
|
||||
{'form':form,
|
||||
'teams':get_my_teams(request.user),
|
||||
'optionsform': optionsform,
|
||||
'rowerform':rowerform,
|
||||
'message':message,
|
||||
'successmessage':successmessage,
|
||||
})
|
||||
|
||||
|
||||
|
||||
# Ask the user if he really wants to delete the workout
|
||||
@@ -6416,11 +7020,14 @@ def rower_teams_view(request,message='',successmessage=''):
|
||||
requests = TeamRequest.objects.filter(user=request.user)
|
||||
myrequests = TeamRequest.objects.filter(team__in=myteams)
|
||||
myinvites = TeamInvite.objects.filter(team__in=myteams)
|
||||
clubsize = teams.count_invites(request.user)+teams.count_club_members(request.user)
|
||||
max_clubsize = r.clubsize
|
||||
|
||||
return render(request, 'teams.html',
|
||||
{
|
||||
'teams':ts,
|
||||
'teams':get_my_teams(request.user),
|
||||
'clubsize':clubsize,
|
||||
'max_clubsize':max_clubsize,
|
||||
'myteams':myteams,
|
||||
'invites':invites,
|
||||
'otherteams':otherteams,
|
||||
@@ -6431,6 +7038,7 @@ def rower_teams_view(request,message='',successmessage=''):
|
||||
'successmessage':successmessage,
|
||||
'myinvites':myinvites,
|
||||
})
|
||||
|
||||
@user_passes_test(iscoachmember,login_url="/",redirect_field_name=None)
|
||||
def invitation_revoke_view(request,id):
|
||||
res,text = teams.revoke_invite(request.user,id)
|
||||
|
||||
Reference in New Issue
Block a user