Private
Public Access
1
0

Merge branch 'release/v18.6.0'

This commit is contained in:
Sander Roosendaal
2022-07-21 17:18:49 +02:00
22 changed files with 3286 additions and 3399 deletions
+6 -6
View File
@@ -23,12 +23,12 @@ def time_in_path(df, p, maxmin='max', getall=False, name='unknown', logfile=None
def f(x):
return coordinate_in_path(x['latitude'], x['longitude'], p)
df['inpolygon'] = df.apply(f, axis=1)
inpolygon = df.apply(lambda row:f(row), axis=1).copy()
if maxmin == 'max':
b = (~df['inpolygon']).shift(-1)+df['inpolygon']
b = (~inpolygon).shift(-1)+inpolygon
else: # pragma: no cover
b = (~df['inpolygon']).shift(1)+df['inpolygon']
b = (~inpolygon).shift(1)+inpolygon
if len(df[b == 2]):
if logfile is not None: # pragma: no cover
@@ -90,7 +90,7 @@ def coursetime_first(data, paths, polygons=[], logfile=None):
try:
entrytime, entrydistance = time_in_path(
data, paths[0], maxmin='max', name=polygons[0][1], logfile=logfile)
data, paths[0], maxmin='max', name=str(polygons[0]), logfile=logfile)
coursecompleted = True
except InvalidTrajectoryError: # pragma: no cover
entrytime = data['time'].max()
@@ -118,7 +118,7 @@ def coursetime_paths(data, paths, finalmaxmin='min', polygons=[], logfile=None):
(
entrytime,
entrydistance
) = time_in_path(data, paths[0], maxmin=finalmaxmin, name=polygons[0][1], logfile=logfile)
) = time_in_path(data, paths[0], maxmin=finalmaxmin, name=str(polygons[0]), logfile=logfile)
coursecompleted = True
except InvalidTrajectoryError: # pragma: no cover
entrytime = data['time'].max()
@@ -129,7 +129,7 @@ def coursetime_paths(data, paths, finalmaxmin='min', polygons=[], logfile=None):
if len(paths) > 1:
try:
time, dist = time_in_path(
data, paths[0], name=polygons[0][1], logfile=logfile)
data, paths[0], name=str(polygons[0]), logfile=logfile)
data2 = data[data['time'] > time].copy()
data2['time'] = data2['time'].apply(lambda x: x-time)
data2['cum_dist'] = data2['cum_dist'].apply(lambda x: x-dist)
+4
View File
@@ -16,3 +16,7 @@ database_url = 'mysql://{user}:{password}@{host}:{port}/{database_name}'.format(
if settings.DEBUG or user == '':
database_url = 'sqlite:///db.sqlite3'
#database_name_dev = DEV_DATABASES['default']['NAME']
database_url_debug = database_url
+417 -2075
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+20
View File
@@ -148,6 +148,26 @@ class InstantPlanSelectForm(forms.Form):
self.fields['enddate'].initial = timezone.now(
)+datetime.timedelta(days=instantplan.duration)
# Instroke Metrics interactive chart form
class InstrokeForm(forms.Form):
metric = forms.ChoiceField(label='metric',choices=(('a','a'),('b','b')))
individual_curves = forms.BooleanField(label='individual curves',initial=False,
required=False)
spm_min = forms.IntegerField(initial=15,label='SPM Min',widget=HiddenInput)
spm_max = forms.IntegerField(initial=45,label='SPM Max',widget=HiddenInput)
activeminutesmin = forms.IntegerField(
required=False, initial=0, widget=forms.HiddenInput())
activeminutesmax = forms.IntegerField(
required=False, initial=0, widget=forms.HiddenInput())
def __init__(self, *args, **kwargs): # pragma: no cover
choices = kwargs.pop('choices', [])
super(InstrokeForm, self).__init__(*args, **kwargs)
if len(choices) > 0:
choices = [(choice, choice) for choice in choices]
self.fields['metric'].choices = choices
self.fields['metric'].initial = choices[0]
# Video Analysis creation form
class VideoAnalysisCreateForm(forms.Form):
+127
View File
@@ -4070,6 +4070,133 @@ def interactive_streamchart(id=0, promember=0):
return [script, div]
def instroke_interactive_chart(df,metric, workout, spm_min, spm_max,
activeminutesmin, activeminutesmax,
individual_curves):
df_pos = (df+abs(df))/2.
df_min = -(-df+abs(-df))/2.
mean_vals = df.median()
q75 = df_pos.quantile(q=0.75).replace(0,np.nan)
q25 = df_pos.quantile(q=0.25).replace(0,np.nan)
q75min = df_min.quantile(q=0.75).replace(0,np.nan)
q25min = df_min.quantile(q=0.25).replace(0,np.nan)
xvals = np.arange(len(mean_vals))
df_plot = pd.DataFrame({
'x':xvals,
'median':mean_vals,
'high':q75,
'low':q75min,
'high 2':q25min,
'low 2': q25,
})
df_plot['high'].update(df_plot.pop('high 2'))
df_plot['low'].update(df_plot.pop('low 2'))
df_plot.interpolate(axis=1,inplace=True)
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair'
plot = Figure(plot_width=920,tools=TOOLS,
toolbar_location='above',
toolbar_sticky=False)
plot.sizing_mode = 'stretch_both'
plot.title.text = str(workout) + ' - ' + metric
# add watermark
watermarkurl = "/static/img/logo7.png"
watermarkrange = Range1d(start=0, end=1)
watermarkalpha = 0.6
watermarkx = 0.99
watermarky = 0.01
watermarkw = 184
watermarkh = 35
watermarkanchor = 'bottom_right'
plot.extra_y_ranges = {"watermark": watermarkrange}
plot.extra_x_ranges = {"watermark": watermarkrange}
plot.image_url([watermarkurl], watermarkx, watermarky,
watermarkw, watermarkh,
global_alpha=watermarkalpha,
w_units='screen',
h_units='screen',
anchor=watermarkanchor,
dilate=True,
x_range_name="watermark",
y_range_name="watermark",
)
source = ColumnDataSource(
df_plot
)
TIPS = OrderedDict([
('x','@x'),
('median','@median'),
('high','@high'),
('low','@low')
])
hover = plot.select(type=HoverTool)
hover.tooltips = TIPS
s = 'SPM: {spm_min} - {spm_max}'.format(
spm_min = spm_min,
spm_max = spm_max,
)
label = Label(x=50, y=450, x_units='screen',y_units='screen',
text=s,
background_fill_alpha=0.7,
background_fill_color='white',
text_color='black',
)
s2 = 'Time: {activeminutesmin} - {activeminutesmax} min'.format(
activeminutesmin=activeminutesmin,
activeminutesmax=activeminutesmax
)
label2 = Label(x=50,y=400, x_units='screen', y_units='screen',
text=s2,
background_fill_alpha=0.7,
background_fill_color='white',
text_color='black',
)
plot.add_layout(label)
plot.add_layout(label2)
if individual_curves:
for index,row in df.iterrows():
plot.line(xvals,row,color='lightgray',line_width=1)
else:
plot.varea('x', y1='high', y2='low',source=source,fill_color="lightgray",alpha=0.5)
plot.line('x','median',source=source,legend_label='median',color="black",
line_width=3)
medrange = mean_vals.max()-mean_vals.min()
yrange = Range1d(start=mean_vals.min()-0.2*medrange,
end=mean_vals.max()+0.2*medrange,)
plot.y_range = yrange
plot.add_tools(HoverTool(tooltips=TIPS))
script, div = components(plot)
return (script, div)
def interactive_chart(id=0, promember=0, intervaldata={}):
+2 -1
View File
@@ -2,7 +2,7 @@ from django.shortcuts import redirect
from django.http import HttpResponse
from django.contrib import messages
from rowers.mytypes import otwtypes
from rowers.tasks import handle_sendemail_expired
#from rowers.tasks import handle_sendemail_expired
from django.utils import timezone
from rowers.models import Workout, PowerTimeFitnessMetric, Rower, PaidPlan
import datetime
@@ -99,6 +99,7 @@ class RowerPlanMiddleWare(object):
# remove from Free Coach groups
# send email
from rowers.tasks import handle_sendemail_expired
_ = myqueue(queue,
handle_sendemail_expired,
r.user.email,
+2 -1
View File
@@ -553,8 +553,9 @@ def polygon_coord_center(polygon):
return latitudes.mean(), longitudes.mean()
def polygon_to_path(polygon):
def polygon_to_path(polygon, debug=False):
points = GeoPoint.objects.filter(polygon=polygon).order_by("order_in_poly")
s = []
for point in points:
s.append([point.latitude, point.longitude])
+169 -217
View File
@@ -1,3 +1,23 @@
import os
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
from YamJam import yamjam
CFG = yamjam()['rowsandallapp']
try:
os.environ.setdefault("DJANGO_SETTINGS_MODULE",CFG['settings_name'])
except KeyError: # pragma: no cover
os.environ.setdefault("DJANGO_SETTINGS_MODULE","rowsandall_app.settings")
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
from rowers.models import (
Workout, GeoPolygon, GeoPoint, GeoCourse,
VirtualRaceResult, CourseTestResult, Rower,
GraphImage,
)
import math
from rowers.courseutils import (
coursetime_paths, coursetime_first, time_in_path,
@@ -16,8 +36,7 @@ import rowers.longtask as longtask
import requests
import rowers.datautils as datautils
""" Background tasks done by Celery (develop) or QR (production) """
import os
""" Background tasks done by QR (production) """
import time
import gc
import gzip
@@ -27,6 +46,7 @@ import re
import sys
import json
import traceback
from time import strftime
from scipy import optimize
from scipy.signal import savgol_filter
@@ -36,8 +56,8 @@ import rowingdata
from rowingdata import make_cumvalues
from uuid import uuid4
from rowingdata import rowingdata as rdata
from datetime import timedelta
from sqlalchemy import create_engine
from rowers.celery import app
from celery import shared_task
@@ -61,13 +81,15 @@ import rowers.otw_power_calculator_pb2_grpc as calculator_pb2_grpc
import rowers.rowing_workout_metrics_pb2 as metrics_pb2
import rowers.rowing_workout_metrics_pb2_grpc as metrics_pb2_grpc
from rowsandall_app.settings import SITE_URL
from rowsandall_app.settings_dev import SITE_URL as SITE_URL_DEV
from rowsandall_app.settings import PROGRESS_CACHE_SECRET
from rowsandall_app.settings import SETTINGS_NAME
from rowsandall_app.settings import workoutemailbox
from rowsandall_app.settings import UPLOAD_SERVICE_SECRET, UPLOAD_SERVICE_URL
from rowsandall_app.settings import NK_API_LOCATION
from django.conf import settings
SITE_URL = settings.SITE_URL
SITE_URL_DEV = settings.SITE_URL
PROGRESS_CACHE_SECRET = settings.PROGRESS_CACHE_SECRET
SETTINGS_NAME = settings.SETTINGS_NAME
UPLOAD_SERVICE_URL = settings.UPLOAD_SERVICE_URL
UPLOAD_SERVICE_SECRET = settings.UPLOAD_SERVICE_SECRET
NK_API_LOCATION = settings.NK_API_LOCATION
from requests_oauthlib import OAuth1, OAuth1Session
@@ -83,16 +105,19 @@ from rowers.emails import htmlstrip
from rowers import mytypes
from rowers.dataprepnodjango import (
from rowers.dataroutines import (
getsmallrowdata_db, updatecpdata_sql, update_c2id_sql,
update_workout_field_sql,
#update_workout_field_sql,
update_agegroup_db, update_strokedata,
add_c2_stroke_data_db, totaltime_sec_to_string,
create_c2_stroke_data_db, update_empower,
database_url_debug, database_url, dataprep,
# database_url_debug,
database_url, dataprep,
# create_strava_stroke_data_db
)
database_url_debug = database_url
from rowers.opaque import encoder
@@ -274,10 +299,48 @@ def summaryfromsplitdata(splitdata, data, filename, sep='|', workouttype='rower'
return sums, sa, results
@app.task
def instroke_static(w, metric, debug=False, **kwargs):
f1 = w.csvfilename[6:-4]
rowdata = rdata(csvfile=w.csvfilename)
timestr = strftime("%Y%m%d-%H%M%S")
imagename = f1+timestr+'.png'
fullpathimagename = 'static/plots/'+imagename
r = w.user
fig1 = rowdata.get_plot_instroke(metric)
canvas = FigureCanvas(fig1)
canvas.print_figure('static/plots/'+imagename)
plt.close(fig1)
fig1.clf()
try:
width, height = Image.open(fullpathimagename).size
except:
width = 1200
height = 600
imgs = GraphImage.objects.filter(workout=w)
if imgs.count() < 7:
i = GraphImage(workout=w,
creationdatetime=timezone.now(),
filename=fullpathimagename,
width=width, height=height)
i.save()
else:
return 0
return 1
@app.task
def handle_request_post(url, data, debug=False, **kwargs): # pragma: no cover
response = requests.post(url, data)
if 'localhost' in url:
url = 'http'+url[5:]
response = requests.post(url, data, verify=False)
dologging('upload_api.log', data)
dologging('upload_api.log', response.status_code)
return response.status_code
@@ -297,10 +360,11 @@ def handle_c2_sync(workoutid, url, headers, data, debug=False, **kwargs):
s = response.json()
c2id = s['data']['id']
res = update_workout_field_sql(
workoutid, 'uploadedtoc2', c2id, debug=debug)
workout = Workout.objects.get(id=workoutid)
workout.uploadedtoc2 = c2id
workout.save()
return res
return 1
@app.task
@@ -316,8 +380,10 @@ def handle_sporttracks_sync(workoutid, url, headers, data, debug=False, **kwargs
id = int(m)
_ = update_workout_field_sql(
workoutid, 'uploadedtosporttracks', id, debug=debug)
workout = Workout.objects.get(id=workoutid)
workout.uploadedtosporttracks = id
workout.save()
return 1
@@ -338,7 +404,7 @@ def handle_strava_sync(stravatoken, workoutid, filename, name, activity_type, de
tb = traceback.format_exc()
dologging('strava_fail.log', tb)
failed = True
except stravalib.exc.TimeoutExceeded:
except stravalib.exc.TimeoutExceeded: # pragma: no cover
dologging('strava_fail.log', 'Strava upload failed for Workout {id} TimeOutExceeded'.format(
id=workoutid))
tb = traceback.format_exc()
@@ -378,8 +444,9 @@ def handle_strava_sync(stravatoken, workoutid, filename, name, activity_type, de
failed = True
if not failed:
_ = update_workout_field_sql(
workoutid, 'uploadedtostrava', res.id, debug=debug)
workout = Workout.objects.get(id=workoutid)
workout.uploadedtostrava = res.id
workout.save()
try:
act = client.update_activity(res.id, activity_type=activity_type,
description=description, device_name='Rowsandall.com')
@@ -507,37 +574,14 @@ def getagegrouprecord(age, sex='male', weightcategory='hwt',
return power
def polygon_to_path(polygon, debug=True):
pid = polygon[0]
query = "SELECT id, latitude, longitude FROM rowers_geopoint WHERE polygon_id = {pid}"\
" ORDER BY order_in_poly ASC".format(
pid=pid)
if debug:
engine = create_engine(database_url_debug, echo=False)
else: # pragma: no cover
engine = create_engine(database_url, echo=False)
with engine.connect() as conn, conn.begin():
result = conn.execute(query)
points = result.fetchall()
conn.close()
engine.dispose()
s = []
for point in points:
s.append([point[1], point[2]])
p = path.Path(s[:-1])
return p
from rowers.models import polygon_to_path
@app.task(bind=True)
def handle_check_race_course(self,
f1, workoutid, courseid,
recordid, useremail, userfirstname,
**kwargs): # pragma: no cover
**kwargs):
logfile = 'courselog_{workoutid}_{courseid}.log'.format(
workoutid=workoutid, courseid=courseid)
@@ -567,11 +611,11 @@ def handle_check_race_course(self,
mode = kwargs['mode']
summary = False
if 'summary' in kwargs:
if 'summary' in kwargs: # pragma: no cover
summary = kwargs['summary']
successemail = False
if 'successemail' in kwargs:
if 'successemail' in kwargs: # pragma: no cover
successemail = kwargs['successemail']
try:
@@ -606,35 +650,21 @@ def handle_check_race_course(self,
rowdata.fillna(method='backfill', inplace=True)
rowdata['time'] = rowdata['time']-rowdata.loc[0, 'time']
rowdata = rowdata[rowdata['time'] > splitsecond]
rowdata.loc[:, 'time'] = rowdata.loc[:, 'time'].copy()-rowdata.loc[0, 'time']
rowdata = rowdata.copy()[rowdata['time'] > splitsecond]
# we may want to expand the time (interpolate)
rowdata['dt'] = rowdata['time'].apply(
rowdata.loc[:,'dt'] = rowdata['time'].apply(
lambda x: safetimedelta(x)
)
).values
rowdata = rowdata.resample('100ms', on='dt').mean()
rowdata = rowdata.interpolate()
# initiate database engine
course = GeoCourse.objects.get(id=courseid)
polygons = course.polygons.all()
if debug: # pragma: no cover
engine = create_engine(database_url_debug, echo=False)
else:
engine = create_engine(database_url, echo=False)
# get polygons
query = "SELECT id,name FROM rowers_geopolygon WHERE course_id = {courseid} ORDER BY order_in_course ASC".format(
courseid=courseid
)
with engine.connect() as conn, conn.begin():
result = conn.execute(query)
polygons = result.fetchall()
conn.close()
engine.dispose()
paths = []
for polygon in polygons:
@@ -649,7 +679,7 @@ def handle_check_race_course(self,
try:
entrytimes, entrydistances = time_in_path(rowdata, paths[0], maxmin='max', getall=True,
name=polygons[0].name, logfile=logfile)
except AttributeError: # for testing
except AttributeError: # pragma: no cover
entrytimes, entrydistances = time_in_path(rowdata, paths[0], maxmin='max', getall=True,
name='Start', logfile=logfile)
with open(logfile, 'ab') as f:
@@ -718,7 +748,8 @@ def handle_check_race_course(self,
'endsecond': endseconds,
})
records = records[records['coursecompleted'] is True]
#records = records[records['coursecompleted'] is True]
records = records.loc[records['coursecompleted'], : ]
if len(records):
coursecompleted = True
@@ -736,35 +767,29 @@ def handle_check_race_course(self,
coursedistance = coursemeters
velo = coursedistance/coursetimeseconds
points = 100*(2.-referencespeed/velo)
query = 'UPDATE rowers_virtualraceresult SET coursecompleted = 1,'\
' duration = "{duration}", distance = {distance},'\
' workoutid = {workoutid}, startsecond = {startsecond},'\
' endsecond = {endsecond}, points={points} WHERE id={recordid}'.format(
recordid=recordid,
duration=totaltime_sec_to_string(coursetimeseconds),
distance=int(coursemeters),
points=points,
workoutid=workoutid,
startsecond=startsecond,
endsecond=endsecond,)
if mode == 'coursetest':
query = 'UPDATE rowers_coursetestresult SET coursecompleted = 1,'\
' duration = "{duration}", distance = {distance},'\
' workoutid = {workoutid}, startsecond = {startsecond},'\
' endsecond = {endsecond}, points={points} WHERE id={recordid}'.format(
recordid=recordid,
duration=totaltime_sec_to_string(coursetimeseconds),
distance=int(coursemeters),
points=points,
workoutid=workoutid,
startsecond=startsecond,
endsecond=endsecond,)
if mode != 'coursetest':
record = VirtualRaceResult.objects.get(id=recordid)
record.duration = totaltime_sec_to_string(coursetimeseconds)
record.distance=int(coursemeters)
record.points = points
record.startsecond = startsecond
record.endsecond = endsecond
record.workoutid = workoutid
record.coursecompleted = 1
record.save()
with engine.connect() as conn, conn.begin():
result = conn.execute(query)
else: # pragma: no cover
record = CourseTestResult.objects.get(id=recordid)
record.duration = totaltime_sec_to_string(coursetimeseconds)
record.distance = int(coursemeters)
record.workoutid = workoutid
record.startsecond = startsecond
record.endsecond = endsecond
record.points = points
record.save()
if summary:
if summary: # pragma: no cover
try:
row = rdata(csvfile=f1)
except IOError: # pragma: no cover
@@ -784,17 +809,12 @@ def handle_check_race_course(self,
summary = row.allstats()
row.write_csv(f1, gzip=True)
workout = Workout.objects.get(id=workoutid)
workout.summary = summary
workout.save()
query = "UPDATE `rowers_workout` SET `summary` = '%s' WHERE `id` = %s" % (
summary, workoutid)
with engine.connect() as conn, conn.begin():
result = conn.execute(query)
conn.close()
engine.dispose()
if successemail:
if successemail: # pragma: no cover
handle_sendemail_coursesucceed(
useremail, userfirstname, logfile, workoutid
)
@@ -804,36 +824,26 @@ def handle_check_race_course(self,
return 1
else: # pragma: no cover
query = 'UPDATE rowers_virtualraceresult SET coursecompleted = 0,'\
' duration = "{duration}", distance = {distance},'\
' workoutid = {workoutid}, startsecond = {startsecond},'\
' endsecond = {endsecond}, points={points} WHERE id={recordid}'.format(
recordid=recordid,
duration=totaltime_sec_to_string(0),
distance=0,
points=0.0,
workoutid=workoutid,
startsecond=startsecond,
endsecond=endsecond,)
record = VirtualRaceResult.object.get(id=recordid)
record.duration = totaltime_sec_to_string(0)
record.distance = 0
record.workoutid = workoutid
record.startsecond = startsecond
record.endsecond = endsecond
record.points = 0
record.save()
if mode == 'coursetest':
query = 'UPDATE rowers_coursetestresult SET coursecompleted = 0,'\
' duration = "{duration}", distance = {distance}, workoutid = {workoutid}'\
', startsecond = {startsecond}, endsecond = {endsecond}'\
', points={points} WHERE id={recordid}'.format(
recordid=recordid,
duration=totaltime_sec_to_string(0),
distance=0,
points=0,
workoutid=workoutid,
startsecond=startsecond,
endsecond=endsecond,)
record = CourseTestResult.objects.get(id=recordid)
record.duration = totaltime_sec_to_string(0)
record.distance = 0
record.workoutid = workoutid
record.startsecond = startsecond
record.endsecond = endsecond
record.points = 0
record.save()
with engine.connect() as conn, conn.begin():
result = conn.execute(query)
conn.close()
engine.dispose()
# add times for all gates to log file
with open(logfile, 'ab') as f:
@@ -1086,10 +1096,7 @@ def handle_calctrimp(id,
hrmax,
hrmin,
debug=False, **kwargs):
if debug: # pragma: no cover
engine = create_engine(database_url_debug, echo=False)
else:
engine = create_engine(database_url, echo=False)
tss = 0
normp = 0
@@ -1178,21 +1185,14 @@ def handle_calctrimp(id,
if hrtss > 1000: # pragma: no cover
hrtss = 0
query = 'UPDATE rowers_workout SET rscore = {tss},'\
' normp = {normp}, trimp={trimp}, hrtss={hrtss},'\
' normv={normv}, normw={normw} WHERE id={id}'.format(
tss=int(tss),
normp=int(normp),
trimp=int(trimp),
hrtss=int(hrtss),
normv=normv,
normw=normw,
id=id,)
with engine.connect() as conn, conn.begin():
_ = conn.execute(query)
conn.close()
engine.dispose()
workout = Workout.objects.get(id=id)
workout.tss = int(tss)
workout.normp = int(normp)
workout.trimp = int(trimp)
workout.hrtss = int(hrtss)
workout.normv = normv
workout.normw = normw
workout.save()
return 1
@@ -1974,7 +1974,7 @@ def handle_sendemail_ical(first_name, last_name, email, url, icsfile, **kwargs):
try:
os.remove(icsfile)
except:
except: # pragma: no cover
pass
return 1
@@ -2834,23 +2834,13 @@ def handle_update_wps(rid, types, ids, mode, debug=False, **kwargs):
except ValueError: # pragma: no cover
return 0
rower = Rower.objects.get(id=rid)
if mode == 'water':
query = "UPDATE `rowers_rower` SET `median_wps` = '%s' WHERE `id` = '%s'" % (
wps_median, rid)
rower.median_wps = wps_median
else:
query = "UPDATE `rowers_rower` SET `median_wps_erg` = '%s' WHERE `id` = '%s'" % (
wps_median, rid)
rower.median_wps_erg = wps_median
if debug: # pragma: no cover
engine = create_engine(database_url_debug, echo=False)
else:
engine = create_engine(database_url, echo=False)
with engine.connect() as conn, conn.begin():
_ = conn.execute(query)
conn.close()
engine.dispose()
rower.save()
return wps_median
@@ -2999,23 +2989,8 @@ def handle_nk_async_workout(alldata, userid, nktoken, nkid, delaysec, defaulttim
workoutid, error = add_workout_from_data(userid, nkid, data, df)
# dologging('nklog.log','NK Workout ID {id}'.format(id=workoutid))
if debug: # pragma: no cover
engine = create_engine(database_url_debug, echo=False)
else:
engine = create_engine(database_url, echo=False)
query = 'SELECT uploadedtonk from rowers_workout WHERE id ={workoutid}'.format(
workoutid=workoutid)
newnkid = 0
with engine.connect() as conn, conn.begin():
result = conn.execute(query)
tdata = result.fetchall()
if tdata:
newnkid = tdata[0][0]
conn.close()
workout = Workout.objects.get(id=workoutid)
newnkid = workout.uploadedtonk
parkedids = []
try:
@@ -3303,22 +3278,8 @@ def handle_c2_async_workout(alldata, userid, c2token, c2id, delaysec, defaulttim
workoutid = response.json()['id']
if debug: # pragma: no cover
engine = create_engine(database_url_debug, echo=False)
else:
engine = create_engine(database_url, echo=False)
query = 'SELECT uploadedtoc2 from rowers_workout WHERE id ={workoutid}'.format(
workoutid=workoutid)
newc2id = 0
with engine.connect() as conn, conn.begin():
result = conn.execute(query)
tdata = result.fetchall()
if tdata: # pragma: no cover
newc2id = tdata[0][0]
conn.close()
workout = Workout.objects.get(id=workoutid)
newc2id = workout.uploadedtoc2
parkedids = []
with open('c2blocked.json', 'r') as c2blocked:
@@ -3335,14 +3296,10 @@ def handle_c2_async_workout(alldata, userid, c2token, c2id, delaysec, defaulttim
json.dump(tdata, c2blocked)
# set distance, time
query = "UPDATE `rowers_workout` SET `distance` = '%s', `duration` = '%s' WHERE `id` = '%s'" % (
distance, duration, workoutid)
with engine.connect() as conn, conn.begin():
result = conn.execute(query)
conn.close()
engine.dispose()
workout = Workout.objects.get(id=workoutid)
workout.distance = distance
workout.duration = duration
workout.save()
# summary
if 'workout' in data:
@@ -3359,14 +3316,9 @@ def handle_c2_async_workout(alldata, userid, c2token, c2id, delaysec, defaulttim
summary, sa, results = summaryfromsplitdata(
splitdata, data, csvfilename, workouttype=workouttype)
query = "UPDATE `rowers_workout` SET `summary` = '%s' WHERE `id` = %s" % (
summary, workoutid)
with engine.connect() as conn, conn.begin():
result = conn.execute(query)
conn.close()
engine.dispose()
workout = Workout.objects.get(id=workoutid)
workout.summary = summary
workout.save()
from rowingdata.trainingparser import getlist
if sa:
+5 -1
View File
@@ -42,14 +42,18 @@
{% if instrokemetrics %}
{% for metric in instrokemetrics %}
<p>
<a class="button blue small" href="/rowers/workout/{{ workout.id|encode }}/instroke/{{ metric }}/">{{ metric }}</a>
<a href="/rowers/workout/{{ workout.id|encode }}/instroke/{{ metric }}/">{{ metric }}</a>
</p>
{% endfor %}
<p>
<a href="/rowers/workout/{{ workout.id|encode }}/instroke/interactive/">NEW: Dynamic</a>
</p>
{% else %}
<p>Unfortunately, this workout doesn't have any in stroke metrics</p>
{% endif %}
</li>
</ul>
+161
View File
@@ -0,0 +1,161 @@
{% extends "newbase.html" %}
{% load static %}
{% load rowerfilters %}
{% block title %}Advanced Features {% endblock %}
{% block scripts %}
<script type='text/javascript'
src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'>
</script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>jQuery UI Slider - Range slider</title>
<link rel="stylesheet" href="//code.jquery.com/ui/1.13.2/themes/base/jquery-ui.css">
<link rel="stylesheet" href="/resources/demos/style.css">
<script src="https://code.jquery.com/jquery-3.6.0.js"></script>
<script src="https://code.jquery.com/ui/1.13.2/jquery-ui.js"></script>
<script>
function submit_form() {
console.log("form changed");
var frm = $("#instrokeform");
var data = new FormData(frm[0]);
$.ajax({
url:"/rowers/workout/{{ workout.id|encode }}/instroke/interactive/",
type: "POST",
contentType: false,
processData: false,
data: data,
dataType: 'json',
success: function(data) {
console.log(data);
$("#id_script").replaceWith('<div id="id_script">'+data.script+'</d'+'iv>');
$("#id_chart").replaceWith('<div id="id_chart">'+data.div+'</d'+'iv>');
$("#ds").replaceWith('<div id="ds">'+data.ds+'</d'+'iv>');
$("#dd").replaceWith('<div id="dd">'+data.dd+'</d'+'iv>');
}
});
}
$(document).ready(function() {
var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
function csrfSafeMethod(method) {
return(/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
$("#instrokeform").on('change', function(evt) {
submit_form();
});
});
</script>
<script>
$( function() {
$( "#slider-range" ).slider({
range: true,
min: 0,
max: 60,
values: [ {{ spm_min }}, {{ spm_max }} ],
slide: function( event, ui ) {
$( "#amountspm" ).val(ui.values[ 0 ] + " - " + ui.values[ 1 ] );
$( "#id_spm_min" ).val( ui.values[ 0 ]);
$( "#id_spm_max" ).val(ui.values[ 1 ] );
}
});
$( "#amountspm" ).val($( "#slider-range" ).slider( "values", 0 ) +
" - " + $( "#slider-range" ).slider( "values", 1 ));
} );
</script>
<script>
$("#instrokeform").mouseup(function() {
submit_form();
});
</script>
<script>
$( function() {
console.log({{ activeminutesmin }}, {{ activeminutesmax}}, 'active range');
$( "#slider-timerange" ).slider({
range: true,
min: 0,
max: {{ maxminutes }},
values: [ {{ activeminutesmin }}, {{ activeminutesmax }} ],
slide: function( event, ui ) {
$( "#amount" ).val(ui.values[ 0 ] + " min - " + ui.values[ 1 ] + " min " );
$("#id_activeminutesmin").val(ui.values[0]);
$("#id_activeminutesmax").val(ui.values[1]);
}
});
$( "#amount" ).val($( "#slider-timerange" ).slider( "values", 0 ) +
" min - " + $( "#slider-timerange" ).slider( "values", 1 ) + " min ");
} );
</script>
{% endblock %}
{% block main %}
<script src="https://cdn.pydata.org/bokeh/release/bokeh-2.2.3.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
<div id="id_script">
{{ the_script |safe }}
</div>
<div id="ds">
{{ ds |safe }}
</div>
<h1>In Stroke Metrics</h1>
<ul class="main-content">
<li class="grid_4">
<p>This is an experimental prototype. It's behavior is not guaranteed. Response may be slow.
</li>
<li class="grid_4" >
<form id="instrokeform" enctype="multipart/form-data" action="" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<div id="slider-range"></div>
<p>
<label for="amountspm">Active Range:</label>
<input type="text" id="amountspm" readonly style="border:0; color:#1c75bc; font-weight:bold;">
</p>
<div id="slider-timerange"></div>
<p>
<label for="amount">Active Range:</label>
<input type="text" id="amount" readonly style="border:0; color:#1c75bc; font-weight:bold;">
</p>
<input name='form' class="button" type="submit" value="Submit">
</form>
</li>
<li class="grid_4">
<div id="id_chart" class="flexplot">
{{ the_div|safe }}
</div>
</li>
<li class="grid_4">
<div id="dd" class="flexplot">
{{ dd|safe }}
</div>
</li>
</ul>
{% endblock %}
{% block sidebar %}
{% include 'menu_workout.html' %}
{% endblock %}
+16
View File
@@ -95,19 +95,35 @@
<div>
{% if workout.name != '' %}
<h3>
{% if team %}
<a
href={% url rower.defaultlandingpage3 id=workout.id|encode %}
title="{{ rower.defaultlandingpage3|verbose }}">
{{ workout.name }} ({{ workout.user.user.first_name }} {{ workout.user.user.last_name}})
</a>
{% else %}
<a
href={% url rower.defaultlandingpage3 id=workout.id|encode %}
title="{{ rower.defaultlandingpage3|verbose }}">
{{ workout.name }}
</a>
{% endif %}
</h3>
{% else %}
<h3>
{% if team %}
<a
href={% url rower.defaultlandingpage id=workout.id|encode %}
title="{{ rower.defaultlandingpage|verbose }}">
No Name ({{ workout.user.user.first_name }} {{ workout.user.user.last_name}})
</a>
{% else %}
<a
href={% url rower.defaultlandingpage id=workout.id|encode %}
title="{{ rower.defaultlandingpage|verbose }}">
No Name
</a>
{% endif %}
</h3>
{% endif %}
</div>
+10 -7
View File
@@ -81,10 +81,13 @@ class AsyncTaskTests(TestCase):
def test_polygons(self):
polygons = GeoPolygon.objects.all()
polygon = polygons[0]
obj = (polygon.id,polygon.name)
path = tasks.polygon_to_path(obj)
self.assertEqual(len(path),4)
#obj = (polygon.id,polygon.name)
path = tasks.polygon_to_path(polygon)
self.assertEqual(len(path),6)
def test_summaryfromsplitdata(self):
splitdata = [
@@ -495,21 +498,21 @@ class AsyncTaskTests(TestCase):
self.assertEqual(res,1)
@patch('rowers.dataprepnodjango.create_engine')
@patch('rowers.dataprep.create_engine')
def test_handle_updateergcp(self,mocked_sqlalchemy):
f1 = get_random_file()['filename']
res = tasks.handle_updateergcp(1,[f1])
self.assertEqual(res,1)
@patch('rowers.dataprepnodjango.getsmallrowdata_db')
@patch('rowers.dataprep.getsmallrowdata_db')
def test_handle_updatecp(self,mocked_getsmallrowdata_db_updatecp):
rower_id = 1
workoutids = [1]
res = tasks.handle_updatecp(rower_id,workoutids)
self.assertEqual(res,1)
@patch('rowers.dataprepnodjango.getsmallrowdata_db')
@patch('rowers.dataprep.getsmallrowdata_db')
def test_handle_setcp(self,mocked_getsmallrowdata_db_setcp):
strokesdf = pd.read_csv('rowers/tests/testdata/uhfull.csv')
filename = 'rowers/tests/testdata/temp/pq.gz'
@@ -521,7 +524,7 @@ class AsyncTaskTests(TestCase):
except FileNotFoundError:
pass
@patch('rowers.dataprepnodjango.getsmallrowdata_db')
@patch('rowers.dataprep.getsmallrowdata_db')
def test_handle_update_wps(self,mocked_getsmallrowdata_db_wps):
ids = [1,2,3]
+1 -1
View File
@@ -130,7 +130,7 @@ class CPChartTest(TestCase):
# add some tests of complex form data (no hr, no spm, zero spm, etc)
@patch('rowers.dataprepnodjango.create_engine')
@patch('rowers.dataprep.create_engine')
def test_agerecords(self, mock_sqlalchemy):
# update_records(url='rowers/tests/c2worldrecords.html',verbose=False)
+96
View File
@@ -1554,6 +1554,36 @@ description: ""
self.instantplan.save()
self.startdate = (datetime.datetime.now()-datetime.timedelta(days=1)).date()
self.enddate = (datetime.datetime.now()+datetime.timedelta(days=1)).date()
self.preferreddate = datetime.datetime.now().date()
self.ps = SessionFactory(startdate=self.startdate,enddate=self.enddate,
sessiontype='session',
sessionmode = 'time',
criterium = 'none',
sessionvalue = 60,
sessionunit='min',
preferreddate=self.preferreddate,
manager=self.u,
interval_string = '5x(800m/5min)'
)
self.ps.save()
result = plannedsessions.add_rower_session(self.r,self.ps)
self.step = PlannedSessionStep(
manager = self.u,
name = 'cd',
durationvalue = '50000',
durationtype = 'Distance',
)
self.step.save()
def tearDown(self):
@@ -1562,6 +1592,72 @@ description: ""
except (IOError, FileNotFoundError,OSError):
pass
def test_stepadder(self):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
url = '/rowers/plans/stepeditor/{id}/'.format(id=self.ps.id)
response = self.c.get(url,follow=True)
self.assertEqual(response.status_code,200)
url = '/rowers/plans/stepadder/{id}/'.format(id=self.ps.id)
bdy = json.dumps([self.step.id])
response = self.c.post(url, bdy, content_type='application/json',
**{'HTTP_X_REQUESTED_WITH':'XMLHttpRequest'})
self.assertEqual(response.status_code, 200)
def test_stepdelete(self):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
url = '/rowers/plans/step/{id}/delete'.format(id=self.step.id)
response = self.c.get(url,follow=True)
self.assertEqual(response.status_code,200)
def test_stepedit(self):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
url = '/rowers/plans/step/{id}/edit/{psid}/'.format(
id=self.step.id, psid=self.ps.id
)
response = self.c.get(url)
self.assertEqual(response.status_code,200)
data = {
'durationtype': 'RepeatUntilStepsCmplt',
'durationvalue': '0.0',
#'targettype': None,
'targetvalue': '8',
'targetvaluelow': '0',
'targetvaluehigh': '0',
'intensity': 'Active',
'description': 'aap'
}
form = StepEditorForm(data)
self.assertTrue(form.is_valid())
reponse = self.c.post(url, data)
self.assertTrue(response.status_code,200)
def test_save_plan_yaml_view(self):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
url = '/rowers/sessions/saveasplan/?when={s}/{e}'.format(
s = self.startdate.strftime("%Y-%m-%d"),
e = self.enddate.strftime("%Y-%m-%d")
)
response = self.c.get(url,follow=True)
self.assertEqual(response.status_code,200)
def test_clone_view(self):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
+1 -3
View File
@@ -888,8 +888,7 @@ class ChallengesTest(TestCase):
self.assertEqual(response.status_code, 200)
@patch('rowers.tasks.create_engine', side_effect=mocked_sqlalchemy_courses)
def notest_virtualevent_check_view(self,mocked_sqlalchemy_courses):
def test_virtualevent_check_view(self):
res = tasks.handle_check_race_course(
self.wthyro.csvfilename,
@@ -898,7 +897,6 @@ class ChallengesTest(TestCase):
self.result.id,
self.wthyro.user.user.email,
self.wthyro.user.user.first_name,
mode='coursetest',
)
self.assertEqual(res,1)
+2
View File
@@ -249,6 +249,8 @@ urlpatterns = [
path('404/', TemplateView.as_view(template_name='404.html'), name='404'),
path('400/', TemplateView.as_view(template_name='400.html'), name='400'),
path('403/', TemplateView.as_view(template_name='403.html'), name='403'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/instroke/interactive/$',
views.instroke_chart_interactive, name='instroke_chart_interactive'),
re_path(r'^exportallworkouts/?/$', views.workouts_summaries_email_view,
name='workouts_summaries_email_view'),
path('failedjobs/', views.failed_queue_view, name='failed_queue_view'),
+1 -1
View File
@@ -515,7 +515,7 @@ def rower_process_garmincallback(request): # pragma: no cover
# Process Rojabo callback
@login_required()
def rower_process_rojabocallback(request): # prgrma: no cover
def rower_process_rojabocallback(request): # pragma: no cover
# do stuff
try:
code = request.GET.get('code', None)
+24 -18
View File
@@ -1409,7 +1409,7 @@ def save_plan_yaml(request, userid=0):
steps = ps.steps
steps['filename'] = ""
workouts.append(steps)
else:
else: # pragma: no cover
if ps.sessionmode == 'distance':
ps.interval_string = '{d}m'.format(d=ps.sessionvalue)
elif ps.sessionmode == 'time':
@@ -2994,7 +2994,8 @@ def rower_create_trainingplan(request, id=0):
redirect_field_name=None)
def stepadder(request, id=0):
is_ajax = request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
if not is_ajax:
if not is_ajax: # pragma: no cover
return JSONResponse(
status=403, data={
'status': 'false',
@@ -3005,7 +3006,7 @@ def stepadder(request, id=0):
is_save = request.GET.get('save',0)
if request.method != 'POST':
if request.method != 'POST': # pragma: no cover
message = {'status': 'false',
'message': 'this view cannot be accessed through GET'}
return JSONResponse(status=403, data=message)
@@ -3013,14 +3014,16 @@ def stepadder(request, id=0):
try:
json_data = json.loads(request.body)
post_data = json_data
except (KeyError, JSONDecodeError):
except (KeyError, JSONDecodeError): # pragma: no cover
q = request.POST
post_data = {k: q.getlist(k) if len(
q.getlist(k)) > 1 else v for k, v in q.items()}
# only allow local host
hostt = request.get_host().split(':')
if hostt[0] not in ['localhost', '127.0.0.1', 'dev.rowsandall.com', 'rowsandall.com']:
if hostt[0] not in ['localhost', '127.0.0.1', 'dev.rowsandall.com', 'rowsandall.com',
'testserver']: # pragma: no cover
message = {'status': 'false',
'message': 'permission denied for host '+hostt[0]}
return JSONResponse(status=403, data=message)
@@ -3028,7 +3031,7 @@ def stepadder(request, id=0):
if ps.steps:
filename = ps.steps.get('filename','')
sport = ps.steps.get('sport','rowing')
else:
else: # pragma: no cover
filename = ''
sport = 'rowing'
@@ -3045,10 +3048,10 @@ def stepadder(request, id=0):
d = step.asdict()
d['stepId'] = nr
steps['steps'].append(d)
except PlannedSessionStep.DoesNotExist:
except PlannedSessionStep.DoesNotExist: # pragma: no cover
pass
if is_save:
if is_save: # pragma: no cover
# save the darn thing
ps.steps = steps
@@ -3066,9 +3069,12 @@ def stepdelete(request, id=0):
step.delete()
backid = request.GET.get('id')
backid = request.GET.get('id',0)
if backid: # pragma: no cover
url = reverse(stepeditor,kwargs={'id':backid})
else:
url = reverse('plannedsessions_view')
return HttpResponseRedirect(url)
@@ -3079,7 +3085,7 @@ def stepedit(request, id=0, psid=0):
step = get_object_or_404(PlannedSessionStep, pk=id)
try:
ps = PlannedSession.objects.get(id=psid)
except PlannedSession.DoesNotExist:
except PlannedSession.DoesNotExist: # pragma: no cover
ps = None
form = StepEditorForm(instance=step)
@@ -3094,7 +3100,7 @@ def stepedit(request, id=0, psid=0):
ee = ss.copy()
ee.pop('stepId')
if (dd == ee):
if (dd == ee): # pragma: no cover
ss['durationType'] = form.cleaned_data['durationtype']
ss['durationValue'] = form.cleaned_data['durationvalue']
ss['targetType'] = form.cleaned_data['targettype']
@@ -3126,15 +3132,15 @@ def stepedit(request, id=0, psid=0):
step.name = form.cleaned_data['name']
step.description = form.cleaned_data['description']
if step.durationtype == 'Time':
if step.durationtype == 'Time': # pragma: no cover
step.durationvalue *= 60000
elif step.durationtype == 'Distance':
elif step.durationtype == 'Distance': # pragma: no cover
step.durationvalue *= 100
step.save()
if step.durationtype == 'Time':
if step.durationtype == 'Time': # pragma: no cover
form.fields['durationvalue'].initial = step.durationvalue / 60000
elif step.durationtype == 'Distance':
form.fields['durationvalue'].initial = step.durationvalue / 100
@@ -3143,7 +3149,7 @@ def stepedit(request, id=0, psid=0):
stepdescription = step_to_string(step.asdict(), short=False)[0]
if request.method == 'POST':
if 'stepsave_and_return' in request.POST:
if 'stepsave_and_return' in request.POST: # pragma: no cover
url = reverse('stepeditor',kwargs = {'id': ps.id})
return HttpResponseRedirect(url)
@@ -3204,7 +3210,7 @@ def stepeditor(request, id=0):
targetvaluehigh = targetvaluehigh,
intensity = intensity,
)
if not archived_steps.count() and durationvalue != 0:
if not archived_steps.count():
s = PlannedSessionStep(
manager = request.user,
durationtype = durationtype,
@@ -3217,14 +3223,14 @@ def stepeditor(request, id=0):
name = step.get('wkt_step_name','Step')
)
s.save()
else:
else: # pragma: no cover
s = archived_steps[0]
currentsteps.append(s)
form = StepEditorForm()
if request.method == 'POST':
if request.method == 'POST': # pragma: no cover
form = StepEditorForm(request.POST)
if form.is_valid():
step = form.save(commit=False)
+2 -1
View File
@@ -110,7 +110,7 @@ from rowers.forms import (
VideoAnalysisMetricsForm, SurveyForm, HistorySelectForm,
StravaChartForm, FitnessFitForm, PerformanceManagerForm,
TrainingPlanBillingForm, InstantPlanSelectForm,
TrainingZonesForm,
TrainingZonesForm, InstrokeForm
)
from django.urls import reverse, reverse_lazy
@@ -239,6 +239,7 @@ from rowers.rows import handle_uploaded_file, handle_uploaded_image
from rowers.plannedsessions import *
from rowers.tasks import handle_makeplot, handle_otwsetpower, handle_sendemailtcx, handle_sendemailcsv
from rowers.tasks import (
instroke_static,
fetch_rojabo_session,
handle_sendemail_unrecognized, handle_sendemailnewcomment,
handle_request_post,
+127 -32
View File
@@ -2904,36 +2904,8 @@ def instroke_chart(request, id=0, metric=''): # pragma: no cover
instrokemetrics = rowdata.get_instroke_columns()
if metric in instrokemetrics:
f1 = w.csvfilename[6:-4]
timestr = strftime("%Y%m%d-%H%M%S")
imagename = f1+timestr+'.png'
fullpathimagename = 'static/plots/'+imagename
u = w.user.user
r = getrower(u)
fig1 = rowdata.get_plot_instroke(metric)
canvas = FigureCanvas(fig1)
canvas.print_figure('static/plots/'+imagename)
plt.close(fig1)
fig1.clf()
gc.collect()
try:
width, height = Image.open(fullpathimagename).size
except:
width = 1200
height = 600
imgs = GraphImage.objects.filter(workout=w)
if imgs.count() < 7:
i = GraphImage(workout=w,
creationdatetime=timezone.now(),
filename=fullpathimagename,
width=width, height=height)
i.save()
else:
messages.error(
request, 'You have reached the maximum number of static images for this workout. Delete an image first')
job = myqueue(queuelow,
instroke_static,w, metric)
r = getrower(request.user)
url = reverse(r.defaultlandingpage,
@@ -2943,9 +2915,132 @@ def instroke_chart(request, id=0, metric=''): # pragma: no cover
return HttpResponseRedirect(url)
@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True)
def instroke_chart_interactive(request, id=0):
is_ajax = request_is_ajax(request)
w = get_workoutuser(id, request)
rowdata = rrdata(csvfile=w.csvfilename)
instrokemetrics = rowdata.get_instroke_columns()
form = InstrokeForm(choices=instrokemetrics)
script = ''
div = get_call()
metric = instrokemetrics[0]
spm_min = 15
spm_max = 45
activeminutesmax = int(rowdata.duration/60.)
activeminutesmin = 0
maxminutes = activeminutesmax
individual_curves = False
if request.method == 'POST':
form = InstrokeForm(request.POST,choices=instrokemetrics)
if form.is_valid():
metric = form.cleaned_data['metric']
spm_min = form.cleaned_data['spm_min']
spm_max = form.cleaned_data['spm_max']
activeminutesmin = form.cleaned_data['activeminutesmin']
activeminutesmax = form.cleaned_data['activeminutesmax']
individual_curves = form.cleaned_data['individual_curves']
activesecondsmin = 60.*activeminutesmin
activesecondsmax = 60.*activeminutesmax
data = rowdata.get_instroke_data(
metric,
spm_min=spm_min,
spm_max=spm_max,
activeminutesmin=activeminutesmin,
activeminutesmax=activeminutesmax,
)
script, div = instroke_interactive_chart(
data, metric, w,
spm_min,
spm_max,
activeminutesmin,
activeminutesmax,
individual_curves,
)
# change to range spm_min to spm_max
vals, units, typ = rowdata.updateinterval_range(
' Cadence (stokes/min)', spm_min, spm_max,
debug=False, smoothwindow=2.,
activewindow=[activesecondsmin, activesecondsmax])
intervalstats = rowdata.allstats()
itime, idist, itype = rowdata.intervalstats_values()
intervaldata = {
'itime': itime,
'idist': idist,
'itype': itype,
'selector': '',
'normp': 0,
'normv': 0,
}
ds, dd = interactive_chart(encoder.decode_hex(
id), promember=1, intervaldata=intervaldata)
breadcrumbs = [
{
'url': '/rowers/list-workouts/',
'name': 'Workouts'
},
{
'url': get_workout_default_page(request, id),
'name': w.name
},
{
'url': reverse('instroke_view', kwargs={'id': id}),
'name': 'In-Stroke Metrics'
}
]
if is_ajax:
response = json.dumps({
'script': script,
'div': div,
'ds': ds,
'dd': dd,
})
return HttpResponse(response, content_type='application/json')
return render(request,
'instroke_interactive.html',
{
'workout': w,
'rower': w.user,
'active': 'nav-workouts',
'breadcrumbs': breadcrumbs,
'teams': get_my_teams(request.user),
'instrokemetrics': instrokemetrics,
'form':form,
'the_script': script,
'the_div': div,
'spm_min': spm_min,
'spm_max': spm_max,
'ds': ds,
'dd': dd,
'activeminutesmin': activeminutesmin,
'activeminutesmax': activeminutesmax,
'maxminutes': maxminutes,
})
# erase column
@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True)
def workout_erase_column_view(request, id=0, column=''):
r = getrower(request.user)