Merge branch 'release/statspage'
This commit is contained in:
@@ -53,7 +53,7 @@ if settings.DEBUG or user=='':
|
|||||||
|
|
||||||
# model for Power Zone names
|
# model for Power Zone names
|
||||||
class PowerZonesField(models.TextField):
|
class PowerZonesField(models.TextField):
|
||||||
__metaclass__ = models.SubfieldBase
|
# __metaclass__ = models.SubfieldBase
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.token = kwargs.pop('token',',')
|
self.token = kwargs.pop('token',',')
|
||||||
@@ -65,6 +65,13 @@ class PowerZonesField(models.TextField):
|
|||||||
return value
|
return value
|
||||||
return value.split(self.token)
|
return value.split(self.token)
|
||||||
|
|
||||||
|
def from_db_value(self,value, expression, connection, context):
|
||||||
|
if value is None:
|
||||||
|
return value
|
||||||
|
if isinstance(value, list):
|
||||||
|
return value
|
||||||
|
return value.split(self.token)
|
||||||
|
|
||||||
def get_db_prep_value(self, value, connection, prepared=False):
|
def get_db_prep_value(self, value, connection, prepared=False):
|
||||||
if not value: return
|
if not value: return
|
||||||
assert(isinstance(value, list) or isinstance(value, tuple))
|
assert(isinstance(value, list) or isinstance(value, tuple))
|
||||||
|
|||||||
@@ -8,34 +8,40 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="workouts" class="grid_6 alpha">
|
<div id="workouts" class="grid_6 alpha">
|
||||||
|
|
||||||
{% if form.errors %}
|
{% if form.errors %}
|
||||||
<p style="color: red;">
|
<p style="color: red;">
|
||||||
Please correct the error{{ form.errors|pluralize }} below.
|
Please correct the error{{ form.errors|pluralize }} below.
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<h1>Edit Workout Data</h1>
|
|
||||||
<div class="grid_6 alpha">
|
|
||||||
<div class="grid_2 alpha">
|
|
||||||
<p>
|
|
||||||
<a class="button red small" href="/rowers/workout/{{ workout.id }}/deleteconfirm">Delete</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="grid_2">
|
|
||||||
<p>
|
|
||||||
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/export">Export</a>
|
|
||||||
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="grid_2 omega tooltip">
|
|
||||||
<p>
|
|
||||||
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/advanced">Advanced</a>
|
|
||||||
</p>
|
|
||||||
<span class="tooltiptext">Advanced Functionality (More interactive Charts)</span>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<h1>Edit Workout Data</h1>
|
||||||
|
<div class="grid_6 alpha">
|
||||||
|
<div class="grid_2 alpha">
|
||||||
|
<p>
|
||||||
|
<a class="button red small" href="/rowers/workout/{{ workout.id }}/deleteconfirm">Delete</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="grid_2">
|
||||||
|
<p>
|
||||||
|
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/export">Export</a>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="grid_2 omega tooltip">
|
||||||
|
<p>
|
||||||
|
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/advanced">Advanced</a>
|
||||||
|
</p>
|
||||||
|
<span class="tooltiptext">Advanced Functionality (More interactive Charts)</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid_6 alpha">
|
||||||
|
<div class="grid_2 prefix_4 alpha">
|
||||||
|
<p>
|
||||||
|
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/stats">Statistics</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% localtime on %}
|
{% localtime on %}
|
||||||
<table width=100%>
|
<table width=100%>
|
||||||
|
|||||||
127
rowers/templates/workoutstats.html
Normal file
127
rowers/templates/workoutstats.html
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% load rowerfilters %}
|
||||||
|
|
||||||
|
{% block title %}Workout Statistics{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="grid_12 alpha">
|
||||||
|
<h1>Workout Statistics</h1>
|
||||||
|
<div class="grid_2 alpha">
|
||||||
|
<p>
|
||||||
|
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/edit">Edit Workout</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="grid_2">
|
||||||
|
<p>
|
||||||
|
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/export">Export</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="grid_2">
|
||||||
|
<p>
|
||||||
|
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/advanced">Advanced</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="grid_2 omega suffix_4 tooltip">
|
||||||
|
<form enctype="multipart/form-data" action="{{ formloc }}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% if workstrokesonly == True %}
|
||||||
|
<input class="grid_2 alpha button blue small" type="hidden" name="workstrokesonly" value="False">
|
||||||
|
<input class="grid_2 alpha button blue small" value="Include Rest Strokes" type="Submit">
|
||||||
|
{% else %}
|
||||||
|
<input class="grid_2 alpha button blue small" type="hidden" name="workstrokesonly" value="True">
|
||||||
|
<input class="grid_2 alpha button blue small" value="Remove Rest Strokes" type="Submit">
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
<span class="tooltiptext">If your data source allows, this will show or hide strokes taken during rest intervals.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid_6 alpha">
|
||||||
|
<h2>Stroke Rate</h2>
|
||||||
|
<table width="100%" class="listtable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Metric</th>
|
||||||
|
<th>Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Mean</td><td>{{ stats.spm.mean|floatformat:-2 }}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<td>Minimum</td><td>{{ stats.spm.min|floatformat:-2 }}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<td>25%</td><td>{{ stats.spm.firstq|floatformat:-2 }}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<td>Median</td><td>{{ stats.spm.median|floatformat:-2 }}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<td>75%</td><td>{{ stats.spm.thirdq|floatformat:-2 }}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<td>Maximum</td><td>{{ stats.spm.max|floatformat:-2 }}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<td>Standard Deviation</td><td>{{ stats.spm.std|floatformat:-2 }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="grid_6 alpha">
|
||||||
|
<h2>Heart Rate</h2>
|
||||||
|
<table width="100%" class="listtable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Metric</th>
|
||||||
|
<th>Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Mean</td><td>{{ stats.hr.mean|floatformat:-2 }}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<td>Minimum</td><td>{{ stats.hr.min|floatformat:-2 }}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<td>25%</td><td>{{ stats.hr.firstq|floatformat:-2 }}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<td>Median</td><td>{{ stats.hr.median|floatformat:-2 }}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<td>75%</td><td>{{ stats.hr.thirdq|floatformat:-2 }}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<td>Maximum</td><td>{{ stats.hr.max|floatformat:-2 }}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<td>Standard Deviation</td><td>{{ stats.hr.std|floatformat:-2 }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="grid_6 omega">
|
||||||
|
<div class="grid_6 alpha">
|
||||||
|
<h2>Power</h2>
|
||||||
|
<table width="100%" class="listtable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Metric</th>
|
||||||
|
<th>Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Mean</td><td>{{ stats.power.mean|floatformat:-2 }}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<td>Minimum</td><td>{{ stats.power.min|floatformat:-2 }}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<td>25%</td><td>{{ stats.power.firstq|floatformat:-2 }}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<td>Median</td><td>{{ stats.power.median|floatformat:-2 }}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<td>75%</td><td>{{ stats.power.thirdq|floatformat:-2 }}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<td>Maximum</td><td>{{ stats.power.max|floatformat:-2 }}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<td>Standard Deviation</td><td>{{ stats.power.std|floatformat:-2 }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -154,6 +154,7 @@ urlpatterns = [
|
|||||||
url(r'^workout/(?P<id>\d+)/advanced/s/(?P<successmessage>.+.*)$',views.workout_advanced_view),
|
url(r'^workout/(?P<id>\d+)/advanced/s/(?P<successmessage>.+.*)$',views.workout_advanced_view),
|
||||||
url(r'^workout/(?P<id>\d+)/geeky$',views.workout_geeky_view),
|
url(r'^workout/(?P<id>\d+)/geeky$',views.workout_geeky_view),
|
||||||
url(r'^workout/(\d+)/advanced$',views.workout_advanced_view),
|
url(r'^workout/(\d+)/advanced$',views.workout_advanced_view),
|
||||||
|
url(r'^workout/(\d+)/stats$',views.workout_stats_view),
|
||||||
url(r'^workout/(\d+)/otwsetpower$',views.workout_otwsetpower_view),
|
url(r'^workout/(\d+)/otwsetpower$',views.workout_otwsetpower_view),
|
||||||
url(r'^workout/(\d+)/interactiveotwplot$',views.workout_otwpowerplot_view),
|
url(r'^workout/(\d+)/interactiveotwplot$',views.workout_otwpowerplot_view),
|
||||||
url(r'^workout/(\d+)/wind$',views.workout_wind_view),
|
url(r'^workout/(\d+)/wind$',views.workout_wind_view),
|
||||||
|
|||||||
@@ -2604,6 +2604,84 @@ def workout_geeky_view(request,id=0,message="",successmessage=""):
|
|||||||
'successmessage': successmessage,
|
'successmessage': successmessage,
|
||||||
'interactiveplot':script,
|
'interactiveplot':script,
|
||||||
'the_div':div})
|
'the_div':div})
|
||||||
|
|
||||||
|
# Stats page
|
||||||
|
@login_required()
|
||||||
|
def workout_stats_view(request,id=0,message="",successmessage=""):
|
||||||
|
workstrokesonly = True
|
||||||
|
if request.method == 'POST' and 'workstrokesonly' in request.POST:
|
||||||
|
workstrokesonly = request.POST['workstrokesonly']
|
||||||
|
|
||||||
|
row = Workout.objects.get(id=id)
|
||||||
|
if (checkworkoutuser(request.user,row)==False):
|
||||||
|
message = "You are not allowed to see the stats of this workout"
|
||||||
|
url = reverse(workouts_view,args=[str(message)])
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
columns = ['hr','spm','power','workoutstate']
|
||||||
|
datadf = dataprep.getsmallrowdata_db(columns,ids=[id])
|
||||||
|
|
||||||
|
if datadf.empty:
|
||||||
|
return HttpResponse("CSV data file not found")
|
||||||
|
|
||||||
|
workoutstateswork = [1,4,5,8,9,6,7]
|
||||||
|
workoutstatesrest = [3]
|
||||||
|
workoutstatetransition = [0,2,10,11,12,13]
|
||||||
|
|
||||||
|
if workstrokesonly=='True' or workstrokesonly==True:
|
||||||
|
try:
|
||||||
|
datadf = datadf[~datadf['workoutstate'].isin(workoutstatesrest)]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
workstrokesonly = True
|
||||||
|
|
||||||
|
stats = {}
|
||||||
|
# SPM
|
||||||
|
spmdict = {
|
||||||
|
'mean':datadf['spm'].mean(),
|
||||||
|
'max': datadf['spm'].max(),
|
||||||
|
'min': datadf['spm'].min(),
|
||||||
|
'std': datadf['spm'].std(),
|
||||||
|
'median': datadf['spm'].median(),
|
||||||
|
'firstq':datadf['spm'].quantile(q=0.25),
|
||||||
|
'thirdq':datadf['spm'].quantile(q=0.75),
|
||||||
|
}
|
||||||
|
|
||||||
|
stats['spm'] = spmdict
|
||||||
|
|
||||||
|
# HR
|
||||||
|
hrdict = {
|
||||||
|
'mean':datadf['hr'].mean(),
|
||||||
|
'max': datadf['hr'].max(),
|
||||||
|
'min': datadf['hr'].min(),
|
||||||
|
'std': datadf['hr'].std(),
|
||||||
|
'median': datadf['hr'].median(),
|
||||||
|
'firstq':datadf['hr'].quantile(q=0.25),
|
||||||
|
'thirdq':datadf['hr'].quantile(q=0.75),
|
||||||
|
}
|
||||||
|
|
||||||
|
stats['hr'] = hrdict
|
||||||
|
|
||||||
|
# Power
|
||||||
|
powerdict = {
|
||||||
|
'mean':datadf['power'].mean(),
|
||||||
|
'max': datadf['power'].max(),
|
||||||
|
'min': datadf['power'].min(),
|
||||||
|
'std': datadf['power'].std(),
|
||||||
|
'median': datadf['power'].median(),
|
||||||
|
'firstq':datadf['power'].quantile(q=0.25),
|
||||||
|
'thirdq':datadf['power'].quantile(q=0.75),
|
||||||
|
}
|
||||||
|
|
||||||
|
stats['power'] = powerdict
|
||||||
|
|
||||||
|
return render(request,
|
||||||
|
'workoutstats.html',
|
||||||
|
{
|
||||||
|
'stats':stats,
|
||||||
|
'workout':row,
|
||||||
|
'workstrokesonly':workstrokesonly,
|
||||||
|
})
|
||||||
|
|
||||||
# The Advanced edit page
|
# The Advanced edit page
|
||||||
@login_required()
|
@login_required()
|
||||||
@@ -4596,11 +4674,15 @@ def rower_edit_view(request,message=""):
|
|||||||
successmessage = "Your Heart Rate data were changed"
|
successmessage = "Your Heart Rate data were changed"
|
||||||
form = RowerForm(instance=r)
|
form = RowerForm(instance=r)
|
||||||
powerform = RowerPowerForm(instance=r)
|
powerform = RowerPowerForm(instance=r)
|
||||||
|
powerzonesform = RowerPowerZonesForm(instance=r)
|
||||||
|
accountform = AccountRowerForm(instance=r)
|
||||||
userform = UserForm(instance=request.user)
|
userform = UserForm(instance=request.user)
|
||||||
return render(request, 'rower_form.html',
|
return render(request, 'rower_form.html',
|
||||||
{'form':form,
|
{'form':form,
|
||||||
'powerzonesform':powerzonesform,
|
'powerzonesform':powerzonesform,
|
||||||
'powerform':powerform,
|
'powerform':powerform,
|
||||||
|
'rower':r,
|
||||||
|
'accountform':accountform,
|
||||||
'userform':userform,
|
'userform':userform,
|
||||||
'successmessage':successmessage,
|
'successmessage':successmessage,
|
||||||
})
|
})
|
||||||
@@ -4612,9 +4694,13 @@ def rower_edit_view(request,message=""):
|
|||||||
message = HttpResponse("invalid form")
|
message = HttpResponse("invalid form")
|
||||||
#form = RowerForm(instance=r)
|
#form = RowerForm(instance=r)
|
||||||
powerform = RowerPowerForm(instance=r)
|
powerform = RowerPowerForm(instance=r)
|
||||||
|
powerzonesform = RowerPowerZonesForm(instance=r)
|
||||||
|
userform = UserForm(instance=request.user)
|
||||||
accountform = AccountRowerForm(instance=r)
|
accountform = AccountRowerForm(instance=r)
|
||||||
return render(request, 'rower_form.html',
|
return render(request, 'rower_form.html',
|
||||||
{'form':form,
|
{'form':form,
|
||||||
|
'powerzonesform':powerzonesform,
|
||||||
|
'userform':userform,
|
||||||
'accountform':accountform,
|
'accountform':accountform,
|
||||||
'powerform':powerform,
|
'powerform':powerform,
|
||||||
'rower':r,
|
'rower':r,
|
||||||
@@ -4654,10 +4740,14 @@ def rower_edit_view(request,message=""):
|
|||||||
message = HttpResponse("invalid form")
|
message = HttpResponse("invalid form")
|
||||||
form = RowerForm(instance=r)
|
form = RowerForm(instance=r)
|
||||||
#powerform = RowerPowerForm(instance=r)
|
#powerform = RowerPowerForm(instance=r)
|
||||||
|
powerzonesform = RowerPowerZonesForm(instance=r)
|
||||||
|
userform = UserForm(instance=request.user)
|
||||||
accountform = AccountRowerForm(instance=r)
|
accountform = AccountRowerForm(instance=r)
|
||||||
return render(request, 'rower_form.html',
|
return render(request, 'rower_form.html',
|
||||||
{'form':form,
|
{'form':form,
|
||||||
'powerform':powerform,
|
'powerform':powerform,
|
||||||
|
'rower':r,
|
||||||
|
'userform':userform,
|
||||||
'accountform':accountform,
|
'accountform':accountform,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -4689,12 +4779,16 @@ def rower_edit_view(request,message=""):
|
|||||||
r.powerzones = powerzones
|
r.powerzones = powerzones
|
||||||
r.save()
|
r.save()
|
||||||
successmessage = "Your Power Zone data were changed"
|
successmessage = "Your Power Zone data were changed"
|
||||||
|
form = RowerForm(instance=r)
|
||||||
|
accountform = AccountRowerForm(instance=r)
|
||||||
userform = UserForm(instance=request.user)
|
userform = UserForm(instance=request.user)
|
||||||
powerform = RowerPowerForm(instance=r)
|
powerform = RowerPowerForm(instance=r)
|
||||||
powerzonesform = RowerPowerZonesForm(instance=r)
|
powerzonesform = RowerPowerZonesForm(instance=r)
|
||||||
return render(request, 'rower_form.html',
|
return render(request, 'rower_form.html',
|
||||||
{'form':form,
|
{'form':form,
|
||||||
'powerzonesform':powerzonesform,
|
'powerzonesform':powerzonesform,
|
||||||
|
'powerform':powerform,
|
||||||
|
'userform':userform,
|
||||||
'accountform':accountform,
|
'accountform':accountform,
|
||||||
'rower':r,
|
'rower':r,
|
||||||
'successmessage':successmessage,
|
'successmessage':successmessage,
|
||||||
|
|||||||
Reference in New Issue
Block a user