diff --git a/rowers/c2stuff.py b/rowers/c2stuff.py
index 2a2021e5..91752ca1 100644
--- a/rowers/c2stuff.py
+++ b/rowers/c2stuff.py
@@ -575,7 +575,7 @@ def workout_c2_upload(user,w):
c2id = s['data']['id']
w.uploadedtoc2 = c2id
w.save()
- message = ""
+ message = "Upload to Concept2 was successful"
except:
message = "Something went wrong in workout_c2_upload_view. Response code 200/201 but C2 sync failed: "+response.text
c2id = 0
diff --git a/rowers/dataprep.py b/rowers/dataprep.py
index a9ad37f1..cb226c5f 100644
--- a/rowers/dataprep.py
+++ b/rowers/dataprep.py
@@ -970,8 +970,13 @@ def read_cols_df_sql(ids,columns):
connection = engine.raw_connection()
df = pd.read_sql_query(query,engine)
df = df.fillna(value=0)
+
try:
df['peakforce'] = df['peakforce']*lbstoN
+ except KeyError:
+ pass
+
+ try:
df['averageforce'] = df['averageforce']*lbstoN
except KeyError:
pass
@@ -990,6 +995,10 @@ def read_df_sql(id):
df = df.fillna(value=0)
try:
df['peakforce'] = df['peakforce']*lbstoN
+ except KeyError:
+ pass
+
+ try:
df['averageforce'] = df['averageforce']*lbstoN
except KeyError:
pass
@@ -1037,6 +1046,10 @@ def smalldataprep(therows,xparam,yparam1,yparam2):
try:
df['peakforce'] = df['peakforce']*lbstoN
+ except KeyError:
+ pass
+
+ try:
df['averageforce'] = df['averageforce']*lbstoN
except KeyError:
pass
diff --git a/rowers/forms.py b/rowers/forms.py
index d5a1715d..69b6870f 100644
--- a/rowers/forms.py
+++ b/rowers/forms.py
@@ -297,6 +297,9 @@ formaxlabels = axlabels.copy()
formaxlabels.pop('None')
parchoices = list(sorted(formaxlabels.items(), key = lambda x:x[1]))
+class BoxPlotChoiceForm(forms.Form):
+ yparam = forms.ChoiceField(choices=parchoices,initial='spm',
+ label='Metric')
class ChartParamChoiceForm(forms.Form):
plotchoices = (
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index b13841e0..aa9f45c1 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -108,6 +108,14 @@ def interactive_boxchart(datadf,fieldname):
months=["%d %B %Y"],
years=["%d %B %Y"],
)
+
+ if fieldname == 'pace':
+ plot.yaxis[0].formatter = DatetimeTickFormatter(
+ seconds = ["%S"],
+ minutes = ["%M"]
+ )
+
+
plot.xaxis.major_label_orientation = pi/4
script, div = components(plot)
diff --git a/rowers/models.py b/rowers/models.py
index b618a24c..fa0c839d 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -433,12 +433,14 @@ class Workout(models.Model):
ownerfirst = self.user.user.first_name
ownerlast = self.user.user.last_name
duration = self.duration
+ workouttype = self.workouttype
- stri = u'{d} {n} {dist}m {duration:%H:%M:%S} {ownerfirst} {ownerlast}'.format(
+ stri = u'{d} {n} {dist}m {duration:%H:%M:%S} {workouttype} {ownerfirst} {ownerlast}'.format(
d = date.strftime('%Y-%m-%d'),
n = name,
dist = distance,
duration = duration,
+ workouttype = workouttype,
ownerfirst = ownerfirst,
ownerlast = ownerlast,
)
diff --git a/rowers/sporttracksstuff.py b/rowers/sporttracksstuff.py
index 2319cf77..b16df358 100644
--- a/rowers/sporttracksstuff.py
+++ b/rowers/sporttracksstuff.py
@@ -278,6 +278,11 @@ def createsporttracksworkoutdata(w):
powerdata.append(power[i])
+ try:
+ w.notes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com',
+ except TypeError:
+ w.notes = w.notes+' via rowsandall.com'
+
if haslatlon:
data = {
"type": "Rowing",
@@ -286,7 +291,7 @@ def createsporttracksworkoutdata(w):
"start_time": w.startdatetime.isoformat(),
"total_distance": int(w.distance),
"duration": duration,
- "notes": w.notes+'\n from '+w.workoutsource+' via rowsandall.com',
+ "notes": w.notes,
"avg_heartrate": averagehr,
"max_heartrate": maxhr,
"location": locdata,
@@ -302,7 +307,7 @@ def createsporttracksworkoutdata(w):
"start_time": w.startdatetime.isoformat(),
"total_distance": int(w.distance),
"duration": duration,
- "notes": w.notes+'\n from '+w.workoutsource+' via rowsandall.com',
+ "notes": w.notes,
"avg_heartrate": averagehr,
"max_heartrate": maxhr,
"distance": distancedata,
diff --git a/rowers/templates/analysis.html b/rowers/templates/analysis.html
index 4cdbf511..2dc2cf43 100644
--- a/rowers/templates/analysis.html
+++ b/rowers/templates/analysis.html
@@ -64,11 +64,15 @@
-
- Pro Feature 3
+
+ {% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
+ Box Chart
+ {% else %}
+ Box Chart
+ {% endif %}
- Reserved for future functionality.
+ BETA: Box Chart Statistics of stroke metrics over a date range
diff --git a/rowers/templates/boxplot.html b/rowers/templates/boxplot.html
new file mode 100644
index 00000000..ce250dd2
--- /dev/null
+++ b/rowers/templates/boxplot.html
@@ -0,0 +1,63 @@
+{% extends "base.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block title %}View Comparison {% endblock %}
+
+{% block content %}
+
+
+
+
+{{ interactiveplot |safe }}
+
+
+
+
+
+
+
Box Chart
+
+
+
+
+
+
+
+ {{ the_div|safe }}
+
+
+
+
+{% endblock %}
diff --git a/rowers/templates/user_boxplot_select.html b/rowers/templates/user_boxplot_select.html
new file mode 100644
index 00000000..e5e5c611
--- /dev/null
+++ b/rowers/templates/user_boxplot_select.html
@@ -0,0 +1,93 @@
+{% extends "base.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block title %}Workouts{% endblock %}
+
+{% block content %}
+
+
+
+
+ {% include "teambuttons.html" with teamid=team.id %}
+
+
+
+ {% if theuser %}
+
{{ theuser.first_name }}'s Workouts
+ {% else %}
+ {{ user.first_name }}'s Workouts
+ {% endif %}
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/rowers/urls.py b/rowers/urls.py
index 952b6175..d7b8f857 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -124,6 +124,10 @@ urlpatterns = [
url(r'^team-compare-select/team/(?P\d+)/$',views.team_comparison_select),
url(r'^team-compare-select/(?P\w+.*)/(?P\w+.*)$',views.team_comparison_select),
url(r'^team-compare-select/$',views.team_comparison_select),
+ url(r'^user-boxplot-select/team/(?P\d+)/(?P\w+.*)/(?P\w+.*)$',views.user_boxplot_select),
+ url(r'^user-boxplot-select/user/(?P\d+)/$',views.user_boxplot_select),
+ url(r'^user-boxplot-select/(?P\w+.*)/(?P\w+.*)$',views.user_boxplot_select),
+ url(r'^user-boxplot-select/$',views.user_boxplot_select),
url(r'^list-graphs/$',views.graphs_view),
url(r'^(?P\d+)/ote-bests/(?P\w+.*)/(?P\w+.*)$',views.rankings_view),
url(r'^(?P\d+)/ote-bests/(?P\d+)$',views.rankings_view),
@@ -220,6 +224,7 @@ urlpatterns = [
url(r'^workout/(?P\d+)/underarmouruploadw/$',views.workout_underarmour_upload_view),
url(r'^workout/(?P\d+)/tpuploadw/$',views.workout_tp_upload_view),
url(r'^multi-compare$',views.multi_compare_view),
+ url(r'^user-boxplot$',views.boxplot_view),
url(r'^me/teams/$',views.rower_teams_view),
url(r'^team/(?P\d+)/$',views.team_view),
url(r'^team/(?P\d+)/edit$',views.team_edit_view),
diff --git a/rowers/views.py b/rowers/views.py
index f98d3a29..506fb65a 100644
--- a/rowers/views.py
+++ b/rowers/views.py
@@ -34,7 +34,7 @@ from rowers.forms import (
EmailForm, RegistrationForm, RegistrationFormTermsOfService,
RegistrationFormUniqueEmail,CNsummaryForm,UpdateWindForm,
UpdateStreamForm,WorkoutMultipleCompareForm,ChartParamChoiceForm,
- FusionMetricChoiceForm,
+ FusionMetricChoiceForm,BoxPlotChoiceForm,
)
from rowers.models import Workout, User, Rower, WorkoutForm,FavoriteChart
from rowers.models import (
@@ -2847,6 +2847,8 @@ def team_comparison_select(request,
'teams':get_my_teams(request.user),
})
+
+# Team comparison
@login_required()
def multi_compare_view(request):
promember=0
@@ -2926,6 +2928,181 @@ def multi_compare_view(request):
url = reverse(workouts_view)
return HttpResponseRedirect(url)
+# Box plots
+@user_passes_test(ispromember,login_url="/",redirect_field_name=None)
+def user_boxplot_select(request,
+ startdatestring="",
+ enddatestring="",
+ message='',
+ successmessage='',
+ startdate=timezone.now()-datetime.timedelta(days=30),
+ enddate=timezone.now()+datetime.timedelta(days=1),
+ userid=0):
+
+ if userid == 0:
+ user = request.user
+ else:
+ user = User.objects.get(id=userid)
+
+ try:
+ r = Rower.objects.get(user=user)
+ except Rower.DoesNotExist:
+ raise Http404("Rower doesn't exist")
+
+ if request.method == 'POST':
+ dateform = DateRangeForm(request.POST)
+ if dateform.is_valid():
+ startdate = dateform.cleaned_data['startdate']
+ enddate = dateform.cleaned_data['enddate']
+ else:
+ dateform = DateRangeForm(initial={
+ 'startdate':startdate,
+ 'enddate':enddate,
+ })
+
+ startdate = datetime.datetime.combine(startdate,datetime.time())
+ enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
+ enddate = enddate+datetime.timedelta(days=1)
+
+ if startdatestring:
+ startdate = iso8601.parse_date(startdatestring)
+ if enddatestring:
+ enddate = iso8601.parse_date(enddatestring)
+
+ if enddate < startdate:
+ s = enddate
+ enddate = startdate
+ startdate = s
+
+
+ workouts = Workout.objects.filter(user=r,
+ startdatetime__gte=startdate,
+ startdatetime__lte=enddate).order_by("-date", "-starttime")
+
+ query = request.GET.get('q')
+ if query:
+ query_list = query.split()
+ workouts = workouts.filter(
+ reduce(operator.and_,
+ (Q(name__icontains=q) for q in query_list)) |
+ reduce(operator.and_,
+ (Q(notes__icontains=q) for q in query_list))
+ )
+
+ form = WorkoutMultipleCompareForm()
+ form.fields["workouts"].queryset = workouts
+
+ chartform = BoxPlotChoiceForm()
+
+ messages.info(request,successmessage)
+ messages.error(request,message)
+
+ return render(request, 'user_boxplot_select.html',
+ {'workouts': workouts,
+ 'dateform':dateform,
+ 'startdate':startdate,
+ 'enddate':enddate,
+ 'theuser':user,
+ 'form':form,
+ 'chartform':chartform,
+ 'teams':get_my_teams(request.user),
+ })
+
+@user_passes_test(ispromember,login_url="/",redirect_field_name=None)
+def boxplot_view(request,userid=0,
+ options={
+ 'includereststrokes':False,
+ }):
+
+ if 'options' in request.session:
+ options = request.session['options']
+
+ includereststrokes = options['includereststrokes']
+ workstrokesonly = not includereststrokes
+
+ if request.method == 'POST' and 'workouts' in request.POST:
+ form = WorkoutMultipleCompareForm(request.POST)
+ chartform = BoxPlotChoiceForm(request.POST)
+ if form.is_valid() and chartform.is_valid():
+ cd = form.cleaned_data
+ workouts = cd['workouts']
+ plotfield = chartform.cleaned_data['yparam']
+ ids = [int(w.id) for w in workouts]
+ request.session['ids'] = ids
+
+ labeldict = {
+ int(w.id): w.__unicode__() for w in workouts
+ }
+
+
+ datemapping = {
+ w.id:w.date for w in workouts
+ }
+
+
+
+ fieldlist,fielddict = dataprep.getstatsfields()
+ fieldlist = [plotfield,'workoutid']
+
+ # prepare data frame
+ datadf = dataprep.read_cols_df_sql(ids,fieldlist)
+
+ datadf = dataprep.clean_df_stats(datadf,workstrokesonly=workstrokesonly)
+ datadf['workoutid'].replace(datemapping,inplace=True)
+ datadf.rename(columns={"workoutid":"date"},inplace=True)
+ datadf = datadf.sort_values(['date'])
+ script,div = interactive_boxchart(datadf,plotfield)
+
+
+ return render(request,'boxplot.html',
+ {'interactiveplot':script,
+ 'the_div':div,
+ 'chartform':chartform,
+ 'teams':get_my_teams(request.user),
+ })
+ else:
+ return HttpResponse("Form is not valid")
+ if request.method == 'POST' and 'ids' in request.session:
+ chartform = BoxPlotChoiceForm(request.POST)
+ if chartform.is_valid():
+ plotfield = chartform.cleaned_data['yparam']
+ ids = request.session['ids']
+ request.session['ids'] = ids
+ workouts = [Workout.objects.get(id=id) for id in ids]
+
+ labeldict = {
+ int(w.id): w.__unicode__() for w in workouts
+ }
+
+ datemapping = {
+ w.id:w.date for w in workouts
+ }
+
+ fieldlist,fielddict = dataprep.getstatsfields()
+ fieldlist = [plotfield,'workoutid']
+
+ # prepare data frame
+ datadf = dataprep.read_cols_df_sql(ids,fieldlist)
+
+ datadf = dataprep.clean_df_stats(datadf,workstrokesonly=workstrokesonly)
+ datadf['workoutid'].replace(datemapping,inplace=True)
+ datadf.rename(columns={"workoutid":"date"},inplace=True)
+ datadf = datadf.sort_values(['date'])
+ script,div = interactive_boxchart(datadf,plotfield)
+
+
+ return render(request,'boxplot.html',
+ {'interactiveplot':script,
+ 'the_div':div,
+ 'chartform':chartform,
+ 'teams':get_my_teams(request.user),
+ })
+ else:
+ return HttpResponse("invalid form")
+ else:
+ url = reverse(workouts_view)
+ return HttpResponseRedirect(url)
+
# List Workouts
@login_required()
def workouts_view(request,message='',successmessage='',
@@ -4113,7 +4290,8 @@ def workout_stats_view(request,id=0,message="",successmessage=""):
fieldlist,fielddict = dataprep.getstatsfields()
fielddict.pop('workoutstate')
-
+ fielddict.pop('workoutid')
+
for field,verbosename in fielddict.iteritems():
thedict = {
'mean':datadf[field].mean(),