first working version of exported session to intervals.icu
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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' %}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
Reference in New Issue
Block a user