From 91d59dfc28a87e989629ecc7d7d7e668555857e4 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 7 Feb 2018 16:37:03 +0100 Subject: [PATCH] attach workouts to sessions version 0 --- rowers/forms.py | 26 ++++ rowers/models.py | 4 - rowers/plannedsessions.py | 138 +++++++++++++++-- rowers/templates/.#list_workouts.html | 1 + rowers/templates/plannedsessioncreate.html | 2 +- rowers/templates/plannedsessionedit.html | 7 +- rowers/templates/plannedsessions.html | 32 +++- rowers/templates/plannedsessionsmanage.html | 74 +++++++++ rowers/templates/plannedsessionview.html | 32 +++- rowers/templates/plannedsssionsmanage.html | 48 ++++++ rowers/urls.py | 8 + rowers/views.py | 159 +++++++++++++++----- static/css/rowsandall.css | 13 ++ 13 files changed, 483 insertions(+), 61 deletions(-) create mode 100644 rowers/templates/.#list_workouts.html create mode 100644 rowers/templates/plannedsessionsmanage.html create mode 100644 rowers/templates/plannedsssionsmanage.html diff --git a/rowers/forms.py b/rowers/forms.py index 5a7c4a45..e4b3f4b8 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -602,3 +602,29 @@ class FusionMetricChoiceForm(ModelForm): metricchoices = list(sorted(formaxlabels2.items(), key = lambda x:x[1])) self.fields['columns'].choices = metricchoices +class PlannedSessionSelectForm(forms.Form): + + def __init__(self, sessionchoices, *args, **kwargs): + + super(PlannedSessionSelectForm, self).__init__(*args,**kwargs) + + self.fields['plannedsession'] = forms.ChoiceField( + label='Sessions', + choices = sessionchoices, + widget = forms.RadioSelect, + ) + + +class WorkoutSessionSelectForm(forms.Form): + + def __init__(self, workoutdata, *args, **kwargs): + + super(WorkoutSessionSelectForm, self).__init__(*args, **kwargs) + + self.fields['workouts'] = forms.MultipleChoiceField( + label='Workouts', + choices = workoutdata['choices'], + initial=workoutdata['initial'], + widget = forms.CheckboxSelectMultiple, + ) + diff --git a/rowers/models.py b/rowers/models.py index 9b801cc3..678920bb 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -855,10 +855,6 @@ class PlannedSession(models.Model): choices=verificationchoices ) - # 0 = incomplete, 1 = complete, >1 = partial (details could - # be defined later) - sessioncompleted = models.IntegerField(default=0) - team = models.ManyToManyField(Team,blank=True) rower = models.ManyToManyField(Rower,blank=True) diff --git a/rowers/plannedsessions.py b/rowers/plannedsessions.py index 1a5ae4ea..1b907f7c 100644 --- a/rowers/plannedsessions.py +++ b/rowers/plannedsessions.py @@ -26,14 +26,28 @@ import numpy as np import dataprep # Low Level functions - to be called by higher level methods - -# dummies for now def add_workouts_plannedsession(ws,ps): - for w in ws: - w.plannedsession = ps - w.save() + result = 0 + comments = [] + errors = [] - return 1 + # check if all sessions have same date + dates = [w.date for w in ws] + if (not all(d == dates[0] for d in dates)) and ps.sessiontype != 'challenge': + errors.append('For tests and training sessions, selected workouts must all be done on the same date') + return result,comments,errors + + # start adding sessions + for w in ws: + if w.date>=ps.startdate and w.date<=ps.enddate: + w.plannedsession = ps + w.save() + result += 1 + comments.append('Attached workout %i to session' % w.id) + else: + errors.append('Workout %i did not match session dates' % w.id) + + return result,comments,errors def remove_workout_plannedsession(w,ps): @@ -57,8 +71,20 @@ def timefield_to_seconds_duration(t): return duration -def is_session_complete(ps): - ws = Workout.objects.filter(plannedsession=ps) +def is_session_complete(r,ps): + status = 'not done' + + ws = Workout.objects.filter(user=r,plannedsession=ps) + + if len(ws)==0: + today = date.today() + if today > ps.enddate: + status = 'missed' + ratio = 0 + return ratio,status + else: + return 0,'not done' + score = 0 for w in ws: if ps.sessionmode == 'distance': @@ -73,13 +99,56 @@ def is_session_complete(ps): rscore = dataprep.workout_rscore(w) score += rscore - ratio = score/float(ps.value) + value = ps.sessionvalue + if ps.sessionunit == 'min': + value *= 60. + elif ps.sessionunit == 'km': + value *= 1000. - if ratio>0.8 and ratio<1.2: - return True + print score,value,ps.sessionvalue,ps.sessionunit + + ratio = score/float(value) - return False + status = 'partial' + if ps.sessiontype == 'training': + if ps.sessioncriterium == 'exact': + if ratio == 1.0: + return ratio,'completed' + else: + return ratio,'partial' + elif ps.sessioncriterium == 'minimum': + if ratio > 1.0: + return ratio,'completed' + else: + return ratio,'partial' + else: + if ratio>0.8 and ratio<1.2: + return ratio,'completed' + else: + return ratio,'partial' + elif ps.sessiontype == 'test': + if ratio==1.0: + return ratio,'completed' + else: + return ratio,'partial' + elif ps.sessiontype == 'challenge': + if ps.sessioncriterium == 'exact': + if ratio == 1.0: + return ratio,'completed' + else: + return ratio,'partial' + elif ps.sessioncriterium == 'minimum': + if ratio > 1.0: + return ratio,'completed' + else: + return ratio,'partial' + else: + return ratio,'partial' + + else: + return ratio,status + def rank_results(ps): return 1 @@ -105,17 +174,58 @@ def remove_rower_session(r,ps): return 1 +def get_dates_timeperiod(timeperiod): + # set start end date according timeperiod + if timeperiod=='today': + startdate=date.today() + enddate=date.today() + elif timeperiod=='tomorrow': + startdate=date.today()+timezone.timedelta(days=1) + enddate=date.today()+timezone.timedelta(days=1) + elif timeperiod=='thisweek': + today = date.today() + startdate = date.today()-timezone.timedelta(days=today.weekday()) + enddate = startdate+timezone.timedelta(days=6) + elif timeperiod=='thismonth': + today = date.today() + startdate = today.replace(day=1) + enddate = startdate+timezone.timedelta(days=32) + enddate = enddate.replace(day=1) + enddate = enddate-timezone.timedelta(days=1) + elif timeperiod=='lastweek': + today = date.today() + enddate = today-timezone.timedelta(days=today.weekday())-timezone.timedelta(days=1) + startdate = enddate-timezone.timedelta(days=6) + elif timeperiod=='lastmonth': + today = date.today() + startdate = today.replace(day=1) + startdate = startdate-timezone.timedelta(days=3) + startdate = startdate.replace(day=1) + enddate = startdate+timezone.timedelta(days=32) + enddate = enddate.replace(day=1) + enddate = enddate-timezone.timedelta(days=1) + else: + startdate = date.today() + enddate = date.today() + + return startdate,enddate + def get_sessions(r,startdate=date.today(), enddate=date.today()+timezone.timedelta(+1000)): sps = PlannedSession.objects.filter( rower__in=[r], - startdate__gte=startdate, - enddate__lte=enddate, + startdate__lte=enddate, + enddate__gte=startdate, ).order_by("startdate","enddate") return sps +def get_workouts_session(r,ps): + ws = Workout.objects.filter(user=r,plannedsession=ps) + + return ws + def update_plannedsession(ps,cd): for attr, value in cd.items(): setattr(ps, attr, value) diff --git a/rowers/templates/.#list_workouts.html b/rowers/templates/.#list_workouts.html new file mode 100644 index 00000000..032838e6 --- /dev/null +++ b/rowers/templates/.#list_workouts.html @@ -0,0 +1 @@ +e408191@CZ27LT9RCGN72.19768:1517816528 \ No newline at end of file diff --git a/rowers/templates/plannedsessioncreate.html b/rowers/templates/plannedsessioncreate.html index b3c53315..5e9364ae 100644 --- a/rowers/templates/plannedsessioncreate.html +++ b/rowers/templates/plannedsessioncreate.html @@ -30,7 +30,7 @@

Click on session name to view

- +
diff --git a/rowers/templates/plannedsessionedit.html b/rowers/templates/plannedsessionedit.html index 77884076..af81d9b5 100644 --- a/rowers/templates/plannedsessionedit.html +++ b/rowers/templates/plannedsessionedit.html @@ -21,7 +21,10 @@ {{ form.as_table }}
After
{% csrf_token %} -
+
+ Delete +
+
@@ -30,7 +33,7 @@

Click on session name to view

- +
diff --git a/rowers/templates/plannedsessions.html b/rowers/templates/plannedsessions.html index efa4d83b..0288e60d 100644 --- a/rowers/templates/plannedsessions.html +++ b/rowers/templates/plannedsessions.html @@ -1,5 +1,6 @@ {% extends "base.html" %} {% load staticfiles %} +{% load rowerfilters %} {% block title %}Planned Sessions{% endblock %} @@ -36,8 +37,10 @@
+ {% if plannedsessions %}

- Click on session name to view + Click on session name to view, edit to change the session and on the + traffic light symbol to add workouts to the session

After
@@ -45,10 +48,11 @@ + - + @@ -66,13 +70,37 @@ href="/rowers/sessions/{{ ps.id }}">Unnamed Session {% endif %} + + {% endfor %}
After Before NameEdit Value   TypeDoneStatus
+ {% if ps.manager == request.user %} + Edit + {% else %} +   + {% endif %} + {{ ps.sessionvalue }} {{ ps.sessionunit }} {{ ps.sessiontype }} + {% if completeness|lookup:ps.id == 'not done' %} +   + {% elif completeness|lookup:ps.id == 'completed' %} +   + {% elif completeness|lookup:ps.id == 'partial' %} +   + {% else %} +   + {% endif %} +
+ {% else %} + You have no planned workouts for this period. Planned workouts are created + by your coach if you are part of a team. You can create your own + planned workouts by purchasing the "Coach" or "Self-Coach" plans. + {% endif %} diff --git a/rowers/templates/plannedsessionsmanage.html b/rowers/templates/plannedsessionsmanage.html new file mode 100644 index 00000000..02875894 --- /dev/null +++ b/rowers/templates/plannedsessionsmanage.html @@ -0,0 +1,74 @@ +{% extends "base.html" %} +{% load staticfiles %} +{% load rowerfilters %} + +{% block title %}Planned Sessions{% endblock %} + +{% block content %} +
+ {% include "planningbuttons.html" %} +
+
+

Manage Plan Execution for {{ rower.user.first_name }} {{ rower.user.last_name }}

+
+ + +
+

Select one session on the left, and one or more workouts on the right + to match the workouts to the session. For tests and training sessions, + the selected workouts must be done on the same date. For all sessions, + the workout dates must be between the start and end date for the + session. +

+

+ If you select a workout that has already been matched to another session, + it will change to match this session. +

+

+ We will make this form smarter in the near future. +

+
+
+
+
+ {{ ps_form.as_table}} +
+
+ {{ w_form.as_table}} +
+
+
+ {% csrf_token %} + +
+
+ + + + +{% endblock %} diff --git a/rowers/templates/plannedsessionview.html b/rowers/templates/plannedsessionview.html index fb70d416..be61c544 100644 --- a/rowers/templates/plannedsessionview.html +++ b/rowers/templates/plannedsessionview.html @@ -1,5 +1,6 @@ {% extends "base.html" %} {% load staticfiles %} +{% load rowerfilters %} {% block title %}Planned Session{% endblock %} @@ -10,7 +11,7 @@

Session {{ psdict.name.1 }}

- +
{% for attr in attrs %} {% for key,value in psdict.items %} {% if key == attr %} @@ -21,9 +22,36 @@ {% endfor %} {% endfor %}
+

Result

+

Status: {{ status }}

+

Percentage complete: {{ ratio }}

diff --git a/rowers/templates/plannedsssionsmanage.html b/rowers/templates/plannedsssionsmanage.html new file mode 100644 index 00000000..8be3f91b --- /dev/null +++ b/rowers/templates/plannedsssionsmanage.html @@ -0,0 +1,48 @@ +{% extends "base.html" %} +{% load staticfiles %} +{% load rowerfilters %} + +{% block title %}Planned Sessions{% endblock %} + +{% block content %} +
+ {% include "planningbuttons.html" %} +
+
+

Plan for {{ rower.user.first_name }} {{ rower.user.last_name }}

+
+ +
+

+ Click on session name to view +

+
+ + + + +{% endblock %} diff --git a/rowers/urls.py b/rowers/urls.py index 06676d0f..5f9238b1 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -401,6 +401,14 @@ urlpatterns = [ url(r'^sessions/(?P\d+)$',views.plannedsession_view), url(r'^sessions/(?P\d+)/deleteconfirm$',views.plannedsession_deleteconfirm_view), url(r'^sessions/(?P\d+)/delete$',views.plannedsession_delete_view), + url(r'^sessions/manage/?$', + views.plannedsessions_manage_view), + url(r'^sessions/manage/rower/(?P\d+)$', + views.plannedsessions_manage_view), + url(r'^sessions/manage/(?P[\w\ ]+.*)/rower/(?P\d+)$', + views.plannedsessions_manage_view), + url(r'^sessions/manage/(?P[\w\ ]+.*)$', + views.plannedsessions_manage_view), url(r'^sessions/?$',views.plannedsessions_view), url(r'^sessions/rower/(?P\d+)$',views.plannedsessions_view), url(r'^sessions/(?P[\w\ ]+.*)/rower/(?P\d+)$',views.plannedsessions_view), diff --git a/rowers/views.py b/rowers/views.py index 625c790f..e4921eb3 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -30,7 +30,7 @@ from rowers.forms import ( LoginForm,DocumentsForm,UploadOptionsForm,ImageForm, TeamUploadOptionsForm,WorkFlowLeftPanelForm,WorkFlowMiddlePanelForm, WorkFlowLeftPanelElement,WorkFlowMiddlePanelElement, - LandingPageForm, + LandingPageForm,PlannedSessionSelectForm,WorkoutSessionSelectForm ) from django.core.urlresolvers import reverse from django.core.exceptions import PermissionDenied @@ -606,6 +606,7 @@ def rowhascoordinates(row): if rowdata != 0: try: latitude = rowdata.df[' latitude'] + if not latitude.std(): hascoordinates = 0 except KeyError,AttributeError: @@ -11765,48 +11766,110 @@ def plannedsessions_view(request,timeperiod='today',rowerid=0): if not checkaccessuser(request.user,r): raise Http404("You don't have access to this plan") - # set start end date according timeperiod - if timeperiod=='today': - startdate=datetime.date.today() - enddate=datetime.date.today() - elif timeperiod=='tomorrow': - startdate=datetime.date.today()+timezone.timedelta(days=1) - enddate=datetime.date.today()+timezone.timedelta(days=1) - elif timeperiod=='thisweek': - today = datetime.date.today() - startdate = datetime.date.today()-timezone.timedelta(days=today.weekday()) - enddate = startdate+timezone.timedelta(weeks=1) - elif timeperiod=='thismonth': - today = datetime.date.today() - startdate = today.replace(day=1) - enddate = startdate+timezone.timedelta(days=32) - enddate = enddate.replace(day=1) - elif timeperiod=='lastweek': - today = datetime.date.today() - enddate = today-timezone.timedelta(days=today.weekday()) - startdate = enddate-timezone.timedelta(days=7) - elif timeperiod=='lastmonth': - today = datetime.date.today() - startdate = today.replace(day=1) - startdate = startdate-timezone.timedelta(days=3) - startdate = startdate.replace(day=1) - enddate = startdate+timezone.timedelta(days=32) - enddate = enddate.replace(day=1) - else: - startdate = datetime.date.today() - enddate = datetime.date.today() - - + startdate,enddate = get_dates_timeperiod(timeperiod) sps = get_sessions(r,startdate=startdate,enddate=enddate) + + completeness = {} + + for ps in sps: + ratio,status = is_session_complete(r,ps) + completeness[ps.id] = status return render(request,'plannedsessions.html', { 'teams':get_my_teams(request.user), 'plannedsessions':sps, 'rower':r, + 'timeperiod':timeperiod, + 'completeness':completeness, }) +@login_required() +def plannedsessions_manage_view(request,timeperiod='today',rowerid=0): + + if rowerid==0: + r = getrower(request.user) + else: + try: + r = Rower.objects.get(id=rowerid) + except Rower.DoesNotExist: + raise Http404("This rower doesn't exist") + if not checkaccessuser(request.user,r): + raise Http404("You don't have access to this plan") + + startdate,enddate = get_dates_timeperiod(timeperiod) + + + + sps = get_sessions(r,startdate=startdate,enddate=enddate) + + ws = Workout.objects.filter( + user=r,date__gte=startdate, + date__lte=enddate + ).order_by( + "date","id" + ) + + + plannedsessionstuple = [] + + for ps in sps: + sessiontpl = (ps.id,ps.__unicode__()) + plannedsessionstuple.append(sessiontpl) + + plannedsessionstuple = tuple(plannedsessionstuple) + + workoutdata = {} + workoutdata['initial'] = [] + + choices = [] + + for w in ws: + wtpl = (w.id, w.__unicode__()) + choices.append(wtpl) + + workoutdata['choices'] = tuple(choices) + + if request.method == 'POST': + ps_form = PlannedSessionSelectForm(plannedsessionstuple,request.POST) + w_form = WorkoutSessionSelectForm(workoutdata,request.POST) + + if ps_form.is_valid(): + ps = PlannedSession.objects.get(id=ps_form.cleaned_data['plannedsession']) + if w_form.is_valid(): + selectedworkouts = w_form.cleaned_data['workouts'] + else: + selectedworkouts = [] + + + if selectedworkouts: + workouts = Workout.objects.filter(user=r,id__in=selectedworkouts) + for w in ws: + if w.id not in selectedworkouts: + remove_workout_plannedsession(w,ps) + + result,comments,errors = add_workouts_plannedsession(workouts,ps) + for c in comments: + messages.info(request,c) + for er in errors: + messages.error(request,er) + + + ps_form = PlannedSessionSelectForm(plannedsessionstuple) + w_form = WorkoutSessionSelectForm(workoutdata=workoutdata) + + return render(request,'plannedsessionsmanage.html', + { + 'teams':get_my_teams(request.user), + 'plannedsessions':sps, + 'workouts':ws, + 'timeperiod':timeperiod, + 'rower':r, + 'ps_form':ps_form, + 'w_form':w_form, + }) + # Edit an existing planned session @user_passes_test(hasplannedsessions,login_url="/rowers/promembership/", @@ -11864,9 +11927,14 @@ def plannedsession_edit_view(request,id=0): @login_required() -def plannedsession_view(request,id=0): +def plannedsession_view(request,id=0,rowerid=0): - r = getrower(request.user) + m = getrower(request.user) + + if not rowerid: + r = m + else: + r = Rower.objects.get(id=rowerid) try: ps = PlannedSession.objects.get(id=id) @@ -11879,13 +11947,27 @@ def plannedsession_view(request,id=0): psdict = my_dict_from_instance(ps,PlannedSession) + + ws = get_workouts_session(r,ps) + + ratio,status = is_session_complete(r,ps) + + print ratio + + ratio = int(100.*ratio) return render(request,'plannedsessionview.html', { 'psdict': psdict, 'attrs':[ 'name','startdate','enddate','sessiontype', - ] + 'sessionvalue','sessionunit' + ], + 'workouts': ws, + 'manager':m, + 'rower':r, + 'ratio':ratio, + 'status':status } ) @@ -11902,6 +11984,11 @@ def plannedsession_delete_view(request,id=0): if ps.manager != request.user: raise Http404("You are not allowed to delete this planned session") + ws = Workout.objects.filter(plannedsession=ps) + for w in ws: + w.plannedsession=None + w.save() + ps.delete() url = reverse(plannedsessions_view) diff --git a/static/css/rowsandall.css b/static/css/rowsandall.css index 8b649642..36fe4242 100644 --- a/static/css/rowsandall.css +++ b/static/css/rowsandall.css @@ -291,6 +291,19 @@ th.rotate > div > span { border: solid 1px #333; } +.dot { + border-radius: 50%; + display: block; + text-align: center; + width: 25px; + height: 25px; + border: solid 1px #333; +} + +.dot:hover { + text-decoration: none; +} + .button { font: 1.1em/1.5em sans-serif; text-decoration: none;