From 6da1be55d03917e086755925573e8fcbf5ad9cc5 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 6 Mar 2021 13:40:16 +0100 Subject: [PATCH 1/4] fix steps to string for target watt --- rowers/utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rowers/utils.py b/rowers/utils.py index c12d71d5..961b29ee 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -901,6 +901,10 @@ def step_to_string(step): if value < 10 and value>0: target = 'Target: Power in zone {v}'.format(v=value) + elif value > 10 and value < 1000: + target = 'Target: Power at {v} % of FTP'.format(v=value) + elif value > 1000: + target = 'Target: Power at {v} Watt'.format(v=value-1000) else: if valuelow < 1000: target = 'Target: Power between {l} and {h} % of FTP'.format( From b18d04e088312b07a8722a0478a1817d4da051e9 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 10 Mar 2021 06:35:15 +0100 Subject: [PATCH 2/4] instant plans proto page attached workout notification using encoded workout id --- requirements.txt | 2 +- rowers/plannedsessions.py | 4 ++- rowers/templates/instantplans.html | 24 +++++++++++++++ rowers/urls.py | 1 + rowers/views/planviews.py | 48 ++++++++++++++++++++++++++++++ 5 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 rowers/templates/instantplans.html diff --git a/requirements.txt b/requirements.txt index de167e15..674068c8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -184,7 +184,7 @@ ratelim==0.1.6 redis==3.5.3 requests==2.23.0 requests-oauthlib==1.2.0 -rowingdata==3.1.8 +rowingdata==3.2.7 rowingphysics==0.5.0 rq==0.13.0 rules==2.1 diff --git a/rowers/plannedsessions.py b/rowers/plannedsessions.py index 16790573..243e79a7 100644 --- a/rowers/plannedsessions.py +++ b/rowers/plannedsessions.py @@ -30,6 +30,8 @@ import pandas as pd from rowingdata import rowingdata as rrdata from rowingdata import rower as rrower +from rowers.opaque import encoder + def to_pace(pace): minutes, seconds = divmod(pace,60) seconds, rest = divmod(seconds, 1) @@ -455,7 +457,7 @@ def add_workouts_plannedsession(ws,ps,r): w.plannedsession = ps w.save() result += 1 - comments.append('Attached workout %i to session' % w.id) + comments.append('Attached workout %s to session' % encoder.encode_hex(w.id)) if ps.sessiontype == 'coursetest': record = CourseTestResult( userid=w.user.id, diff --git a/rowers/templates/instantplans.html b/rowers/templates/instantplans.html new file mode 100644 index 00000000..b421fd6b --- /dev/null +++ b/rowers/templates/instantplans.html @@ -0,0 +1,24 @@ +{% extends "newbase.html" %} +{% load staticfiles %} +{% load rowerfilters %} + +{% block title %}Rowsandall Training Plans{% endblock %} + + +{% block main %} +

Training Plans

+ + + +{% endblock %} + +{% block sidebar %} +{% include 'menu_plan.html' %} +{% endblock %} diff --git a/rowers/urls.py b/rowers/urls.py index 2e7bcd48..9bb36e53 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -740,6 +740,7 @@ urlpatterns = [ re_path(r'^test\_callback',views.rower_process_testcallback,name='rower_process_testcallback'), re_path(r'^createplan/$',views.rower_create_trainingplan,name='rower_create_trainingplan'), re_path(r'^createplan/user/(?P\d+)/$',views.rower_create_trainingplan,name='rower_create_trainingplan'), + re_path(r'^plans/$', views.rower_select_instantplan, name='rower_select_instantplan'), re_path(r'^deleteplan/(?P\d+)/$',login_required( views.TrainingPlanDelete.as_view()),name='trainingplan_delete_view'), re_path(r'^deletemicrocycle/(?P\d+)/$',login_required( diff --git a/rowers/views/planviews.py b/rowers/views/planviews.py index a8de7582..3b2bc520 100644 --- a/rowers/views/planviews.py +++ b/rowers/views/planviews.py @@ -2425,6 +2425,54 @@ class PlannedSessionDelete(DeleteView): return obj +@user_passes_test(can_plan,login_url="/rowers/paidplans", + message="This functionality requires a Coach or Self-Coach plan", + redirect_field_name=None) +def rower_select_instantplan(request,id=0): + r = getrequestrower(request,userid=id) + themanager = getrower(request.user) + + # get and present available plans + authorizationstring = 'Bearer '+settings.WORKOUTS_FIT_TOKEN + + url = settings.WORKOUTS_FIT_URL+"/trainingplan/" + headers = {'Authorization':authorizationstring} + + trainingdict = {} + response = requests.get(url=url, headers=headers) + if response.status_code != 200: + messages.error(request,"Could not connect to the training plan server") + else: + trainingdict = response.json()['plans'] + + for plan in trainingdict: + print(plan['ID'],plan['name'],plan['plan']['duration']) + + breadcrumbs = [ + { + 'url':reverse('plannedsessions_view'), + 'name': 'Sessions' + }, + { + 'url':reverse(rower_create_trainingplan, + kwargs={'id':id}), + 'name': 'Manage Plans and Targets' + }, + { + 'url':reverse('rower_select_instantplan'), + 'name': 'Select Existing Plans' + } + ] + + + return render(request, + 'instantplans.html', + { + 'rower':r, + 'active':'nav-plan', + 'trainingdict':trainingdict, + + }) @user_passes_test(can_plan,login_url="/rowers/paidplans", message="This functionality requires a Coach or Self-Coach plan", From 1f4f76f56a4765102bfbc7f37cd890b9e96e5bc2 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 10 Mar 2021 08:34:03 +0100 Subject: [PATCH 3/4] more proto --- rowers/templates/instantplan.html | 43 +++++++++++++++++ rowers/templatetags/rowerfilters.py | 8 ++- rowers/urls.py | 2 + rowers/views/planviews.py | 75 +++++++++++++++++++++++++++-- 4 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 rowers/templates/instantplan.html diff --git a/rowers/templates/instantplan.html b/rowers/templates/instantplan.html new file mode 100644 index 00000000..897d8174 --- /dev/null +++ b/rowers/templates/instantplan.html @@ -0,0 +1,43 @@ +{% extends "newbase.html" %} +{% load staticfiles %} +{% load rowerfilters %} + +{% block title %}{{ plan.name }}{% endblock %} + + +{% block main %} +

{{ plan.name }}

+ +
    +
  • + + + + + + + + + + {% for day in trainingdays %} + + + + + + {% endfor %} + +
    WeekDayWorkouts
    {{ day.week }}{{ day.order }} + {% for workout in day.workouts %} +

    {{ workout.workoutName }}

    + {{ workout|steptostring|safe }} + {% endfor %} +
    +
  • +
+ +{% endblock %} + +{% block sidebar %} +{% include 'menu_plan.html' %} +{% endblock %} diff --git a/rowers/templatetags/rowerfilters.py b/rowers/templatetags/rowerfilters.py index 7cc4d99c..412e2427 100644 --- a/rowers/templatetags/rowerfilters.py +++ b/rowers/templatetags/rowerfilters.py @@ -26,12 +26,13 @@ from rowers.rower_rules import is_coach_user, is_workout_user, isplanmember,ispr from rowers.mytypes import ( otwtypes,adaptivetypes,sexcategories,weightcategories,workouttypes, ) -from rowers.utils import NoTokenError +from rowers.utils import NoTokenError, step_to_string import rowers.payments as payments from rowers.opaque import encoder +from rowers.plannedsessions import ps_dict_get_description_html import arrow @@ -70,6 +71,11 @@ favanalysisicons = { 'cp':'fa-user-chart', } +@register.filter +def steptostring(steps): + res = ps_dict_get_description_html(steps) + return res + # for verbose version of fav analysis @register.filter def verbose(s): diff --git a/rowers/urls.py b/rowers/urls.py index 9bb36e53..89ee2771 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -741,6 +741,8 @@ urlpatterns = [ re_path(r'^createplan/$',views.rower_create_trainingplan,name='rower_create_trainingplan'), re_path(r'^createplan/user/(?P\d+)/$',views.rower_create_trainingplan,name='rower_create_trainingplan'), re_path(r'^plans/$', views.rower_select_instantplan, name='rower_select_instantplan'), + re_path(r'^plans/(?P[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12})/$', + views.rower_view_instantplan, name='rower_view_instantplan'), re_path(r'^deleteplan/(?P\d+)/$',login_required( views.TrainingPlanDelete.as_view()),name='trainingplan_delete_view'), re_path(r'^deletemicrocycle/(?P\d+)/$',login_required( diff --git a/rowers/views/planviews.py b/rowers/views/planviews.py index 3b2bc520..bf925e49 100644 --- a/rowers/views/planviews.py +++ b/rowers/views/planviews.py @@ -2425,6 +2425,78 @@ class PlannedSessionDelete(DeleteView): return obj +@user_passes_test(can_plan,login_url="/rowers/paidplans", + message="This functionality requires a Coach or Self-Coach plan", + redirect_field_name=None) +def rower_view_instantplan(request,id='',userid=0): + r = getrequestrower(request,userid=userid) + if not id: + raise Http404("Plan does not exist") + + authorizationstring = 'Bearer '+settings.WORKOUTS_FIT_TOKEN + url = settings.WORKOUTS_FIT_URL+"/trainingplan/"+id + headers = {'Authorization':authorizationstring} + + response = requests.get(url=url,headers=headers) + if response.status_code != 200: + messages.error(request,"Could not connect to the training plan server") + return HttpResponseRedirect(reverse('rower_select_instantplan')) + + plan = response.json() + trainingdays = plan['plan']['trainingDays'] + + + trainingdays2 = [] + nextday = trainingdays.pop(0) + for i in range(plan['plan']['duration']): + if nextday['order'] == i+1: + nextday['week'] = (divmod(i,7)[0])+1 + trainingdays2.append(nextday) + try: + nextday = trainingdays.pop(0) + except IndexError: + continue + else: + trainingdays2.append( + { + 'order':i+1, + 'week': (divmod(i,7)[0])+1, + 'workouts':[] + } + ) + + breadcrumbs = [ + { + 'url':reverse('plannedsessions_view'), + 'name': 'Sessions' + }, + { + 'url':reverse(rower_create_trainingplan, + kwargs={'id':userid}), + 'name': 'Manage Plans and Targets' + }, + { + 'url':reverse('rower_select_instantplan'), + 'name': 'Select Existing Plans' + }, + { + 'url':reverse('rower_view_instantplan',kwargs={ + 'id':id, +# 'userid':userid, + }), + 'name':plan['name'] + } + ] + + return render(request, + 'instantplan.html', + { + 'rower':r, + 'active':'nav-plan', + 'plan':plan, + 'trainingdays':trainingdays2, + }) + @user_passes_test(can_plan,login_url="/rowers/paidplans", message="This functionality requires a Coach or Self-Coach plan", redirect_field_name=None) @@ -2445,9 +2517,6 @@ def rower_select_instantplan(request,id=0): else: trainingdict = response.json()['plans'] - for plan in trainingdict: - print(plan['ID'],plan['name'],plan['plan']['duration']) - breadcrumbs = [ { 'url':reverse('plannedsessions_view'), From 565735dddd65ca083151abace2c09abd70c2c163 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 10 Mar 2021 08:43:50 +0100 Subject: [PATCH 4/4] Initial model InstantPlan --- rowers/models.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rowers/models.py b/rowers/models.py index 324a7927..9980e7ab 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -1531,6 +1531,15 @@ class TrainingTargetForm(ModelForm): ).distinct().order_by("user__last_name","user__first_name") +class InstantPlan(models.Model): + uuid = models.UUIDField(primary_key=False,editable=True,default=uuid.uuid4) + owner = models.ForeignKey(User,on_delete=models.SET_NULL,null=True) + name = models.CharField(max_length=150,blank=True) + goal = models.CharField(max_length=150,blank=True) + description = models.TextField(max_length=300,blank=True) + duration = models.IntegerField(default=6) + target = models.TextField(max_length=300,blank=True) + hoursperweek = models.IntegerField(default=4) @python_2_unicode_compatible