1222 lines
43 KiB
Python
1222 lines
43 KiB
Python
from rowers.views.statements import *
|
|
from rowers.tasks import handle_calctrimp
|
|
from rowers.opaque import encoder
|
|
from rowers.courses import coursetokml, coursestokml
|
|
#from xml.etree import ElementTree as ET
|
|
from defusedxml import ElementTree as ET
|
|
|
|
import arrow
|
|
import pendulum
|
|
from pendulum.parsing.exceptions import ParserError
|
|
from rowsandall_app.settings import UPLOAD_SERVICE_SECRET, UPLOAD_SERVICE_URL
|
|
from rowers.dataroutines import get_workouttype_from_tcx, get_startdate_time_zone
|
|
|
|
from rest_framework.decorators import parser_classes
|
|
from rest_framework.parsers import BaseParser
|
|
|
|
from rowers.utils import geo_distance
|
|
|
|
from datetime import datetime as dt
|
|
|
|
import rowingdata.tcxtools as tcxtools
|
|
from rowingdata import TCXParser, rowingdata
|
|
from rowingdata import FITParser as FP
|
|
import arrow
|
|
|
|
import base64
|
|
|
|
# create a FITParser which parses the application/octet-stream and creates a fit file
|
|
class FITParser(BaseParser):
|
|
media_type = "application/octet-stream"
|
|
|
|
def parse(self, stream, media_type=None, parser_context=None):
|
|
try:
|
|
return stream.read()
|
|
except Exception as e:
|
|
dologging("apilog.log", "FIT Parser")
|
|
dolofging("apilog.log", e)
|
|
raise ValueError(f"Failed to read FIT file: {str(e)}")
|
|
|
|
return stream.read()
|
|
|
|
class XMLParser(BaseParser):
|
|
media_type = "application/xml"
|
|
|
|
def parse(self, stream, media_type=None, parser_context=None):
|
|
dologging("apilog.log", "XML Parser")
|
|
try:
|
|
s = ET.parse(stream).getroot()
|
|
except ET.XMLSyntaxError:
|
|
raise ValueError("XML Syntax Error")
|
|
except Exception as e: # pragma: no cover
|
|
dologging("apilog.log",e)
|
|
raise ValueError(f"Failed to parse XML file: {str(e)}")
|
|
return s
|
|
|
|
# Stroke data form to test API upload
|
|
|
|
@csrf_exempt
|
|
def javascript_log(request):
|
|
if request.method != 'POST':
|
|
message = {'status': 'false',
|
|
'message': 'this view cannot be accessed through GET'}
|
|
return JSONResponse(status=403, data=message)
|
|
|
|
# test if JSON
|
|
try:
|
|
json_data = json.loads(request.body)
|
|
secret = json_data['secret']
|
|
post_data = json_data
|
|
except:
|
|
q = request.POST
|
|
post_data = {k: q.getlist(k) if len(
|
|
q.getlist(k)) > 1 else v for k, v in q.items()}
|
|
|
|
# only allow local host
|
|
hostt = request.get_host().split(':')
|
|
if hostt[0] not in ['localhost', '127.0.0.1', 'dev.rowsandall.com', 'rowsandall.com']:
|
|
message = {'status': 'false',
|
|
'message': 'permission denied for host '+hostt[0]}
|
|
return JSONResponse(status=403, data=message)
|
|
|
|
# check credentials here
|
|
try:
|
|
secret = post_data['secret']
|
|
except KeyError:
|
|
dologging('own_api.log','Missing credentials')
|
|
message = {'status': 'false', 'message': 'missing credentials'}
|
|
return JSONResponse(status=400, data=message)
|
|
if secret != settings.LOG_SECRET:
|
|
message = {'status': 'false', 'message': 'invalid credentials'}
|
|
return JSONResponse(status=403, data=message)
|
|
|
|
try:
|
|
message = post_data['message']
|
|
except KeyError:
|
|
dologging('javascript_log.log','no message received')
|
|
message = {'status': 'false', 'message': 'no filename given'}
|
|
return JSONResponse(status=400, data=message)
|
|
|
|
dologging('javascript_log.log', message)
|
|
return JSONResponse(status=200, data = {'status': 'true', 'message': message})
|
|
|
|
@login_required()
|
|
@permission_required('rower.is_not_freecoach', fn=get_user_by_userid, raise_exception=True)
|
|
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, ColumnNotFoundError): # pragma: no cover
|
|
try:
|
|
time = df['t']/10.
|
|
except (KeyError, ColumnNotFoundError):
|
|
return 400, "Missing time", pl.DataFrame()
|
|
|
|
try:
|
|
spm = df['spm']
|
|
except (KeyError, ColumnNotFoundError): # pragma: no cover
|
|
return 400, "Missing spm", pl.DataFrame()
|
|
try:
|
|
distance = df['distance']
|
|
except (KeyError, ColumnNotFoundError): # pragma: no cover
|
|
try:
|
|
distance = df['d']/10.
|
|
except (KeyError, ColumnNotFoundError):
|
|
return 400, "Missing distance", pl.DataFrame()
|
|
|
|
try:
|
|
pace = df['pace']/1.e3
|
|
except (KeyError, ColumnNotFoundError): # pragma: no cover
|
|
try:
|
|
pace = df['p']/10.
|
|
except (KeyError, ColumnNotFoundError):
|
|
return 400, "Missing pace", pl.DataFrame()
|
|
|
|
|
|
try:
|
|
power = df['power']
|
|
except (KeyError, ColumnNotFoundError): # pragma: no cover
|
|
power = 0*time
|
|
try:
|
|
drivelength = df['drivelength']
|
|
except (KeyError, ColumnNotFoundError):
|
|
drivelength = 0*time
|
|
try:
|
|
dragfactor = df['dragfactor']
|
|
except (KeyError, ColumnNotFoundError):
|
|
dragfactor = 0*time
|
|
try:
|
|
drivetime = df['drivetime']
|
|
except (KeyError, ColumnNotFoundError):
|
|
drivetime = 0*time
|
|
try:
|
|
strokerecoverytime = df['strokerecoverytime']
|
|
except (KeyError, ColumnNotFoundError):
|
|
strokerecoverytime = 0*time
|
|
try:
|
|
averagedriveforce = df['averagedriveforce']
|
|
except (KeyError, ColumnNotFoundError):
|
|
averagedriveforce = 0*time
|
|
try:
|
|
peakdriveforce = df['peakdriveforce']
|
|
except (KeyError, ColumnNotFoundError):
|
|
peakdriveforce = 0*time
|
|
try:
|
|
wash = df['wash']
|
|
except (KeyError, ColumnNotFoundError):
|
|
wash = 0*time
|
|
try:
|
|
catch = df['catch']
|
|
except (KeyError, ColumnNotFoundError):
|
|
catch = 0*time
|
|
try:
|
|
finish = df['finish']
|
|
except (KeyError, ColumnNotFoundError):
|
|
finish = 0*time
|
|
try:
|
|
peakforceangle = df['peakforceangle']
|
|
except (KeyError, ColumnNotFoundError):
|
|
peakforceangle = 0*time
|
|
try:
|
|
driveenergy = df['driveenergy']
|
|
except (KeyError, ColumnNotFoundError):
|
|
driveenergy = 60.*power/spm
|
|
try:
|
|
slip = df['slip']
|
|
except (KeyError, ColumnNotFoundError):
|
|
slip = 0*time
|
|
try:
|
|
lapidx = df['lapidx']
|
|
except (KeyError, ColumnNotFoundError):
|
|
lapidx = 0*time
|
|
try:
|
|
hr = df['hr']
|
|
except (KeyError, ColumnNotFoundError): # pragma: no cover
|
|
hr = 0*df['time']
|
|
|
|
try:
|
|
latitude = df['latitude']
|
|
except (KeyError, ColumnNotFoundError):
|
|
latitude = 0*df['time']
|
|
|
|
try:
|
|
longitude = df['longitude']
|
|
except (KeyError, ColumnNotFoundError):
|
|
longitude = 0*df['time']
|
|
|
|
starttime = totimestamp(startdatetime)+time[0]
|
|
unixtime = starttime+time
|
|
|
|
dologging('apilog.log',"(strokedatajson_v2/3 POST - data parsed)")
|
|
|
|
data = pl.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, "Success", data
|
|
|
|
|
|
@login_required()
|
|
@permission_required('rower.is_not_freecoach', fn=get_user_by_userid, raise_exception=True)
|
|
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
|
|
|
|
def part_of_day(hour):
|
|
if 5 <= hour < 12:
|
|
return "Morning"
|
|
elif 12 <= hour < 18: # pragma: no cover
|
|
return "Afternoon"
|
|
elif 18 <= hour < 24: # pragma: no cover
|
|
return "Evening"
|
|
else: # pragma: no cover
|
|
return "Night"
|
|
|
|
# KML API views
|
|
"""
|
|
- Get a list of courses OK
|
|
- Nearby a certain coordinate
|
|
- Filtered by
|
|
- Get a (KML) course (in response.content rather than as attachment) OK
|
|
- Get multiple courses as one KML in response.content OK
|
|
- GET with parameters?
|
|
Optional, not for CN
|
|
- Create one or more new courses from KML
|
|
- Should check for duplicates (Placemark ID)
|
|
- Update one or more new courses from KML
|
|
"""
|
|
@api_view(["GET"])
|
|
@permission_classes([IsAuthenticated])
|
|
def course_list(request):
|
|
if request.method != 'GET': # pragma: no cover
|
|
dologging('apilog.log','{m} request to KML endpoint'.format(m=request.method))
|
|
return HttpResponseNotAllowed("Method not supported")
|
|
|
|
query_data = {}
|
|
name = request.GET.get('name')
|
|
distance = request.GET.get('course_distance')
|
|
latitude = request.GET.get('latitude')
|
|
longitude = request.GET.get('longitude')
|
|
distance_from = request.GET.get('distance_from')
|
|
if name is not None:
|
|
query_data['name__contains'] = name
|
|
if distance is not None:
|
|
try:
|
|
query_data['distance__lte'] = int(distance)+50
|
|
query_data['distance__gte'] = int(distance)-50
|
|
except:
|
|
pass
|
|
|
|
|
|
|
|
courses = GeoCourse.objects.filter(**query_data)
|
|
if latitude is not None and longitude is not None and distance_from is not None:
|
|
try:
|
|
newlist = []
|
|
for c in courses:
|
|
distance = geo_distance(float(latitude), float(longitude), c.coord[0], c.coord[1])[0]
|
|
if distance < float(distance_from): # pragma: no cover
|
|
newlist.append(c)
|
|
courses = newlist
|
|
except ValueError:
|
|
pass
|
|
|
|
|
|
courselist = []
|
|
for c in courses:
|
|
d = {
|
|
'id': c.id,
|
|
'name':c.name,
|
|
'country':c.country,
|
|
'distance':c.distance,
|
|
'notes':c.notes,
|
|
'location':(c.coord[0], c.coord[1]),
|
|
}
|
|
courselist.append(d)
|
|
|
|
response_dict = {'courses': courselist}
|
|
|
|
return JsonResponse(response_dict, content_type='application/json; charset=utf8')
|
|
|
|
@api_view(["GET"])
|
|
@permission_classes([IsAuthenticated])
|
|
def get_crewnerd_kml(request,id=0):
|
|
if request.method != 'GET': # pragma: no cover
|
|
dologging('apilog.log','{m} request to CrewNerd KML endpoint'.format(m=request.method))
|
|
return HttpResponseNotAllowed("Method not supported")
|
|
|
|
try:
|
|
c = GeoCourse.objects.get(id=id)
|
|
except GeoCourse.DoesNotExist: # pragma: no cover
|
|
raise Http404("This course does not exist")
|
|
|
|
kml = coursetokml(c, cn=True)
|
|
|
|
return HttpResponse(kml, content_type='text/xml')
|
|
|
|
@csrf_exempt
|
|
@api_view(["GET"])
|
|
@permission_classes([IsAuthenticated])
|
|
def get_crewnerd_multiple(request):
|
|
if request.method != 'GET': # pragma: no cover
|
|
dologging('apilog.log','{m} request to CrewNerd KML endpoint'.format(m=request.method))
|
|
return HttpResponseNotAllowed("Method not supported")
|
|
|
|
ids = request.GET.get('id')
|
|
if ids is not None:
|
|
tdict = dict(request.GET.lists())
|
|
idsnew = []
|
|
for id in tdict['id']:
|
|
try:
|
|
idsnew.append(int(id))
|
|
except ValueError:
|
|
pass
|
|
ids = idsnew
|
|
else:
|
|
gcs = GeoCourse.objects.all()
|
|
ids = [c.id for c in gcs]
|
|
|
|
kml = coursestokml(ids, cn=True)
|
|
|
|
return HttpResponse(kml, content_type='text/xml')
|
|
|
|
@csrf_exempt
|
|
@api_view(["GET"])
|
|
@permission_classes([IsAuthenticated])
|
|
def get_crewnerd_liked(request):
|
|
r = getrower(request.user)
|
|
if request.method != 'GET': # pragma: no cover
|
|
dologging('apilog.log','{m} request to CrewNerd KML endpoint'.format(m=request.method))
|
|
return HttpResponseNotAllowed("Method not supported")
|
|
|
|
ids = request.GET.get('id')
|
|
if ids is not None:
|
|
tdict = dict(request.GET.lists())
|
|
idsnew = []
|
|
for id in tdict['id']:
|
|
try:
|
|
idsnew.append(int(id))
|
|
except ValueError:
|
|
pass
|
|
ids = idsnew
|
|
else:
|
|
gcs = GeoCourse.objects.filter(followers=r)
|
|
ids = [c.id for c in gcs]
|
|
|
|
kml = coursestokml(ids, cn=True)
|
|
|
|
return HttpResponse(kml, content_type='text/xml')
|
|
|
|
# Stroke data views
|
|
|
|
@csrf_exempt
|
|
@logged_in_or_basicauth()
|
|
#@api_view(["POST"])
|
|
#@permission_required('rower.is_not_freecoach', fn=get_user_by_userid, raise_exception=True)
|
|
#@permission_classes([IsAuthenticated])
|
|
def strokedata_rowingdata(request):
|
|
"""
|
|
Upload a .csv file (rowingdata standard) through API, using Basic Auth
|
|
"""
|
|
r = getrower(request.user)
|
|
if r.rowerplan == 'freecoach':
|
|
return HttpResponseNotAllowed("This endpoint is for users, not for free coach accounts")
|
|
|
|
if request.method != 'POST':
|
|
return HttpResponseNotAllowed("Method not supported") # pragma: no cover
|
|
|
|
form = DocumentsForm(request.POST, request.FILES)
|
|
if not form.is_valid():
|
|
return HttpResponseBadRequest(json.dumps(form.errors))
|
|
|
|
f = form.cleaned_data['file']
|
|
if f is None:
|
|
return HttpResponseBadRequest("Missing file")
|
|
|
|
filename, completefilename = handle_uploaded_file(f)
|
|
|
|
uploadoptions = {
|
|
'secret': settings.UPLOAD_SERVICE_SECRET,
|
|
'user': r.user.id,
|
|
'file': completefilename,
|
|
'workouttype': form.cleaned_data['workouttype'],
|
|
'boattype': form.cleaned_data['boattype'],
|
|
'title': form.cleaned_data['title'],
|
|
'rpe': form.cleaned_data['rpe'],
|
|
'notes': form.cleaned_data['notes']
|
|
}
|
|
|
|
url = settings.UPLOAD_SERVICE_URL
|
|
|
|
_ = myqueue(queuehigh,
|
|
handle_request_post,
|
|
url,
|
|
uploadoptions)
|
|
response = JsonResponse(
|
|
{
|
|
"status": "success",
|
|
}
|
|
)
|
|
response.status_code = 201
|
|
return response
|
|
|
|
|
|
@csrf_exempt
|
|
@api_view(["POST"])
|
|
@permission_required('rower.is_not_freecoach', fn=get_user_by_userid, raise_exception=True)
|
|
@permission_classes([IsAuthenticated])
|
|
def strokedata_rowingdata_apikey(request):
|
|
"""
|
|
Upload a .csv file (rowingdata standard) through API, using
|
|
"""
|
|
|
|
r = getrower(request.user)
|
|
if r.rowerplan == 'freecoach':
|
|
return HttpResponseNotAllowed("This endpoint is for users, not for free coach accounts")
|
|
|
|
if request.method != 'POST':
|
|
return HttpResponseNotAllowed("Method not supported")
|
|
|
|
form = DocumentsForm(request.POST, request.FILES)
|
|
if not form.is_valid():
|
|
return HttpResponseBadRequest(json.dumps(form.errors))
|
|
|
|
f = form.cleaned_data['file']
|
|
if f is None:
|
|
return HttpResponseBadRequest("Missing file")
|
|
|
|
filename, completefilename = handle_uploaded_file(f)
|
|
|
|
uploadoptions = {
|
|
'secret': settings.UPLOAD_SERVICE_SECRET,
|
|
'user': r.user.id,
|
|
'file': completefilename,
|
|
'workouttype': form.cleaned_data['workouttype'],
|
|
'boattype': form.cleaned_data['boattype'],
|
|
'title': form.cleaned_data['title'],
|
|
'rpe': form.cleaned_data['rpe'],
|
|
'notes': form.cleaned_data['notes']
|
|
}
|
|
|
|
url = settings.UPLOAD_SERVICE_URL
|
|
|
|
_ = myqueue(queuehigh,
|
|
handle_request_post,
|
|
url,
|
|
uploadoptions)
|
|
response = JsonResponse(
|
|
{
|
|
"status": "success",
|
|
}
|
|
)
|
|
response.status_code = 201
|
|
return response
|
|
|
|
@csrf_exempt
|
|
@api_view(["POST"])
|
|
@permission_required('rower.is_not_freecoach', fn=get_user_by_userid, raise_exception=True)
|
|
@permission_classes([IsAuthenticated])
|
|
@parser_classes([FITParser])
|
|
def strokedata_fit(request):
|
|
"""
|
|
Handle a POST request to upload a binary FIT file and save it locally.
|
|
"""
|
|
if request.method != 'POST':
|
|
return HttpResponseBadRequest("Only POST requests are allowed.")
|
|
|
|
|
|
try:
|
|
fit_data = request.data
|
|
|
|
# Ensure the media directory exists
|
|
media_dir = 'media'
|
|
os.makedirs(media_dir, exist_ok=True)
|
|
|
|
# Generate a unique filename for the FIT file
|
|
fit_filename = os.path.join(media_dir, f'{uuid4().hex[:16]}.fit')
|
|
|
|
# Save the FIT file locally
|
|
with open(fit_filename, 'wb') as fit_file:
|
|
fit_file.write(fit_data)
|
|
except Exception as e:
|
|
return JsonResponse({
|
|
"status": "error",
|
|
"message": f"An error occurred while saving the FIT file: {str(e)}"
|
|
}, status=400)
|
|
|
|
try:
|
|
# Parse the FIT file
|
|
try:
|
|
row = FP(fit_filename)
|
|
except ValueError as e:
|
|
return JsonResponse({
|
|
"status": "error",
|
|
"message": f"An error occurred while parsing the FIT file: {str(e)}"
|
|
}, status=422)
|
|
|
|
rowdata = rowingdata(df=row.df)
|
|
|
|
duration = totaltime_sec_to_string(rowdata.duration)
|
|
distance = rowdata.df[" Horizontal (meters)"].iloc[-1]
|
|
title = ""
|
|
try:
|
|
startdatetime = rowdata.rowdatetime
|
|
startdate = startdatetime.date()
|
|
partofday = part_of_day(startdatetime.hour)
|
|
title = '{partofday} water'.format(partofday=partofday)
|
|
except Exception as e:
|
|
dologging('apilog.log','FIT error to get time')
|
|
dologging('apilog.log',e)
|
|
_ = myqueue(queuehigh, handle_sendemail_unrecognized, fit_filename, "fit parser")
|
|
|
|
w = Workout.objects.create(user=request.user.rower,
|
|
duration=duration,
|
|
distance=distance,
|
|
name=title,
|
|
date=startdate,
|
|
workouttype='water',)
|
|
|
|
uploadoptions = {
|
|
'secret': UPLOAD_SERVICE_SECRET,
|
|
'user': request.user.id,
|
|
'file': fit_filename,
|
|
'workouttype': 'water',
|
|
'boattype': '1x',
|
|
'title': title,
|
|
'rpe': 0,
|
|
'notes': '',
|
|
'id': w.id,
|
|
'offline': False,
|
|
}
|
|
|
|
url = UPLOAD_SERVICE_URL
|
|
|
|
_ = myqueue(queuehigh,
|
|
handle_request_post,
|
|
url,
|
|
uploadoptions)
|
|
|
|
return JsonResponse(
|
|
{"status": "success",
|
|
"workout public id": encoder.encode_hex(w.id),
|
|
"workout id": w.id,
|
|
})
|
|
except Exception as e:
|
|
dologging('apilog.log','FIT API endpoint')
|
|
dologging('apilog.log',e)
|
|
_ = myqueue(queuehigh, handle_sendemail_unrecognized, fit_filename, "fit parser")
|
|
return HttpResponse(status=500)
|
|
|
|
|
|
|
|
|
|
@csrf_exempt
|
|
#@login_required()
|
|
@api_view(["POST"])
|
|
@permission_required('rower.is_not_freecoach', fn=get_user_by_userid, raise_exception=True)
|
|
@permission_classes([IsAuthenticated])
|
|
@parser_classes([XMLParser])
|
|
def strokedata_tcx(request):
|
|
"""
|
|
Upload a TCX file through API
|
|
"""
|
|
if request.method != 'POST': # pragma: no cover
|
|
dologging('apilog.log','GET request to TCX endpoint')
|
|
return HttpResponseNotAllowed("Method not supported")
|
|
|
|
if 'application/xml' not in request.content_type.lower(): # pragma: no cover
|
|
dologging('apilog.log','POST data not application/xml, request to TCX endpoint')
|
|
dologging('apilog.log', request.content_type.lower())
|
|
return HttpResponseNotAllowed("Need application/xml")
|
|
|
|
dologging('apilog.log', request.user.username+" (strokedata_tcx POST)")
|
|
|
|
try:
|
|
tcxdata = request.data
|
|
activity_node = tcxdata.find(".//{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}Activity")
|
|
|
|
# Extract the activity start time
|
|
start_time_node = activity_node.find(".//{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}Id")
|
|
start_time_str = start_time_node.text
|
|
|
|
# Calculate the total duration of the entire activity
|
|
total_duration = 0
|
|
|
|
# Find all Lap nodes
|
|
lap_nodes = activity_node.findall(".//{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}Lap")
|
|
|
|
# Sum up the durations of all laps
|
|
for lap_node in lap_nodes:
|
|
lap_duration_node = lap_node.find(".//{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}TotalTimeSeconds")
|
|
lap_duration_seconds = float(lap_duration_node.text)
|
|
total_duration += lap_duration_seconds
|
|
|
|
except Exception as e: # pragma: no cover
|
|
dologging('apilog.log','TCX')
|
|
dologging('apilog.log',e)
|
|
if request.data:
|
|
tcxdata = request.data
|
|
tcxfilename = 'media/{code}.tcx'.format(code=uuid4().hex[:16])
|
|
try:
|
|
xml_string = ET.tostring(tcxdata, encoding='utf-8', method='xml').decode('utf-8')
|
|
except AttributeError:
|
|
return HttpResponseNotAllowed("Could not parse TCX data")
|
|
|
|
with open(tcxfilename, 'w', encoding='utf-8') as xml_file:
|
|
xml_file.write(xml_string)
|
|
_ = myqueue(queuehigh, handle_sendemail_unrecognized, tcxfilename, "tcx parser")
|
|
return HttpResponseNotAllowed("Could not parse TCX data")
|
|
|
|
|
|
try:
|
|
tcxfilename = 'media/{code}.tcx'.format(code=uuid4().hex[:16])
|
|
xml_string = ET.tostring(tcxdata, encoding='utf-8', method='xml').decode('utf-8')
|
|
|
|
with open(tcxfilename, 'w', encoding='utf-8') as xml_file:
|
|
xml_file.write(xml_string)
|
|
|
|
duration = totaltime_sec_to_string(total_duration)
|
|
title = "CrewNerd water"
|
|
try:
|
|
startdatetime = arrow.get(start_time_str)
|
|
#startdatetime = dt.strptime(start_time_str, "%Y-%m-%dT%H:%M:%S%z")
|
|
startdate = startdatetime.date()
|
|
partofday = part_of_day(startdatetime.hour)
|
|
title = '{partofday} water'.format(partofday=partofday)
|
|
except Exception as e:
|
|
dologging('apilog.log','TCX error to get time')
|
|
dologging('apilog.log',e)
|
|
_ = myqueue(queuehigh, handle_sendemail_unrecognized, tcxfilename, "tcx parser")
|
|
|
|
|
|
w = Workout(user=request.user.rower,
|
|
date=startdate,
|
|
name=title,
|
|
duration=duration)
|
|
w.save()
|
|
|
|
# need workouttype, duration
|
|
|
|
uploadoptions = {
|
|
'secret': UPLOAD_SERVICE_SECRET,
|
|
'user': request.user.id,
|
|
'file': tcxfilename,
|
|
'id': w.id,
|
|
'title': title,
|
|
'rpe': 0,
|
|
'workouttype': 'water',
|
|
'boattype': '1x',
|
|
'notes': '',
|
|
'offline': False,
|
|
}
|
|
|
|
|
|
_ = myqueue(queuehigh,
|
|
handle_post_workout_api,
|
|
uploadoptions)
|
|
|
|
workoutid = w.id
|
|
|
|
return JsonResponse(
|
|
{"workout public id": encoder.encode_hex(workoutid),
|
|
"workout id": workoutid,
|
|
"status": "success",
|
|
})
|
|
except Exception as e: # pragma: no cover
|
|
dologging('apilog.log','TCX API endpoint')
|
|
dologging('apilog.log',e)
|
|
_ = myqueue(queuehigh, handle_sendemail_unrecognized, tcxfilename, "tcx parser")
|
|
return HttpResponse(status=500)
|
|
|
|
|
|
|
|
|
|
@csrf_exempt
|
|
@api_view(["POST"])
|
|
#@login_required()
|
|
@permission_required('rower.is_not_freecoach', fn=get_user_by_userid, raise_exception=True)
|
|
@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, ColumnNotFoundError): # pragma: no cover
|
|
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, ColumnNotFoundError): # pragma: no cover
|
|
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())
|
|
|
|
try:
|
|
startdatetime = pendulum.parse(startdatetime)
|
|
except ParserError:
|
|
startdatetime = timezone.now()
|
|
|
|
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 = pl.DataFrame()
|
|
try:
|
|
strokes = request.data['strokes']
|
|
except (KeyError, ColumnNotFoundError): # pragma: no cover
|
|
return HttpResponse("No Stroke Data in JSON", status=400)
|
|
|
|
try:
|
|
df = pl.DataFrame(strokes['data'])
|
|
except (KeyError, ColumnNotFoundError): # pragma: no cover
|
|
try:
|
|
df = pl.DataFrame(request.data['strokedata'])
|
|
except:
|
|
return HttpResponse("No JSON Object could be decoded", status=400)
|
|
|
|
try:
|
|
df = df.sort("time")
|
|
except ColumnNotFoundError:
|
|
return HttpResponse("No time column", status=400)
|
|
|
|
status, comment, data = api_get_dataframe(startdatetime, df)
|
|
|
|
if status != 200: # pragma: no cover
|
|
return HttpResponse(comment, status=status)
|
|
|
|
|
|
csvfilename = 'media/{code}.csv.gz'.format(code=uuid4().hex[:16])
|
|
with gzip.open(csvfilename, 'w') as f:
|
|
_ = data.write_csv(f)
|
|
|
|
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()
|
|
@permission_required('rower.is_not_freecoach', fn=get_user_by_userid, raise_exception=True)
|
|
@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.read_data(columns, ids=[id])
|
|
datadf = dataprep.remove_nulls_pl(datadf)
|
|
dologging('apilog.log',request.user.username+"(strokedatajson_v2 GET)")
|
|
|
|
data = datadf.write_json(row_oriented=True)
|
|
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, ColumnNotFoundError): # pragma: no cover
|
|
try:
|
|
for d in request.data['strokedata']:
|
|
dologging('apilog.log',json.dumps(d))
|
|
except (KeyError, ColumnNotFoundError):
|
|
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 = pl.DataFrame()
|
|
|
|
try:
|
|
df = pl.DataFrame(request.data['data'])
|
|
except (KeyError, ColumnNotFoundError): # pragma: no cover
|
|
try:
|
|
df = pl.DataFrame(request.data['strokedata'])
|
|
except:
|
|
return HttpResponse("No JSON object could be decoded", status=400)
|
|
|
|
df = df.sort("time")
|
|
|
|
status, comment, data = api_get_dataframe(row.startdatetime, df)
|
|
if status != 200: # pragma: no cover
|
|
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()
|
|
|
|
with gzip.open(csvfilename+'.gz', 'w') as f:
|
|
_ = data.write_csv(f)
|
|
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.dataplep(
|
|
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()
|
|
@permission_required('rower.is_not_freecoach', fn=get_user_by_userid, raise_exception=True)
|
|
@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.read_data(columns, ids=[id])
|
|
datadf = dataprep.remove_nulls_pl(datadf)
|
|
datadf = datadf.to_pandas()
|
|
|
|
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 = pl.DataFrame(strokedata)
|
|
except ValueError: # pragma: no cover
|
|
return HttpResponse("Arrays must all be same length", status=400)
|
|
df = df.sort("time")
|
|
try:
|
|
time = df['time']/1.e3
|
|
except (KeyError, ColumnNotFoundError): # 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 = pl.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'
|
|
|
|
with gzip.open(csvfilename+'.gz','w') as f:
|
|
res = data.write_csv(f)
|
|
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.dataplep(
|
|
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
|