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
-
+
| After |
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 }}
{% csrf_token %}
-
@@ -30,7 +33,7 @@
Click on session name to view
-
+
| After |
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
@@ -45,10 +48,11 @@
| After |
Before |
Name |
+ Edit |
Value |
|
Type |
- Done |
+ Status |
|
@@ -66,13 +70,37 @@
href="/rowers/sessions/{{ ps.id }}">Unnamed Session
{% endif %}
+
+ {% 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 %}
+ |
{% endfor %}
+ {% 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.
+
+
+
+
+
+
+
+{% 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 }}
-
+
Workouts attached
+
+
+
+ | Date |
+ Name |
+ Distance |
+ Duration |
+
+
+
+ {% for workout in workouts %}
+
+ | {{ workout.date|date:"Y-m-d" }} |
+
+
+ {{ workout.name }}
+
+ |
+ {{ workout.distance }}m |
+ {{ workout.duration |durationprint:"%H:%M:%S.%f" }} |
+
+ {% endfor %}
+
+
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;