diff --git a/008fc633-d9d4-4abc-9812-e22014e19498.tcx b/008fc633-d9d4-4abc-9812-e22014e19498.tcx
new file mode 100644
index 00000000..31d671d7
--- /dev/null
+++ b/008fc633-d9d4-4abc-9812-e22014e19498.tcx
@@ -0,0 +1,2523 @@
+
+
+
+
+ 2016-05-20T13:41:26.962390+00:00
+
+ 537
+ 2000
+ 1
+
+ 148
+
+
+ 156
+
+ Active
+ 21
+ Manual
+
+
+ <Element 'Notes' at 0x7f5eb3c3ef98>
+
+
+
+ rowsandall.com/rowingdata
+
+
+ rowingdata
+
+
+ 0
+ 75
+
+ Release
+
+ EN
+ 000-00000-00
+
+
diff --git a/requirements.txt b/requirements.txt
index f7fe3525..5dfec22a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,6 +3,7 @@ apipkg==1.5
appdirs==1.4.3
arcgis==1.6.0
arrow==0.13.1
+asn1crypto==0.24.0
atomicwrites==1.3.0
attrs==19.1.0
backcall==0.1.0
@@ -24,29 +25,30 @@ cookies==2.2.1
coreapi==2.3.3
coreschema==0.0.4
coverage==4.5.3
+cryptography==2.6.1
cycler==0.10.0
dask==1.1.4
decorator==4.4.0
defusedxml==0.5.0
-Django==1.9.5
+Django==2.1.7
django-analytical==2.5.0
django-async-messages==0.3.1
django-braces==1.13.0
django-classy-tags==0.8.0
django-cookie-law==2.0.1
-django-cors-headers==2.4.0
+django-cors-headers==2.5.2
django-countries==5.3.3
django-datetime-widget==0.9.3
-django-debug-toolbar==1.4
+django-debug-toolbar==1.11
django-extensions==2.1.6
-django-htmlmin==0.10.0
+django-htmlmin==0.11.0
django-leaflet==0.24.0
django-mailbox==4.7.1
-django-oauth-toolkit==0.10.0
+django-oauth-toolkit==1.2.0
django-oauth2-provider==0.2.6.1
django-rest-framework==0.1.0
django-rest-swagger==2.2.0
-django-rq==1.3.0
+django-rq==1.3.1
django-rq-dashboard==0.3.3
django-ses==0.8.10
django-shell-plus==1.1.7
@@ -54,7 +56,7 @@ django-social-share==1.3.2
django-suit==0.2.26
django-suit-rq==1.0.1
django-tz-detect==0.2.9
-djangorestframework==3.5.4
+djangorestframework==3.9.2
docopt==0.6.2
docutils==0.14
entrypoints==0.3
@@ -63,7 +65,6 @@ factory-boy==2.11.1
Faker==1.0.4
fitparse==1.1.0
future==0.17.1
-GDAL==2.3.3
geocoder==1.38.1
holoviews==1.11.3
html5lib==1.0.1
@@ -75,13 +76,14 @@ idna==2.8
image==1.5.27
importlib-resources==1.0.2
ipykernel==5.1.0
-ipython==7.3.0
+ipython==7.4.0
ipython-genutils==0.2.0
ipywidgets==7.4.2
iso8601==0.1.12
isodate==0.6.0
itypes==1.1.0
jedi==0.13.3
+jeepney==0.4
Jinja2==2.10
jsonschema==3.0.1
jupyter==1.0.0
@@ -89,7 +91,7 @@ jupyter-client==5.2.4
jupyter-console==6.0.0
jupyter-core==4.4.0
jupyterlab==0.35.4
-jupyterlab-server==0.2.0
+jupyterlab-server==0.3.0
keyring==18.0.0
kiwisolver==1.0.1
kombu==4.3.0
@@ -109,7 +111,7 @@ nose-parameterized==0.6.0
notebook==5.7.6
numpy==1.16.2
oauth2==1.9.0.post1
-oauthlib==1.0.3
+oauthlib==3.0.1
openapi-codec==1.3.2
packaging==19.0
pandas==0.24.2
@@ -152,10 +154,11 @@ ratelim==0.1.6
redis==3.2.1
requests==2.21.0
requests-oauthlib==1.2.0
-rowingdata==2.2.3
+rowingdata==2.2.5
rowingphysics==0.5.0
rq==0.13.0
scipy==1.2.1
+SecretStorage==3.1.1
Send2Trash==1.5.0
shell==1.0.1
shortuuid==0.5.0
@@ -182,7 +185,6 @@ vine==1.3.0
wcwidth==0.1.7
webencodings==0.5.1
widgetsnbextension==3.4.2
-winkerberos==0.7.0
xmltodict==0.12.0
yamjam==0.1.7
yamllint==1.15.0
diff --git a/rowers/dataprep.py b/rowers/dataprep.py
index 757254f5..692b9359 100644
--- a/rowers/dataprep.py
+++ b/rowers/dataprep.py
@@ -54,7 +54,7 @@ from rowers.models import strokedatafields
#allowedcolumns = [item[0] for item in rowingmetrics]
allowedcolumns = [key for key,value in strokedatafields.items()]
-from async_messages import messages as a_messages
+#from async_messages import messages as a_messages
import os
import zipfile
import pandas as pd
@@ -920,12 +920,6 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower',
for key, value in checks.items():
if not value:
allchecks = 0
- if consistencychecks:
- a_messages.error(
- r.user, 'Failed consistency check: ' + key + ', autocorrected')
- else:
- pass
- # a_messages.error(r.user,'Failed consistency check: '+key+', not corrected')
except ZeroDivisionError:
pass
@@ -1148,9 +1142,6 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower',
# submit email task to send email about breakthrough workout
if isbreakthrough:
- a_messages.info(
- r.user, 'It looks like you have a new breakthrough workout'
- )
if r.getemailnotifications and not r.emailbounced:
job = myqueue(queuehigh,handle_sendemail_breakthrough,
w.id,
@@ -1161,7 +1152,6 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower',
# submit email task to send email about breakthrough workout
if ishard:
- a_messages.info(r.user, 'That was a pretty hard workout')
if r.getemailnotifications and not r.emailbounced:
job = myqueue(queuehigh,handle_sendemail_hard,
w.id,
diff --git a/rowers/dataprepnodjango.py b/rowers/dataprepnodjango.py
index 7bb0c4cb..d2f3c84a 100644
--- a/rowers/dataprepnodjango.py
+++ b/rowers/dataprepnodjango.py
@@ -1007,7 +1007,10 @@ def dataprep(rowdatadf,id=0,bands=True,barchart=True,otwpower=True,
f = rowdatadf['TimeStamp (sec)'].diff().mean()
if f != 0:
- windowsize = 2*(int(10./(f)))+1
+ try:
+ windowsize = 2*(int(10./(f)))+1
+ except ValueError:
+ windowsize = 1
else:
windowsize = 1
if windowsize <= 3:
diff --git a/rowers/decorators.py b/rowers/decorators.py
index 36a4a3b5..f9d03ed7 100644
--- a/rowers/decorators.py
+++ b/rowers/decorators.py
@@ -42,7 +42,7 @@ def login_required_message(function=None, message=default_message):
to the log-in page if necessary.
"""
actual_decorator = user_passes_test(
- lambda u: u.is_authenticated(),
+ lambda u: u.is_authenticated,
message=message,
)
if function:
diff --git a/rowers/forms.py b/rowers/forms.py
index ff61a245..30c6836b 100644
--- a/rowers/forms.py
+++ b/rowers/forms.py
@@ -25,6 +25,12 @@ import datetime
from django.forms import formset_factory
from rowers.utils import landingpages
from rowers.metrics import axes
+from rowers.metrics import axlabels
+
+formaxlabels = axlabels.copy()
+formaxlabels.pop('None')
+parchoices = list(sorted(formaxlabels.items(), key = lambda x:x[1]))
+
class FlexibleDecimalField(forms.DecimalField):
@@ -710,6 +716,12 @@ class DataFrameColumnsForm(forms.Form):
cols = forms.MultipleChoiceField(choices=colchoices,
label='Table Columns')
+class HistoForm(forms.Form):
+ includereststrokes = forms.BooleanField(initial=False,label='Include Rest Strokes',required=False)
+ histoparam = forms.ChoiceField(choices=parchoices,initial='power',
+ label='Metric')
+
+
# form to select modality and boat type for trend flex
class TrendFlexModalForm(forms.Form):
modality = forms.ChoiceField(choices=workouttypes,
@@ -797,12 +809,6 @@ class PlannedSessionMultipleCloneForm(forms.Form):
label='Planned Sessions'
)
-from rowers.metrics import axlabels
-
-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',
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index e1207fb6..141bc9f0 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -1214,12 +1214,13 @@ def fitnessmetric_chart(fitnessmetrics,user,workoutmode='rower',startdate=None,
return [script,div]
-def interactive_histoall(theworkouts):
+def interactive_histoall(theworkouts,histoparam,includereststrokes):
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
ids = [int(w.id) for w in theworkouts]
- rowdata = dataprep.getsmallrowdata_db(['power'],ids=ids,doclean=True)
+ workstrokesonly = not includereststrokes
+ rowdata = dataprep.getsmallrowdata_db([histoparam],ids=ids,doclean=True,workstrokesonly=workstrokesonly)
rowdata.dropna(axis=0,how='any',inplace=True)
@@ -1227,16 +1228,16 @@ def interactive_histoall(theworkouts):
return "","No Valid Data Available","",""
try:
- histopwr = rowdata['power'].values
+ histopwr = rowdata[histoparam].values
except KeyError:
- return "","No power data","",""
+ return "","No data","",""
if len(histopwr) == 0:
return "","No valid data available","",""
# throw out nans
histopwr = histopwr[~np.isinf(histopwr)]
- histopwr = histopwr[histopwr > 25]
- histopwr = histopwr[histopwr < 1000]
+ histopwr = histopwr[histopwr > yaxminima[histoparam]]
+ histopwr = histopwr[histopwr < yaxmaxima[histoparam]]
plot = Figure(tools=TOOLS,plot_width=900,
toolbar_sticky=False,
@@ -1289,7 +1290,7 @@ def interactive_histoall(theworkouts):
# plot.quad(top='hist_norm',bottom=0,left=edges[:-1],right=edges[1:])
plot.quad(top='hist_norm',bottom=0,left='left',right='right',source=source)
- plot.xaxis.axis_label = "Power (W)"
+ plot.xaxis.axis_label = axlabels[histoparam]
plot.yaxis.axis_label = "% of strokes"
plot.y_range = Range1d(0,1.05*max(hist_norm))
@@ -1297,7 +1298,7 @@ def interactive_histoall(theworkouts):
hover = plot.select(dict(type=HoverTool))
hover.tooltips = OrderedDict([
- ('Power(W)','@left{int}'),
+ (axlabels[histoparam],'@left{int}'),
('% of strokes','@hist_norm'),
('Cumulative %','@histsum{int}'),
])
@@ -1311,12 +1312,36 @@ def interactive_histoall(theworkouts):
axis_label="Cumulative % of strokes"),'right')
plot.sizing_mode = 'scale_width'
+
+ annolabel = Label(x=50,y=450,x_units='screen',y_units='screen',
+ text='',
+ background_fill_alpha=0.7,
+ background_fill_color='white',
+ text_color='black',
+ )
+
+ plot.add_layout(annolabel)
+
+ callback = CustomJS(args = dict(
+ annolabel=annolabel,
+ ), code="""
+ var annotation = annotation.value
+ annolabel.text = annotation
+ """)
+
+ annotation = TextInput(title="Type your plot notes here", value="",
+ callback=callback)
+ callback.args["annotation"] = annotation
+
+ layout = layoutcolumn([annotation,plot])
+
try:
- script, div = components(plot)
+ script, div = components(layout)
except ValueError:
script = ''
div = ''
-
+
+
return [script,div]
def course_map(course):
@@ -3889,7 +3914,7 @@ def interactive_flex_chart2(id=0,promember=0,
""")
annotation = TextInput(title="Type your plot notes here", value="",
- callback=callback)
+ callback=callback)
callback.args["annotation"] = annotation
slider_spm_min = Slider(start=15.0, end=55,value=15.0, step=.1,
diff --git a/rowers/management/commands/processemail.py b/rowers/management/commands/processemail.py
index 25382b96..ac493684 100644
--- a/rowers/management/commands/processemail.py
+++ b/rowers/management/commands/processemail.py
@@ -14,7 +14,7 @@ import io
from django.core.management.base import BaseCommand
from django_mailbox.models import Message, MessageAttachment,Mailbox
-from django.core.urlresolvers import reverse
+from django.urls import reverse
from django.conf import settings
from django.utils import timezone
diff --git a/rowers/middleware.py b/rowers/middleware.py
index a4e9249a..9b49e13a 100644
--- a/rowers/middleware.py
+++ b/rowers/middleware.py
@@ -9,6 +9,7 @@ queuelow = django_rq.get_queue('low')
from rowers.tasks import handle_updatefitnessmetric,handle_sendemail_expired
from rowers.mytypes import otwtypes
from django.contrib import messages
+from django.http import HttpResponse
def getrower(user):
try:
@@ -70,11 +71,18 @@ def do_update(user,mode='rower',days=42):
class PowerTimeFitnessMetricMiddleWare(object):
- def process_request(self, request):
+ def __init__(self, get_response):
+ self.get_response = get_response
+
+ def __call__(self, request):
# Code to be executed before the view is called
- if request.user.is_authenticated():
+ if request.user.is_authenticated:
result = do_update(request.user,mode='rower')
result = do_update(request.user,mode='water')
+
+ response = self.get_response(request)
+
+ return response
from django.shortcuts import redirect
@@ -93,8 +101,11 @@ allowed_paths = [
]
class GDPRMiddleWare(object):
- def process_request(self, request):
- if request.user.is_authenticated() and request.path not in allowed_paths:
+ def __init__(self, get_response):
+ self.get_response = get_response
+
+ def __call__(self, request):
+ if request.user.is_authenticated and request.path not in allowed_paths:
r = getrower(request.user)
nexturl = request.path
if 'optin' in nexturl:
@@ -104,9 +115,16 @@ class GDPRMiddleWare(object):
'/rowers/me/gdpr-optin/?next=%s' % nexturl
)
+ response = self.get_response(request)
+
+ return response
+
class RowerPlanMiddleWare(object):
- def process_request(self, request):
- if request.user.is_authenticated() and request.user.rower.rowerplan != 'basic':
+ def __init__(self, get_response):
+ self.get_response = get_response
+
+ def __call__(self, request):
+ if request.user.is_authenticated and request.user.rower.rowerplan != 'basic':
if request.user.rower.paymenttype == 'single':
if request.user.rower.planexpires < timezone.now().date():
messg = 'Your paid plan has expired. We have reset you to a free basic plan.'
@@ -122,3 +140,6 @@ class RowerPlanMiddleWare(object):
r.user.last_name,
str(r.planexpires))
+ response = self.get_response(request)
+
+ return response
diff --git a/rowers/models.py b/rowers/models.py
index f3f1eed9..f124ac36 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -1014,7 +1014,7 @@ class BasePlannedSessionFormSet(BaseFormSet):
# Check if workout is owned by this user
def checkworkoutuser(user,workout):
- if user.is_anonymous():
+ if user.is_anonymous:
return False
try:
r = Rower.objects.get(user=user)
@@ -1034,7 +1034,7 @@ def checkworkoutuser(user,workout):
# Check if workout may be viewed by this user
def checkworkoutuserview(user,workout):
- if user.is_anonymous():
+ if user.is_anonymous:
return False
try:
r = Rower.objects.get(user=user)
diff --git a/rowers/tasks.py b/rowers/tasks.py
index 6a8ad981..04c65fec 100644
--- a/rowers/tasks.py
+++ b/rowers/tasks.py
@@ -97,6 +97,12 @@ import arrow
from rowers.utils import get_strava_stream
+def safetimedelta(x):
+ try:
+ return timedelta(seconds=x)
+ except ValueError:
+ return timedelta(seconds=0)
+
siteurl = SITE_URL
# testing task
@@ -292,7 +298,7 @@ def handle_check_race_course(self,
rowdata = rowdata[rowdata['time']>splitsecond]
# we may want to expand the time (interpolate)
rowdata['dt'] = rowdata['time'].apply(
- lambda x: timedelta(seconds=x)
+ lambda x: safetimedelta(seconds=x)
)
rowdata = rowdata.resample('100ms',on='dt').mean()
rowdata = rowdata.interpolate()
@@ -618,7 +624,7 @@ def handle_calctrimp(id,
df2['time'] = df2[' ElapsedTime (sec)']
df2['time'] = df2['time'].apply(
- lambda x:timedelta(seconds=x)
+ lambda x:safetimedelta(seconds=x)
)
duration = df['TimeStamp (sec)'].max()-df['TimeStamp (sec)'].min()
@@ -1659,11 +1665,10 @@ def handle_makeplot(f1, f2, t, hrdata, plotnr, imagename,
try:
haspower = row.df[' Power (watts)'].mean() > 50
- except TypeError:
- haspower = True
- except KeyError:
+ except (TypeError, KeyError):
haspower = False
+
nr_rows = len(row.df)
if (plotnr in [1, 2, 4, 5, 8, 11, 9, 12]) and (nr_rows > 1200):
bin = int(nr_rows / 1200.)
@@ -1700,14 +1705,13 @@ def handle_makeplot(f1, f2, t, hrdata, plotnr, imagename,
t += ' - Power Distribution'
fig1 = row.get_power_piechart(t)
- canvas = FigureCanvas(fig1)
+ if fig1:
+ canvas = FigureCanvas(fig1)
- # plt.savefig('static/plots/'+imagename,format='png')
- canvas.print_figure('static/plots/' + imagename)
- # plt.imsave(fname='static/plots/'+imagename)
- plt.close(fig1)
- fig1.clf()
- gc.collect()
+ canvas.print_figure('static/plots/' + imagename)
+ plt.close(fig1)
+ fig1.clf()
+ gc.collect()
return imagename
diff --git a/rowers/tasks_standalone.py b/rowers/tasks_standalone.py
index f20586c3..d2b2375c 100644
--- a/rowers/tasks_standalone.py
+++ b/rowers/tasks_standalone.py
@@ -14,7 +14,6 @@ django.setup()
import time
from celery import app
from django_rq import job
-from async_messages import message_user,messages
from rowers.models import Workout
from django.contrib.auth.models import User
diff --git a/rowers/templates/analysis.html b/rowers/templates/analysis.html
index d598c144..aca799ec 100644
--- a/rowers/templates/analysis.html
+++ b/rowers/templates/analysis.html
@@ -40,14 +40,14 @@
- Power Histogram
+ Histogram
- Plot a power histogram of all your strokes over a date range.
+ Plot a histogram chart of one metric for all your strokes over a date range.
diff --git a/rowers/templates/histo.html b/rowers/templates/histo.html
index b7b2d7d2..4e200c2d 100644
--- a/rowers/templates/histo.html
+++ b/rowers/templates/histo.html
@@ -75,6 +75,8 @@
+Histogram View
+
diff --git a/rowers/templates/registration/login.html b/rowers/templates/registration/login.html
index aa650e83..21089cb6 100644
--- a/rowers/templates/registration/login.html
+++ b/rowers/templates/registration/login.html
@@ -8,7 +8,7 @@