Private
Public Access
1
0

moved workout creation to dataprep for workout_upload

This commit is contained in:
Sander Roosendaal
2017-02-11 15:03:28 +01:00
parent 97b6212161
commit f287f9af21
4 changed files with 168 additions and 296 deletions

View File

@@ -15,11 +15,23 @@ from pytz import timezone as tz,utc
from django.utils.timezone import get_current_timezone from django.utils.timezone import get_current_timezone
thetimezone = get_current_timezone() thetimezone = get_current_timezone()
from rowingdata import (
TCXParser,RowProParser,ErgDataParser,TCXParserNoHR,
BoatCoachParser,RowPerfectParser,BoatCoachAdvancedParser,
MysteryParser,
painsledDesktopParser,speedcoachParser,ErgStickParser,
SpeedCoach2Parser,FITParser,fitsummarydata,
make_cumvalues,
summarydata,get_file_type,
)
import os
import pandas as pd import pandas as pd
import numpy as np import numpy as np
import itertools import itertools
from tasks import handle_sendemail_unrecognized
from django.conf import settings from django.conf import settings
from sqlalchemy import create_engine from sqlalchemy import create_engine
import sqlalchemy as sa import sqlalchemy as sa
@@ -212,10 +224,46 @@ def new_workout_from_file(r,f2,
workouttype='rower', workouttype='rower',
title='Workout', title='Workout',
notes=''): notes=''):
message = None
fileformat = get_file_type(f2) fileformat = get_file_type(f2)
summary = '' summary = ''
# handle non-Painsled 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':
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)
# 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)
# 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,
request.user.email)
else:
res = queuehigh.enqueue(handle_sendemail_unrecognized,
f2,request.user.email)
return (0,'message')
# handle non-Painsled by converting it to painsled compatible CSV
if (fileformat != 'csv'): if (fileformat != 'csv'):
# handle RowPro: # handle RowPro:
if (fileformat == 'rp'): if (fileformat == 'rp'):
@@ -232,10 +280,18 @@ def new_workout_from_file(r,f2,
if (fileformat == 'tcxnohr'): if (fileformat == 'tcxnohr'):
row = TCXParserNoHR(f2) row = TCXParserNoHR(f2)
# handle RowPerfect
if (fileformat == 'rowperfect3'):
row = RowPerfectParser(f2)
# handle ErgData # handle ErgData
if (fileformat == 'ergdata'): if (fileformat == 'ergdata'):
row = ErgDataParser(f2) row = ErgDataParser(f2)
# handle Mike
if (fileformat == 'bcmike'):
row = BoatCoachAdvancedParser(f2)
# handle BoatCoach # handle BoatCoach
if (fileformat == 'boatcoach'): if (fileformat == 'boatcoach'):
row = BoatCoachParser(f2) row = BoatCoachParser(f2)
@@ -251,7 +307,10 @@ def new_workout_from_file(r,f2,
# handle speed coach GPS 2 # handle speed coach GPS 2
if (fileformat == 'speedcoach2'): if (fileformat == 'speedcoach2'):
row = SpeedCoach2Parser(f2) row = SpeedCoach2Parser(f2)
summary = row.allstats() try:
summary = row.allstats()
except:
pass
# handle ErgStick # handle ErgStick
@@ -266,24 +325,30 @@ def new_workout_from_file(r,f2,
summary = s.summarytext summary = s.summarytext
f_to_be_deleted = f2 f_to_be_deleted = f2
# should delete file # should delete file
f2 = f2[:-4]+'o.csv' f2 = f2[:-4]+'o.csv'
row.write_csv(f2,gzip=True) row.write_csv(f2,gzip=True)
#os.remove(f2) #os.remove(f2)
try: try:
os.remove(f_to_be_deleted) os.remove(f_to_be_deleted)
except: except:
os.remove(f_to_be_deleted+'.gz') os.remove(f_to_be_deleted+'.gz')
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 # make workout and put in database
rr = rrower(hrmax=r.max,hrut2=r.ut2, rr = rrower(hrmax=r.max,hrut2=r.ut2,
hrut1=r.ut1,hrat=r.at, hrut1=r.ut1,hrat=r.at,
hrtr=r.tr,hran=r.an,ftp=r.ftp) hrtr=r.tr,hran=r.an,ftp=r.ftp,
powerperc=powerperc,powerzones=r.powerzones)
row = rdata(f2,rower=rr) row = rdata(f2,rower=rr)
if row == 0: if row == 0:
return HttpResponse("Error: CSV Data File Not Found") return (0,'Error: CSV data file not found')
# auto smoothing # auto smoothing
pace = row.df[' Stroke500mPace (sec/500m)'].values pace = row.df[' Stroke500mPace (sec/500m)'].values
@@ -336,6 +401,10 @@ def new_workout_from_file(r,f2,
totaltime = totaltime+row.df.ix[0,' ElapsedTime (sec)'] totaltime = totaltime+row.df.ix[0,' ElapsedTime (sec)']
hours = int(totaltime/3600.) 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.) minutes = int((totaltime - 3600.*hours)/60.)
seconds = int(totaltime - 3600.*hours - 60.*minutes) seconds = int(totaltime - 3600.*hours - 60.*minutes)
tenths = int(10*(totaltime - 3600.*hours - 60.*minutes - seconds)) tenths = int(10*(totaltime - 3600.*hours - 60.*minutes - seconds))
@@ -362,9 +431,10 @@ def new_workout_from_file(r,f2,
w.save() w.save()
# put stroke data in database # put stroke data in database
res = dataprep(row.df,id=w.id,bands=True,barchart=True,otwpower=True,empower=True) res = dataprep(row.df,id=w.id,bands=True,
barchart=True,otwpower=True,empower=True)
return True return (w.id,message)
# Compare the data from the CSV file and the database # Compare the data from the CSV file and the database
# Currently only calculates number of strokes. To be expanded with # Currently only calculates number of strokes. To be expanded with

View File

@@ -31,6 +31,8 @@ from rowers.tasks import (
inviteduration = 14 # days inviteduration = 14 # days
def update_team(t,name,manager,private,notes): def update_team(t,name,manager,private,notes):
if t.manager != manager:
return (0,'You are not the manager of this team')
try: try:
t.name = name t.name = name
t.manager = manager t.manager = manager

View File

@@ -154,7 +154,7 @@ urlpatterns = [
url(r'^workout/(?P<id>\d+)/export/c/(?P<message>\w+.*)/s/(?P<successmessage>\w+.*)$',views.workout_edit_view), url(r'^workout/(?P<id>\d+)/export/c/(?P<message>\w+.*)/s/(?P<successmessage>\w+.*)$',views.workout_edit_view),
url(r'^workout/(?P<id>\d+)/edit/c/(?P<message>.+.*)$',views.workout_edit_view), url(r'^workout/(?P<id>\d+)/edit/c/(?P<message>.+.*)$',views.workout_edit_view),
url(r'^workout/(?P<id>\d+)/edit/s/(?P<successmessage>.+.*)$',views.workout_edit_view), url(r'^workout/(?P<id>\d+)/edit/s/(?P<successmessage>.+.*)$',views.workout_edit_view),
url(r'^workout/(\d+)/edit$',views.workout_edit_view), url(r'^workout/(?P<id>\d+)/edit$',views.workout_edit_view),
url(r'^workout/(?P<id>\d+)/advanced/c/(?P<message>.+.*)$',views.workout_advanced_view), url(r'^workout/(?P<id>\d+)/advanced/c/(?P<message>.+.*)$',views.workout_advanced_view),
url(r'^workout/(?P<id>\d+)/advanced/s/(?P<successmessage>.+.*)$',views.workout_advanced_view), url(r'^workout/(?P<id>\d+)/advanced/s/(?P<successmessage>.+.*)$',views.workout_advanced_view),
url(r'^workout/(?P<id>\d+)/geeky$',views.workout_geeky_view), url(r'^workout/(?P<id>\d+)/geeky$',views.workout_geeky_view),

View File

@@ -4053,6 +4053,7 @@ def workout_getc2workout_view(request,c2id):
# This is the main view for processing uploaded files # This is the main view for processing uploaded files
@login_required() @login_required()
def workout_upload_view(request,message=""):
r = Rower.objects.get(user=request.user) r = Rower.objects.get(user=request.user)
if request.method == 'POST': if request.method == 'POST':
form = DocumentsForm(request.POST,request.FILES) form = DocumentsForm(request.POST,request.FILES)
@@ -4070,283 +4071,82 @@ def workout_upload_view(request,message=""):
f1 = res[0] # file name 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]
f2 = res[1] # file name incl media directory f2 = res[1] # file name incl media directory
# Some people try to upload Concept2 logbook summaries
if fileformat == 'c2log': id,message = dataprep.new_workout_from_file(r,f2,
os.remove(f2) workouttype=workouttype,
title = t,
notes='')
if not id: if not id:
url = reverse(workout_upload_view, url = reverse(workout_upload_view,
args=[str(message)]) args=[str(message)])
response = HttpResponseRedirect(url) response = HttpResponseRedirect(url)
return response return response
# Some people try to upload RowPro summary logs else:
if fileformat == 'rowprolog': if message:
os.remove(f2) url = reverse(workout_edit_view,
message = "This RowPro logbook summary does not contain stroke data. Please use the Stroke Data CSV file for the individual workout in your log." kwargs = {
url = reverse(workout_upload_view, 'id':id,
args=[str(message)]) 'message':message,
response = HttpResponseRedirect(url) })
else:
url = reverse(workout_edit_view,
kwargs = {
'id':id,
})
response = HttpResponseRedirect(url)
w = Workout.objects.get(id=id) w = Workout.objects.get(id=id)
# Sometimes people try an unsupported file type. if (make_plot):
# Send an email to info@rowsandall.com with the file attached imagename = f1[:-4]+'.png'
# for me to check if it is a bug, or a new file type fullpathimagename = 'static/plots/'+imagename
# 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')
u = request.user u = request.user
r = Rower.objects.get(user=request.user) r = Rower.objects.get(user=request.user)
powerperc = 100*np.array([r.pw_ut2, powerperc = 100*np.array([r.pw_ut2,
r.pw_ut1, r.pw_ut1,
r.pw_at, r.pw_at,
r.pw_tr,r.pw_an])/r.ftp r.pw_tr,r.pw_an])/r.ftp
rr = rrower(hrmax=r.max,hrut2=r.ut2, hrpwrdata = {
hrut1=r.ut1,hrat=r.at, 'hrmax':r.max,
hrtr=r.tr,hran=r.an,ftp=r.ftp, 'hrut2':r.ut2,
powerperc=powerperc,powerzones=r.powerzones) 'hrut1':r.ut1,
row = rdata(f2,rower=rr) 'hrat':r.at,
if row == 0: 'hrtr':r.tr,
'hran':r.an,
'ftp':r.ftp,
'powerperc':serialize_list(powerperc),
'powerzones':serialize_list(r.powerzones),
} }
# auto smoothing # make plot - asynchronous task
pace = row.df[' Stroke500mPace (sec/500m)'].values plotnrs = {
velo = 500./pace 'timeplot':1,
'distanceplot':2,
f = row.df['TimeStamp (sec)'].diff().mean() 'pieplot':3,
windowsize = 2*(int(10./(f)))+1
if not 'originalvelo' in row.df:
} }
if windowsize > 3 and windowsize<len(velo): plotnr = plotnrs[plottype]
if (workouttype=='water'):
plotnr = plotnr+3
if settings.DEBUG:
res = handle_makeplot.delay(f1,f2,t,
hrpwrdata,plotnr,
imagename) imagename)
else: else:
velo2 = velo res = queue.enqueue(handle_makeplot,f1,f2,
t,hrpwrdata,
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
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
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,imagename) plotnr,imagename)
if settings.DEBUG: i = GraphImage(workout=w,
res = handle_makeplot.delay(f1,f2,t, creationdatetime=timezone.now(),
hrpwrdata,plotnr, filename=fullpathimagename)
imagename)
else:
res = queue.enqueue(handle_makeplot,f1,f2,
t,hrpwrdata,
plotnr,imagename)
i = GraphImage(workout=w,
creationdatetime=timezone.now(),
filename=fullpathimagename)
i.save() i.save()
# upload to C2 # upload to C2
@@ -4355,7 +4155,6 @@ def workout_upload_view(request,message=""):
thetoken = c2_open(request.user) thetoken = c2_open(request.user)
except C2NoTokenError: except C2NoTokenError:
return HttpResponseRedirect("/rowers/me/c2authorize/") return HttpResponseRedirect("/rowers/me/c2authorize/")
try:
try: try:
c2userid = c2stuff.get_userid(thetoken) c2userid = c2stuff.get_userid(thetoken)
data = c2stuff.createc2workoutdata(w) data = c2stuff.createc2workoutdata(w)
@@ -4383,28 +4182,29 @@ def workout_upload_view(request,message=""):
s= json.loads(response.text) s= json.loads(response.text)
c2id = s['data']['id'] c2id = s['data']['id']
w.uploadedtoc2 = c2id w.uploadedtoc2 = c2id
w.save() w.save()
except:
except: message = "C2 upload failed"
message = "C2 upload failed" url = reverse(workout_edit_view,
url = reverse(workout_edit_view,
kwargs={ kwargs={
'message':message, 'message':message,
'id':str(w.id), '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
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
url = reverse(workout_upload_view,
args=[str(message)]) if message:
url = reverse(workout_edit_view,
kwargs={
'message':message,
'id':w.id,
})
else:
url = reverse(workout_edit_view,
kwargs = {
'id':w.id,
})
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
else: else:
response = render(request, response = render(request,