From 4c8e81d0e78a74ce2b87dbe5d3b5319a9248a0f0 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Thu, 6 Sep 2018 16:36:27 +0200 Subject: [PATCH 01/17] some overlap checking on training plans --- rowers/models.py | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/rowers/models.py b/rowers/models.py index 38151ee3..28c15d86 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -979,11 +979,48 @@ class TrainingTargetForm(ModelForm): class TrainingPlan(models.Model): rower = models.ForeignKey(Rower) name = models.CharField(max_length=150,blank=True) - target = models.ForeignKey(TrainingTarget,blank=True) + target = models.ForeignKey(TrainingTarget,blank=True,null=True) startdate = models.DateField(default=timezone.now) enddate = models.DateField( default=half_year_from_now) + def __unicode__(self): + name = self.name + startdate = self.startdate + enddate = self.enddate + firstname = self.rower.user.first_name + lastname = self.rower.user.last_name + + stri = u'Training Plan for {first_name} {last_name} {s} - {e}: {n}'.format( + s = startdate.strftime('%Y-%m-%d'), + e = enddate.strftime('%Y-%m-%d'), + firstname = firstname, + lastname = lastname, + name=name + ) + + return stri + + def save(self, *args, **kwargs): + if self.enddate < self.startdate: + startdate = self.startdate + enddate = self.enddate + self.startdate = enddate + self.enddate = startdate + + otherplans = TrainingPlan.objects.filter(rower=self.rower) + + for otherplan in otherplans: + if otherplan.startdate <= self.enddate and otherplan.startdate >= self.startdate: + self.enddate = otherplan.startdate-datetime.timedelta(days=1) + if otherplan.enddate >= self.startdate and otherplan.enddate <= self.enddate: + self.startdate = otherplan.enddate+datetime.timedelta(days=1) + + print self.startdate,self.enddate + + if not self.enddate <= self.startdate: + super(TrainingPlan,self).save(*args, **kwargs) + class TrainingPlanForm(ModelForm): class Meta: model = TrainingPlan From 54cc552a99b5fb22a08d4f4e099eef17e1305b22 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 7 Sep 2018 19:32:04 +0200 Subject: [PATCH 02/17] adding plan and target edit --- rowers/models.py | 5 +- rowers/templates/trainingplan_create.html | 4 ++ rowers/templates/trainingplan_edit.html | 32 +++++++++ rowers/urls.py | 4 ++ rowers/views.py | 81 +++++++++++++++++++++++ 5 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 rowers/templates/trainingplan_edit.html diff --git a/rowers/models.py b/rowers/models.py index 28c15d86..e79c7722 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -840,6 +840,7 @@ def checkworkoutuser(user,workout): except Rower.DoesNotExist: return False + # Check if user is coach or rower def checkaccessuser(user,rower): try: @@ -1008,7 +1009,7 @@ class TrainingPlan(models.Model): self.startdate = enddate self.enddate = startdate - otherplans = TrainingPlan.objects.filter(rower=self.rower) + otherplans = TrainingPlan.objects.filter(rower=self.rower).exclude(pk=self.pk) for otherplan in otherplans: if otherplan.startdate <= self.enddate and otherplan.startdate >= self.startdate: @@ -1016,8 +1017,6 @@ class TrainingPlan(models.Model): if otherplan.enddate >= self.startdate and otherplan.enddate <= self.enddate: self.startdate = otherplan.enddate+datetime.timedelta(days=1) - print self.startdate,self.enddate - if not self.enddate <= self.startdate: super(TrainingPlan,self).save(*args, **kwargs) diff --git a/rowers/templates/trainingplan_create.html b/rowers/templates/trainingplan_create.html index 1a4f2c29..3ea01a93 100644 --- a/rowers/templates/trainingplan_create.html +++ b/rowers/templates/trainingplan_create.html @@ -34,6 +34,8 @@ {{ target.date }} {{ target.name }} {{ target.notes }} + Edit + Delete {% endfor %} @@ -82,6 +84,8 @@ {{ plan.startdate }} {{ plan.enddate }} {{ plan.name }} + Edit + Delete {% endfor %} diff --git a/rowers/templates/trainingplan_edit.html b/rowers/templates/trainingplan_edit.html new file mode 100644 index 00000000..1dc67326 --- /dev/null +++ b/rowers/templates/trainingplan_edit.html @@ -0,0 +1,32 @@ +{% extends "base.html" %} +{% load staticfiles %} +{% load rowerfilters %} + +{% block title %}Rowsandall Training Plans{% endblock %} + +{% block scripts %} + +{% endblock %} + +{% block content %} + + +
+ +
+ {% csrf_token %} + + {{ form.as_table }} +
+
+ +
+
+ + +
+ {% endblock %} diff --git a/rowers/urls.py b/rowers/urls.py index 5e0b41e0..9c554557 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -421,6 +421,10 @@ urlpatterns = [ url(r'^test\_callback',views.rower_process_testcallback), url(r'^createplan$',views.rower_create_trainingplan), url(r'^createplan/(?P\d+)$',views.rower_create_trainingplan), + url(r'^deleteplan/(?P\d+)$',views.rower_delete_trainingplan), + url(r'^deletetarget/(?P\d+)$',views.rower_delete_trainingtarget), + url(r'^editplan/(?P\d+)$',views.TrainingPlanUpdate.as_view()), + url(r'^edittarget/(?P\d+)$',views.TrainingTargetUpdate.as_view()), url(r'^workout/(?P\d+)/test\_strokedata$',views.strokedataform), url(r'^sessions/teamcreate$',views.plannedsession_teamcreate_view), diff --git a/rowers/views.py b/rowers/views.py index 9a1bc520..b261271f 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -27,6 +27,9 @@ import codecs import isodate from django.shortcuts import render + +from django.views.generic.edit import UpdateView + from django.http import ( HttpResponse, HttpResponseRedirect, HttpResponseForbidden, HttpResponseNotAllowed, @@ -125,6 +128,9 @@ from rowers.tasks_standalone import addcomment2 from django.contrib import messages from async_messages import messages as a_messages +from django.contrib.admin.widgets import AdminDateWidget,AdminTimeWidget,AdminSplitDateTime + + import requests import json from rest_framework.renderers import JSONRenderer @@ -14170,6 +14176,41 @@ def virtualevent_submit_result_view(request,id=0): 'w_form':w_form, }) +@user_passes_test(hasplannedsessions,login_url="/", redirect_field_name=None) +def rower_delete_trainingtarget(request,id=0): + try: + target = TrainingTarget.objects.get(id=id) + except TrainingPlan.DoesNotExist: + raise Http404("Training Plan Does Not Exist") + + if checkaccessuser(request.user,target.rower): + target.delete() + messages.info(request,"We have deleted the training plan") + else: + raise PermissionDenied("Access denied") + + url = reverse(rower_create_trainingplan) + + return HttpResponseRedirect(url) + + +@user_passes_test(hasplannedsessions,login_url="/", redirect_field_name=None) +def rower_delete_trainingplan(request,id=0): + try: + plan = TrainingPlan.objects.get(id=id) + except TrainingPlan.DoesNotExist: + raise Http404("Training Plan Does Not Exist") + + if checkaccessuser(request.user,plan.rower): + plan.delete() + messages.info(request,"We have deleted the training plan") + else: + raise PermissionDenied("Access denied") + + url = reverse(rower_create_trainingplan) + + return HttpResponseRedirect(url) + @user_passes_test(hasplannedsessions,login_url="/", redirect_field_name=None) def rower_create_trainingplan(request,id=0): @@ -14228,3 +14269,43 @@ def rower_create_trainingplan(request,id=0): +class TrainingPlanUpdate(UpdateView): + model = TrainingPlan + template_name = 'trainingplan_edit.html' + form_class = TrainingPlanForm + + def get_success_url(self): + return reverse(rower_create_trainingplan) + + def form_valid(self, form): + form.instance.user = self.request.user + form.instance.post_date = datetime.datetime.now() + plan = form.save() + return super(TrainingPlanUpdate, self).form_valid(form) + + def get_object(self, *args, **kwargs): + obj = super(TrainingPlanUpdate, self).get_object(*args, **kwargs) + if not checkaccessuser(self.request.user,obj.rower): + raise Http404 + return obj + +class TrainingTargetUpdate(UpdateView): + model = TrainingTarget + template_name = 'trainingplan_edit.html' + form_class = TrainingTargetForm + + def get_success_url(self): + return reverse(rower_create_trainingplan) + + def form_valid(self, form): + form.instance.user = self.request.user + form.instance.post_date = datetime.datetime.now() + plan = form.save() + return super(TrainingTargetUpdate, self).form_valid(form) + + def get_object(self, *args, **kwargs): + obj = super(TrainingTargetUpdate, self).get_object(*args, **kwargs) + if not checkaccessuser(self.request.user,obj.rower): + raise Http404 + return obj + From 47eed8447995f8c33ab2a8faf0cc26ef5e403731 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 7 Sep 2018 21:06:50 +0200 Subject: [PATCH 03/17] adding macro and meso cycles and adding filler cycles --- rowers/models.py | 79 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/rowers/models.py b/rowers/models.py index e79c7722..ad5a7b4c 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -1009,7 +1009,7 @@ class TrainingPlan(models.Model): self.startdate = enddate self.enddate = startdate - otherplans = TrainingPlan.objects.filter(rower=self.rower).exclude(pk=self.pk) + otherplans = TrainingPlan.objects.filter(rower=self.rower).exclude(pk=self.pk).order_by("-startdate") for otherplan in otherplans: if otherplan.startdate <= self.enddate and otherplan.startdate >= self.startdate: @@ -1020,6 +1020,19 @@ class TrainingPlan(models.Model): if not self.enddate <= self.startdate: super(TrainingPlan,self).save(*args, **kwargs) + macrocycles = TrainingMacroCycle.objects.filter(plan = self) + if not macrocycles: + m = TrainingMacroCycle( + plan = self, + name = 'Filler', + startdate = self.startdate, + enddate = self.enddate, + ) + + m.save() + + createmacrofillers(self) + class TrainingPlanForm(ModelForm): class Meta: model = TrainingPlan @@ -1045,6 +1058,32 @@ cycletypechoices = ( ('userdefined','User Defined') ) +def createmacrofillers(plan): + fillers = TrainingMacroCycle.objects.filter( + plan = plan, type = 'filler' + ) + + for f in fillers: + f.delete() + + cycles = TrainingMacroCycle.objects.filter( + plan = plan + ).order_by("-startdate") + + thedate = plan.enddate + while cycles: + if cycles[0].enddate < thedate: + macr = TrainingMacroCycle( + startdate = cycles[0].enddate+datetime.timedelta(days=1), + enddate = thedate, + type='filler', + name='Filler' + ) + macr.save() + thedate = cycles[0].startdate-datetime.timedelta(days=1) + cycles = cycles[1:] + + class TrainingMacroCycle(models.Model): plan = models.ForeignKey(TrainingPlan) name = models.CharField(max_length=150,blank=True) @@ -1056,6 +1095,44 @@ class TrainingMacroCycle(models.Model): choices=cycletypechoices, max_length=150) + def save(self, *args, **kwargs): + if self.enddate < self.startdate: + startdate = self.startdate + enddate = self.enddate + self.startdate = enddate + self.enddate = startdate + + fillers = TrainingMacroCycle.objects.filter( + plan=self.plan,type='filler') + for f in fillers: + f.delete() + + othercycles = TrainingMacroCycle.objects.filter( + plan=self.plan).exclude(pk=self.pk).order_by("-startdate") + + for othercycle in othercycles: + if othercycle.startdate <= self.enddate and othercycle.startdate >= self.startdate: + self.enddate = othercycle.startdate-datetime.timedelta(days=1) + + if othercycle.enddate >= self.startdate and othercycle.enddate <= self.enddate: + self.startdate = othercycle.enddate+datetime.timedelta(days=1) + + if not self.enddate <= self.startdate: + super(TrainingMacroCycle,self).save(*args, **kwargs) + + + mesocycles = TrainingMesoCycle.objects.filter(plan = self) + if not mesocycles: + meso = TrainingMesoCycle( + plan = self, + name = 'Filler', + startdate = self.startdate, + enddate = self.enddate, + ) + + meso.save() + + class TrainingMesoCycle(models.Model): plan = models.ForeignKey(TrainingMacroCycle) name = models.CharField(max_length=150,blank=True) From 33c7db5ffc29d5bf6ed1ee2a6866c7a501d9539c Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 8 Sep 2018 12:40:02 +0200 Subject: [PATCH 04/17] moved power progress out of labs and training targets in --- rowers/templates/analysis.html | 14 +++++++++++++- rowers/templates/laboratory.html | 6 +++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/rowers/templates/analysis.html b/rowers/templates/analysis.html index e18454fc..56eb39e2 100644 --- a/rowers/templates/analysis.html +++ b/rowers/templates/analysis.html @@ -138,7 +138,19 @@ Analyse power vs piece duration to make predictions, for erg pieces.

-
+
+

+ {% if user|is_planmember %} + Power Progress + {% else %} + Power Progress + {% endif %} +

+

+ Monitoring power duration evidence from all your workouts. Feel free to explore. +

+
+

{% if user|is_planmember %} The Labs diff --git a/rowers/templates/laboratory.html b/rowers/templates/laboratory.html index 9b5b2964..1485f78a 100644 --- a/rowers/templates/laboratory.html +++ b/rowers/templates/laboratory.html @@ -24,13 +24,13 @@

{% if user|is_planmember %} - Power Progress + Training Target {% else %} - Power Progress + Training Target {% endif %}

- Monitoring power duration evidence from all your workouts. Feel free to explore. + Experiments with a training target and plan builder

From a99ac4f5b3b1d8500d5768c9330c77c639f62084 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 8 Sep 2018 12:52:52 +0200 Subject: [PATCH 05/17] start of plan view --- rowers/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rowers/views.py b/rowers/views.py index b261271f..33f92b32 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -14268,6 +14268,9 @@ def rower_create_trainingplan(request,id=0): }) +@user_passes_test(hasplannedsessions,login_url="/", redirect_field_name=None) +def rower_view_trainingplan(request,id=0): + pass class TrainingPlanUpdate(UpdateView): model = TrainingPlan From 7774f4159fbed50c7dd5d4afa290bcc735be0c23 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 8 Sep 2018 17:21:35 +0200 Subject: [PATCH 06/17] basic plan page and edit macro cycle --- rowers/models.py | 49 +++++++++++++++++- rowers/templates/trainingplan.html | 60 +++++++++++++++++++++ rowers/templates/trainingplan_create.html | 2 +- rowers/urls.py | 2 + rowers/views.py | 63 ++++++++++++++++++++++- 5 files changed, 171 insertions(+), 5 deletions(-) create mode 100644 rowers/templates/trainingplan.html diff --git a/rowers/models.py b/rowers/models.py index ad5a7b4c..1d74caf9 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -992,7 +992,7 @@ class TrainingPlan(models.Model): firstname = self.rower.user.first_name lastname = self.rower.user.last_name - stri = u'Training Plan for {first_name} {last_name} {s} - {e}: {n}'.format( + stri = u'Training Plan for {firstname} {lastname} {s} - {e}: {n}'.format( s = startdate.strftime('%Y-%m-%d'), e = enddate.strftime('%Y-%m-%d'), firstname = firstname, @@ -1031,7 +1031,8 @@ class TrainingPlan(models.Model): m.save() - createmacrofillers(self) + else: + createmacrofillers(self) class TrainingPlanForm(ModelForm): class Meta: @@ -1070,10 +1071,20 @@ def createmacrofillers(plan): plan = plan ).order_by("-startdate") + if not cycles: + macr = TrainingMacroCycle( + startdate = plan.startdate, + enddate = plan.enddate, + type='filler', + name='Filler' + ) + macr.save() + thedate = plan.enddate while cycles: if cycles[0].enddate < thedate: macr = TrainingMacroCycle( + plan=plan, startdate = cycles[0].enddate+datetime.timedelta(days=1), enddate = thedate, type='filler', @@ -1095,6 +1106,15 @@ class TrainingMacroCycle(models.Model): choices=cycletypechoices, max_length=150) + def __unicode__(self): + stri = 'Macro Cycle - {n} ({sd} - {ed})'.format( + n = self.name, + sd = self.startdate, + ed = self.enddate, + ) + + return stri + def save(self, *args, **kwargs): if self.enddate < self.startdate: startdate = self.startdate @@ -1132,6 +1152,15 @@ class TrainingMacroCycle(models.Model): meso.save() +class TrainingMacroCycleForm(ModelForm): + class Meta: + model = TrainingMacroCycle + fields = ['name','startdate','enddate'] + + widgets = { + 'startdate': AdminDateWidget(), + 'enddate': AdminDateWidget() + } class TrainingMesoCycle(models.Model): plan = models.ForeignKey(TrainingMacroCycle) @@ -1143,6 +1172,14 @@ class TrainingMesoCycle(models.Model): type = models.CharField(default='filler', choices=cycletypechoices, max_length=150) + def __unicode__(self): + stri = 'Meso Cycle - {n} ({sd} - {ed})'.format( + n = self.name, + sd = self.startdate, + ed = self.enddate, + ) + + return stri class TrainingMicroCycle(models.Model): @@ -1155,6 +1192,14 @@ class TrainingMicroCycle(models.Model): type = models.CharField(default='filler', choices=cycletypechoices, max_length=150) + def __unicode__(self): + stri = 'Micro Cycle - {n} ({sd} - {ed})'.format( + n = self.name, + sd = self.startdate, + ed = self.enddate, + ) + + return stri # Needs some error checking diff --git a/rowers/templates/trainingplan.html b/rowers/templates/trainingplan.html new file mode 100644 index 00000000..8c726804 --- /dev/null +++ b/rowers/templates/trainingplan.html @@ -0,0 +1,60 @@ +{% extends "base.html" %} +{% load staticfiles %} +{% load rowerfilters %} + +{% block title %}Rowsandall Training Plans{% endblock %} + +{% block scripts %} + +{% endblock %} + +{% block content %} + + +
+
+

Training Plan - {{ plan.name }}

+
+
+

Macro Cycles

+
+
+

Meso Cycles

+
+
+

Micro Cycles

+
+
+
+ {% for key,macrocycle in cycles.items %} +
+
+

{{ macrocycle.0.name }}

+

{{ macrocycle.0.startdate }}

+

{{ macrocycle.0.enddate }}

+

edit

+
+
+ {% for key, mesocycle in macrocycle.1.items %} +
+ {{ mesocycle.0 }} +
+
+ {% for key, microcycle in mesocycle.1.items %} +
+ {{ microcycle }} +
+
+ {% endfor %} +
+ {% endfor %} +
+ {% endfor %} + +
+ + {% endblock %} diff --git a/rowers/templates/trainingplan_create.html b/rowers/templates/trainingplan_create.html index 3ea01a93..3d32874b 100644 --- a/rowers/templates/trainingplan_create.html +++ b/rowers/templates/trainingplan_create.html @@ -83,7 +83,7 @@ {{ plan.startdate }} {{ plan.enddate }} - {{ plan.name }} + {{ plan.name }} Edit Delete diff --git a/rowers/urls.py b/rowers/urls.py index 9c554557..2df4078f 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -422,6 +422,8 @@ urlpatterns = [ url(r'^createplan$',views.rower_create_trainingplan), url(r'^createplan/(?P\d+)$',views.rower_create_trainingplan), url(r'^deleteplan/(?P\d+)$',views.rower_delete_trainingplan), + url(r'^plan/(?P\d+)$',views.rower_trainingplan_view), + url(r'^macrocycle/(?P\d+)$',views.TrainingMacroCycleUpdate.as_view()), url(r'^deletetarget/(?P\d+)$',views.rower_delete_trainingtarget), url(r'^editplan/(?P\d+)$',views.TrainingPlanUpdate.as_view()), url(r'^edittarget/(?P\d+)$',views.TrainingTargetUpdate.as_view()), diff --git a/rowers/views.py b/rowers/views.py index 33f92b32..1b0bf330 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -71,6 +71,7 @@ from rowers.models import ( TrainingPlan,TrainingPlanForm,TrainingTarget,TrainingTargetForm, TrainingMacroCycle,TrainingMesoCycle,TrainingMicroCycle, TrainingTarget,TrainingTargetForm, + TrainingMacroCycleForm,createmacrofillers ) from rowers.models import ( RowerPowerForm,RowerForm,GraphImage,AdvancedWorkoutForm, @@ -14269,8 +14270,66 @@ def rower_create_trainingplan(request,id=0): @user_passes_test(hasplannedsessions,login_url="/", redirect_field_name=None) -def rower_view_trainingplan(request,id=0): - pass +def rower_trainingplan_view(request,id=0): + try: + plan = TrainingPlan.objects.get(id=id) + except TrainingPlan.DoesNotExist: + raise Http404("Training Plan Does Not Exist") + + if not checkaccessuser(request.user,plan.rower): + raise PermissionDenied("Access denied") + + macrocycles = TrainingMacroCycle.objects.filter(plan=plan).order_by("startdate") + + count = 0 + cycles = {} + + + for m in macrocycles: + mesocycles = TrainingMesoCycle.objects.filter(plan=m).order_by("startdate") + mesos = {} + count2 = 0 + for me in mesocycles: + microcycles = TrainingMicroCycle.objects.filter(plan=me).order_by("startdate") + mesos[count2] = (me, microcycles) + count2 += 1 + cycles[count] = (m,mesos) + count += 1 + + + return render(request,'trainingplan.html', + { + 'plan':plan, + 'cycles':cycles, + } + ) + +class TrainingMacroCycleUpdate(UpdateView): + model = TrainingMacroCycle + template_name = 'trainingplan_edit.html' + form_class = TrainingMacroCycleForm + + + def get_success_url(self): + plan = self.object.plan + createmacrofillers(plan) + return reverse(rower_trainingplan_view, + kwargs = { + 'id':plan.id + } + ) + def form_valid(self, form): + macrocycle = form.save() + return super(TrainingMacroCycleUpdate, self).form_valid(form) + + def get_object(self, *args, **kwargs): + obj = super(TrainingMacroCycleUpdate, self).get_object(*args, **kwargs) + if not checkaccessuser(self.request.user,obj.plan.rower): + raise Http404 + else: + obj.type = 'userdefined' + obj.save() + return obj class TrainingPlanUpdate(UpdateView): model = TrainingPlan From 2c4c0030b4a903e4096b043db1bf27658091b888 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 9 Sep 2018 16:21:44 +0200 Subject: [PATCH 07/17] mostly working - a few bugs remaining --- rowers/models.py | 235 ++++++++++++++++++++++++++++- rowers/templates/trainingplan.html | 39 ++--- rowers/urls.py | 2 + rowers/views.py | 75 ++++++++- static/css/rowsandall.css | 30 ++++ 5 files changed, 355 insertions(+), 26 deletions(-) diff --git a/rowers/models.py b/rowers/models.py index 1d74caf9..c33b46d8 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -1073,6 +1073,7 @@ def createmacrofillers(plan): if not cycles: macr = TrainingMacroCycle( + plan=plan, startdate = plan.startdate, enddate = plan.enddate, type='filler', @@ -1095,6 +1096,131 @@ def createmacrofillers(plan): cycles = cycles[1:] +def createmesofillers(plan): + fillers = TrainingMesoCycle.objects.filter( + plan = plan, type = 'filler' + ) + + for f in fillers: + f.delete() + + cycles = TrainingMesoCycle.objects.filter( + plan = plan + ).order_by("-startdate") + + if not cycles: + macr = TrainingMesoCycle( + plan = plan, + startdate = plan.startdate, + enddate = plan.enddate, + type='filler', + name='Filler' + ) + macr.save() + + thedate = plan.enddate + while cycles: + if cycles[0].enddate < thedate: + macr = TrainingMesoCycle( + plan=plan, + startdate = cycles[0].enddate+datetime.timedelta(days=1), + enddate = thedate, + type='filler', + name='Filler' + ) + macr.save() + thedate = cycles[0].startdate-datetime.timedelta(days=1) + cycles = cycles[1:] + + +def createmicrofillers(plan): + fillers = TrainingMicroCycle.objects.filter( + plan = plan, type = 'filler' + ) + + for f in fillers: + f.delete() + + cycles = TrainingMicroCycle.objects.filter( + plan = plan + ).order_by("-startdate") + + if not cycles: + macr = TrainingMicroCycle( + plan=plan, + startdate = plan.startdate, + enddate = plan.enddate, + type='filler', + name='Filler' + ) + macr.save() + + thedate = plan.enddate + while cycles: + if cycles[0].enddate < thedate: + macr = TrainingMicroCycle( + plan=plan, + startdate = cycles[0].enddate+datetime.timedelta(days=1), + enddate = thedate, + type='filler', + name='Filler' + ) + macr.save() + thedate = cycles[0].startdate-datetime.timedelta(days=1) + cycles = cycles[1:] + +def microcyclecheckdates(plan): + cycles = TrainingMicroCycle.objects.filter( + plan=plan + ).order_by("-startdate") + + thedate = plan.enddate + while cycles: + if cycles[0].enddate < plan.startdate: + cycles[0].delete() + if cycles[0].startdate > plan.enddate: + cycles[0].delete() + if cycles[0].enddate > thedate: + cycles[0].enddate = thedate + cycles[0].save() + thedate = cycles[0].startdate-datetime.timedelta(days=1) + cycles = cycles[1:] + +def mesocyclecheckdates(plan): + cycles = TrainingMesoCycle.objects.filter( + plan=plan + ).order_by("-startdate") + + thedate = plan.enddate + while cycles: + if cycles[0].enddate < plan.startdate: + cycles[0].delete() + if cycles[0].startdate > plan.enddate: + cycles[0].delete() + if cycles[0].enddate > thedate: + cycles[0].enddate = thedate + cycles[0].save() + thedate = cycles[0].startdate-datetime.timedelta(days=1) + cycles = cycles[1:] + +def macrocyclecheckdates(plan): + cycles = TrainingMacroCycle.objects.filter( + plan=plan + ).order_by("-startdate") + + thedate = plan.enddate + while cycles: + if cycles[0].enddate < plan.startdate: + cycles[0].delete() + if cycles[0].startdate > plan.enddate: + cycles[0].delete() + if cycles[0].enddate > thedate: + cycles[0].enddate = thedate + cycles[0].save() + thedate = cycles[0].startdate-datetime.timedelta(days=1) + cycles = cycles[1:] + + class TrainingMacroCycle(models.Model): plan = models.ForeignKey(TrainingPlan) name = models.CharField(max_length=150,blank=True) @@ -1127,6 +1253,12 @@ class TrainingMacroCycle(models.Model): for f in fillers: f.delete() + if self.enddate > self.plan.enddate: + self.enddate = self.plan.enddate + + if self.startdate < self.plan.startdate: + self.startdate = self.plan.startdate + othercycles = TrainingMacroCycle.objects.filter( plan=self.plan).exclude(pk=self.pk).order_by("-startdate") @@ -1151,11 +1283,13 @@ class TrainingMacroCycle(models.Model): ) meso.save() + else: + createmesofillers(self) class TrainingMacroCycleForm(ModelForm): class Meta: model = TrainingMacroCycle - fields = ['name','startdate','enddate'] + fields = ['name','startdate','enddate','notes'] widgets = { 'startdate': AdminDateWidget(), @@ -1181,6 +1315,49 @@ class TrainingMesoCycle(models.Model): return stri + def save(self, *args, **kwargs): + if self.enddate < self.startdate: + startdate = self.startdate + enddate = self.enddate + self.startdate = enddate + self.enddate = startdate + + fillers = TrainingMesoCycle.objects.filter( + plan=self.plan,type='filler') + for f in fillers: + f.delete() + + if self.enddate > self.plan.enddate: + self.enddate = self.plan.enddate + + if self.startdate < self.plan.startdate: + self.startdate = self.plan.startdate + + othercycles = TrainingMesoCycle.objects.filter( + plan=self.plan).exclude(pk=self.pk).order_by("-startdate") + + for othercycle in othercycles: + if othercycle.startdate <= self.enddate and othercycle.startdate >= self.startdate: + self.enddate = othercycle.startdate-datetime.timedelta(days=1) + + if othercycle.enddate >= self.startdate and othercycle.enddate <= self.enddate: + self.startdate = othercycle.enddate+datetime.timedelta(days=1) + + if not self.enddate <= self.startdate: + super(TrainingMesoCycle,self).save(*args, **kwargs) + + microcycles = TrainingMicroCycle.objects.filter(plan = self) + if not microcycles: + micro = TrainingMicroCycle( + plan = self, + name = 'Filler', + startdate = self.startdate, + enddate = self.enddate, + ) + + micro.save() + else: + createmicrofillers(self) class TrainingMicroCycle(models.Model): plan = models.ForeignKey(TrainingMesoCycle) @@ -1201,15 +1378,57 @@ class TrainingMicroCycle(models.Model): return stri + def save(self, *args, **kwargs): + if self.enddate < self.startdate: + startdate = self.startdate + enddate = self.enddate + self.startdate = enddate + self.enddate = startdate -# Needs some error checking -# - Microcycles should not overlap with other microcycles, same for MesoCycles, MacroCycles -# - When a TrainingPlan is created, it should create 1 "collector" Macro, Meso & MicroCycle - this is invisible for users who choose to not use cycles -# - When a new Microcycle is inserted, the "collector" cycle is automatically adjusted to "go out of the way" of the new MicroCycle - and similar for Macro & Meso -# - If the entire MesoCycle is filled with user defined MicroCycles - there are no "filler" MicroCycles -# - Sessions are automatically linked to the correct Cycles based on their start/end date - no need for a hard link + fillers = TrainingMicroCycle.objects.filter( + plan=self.plan,type='filler') + for f in fillers: + f.delete() + + if self.enddate > self.plan.enddate: + self.enddate = self.plan.enddate + + if self.startdate < self.plan.startdate: + self.startdate = self.plan.startdate + + othercycles = TrainingMicroCycle.objects.filter( + plan=self.plan).exclude(pk=self.pk).order_by("-startdate") + + for othercycle in othercycles: + if othercycle.startdate <= self.enddate and othercycle.startdate >= self.startdate: + self.enddate = othercycle.startdate-datetime.timedelta(days=1) + + if othercycle.enddate >= self.startdate and othercycle.enddate <= self.enddate: + self.startdate = othercycle.enddate+datetime.timedelta(days=1) + + if not self.enddate <= self.startdate: + super(TrainingMicroCycle,self).save(*args, **kwargs) + +class TrainingMesoCycleForm(ModelForm): + class Meta: + model = TrainingMesoCycle + fields = ['name','startdate','enddate','notes'] + + widgets = { + 'startdate': AdminDateWidget(), + 'enddate': AdminDateWidget() + } + +class TrainingMicroCycleForm(ModelForm): + class Meta: + model = TrainingMicroCycle + fields = ['name','startdate','enddate','notes'] + + widgets = { + 'startdate': AdminDateWidget(), + 'enddate': AdminDateWidget() + } -# Cycle error checking goes in forms # model for Planned Session (Workout, Challenge, Test) class PlannedSession(models.Model): diff --git a/rowers/templates/trainingplan.html b/rowers/templates/trainingplan.html index 8c726804..4477b255 100644 --- a/rowers/templates/trainingplan.html +++ b/rowers/templates/trainingplan.html @@ -18,43 +18,48 @@

Training Plan - {{ plan.name }}

+

Edit

Macro Cycles

-
+

Meso Cycles

-
+

Micro Cycles

-
+
{% for key,macrocycle in cycles.items %}
-
-

{{ macrocycle.0.name }}

-

{{ macrocycle.0.startdate }}

-

{{ macrocycle.0.enddate }}

-

edit

+
+
+

{{ macrocycle.0.name }} ({{ macrocycle.0.startdate }} - {{ macrocycle.0.enddate }})

+

edit

+
-
+
{% for key, mesocycle in macrocycle.1.items %} -
- {{ mesocycle.0 }} +
+
+

{{ mesocycle.0.name }} ({{ mesocycle.0.startdate }} - {{ mesocycle.0.enddate }})

+

edit

+
- {% for key, microcycle in mesocycle.1.items %} -
- {{ microcycle }} + {% for microcycle in mesocycle.1 %} +
+
+

{{ microcycle.name }} ({{ microcycle.startdate }} - {{ microcycle.enddate }})

+

edit

+
+ {% endfor %}
{% endfor %}
- {% endfor %}
{% endfor %} -
- {% endblock %} diff --git a/rowers/urls.py b/rowers/urls.py index 2df4078f..7210e921 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -424,6 +424,8 @@ urlpatterns = [ url(r'^deleteplan/(?P\d+)$',views.rower_delete_trainingplan), url(r'^plan/(?P\d+)$',views.rower_trainingplan_view), url(r'^macrocycle/(?P\d+)$',views.TrainingMacroCycleUpdate.as_view()), + url(r'^mesocycle/(?P\d+)$',views.TrainingMesoCycleUpdate.as_view()), + url(r'^microcycle/(?P\d+)$',views.TrainingMicroCycleUpdate.as_view()), url(r'^deletetarget/(?P\d+)$',views.rower_delete_trainingtarget), url(r'^editplan/(?P\d+)$',views.TrainingPlanUpdate.as_view()), url(r'^edittarget/(?P\d+)$',views.TrainingTargetUpdate.as_view()), diff --git a/rowers/views.py b/rowers/views.py index 1b0bf330..63d16ae5 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -71,7 +71,10 @@ from rowers.models import ( TrainingPlan,TrainingPlanForm,TrainingTarget,TrainingTargetForm, TrainingMacroCycle,TrainingMesoCycle,TrainingMicroCycle, TrainingTarget,TrainingTargetForm, - TrainingMacroCycleForm,createmacrofillers + TrainingMacroCycleForm,createmacrofillers, + createmicrofillers, createmesofillers, + microcyclecheckdates,mesocyclecheckdates,macrocyclecheckdates, + TrainingMesoCycleForm, TrainingMicroCycleForm, ) from rowers.models import ( RowerPowerForm,RowerForm,GraphImage,AdvancedWorkoutForm, @@ -14279,6 +14282,7 @@ def rower_trainingplan_view(request,id=0): if not checkaccessuser(request.user,plan.rower): raise PermissionDenied("Access denied") + createmacrofillers(plan) macrocycles = TrainingMacroCycle.objects.filter(plan=plan).order_by("startdate") count = 0 @@ -14286,10 +14290,12 @@ def rower_trainingplan_view(request,id=0): for m in macrocycles: + createmesofillers(m) mesocycles = TrainingMesoCycle.objects.filter(plan=m).order_by("startdate") mesos = {} count2 = 0 for me in mesocycles: + createmicrofillers(me) microcycles = TrainingMicroCycle.objects.filter(plan=me).order_by("startdate") mesos[count2] = (me, microcycles) count2 += 1 @@ -14318,8 +14324,12 @@ class TrainingMacroCycleUpdate(UpdateView): 'id':plan.id } ) + def form_valid(self, form): + form.instance.user = self.request.user + form.instance.post_date = datetime.datetime.now() macrocycle = form.save() + mesocyclecheckdates(macrocycle) return super(TrainingMacroCycleUpdate, self).form_valid(form) def get_object(self, *args, **kwargs): @@ -14331,6 +14341,68 @@ class TrainingMacroCycleUpdate(UpdateView): obj.save() return obj +class TrainingMesoCycleUpdate(UpdateView): + model = TrainingMesoCycle + template_name = 'trainingplan_edit.html' + form_class = TrainingMesoCycleForm + + + def get_success_url(self): + plan = self.object.plan + createmesofillers(plan) + return reverse(rower_trainingplan_view, + kwargs = { + 'id':plan.plan.id + } + ) + + def form_valid(self, form): + form.instance.user = self.request.user + form.instance.post_date = datetime.datetime.now() + mesocycle = form.save() + microcyclecheckdates(mesocycle) + return super(TrainingMesoCycleUpdate, self).form_valid(form) + + def get_object(self, *args, **kwargs): + obj = super(TrainingMesoCycleUpdate, self).get_object(*args, **kwargs) + r = obj.plan.plan.rower + if not checkaccessuser(self.request.user,r): + raise Http404 + else: + obj.type = 'userdefined' + obj.save() + return obj + +class TrainingMicroCycleUpdate(UpdateView): + model = TrainingMicroCycle + template_name = 'trainingplan_edit.html' + form_class = TrainingMicroCycleForm + + + def get_success_url(self): + plan = self.object.plan + createmicrofillers(plan) + return reverse(rower_trainingplan_view, + kwargs = { + 'id':plan.plan.plan.id + } + ) + def form_valid(self, form): + form.instance.user = self.request.user + form.instance.post_date = datetime.datetime.now() + microcycle = form.save() + return super(TrainingMicroCycleUpdate, self).form_valid(form) + + def get_object(self, *args, **kwargs): + obj = super(TrainingMicroCycleUpdate, self).get_object(*args, **kwargs) + r = obj.plan.plan.plan.rower + if not checkaccessuser(self.request.user,r): + raise Http404 + else: + obj.type = 'userdefined' + obj.save() + return obj + class TrainingPlanUpdate(UpdateView): model = TrainingPlan template_name = 'trainingplan_edit.html' @@ -14343,6 +14415,7 @@ class TrainingPlanUpdate(UpdateView): form.instance.user = self.request.user form.instance.post_date = datetime.datetime.now() plan = form.save() + macrocyclecheckdates(plan) return super(TrainingPlanUpdate, self).form_valid(form) def get_object(self, *args, **kwargs): diff --git a/static/css/rowsandall.css b/static/css/rowsandall.css index 776bfd55..dfcc8a06 100644 --- a/static/css/rowsandall.css +++ b/static/css/rowsandall.css @@ -490,6 +490,36 @@ a.button { color: #a9c08c; } +/* palegreen */ +.palegreen { + background: palegreen; + box-shadow:inset 0px 0px 0px 6px #fff; + -moz-box-shadow:inset 0px 0px 0px 6px #fff; + box-shadow:inset 0px 0px 0px 6px #fff; +} + +/* paleblue */ +.paleblue { +# padding: 8px; + background: aliceblue; + box-shadow:inset 0px 0px 0px 6px #fff; + -moz-box-shadow:inset 0px 0px 0px 6px #fff; + box-shadow:inset 0px 0px 0px 6px #fff; +} + +/* lightsalmon */ +.lightsalmon { +# padding: 4px; + background: lightsalmon; + box-shadow:inset 0px 0px 0px 6px #fff; + -moz-box-shadow:inset 0px 0px 0px 6px #fff; + box-shadow:inset 0px 0px 0px 6px #fff; +} + +.padded { + padding: 10px; +} + /* pink */ .pink { color: #feeef5; From 357193c9c88457860bbb3d282c882f1e5d613789 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 10 Sep 2018 15:10:43 +0200 Subject: [PATCH 08/17] next steps in training plan/target - delete cycles - change user --- rowers/models.py | 2 +- rowers/templates/trainingplan.html | 42 +++++- rowers/templates/trainingplan_create.html | 22 ++- rowers/templates/trainingplan_delete.html | 30 ++++ rowers/urls.py | 8 +- rowers/views.py | 159 ++++++++++++++++------ static/css/rowsandall.css | 8 ++ 7 files changed, 219 insertions(+), 52 deletions(-) create mode 100644 rowers/templates/trainingplan_delete.html diff --git a/rowers/models.py b/rowers/models.py index c33b46d8..924c4a4a 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -992,7 +992,7 @@ class TrainingPlan(models.Model): firstname = self.rower.user.first_name lastname = self.rower.user.last_name - stri = u'Training Plan for {firstname} {lastname} {s} - {e}: {n}'.format( + stri = u'Training Plan for {firstname} {lastname} {s} - {e}: {name}'.format( s = startdate.strftime('%Y-%m-%d'), e = enddate.strftime('%Y-%m-%d'), firstname = firstname, diff --git a/rowers/templates/trainingplan.html b/rowers/templates/trainingplan.html index 4477b255..62dbb83c 100644 --- a/rowers/templates/trainingplan.html +++ b/rowers/templates/trainingplan.html @@ -18,7 +18,18 @@

Training Plan - {{ plan.name }}

-

Edit

+

This plan starts on {{ plan.startdate }} and ends on {{ plan.enddate }}. The training plan target is: {{ plan.target.name }} on {{ plan.target.date }}.

+

Edit the plan

+

Click on the plan cycles below to edit their names, start and end dates. The gray "filler" + cycles are generated, adjusted and deleted automatically to ensure the entire plan + duration is covered with non-overlapping cycles. + Once you edit a filler cycle, it become a user-defined cycle, which cannot be deleted + by the system.

+

A good way to organize the plan is to think of micro cycles as training weeks. Macro cycles + are typically used to address specific phases of preparation and to indicate the racing + season and may span several months. + Meso cycles can be used to group sequences of three to five light, medium and + hard weeks.

Macro Cycles

@@ -33,26 +44,45 @@
{% for key,macrocycle in cycles.items %}
-
+ {% if macrocycle.0.type == 'filler' %} +
+ {% else %} +
+ {% endif %}

{{ macrocycle.0.name }} ({{ macrocycle.0.startdate }} - {{ macrocycle.0.enddate }})

-

edit

+

edit + / + delete

{% for key, mesocycle in macrocycle.1.items %} + {% if mesocycle.0.type == 'filler' %} +
+ {% else %}
+ {% endif %}

{{ mesocycle.0.name }} ({{ mesocycle.0.startdate }} - {{ mesocycle.0.enddate }})

-

edit

+

edit + / + delete

{% for microcycle in mesocycle.1 %} -
+ {% if microcycle.type == 'filler' %} +
+ {% else %} +
+ {% endif %}

{{ microcycle.name }} ({{ microcycle.startdate }} - {{ microcycle.enddate }})

-

edit

+

+ edit + / + delete

{% endfor %} diff --git a/rowers/templates/trainingplan_create.html b/rowers/templates/trainingplan_create.html index 3d32874b..83a7654b 100644 --- a/rowers/templates/trainingplan_create.html +++ b/rowers/templates/trainingplan_create.html @@ -15,7 +15,23 @@ } -
+
+ {% if user.is_authenticated and user|is_manager %} + + {% endif %} +
+ + +

Training Targets

@@ -64,8 +80,8 @@
-
- +
+

Plans

diff --git a/rowers/templates/trainingplan_delete.html b/rowers/templates/trainingplan_delete.html new file mode 100644 index 00000000..ffb9362c --- /dev/null +++ b/rowers/templates/trainingplan_delete.html @@ -0,0 +1,30 @@ +{% extends "base.html" %} +{% load staticfiles %} +{% load rowerfilters %} + +{% block title %}Rowsandall Training Plans{% endblock %} + +{% block scripts %} + +{% endblock %} + +{% block content %} + + +
+ +
+ {% csrf_token %} +

Are you sure you want to delete {{ object }}?

+
+ +
+
+ + +
+ {% endblock %} diff --git a/rowers/urls.py b/rowers/urls.py index 7210e921..f89c1716 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -420,8 +420,12 @@ urlpatterns = [ url(r'^workout/compare/(?P\d+)/(?P\d+)/(?P\w+.*)/(?P[\w\ ]+.*)/$',views.workout_comparison_view2), url(r'^test\_callback',views.rower_process_testcallback), url(r'^createplan$',views.rower_create_trainingplan), - url(r'^createplan/(?P\d+)$',views.rower_create_trainingplan), - url(r'^deleteplan/(?P\d+)$',views.rower_delete_trainingplan), + url(r'^createplan/(?P\d+)$',views.rower_create_trainingplan), + url(r'^deleteplan/(?P\d+)$',views.TrainingPlanDelete.as_view()), + url(r'^deletemicrocycle/(?P\d+)$',views.MicroCycleDelete.as_view()), + url(r'^deletemesocycle/(?P\d+)$',views.MesoCycleDelete.as_view()), + url(r'^deletemacrocycle/(?P\d+)$',views.MacroCycleDelete.as_view()), +# url(r'^deleteplan/(?P\d+)$',views.rower_delete_trainingplan), url(r'^plan/(?P\d+)$',views.rower_trainingplan_view), url(r'^macrocycle/(?P\d+)$',views.TrainingMacroCycleUpdate.as_view()), url(r'^mesocycle/(?P\d+)$',views.TrainingMesoCycleUpdate.as_view()), diff --git a/rowers/views.py b/rowers/views.py index 63d16ae5..4f5795f5 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -28,7 +28,7 @@ import isodate from django.shortcuts import render -from django.views.generic.edit import UpdateView +from django.views.generic.edit import UpdateView,DeleteView from django.http import ( HttpResponse, HttpResponseRedirect, @@ -45,7 +45,8 @@ from rowers.forms import ( VirtualRaceSelectForm,WorkoutRaceSelectForm,CourseSelectForm, RaceResultFilterForm,PowerIntervalUpdateForm ) -from django.core.urlresolvers import reverse +from django.core.urlresolvers import reverse, reverse_lazy + from django.core.exceptions import PermissionDenied from django.template import RequestContext from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger @@ -207,6 +208,7 @@ class JSONResponse(HttpResponse): def getrequestrower(request,rowerid=0,userid=0,notpermanent=False): + if notpermanent == False: if rowerid == 0 and 'rowerid' in request.session: rowerid = request.session['rowerid'] @@ -14181,47 +14183,11 @@ def virtualevent_submit_result_view(request,id=0): }) @user_passes_test(hasplannedsessions,login_url="/", redirect_field_name=None) -def rower_delete_trainingtarget(request,id=0): - try: - target = TrainingTarget.objects.get(id=id) - except TrainingPlan.DoesNotExist: - raise Http404("Training Plan Does Not Exist") - - if checkaccessuser(request.user,target.rower): - target.delete() - messages.info(request,"We have deleted the training plan") - else: - raise PermissionDenied("Access denied") +def rower_create_trainingplan(request,userid=0): - url = reverse(rower_create_trainingplan) - - return HttpResponseRedirect(url) - - -@user_passes_test(hasplannedsessions,login_url="/", redirect_field_name=None) -def rower_delete_trainingplan(request,id=0): - try: - plan = TrainingPlan.objects.get(id=id) - except TrainingPlan.DoesNotExist: - raise Http404("Training Plan Does Not Exist") - - if checkaccessuser(request.user,plan.rower): - plan.delete() - messages.info(request,"We have deleted the training plan") - else: - raise PermissionDenied("Access denied") - - url = reverse(rower_create_trainingplan) - - return HttpResponseRedirect(url) - -@user_passes_test(hasplannedsessions,login_url="/", redirect_field_name=None) -def rower_create_trainingplan(request,id=0): - - therower = getrequestrower(request,userid=id) + therower = getrequestrower(request,userid=userid) theuser = therower.user - if request.method == 'POST' and 'date' in request.POST: targetform = TrainingTargetForm(request.POST) if targetform.is_valid(): @@ -14266,10 +14232,119 @@ def rower_create_trainingplan(request,id=0): return render(request,'trainingplan_create.html', { 'form':form, + 'rower':therower, 'plans':plans, 'targets':targets, 'targetform':targetform, }) + +@user_passes_test(hasplannedsessions,login_url="/", redirect_field_name=None) +def rower_delete_trainingtarget(request,id=0): + try: + target = TrainingTarget.objects.get(id=id) + except TrainingPlan.DoesNotExist: + raise Http404("Training Plan Does Not Exist") + + if checkaccessuser(request.user,target.rower): + target.delete() + messages.info(request,"We have deleted the training plan") + else: + raise PermissionDenied("Access denied") + + url = reverse(rower_create_trainingplan) + + return HttpResponseRedirect(url) + + +@user_passes_test(hasplannedsessions,login_url="/", redirect_field_name=None) +def rower_delete_trainingplan(request,id=0): + try: + plan = TrainingPlan.objects.get(id=id) + except TrainingPlan.DoesNotExist: + raise Http404("Training Plan Does Not Exist") + + if checkaccessuser(request.user,plan.rower): + plan.delete() + messages.info(request,"We have deleted the training plan") + else: + raise PermissionDenied("Access denied") + + url = reverse(rower_create_trainingplan) + + return HttpResponseRedirect(url) + +class TrainingPlanDelete(DeleteView): + model = TrainingPlan + template_name = 'trainingplan_delete.html' + success_url = reverse_lazy(rower_create_trainingplan) + + def get_object(self, *args, **kwargs): + obj = super(TrainingPlanDelete, self).get_object(*args, **kwargs) + if not checkaccessuser(self.request.user,obj.plan.rower): + raise Http404 + + return obj + +class MicroCycleDelete(DeleteView): + model = TrainingMicroCycle + template_name = 'trainingplan_delete.html' + + def get_success_url(self): + plan = self.object.plan.plan.plan + createmacrofillers(plan) + return reverse(rower_trainingplan_view, + kwargs = { + 'id':plan.id + }) + + def get_object(self, *args, **kwargs): + obj = super(MicroCycleDelete, self).get_object(*args, **kwargs) + if not checkaccessuser(self.request.user,obj.plan.plan.plan.rower): + raise Http404 + + return obj + + +class MesoCycleDelete(DeleteView): + model = TrainingMesoCycle + template_name = 'trainingplan_delete.html' + + def get_success_url(self): + plan = self.object.plan.plan + createmacrofillers(plan) + return reverse(rower_trainingplan_view, + kwargs = { + 'id':plan.id + }) + + def get_object(self, *args, **kwargs): + obj = super(MesoCycleDelete, self).get_object(*args, **kwargs) + if not checkaccessuser(self.request.user,obj.plan.plan.rower): + raise Http404 + + return obj + + +class MacroCycleDelete(DeleteView): + model = TrainingMacroCycle + template_name = 'trainingplan_delete.html' + + def get_success_url(self): + plan = self.object.plan + createmacrofillers(plan) + return reverse(rower_trainingplan_view, + kwargs = { + 'id':plan.id + }) + + def get_object(self, *args, **kwargs): + obj = super(MacroCycleDelete, self).get_object(*args, **kwargs) + if not checkaccessuser(self.request.user,obj.plan.rower): + raise Http404 + + return obj + + @user_passes_test(hasplannedsessions,login_url="/", redirect_field_name=None) @@ -14371,6 +14446,8 @@ class TrainingMesoCycleUpdate(UpdateView): else: obj.type = 'userdefined' obj.save() + obj.plan.type = 'userdefined' + obj.plan.save() return obj class TrainingMicroCycleUpdate(UpdateView): @@ -14401,6 +14478,8 @@ class TrainingMicroCycleUpdate(UpdateView): else: obj.type = 'userdefined' obj.save() + obj.plan.type = 'userdefined' + obj.plan.save() return obj class TrainingPlanUpdate(UpdateView): diff --git a/static/css/rowsandall.css b/static/css/rowsandall.css index dfcc8a06..95fd1d8b 100644 --- a/static/css/rowsandall.css +++ b/static/css/rowsandall.css @@ -516,6 +516,14 @@ a.button { box-shadow:inset 0px 0px 0px 6px #fff; } +/* filler */ +.filler { + background: darkgray; + box-shadow:inset 0px 0px 0px 6px #fff; + -moz-box-shadow:inset 0px 0px 0px 6px #fff; + box-shadow:inset 0px 0px 0px 6px #fff; +} + .padded { padding: 10px; } From ae83fafdd040e237fc694ed3992baf0e86a63070 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 10 Sep 2018 21:11:57 +0200 Subject: [PATCH 09/17] some more logic around fillers and start dates --- rowers/models.py | 56 +++++++++++++++++++++++ rowers/templates/trainingplan.html | 19 ++++++-- rowers/templates/trainingplan_create.html | 5 +- rowers/templatetags/rowerfilters.py | 7 +++ 4 files changed, 81 insertions(+), 6 deletions(-) diff --git a/rowers/models.py b/rowers/models.py index 924c4a4a..00fd6da7 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -1095,7 +1095,20 @@ def createmacrofillers(plan): thedate = cycles[0].startdate-datetime.timedelta(days=1) cycles = cycles[1:] + cycles = TrainingMacroCycle.objects.filter( + plan = plan + ).order_by("startdate") + if cycles[0].startdate > plan.startdate: + macr = TrainingMacroCycle( + plan=plan, + startdate = plan.startdate, + enddate = cycles[0].startdate-datetime.timedelta(days=1), + type='filler', + name='Filler' + ) + macr.save() + def createmesofillers(plan): fillers = TrainingMesoCycle.objects.filter( plan = plan, type = 'filler' @@ -1131,6 +1144,20 @@ def createmesofillers(plan): macr.save() thedate = cycles[0].startdate-datetime.timedelta(days=1) cycles = cycles[1:] + + cycles = TrainingMesoCycle.objects.filter( + plan = plan + ).order_by("startdate") + + if cycles[0].startdate > plan.startdate: + macr = TrainingMesoCycle( + plan=plan, + startdate = plan.startdate, + enddate = cycles[0].startdate-datetime.timedelta(days=1), + type='filler', + name='Filler' + ) + macr.save() def createmicrofillers(plan): @@ -1169,6 +1196,20 @@ def createmicrofillers(plan): thedate = cycles[0].startdate-datetime.timedelta(days=1) cycles = cycles[1:] + cycles = TrainingMicroCycle.objects.filter( + plan = plan + ).order_by("startdate") + + if cycles[0].startdate > plan.startdate: + macr = TrainingMicroCycle( + plan=plan, + startdate = plan.startdate, + enddate = cycles[0].startdate-datetime.timedelta(days=1), + type='filler', + name='Filler' + ) + macr.save() + def microcyclecheckdates(plan): cycles = TrainingMicroCycle.objects.filter( plan=plan @@ -1203,6 +1244,21 @@ def mesocyclecheckdates(plan): thedate = cycles[0].startdate-datetime.timedelta(days=1) cycles = cycles[1:] + cycles = TrainingMesoCycle.objects.filter( + plan=plan + ).order_by("startdate") + + thedate = plan.startdate + while cycles: + if cycles[0].startdate < thedate: + cycles[0].startdate = thedate + cycles[0].save() + try: + thedate = cycles[1].startdate-datetime.timedelta(days=1) + except IndexError: + pass + cycles = cycles[1:] + def macrocyclecheckdates(plan): cycles = TrainingMacroCycle.objects.filter( plan=plan diff --git a/rowers/templates/trainingplan.html b/rowers/templates/trainingplan.html index 62dbb83c..cb063a44 100644 --- a/rowers/templates/trainingplan.html +++ b/rowers/templates/trainingplan.html @@ -29,7 +29,7 @@ are typically used to address specific phases of preparation and to indicate the racing season and may span several months. Meso cycles can be used to group sequences of three to five light, medium and - hard weeks.

+ hard weeks. It is recommended to work from left to right, starting with the macro cycles.

Macro Cycles

@@ -42,6 +42,7 @@
+ {% now "Y-m-d" as todays_date %} {% for key,macrocycle in cycles.items %}
{% if macrocycle.0.type == 'filler' %} @@ -51,9 +52,11 @@ {% endif %}

{{ macrocycle.0.name }} ({{ macrocycle.0.startdate }} - {{ macrocycle.0.enddate }})

+ {% if todays_date <= macrocycle.0.enddate|date:"Y-m-d" %}

edit - / - delete

+ / + delete

+ {% endif %}
@@ -65,9 +68,13 @@ {% endif %}

{{ mesocycle.0.name }} ({{ mesocycle.0.startdate }} - {{ mesocycle.0.enddate }})

+ {% if mesocycle.0.plan.type == 'userdefined' %} + {% if todays_date <= mesocycle.0.enddate|date:"Y-m-d" %}

edit / - delete

+ delete

+ {% endif %} + {% endif %}
@@ -79,10 +86,14 @@ {% endif %}

{{ microcycle.name }} ({{ microcycle.startdate }} - {{ microcycle.enddate }})

+ {% if microcycle.plan.type == 'userdefined' %} + {% if todays_date <= microcycle.enddate|date:"Y-m-d" %}

edit / delete

+ {% endif %} + {% endif %}
{% endfor %} diff --git a/rowers/templates/trainingplan_create.html b/rowers/templates/trainingplan_create.html index 83a7654b..350e2119 100644 --- a/rowers/templates/trainingplan_create.html +++ b/rowers/templates/trainingplan_create.html @@ -100,8 +100,9 @@ {{ plan.startdate }} {{ plan.enddate }} {{ plan.name }} - Edit - Delete + Edit + Plan + Delete {% endfor %} diff --git a/rowers/templatetags/rowerfilters.py b/rowers/templatetags/rowerfilters.py index 80e888a7..dede3b70 100644 --- a/rowers/templatetags/rowerfilters.py +++ b/rowers/templatetags/rowerfilters.py @@ -275,3 +275,10 @@ def future_registered(race,r): is_complete, has_registered = race_rower_status(r,race) is_open = race.evaluation_closure > timezone.now() return has_registered and not is_complete and is_open + +@property +def is_past_due(self): + return datetime.date.today() > self.date +@property +def is_not_past_due(self): + return datetime.date.today() <= self.date From a15a9f7d0d05e412289208aa8053e675d487b439 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 10 Sep 2018 21:14:30 +0200 Subject: [PATCH 10/17] extending filler and cycle date check logic --- rowers/models.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/rowers/models.py b/rowers/models.py index 00fd6da7..4b4e5a73 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -1227,6 +1227,21 @@ def microcyclecheckdates(plan): thedate = cycles[0].startdate-datetime.timedelta(days=1) cycles = cycles[1:] + cycles = TrainingMicroCycle.objects.filter( + plan=plan + ).order_by("startdate") + + thedate = plan.startdate + while cycles: + if cycles[0].startdate < thedate: + cycles[0].startdate = thedate + cycles[0].save() + try: + thedate = cycles[1].startdate-datetime.timedelta(days=1) + except IndexError: + pass + cycles = cycles[1:] + def mesocyclecheckdates(plan): cycles = TrainingMesoCycle.objects.filter( plan=plan @@ -1276,6 +1291,20 @@ def macrocyclecheckdates(plan): thedate = cycles[0].startdate-datetime.timedelta(days=1) cycles = cycles[1:] + cycles = TrainingMacroCycle.objects.filter( + plan=plan + ).order_by("startdate") + + thedate = plan.startdate + while cycles: + if cycles[0].startdate < thedate: + cycles[0].startdate = thedate + cycles[0].save() + try: + thedate = cycles[1].startdate-datetime.timedelta(days=1) + except IndexError: + pass + cycles = cycles[1:] class TrainingMacroCycle(models.Model): plan = models.ForeignKey(TrainingPlan) From 1acb549711d45e22a8b9159eb86d638ed7357e77 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 11 Sep 2018 18:46:11 +0200 Subject: [PATCH 11/17] sessions now also work with date range --- rowers/plannedsessions.py | 17 ++++++++++++++++- rowers/uploads.py | 1 - 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/rowers/plannedsessions.py b/rowers/plannedsessions.py index 4134ed87..e40416f0 100644 --- a/rowers/plannedsessions.py +++ b/rowers/plannedsessions.py @@ -10,7 +10,7 @@ import uuid from django.conf import settings import pytz from utils import myqueue,calculate_age,totaltime_sec_to_string - +import re import django_rq queue = django_rq.get_queue('default') queuelow = django_rq.get_queue('low') @@ -426,6 +426,8 @@ def remove_rower_session(r,ps): def get_dates_timeperiod(timeperiod): # set start end date according timeperiod + daterangetester = re.compile('^(\d+-\d+-\d+)\/(\d+-\d+-\d+)') + if timeperiod=='today': startdate=date.today() enddate=date.today() @@ -466,6 +468,19 @@ def get_dates_timeperiod(timeperiod): enddate = startdate+timezone.timedelta(days=32) enddate = enddate.replace(day=1) enddate = enddate-timezone.timedelta(days=1) + elif daterangetester.match(timeperiod): + startdatestring = daterangetester.match(timeperiod).group(1) + enddatestring = daterangetester.match(timeperiod).group(2) + try: + startdate = dt.datetime.strptime(startdatestring,'%Y-%m-%d').date() + enddate = dt.datetime.strptime(enddatestring,'%Y-%m-%d').date() + if startdate > enddate: + startdate2 = enddate + enddate = startdate + startdate = startdate2 + except ValueError: + startdate = date.today() + enddate = date.today() else: startdate = date.today() enddate = date.today() diff --git a/rowers/uploads.py b/rowers/uploads.py index 2304e37d..d871d870 100644 --- a/rowers/uploads.py +++ b/rowers/uploads.py @@ -20,7 +20,6 @@ from subprocess import call import re from verbalexpressions import VerEx -import re import django_rq queue = django_rq.get_queue('default') From 0e2c5c9d39b5fec4d12f620fd941d7a3814e323e Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Thu, 13 Sep 2018 12:54:30 +0200 Subject: [PATCH 12/17] linking back to plan --- rowers/templates/planningbuttons.html | 22 +++++++----- rowers/templates/trainingplan.html | 52 +++++++++++++++++++-------- rowers/views.py | 40 ++++++++++++++++++--- 3 files changed, 87 insertions(+), 27 deletions(-) diff --git a/rowers/templates/planningbuttons.html b/rowers/templates/planningbuttons.html index 1e9c944a..324efd56 100644 --- a/rowers/templates/planningbuttons.html +++ b/rowers/templates/planningbuttons.html @@ -1,14 +1,18 @@ {% load rowerfilters %}
-

- {% if timeperiod and rower %} - Plan Overview - {% elif timeperiod %} - Plan Overview - {% else %} - Plan Overview - {% endif %} -

+

diff --git a/rowers/templates/trainingplan.html b/rowers/templates/trainingplan.html index cb063a44..d7a9f850 100644 --- a/rowers/templates/trainingplan.html +++ b/rowers/templates/trainingplan.html @@ -20,16 +20,6 @@

Training Plan - {{ plan.name }}

This plan starts on {{ plan.startdate }} and ends on {{ plan.enddate }}. The training plan target is: {{ plan.target.name }} on {{ plan.target.date }}.

Edit the plan

-

Click on the plan cycles below to edit their names, start and end dates. The gray "filler" - cycles are generated, adjusted and deleted automatically to ensure the entire plan - duration is covered with non-overlapping cycles. - Once you edit a filler cycle, it become a user-defined cycle, which cannot be deleted - by the system.

-

A good way to organize the plan is to think of micro cycles as training weeks. Macro cycles - are typically used to address specific phases of preparation and to indicate the racing - season and may span several months. - Meso cycles can be used to group sequences of three to five light, medium and - hard weeks. It is recommended to work from left to right, starting with the macro cycles.

Macro Cycles

@@ -55,7 +45,12 @@ {% if todays_date <= macrocycle.0.enddate|date:"Y-m-d" %}

edit / - delete

+ delete + / + sessions +

+ {% else %} +

 

{% endif %}
@@ -72,8 +67,14 @@ {% if todays_date <= mesocycle.0.enddate|date:"Y-m-d" %}

edit / - delete

+ delete + / + sessions + +

{% endif %} + {% else %} +

 

{% endif %}
@@ -91,8 +92,13 @@

edit / - delete

+ delete + / + sessions +

{% endif %} + {% else %} +

 

{% endif %}
@@ -102,5 +108,23 @@
{% endfor %} -
+
+
+

Click on the plan cycles to edit their names, start and end dates. The gray "filler" + cycles are generated, adjusted and deleted automatically to ensure the entire plan + duration is covered with non-overlapping cycles. + Once you edit a filler cycle, it become a user-defined cycle, which cannot be deleted + by the system.

+

Filler cycles which have a filler cycle as a parent cannot be edited + or deleted. You have to edit the parent cycle first. The reason is + that children of filler cycles are not safe. They are deleted when + their parent is deleted by the system.

+

Click on "Sessions" in the cycle of your interest to see details + of the individual training sessions planned for this period.

+

A good way to organize the plan is to think of micro cycles as training weeks. Macro cycles + are typically used to address specific phases of preparation and to indicate the racing + season and may span several months. + Meso cycles can be used to group sequences of three to five light, medium and + hard weeks. It is recommended to work from left to right, starting with the macro cycles.

+
{% endblock %} diff --git a/rowers/views.py b/rowers/views.py index 4f5795f5..90acf0b8 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -12483,6 +12483,7 @@ def plannedsession_multiclone_view( def plannedsession_create_view(request,timeperiod='thisweek',rowerid=0): r = getrequestrower(request,rowerid=rowerid) + startdate,enddate = get_dates_timeperiod(timeperiod) if request.method == 'POST': sessioncreateform = PlannedSessionForm(request.POST) @@ -12539,26 +12540,46 @@ def plannedsession_create_view(request,timeperiod='thisweek',rowerid=0): fstartdate = arrow.get(request.session['fstartdate']).date() except KeyError: fstartdate = timezone.now().date() + if fstartdate < startdate: + fstartdate = startdate try: fenddate = arrow.get(request.session['fenddate']).date() except KeyError: fenddate = timezone.now().date() + if fenddate > enddate: + fenddate = enddate try: fprefdate = arrow.get(request.session['fprefdate']).date() except KeyError: fprefdate = timezone.now().date() + if fprefdate < startdate: + fprefdate = startdate + + if fprefdate > enddate: + fprefdate = enddate + forminitial = { 'startdate':fstartdate, 'enddate':fenddate, 'preferreddate':fprefdate } else: - forminitial = {} + preferreddate = startdate + if preferreddate < timezone.now().date(): + preferreddate = timezone.now().date() + + if preferreddate > enddate: + preferreddate = enddate + + forminitial = { + 'startdate':startdate, + 'enddate':enddate, + 'preferreddate':preferreddate, + } sessioncreateform = PlannedSessionForm(initial=forminitial) - startdate,enddate = get_dates_timeperiod(timeperiod) sps = get_sessions(r,startdate=startdate,enddate=enddate) return render(request,'plannedsessioncreate.html', @@ -12943,7 +12964,15 @@ def plannedsessions_view(request,timeperiod='thisweek',rowerid=0): r = getrequestrower(request,rowerid=rowerid) startdate,enddate = get_dates_timeperiod(timeperiod) - + + try: + trainingplan = TrainingPlan.objects.filter( + startdate__lte = startdate, + enddate__gte = enddate)[0] + except IndexError: + trainingplan = None + + sps = get_sessions(r,startdate=startdate,enddate=enddate) completeness = {} @@ -12966,6 +12995,7 @@ def plannedsessions_view(request,timeperiod='thisweek',rowerid=0): { 'teams':get_my_teams(request.user), 'plannedsessions':sps, + 'plan':trainingplan, 'rower':r, 'timeperiod':timeperiod, 'completeness':completeness, @@ -13120,6 +13150,7 @@ def plannedsessions_manage_view(request,timeperiod='thisweek',rowerid=0, # Clone an existing planned session +# need clarity on cloning behavior time shift @user_passes_test(hasplannedsessions,login_url="/rowers/planmembership/", redirect_field_name=None) def plannedsession_clone_view(request,id=0,rowerid=0, @@ -13146,8 +13177,9 @@ def plannedsession_clone_view(request,id=0,rowerid=0, ps.startdate = timezone.now().date() ps.enddate = (timezone.now()+deltadays).date() + ps.preferreddate = ps.preferreddate+deltadays ps.name += ' (copy)' - + ps.save() for rower in rowers: From f5f6842a7cb4b5f735ea320a82b83ea21cad6193 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 14 Sep 2018 06:49:23 +0200 Subject: [PATCH 13/17] select back to plan --- rowers/templates/planningbuttons.html | 2 + rowers/views.py | 86 +++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/rowers/templates/planningbuttons.html b/rowers/templates/planningbuttons.html index 324efd56..57113b33 100644 --- a/rowers/templates/planningbuttons.html +++ b/rowers/templates/planningbuttons.html @@ -10,7 +10,9 @@ {% else %} Sessions Overview {% endif %} + {% if plan %} Plan Overview + {% endif %}
diff --git a/rowers/views.py b/rowers/views.py index 90acf0b8..88dc3293 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -12465,8 +12465,18 @@ def plannedsession_multiclone_view( dateshiftform = SessionDateShiftForm() + try: + trainingplan = TrainingPlan.objects.filter( + startdate__lte = startdate, + rower = r, + enddate__gte = enddate)[0] + except IndexError: + trainingplan = None + + return render(request, 'plannedsessions_multiclone_select.html', {'plannedsessions':sps, + 'plan':trainingplan, 'dateform':dateform, 'startdate':startdate, 'enddate':enddate, @@ -12581,10 +12591,19 @@ def plannedsession_create_view(request,timeperiod='thisweek',rowerid=0): sessioncreateform = PlannedSessionForm(initial=forminitial) sps = get_sessions(r,startdate=startdate,enddate=enddate) + try: + trainingplan = TrainingPlan.objects.filter( + startdate__lte = startdate, + rower = r, + enddate__gte = enddate)[0] + except IndexError: + trainingplan = None + return render(request,'plannedsessioncreate.html', { 'teams':get_my_teams(request.user), + 'plan':trainingplan, 'form':sessioncreateform, 'plannedsessions':sps, 'rower':r, @@ -12601,6 +12620,14 @@ def plannedsession_multicreate_view(request,timeperiod='thisweek', r = getrequestrower(request,rowerid=rowerid) startdate,enddate = get_dates_timeperiod(timeperiod) + try: + trainingplan = TrainingPlan.objects.filter( + startdate__lte = startdate, + rower = r, + enddate__gte = enddate)[0] + except IndexError: + trainingplan = None + sps = get_sessions(r,startdate=startdate,enddate=enddate) m = Rower.objects.get(user=request.user) @@ -12675,6 +12702,7 @@ def plannedsession_multicreate_view(request,timeperiod='thisweek', context = { 'ps_formset':ps_formset, 'rower':r, + 'plan':trainingplan, 'timeperiod':timeperiod, 'teams':get_my_teams(request.user), 'extrasessions': extrasessions+1 @@ -12700,6 +12728,14 @@ def plannedsession_teamcreate_view(request,timeperiod='thisweek', startdate,enddate = get_dates_timeperiod(timeperiod) + try: + trainingplan = TrainingPlan.objects.filter( + startdate__lte = startdate, + rower = r, + enddate__gte = enddate)[0] + except IndexError: + trainingplan = None + sps = [] for team in teams: res = get_sessions_manager(request.user,startdate=startdate,enddate=enddate) @@ -12772,6 +12808,7 @@ def plannedsession_teamcreate_view(request,timeperiod='thisweek', return render(request,'plannedsessionteamcreate.html', { 'teams':get_my_teams(request.user), + 'plan':trainingplan, 'form':sessioncreateform, 'teamform':sessionteamselectform, 'timeperiod':timeperiod, @@ -12799,6 +12836,14 @@ def plannedsession_teamedit_view(request,timeperiod='thisweek', startdate,enddate = get_dates_timeperiod(timeperiod) + try: + trainingplan = TrainingPlan.objects.filter( + startdate__lte = startdate, + rower = r, + enddate__gte = enddate)[0] + except IndexError: + trainingplan = None + sps = [] rowers = [] for team in teams: @@ -12885,6 +12930,7 @@ def plannedsession_teamedit_view(request,timeperiod='thisweek', return render(request,'plannedsessionteamedit.html', { 'plannedsession':ps, + 'plan':trainingplan, 'teams':get_my_teams(request.user), 'form':sessioncreateform, 'teamform':sessionteamselectform, @@ -12900,6 +12946,8 @@ def plannedsessions_coach_view(request,timeperiod='thisweek', startdate,enddate = get_dates_timeperiod(timeperiod) + trainingplan = None + if teamid != 0: try: theteam = Team.objects.get(id=teamid) @@ -12949,6 +12997,7 @@ def plannedsessions_coach_view(request,timeperiod='thisweek', { 'myteams':myteams, 'plannedsessions':sps, + 'plan':trainingplan, 'statusdict':statusdict, 'timeperiod':timeperiod, 'rowers':rowers, @@ -12968,6 +13017,7 @@ def plannedsessions_view(request,timeperiod='thisweek',rowerid=0): try: trainingplan = TrainingPlan.objects.filter( startdate__lte = startdate, + rower = r, enddate__gte = enddate)[0] except IndexError: trainingplan = None @@ -13011,6 +13061,14 @@ def plannedsessions_print_view(request,timeperiod='thisweek',rowerid=0): startdate,enddate = get_dates_timeperiod(timeperiod) + try: + trainingplan = TrainingPlan.objects.filter( + startdate__lte = startdate, + rower = r, + enddate__gte = enddate)[0] + except IndexError: + trainingplan = None + sps = get_sessions(r,startdate=startdate,enddate=enddate) completeness = {} @@ -13020,6 +13078,7 @@ def plannedsessions_print_view(request,timeperiod='thisweek',rowerid=0): return render(request,'plannedsessions_print.html', { 'teams':get_my_teams(request.user), + 'plan':trainingplan, 'plannedsessions':sps, 'rower':r, 'startdate':startdate, @@ -13040,6 +13099,14 @@ def plannedsessions_manage_view(request,timeperiod='thisweek',rowerid=0, startdate,enddate = get_dates_timeperiod(timeperiod) + try: + trainingplan = TrainingPlan.objects.filter( + startdate__lte = startdate, + rower = r, + enddate__gte = enddate)[0] + except IndexError: + trainingplan = None + sps = get_sessions(r,startdate=startdate,enddate=enddate) if initialsession==0: try: @@ -13140,6 +13207,7 @@ def plannedsessions_manage_view(request,timeperiod='thisweek',rowerid=0, return render(request,'plannedsessionsmanage.html', { 'teams':get_my_teams(request.user), + 'plan':trainingplan, 'plannedsessions':sps, 'workouts':ws, 'timeperiod':timeperiod, @@ -13160,6 +13228,14 @@ def plannedsession_clone_view(request,id=0,rowerid=0, startdate,enddate = get_dates_timeperiod(timeperiod) + try: + trainingplan = TrainingPlan.objects.filter( + startdate__lte = startdate, + rower = r, + enddate__gte = enddate)[0] + except IndexError: + trainingplan = None + try: ps = PlannedSession.objects.get(id=id) except PlannedSession.DoesNotExist: @@ -13208,6 +13284,14 @@ def plannedsession_edit_view(request,id=0,timeperiod='thisweek',rowerid=0): startdate,enddate = get_dates_timeperiod(timeperiod) + try: + trainingplan = TrainingPlan.objects.filter( + startdate__lte = startdate, + rower = r, + enddate__gte = enddate)[0] + except IndexError: + trainingplan = None + try: ps = PlannedSession.objects.get(id=id) except PlannedSession.DoesNotExist: @@ -13257,6 +13341,7 @@ def plannedsession_edit_view(request,id=0,timeperiod='thisweek',rowerid=0): return render(request,'plannedsessionedit.html', { 'teams':get_my_teams(request.user), + 'plan':trainingplan, 'form':sessioncreateform, 'plannedsessions':sps, 'thesession':ps, @@ -13385,6 +13470,7 @@ def plannedsession_view(request,id=0,rowerid=0, 'manager':m, 'rower':r, 'ratio':ratio, + 'plan':trainingplan, 'status':status, 'results':resultsdict, 'plannedsession':ps, From 09c10633ac347ec60eb8526d5068718dada8d3e3 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 15 Sep 2018 12:29:55 +0200 Subject: [PATCH 14/17] improved planned sessions for course test --- rowers/plannedsessions.py | 68 ++++----------------------------------- rowers/views.py | 40 +++++++++++++++-------- 2 files changed, 34 insertions(+), 74 deletions(-) diff --git a/rowers/plannedsessions.py b/rowers/plannedsessions.py index e40416f0..bec7cc52 100644 --- a/rowers/plannedsessions.py +++ b/rowers/plannedsessions.py @@ -296,38 +296,10 @@ def is_session_complete_ws(ws,ps): if record.coursecompleted: ratio = record.distance/ps.sessionvalue return ratio,'completed',completiondate - - if ps.course: - ( - coursetime, - coursemeters, - coursecompleted - ) = get_time_course(ws,ps.course) - if coursecompleted: - return 1.0,'completed',completiondate - else: - return ratio,'partial',completiondate - else: - if ps.criterium == 'exact': - if ratio == 1.0: - return ratio,'completed',completiondate - else: - if not completiondate: - completiondate = ws.reverse()[0].date - return ratio,'partial',completiondate - elif ps.criterium == 'minimum': - if ratio >= 1.0: - return ratio,'completed',completiondate - else: - if not completiondate: - completiondate = ws.reverse()[0].date - - return ratio,'partial',completiondate - else: - if ratio>cratiomin and ratio= 1.0: - return ratio,'completed',completiondate - else: - if not completiondate: - completiondate = ws.reverse()[0].date - return ratio,'partial',completiondate - else: - if ratio>cratiomin and ratio Date: Sat, 15 Sep 2018 16:07:19 +0200 Subject: [PATCH 15/17] fixed bug in race check --- rowers/courseutils.py | 2 +- rowers/plannedsessions.py | 1 + rowers/tasks.py | 8 +++++--- rowers/templates/plannedsessionview.html | 2 ++ rowers/views.py | 8 ++++++-- 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/rowers/courseutils.py b/rowers/courseutils.py index 75130af5..2ce31fb3 100644 --- a/rowers/courseutils.py +++ b/rowers/courseutils.py @@ -95,5 +95,5 @@ def coursetime_paths(data,paths,finalmaxmin='min'): entrytime = data['time'].max() entrydistance = data['cum_dist'].max() coursecompleted = False - + return entrytime, entrydistance, coursecompleted diff --git a/rowers/plannedsessions.py b/rowers/plannedsessions.py index bec7cc52..d9534de4 100644 --- a/rowers/plannedsessions.py +++ b/rowers/plannedsessions.py @@ -317,6 +317,7 @@ def is_session_complete_ws(ws,ps): record = CourseTestResult( userid=ws[0].user.id, plannedsession=ps, + workoutid=ws[0].id, duration=dt.time(0,0), coursecompleted=False, ) diff --git a/rowers/tasks.py b/rowers/tasks.py index 1581d28c..72f278e8 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -255,7 +255,7 @@ def handle_check_race_course(self, rowdata.rename(columns = { ' latitude':'latitude', ' longitude':'longitude', - ' ElapsedTime (sec)': 'time', + 'TimeStamp (sec)': 'time', }, inplace=True) rowdata.fillna(method='backfill',inplace=True) @@ -299,9 +299,9 @@ def handle_check_race_course(self, cseconds = [] cmeters = [] ccomplete = [] - + for startt in entrytimes: - + rowdata2 = rowdata[rowdata['time']>(startt-10.)] ( @@ -322,6 +322,7 @@ def handle_check_race_course(self, coursetimeseconds = coursetimeseconds-coursetimefirst coursemeters = coursemeters-coursemetersfirst + cseconds.append(coursetimeseconds) cmeters.append(coursemeters) ccomplete.append(coursecompleted) @@ -335,6 +336,7 @@ def handle_check_race_course(self, records = records[records['coursecompleted'] == True] + if len(records): coursecompleted = True mintime = records['coursetimeseconds'].min() diff --git a/rowers/templates/plannedsessionview.html b/rowers/templates/plannedsessionview.html index 8ace4c3a..34df6972 100644 --- a/rowers/templates/plannedsessionview.html +++ b/rowers/templates/plannedsessionview.html @@ -65,6 +65,7 @@ {% for result in ranking %} + {% if result|lookup:'coursecompleted' %} {{ forloop.counter }} {{ result|lookup:'name' }} @@ -73,6 +74,7 @@ {{ result|lookup:'date'|date:"Y-m-d" }} {{ result|lookup:'type' }} + {% endif %} {% endfor %} diff --git a/rowers/views.py b/rowers/views.py index c150cd9e..d53ff3c6 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -13418,6 +13418,7 @@ def plannedsession_view(request,id=0,rowerid=0, 'distance': w.distance, 'time': dddelta, 'type': w.workouttype, + 'coursecompleted':True, } if ps.sessiontype == 'coursetest': vs = CourseTestResult.objects.filter(plannedsession=ps, @@ -13436,12 +13437,13 @@ def plannedsession_view(request,id=0,rowerid=0, microseconds=t.microsecond ) wdict['distance'] = int(round(coursemeters)) + wdict['coursecompleted'] = coursecompleted else: record = CourseTestResult( userid=w.user.id, workoutid=w.id, plannedsession=ps, - duration=dt.time(0,0), + duration=w.duration, coursecompleted=False, ) record.save() @@ -13452,8 +13454,10 @@ def plannedsession_view(request,id=0,rowerid=0, intsecs = 0 microsecs = 0 - wdict['time'] = record.duration + # taking workout duration plus 1 minute penalty + wdict['time'] = w.duration wdict['distance'] = ps.course.distance + wdict['coursecompleted'] = False ranking.append(wdict) From 7fb606842d3c1c77348a4ed76764ac8106a55b1a Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 15 Sep 2018 22:05:29 +0200 Subject: [PATCH 16/17] adding plan/actual in plan view --- rowers/models.py | 39 ++++++ rowers/plannedsessions.py | 5 +- rowers/templates/trainingplan.html | 199 +++++++++++++++++++++-------- rowers/views.py | 82 ++++++++++++ 4 files changed, 269 insertions(+), 56 deletions(-) diff --git a/rowers/models.py b/rowers/models.py index 4b4e5a73..cbc298ef 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -1313,10 +1313,21 @@ class TrainingMacroCycle(models.Model): enddate = models.DateField( default=half_year_from_now) notes = models.TextField(max_length=300,blank=True) + type = models.CharField(default='filler', choices=cycletypechoices, max_length=150) + plantime = models.IntegerField(default=0,verbose_name='Planned Duration') + plandistance = models.IntegerField(default=0,verbose_name='Planned Distance') + planrscore = models.IntegerField(default=0,verbose_name='Planned rScore') + plantrimp = models.IntegerField(default=0,verbose_name='Planned TRIMP') + + actualtime = models.IntegerField(default=0,verbose_name='Actual Duration') + actualdistance = models.IntegerField(default=0,verbose_name='Actual Distance') + actualrscore = models.IntegerField(default=0,verbose_name='Actual rScore') + actualtrimp = models.IntegerField(default=0,verbose_name='Actual TRIMP') + def __unicode__(self): stri = 'Macro Cycle - {n} ({sd} - {ed})'.format( n = self.name, @@ -1391,6 +1402,18 @@ class TrainingMesoCycle(models.Model): type = models.CharField(default='filler', choices=cycletypechoices, max_length=150) + + plantime = models.IntegerField(default=0,verbose_name='Planned Duration') + plandistance = models.IntegerField(default=0,verbose_name='Planned Distance') + planrscore = models.IntegerField(default=0,verbose_name='Planned rScore') + plantrimp = models.IntegerField(default=0,verbose_name='Planned TRIMP') + + actualtime = models.IntegerField(default=0,verbose_name='Actual Duration') + actualdistance = models.IntegerField(default=0,verbose_name='Actual Distance') + actualrscore = models.IntegerField(default=0,verbose_name='Actual rScore') + actualtrimp = models.IntegerField(default=0,verbose_name='Actual TRIMP') + + def __unicode__(self): stri = 'Meso Cycle - {n} ({sd} - {ed})'.format( n = self.name, @@ -1454,6 +1477,20 @@ class TrainingMicroCycle(models.Model): type = models.CharField(default='filler', choices=cycletypechoices, max_length=150) + + plantime = models.IntegerField(default=0,verbose_name='Planned Duration') + plandistance = models.IntegerField(default=0,verbose_name='Planned Distance') + planrscore = models.IntegerField(default=0,verbose_name='Planned rScore') + plantrimp = models.IntegerField(default=0,verbose_name='Planned TRIMP') + + actualtime = models.IntegerField(default=0,verbose_name='Actual Duration') + actualdistance = models.IntegerField(default=0,verbose_name='Actual Distance') + actualrscore = models.IntegerField(default=0,verbose_name='Actual rScore') + actualtrimp = models.IntegerField(default=0,verbose_name='Actual TRIMP') + + + + def __unicode__(self): stri = 'Micro Cycle - {n} ({sd} - {ed})'.format( n = self.name, @@ -1491,6 +1528,8 @@ class TrainingMicroCycle(models.Model): if othercycle.enddate >= self.startdate and othercycle.enddate <= self.enddate: self.startdate = othercycle.enddate+datetime.timedelta(days=1) + + if not self.enddate <= self.startdate: super(TrainingMicroCycle,self).save(*args, **kwargs) diff --git a/rowers/plannedsessions.py b/rowers/plannedsessions.py index d9534de4..46b7f326 100644 --- a/rowers/plannedsessions.py +++ b/rowers/plannedsessions.py @@ -306,10 +306,10 @@ def is_session_complete_ws(ws,ps): for record in vs: if record.workoutid in wids: if record.coursecompleted: - ratio = record.distance/ps.sessionvalue + ratio = record.distance/float(ps.sessionvalue) return ratio,'completed',completiondate else: - ratio = record.distance/ps.sessionvalue + ratio = record.distance/float(ps.sessionvalue) return ratio,'partial',completiondate # we're still here - no record, need to create one @@ -338,6 +338,7 @@ def is_session_complete_ws(ws,ps): def is_session_complete(r,ps): status = 'not done' + if r not in ps.rower.all(): return 0,'not assigned',None diff --git a/rowers/templates/trainingplan.html b/rowers/templates/trainingplan.html index d7a9f850..e3d67466 100644 --- a/rowers/templates/trainingplan.html +++ b/rowers/templates/trainingplan.html @@ -40,18 +40,49 @@ {% else %}
{% endif %} -
-

{{ macrocycle.0.name }} ({{ macrocycle.0.startdate }} - {{ macrocycle.0.enddate }})

- {% if todays_date <= macrocycle.0.enddate|date:"Y-m-d" %} -

edit - / - delete - / - sessions -

- {% else %} -

 

- {% endif %} +
+ + + + + + + + + + + + + + + + + + + + + + + + + + {% if todays_date <= macrocycle.0.enddate|date:"Y-m-d" %} + + + + + + + {% endif %} +
+ {{ macrocycle.0.name }} ({{ macrocycle.0.startdate }} - {{ macrocycle.0.enddate }}) +
dist (m)t (min)rScoreTRIMP
plan{{ macrocycle.0.plandistance }}{{ macrocycle.0.plantime }}{{ maccrocycle.0.planrscore }}{{ macrocycle.0.plantrimp }}
actual{{ macrocycle.0.actualdistance }}{{ macrocycle.0.actualtime }}{{ macrocycle.0.actualrscore }}{{ macrocycle.0.actualtrimp }}
 
+ edit + / + delete + / + sessions +
@@ -59,55 +90,115 @@ {% if mesocycle.0.type == 'filler' %}
{% else %} -
+
+ {% endif %} +
+ + + + + {% if mesocycle.0.plan.type == 'userdefined' %} + + + + + + + + + + + + + + + + + + + + + + {% if todays_date <= mesocycle.0.enddate|date:"Y-m-d" %} + + + + + + + {% endif %} + {% endif %} +
+ {{ mesocycle.0.name }} ({{ mesocycle.0.startdate }} - {{ mesocycle.0.enddate }}) +
dist (m)t (min)rScoreTRIMP
plan{{ mesocycle.0.plandistance }}{{ mesocycle.0.plantime }}{{ mesocycle.0.planrscore }}{{ mesocycle.0.plantrimp }}
actual{{ mesocycle.0.actualdistance }}{{ mesocycle.0.actualtime }}{{ mesocycle.0.actualrscore }}{{ mesocycle.0.actualtrimp }}
 
+ edit + / + delete + / + sessions +
+
+
+
+ {% for microcycle in mesocycle.1 %} + {% if microcycle.type == 'filler' %} +
+ {% else %} +
{% endif %}
-

{{ mesocycle.0.name }} ({{ mesocycle.0.startdate }} - {{ mesocycle.0.enddate }})

- {% if mesocycle.0.plan.type == 'userdefined' %} - {% if todays_date <= mesocycle.0.enddate|date:"Y-m-d" %} -

edit - / - delete - / - sessions - -

- {% endif %} - {% else %} -

 

- {% endif %} + + + + + {% if microcycle.plan.type == 'userdefined' %} + + + + + + + + + + + + + + + + + + + + + + {% if todays_date <= microcycle.enddate|date:"Y-m-d" %} + + + + + + + {% endif %} + {% endif %} +
+ {{ microcycle.name }} ({{ microcycle.startdate }} - {{ microcycle.enddate }}) +
dist (m)t (min)rScoreTRIMP
plan{{ microcycle.plandistance }}{{ microcycle.plantime }}{{ microcycle.planrscore }}{{ microcycle.plantrimp }}
actual{{ microcycle.actualdistance }}{{ microcycle.actualtime }}{{ microcycle.actualrscore }}{{ microcycle.actualtrimp }}
 
+ edit + / + delete + / + sessions +
-
- {% for microcycle in mesocycle.1 %} - {% if microcycle.type == 'filler' %} -
- {% else %} -
- {% endif %} -
-

{{ microcycle.name }} ({{ microcycle.startdate }} - {{ microcycle.enddate }})

- {% if microcycle.plan.type == 'userdefined' %} - {% if todays_date <= microcycle.enddate|date:"Y-m-d" %} -

- edit - / - delete - / - sessions -

- {% endif %} - {% else %} -

 

- {% endif %} -
-
- {% endfor %} + {% endfor %}
{% endfor %}
-
- {% endfor %} +
+ {% endfor %}

Click on the plan cycles to edit their names, start and end dates. The gray "filler" diff --git a/rowers/views.py b/rowers/views.py index d53ff3c6..df481220 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -14499,17 +14499,98 @@ def rower_trainingplan_view(request,id=0): count = 0 cycles = {} + r = plan.rower for m in macrocycles: createmesofillers(m) + m.plantime = 0 + m.actualtime = 0 + m.plandistance = 0 + m.actualdistance = 0 + m.planrscore = 0 + m.actualrscore = 0 + m.plantrimp = 0 + m.actualtrimp = 0 + + mesocycles = TrainingMesoCycle.objects.filter(plan=m).order_by("startdate") mesos = {} count2 = 0 for me in mesocycles: createmicrofillers(me) + me.plantime = 0 + me.actualtime = 0 + me.plandistance = 0 + me.actualdistance = 0 + me.planrscore = 0 + me.actualrscore = 0 + me.plantrimp = 0 + me.actualtrimp = 0 + microcycles = TrainingMicroCycle.objects.filter(plan=me).order_by("startdate") + + for mm in microcycles: + sps = PlannedSession.objects.filter( + rower = plan.rower, + startdate__lte=mm.enddate, + enddate__gte=mm.startdate) + + + mm.plantime = 0 + mm.actualtime = 0 + mm.plandistance = 0 + mm.actualdistance = 0 + mm.planrscore = 0 + mm.actualrscore = 0 + mm.plantrimp = 0 + mm.actualtrimp = 0 + + + if mm.type == 'userdefined': + for ps in sps: + ratio, status, cdate = is_session_complete(r,ps) + if ps.sessionmode == 'time': + mm.plantime += ps.sessionvalue + mm.actualtime += ps.sessionvalue*ratio + elif ps.sessionmode == 'distance': + mm.plandistance += ps.sessionvalue + mm.actualdistance += ps.sessionvalue*ratio + elif ps.sessionmode == 'rScore': + mm.planrscore += ps.sessionvalue + mm.actualrscore += ps.sessionvalue*ratio + elif ps.sessionmode == 'TRIMP': + mm.plantrimp += ps.sessionvalue + mm.actualtrimp += ps.sessionvalue*ratio + + mm.save() + + me.plantime += mm.plantime + me.actualtime += mm.actualtime + me.plandistance += mm.plandistance + me.actualdistance += mm.actualdistance + me.planrscore += mm.planrscore + me.actualrscore += mm.actualrscore + me.plantrimp += mm.plantrimp + me.actualtrimp += mm.actualtrimp + + if me.type == 'userdefined': + me.save() + + m.plantime += me.plantime + m.actualtime += me.actualtime + m.plandistance += me.plandistance + m.actualdistance += me.actualdistance + m.planrscore += me.planrscore + m.actualrscore += me.actualrscore + m.plantrimp += me.plantrimp + m.actualtrimp += me.actualtrimp + + mesos[count2] = (me, microcycles) count2 += 1 + + if m.type == 'userdefined': + m.save() cycles[count] = (m,mesos) count += 1 @@ -14604,6 +14685,7 @@ class TrainingMicroCycleUpdate(UpdateView): form.instance.user = self.request.user form.instance.post_date = datetime.datetime.now() microcycle = form.save() + return super(TrainingMicroCycleUpdate, self).form_valid(form) def get_object(self, *args, **kwargs): From 8e142cfa3a76058ddef421399fc607dd1d2a6d8d Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 15 Sep 2018 22:10:57 +0200 Subject: [PATCH 17/17] plan view actuals as int --- rowers/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rowers/views.py b/rowers/views.py index df481220..7a232db9 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -14551,16 +14551,16 @@ def rower_trainingplan_view(request,id=0): ratio, status, cdate = is_session_complete(r,ps) if ps.sessionmode == 'time': mm.plantime += ps.sessionvalue - mm.actualtime += ps.sessionvalue*ratio + mm.actualtime += int(ps.sessionvalue*ratio) elif ps.sessionmode == 'distance': mm.plandistance += ps.sessionvalue - mm.actualdistance += ps.sessionvalue*ratio + mm.actualdistance += int(ps.sessionvalue*ratio) elif ps.sessionmode == 'rScore': mm.planrscore += ps.sessionvalue - mm.actualrscore += ps.sessionvalue*ratio + mm.actualrscore += int(ps.sessionvalue*ratio) elif ps.sessionmode == 'TRIMP': mm.plantrimp += ps.sessionvalue - mm.actualtrimp += ps.sessionvalue*ratio + mm.actualtrimp += int(ps.sessionvalue*ratio) mm.save()