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 }}
+
+
+ -
+
+
+
+ | Week |
+ Day |
+ Workouts |
+
+
+
+ {% for day in trainingdays %}
+
+ | {{ day.week }} |
+ {{ day.order }} |
+
+ {% for workout in day.workouts %}
+ {{ workout.workoutName }}
+ {{ workout|steptostring|safe }}
+ {% endfor %}
+ |
+
+ {% endfor %}
+
+
+
+
+
+{% 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
+
+
+ {% for plan in trainingdict %}
+ -
+
{{ plan.name }}
+ {{ plan.plan|lookup:"duration"}}
+
+ {% 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 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",