Private
Public Access
1
0

Merge branch 'release/teamcompare'

This commit is contained in:
Sander Roosendaal
2017-02-19 16:56:39 +01:00
12 changed files with 622 additions and 141 deletions

View File

@@ -449,6 +449,7 @@ def get_username(access_token):
try:
res = me_json['data']['username']
id = me_json['data']['id']
except KeyError:
res = None
@@ -482,7 +483,7 @@ def process_callback(request):
access_token = get_token(code)
username = get_username(access_token)
username,id = get_username(access_token)
return HttpResponse("got a user name: %s" % username)

View File

@@ -252,3 +252,23 @@ class StatsOptionsForm(forms.Form):
paddle = forms.BooleanField(initial=False,required=False)
snow = forms.BooleanField(initial=False,required=False)
other = forms.BooleanField(initial=False,required=False)
class WorkoutMultipleCompareForm(forms.Form):
workouts = forms.ModelMultipleChoiceField(queryset=Workout.objects.all(),
widget=forms.CheckboxSelectMultiple())
from rowers.interactiveplots import axlabels
axlabels.pop('None')
axlabels = list(axlabels.items())
class ChartParamChoiceForm(forms.Form):
plotchoices = (
('line','Line Plot'),
('scatter','Scatter Plot'),
)
xparam = forms.ChoiceField(choices=axlabels,initial='distance')
yparam = forms.ChoiceField(choices=axlabels,initial='hr')
plottype = forms.ChoiceField(choices=plotchoices,initial='scatter')
teamid = forms.IntegerField(widget=forms.HiddenInput())

View File

@@ -8,6 +8,8 @@ from rowingdata import rowingdata as rrdata
from django.utils import timezone
from bokeh.palettes import Dark2_8 as palette
import itertools
from bokeh.plotting import figure, ColumnDataSource, Figure,curdoc
from bokeh.models import CustomJS,Slider
from bokeh.charts import Histogram,HeatMap
@@ -52,7 +54,7 @@ import rowers.dataprep as dataprep
axlabels = {
'time': 'Time',
'distance': 'Distance (m)',
'cumdist': 'Distance (m)',
'cumdist': 'Cumulative Distance (m)',
'hr': 'Heart Rate (bpm)',
'spm': 'Stroke Rate (spm)',
'pace': 'Pace (/500m)',
@@ -1579,6 +1581,139 @@ def interactive_bar_chart(id=0,promember=0):
return [script,div]
def interactive_multiple_compare_chart(ids,xparam,yparam,plottype='line',
promember=0,
labeldict=None):
columns = [xparam,yparam,
'ftime','distance','fpace',
'power','hr','spm',
'time','pace','workoutstate',
'workoutid']
datadf = dataprep.getsmallrowdata_db(columns,ids=ids)
yparamname = axlabels[yparam]
#datadf = datadf[datadf[yparam] > 0]
#datadf = datadf[datadf[xparam] > 0]
# check if dataframe not empty
if datadf.empty:
return ['','<p>No non-zero data in selection</p>','','']
if xparam=='distance':
xaxmax = datadf['distance'].max()
xaxmin = datadf['distance'].min()
else:
xaxmax = yaxmaxima[xparam]
xaxmin = yaxminima[xparam]
x_axis_type = 'linear'
y_axis_type = 'linear'
# Add hover to this comma-separated string and see what changes
if (promember==1):
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,resize,crosshair'
else:
TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,crosshair'
if yparam == 'pace':
y_axis_type = 'datetime'
yaxmax = 90.
yaxmin = 150.
if xparam == 'time':
x_axis_type = 'datetime'
if xparam != 'time':
xvals = xaxmin+np.arange(100)*(xaxmax-xaxmin)/100.
else:
xvals = np.arange(100)
plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type,
tools=TOOLS,
toolbar_location="above",
plot_width=920,
toolbar_sticky=False)
colors = itertools.cycle(palette)
cntr = 0
for id,color in itertools.izip(ids,colors):
group = datadf[datadf['workoutid']==int(id)].copy()
group.sort_values(by='time',ascending=True,inplace=True)
group['x'] = group[xparam]
group['y'] = group[yparam]
ymean = group['y'].mean()
ylabel = Label(x=100,y=70+20*cntr,
x_units='screen',y_units='screen',
text=yparam+": {ymean:6.2f}".format(ymean=ymean),
background_fill_alpha=.7,
text_color=color,
)
if yparam != 'time' and yparam != 'pace':
plot.add_layout(ylabel)
print cntr,id,len(group),ymean
source = ColumnDataSource(
group
)
if labeldict:
legend=labeldict[id]
else:
legend=str(id)
if plottype=='line':
plot.line('x','y',source=source,color=color,legend=legend)
else:
plot.scatter('x','y',source=source,color=color,legend=legend,
fill_alpha=0.4,line_color=None)
cntr += 1
plot.legend.location='bottom_right'
plot.xaxis.axis_label = axlabels[xparam]
plot.yaxis.axis_label = axlabels[yparam]
if (xparam != 'time') and (xparam != 'distance') and (xparam != 'cumdist'):
xrange1 = Range1d(start=yaxminima[xparam],end=yaxmaxima[xparam])
plot.x_range = xrange1
if xparam == 'time':
xrange1 = Range1d(start=xaxmin,end=xaxmax)
plot.x_range = xrange1
plot.xaxis[0].formatter = DatetimeTickFormatter(
hours = ["%H"],
minutes = ["%M"],
seconds = ["%S"],
days = ["0"],
months = [""],
years = [""]
)
if yparam == 'pace':
plot.yaxis[0].formatter = DatetimeTickFormatter(
seconds = ["%S"],
minutes = ["%M"]
)
script, div = components(plot)
return [script,div]
def interactive_comparison_chart(id1=0,id2=0,xparam='distance',yparam='spm',
promember=0,plottype='line'):

View File

@@ -398,17 +398,23 @@ class Workout(models.Model):
privacy = models.CharField(default='visible',max_length=30,
choices=privacychoices)
def __str__(self):
def __unicode__(self):
date = self.date
name = self.name
distance = str(self.distance)
ownerfirst = self.user.user.first_name
ownerlast = self.user.user.last_name
duration = self.duration
try:
stri = date.strftime('%Y-%m-%d')+'_'+name
except AttributeError:
stri = str(date)+'_'+name
stri = u'{d} {n} {dist}m {duration:%H:%M:%S} {ownerfirst} {ownerlast}'.format(
d = date.strftime('%Y-%m-%d'),
n = name,
dist = distance,
duration = duration,
ownerfirst = ownerfirst,
ownerlast = ownerlast,
)
return stri
@@ -890,7 +896,7 @@ class WorkoutComment(models.Model):
def __unicode__(self):
return u'Comment to: {w} by {u1} {u2}'.format(
w=self.workout.name,
w=self.workout,
u1 = self.user.first_name,
u2 = self.user.last_name,
)

View File

@@ -39,6 +39,7 @@ from rowsandall_app.settings import C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SEC
def ewmovingaverage(interval,window_size):
# Experimental code using Exponential Weighted moving average
try:
intervaldf = pd.DataFrame({'v':interval})
idf_ewma1 = intervaldf.ewm(span=window_size)
idf_ewma2 = intervaldf[::-1].ewm(span=window_size)
@@ -48,6 +49,8 @@ def ewmovingaverage(interval,window_size):
interval2 = np.vstack((i_ewma1,i_ewma2[::-1]))
interval2 = np.mean( interval2, axis=0) # average
except ValueError:
interval2 = interval
return interval2
@@ -234,7 +237,7 @@ def handle_stravaexport(f2,workoutname,stravatoken,description=''):
act = client.upload_activity(f2,'tcx',name=workoutname)
try:
res = act.wait(poll_interval=5.0)
res = act.wait(poll_interval=5.0,timeout=30)
message = 'Workout successfully synchronized to Strava'
except:
res = 0
@@ -246,6 +249,7 @@ def handle_stravaexport(f2,workoutname,stravatoken,description=''):
act = client.update_activity(res.id,activity_type='Rowing',description=description)
else:
message = 'Strava upload timed out.'
return (0,message)
return (res.id,message)

View File

@@ -42,6 +42,7 @@ Click on the icon to upload this workout to your site of choice. A checkmark ind
{% endif %}
{% else %}
<div class="grid_1 alpha">
<a href="http://log.concept2.com/profile/{{ c2userid }}/log/{{ workout.uploadedtoc2 }}">
<img src="/static/img/c2square_checked.png" alt="Concept2 icon" width="60" height="60"></a>
</div>
{% endif %}
@@ -59,6 +60,7 @@ Click on the icon to upload this workout to your site of choice. A checkmark ind
{% endif %}
{% else %}
<div class="grid_1">
<a href="https://www.strava.com/activities/{{ workout.uploadedtostrava }}">
<img src="/static/img/stravasquare_checked.png" alt="Concept2 icon" width="60" height="60"></a>
</div>
{% endif %}
@@ -76,6 +78,7 @@ Click on the icon to upload this workout to your site of choice. A checkmark ind
{% endif %}
{% else %}
<div class="grid_1">
<a href="https://sporttracks.mobi/activity/{{ workout.uploadedtosporttracks }}">
<img src="/static/img/sporttrackssquare_checked.png" alt="Concept2 icon" width="60" height="60"></a>
</div>
{% endif %}

View File

@@ -112,6 +112,13 @@
</div>
<div class="grid_4 omega">
{% if team %}
<div class="grid_4" id="teambuttons">
<div class="grid_3 alpha">
<a class="button gray small" href="/rowers/team-compare-select/team/{{ team.id }}/">Multi Workout Compare</a>
</div>
</div>
{% endif %}
<div class="grid_4" id="announcements">
{% if announcements %}
<h3>What's New?</h3>

View File

@@ -0,0 +1,58 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}View Comparison {% endblock %}
{% block content %}
<script type="text/javascript" src="/static/js/bokeh-0.12.3.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{{ interactiveplot |safe }}
<script>
// Set things up to resize the plot on a window resize. You can play with
// the arguments of resize_width_height() to change the plot's behavior.
var plot_resize_setup = function () {
var plotid = Object.keys(Bokeh.index)[0]; // assume we have just one plot
var plot = Bokeh.index[plotid];
var plotresizer = function() {
// arguments: use width, use height, maintain aspect ratio
plot.resize_width_height(true, false, false);
};
window.addEventListener('resize', plotresizer);
plotresizer();
};
window.addEventListener('load', plot_resize_setup);
</script>
<style>
/* Need this to get the page in "desktop mode"; not having an infinite height.*/
html, body {height: 100%; margin:5px;}
</style>
<div id="workouts" class="grid_12 alpha">
<h1>Interactive Comparison</h1>
<div class="grid_2 alpha">
<a class="button gray small" href="/rowers/list-workouts/team/{{ teamid }}/">Team Workouts</a>
</div>
<div class="grid_2">
<a class="button gray small" href="/rowers/team-compare-select/team/{{ teamid }}/">Multi Compare</a>
</div>
<div class="grid_2 suffix_6 omega">
<a class="button gray small" href="/rowers/team/{{ teamid }}/">Team Page</a>
</div>
<div class="grid_12 alpha">
<div id="theplot" class="grid_12 alpha flexplot">
{{ the_div|safe }}
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,94 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}Workouts{% endblock %}
{% block content %}
<script>
setTimeout("location.reload(true);",60000);
</script>
<div class="grid_12">
<div class="grid_4 alpha">
{% if team %}
<form enctype="multipart/form-data" action="/rowers/team-compare-select/team/{{ team.id }}/" method="post">
{% else %}
<form enctype="multipart/form-data" action="/rowers/team-compare-select/" method="post">
{% endif %}
<table>
{{ dateform.as_table }}
</table>
{% csrf_token %}
</div>
<div class="grid_2">
<input name='daterange' class="button green" type="submit" value="Submit"> </form>
</div>
<div class="grid_5 prefix_1 omega">
{% if team %}
<form id="searchform" action="/rowers/team-compare-select/team/{{ team.id }}/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}"
method="get" accept-charset="utf-8">
{% else %}
<form id="searchform" action="/rowers/team-compare-select/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}"
method="get" accept-charset="utf-8">
{% endif %}
<div class="grid_3 prefix_1 alpha">
<input class="searchfield" id="searchbox" name="q" type="text" placeholder="Search">
</div>
<div class="grid_1 omega">
<button class="button blue small" type="submit">
Search
</button>
</div>
</form>
</div>
</div>
<div class="grid_12 alpha">
<h3>{{ team.name }} Team Workouts</h3>
</div>
<form enctype="multipart/form-data" action="/rowers/multi-compare" method="post">
<div id="workouts_table" class="grid_8 alpha">
{% if workouts %}
<table width="100%" class="listtable">
{{ form.as_table }}
</table>
{% else %}
<p> No workouts found </p>
{% endif %}
</div>
<div id="form_settings" class="grid_4 alpha">
<p><b>Warning: You are on an experimental part of the site. Use at your own risk.</b></p>
<p>Select two or more workouts on the left, set your plot settings below,
and press submit"</p>
{% csrf_token %}
<table>
{{ chartform.as_table }}
</table>
<div class="grid_1 prefix_2 suffix_1">
<p>
<input name='workoutselectform' class="button green" type="submit" value="Submit">
</p>
</div>
<div class="grid_4">
<p>You can use the date and search forms above to search through all
workouts from this team.</p>
<p>TIP: Agree with your team members to put tags (e.g. '8x500m') in the notes section of
your workouts. That makes it easy to search.</p>
</div>
</div>
</form>
{% endblock %}

View File

@@ -113,6 +113,13 @@ urlpatterns = [
url(r'^list-workouts/team/(?P<teamid>\d+)/$',views.workouts_view),
url(r'^list-workouts/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.workouts_view),
url(r'^list-workouts/$',views.workouts_view),
url(r'^team-compare-select/c/(?P<message>\w+.*)/$',views.team_comparison_select),
url(r'^team-compare-select/s/(?P<successmessage>\w+.*)/$',views.team_comparison_select),
url(r'^team-compare-select/c/(?P<message>\w+.*)/s/(?P<successmessage>\w+.*)$',views.team_comparison_select),
url(r'^team-compare-select/team/(?P<teamid>\d+)/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.team_comparison_select),
url(r'^team-compare-select/team/(?P<teamid>\d+)/$',views.team_comparison_select),
url(r'^team-compare-select/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.team_comparison_select),
url(r'^team-compare-select/$',views.team_comparison_select),
url(r'^list-graphs/$',views.graphs_view),
url(r'^(?P<theuser>\d+)/ote-bests/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.rankings_view),
url(r'^(?P<theuser>\d+)/ote-bests/(?P<deltadays>\d+)$',views.rankings_view),
@@ -204,6 +211,7 @@ urlpatterns = [
url(r'^workout/(\d+)/stravauploadw/$',views.workout_strava_upload_view),
url(r'^workout/(\d+)/recalcsummary/$',views.workout_recalcsummary_view),
url(r'^workout/(\d+)/sporttracksuploadw/$',views.workout_sporttracks_upload_view),
url(r'^multi-compare$',views.multi_compare_view),
url(r'^me/teams/c/(?P<message>\w+.*)/s/(?P<successmessage>\w+.*)$',views.rower_teams_view),
url(r'^me/teams/s/(?P<successmessage>\w+.*)$',views.rower_teams_view),
url(r'^me/teams/c/(?P<message>\w+.*)$',views.rower_teams_view),

View File

@@ -26,7 +26,7 @@ from rowers.forms import (
StatsOptionsForm,PredictedPieceForm,DateRangeForm,DeltaDaysForm,
EmailForm, RegistrationForm, RegistrationFormTermsOfService,
RegistrationFormUniqueEmail,CNsummaryForm,UpdateWindForm,
UpdateStreamForm
UpdateStreamForm,WorkoutMultipleCompareForm,ChartParamChoiceForm
)
from rowers.models import Workout, User, Rower, WorkoutForm,FavoriteChart
from rowers.models import (
@@ -810,7 +810,10 @@ def workout_strava_upload_view(request,id=0):
message = mes
w.uploadedtostrava = -1
w.save()
try:
os.remove(tcxfile)
except WindowsError:
pass
url = reverse(workout_export_view,
kwargs = {
'id':str(w.id),
@@ -1958,6 +1961,137 @@ def workout_setprivate_view(request,id,
})
return HttpResponseRedirect(url)
# Team comparison
@login_required()
def team_comparison_select(request,
startdatestring="",
enddatestring="",
message='',
successmessage='',
startdate=timezone.now()-datetime.timedelta(days=30),
enddate=timezone.now()+datetime.timedelta(days=1),
teamid=0):
try:
r = Rower.objects.get(user=request.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
try:
theteam = Team.objects.get(id=teamid)
except Team.DoesNotExist:
raise Http404("Team doesn't exist")
if theteam.viewing == 'allmembers' or theteam.manager == request.user:
workouts = Workout.objects.filter(team=theteam,
startdatetime__gte=startdate,
startdatetime__lte=enddate).order_by("-date", "-starttime")
elif theteam.viewing == 'coachonly':
workouts = Workout.objects.filter(team=theteam,user=r,
startdatetime__gte=startdate,
startdatetime__lte=enddate).order_by("-date","-starttime")
else:
theteam = None
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 = ChartParamChoiceForm(initial={'teamid':theteam.id})
return render(request, 'team_compare_select.html',
{'workouts': workouts,
'message': message,
'successmessage':successmessage,
'dateform':dateform,
'startdate':startdate,
'enddate':enddate,
'team':theteam,
'form':form,
'chartform':chartform,
})
@login_required()
def multi_compare_view(request):
promember=0
if not request.user.is_anonymous():
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
if request.method == 'POST':
form = WorkoutMultipleCompareForm(request.POST)
chartform = ChartParamChoiceForm(request.POST)
if form.is_valid() and chartform.is_valid():
cd = form.cleaned_data
workouts = cd['workouts']
xparam = chartform.cleaned_data['xparam']
yparam = chartform.cleaned_data['yparam']
plottype = chartform.cleaned_data['plottype']
teamid = chartform.cleaned_data['teamid']
ids = [w.id for w in workouts]
labeldict = {
w.id: w.__unicode__() for w in workouts
}
res = interactive_multiple_compare_chart(ids,xparam,yparam,
promember=promember,
plottype=plottype,
labeldict=labeldict)
script = res[0]
div = res[1]
return render(request,'multicompare.html',
{'interactiveplot':script,
'the_div':div,
'promember':promember,
'teamid':teamid,
})
else:
return HttpResponse("Form is not valid")
else:
url = reverse(workouts_view)
return HttpResponseRedirect(url)
# List Workouts
@login_required()
@@ -3302,6 +3436,16 @@ def workout_export_view(request,id=0, message="", successmessage=""):
row = Workout.objects.get(id=id)
except Workout.DoesNotExist:
raise Http404("Workout doesn't exist")
try:
thetoken = c2_open(request.user)
except C2NoTokenError:
thetoken = 0
if (checkworkoutuser(request.user,row)) and thetoken:
c2userid = c2stuff.get_userid(thetoken)
else:
c2userid = 0
form = WorkoutForm(instance=row)
g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime")
@@ -3318,6 +3462,7 @@ def workout_export_view(request,id=0, message="", successmessage=""):
'export.html',
{'workout':row,
'message':message,
'successmessage':successmessage,
'c2userid':c2userid,
})