diff --git a/rowers/dataprep.py b/rowers/dataprep.py
index 827a9717..28bf84be 100644
--- a/rowers/dataprep.py
+++ b/rowers/dataprep.py
@@ -369,7 +369,7 @@ def workout_summary_to_df(
return df
-def resample(id, r, parent, overwrite='copy'):
+def resample(id, r, parent, overwrite=False):
data, row = getrowdata_db(id=id)
messages = []
@@ -393,7 +393,7 @@ def resample(id, r, parent, overwrite='copy'):
data['pace'] = data['pace'] / 1000.
data['time'] = data['time'] / 1000.
- if overwrite == 'overwrite':
+ if overwrite == True:
# remove CP data
try:
cpfile = 'media/cpdata_{id}.parquet.gz'.format(id=parent.id)
diff --git a/rowers/forms.py b/rowers/forms.py
index 0ebc5fb3..506f900f 100644
--- a/rowers/forms.py
+++ b/rowers/forms.py
@@ -67,13 +67,12 @@ class FlexibleDecimalField(forms.DecimalField):
class ResampleForm(forms.Form):
- resamplechoices = (
- ('overwrite', 'Overwrite Workout'),
- ('copy', 'Create a Duplicate Workout')
- )
-
+ # add resamplechoice field, the result is a True or False boolean, labels are "overwrite" and "create copy"
resamplechoice = forms.ChoiceField(
- initial='copy', choices=resamplechoices, label='Copy behavior')
+ required=True,
+ choices=((True, 'overwrite'), (False, 'create copy')),
+ label='Resample choice',
+ widget=forms.RadioSelect)
class TrainingZonesForm(forms.Form):
@@ -582,6 +581,11 @@ class UploadOptionsForm(forms.Form):
races = VirtualRace.objects.filter(
registration_closure__gt=timezone.now())
+ # set upload_to_X based on r.X_auto_export
+ for field in ['C2', 'Strava', 'SportTracks', 'TrainingPeaks', 'Intervals']:
+ if getattr(r, field.lower()+'_auto_export') and r.rowerplan in ['pro', 'plan','coach']:
+ self.fields['upload_to_'+field].initial = True
+
registrations = IndoorVirtualRaceResult.objects.filter(
race__in=races,
userid=r.id)
@@ -665,6 +669,10 @@ class TeamUploadOptionsForm(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 TrainingPeaks')
# 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 4e0ac680..7db883ae 100644
--- a/rowers/integrations/intervals.py
+++ b/rowers/integrations/intervals.py
@@ -17,6 +17,7 @@ import os
from uuid import uuid4
from django.utils import timezone
from datetime import timedelta
+import rowers.dataprep as dataprep
from rowsandall_app.settings import (
INTERVALS_CLIENT_ID, INTERVALS_REDIRECT_URI, INTERVALS_CLIENT_SECRET, SITE_URL
@@ -101,7 +102,16 @@ class IntervalsIntegration(SyncIntegration):
def createworkoutdata(self, w, *args, **kwargs) -> str:
dozip = kwargs.get('dozip', True)
- filename = w.csvfilename
+ # resample if wanted by user, not tested
+ if w.user.intervals_resample_to_1s:
+ datadf, id, msgs = dataprep.resample(
+ w.id, w.user, w, overwrite=False
+ )
+ w_resampled = Workout.objects.get(id=id)
+ filename = w_resampled.csvfilename
+ else:
+ w_resampled = None
+ filename = w.csvfilename
try:
row = rowingdata(csvfile=filename)
except IOError: # pragma: no cover
@@ -128,6 +138,8 @@ class IntervalsIntegration(SyncIntegration):
except TypeError:
newnotes = 'from'+w.workoutsource+' via rowsandall.com'
+ if w.user.intervals_resample_to_1s and w_resampled:
+ w_resampled.delete()
row.exporttotcx(tcxfilename, notes=newnotes, sport=mytypes.intervalsmapping[w.workouttype])
if dozip:
gzfilename = tcxfilename + '.gz'
diff --git a/rowers/models.py b/rowers/models.py
index f4715081..047d8d0a 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -1174,6 +1174,7 @@ class Rower(models.Model):
c2_auto_import = models.BooleanField(default=False)
intervals_auto_export = models.BooleanField(default=False)
intervals_auto_import = models.BooleanField(default=False)
+ intervals_resample_to_1s = models.BooleanField(default=False, verbose_name='Resample to 1s on export')
sporttrackstoken = models.CharField(
default='', max_length=200, blank=True, null=True)
sporttrackstokenexpirydate = models.DateTimeField(blank=True, null=True)
@@ -4560,7 +4561,78 @@ class RowerExportForm(ModelForm):
'rp3_auto_import',
'intervals_auto_import',
'intervals_auto_export',
+ 'intervals_resample_to_1s'
]
+
+class RowerExportFormStrava(ModelForm):
+ class Meta:
+ model = Rower
+ fields = [
+ 'stravaexportas',
+ 'strava_auto_export',
+ 'strava_auto_import',
+ 'strava_auto_delete',
+ ]
+
+class RowerExportFormIntervals(ModelForm):
+ class Meta:
+ model = Rower
+ fields = [
+ 'intervals_auto_import',
+ 'intervals_auto_export',
+ 'intervals_resample_to_1s',
+ ]
+
+class RowerExportFormGarmin(ModelForm):
+ class Meta:
+ model = Rower
+ fields = [
+ 'garminactivity',
+ ]
+
+class RowerExportFormPolar(ModelForm):
+ class Meta:
+ model = Rower
+ fields = [
+ 'polar_auto_import',
+ ]
+
+class RowerExportFormConcept2(ModelForm):
+ class Meta:
+ model = Rower
+ fields = [
+ 'c2_auto_export',
+ 'c2_auto_import',
+ ]
+
+class RowerExportFormSportTracks(ModelForm):
+ class Meta:
+ model = Rower
+ fields = [
+ 'sporttracks_auto_export',
+ ]
+
+class RowerExportFormTrainingPeaks(ModelForm):
+ class Meta:
+ model = Rower
+ fields = [
+ 'trainingpeaks_auto_export',
+ ]
+
+class RowerExportFormRP3(ModelForm):
+ class Meta:
+ model = Rower
+ fields = [
+ 'rp3_auto_import',
+ ]
+
+class RowerExportFormNK(ModelForm):
+ class Meta:
+ model = Rower
+ fields = [
+ 'nk_auto_import'
+ ]
+
# Simple form to set rower's Functional Threshold Power
class SimpleRowerPowerForm(ModelForm):
diff --git a/rowers/templates/rower_exportsettings.html b/rowers/templates/rower_exportsettings.html
index b6aae035..13418d15 100644
--- a/rowers/templates/rower_exportsettings.html
+++ b/rowers/templates/rower_exportsettings.html
@@ -7,8 +7,10 @@
{% block main %}
Import and Export Settings for {{ rower.user.first_name }} {{ rower.user.last_name }}
+
+
{% endblock %}
diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz
index f15cf389..f05208b7 100644
Binary files a/rowers/tests/testdata/testdata.tcx.gz and b/rowers/tests/testdata/testdata.tcx.gz differ
diff --git a/rowers/uploads.py b/rowers/uploads.py
index 71f07553..fae902d5 100644
--- a/rowers/uploads.py
+++ b/rowers/uploads.py
@@ -245,23 +245,6 @@ def do_sync(w, options, quick=False):
dologging('c2_log.log','Error C2')
pass
- if do_strava_export: # pragma: no cover
- strava_integration = StravaIntegration(w.user.user)
- try:
- id = strava_integration.workout_export(w)
- dologging(
- 'strava_export_log.log',
- 'exporting workout {id} as {type}'.format(
- id=w.id,
- type=w.workouttype,
- )
- )
- except NoTokenError: # pragma: no cover
- id = 0
- message = "Please connect to Strava first"
- except Exception as e:
- dologging('stravalog.log', e)
-
if do_icu_export:
intervals_integration = IntervalsIntegration(w.user.user)
try:
@@ -334,4 +317,23 @@ def do_sync(w, options, quick=False):
dologging('tp_export.log','No Token Error')
return 0
+ # we do Strava last.
+ if do_strava_export: # pragma: no cover
+ strava_integration = StravaIntegration(w.user.user)
+ try:
+ id = strava_integration.workout_export(w)
+ dologging(
+ 'strava_export_log.log',
+ 'exporting workout {id} as {type}'.format(
+ id=w.id,
+ type=w.workouttype,
+ )
+ )
+ except NoTokenError: # pragma: no cover
+ id = 0
+ message = "Please connect to Strava first"
+ except Exception as e:
+ dologging('stravalog.log', e)
+
+
return 1
diff --git a/rowers/urls.py b/rowers/urls.py
index 5a4484da..06047059 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -662,6 +662,7 @@ urlpatterns = [
re_path(r'^me/messages/$', views.user_messages, name='user_messages'),
re_path(r'^me/messages/delete/$', views.user_messages_delete_all, name='user_messages_delete_all'),
re_path(r'^me/messages/(?P\d+)/markread/$', views.user_message_markread, name='user_message_markread'),
+ re_path(r'^me/messages/(?P\d+)/delete/$', views.user_message_delete, name='user_message_delete'),
re_path(r'^me/messages/user/(?P\d+)/$', views.user_messages, name='user_messages'),
re_path(r'^me/delete/$', views.remove_user, name='remove_user'),
re_path(r'^survey/$', views.survey, name='survey'),
diff --git a/rowers/views/statements.py b/rowers/views/statements.py
index 01771ce6..09ac2fa1 100644
--- a/rowers/views/statements.py
+++ b/rowers/views/statements.py
@@ -179,7 +179,13 @@ from rowers.models import ( RowerPowerForm, RowerHRZonesForm, SimpleRowerPowerFo
IndoorVirtualRaceForm, PlannedSessionCommentForm, Alert,
Condition, StaticChartRowerForm, FollowerForm,
VirtualRaceAthleteForm, InstantPlanForm, DataRowerForm,
- StepEditorForm, iDokladToken )
+ StepEditorForm, iDokladToken,
+ RowerExportFormStrava, RowerExportFormPolar,
+ RowerExportFormSportTracks, RowerExportFormTrainingPeaks,
+ RowerExportFormConcept2, RowerExportFormGarmin,
+ RowerExportFormIntervals, RowerExportFormRP3,
+ RowerExportFormNK,
+ )
from rowers.models import (
FavoriteForm, BaseFavoriteFormSet, SiteAnnouncement, BasePlannedSessionFormSet,
get_course_timezone, BaseConditionFormSet,
diff --git a/rowers/views/userviews.py b/rowers/views/userviews.py
index 629be7a2..cf004032 100644
--- a/rowers/views/userviews.py
+++ b/rowers/views/userviews.py
@@ -279,7 +279,6 @@ def user_message_delete(request,id=0): # pragma: no cover
messages.error(request,'Could not find this message')
url = reverse('user_messages')
return HttpResponseRedirect(url)
-
if msg.receiver == request.user.rower:
msg.delete()
@@ -458,19 +457,43 @@ def rower_exportsettings_view(request, userid=0):
'polar_auto_import': 'polartoken',
'c2_auto_export': 'c2token',
'c2_auto_import': 'c2token',
- 'runkeeper_auto_export': 'runkeepertoken',
'sporttracks_auto_export': 'sporttrackstoken',
'strava_auto_export': 'stravatoken',
'strava_auto_import': 'stravatoken',
'strava_auto_delete': 'stravatoken',
'trainingpeaks_auto_export': 'tptoken',
'rp3_auto_import': 'rp3token',
- 'nk_auto_import': 'nktoken'
+ 'nk_auto_import': 'nktoken',
+ 'intervals_auto_export': 'intervals_token',
+ 'intervals_resample_to_1s': 'intervals_token',
}
r = getrequestrowercoachee(request, userid=userid)
+
+ forms = {
+ 'polar': RowerExportFormPolar(instance=r),
+ 'c2': RowerExportFormConcept2(instance=r),
+ 'sporttracks': RowerExportFormSportTracks(instance=r),
+ 'strava': RowerExportFormStrava(instance=r),
+ 'trainingpeaks': RowerExportFormTrainingPeaks(instance=r),
+ 'rp3': RowerExportFormRP3(instance=r),
+ 'intervals': RowerExportFormIntervals(instance=r),
+ 'nk': RowerExportFormNK(instance=r),
+ 'garmin': RowerExportFormGarmin(instance=r),
+ }
if request.method == 'POST':
form = RowerExportForm(request.POST)
+ forms = {
+ 'polar': RowerExportFormPolar(request.POST, instance=r),
+ 'c2': RowerExportFormConcept2(request.POST, instance=r),
+ 'sporttracks': RowerExportFormSportTracks(request.POST, instance=r),
+ 'strava': RowerExportFormStrava(request.POST, instance=r),
+ 'trainingpeaks': RowerExportFormTrainingPeaks(request.POST, instance=r),
+ 'rp3': RowerExportFormRP3(request.POST, instance=r),
+ 'intervals': RowerExportFormIntervals(request.POST, instance=r),
+ 'nk': RowerExportFormNK(request.POST, instance=r),
+ 'garmin': RowerExportFormGarmin(request.POST, instance=r),
+ }
if form.is_valid():
cd = form.cleaned_data
if r.rowerplan == 'basic': # pragma: no cover
@@ -529,6 +552,7 @@ def rower_exportsettings_view(request, userid=0):
return render(request, 'rower_exportsettings.html',
{'form': form,
+ 'forms': forms,
'rower': r,
'breadcrumbs': breadcrumbs,
'grants': grants,
diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py
index 14112726..0412c7ff 100644
--- a/rowers/views/workoutviews.py
+++ b/rowers/views/workoutviews.py
@@ -5609,17 +5609,6 @@ def workout_upload_view(request,
return response
else:
if not is_ajax:
- if r.c2_auto_export and ispromember(r.user): # pragma: no cover
- uploadoptions['upload_to_C2'] = True
-
- if r.strava_auto_export and ispromember(r.user): # pragma: no cover
- uploadoptions['upload_to_Strava'] = True
-
- if r.sporttracks_auto_export and ispromember(r.user): # pragma: no cover
- uploadoptions['upload_to_SportTracks'] = True
-
- if r.trainingpeaks_auto_export and ispromember(r.user): # pragma: no cover
- uploadoptions['upload_to_TrainingPeaks'] = True
form = DocumentsForm(initial=docformoptions)
optionsform = UploadOptionsForm(initial=uploadoptions,
diff --git a/static/img/intervals_logo_with_name.png b/static/img/intervals_logo_with_name.png
new file mode 100644
index 00000000..b448dd4e
Binary files /dev/null and b/static/img/intervals_logo_with_name.png differ