Merge branch 'release/teamcompare'
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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'):
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -39,15 +39,18 @@ 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
|
||||
|
||||
intervaldf = pd.DataFrame({'v':interval})
|
||||
idf_ewma1 = intervaldf.ewm(span=window_size)
|
||||
idf_ewma2 = intervaldf[::-1].ewm(span=window_size)
|
||||
try:
|
||||
intervaldf = pd.DataFrame({'v':interval})
|
||||
idf_ewma1 = intervaldf.ewm(span=window_size)
|
||||
idf_ewma2 = intervaldf[::-1].ewm(span=window_size)
|
||||
|
||||
i_ewma1 = idf_ewma1.mean().ix[:,'v']
|
||||
i_ewma2 = idf_ewma2.mean().ix[:,'v']
|
||||
|
||||
i_ewma1 = idf_ewma1.mean().ix[:,'v']
|
||||
i_ewma2 = idf_ewma2.mean().ix[:,'v']
|
||||
|
||||
interval2 = np.vstack((i_ewma1,i_ewma2[::-1]))
|
||||
interval2 = np.mean( interval2, axis=0) # average
|
||||
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)
|
||||
|
||||
|
||||
@@ -6,57 +6,57 @@
|
||||
|
||||
{% 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>
|
||||
<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 }}
|
||||
{{ 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>
|
||||
<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 Plot</h1>
|
||||
|
||||
{% if user.is_authenticated and mayedit %}
|
||||
<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 suffix_2 omega">
|
||||
<p>
|
||||
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/advanced">Advanced Edit</a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div id="theplot" class="grid_12 alpha flexplot">
|
||||
|
||||
|
||||
<h1>Interactive Plot</h1>
|
||||
|
||||
{% if user.is_authenticated and mayedit %}
|
||||
<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 suffix_2 omega">
|
||||
<p>
|
||||
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/advanced">Advanced Edit</a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div id="theplot" class="grid_12 alpha flexplot">
|
||||
{{ the_div|safe }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -6,88 +6,91 @@
|
||||
|
||||
{% block content %}
|
||||
<div id="exportbuttons" class="grid_6 alpha">
|
||||
|
||||
|
||||
<h3>Export Workout</h3>
|
||||
|
||||
<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 suffix_2 omega">
|
||||
<p>
|
||||
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/advanced">Advanced Edit</a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="grid_6 alpha">
|
||||
<p>
|
||||
Click on the icon to upload this workout to your site of choice. A checkmark indicates that the workout has already been uploaded. If the button is grayed out, click it to authorize the connection to that site. Use TCX or CSV export to email a TCX or CSV file of your workout to yourself.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<h3>Export Workout</h3>
|
||||
|
||||
<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 suffix_2 omega">
|
||||
<p>
|
||||
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/advanced">Advanced Edit</a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="grid_6 alpha">
|
||||
<p>
|
||||
Click on the icon to upload this workout to your site of choice. A checkmark indicates that the workout has already been uploaded. If the button is grayed out, click it to authorize the connection to that site. Use TCX or CSV export to email a TCX or CSV file of your workout to yourself.
|
||||
</p>
|
||||
|
||||
{% if workout.uploadedtoc2 == 0 %}
|
||||
{% if user.rower.c2token == None or user.rower.c2token == '' %}
|
||||
<div class="grid_1 alpha">
|
||||
<a href="/rowers/me/c2authorize">
|
||||
<img src="/static/img/c2square_gray.png" alt="C2 icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="grid_1 alpha">
|
||||
<a href="/rowers/workout/{{ workout.id }}/c2uploadw"><img src="/static/img/c2square.jpg" alt="Concept2 icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% 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>
|
||||
|
||||
{% if workout.uploadedtoc2 == 0 %}
|
||||
{% if user.rower.c2token == None or user.rower.c2token == '' %}
|
||||
<div class="grid_1 alpha">
|
||||
<a href="/rowers/me/c2authorize">
|
||||
<img src="/static/img/c2square_gray.png" alt="C2 icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="grid_1 alpha">
|
||||
<a href="/rowers/workout/{{ workout.id }}/c2uploadw"><img src="/static/img/c2square.jpg" alt="Concept2 icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="grid_1 alpha">
|
||||
<img src="/static/img/c2square_checked.png" alt="Concept2 icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if workout.uploadedtostrava == 0 %}
|
||||
{% if user.rower.stravatoken == None or user.rower.stravatoken == '' %}
|
||||
<div class="grid_1">
|
||||
<a href="/rowers/me/stravaauthorize">
|
||||
<img src="/static/img/stravasquare_gray.png" alt="Strava icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="grid_1">
|
||||
<a href="/rowers/workout/{{ workout.id }}/stravauploadw"><img src="/static/img/stravasquare.png" alt="Strava icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if user.rower.stravatoken == None or user.rower.stravatoken == '' %}
|
||||
<div class="grid_1">
|
||||
<a href="/rowers/me/stravaauthorize">
|
||||
<img src="/static/img/stravasquare_gray.png" alt="Strava icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="grid_1">
|
||||
<img src="/static/img/stravasquare_checked.png" alt="Concept2 icon" width="60" height="60"></a>
|
||||
</div>
|
||||
<div class="grid_1">
|
||||
<a href="/rowers/workout/{{ workout.id }}/stravauploadw"><img src="/static/img/stravasquare.png" alt="Strava icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% 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 %}
|
||||
{% if workout.uploadedtosporttracks == 0 %}
|
||||
{% if user.rower.sporttrackstoken == None or user.rower.sporttrackstoken == '' %}
|
||||
<div class="grid_1">
|
||||
<a href="/rowers/me/sporttracksauthorize">
|
||||
<img src="/static/img/sporttrackssquare_gray.png" alt="SportTracks icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="grid_1">
|
||||
<a href="/rowers/workout/{{ workout.id }}/sporttracksuploadw">
|
||||
<img src="/static/img/sporttrackssquare.png" alt="SportTracks icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if user.rower.sporttrackstoken == None or user.rower.sporttrackstoken == '' %}
|
||||
<div class="grid_1">
|
||||
<a href="/rowers/me/sporttracksauthorize">
|
||||
<img src="/static/img/sporttrackssquare_gray.png" alt="SportTracks icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="grid_1">
|
||||
<img src="/static/img/sporttrackssquare_checked.png" alt="Concept2 icon" width="60" height="60"></a>
|
||||
</div>
|
||||
<div class="grid_1">
|
||||
<a href="/rowers/workout/{{ workout.id }}/sporttracksuploadw">
|
||||
<img src="/static/img/sporttrackssquare.png" alt="SportTracks icon" width="60" height="60"></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="grid_1">
|
||||
<a href="/rowers/workout/{{ workout.id }}/emailtcx">
|
||||
<img src="/static/img/export.png" alt="TCX Export" width="60" height="60"></a>
|
||||
</div>
|
||||
{% 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 %}
|
||||
<div class="grid_1">
|
||||
<a href="/rowers/workout/{{ workout.id }}/emailtcx">
|
||||
<img src="/static/img/export.png" alt="TCX Export" width="60" height="60"></a>
|
||||
</div>
|
||||
|
||||
<div class="grid_1">
|
||||
<a href="/rowers/workout/{{ workout.id }}/emailcsv">
|
||||
<img src="/static/img/CSVsquare.png" alt="CSV Export" width="60" height="60"></a>
|
||||
</div>
|
||||
<div class="grid_1">
|
||||
<a href="/rowers/workout/{{ workout.id }}/emailcsv">
|
||||
<img src="/static/img/CSVsquare.png" alt="CSV Export" width="60" height="60"></a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -100,16 +103,16 @@ You only need to do this once. After that, the site will have access until you
|
||||
revoke the authorization for the "rowingdata" app.</p>
|
||||
|
||||
<div class="grid_2 alpha">
|
||||
<p><a href="/rowers/me/stravaauthorize/"><img src="/static/img/ConnectWithStrava.png" alt="connect with strava" width="120"></a></p>
|
||||
<p><a href="/rowers/me/stravaauthorize/"><img src="/static/img/ConnectWithStrava.png" alt="connect with strava" width="120"></a></p>
|
||||
|
||||
</div>
|
||||
<div class="grid_2">
|
||||
<p><a href="/rowers/me/c2authorize/"><img src="/static/img/blueC2logo.png" alt="connect with Concept2" width="120"></a></p>
|
||||
<p><a href="/rowers/me/c2authorize/"><img src="/static/img/blueC2logo.png" alt="connect with Concept2" width="120"></a></p>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="grid_2 omega">
|
||||
<p><a href="/rowers/me/sporttracksauthorize/"><img src="/static/img/sporttracks-button.png" alt="connect with SportTracks" width="120"></a></p>
|
||||
<p><a href="/rowers/me/sporttracksauthorize/"><img src="/static/img/sporttracks-button.png" alt="connect with SportTracks" width="120"></a></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -117,4 +120,4 @@ revoke the authorization for the "rowingdata" app.</p>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
58
rowers/templates/multicompare.html
Normal file
58
rowers/templates/multicompare.html
Normal 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 %}
|
||||
94
rowers/templates/team_compare_select.html
Normal file
94
rowers/templates/team_compare_select.html
Normal 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 %}
|
||||
@@ -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),
|
||||
|
||||
149
rowers/views.py
149
rowers/views.py
@@ -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()
|
||||
os.remove(tcxfile)
|
||||
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,
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user