Private
Public Access
1
0
Files
rowsandall/rowers/views/apiviews.py
2023-02-01 19:12:07 +01:00

648 lines
23 KiB
Python

from rowers.views.statements import *
from rowers.tasks import handle_calctrimp
from rowers.opaque import encoder
import arrow
import pendulum
from rowsandall_app.settings import UPLOAD_SERVICE_SECRET, UPLOAD_SERVICE_URL
# Stroke data form to test API upload
@login_required()
def strokedataform(request, id=0):
id = encoder.decode_hex(id)
try:
w = Workout.objects.get(id=id)
except Workout.DoesNotExist: # pragma: no cover
raise Http404("Workout doesn't exist")
if request.method == 'GET':
form = StrokeDataForm()
return render(request, 'strokedata_form.html',
{
'form': form,
'teams': get_my_teams(request.user),
'id': id,
'workout': w,
})
elif request.method == 'POST': # pragma: no cover
form = StrokeDataForm()
return render(request, 'strokedata_form.html',
{
'form': form,
'teams': get_my_teams(request.user),
'id': id,
'workout': w,
}) # pragma: no cover
def api_get_dataframe(startdatetime, df):
try:
time = df['time']/1.e3
except KeyError: # pragma: no cover
try:
time = df['t']/10.
except KeyError:
return 400, "Missing time", pd.DataFrame()
try:
spm = df['spm']
except KeyError: # pragma: no cover
return 400, "Missing spm", pd.DataFrame()
try:
distance = df['distance']
except KeyError: # pragma: no cover
try:
distance = df['d']/10.
except KeyError:
return 400, "Missing distance", pd.DataFrame()
try:
pace = df['pace']/1.e3
except KeyError: # pragma: no cover
try:
pace = df['p']/10.
except KeyError:
return 400, "Missing pace", pd.DataFrame()
try:
power = df['power']
except KeyError: # pragma: no cover
power = 0*time
try:
drivelength = df['drivelength']
except KeyError:
drivelength = 0*time
try:
dragfactor = df['dragfactor']
except KeyError:
dragfactor = 0*time
try:
drivetime = df['drivetime']
except KeyError:
drivetime = 0*time
try:
strokerecoverytime = df['strokerecoverytime']
except KeyError:
strokerecoverytime = 0*time
try:
averagedriveforce = df['averagedriveforce']
except KeyError:
averagedriveforce = 0*time
try:
peakdriveforce = df['peakdriveforce']
except KeyError:
peakdriveforce = 0*time
try:
wash = df['wash']
except KeyError:
wash = 0*time
try:
catch = df['catch']
except KeyError:
catch = 0*time
try:
finish = df['finish']
except KeyError:
finish = 0*time
try:
peakforceangle = df['peakforceangle']
except KeyError:
peakforceangle = 0*time
try:
driveenergy = df['driveenergy']
except KeyError:
driveenergy = 60.*power/spm
try:
slip = df['slip']
except KeyError:
slip = 0*time
try:
lapidx = df['lapidx']
except KeyError:
lapidx = 0*time
try:
hr = df['hr']
except KeyError: # pragma: no cover
hr = 0*df['time']
try:
latitude = df['latitude']
except KeyError:
latitude = 0*df['time']
try:
longitude = df['longitude']
except KeyError:
longitude = 0*df['time']
starttime = totimestamp(startdatetime)+time[0]
unixtime = starttime+time
dologging('apilog.log',"(strokedatajson_v2/3 POST - data parsed)")
data = pd.DataFrame({'TimeStamp (sec)': unixtime,
' Horizontal (meters)': distance,
' Cadence (stokes/min)': spm,
' HRCur (bpm)': hr,
' DragFactor': dragfactor,
' Stroke500mPace (sec/500m)': pace,
' Power (watts)': power,
' DriveLength (meters)': drivelength,
' DriveTime (ms)': drivetime,
' StrokeRecoveryTime (ms)': strokerecoverytime,
' AverageDriveForce (lbs)': averagedriveforce,
' PeakDriveForce (lbs)': peakdriveforce,
' lapIdx': lapidx,
' ElapsedTime (sec)': time,
'catch': catch,
'slip': slip,
'finish': finish,
'wash': wash,
'driveenergy': driveenergy,
'peakforceangle': peakforceangle,
' latitude': latitude,
' longitude': longitude,
})
return 200, "", data
@login_required()
def strokedataform_v2(request, id=0):
id = encoder.decode_hex(id)
try:
w = Workout.objects.get(id=id)
except Workout.DoesNotExist: # pragma: no cover
raise Http404("Workout doesn't exist")
if request.method == 'GET':
form = StrokeDataForm()
return render(request, 'strokedata_form_v2.html',
{
'form': form,
'teams': get_my_teams(request.user),
'id': id,
'workout': w,
})
elif request.method == 'POST': # pragma: no cover
form = StrokeDataForm()
return render(request, 'strokedata_form_v2.html',
{
'form': form,
'teams': get_my_teams(request.user),
'id': id,
'workout': w,
}) # pragma: no cover
@csrf_exempt
@login_required()
@api_view(["POST"])
@permission_classes([IsAuthenticated])
def strokedatajson_v3(request):
"""
POST: Add Stroke data to workout
GET: Get stroke data of workout
This v2 API works on stroke based data dict:
{
"distance": 2100,
"elapsedTime": 592,
"duration": "0:09:52",
"name": "Test Workout (GO)",
"startdatetime": "2023-01-16 17:54:35.588838+00:00",
"workouttype": "water",
"boattype": "1x",
"notes": "some\nnotes",
"strokes": {"data": [
{"distance":5, "power": 112, "hr": 132, "pace": 145800, "spm": 11, "time": 0, "latitude":52.2264097,"longitude":6.8493638},
{"distance":12, "power": 221, "hr": 131, "pace": 116400, "spm": 41, "time": 2200, "latitude":52.2263474,"longitude":6.8495814},
{"distance":19, "power": 511, "hr": 131, "pace": 88100, "spm": 56, "time": 4599, "latitude":52.2262715,"longitude":6.8496975},
{"distance":27, "power": 673, "hr": 132, "pace": 80400, "spm": 59, "time": 7000, "latitude":52.2262003,"longitude":6.8498095},
{"distance":35, "power": 744, "hr": 133, "pace": 77700, "spm": 55, "time": 9599, "latitude":52.2261312, "longitude":6.8499267},
{"distance":43, "power": 754, "hr": 136, "pace": 77400, "spm": 48, "time": 12000, "latitude":52.2260576,"longitude":6.8500366},
{"distance":51, "power": 754, "hr": 139, "pace": 77400, "spm": 48, "time": 14400, "latitude":52.2259872,"longitude":6.8501561},
{"distance":59, "power": 749, "hr": 142, "pace": 77600, "spm": 48, "time": 16799, "latitude":52.2259154,"longitude":6.8502769},
{"distance":67, "power": 729, "hr": 145, "pace": 78300, "spm": 48, "time": 19400, "latitude":52.2257749,"longitude":6.8503918},
{"distance":75, "power": 729, "hr": 147, "pace": 78300, "spm": 48, "time": 21799, "latitude":52.2259154,"longitude":6.8502769},
{"distance":82, "power": 726, "hr": 150, "pace": 78400, "spm": 48, "time": 24200, "latitude":52.2259872,"longitude":6.8501561},
{"distance":90, "power": 709, "hr": 152, "pace": 79000, "spm": 48, "time": 26599, "latitude":52.2260576,"longitude":6.8500366},
{"distance":100, "power": 707, "hr": 153, "pace": 79100, "spm": 49, "time": 29000, "latitude":52.2261312,"longitude":6.8499267}]}
}
"""
if request.method != 'POST':
return HttpResponseNotAllowed("Method not supported") # pragma: no cover
dologging('apilog.log', request.user.username+" (strokedatajson_v3 POST)")
title = request.data.get('name','')
try:
elapsedTime = request.data['elapsedTime']
except KeyError:
try:
duration = request.data['duration']
try:
t = datetime.strptime(duration,"%H:%M:%S.%d")
except ValueError:
t = datetime.strptime(duration,"%H:%M:%S")
elapsedTime = 3600*t.hour+60.*t.minute+t.second+t.microsecond/1.e6
except:
return HttpResponse("Missing Elapsed Time", status=400)
try:
totalDistance = request.data['distance']
except KeyError:
return HttpResponse("Missing Total Distance", status=400)
timeZone = request.data.get('timezone','UTC')
workouttype = request.data.get('workouttype','rower')
boattype = request.data.get('boattype','1x')
notes = request.data.get('notes','')
rpe = request.data.get('rpe',0)
startdatetime = request.data.get('startdatetime',"%s" % timezone.now())
startdatetime = pendulum.parse(startdatetime)
dologging('apilog.log',workouttype)
dologging('apilog.log',boattype)
dologging('apilog.log',notes)
dologging('apilog.log',title)
dologging('apilog.log',totalDistance)
dologging('apilog.log',elapsedTime)
df = pd.DataFrame()
try:
strokes = request.data['strokes']
except KeyError:
return HttpResponse("No Stroke Data in JSON", status=400)
try:
df = pd.DataFrame(strokes['data'])
except KeyError:
try:
df = pd.DataFrame(request.data['strokedata'])
except:
return HttpResponse("No JSON Object could be decoded", status=400)
df.index = df.index.astype(int)
df.sort_index(inplace=True)
status, comment, data = api_get_dataframe(startdatetime, df)
if status != 200:
return HttpResponse(comment, status=status)
csvfilename = 'media/{code}.csv.gz'.format(code=uuid4().hex[:16])
_ = data.to_csv(csvfilename, index_label='index', compression='gzip')
duration = datetime.time(0,0,1)
w = Workout(
user=request.user.rower,
date=timezone.now().date(),
duration=duration)
w.save()
uploadoptions = {
'secret': UPLOAD_SERVICE_SECRET,
'user': request.user.id,
'file': csvfilename,
'title': title,
'workouttype': workouttype,
'boattype': boattype,
'elapsedTime': elapsedTime/1000., # in seconds
'totalDistance': totalDistance,
'rpe': rpe,
'notes': notes,
'timezone': timeZone,
'id': w.id,
}
_ = myqueue(queuehigh,
handle_post_workout_api,
uploadoptions)
workoutid = w.id
return JsonResponse(
{"workout public id": encoder.encode_hex(workoutid),
"workout id": workoutid,
"status": "success",
})
# Process the POSTed stroke data according to the API definition
# Return the GET stroke data according to the API definition
@csrf_exempt
@login_required()
@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def strokedatajson_v2(request, id):
"""
POST: Add Stroke data to workout
GET: Get stroke data of workout
This v2 API works on stroke based data dict:
{"data":
[
{"hr": 110, "p": 3600, "spm": 53, "d": 6, "t": 12},
{"hr": 111, "p": 3600, "spm": 53, "d": 6, "t": 12},
{"hr": 111, "p": 3600, "spm": 64, "d": 6, "t": 22},
{"hr": 110, "p": 3600, "spm": 16, "d": 14, "t": 55},
{"hr": 110, "p": 3600, "spm": 16, "d": 14, "t": 82},
{"hr": 107, "p": 3600, "spm": 12, "d": 22, "t": 109},
{"hr": 107, "p": 3600, "spm": 12, "d": 22, "t": 133},
{"hr": 108, "p": 3600, "spm": 12, "d": 32, "t": 157},
{"hr": 108, "p": 3577, "spm": 12, "d": 32, "t": 157},
{"hr": 108, "p": 3411, "spm": 12, "d": 32, "t": 157},
{"hr": 108, "p": 2649, "spm": 12, "d": 32, "t": 157},
{"hr": 108, "p": 3099, "spm": 12, "d": 32, "t": 157},
{"hr": 108, "p": 3600, "spm": 12, "d": 32, "t": 157},
{"hr": 100, "p": 3600, "spm": 44, "d": 115, "t": 292},
{"hr": 99, "p": 3600, "spm": 27, "d": 129, "t": 305},
{"hr": 97, "p": 3600, "spm": 34, "d": 161, "t": 330},
{"hr": 96, "p": 3600, "spm": 25, "d": 177, "t": 344},
{"hr": 96, "p": 3494, "spm": 43, "d": 196, "t": 357},
{"hr": 98, "p": 2927, "spm": 26, "d": 235, "t": 377},
{"hr": 102, "p": 2718, "spm": 27, "d": 380, "t": 455},
{"hr": 102, "p": 2753, "spm": 9, "d": 398, "t": 472},
{"hr": 102, "p": 2864, "spm": 61, "d": 406, "t": 477},
{"hr": 101, "p": 2780, "spm": 15, "d": 484, "t": 515},
{"hr": 101, "p": 2365, "spm": 16, "d": 583, "t": 554},
{"hr": 103, "p": 1965, "spm": 16, "d": 681, "t": 592},
]
}
"""
row = get_object_or_404(Workout, pk=id)
if row.user != request.user.rower: # pragma: no cover
return HttpResponse("You do not have permission to perform this action", status=403)
try:
id = int(id)
except ValueError: # pragma: no cover
return HttpResponse("Not a valid workout number", status=404)
if request.method == 'GET':
columns = ['spm', 'time', 'hr', 'pace', 'power', 'distance']
datadf = dataprep.getsmallrowdata_db(columns, ids=[id])
dologging('apilog.log',request.user.username+"(strokedatajson_v2 GET)")
data = datadf.to_json(orient='records')
data2 = json.loads(data)
data2 = {"data": data2}
return JSONResponse(data2)
if request.method == 'POST':
dologging('apilog.log',request.user.username+" (strokedatajson_v2 POST)")
try:
for d in request.data['data']:
dologging('apilog.log',json.dumps(d))
except KeyError:
try:
for d in request.data['strokedata']:
dologging('apilog.log',json.dumps(d))
except KeyError:
dologging('apilog.log','No data in request.data')
checkdata, r = dataprep.getrowdata_db(id=row.id)
if not checkdata.empty: # pragma: no cover
return HttpResponse("Duplicate Error", status=409)
df = pd.DataFrame()
try:
df = pd.DataFrame(request.data['data'])
except KeyError: # pragma: no cover
try:
df = pd.DataFrame(request.data['strokedata'])
except:
return HttpResponse("No JSON object could be decoded", status=400)
df.index = df.index.astype(int)
df.sort_index(inplace=True)
status, comment, data = api_get_dataframe(row.startdatetime, df)
if status != 200:
return HttpResponse(comment, status=status)
r = getrower(request.user)
timestr = row.startdatetime.strftime("%Y%m%d-%H%M%S")
csvfilename = 'media/Import_'+timestr+'.csv'
workoutdate = row.date
workoutstartdatetime = row.startdatetime
workoutenddatetime = workoutstartdatetime + \
datetime.timedelta(seconds=data[' ElapsedTime (sec)'].max())
duplicate = dataprep.checkduplicates(
r, workoutdate, workoutstartdatetime, workoutenddatetime)
if duplicate:
row.duplicate = True
row.save()
_ = data.to_csv(csvfilename+'.gz', index_label='index', compression='gzip')
row.csvfilename = csvfilename
row.save()
powerperc = 100*np.array([r.pw_ut2,
r.pw_ut1,
r.pw_at,
r.pw_tr, r.pw_an])/r.ftp
ftp = float(r.ftp)
if row.workouttype in mytypes.otwtypes:
ftp = ftp*(100.-r.otwslack)/100.
rr = rrower(hrmax=r.max, hrut2=r.ut2,
hrut1=r.ut1, hrat=r.at,
hrtr=r.tr, hran=r.an, ftp=ftp,
powerperc=powerperc, powerzones=r.powerzones)
rowdata = rdata(csvfile=row.csvfilename, rower=rr).df
datadf = dataprep.dataprep(
rowdata, id=row.id, bands=True, barchart=True, otwpower=True, empower=True)
_ = myqueue(queuehigh, handle_calctrimp, row.id,
row.csvfilename, r.ftp, r.sex, r.hrftp, r.max, r.rest)
isbreakthrough, ishard = dataprep.checkbreakthrough(row, r)
if r.getemailnotifications and not r.emailbounced: # pragma: no cover
link = settings.SITE_URL+reverse(
r.defaultlandingpage,
kwargs={
'id': encoder.encode_hex(row.id),
}
)
_ = send_confirm(r.user, row.name, link, '')
_ = uploads.do_sync(row, {}, quick=True)
dologging('apilog.log'," (strokedatajson_v2 POST completed successfully)")
return(JsonResponse(
{"workout public id": encoder.encode_hex(row.id),
"workout private id": row.id,
"status": "success",
}))
# return(HttpResponse(encoder.encode_hex(row.id),status=201))
return HttpResponseNotAllowed("Method not supported") # pragma: no cover
@csrf_exempt
@login_required()
@api_view(['GET', 'POST'])
@permission_classes([IsAuthenticated])
def strokedatajson(request, id=0):
"""
POST: Add Stroke data to workout
GET: Get stroke data of workout
"""
row = get_object_or_404(Workout, pk=id)
if row.user != request.user.rower: # pragma: no cover
raise PermissionDenied("You have no access to this workout")
try:
id = int(id)
except ValueError: # pragma: no cover
return HttpResponse("Not a valid workout number", status=403)
if request.method == 'GET':
# currently only returns a subset.
columns = ['spm', 'time', 'hr', 'pace', 'power', 'distance']
datadf = dataprep.getsmallrowdata_db(columns, ids=[id])
dologging("apilog.log",request.user.username+"(strokedatajson GET) ")
return JSONResponse(datadf)
if request.method == 'POST':
dologging("apilog.log",request.user.username+"(strokedatajson POST)")
checkdata, r = dataprep.getrowdata_db(id=row.id)
if not checkdata.empty: # pragma: no cover
return HttpResponse("Duplicate Error", status=409)
# strokedata = request.POST['strokedata']
# checking/validating and cleaning
try:
strokedata = json.loads(request.data)['strokedata']
except: # pragma: no cover
try:
s = json.dumps(request.data)
strokedata = json.loads(s)['strokedata']
except: # pragma: no cover
return HttpResponse("No JSON object could be decoded", status=400)
try:
df = pd.DataFrame(strokedata)
except ValueError: # pragma: no cover
return HttpResponse("Arrays must all be same length", status=400)
df.index = df.index.astype(int)
df.sort_index(inplace=True)
try:
time = df['time']/1.e3
except KeyError: # pragma: no cover
return HttpResponse("There must be time values", status=400)
aantal = len(time)
pace = df['pace']/1.e3
if len(pace) != aantal: # pragma: no cover
return HttpResponse("Pace array has incorrect length", status=400)
distance = df['distance']
if len(distance) != aantal: # pragma: no cover
return HttpResponse("Distance array has incorrect length", status=400)
spm = df['spm']
if len(spm) != aantal: # pragma: no cover
return HttpResponse("SPM array has incorrect length", status=400)
res = dataprep.testdata(time, distance, pace, spm)
if not res: # pragma: no cover
return HttpResponse("Data are not numerical", status=400)
power = trydf(df, aantal, 'power')
drivelength = trydf(df, aantal, 'drivelength')
dragfactor = trydf(df, aantal, 'dragfactor')
drivetime = trydf(df, aantal, 'drivetime')
strokerecoverytime = trydf(df, aantal, 'strokerecoverytime')
averagedriveforce = trydf(df, aantal, 'averagedriveforce')
peakdriveforce = trydf(df, aantal, 'peakdriveforce')
wash = trydf(df, aantal, 'wash')
catch = trydf(df, aantal, 'catch')
finish = trydf(df, aantal, 'finish')
peakforceangle = trydf(df, aantal, 'peakforceangle')
driveenergy = trydf(df, aantal, 'driveenergy')
slip = trydf(df, aantal, 'slip')
lapidx = trydf(df, aantal, 'lapidx')
hr = trydf(df, aantal, 'hr')
starttime = totimestamp(row.startdatetime)+time[0]
unixtime = starttime+time
dologging("apilog.log",request.user.username+"(POST)")
data = pd.DataFrame({'TimeStamp (sec)': unixtime,
' Horizontal (meters)': distance,
' Cadence (stokes/min)': spm,
' HRCur (bpm)': hr,
' DragFactor': dragfactor,
' Stroke500mPace (sec/500m)': pace,
' Power (watts)': power,
' DriveLength (meters)': drivelength,
' DriveTime (ms)': drivetime,
' StrokeRecoveryTime (ms)': strokerecoverytime,
' AverageDriveForce (lbs)': averagedriveforce,
' PeakDriveForce (lbs)': peakdriveforce,
' lapIdx': lapidx,
' ElapsedTime (sec)': time,
'catch': catch,
'slip': slip,
'finish': finish,
'wash': wash,
'driveenergy': driveenergy,
'peakforceangle': peakforceangle,
})
r = getrower(request.user)
timestr = row.startdatetime.strftime("%Y%m%d-%H%M%S")
csvfilename = 'media/Import_'+timestr+'.csv'
res = data.to_csv(csvfilename+'.gz', index_label='index',
compression='gzip')
row.csvfilename = csvfilename
row.save()
powerperc = 100*np.array([r.pw_ut2,
r.pw_ut1,
r.pw_at,
r.pw_tr, r.pw_an])/r.ftp
ftp = float(r.ftp)
if row.workouttype in mytypes.otwtypes:
ftp = ftp*(100.-r.otwslack)/100.
rr = rrower(hrmax=r.max, hrut2=r.ut2,
hrut1=r.ut1, hrat=r.at,
hrtr=r.tr, hran=r.an, ftp=ftp,
powerperc=powerperc, powerzones=r.powerzones)
rowdata = rdata(csvfile=row.csvfilename, rower=rr).df
datadf = dataprep.dataprep(
rowdata, id=row.id, bands=True, barchart=True, otwpower=True, empower=True)
# mangling
#
return HttpResponse(encoder.encode_hex(row.id), status=201)
# Method not supported
return HttpResponseNotAllowed("Method not supported") # pragma: no cover