From 61030dad0794f89382f924e6285d6ad2d015634c Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Tue, 10 Dec 2024 20:00:52 +0100
Subject: [PATCH 1/2] intervals auto im & export
---
rowers/forms.py | 3 ++
rowers/integrations/intervals.py | 28 +++++++++++++--
rowers/management/commands/getsyncids.py | 6 ++--
rowers/management/commands/processemail.py | 11 ++++++
rowers/models.py | 4 ++-
rowers/templates/rower_exportsettings.html | 3 ++
rowers/uploads.py | 42 +++++++++++++---------
rowers/views/workoutviews.py | 11 ++++++
8 files changed, 87 insertions(+), 21 deletions(-)
diff --git a/rowers/forms.py b/rowers/forms.py
index fa3f1b4b..0ebc5fb3 100644
--- a/rowers/forms.py
+++ b/rowers/forms.py
@@ -554,6 +554,9 @@ class UploadOptionsForm(forms.Form):
upload_to_TrainingPeaks = forms.BooleanField(initial=False,
required=False,
label='Export to TrainingPeaks')
+ upload_to_Intervals = forms.BooleanField(initial=False,
+ required=False,
+ label='Export to Intervals')
# do_physics = forms.BooleanField(initial=False,required=False,label='Power Estimate (OTW)')
makeprivate = forms.BooleanField(initial=False, required=False,
label='Make Workout Private')
diff --git a/rowers/integrations/intervals.py b/rowers/integrations/intervals.py
index 2669907f..4437019e 100644
--- a/rowers/integrations/intervals.py
+++ b/rowers/integrations/intervals.py
@@ -183,6 +183,7 @@ class IntervalsIntegration(SyncIntegration):
thetype = mytypes.intervalsmapping[workout.workouttype]
response = requests.put(url, headers=headers, json={'type': thetype})
+
if response.status_code not in [200, 201]:
return 0
@@ -260,8 +261,31 @@ class IntervalsIntegration(SyncIntegration):
self.rower.intervals_token,
id)
- def get_workouts(workout, *args, **kwargs) -> list:
- return NotImplemented
+ return 1
+
+ def get_workouts(self, *args, **kwargs):
+ startdate = timezone.now() - timedelta(days=7)
+ enddate = timezone.now() + timedelta(days=1)
+ startdatestring = kwargs.get(startdate,"")
+ enddatestring = kwargs.get(enddate,"")
+
+ try:
+ startdate = arrow.get(startdatestring).datetime
+ except:
+ pass
+ try:
+ enddate = arrow.get(enddatestring).datetime
+ except:
+ pass
+
+ count = 0
+ workouts = self.get_workout_list(startdate=startdate, enddate=enddate)
+ for workout in workouts:
+ if workout['new'] == 'NEW':
+ self.get_workout(workout['id'])
+ count +=1
+
+ return count
def make_authorization_url(self, *args, **kwargs):
return super(IntervalsIntegration, self).make_authorization_url(*args, **kwargs)
diff --git a/rowers/management/commands/getsyncids.py b/rowers/management/commands/getsyncids.py
index a2f9bf75..806a7218 100644
--- a/rowers/management/commands/getsyncids.py
+++ b/rowers/management/commands/getsyncids.py
@@ -24,7 +24,8 @@ class Command(BaseCommand):
record.sporttracksid = w.uploadedtosporttracks
if w.uploadedtoc2:
record.c2id = w.uploadedtoc2
-
+ if w.uploadedtointervals:
+ record.intervalsid = w.uploadedtointervals
try:
record.save()
except IntegrityError:
@@ -52,7 +53,8 @@ class Command(BaseCommand):
record.sporttracksid = w.uploadedtosporttracks
if w.uploadedtoc2:
record.c2id = w.uploadedtoc2
-
+ if w.uploadedtointervals:
+ record.intervalsid = w.uploadedtointervals
try:
record.save()
except IntegrityError:
diff --git a/rowers/management/commands/processemail.py b/rowers/management/commands/processemail.py
index 6d12ff71..be02ab17 100644
--- a/rowers/management/commands/processemail.py
+++ b/rowers/management/commands/processemail.py
@@ -117,5 +117,16 @@ class Command(BaseCommand):
lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
dologging('processemail.log', ''.join('!! ' + line for line in lines))
+ rowers = Rower.objects.filter(intervals_auto_import=True)
+ for r in rowers:
+ try:
+ if user_is_not_basic(r.user) or user_is_coachee(r.user):
+ intervals_integration = IntervalsIntegration(r.user)
+ _ = intervals_integration.get_workouts()
+ except:
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
+ dologging('processemail.log', ''.join('!! ' + line for line in lines))
+
self.stdout.write(self.style.SUCCESS(
'Successfully processed email attachments'))
diff --git a/rowers/models.py b/rowers/models.py
index 259f9f55..f4715081 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -4557,7 +4557,9 @@ class RowerExportForm(ModelForm):
'strava_auto_import',
'strava_auto_delete',
'trainingpeaks_auto_export',
- 'rp3_auto_import'
+ 'rp3_auto_import',
+ 'intervals_auto_import',
+ 'intervals_auto_export',
]
# Simple form to set rower's Functional Threshold Power
diff --git a/rowers/templates/rower_exportsettings.html b/rowers/templates/rower_exportsettings.html
index 866a771a..3ad0019d 100644
--- a/rowers/templates/rower_exportsettings.html
+++ b/rowers/templates/rower_exportsettings.html
@@ -37,6 +37,9 @@
{% if rower.rojabo_token is not None and rower.rojabo_token != '' %}
Rojabo
{% endif %}
+ {% if rower.intervals_token is not None and rower.intervals_token != '' %}
+ Intervals.icu
+ {% endif %}
{% if form.errors %}
diff --git a/rowers/uploads.py b/rowers/uploads.py
index b13881a9..bb587c21 100644
--- a/rowers/uploads.py
+++ b/rowers/uploads.py
@@ -130,11 +130,14 @@ def make_plot(r, w, f1, f2, plottype, title, imagename='', plotnr=0):
def do_sync(w, options, quick=False):
- do_strava_export = w.user.strava_auto_export
- try:
- do_strava_export = options['upload_to_Strava'] or do_strava_export
- except KeyError:
- pass
+ do_strava_export = False
+ if w.user.strava_auto_export is True:
+ do_strava_export = True
+ else:
+ try:
+ do_strava_export = options['upload_to_Strava'] or do_strava_export
+ except KeyError:
+ pass
try:
if options['stravaid'] != 0 and options['stravaid'] != '': # pragma: no cover
@@ -146,11 +149,15 @@ def do_sync(w, options, quick=False):
except KeyError:
pass
- do_icu_export = w.user.intervals_auto_export
- try:
- do_icu_export = options['upload_to_Intervals'] or do_icu_export
- except KeyError:
- pass
+ do_icu_export = False
+ if w.user.intervals_auto_export is True:
+ do_icu_export = True
+ else:
+ try:
+ do_icu_export = options['upload_to_Intervals']
+ except KeyError:
+ pass
+
try:
if options['intervalsid'] != 0 and options['intervalsid'] != '': # pragma: no cover
@@ -193,11 +200,14 @@ def do_sync(w, options, quick=False):
except KeyError:
pass
- do_c2_export = w.user.c2_auto_export
- try:
- do_c2_export = options['upload_to_C2'] or do_c2_export
- except KeyError:
- pass
+ do_c2_export = False
+ if w.user.c2_auto_export is True:
+ do_c2_export = True
+ else:
+ try:
+ do_c2_export = options['upload_to_C2'] or do_c2_export
+ except KeyError:
+ pass
try:
if options['c2id'] != 0 and options['c2id'] != '': # pragma: no cover
@@ -266,7 +276,7 @@ def do_sync(w, options, quick=False):
id = 0
message = "Please connect to Intervals.icu first"
except Exception as e:
- dologging(
+ dologging(
'intervals.icu.log',
e
)
diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py
index a250e4aa..14112726 100644
--- a/rowers/views/workoutviews.py
+++ b/rowers/views/workoutviews.py
@@ -5247,6 +5247,7 @@ def workout_upload_view(request,
upload_to_strava = uploadoptions.get('upload_to_Strava', False)
upload_to_st = uploadoptions.get('upload_to_SportTracks', False)
upload_to_tp = uploadoptions.get('upload_to_TrainingPeaks', False)
+ upload_to_intervals = uploadoptions.get('upload_to_Intervals', False)
response = {}
if request.method == 'POST':
@@ -5296,6 +5297,7 @@ def workout_upload_view(request,
upload_to_strava = optionsform.cleaned_data['upload_to_Strava']
upload_to_st = optionsform.cleaned_data['upload_to_SportTracks']
upload_to_tp = optionsform.cleaned_data['upload_to_TrainingPeaks']
+ upload_to_intervals = optionsform.cleaned_data['upload_to_Intervals']
makeprivate = optionsform.cleaned_data['makeprivate']
landingpage = optionsform.cleaned_data['landingpage']
raceid = optionsform.cleaned_data['raceid']
@@ -5313,6 +5315,7 @@ def workout_upload_view(request,
'upload_to_Strava': upload_to_strava,
'upload_to_SportTracks': upload_to_st,
'upload_to_TrainingPeaks': upload_to_tp,
+ 'upload_to_Intervals': upload_to_intervals,
'landingpage': landingpage,
'boattype': boattype,
'rpe': rpe,
@@ -5447,6 +5450,14 @@ def workout_upload_view(request,
message = "Please connect to TrainingPeaks first"
messages.error(request, message)
+ if (upload_to_intervals):
+ intervals_integration = IntervalsIntegration(request.user)
+ try:
+ id = intervals_integration.workout_export(w)
+ except NoTokenError:
+ message = "Please connect to Intervals.icu first"
+ messages.error(request, message)
+
if int(registrationid) < 0: # pragma: no cover
race = VirtualRace.objects.get(id=-int(registrationid))
if race.sessiontype == 'race':
From 8327e51841804638b4000790ca6dd86ee5199f66 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Wed, 11 Dec 2024 08:07:13 +0100
Subject: [PATCH 2/2] testing
---
rowers/templates/rower_exportsettings.html | 4 ++--
rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 3999 bytes
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/rowers/templates/rower_exportsettings.html b/rowers/templates/rower_exportsettings.html
index 3ad0019d..b6aae035 100644
--- a/rowers/templates/rower_exportsettings.html
+++ b/rowers/templates/rower_exportsettings.html
@@ -32,10 +32,10 @@
Strava,
{% endif %}
{% if rower.rp3token is not None and rower.rp3token != '' %}
- RP3
+ RP3,
{% endif %}
{% if rower.rojabo_token is not None and rower.rojabo_token != '' %}
- Rojabo
+ Rojabo,
{% endif %}
{% if rower.intervals_token is not None and rower.intervals_token != '' %}
Intervals.icu
diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz
index 4ab2f8bd4647adef840c754fefc2d20fa0e34e9e..6ae0e30902e4a847a9bea105dfbe010424bba57f 100644
GIT binary patch
literal 3999
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
F0RYMsI8guq
literal 4000
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
GfB^t2@GX7-