Private
Public Access
1
0

Merge branch 'release/v22.4.7'

This commit is contained in:
2024-12-14 14:14:54 +01:00
12 changed files with 285 additions and 103 deletions
+2 -2
View File
@@ -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)
+14 -6
View File
@@ -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')
+12
View File
@@ -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,6 +102,15 @@ class IntervalsIntegration(SyncIntegration):
def createworkoutdata(self, w, *args, **kwargs) -> str:
dozip = kwargs.get('dozip', True)
# 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)
@@ -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'
+72
View File
@@ -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,8 +4561,79 @@ 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):
otwftp = forms.IntegerField(initial=0,required=True, label='FTP on water')
+117 -49
View File
@@ -7,8 +7,10 @@
{% block main %}
<h1>Import and Export Settings for {{ rower.user.first_name }} {{ rower.user.last_name }}</h1>
<form enctype="multipart/form-data" action="" method="post">
{% csrf_token %}
<ul class="main-content">
<li class="grid_2">
<li class="grid_4">
<p>You are currently connected to:
{% if rower.c2token is not None and rower.c2token != '' %}
Concept2 Logbook,
@@ -41,44 +43,133 @@
Intervals.icu
{% endif %}
</p>
<p>
Click on the icons to establish the connection or to renew the authorization.
</p>
</li>
<li class="grid_4">
<h2>API Key</h2>
<p>{{ apikey }}</p>
<p>
<a href="/rowers/me/regenerateapikey/">Regenerate</a>
</p>
<p>This API key can be used to access the Rowsandall API. It is used by some third party applications to access your data. Keep it secret.</p>
</li>
{% if form.errors %}
<p style="color: red;">
{% if form.errors %}
<li class="rounder">
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
<p>
<form enctype="multipart/form-data" action="" method="post">
</p>
</li>
{% endif %}
<li class="rounder">
<h2>NK</h2>
<table>
{{ form.as_table }}
</table>
{% csrf_token %}
{{ forms.nk.as_table }}
<input type="submit" value="Save">
</form>
</p>
{% if rower.garmintoken and rower.garmintoken != '' %}
<p>
<em>You are connected to Garmin.</em> Switching off Garmin Connect sync is on the
<a href="https://connect.garmin.com/modern/settings/accountInformation">Account settings</a>
page. Look for the "Rowsandall" app.
</p>
{% endif %}
<p>
</table>
<p><a href="/rowers/me/nkauthorize/"><img src="/static/img/NKLiNKLogbook.png" alt="connect with NK Logbook" width="120"></a></p>
</li>
<li class="rounder">
<h2>Concept2</h2>
<table>
{{ forms.c2.as_table }}
<input type="submit" value="Save">
</table>
<p><a href="/rowers/me/c2authorize/"><img src="/static/img/blueC2logo.png" alt="connect with Concept2" width="120"></a></p>
</li>
<li class="rounder">
<h2>RP3</h2>
<table>
{{ forms.rp3.as_table }}
<input type="submit" value="Save">
</table>
<p><a href="/rowers/me/rp3authorize"><img src="/static/img/logo-rp3-full-black.png"
alt="connect with RP3" width="130"></a></p>
</li>
<li class="rounder">
<h2>Rojabo</h2>
<p><a href="/rowers/me/rojaboauthorize"><img src="/static/img/rojabo.png"
alt="connect with Rojabo" width="130"></a></p>
</li>
<li class="rounder">
<h2>Intervals.icu</h2>
<table>
{{ forms.intervals.as_table }}
<input type="submit" value="Save">
</table>
<p><a href="/rowers/me/intervalsauthorize"><img src="/static/img/intervals_logo_with_name.png"
alt="connect with intervals.icu"></a></p>
</li>
<li class="rounder">
<h2>SportTracks</h2>
<table>
{{ forms.sporttracks.as_table }}
<input type="submit" value="Save">
</table>
<p><a href="/rowers/me/sporttracksauthorize/"><img src="/static/img/sporttracks-button.png" alt="connect with SportTracks" width="120"></a></p>
</li>
<li class="rounder">
<h2>TrainingPeaks</h2>
<table>
{{ forms.trainingpeaks.as_table }}
<input type="submit" value="Save">
</table>
<p><a href="/rowers/me/tpauthorize/"><img src="/static/img/TP_logo_horz_2_color.png"
alt="connect with Polar" width="130"></a></p>
</li>
<li class="rounder">
<h2>Polar</h2>
<table>
{{ forms.polar.as_table }}
<input type="submit" value="Save">
</table>
<p><a href="/rowers/me/polarauthorize/"><img src="/static/img/Polar_connectwith_btn_white.png"
alt="connect with Polar" width="130"></a></p>
</li>
<li class="rounder">
<h2>Garmin Connect</h2>
<table>
{{ forms.garmin.as_table }}
<input type="submit" value="Save">
</table>
<p><a href="/rowers/me/garminauthorize"><img src="/static/img/garmin_badge_130.png"
alt="connect with Garmin" width="130"></a></p>
<p>
Garmin Connnect has no manual sync, so connecting your account to your Garmin account will
automatically auto-sync workouts from Garmin to Rowsandall (but not in the other direction). If you
want to export our structured workout sessions to your Garmin device, you have to set the "Garmin Activity"
to a activity type that is supported by your watch. Not all watches support "Custom" activities, so
you may have to set your activity to Run or Ride while rowing.
</p>
<p>
</p>
{% if rower.garmintoken and rower.garmintoken != '' %}
<p>
<em>You are connected to Garmin.</em> Switching off Garmin Connect sync is on the
<a href="https://connect.garmin.com/modern/settings/accountInformation">Account settings</a>
page. Look for the "Rowsandall" app.
</p>
{% endif %}
</li>
<li class="rounder">
<h2>Strava</h2>
<p><input type="submit" value="Save"></p>
{{ forms.strava.as_p }}
<p><a href="/rowers/me/stravaauthorize/"><img src="/static/img/ConnectWithStrava.png" alt="connect with strava" width="120"></a></p>
<p>
Strava Auto Import also imports activity changes on Strava to Rowsandall, except when you delete
a workout on Strava. If you want Deletions to propagate to Rowsandall, tick the Strava Auto Delete
check box.
</p>
</p>
</li>
<li class="grid_2">
{% if grants %}
<li class="rounder">
<h2>Applications</h2>
<p>
These applications have access to your Rowsandall data.
</p>
<table width="100%">
<thead>
<tr>
@@ -99,35 +190,12 @@
{% endfor %}
</tbody>
</table>
<li>
{% endif %}
<h2>API Key</h2>
<p>{{ apikey }}</p>
<p>
<a href="/rowers/me/regenerateapikey/">Regenerate</a>
</p>
This API key can be used to access the Rowsandall API. It is used by some third party applications to access your data. Keep it secret.
</li>
</ul>
<p>Click on one of the icons below to connect to the service of your
choice or to renew the authorization.</p>
<p><a href="/rowers/me/stravaauthorize/"><img src="/static/img/ConnectWithStrava.png" alt="connect with strava" width="120"></a></p>
<p><a href="/rowers/me/c2authorize/"><img src="/static/img/blueC2logo.png" alt="connect with Concept2" width="120"></a></p>
<p><a href="/rowers/me/nkauthorize/"><img src="/static/img/NKLiNKLogbook.png" alt="connect with NK Logbook" width="120"></a></p>
<p><a href="/rowers/me/sporttracksauthorize/"><img src="/static/img/sporttracks-button.png" alt="connect with SportTracks" width="120"></a></p>
<p><a href="/rowers/me/polarauthorize/"><img src="/static/img/Polar_connectwith_btn_white.png"
alt="connect with Polar" width="130"></a></p>
<p><a href="/rowers/me/tpauthorize/"><img src="/static/img/TP_logo_horz_2_color.png"
alt="connect with Polar" width="130"></a></p>
</form>
<p><a href="/rowers/me/garminauthorize"><img src="/static/img/garmin_badge_130.png"
alt="connect with Garmin" width="130"></a></p>
<p><a href="/rowers/me/rp3authorize"><img src="/static/img/logo-rp3-full-black.png"
alt="connect with RP3" width="130"></a></p>
<p><a href="/rowers/me/rojaboauthorize"><img src="/static/img/rojabo.png"
alt="connect with Rojabo" width="130"></a></p>
<p><a href="/rowers/me/intervalsauthorize"><img src="/static/img/intervals_icu.png"
alt="connect with intervals.icu" height="30"></a></p>
{% endblock %}
Binary file not shown.
+19 -17
View File
@@ -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
+1
View File
@@ -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<id>\d+)/markread/$', views.user_message_markread, name='user_message_markread'),
re_path(r'^me/messages/(?P<id>\d+)/delete/$', views.user_message_delete, name='user_message_delete'),
re_path(r'^me/messages/user/(?P<userid>\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'),
+7 -1
View File
@@ -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,
+27 -3
View File
@@ -280,7 +280,6 @@ def user_message_delete(request,id=0): # pragma: no cover
url = reverse('user_messages')
return HttpResponseRedirect(url)
if msg.receiver == request.user.rower:
msg.delete()
messages.info(request,'Deleted message {id}'.format(id=id))
@@ -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,
-11
View File
@@ -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,
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB