Private
Public Access
1
0

first working version of exported session to intervals.icu

This commit is contained in:
2024-12-18 20:35:46 +01:00
parent 946a9dc2a5
commit e6cc169cef
11 changed files with 166 additions and 53 deletions

View File

@@ -103,6 +103,7 @@ class InstantPlanSelectForm(forms.Form):
initial=timezone.now()+datetime.timedelta(days=21), initial=timezone.now()+datetime.timedelta(days=21),
widget=AdminDateWidget(), # format='%Y-%m-%d'), widget=AdminDateWidget(), # format='%Y-%m-%d'),
label='End Date') label='End Date')
plan_past_days = forms.BooleanField(initial=False, required=False, label='Insert sessions for the past')
target = forms.ChoiceField(required=False) target = forms.ChoiceField(required=False)
datechoice = forms.ChoiceField(choices=datechoices, initial='enddate', label='Plan by target, start or end date', datechoice = forms.ChoiceField(choices=datechoices, initial='enddate', label='Plan by target, start or end date',
widget=forms.RadioSelect) widget=forms.RadioSelect)

View File

@@ -370,5 +370,37 @@ class IntervalsIntegration(SyncIntegration):
return data return data
def plannedsession_create(self, ps, *args, **kwargs):
_ = self.open()
r = self.rower
headers = {
'Authorization': 'Bearer ' + r.intervals_token,
}
stepstext = ps.steps_intervals()
data = {
"start_date_local": ps.preferreddate.strftime('%Y-%m-%dT%H:%M:%S'),
"type": mytypes.intervalsmapping[ps.sessionsport],
"category": "WORKOUT",
"end_date_local": ps.preferreddate.strftime('%Y-%m-%d') + 'T23:59:59',
"name": ps.name,
"description": stepstext,
"indoor": ps.sessionsport in mytypes.ergtypes,
}
url = self.oauth_data['base_url'] + 'athlete/0/events'
response = requests.post(url, headers=headers, json=data)
if response.status_code != 200:
dologging('intervals.icu.log', response.text)
return 0
data = response.json()
id = data['id']
ps.intervalsid = id
ps.save()
return id

View File

@@ -5,7 +5,7 @@ from rowers.courseutils import coordinate_in_path
from rowers.utils import ( from rowers.utils import (
# workflowleftpanel, workflowmiddlepanel, # workflowleftpanel, workflowmiddlepanel,
defaultleft, defaultmiddle, landingpages, landingpages2, defaultleft, defaultmiddle, landingpages, landingpages2,
steps_read_fit, steps_write_fit, ps_dict_order, uniqify steps_read_fit, steps_write_fit, steps_read_intervals, ps_dict_order, uniqify
) )
from rowers.metrics import axlabels from rowers.metrics import axlabels
from rowers.utils import geo_distance, move_one_meter from rowers.utils import geo_distance, move_one_meter
@@ -2000,12 +2000,18 @@ class TrainingPlan(models.Model):
delete_sessions = kwargs.pop('delete_sessions', False) delete_sessions = kwargs.pop('delete_sessions', False)
delete_all_sessions = kwargs.pop('delete_all_sessions', False) delete_all_sessions = kwargs.pop('delete_all_sessions', False)
if delete_sessions: if delete_sessions:
sessions = PlannedSession.objects.filter(from_plan=self) sessions = PlannedSession.objects.filter(from_plan=self).exclude(
sessiontype__in=['race','indoorrace']
)
for s in sessions: for s in sessions:
s.delete() s.delete()
if delete_all_sessions: if delete_all_sessions:
sessions = PlannedSession.objects.filter(startdate__gte=self.startdate,enddate__lte=self.enddate) sessions = PlannedSession.objects.filter(
startdate__gte=self.startdate,enddate__lte=self.enddate,manager=self.manager.user
).exclude(
sessiontype__in=['race','indoorrace']
)
for s in sessions: for s in sessions:
s.delete() s.delete()
@@ -2940,6 +2946,10 @@ class PlannedSession(models.Model):
self.save() self.save()
def steps_intervals(self, *args, **kwargs):
s = steps_read_intervals(settings.MEDIA_ROOT+'/'+self.fitfile.name)
return s
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if self.sessionvalue <= 0: # pragma: no cover if self.sessionvalue <= 0: # pragma: no cover
self.sessionvalue = 1 self.sessionvalue = 1

View File

@@ -1069,6 +1069,7 @@ def get_workouts_session(r, ps):
return ws return ws
def create_sessions_from_json(plansteps, rower, startdate, manager, planbyrscore=False, plan=None, def create_sessions_from_json(plansteps, rower, startdate, manager, planbyrscore=False, plan=None,
plan_past_days=False,
asynchronous=False, queue=queue): asynchronous=False, queue=queue):
trainingdays = plansteps['trainingDays'] trainingdays = plansteps['trainingDays']
planstartdate = startdate planstartdate = startdate
@@ -1087,28 +1088,27 @@ def create_sessions_from_json(plansteps, rower, startdate, manager, planbyrscore
if planbyrscore: if planbyrscore:
sessionmode = 'rScore' sessionmode = 'rScore'
ps = PlannedSession( if plan_past_days or startdate >= timezone.now().date():
startdate=preferreddate - ps = PlannedSession(
timedelta(days=preferreddate.weekday()), startdate=preferreddate - timedelta(days=preferreddate.weekday()),
enddate=preferreddate + enddate=preferreddate + timedelta(days=-preferreddate.weekday()-1, weeks=1),
timedelta(days=-preferreddate.weekday()-1, weeks=1), preferreddate=preferreddate,
preferreddate=preferreddate, sessionsport=sessionsport, # change this
sessionsport=sessionsport, # change this name=workout['workoutName'],
name=workout['workoutName'], steps=workout,
steps=workout, manager=manager,
manager=manager, sessionmode=sessionmode,
sessionmode=sessionmode, comment=workout['description'],
comment=workout['description'], from_plan=plan,
from_plan=plan, )
)
ps.save()
ps.save() add_rower_session(rower, ps)
add_rower_session(rower, ps)
return return
# async version # async version
_ = myqueue(queue, create_sessions_from_json_async, plansteps, rower, startdate, manager, planbyrscore, plan) _ = myqueue(queue, create_sessions_from_json_async, plansteps, rower, startdate, manager, planbyrscore, plan, plan_past_days)

View File

@@ -374,7 +374,7 @@ def handle_assignworkouts(workouts, rowers, remove_workout, debug=False, **kwarg
return 1 return 1
@app.task @app.task
def create_sessions_from_json_async(plansteps, rower, startdate, manager, planbyrscore, plan, debug=False, **kwargs): def create_sessions_from_json_async(plansteps, rower, startdate, manager, planbyrscore, plan, plan_past_days, debug=False, **kwargs):
trainingdays = plansteps['trainingDays'] trainingdays = plansteps['trainingDays']
planstartdate = startdate planstartdate = startdate
for day in trainingdays: for day in trainingdays:
@@ -391,32 +391,39 @@ def create_sessions_from_json_async(plansteps, rower, startdate, manager, planby
if planbyrscore: if planbyrscore:
sessionmode = 'rScore' sessionmode = 'rScore'
ps = PlannedSession( create_session = False
startdate=preferreddate - if plan_past_days:
timedelta(days=preferreddate.weekday()), create_session = True
enddate=preferreddate + elif preferreddate >= timezone.now().date():
timedelta(days=-preferreddate.weekday()-1, weeks=1), create_session = True
preferreddate=preferreddate,
sessionsport=sessionsport, # change this
name=workout['workoutName'],
steps=workout,
manager=manager,
sessionmode=sessionmode,
comment=workout['description'],
from_plan=plan,
)
ps.save() if create_session:
ps = PlannedSession(
startdate=preferreddate -
timedelta(days=preferreddate.weekday()),
enddate=preferreddate +
timedelta(days=-preferreddate.weekday()-1, weeks=1),
preferreddate=preferreddate,
sessionsport=sessionsport, # change this
name=workout['workoutName'],
steps=workout,
manager=manager,
sessionmode=sessionmode,
comment=workout['description'],
from_plan=plan,
)
teams = Team.objects.filter(manager=ps.manager)
members = Rower.objects.filter(team__in=teams).distinct()
if rower in members and rower.rowerplan != 'freecoach':
ps.rower.add(rower)
ps.save()
elif ps.manager.rower == rower and rower.rowerplan != 'freecoach':
ps.rower.add(rower)
ps.save() ps.save()
teams = Team.objects.filter(manager=ps.manager)
members = Rower.objects.filter(team__in=teams).distinct()
if rower in members and rower.rowerplan != 'freecoach':
ps.rower.add(rower)
ps.save()
elif ps.manager.rower == rower and rower.rowerplan != 'freecoach':
ps.rower.add(rower)
ps.save()
return 1 return 1
@app.task @app.task

View File

@@ -23,6 +23,7 @@
{% else %} {% else %}
<a href="/rowers/sessions/{{ psdict.id.1 }}/togarmin/?next={{ request.path|urlencode }}"><i class="fas fa-watch-fitness fa-fw"></i> Export to Garmin</a> <a href="/rowers/sessions/{{ psdict.id.1 }}/togarmin/?next={{ request.path|urlencode }}"><i class="fas fa-watch-fitness fa-fw"></i> Export to Garmin</a>
{% endif %} {% endif %}
<a href="/rowers/sessions/{{ psdict.id.1 }}/tointervals/?next={{ request.path|urlencode }}"><i class="fas fa-cloud fa-fw"></i> Export to intervals.icu</a>
</p> </p>
{% endif %} {% endif %}
<h1>Session {{ psdict.name.1 }}</h1> <h1>Session {{ psdict.name.1 }}</h1>
@@ -46,10 +47,10 @@
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
</table> </table>
{% if steps %} {% if steps %}
<h2>Steps</h2> <h2>Steps</h2>
<p>{{ steps|safe }}</p> <p>{{ steps|safe }}</p>
{% endif %} {% endif %}
</li> </li>
<li class="grid_2"> <li class="grid_2">
{% if plannedsession.sessiontype == 'test' or plannedsession.sessiontype == 'coursetest' or plannedsession.sessiontype == 'fastest_distance' or plannedsession.sessiontype == 'fastest_time' %} {% if plannedsession.sessiontype == 'test' or plannedsession.sessiontype == 'coursetest' or plannedsession.sessiontype == 'fastest_distance' or plannedsession.sessiontype == 'fastest_time' %}

View File

@@ -1852,7 +1852,10 @@ description: ""
response = self.c.get(url) response = self.c.get(url)
self.assertEqual(response.status_code,200) self.assertEqual(response.status_code,200)
form = {} form = {
'delete_sessions': 1,
'delete_all_sessions': 0,
}
response = self.c.post(url,form) response = self.c.post(url,form)
self.assertEqual(response.status_code,302) self.assertEqual(response.status_code,302)

View File

@@ -1015,6 +1015,8 @@ urlpatterns = [
name='plannedsession_totemplate_view'), name='plannedsession_totemplate_view'),
re_path(r'^sessions/(?P<id>\d+)/togarmin/$', views.plannedsession_togarmin_view, re_path(r'^sessions/(?P<id>\d+)/togarmin/$', views.plannedsession_togarmin_view,
name='plannedsession_togarmin_view'), name='plannedsession_togarmin_view'),
re_path(r'^sessions/(?P<id>\d+)/tointervals/$', views.plannedsession_tointervals_view,
name='plannedsession_tointervals_view'),
re_path(r'^sessions/(?P<id>\d+)/compare/$', re_path(r'^sessions/(?P<id>\d+)/compare/$',
views.plannedsession_compare_view, views.plannedsession_compare_view,
name='plannedsession_compare_view'), name='plannedsession_compare_view'),

View File

@@ -717,6 +717,20 @@ def steps_read_fit(filename, name='', sport='Custom'): # pragma: no cover
return d return d
def steps_read_intervals(filename, name='', sport='Custom'): # pragma: no cover
authorizationstring = 'Bearer '+settings.WORKOUTS_FIT_TOKEN
url = settings.WORKOUTS_FIT_URL+"/tointervals"
headers = {'Authorization': authorizationstring}
response = requests.post(url=url, headers=headers,
json={'filename': filename})
if response.status_code != 200: # pragma: no cover
return None
w = response.text
return w
def steps_write_fit(steps): def steps_write_fit(steps):
authorizationstring = 'Bearer '+settings.WORKOUTS_FIT_TOKEN authorizationstring = 'Bearer '+settings.WORKOUTS_FIT_TOKEN

View File

@@ -7,6 +7,7 @@ from rowers.views.statements import *
from rowers.plannedsessions import get_dates_timeperiod from rowers.plannedsessions import get_dates_timeperiod
from rowers.tasks import fetch_strava_workout from rowers.tasks import fetch_strava_workout
from rowers.utils import NoTokenError from rowers.utils import NoTokenError
from rowers.models import PlannedSession
import rowers.integrations.strava as strava import rowers.integrations.strava as strava
from rowers.integrations import importsources from rowers.integrations import importsources

View File

@@ -2052,6 +2052,41 @@ def plannedsession_templateedit_view(request, id=0):
'steps': steps, 'steps': steps,
}) })
@permission_required('plannedsession.change_session', fn=get_session_by_pk, raise_exception=True)
@user_passes_test(can_plan, login_url="/rowers/paidplans/",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
def plannedsession_tointervals_view(request, id=0):
r = getrequestplanrower(request)
startdate, enddate = get_dates_timeperiod(request)
startdate = startdate.date()
enddate = enddate.date()
ps = get_object_or_404(PlannedSession, pk=id)
intervals = IntervalsIntegration(request.user)
result = intervals.plannedsession_create(ps)
if not result: # pragma: no cover
messages.error(
request, 'You failed to export your session to Intervals')
else:
messages.info(
request, 'Session is now on Intervals.')
url = reverse(plannedsession_view, kwargs={'userid': r.user.id,
'id': ps.id, })
startdatestring = startdate.strftime('%Y-%m-%d')
enddatestring = enddate.strftime('%Y-%m-%d')
url += '?when='+startdatestring+'/'+enddatestring
next = request.GET.get('next', url)
return HttpResponseRedirect(next)
@permission_required('plannedsession.change_session', fn=get_session_by_pk, raise_exception=True) @permission_required('plannedsession.change_session', fn=get_session_by_pk, raise_exception=True)
@user_passes_test(can_plan, login_url="/rowers/paidplans/", @user_passes_test(can_plan, login_url="/rowers/paidplans/",
@@ -2486,6 +2521,7 @@ def plannedsession_view(request, id=0, userid=0):
if ps.steps: # pragma: no cover if ps.steps: # pragma: no cover
d = ps.steps d = ps.steps
steps = ps_dict_get_description_html(d, short=False) steps = ps_dict_get_description_html(d, short=False)
steps_intervals = ps.steps_intervals()
return render(request, 'plannedsessionview.html', return render(request, 'plannedsessionview.html',
{ {
@@ -2516,6 +2552,7 @@ def plannedsession_view(request, id=0, userid=0):
'coursediv': coursediv, 'coursediv': coursediv,
'comments': comments, 'comments': comments,
'steps': steps, 'steps': steps,
'steps_intervals': steps_intervals,
} }
) )
@@ -2707,6 +2744,7 @@ def rower_view_instantplan(request, id='', userid=0):
startdate = form.cleaned_data['startdate'] startdate = form.cleaned_data['startdate']
notes = form.cleaned_data['notes'] notes = form.cleaned_data['notes']
datechoice = form.cleaned_data['datechoice'] datechoice = form.cleaned_data['datechoice']
plan_past_days = form.cleaned_data['plan_past_days']
status = True status = True
if target and datechoice == 'target': # pragma: no cover if target and datechoice == 'target': # pragma: no cover
@@ -2726,10 +2764,14 @@ def rower_view_instantplan(request, id='', userid=0):
notes=notes, notes=notes,
) )
if not plan_past_days:
p.startdate = timezone.now().date()
p.save() p.save()
p.rowers.add(r) p.rowers.add(r)
create_sessions_from_json(plansteps, r, startdate, r.user, planbyrscore=byrscore, plan=p, asynchronous=True) create_sessions_from_json(plansteps, r, startdate, r.user, planbyrscore=byrscore,
plan=p, plan_past_days = plan_past_days, asynchronous=True)
messages.info(request, 'Your Sessions have been added') messages.info(request, 'Your Sessions have been added')
@@ -3336,8 +3378,8 @@ class TrainingPlanDelete(DeleteView):
success_url = reverse_lazy(rower_create_trainingplan) success_url = reverse_lazy(rower_create_trainingplan)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
delete_sessions = request.POST.get('delete_sessions') delete_sessions = request.POST.get('delete_sessions',0)
delete_all_sessions = request.POST.get('delete_all_sessions') delete_all_sessions = request.POST.get('delete_all_sessions',0)
self.object = self.get_object() self.object = self.get_object()
self.object.delete(delete_sessions=delete_sessions, delete_all_sessions=delete_all_sessions) self.object.delete(delete_sessions=delete_sessions, delete_all_sessions=delete_all_sessions)
return HttpResponseRedirect(self.get_success_url()) return HttpResponseRedirect(self.get_success_url())