Merge branch 'release/v20.3.0'
This commit is contained in:
@@ -3778,9 +3778,12 @@ def update_duplicates_on_delete(sender, instance, **kwargs):
|
||||
t = ww.duration
|
||||
delta = datetime.timedelta(
|
||||
hours=t.hour, minutes=t.minute, seconds=t.second)
|
||||
enddatetime = ww.startdatetime+delta
|
||||
if enddatetime > d.startdatetime:
|
||||
ws2.append(ww)
|
||||
try:
|
||||
enddatetime = ww.startdatetime+delta
|
||||
if enddatetime > d.startdatetime:
|
||||
ws2.append(ww)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
if len(ws2) == 0:
|
||||
d.duplicate = False
|
||||
|
||||
@@ -181,6 +181,30 @@ class OwnApi(TestCase):
|
||||
|
||||
self.assertEqual(response.status_code,200)
|
||||
|
||||
def test_strokedataform_tcx(self):
|
||||
login = self.c.login(username=self.u.username, password=self.password)
|
||||
self.assertTrue(login)
|
||||
|
||||
w = self.user_workouts[1]
|
||||
|
||||
url = reverse('strokedata_tcx')
|
||||
|
||||
with open('rowers/tests/testdata/crewnerddata.tcx') as f:
|
||||
tcxdata_str = f.read()
|
||||
|
||||
result = get_random_file(filename='rowers/tests/testdata/thyro.csv')
|
||||
|
||||
request = self.factory.post(url, data = tcxdata_str, content_type='application/xml')
|
||||
request.user = self.u
|
||||
request.content_type = 'application/xml'
|
||||
|
||||
force_authenticate(request, user=self.u)
|
||||
with patch('rowers.dataprep.getrowdata_db') as mock_getrowdata:
|
||||
mock_getrowdata.return_value = (pd.DataFrame(),None)
|
||||
response = strokedata_tcx(request)
|
||||
self.assertEqual(response.status_code,200)
|
||||
|
||||
|
||||
def test_strokedataform_v3(self):
|
||||
login = self.c.login(username=self.u.username, password=self.password)
|
||||
self.assertTrue(login)
|
||||
|
||||
BIN
rowers/tests/testdata/testdata.tcx.gz
vendored
BIN
rowers/tests/testdata/testdata.tcx.gz
vendored
Binary file not shown.
@@ -249,6 +249,8 @@ urlpatterns = [
|
||||
name='strokedatajson_v2'),
|
||||
re_path(r'^api/v3/workouts/$', views.strokedatajson_v3,
|
||||
name='strokedatajson_v3'),
|
||||
re_path(r'^api/TCX/workouts/$', views.strokedata_tcx,
|
||||
name='strokedata_tcx'),
|
||||
re_path(r'^500v/$', views.error500_view, name='error500_view'),
|
||||
re_path(r'^500q/$', views.servererror_view, name='servererror_view'),
|
||||
path('502/', TemplateView.as_view(template_name='502.html'), name='502'),
|
||||
|
||||
@@ -1,10 +1,27 @@
|
||||
from rowers.views.statements import *
|
||||
from rowers.tasks import handle_calctrimp
|
||||
from rowers.opaque import encoder
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
import arrow
|
||||
import pendulum
|
||||
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 datetime import datetime as dt
|
||||
|
||||
import rowingdata.tcxtools as tcxtools
|
||||
from rowingdata import TCXParser, rowingdata
|
||||
|
||||
|
||||
class XMLParser(BaseParser):
|
||||
media_type = "application/xml"
|
||||
|
||||
def parse(self, stream, media_type=None, parser_context=None):
|
||||
return ET.parse(stream).getroot()
|
||||
|
||||
# Stroke data form to test API upload
|
||||
|
||||
@@ -202,6 +219,106 @@ def strokedataform_v2(request, id=0):
|
||||
'workout': w,
|
||||
}) # pragma: no cover
|
||||
|
||||
def part_of_day(hour):
|
||||
if 5 <= hour < 12:
|
||||
return "Morning"
|
||||
elif 12 <= hour < 18:
|
||||
return "Afternoon"
|
||||
elif 18 <= hour < 24:
|
||||
return "Evening"
|
||||
else:
|
||||
return "Night"
|
||||
|
||||
@csrf_exempt
|
||||
@login_required()
|
||||
@api_view(["POST"])
|
||||
@permission_classes([IsAuthenticated])
|
||||
@parser_classes([XMLParser])
|
||||
def strokedata_tcx(request):
|
||||
"""
|
||||
Upload a TCX file through API
|
||||
"""
|
||||
if request.method != 'POST':
|
||||
return HttpResponseNotAllowed("Method not supported") # pragma: no cover
|
||||
|
||||
if request.content_type.lower() != 'application/xml':
|
||||
return HttpResponseNotAllowed("Need application/xml")
|
||||
|
||||
dologging('apilog.log', request.user.username+" (strokedatajson_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:
|
||||
dologging('apilog.log',e)
|
||||
return HttpResponseNotAllowed("Could not parse TCX data")
|
||||
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
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",
|
||||
})
|
||||
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@login_required()
|
||||
|
||||
Reference in New Issue
Block a user