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/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 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/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 }}

+ + + +{% endblock %} + +{% block sidebar %} +{% include 'menu_plan.html' %} +{% endblock %} 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/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 2e7bcd48..89ee2771 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -740,6 +740,9 @@ 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'^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 a8de7582..bf925e49 100644 --- a/rowers/views/planviews.py +++ b/rowers/views/planviews.py @@ -2425,6 +2425,123 @@ 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) +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'] + + 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",