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): def f(x):
return coordinate_in_path(x['latitude'], x['longitude'], p) 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': if maxmin == 'max':
b = (~df['inpolygon']).shift(-1)+df['inpolygon'] b = (~inpolygon).shift(-1)+inpolygon
else: # pragma: no cover else: # pragma: no cover
b = (~df['inpolygon']).shift(1)+df['inpolygon'] b = (~inpolygon).shift(1)+inpolygon
if len(df[b == 2]): if len(df[b == 2]):
if logfile is not None: # pragma: no cover if logfile is not None: # pragma: no cover
@@ -90,7 +90,7 @@ def coursetime_first(data, paths, polygons=[], logfile=None):
try: try:
entrytime, entrydistance = time_in_path( 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 coursecompleted = True
except InvalidTrajectoryError: # pragma: no cover except InvalidTrajectoryError: # pragma: no cover
entrytime = data['time'].max() entrytime = data['time'].max()
@@ -118,7 +118,7 @@ def coursetime_paths(data, paths, finalmaxmin='min', polygons=[], logfile=None):
( (
entrytime, entrytime,
entrydistance 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 coursecompleted = True
except InvalidTrajectoryError: # pragma: no cover except InvalidTrajectoryError: # pragma: no cover
entrytime = data['time'].max() entrytime = data['time'].max()
@@ -129,7 +129,7 @@ def coursetime_paths(data, paths, finalmaxmin='min', polygons=[], logfile=None):
if len(paths) > 1: if len(paths) > 1:
try: try:
time, dist = time_in_path( 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 = data[data['time'] > time].copy()
data2['time'] = data2['time'].apply(lambda x: x-time) data2['time'] = data2['time'].apply(lambda x: x-time)
data2['cum_dist'] = data2['cum_dist'].apply(lambda x: x-dist) 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 == '': if settings.DEBUG or user == '':
database_url = 'sqlite:///db.sqlite3' database_url = 'sqlite:///db.sqlite3'
#database_name_dev = DEV_DATABASES['default']['NAME']
database_url_debug = database_url
+425 -2083
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( self.fields['enddate'].initial = timezone.now(
)+datetime.timedelta(days=instantplan.duration) )+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 # Video Analysis creation form
class VideoAnalysisCreateForm(forms.Form): class VideoAnalysisCreateForm(forms.Form):
+127
View File
@@ -4070,6 +4070,133 @@ def interactive_streamchart(id=0, promember=0):
return [script, div] 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={}): 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.http import HttpResponse
from django.contrib import messages from django.contrib import messages
from rowers.mytypes import otwtypes 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 django.utils import timezone
from rowers.models import Workout, PowerTimeFitnessMetric, Rower, PaidPlan from rowers.models import Workout, PowerTimeFitnessMetric, Rower, PaidPlan
import datetime import datetime
@@ -99,6 +99,7 @@ class RowerPlanMiddleWare(object):
# remove from Free Coach groups # remove from Free Coach groups
# send email # send email
from rowers.tasks import handle_sendemail_expired
_ = myqueue(queue, _ = myqueue(queue,
handle_sendemail_expired, handle_sendemail_expired,
r.user.email, r.user.email,
+2 -1
View File
@@ -553,8 +553,9 @@ def polygon_coord_center(polygon):
return latitudes.mean(), longitudes.mean() 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") points = GeoPoint.objects.filter(polygon=polygon).order_by("order_in_poly")
s = [] s = []
for point in points: for point in points:
s.append([point.latitude, point.longitude]) 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 import math
from rowers.courseutils import ( from rowers.courseutils import (
coursetime_paths, coursetime_first, time_in_path, coursetime_paths, coursetime_first, time_in_path,
@@ -16,8 +36,7 @@ import rowers.longtask as longtask
import requests import requests
import rowers.datautils as datautils import rowers.datautils as datautils
""" Background tasks done by Celery (develop) or QR (production) """ """ Background tasks done by QR (production) """
import os
import time import time
import gc import gc
import gzip import gzip
@@ -27,6 +46,7 @@ import re
import sys import sys
import json import json
import traceback import traceback
from time import strftime
from scipy import optimize from scipy import optimize
from scipy.signal import savgol_filter from scipy.signal import savgol_filter
@@ -36,8 +56,8 @@ import rowingdata
from rowingdata import make_cumvalues from rowingdata import make_cumvalues
from uuid import uuid4 from uuid import uuid4
from rowingdata import rowingdata as rdata from rowingdata import rowingdata as rdata
from datetime import timedelta from datetime import timedelta
from sqlalchemy import create_engine
from rowers.celery import app from rowers.celery import app
from celery import shared_task 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 as metrics_pb2
import rowers.rowing_workout_metrics_pb2_grpc as metrics_pb2_grpc import rowers.rowing_workout_metrics_pb2_grpc as metrics_pb2_grpc
from rowsandall_app.settings import SITE_URL from django.conf import settings
from rowsandall_app.settings_dev import SITE_URL as SITE_URL_DEV SITE_URL = settings.SITE_URL
from rowsandall_app.settings import PROGRESS_CACHE_SECRET SITE_URL_DEV = settings.SITE_URL
from rowsandall_app.settings import SETTINGS_NAME PROGRESS_CACHE_SECRET = settings.PROGRESS_CACHE_SECRET
from rowsandall_app.settings import workoutemailbox SETTINGS_NAME = settings.SETTINGS_NAME
from rowsandall_app.settings import UPLOAD_SERVICE_SECRET, UPLOAD_SERVICE_URL
from rowsandall_app.settings import NK_API_LOCATION 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 from requests_oauthlib import OAuth1, OAuth1Session
@@ -83,16 +105,19 @@ from rowers.emails import htmlstrip
from rowers import mytypes from rowers import mytypes
from rowers.dataprepnodjango import ( from rowers.dataroutines import (
getsmallrowdata_db, updatecpdata_sql, update_c2id_sql, getsmallrowdata_db, updatecpdata_sql, update_c2id_sql,
update_workout_field_sql, #update_workout_field_sql,
update_agegroup_db, update_strokedata, update_agegroup_db, update_strokedata,
add_c2_stroke_data_db, totaltime_sec_to_string, add_c2_stroke_data_db, totaltime_sec_to_string,
create_c2_stroke_data_db, update_empower, create_c2_stroke_data_db, update_empower,
database_url_debug, database_url, dataprep, # database_url_debug,
database_url, dataprep,
# create_strava_stroke_data_db # create_strava_stroke_data_db
) )
database_url_debug = database_url
from rowers.opaque import encoder from rowers.opaque import encoder
@@ -274,10 +299,48 @@ def summaryfromsplitdata(splitdata, data, filename, sep='|', workouttype='rower'
return sums, sa, results 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 @app.task
def handle_request_post(url, data, debug=False, **kwargs): # pragma: no cover 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', data)
dologging('upload_api.log', response.status_code) dologging('upload_api.log', response.status_code)
return 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() s = response.json()
c2id = s['data']['id'] c2id = s['data']['id']
res = update_workout_field_sql( workout = Workout.objects.get(id=workoutid)
workoutid, 'uploadedtoc2', c2id, debug=debug) workout.uploadedtoc2 = c2id
workout.save()
return res return 1
@app.task @app.task
@@ -316,8 +380,10 @@ def handle_sporttracks_sync(workoutid, url, headers, data, debug=False, **kwargs
id = int(m) id = int(m)
_ = update_workout_field_sql( workout = Workout.objects.get(id=workoutid)
workoutid, 'uploadedtosporttracks', id, debug=debug) workout.uploadedtosporttracks = id
workout.save()
return 1 return 1
@@ -338,7 +404,7 @@ def handle_strava_sync(stravatoken, workoutid, filename, name, activity_type, de
tb = traceback.format_exc() tb = traceback.format_exc()
dologging('strava_fail.log', tb) dologging('strava_fail.log', tb)
failed = True 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( dologging('strava_fail.log', 'Strava upload failed for Workout {id} TimeOutExceeded'.format(
id=workoutid)) id=workoutid))
tb = traceback.format_exc() tb = traceback.format_exc()
@@ -378,8 +444,9 @@ def handle_strava_sync(stravatoken, workoutid, filename, name, activity_type, de
failed = True failed = True
if not failed: if not failed:
_ = update_workout_field_sql( workout = Workout.objects.get(id=workoutid)
workoutid, 'uploadedtostrava', res.id, debug=debug) workout.uploadedtostrava = res.id
workout.save()
try: try:
act = client.update_activity(res.id, activity_type=activity_type, act = client.update_activity(res.id, activity_type=activity_type,
description=description, device_name='Rowsandall.com') description=description, device_name='Rowsandall.com')
@@ -507,37 +574,14 @@ def getagegrouprecord(age, sex='male', weightcategory='hwt',
return power return power
def polygon_to_path(polygon, debug=True): from rowers.models import polygon_to_path
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
@app.task(bind=True) @app.task(bind=True)
def handle_check_race_course(self, def handle_check_race_course(self,
f1, workoutid, courseid, f1, workoutid, courseid,
recordid, useremail, userfirstname, recordid, useremail, userfirstname,
**kwargs): # pragma: no cover **kwargs):
logfile = 'courselog_{workoutid}_{courseid}.log'.format( logfile = 'courselog_{workoutid}_{courseid}.log'.format(
workoutid=workoutid, courseid=courseid) workoutid=workoutid, courseid=courseid)
@@ -567,11 +611,11 @@ def handle_check_race_course(self,
mode = kwargs['mode'] mode = kwargs['mode']
summary = False summary = False
if 'summary' in kwargs: if 'summary' in kwargs: # pragma: no cover
summary = kwargs['summary'] summary = kwargs['summary']
successemail = False successemail = False
if 'successemail' in kwargs: if 'successemail' in kwargs: # pragma: no cover
successemail = kwargs['successemail'] successemail = kwargs['successemail']
try: try:
@@ -606,35 +650,21 @@ def handle_check_race_course(self,
rowdata.fillna(method='backfill', inplace=True) rowdata.fillna(method='backfill', inplace=True)
rowdata['time'] = rowdata['time']-rowdata.loc[0, 'time'] rowdata.loc[:, 'time'] = rowdata.loc[:, 'time'].copy()-rowdata.loc[0, 'time']
rowdata = rowdata[rowdata['time'] > splitsecond] rowdata = rowdata.copy()[rowdata['time'] > splitsecond]
# we may want to expand the time (interpolate) # we may want to expand the time (interpolate)
rowdata['dt'] = rowdata['time'].apply(
rowdata.loc[:,'dt'] = rowdata['time'].apply(
lambda x: safetimedelta(x) lambda x: safetimedelta(x)
) ).values
rowdata = rowdata.resample('100ms', on='dt').mean() rowdata = rowdata.resample('100ms', on='dt').mean()
rowdata = rowdata.interpolate() 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 = [] paths = []
for polygon in polygons: for polygon in polygons:
@@ -649,7 +679,7 @@ def handle_check_race_course(self,
try: try:
entrytimes, entrydistances = time_in_path(rowdata, paths[0], maxmin='max', getall=True, entrytimes, entrydistances = time_in_path(rowdata, paths[0], maxmin='max', getall=True,
name=polygons[0].name, logfile=logfile) 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, entrytimes, entrydistances = time_in_path(rowdata, paths[0], maxmin='max', getall=True,
name='Start', logfile=logfile) name='Start', logfile=logfile)
with open(logfile, 'ab') as f: with open(logfile, 'ab') as f:
@@ -718,7 +748,8 @@ def handle_check_race_course(self,
'endsecond': endseconds, 'endsecond': endseconds,
}) })
records = records[records['coursecompleted'] is True] #records = records[records['coursecompleted'] is True]
records = records.loc[records['coursecompleted'], : ]
if len(records): if len(records):
coursecompleted = True coursecompleted = True
@@ -736,35 +767,29 @@ def handle_check_race_course(self,
coursedistance = coursemeters coursedistance = coursemeters
velo = coursedistance/coursetimeseconds velo = coursedistance/coursetimeseconds
points = 100*(2.-referencespeed/velo) 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': if mode != 'coursetest':
query = 'UPDATE rowers_coursetestresult SET coursecompleted = 1,'\ record = VirtualRaceResult.objects.get(id=recordid)
' duration = "{duration}", distance = {distance},'\ record.duration = totaltime_sec_to_string(coursetimeseconds)
' workoutid = {workoutid}, startsecond = {startsecond},'\ record.distance=int(coursemeters)
' endsecond = {endsecond}, points={points} WHERE id={recordid}'.format( record.points = points
recordid=recordid, record.startsecond = startsecond
duration=totaltime_sec_to_string(coursetimeseconds), record.endsecond = endsecond
distance=int(coursemeters), record.workoutid = workoutid
points=points, record.coursecompleted = 1
workoutid=workoutid, record.save()
startsecond=startsecond,
endsecond=endsecond,)
with engine.connect() as conn, conn.begin(): else: # pragma: no cover
result = conn.execute(query) 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: try:
row = rdata(csvfile=f1) row = rdata(csvfile=f1)
except IOError: # pragma: no cover except IOError: # pragma: no cover
@@ -784,17 +809,12 @@ def handle_check_race_course(self,
summary = row.allstats() summary = row.allstats()
row.write_csv(f1, gzip=True) 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(): if successemail: # pragma: no cover
result = conn.execute(query)
conn.close()
engine.dispose()
if successemail:
handle_sendemail_coursesucceed( handle_sendemail_coursesucceed(
useremail, userfirstname, logfile, workoutid useremail, userfirstname, logfile, workoutid
) )
@@ -804,36 +824,26 @@ def handle_check_race_course(self,
return 1 return 1
else: # pragma: no cover else: # pragma: no cover
query = 'UPDATE rowers_virtualraceresult SET coursecompleted = 0,'\ record = VirtualRaceResult.object.get(id=recordid)
' duration = "{duration}", distance = {distance},'\ record.duration = totaltime_sec_to_string(0)
' workoutid = {workoutid}, startsecond = {startsecond},'\ record.distance = 0
' endsecond = {endsecond}, points={points} WHERE id={recordid}'.format( record.workoutid = workoutid
recordid=recordid, record.startsecond = startsecond
duration=totaltime_sec_to_string(0), record.endsecond = endsecond
distance=0, record.points = 0
points=0.0, record.save()
workoutid=workoutid,
startsecond=startsecond,
endsecond=endsecond,)
if mode == 'coursetest': if mode == 'coursetest':
query = 'UPDATE rowers_coursetestresult SET coursecompleted = 0,'\ record = CourseTestResult.objects.get(id=recordid)
' duration = "{duration}", distance = {distance}, workoutid = {workoutid}'\ record.duration = totaltime_sec_to_string(0)
', startsecond = {startsecond}, endsecond = {endsecond}'\ record.distance = 0
', points={points} WHERE id={recordid}'.format( record.workoutid = workoutid
recordid=recordid, record.startsecond = startsecond
duration=totaltime_sec_to_string(0), record.endsecond = endsecond
distance=0, record.points = 0
points=0, record.save()
workoutid=workoutid,
startsecond=startsecond,
endsecond=endsecond,)
with engine.connect() as conn, conn.begin():
result = conn.execute(query)
conn.close()
engine.dispose()
# add times for all gates to log file # add times for all gates to log file
with open(logfile, 'ab') as f: with open(logfile, 'ab') as f:
@@ -1086,10 +1096,7 @@ def handle_calctrimp(id,
hrmax, hrmax,
hrmin, hrmin,
debug=False, **kwargs): 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 tss = 0
normp = 0 normp = 0
@@ -1178,21 +1185,14 @@ def handle_calctrimp(id,
if hrtss > 1000: # pragma: no cover if hrtss > 1000: # pragma: no cover
hrtss = 0 hrtss = 0
query = 'UPDATE rowers_workout SET rscore = {tss},'\ workout = Workout.objects.get(id=id)
' normp = {normp}, trimp={trimp}, hrtss={hrtss},'\ workout.tss = int(tss)
' normv={normv}, normw={normw} WHERE id={id}'.format( workout.normp = int(normp)
tss=int(tss), workout.trimp = int(trimp)
normp=int(normp), workout.hrtss = int(hrtss)
trimp=int(trimp), workout.normv = normv
hrtss=int(hrtss), workout.normw = normw
normv=normv, workout.save()
normw=normw,
id=id,)
with engine.connect() as conn, conn.begin():
_ = conn.execute(query)
conn.close()
engine.dispose()
return 1 return 1
@@ -1974,7 +1974,7 @@ def handle_sendemail_ical(first_name, last_name, email, url, icsfile, **kwargs):
try: try:
os.remove(icsfile) os.remove(icsfile)
except: except: # pragma: no cover
pass pass
return 1 return 1
@@ -2834,23 +2834,13 @@ def handle_update_wps(rid, types, ids, mode, debug=False, **kwargs):
except ValueError: # pragma: no cover except ValueError: # pragma: no cover
return 0 return 0
rower = Rower.objects.get(id=rid)
if mode == 'water': if mode == 'water':
query = "UPDATE `rowers_rower` SET `median_wps` = '%s' WHERE `id` = '%s'" % ( rower.median_wps = wps_median
wps_median, rid)
else: else:
query = "UPDATE `rowers_rower` SET `median_wps_erg` = '%s' WHERE `id` = '%s'" % ( rower.median_wps_erg = wps_median
wps_median, rid)
if debug: # pragma: no cover rower.save()
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()
return wps_median 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) workoutid, error = add_workout_from_data(userid, nkid, data, df)
# dologging('nklog.log','NK Workout ID {id}'.format(id=workoutid)) # dologging('nklog.log','NK Workout ID {id}'.format(id=workoutid))
workout = Workout.objects.get(id=workoutid)
if debug: # pragma: no cover newnkid = workout.uploadedtonk
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()
parkedids = [] parkedids = []
try: try:
@@ -3303,22 +3278,8 @@ def handle_c2_async_workout(alldata, userid, c2token, c2id, delaysec, defaulttim
workoutid = response.json()['id'] workoutid = response.json()['id']
if debug: # pragma: no cover workout = Workout.objects.get(id=workoutid)
engine = create_engine(database_url_debug, echo=False) newc2id = workout.uploadedtoc2
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()
parkedids = [] parkedids = []
with open('c2blocked.json', 'r') as c2blocked: 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) json.dump(tdata, c2blocked)
# set distance, time # set distance, time
query = "UPDATE `rowers_workout` SET `distance` = '%s', `duration` = '%s' WHERE `id` = '%s'" % ( workout = Workout.objects.get(id=workoutid)
distance, duration, workoutid) workout.distance = distance
workout.duration = duration
with engine.connect() as conn, conn.begin(): workout.save()
result = conn.execute(query)
conn.close()
engine.dispose()
# summary # summary
if 'workout' in data: if 'workout' in data:
@@ -3359,14 +3316,9 @@ def handle_c2_async_workout(alldata, userid, c2token, c2id, delaysec, defaulttim
summary, sa, results = summaryfromsplitdata( summary, sa, results = summaryfromsplitdata(
splitdata, data, csvfilename, workouttype=workouttype) splitdata, data, csvfilename, workouttype=workouttype)
query = "UPDATE `rowers_workout` SET `summary` = '%s' WHERE `id` = %s" % ( workout = Workout.objects.get(id=workoutid)
summary, workoutid) workout.summary = summary
workout.save()
with engine.connect() as conn, conn.begin():
result = conn.execute(query)
conn.close()
engine.dispose()
from rowingdata.trainingparser import getlist from rowingdata.trainingparser import getlist
if sa: if sa:
+8 -4
View File
@@ -9,7 +9,7 @@
<ul class="main-content"> <ul class="main-content">
<li class="grid_4"> <li class="grid_4">
{% if user.rower.rowerplan == 'basic' %} {% if user.rower.rowerplan == 'basic' %}
<p> <p>
This is a preview of the page with advanced functionality for Pro users. This is a preview of the page with advanced functionality for Pro users.
See <a href="/rowers/about/">the About page</a> for more information See <a href="/rowers/about/">the About page</a> for more information
@@ -32,7 +32,7 @@
<td> <td>
<a href="/rowers/workout/{{ workout.id|encode }}/">https://rowsandall.com/rowers/workout/{{ workout.id|encode }}</a> <a href="/rowers/workout/{{ workout.id|encode }}/">https://rowsandall.com/rowers/workout/{{ workout.id|encode }}</a>
<td> <td>
</table> </table>
</li> </li>
@@ -42,14 +42,18 @@
{% if instrokemetrics %} {% if instrokemetrics %}
{% for metric in instrokemetrics %} {% for metric in instrokemetrics %}
<p> <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> </p>
{% endfor %} {% endfor %}
<p>
<a href="/rowers/workout/{{ workout.id|encode }}/instroke/interactive/">NEW: Dynamic</a>
</p>
{% else %} {% else %}
<p>Unfortunately, this workout doesn't have any in stroke metrics</p> <p>Unfortunately, this workout doesn't have any in stroke metrics</p>
{% endif %} {% endif %}
</li> </li>
</ul> </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 %}
+17 -1
View File
@@ -95,19 +95,35 @@
<div> <div>
{% if workout.name != '' %} {% if workout.name != '' %}
<h3> <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 <a
href={% url rower.defaultlandingpage3 id=workout.id|encode %} href={% url rower.defaultlandingpage3 id=workout.id|encode %}
title="{{ rower.defaultlandingpage3|verbose }}"> title="{{ rower.defaultlandingpage3|verbose }}">
{{ workout.name }} {{ workout.name }}
</a> </a>
{% endif %}
</h3> </h3>
{% else %} {% else %}
<h3> <h3>
{% if team %}
<a <a
href={% url rower.defaultlandingpage id=workout.id|encode %} href={% url rower.defaultlandingpage id=workout.id|encode %}
title="{{ rower.defaultlandingpage|verbose }}"> title="{{ rower.defaultlandingpage|verbose }}">
No Name No Name ({{ workout.user.user.first_name }} {{ workout.user.user.last_name}})
</a> </a>
{% else %}
<a
href={% url rower.defaultlandingpage id=workout.id|encode %}
title="{{ rower.defaultlandingpage|verbose }}">
No Name
</a>
{% endif %}
</h3> </h3>
{% endif %} {% endif %}
</div> </div>
+10 -7
View File
@@ -81,10 +81,13 @@ class AsyncTaskTests(TestCase):
def test_polygons(self): def test_polygons(self):
polygons = GeoPolygon.objects.all() polygons = GeoPolygon.objects.all()
polygon = polygons[0] polygon = polygons[0]
obj = (polygon.id,polygon.name)
path = tasks.polygon_to_path(obj) #obj = (polygon.id,polygon.name)
self.assertEqual(len(path),4) path = tasks.polygon_to_path(polygon)
self.assertEqual(len(path),6)
def test_summaryfromsplitdata(self): def test_summaryfromsplitdata(self):
splitdata = [ splitdata = [
@@ -495,21 +498,21 @@ class AsyncTaskTests(TestCase):
self.assertEqual(res,1) self.assertEqual(res,1)
@patch('rowers.dataprepnodjango.create_engine') @patch('rowers.dataprep.create_engine')
def test_handle_updateergcp(self,mocked_sqlalchemy): def test_handle_updateergcp(self,mocked_sqlalchemy):
f1 = get_random_file()['filename'] f1 = get_random_file()['filename']
res = tasks.handle_updateergcp(1,[f1]) res = tasks.handle_updateergcp(1,[f1])
self.assertEqual(res,1) self.assertEqual(res,1)
@patch('rowers.dataprepnodjango.getsmallrowdata_db') @patch('rowers.dataprep.getsmallrowdata_db')
def test_handle_updatecp(self,mocked_getsmallrowdata_db_updatecp): def test_handle_updatecp(self,mocked_getsmallrowdata_db_updatecp):
rower_id = 1 rower_id = 1
workoutids = [1] workoutids = [1]
res = tasks.handle_updatecp(rower_id,workoutids) res = tasks.handle_updatecp(rower_id,workoutids)
self.assertEqual(res,1) self.assertEqual(res,1)
@patch('rowers.dataprepnodjango.getsmallrowdata_db') @patch('rowers.dataprep.getsmallrowdata_db')
def test_handle_setcp(self,mocked_getsmallrowdata_db_setcp): def test_handle_setcp(self,mocked_getsmallrowdata_db_setcp):
strokesdf = pd.read_csv('rowers/tests/testdata/uhfull.csv') strokesdf = pd.read_csv('rowers/tests/testdata/uhfull.csv')
filename = 'rowers/tests/testdata/temp/pq.gz' filename = 'rowers/tests/testdata/temp/pq.gz'
@@ -521,7 +524,7 @@ class AsyncTaskTests(TestCase):
except FileNotFoundError: except FileNotFoundError:
pass pass
@patch('rowers.dataprepnodjango.getsmallrowdata_db') @patch('rowers.dataprep.getsmallrowdata_db')
def test_handle_update_wps(self,mocked_getsmallrowdata_db_wps): def test_handle_update_wps(self,mocked_getsmallrowdata_db_wps):
ids = [1,2,3] 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) # 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): def test_agerecords(self, mock_sqlalchemy):
# update_records(url='rowers/tests/c2worldrecords.html',verbose=False) # update_records(url='rowers/tests/c2worldrecords.html',verbose=False)
+96
View File
@@ -1554,6 +1554,36 @@ description: ""
self.instantplan.save() 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): def tearDown(self):
@@ -1562,6 +1592,72 @@ description: ""
except (IOError, FileNotFoundError,OSError): except (IOError, FileNotFoundError,OSError):
pass 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): def test_clone_view(self):
login = self.c.login(username=self.u.username, password=self.password) login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login) self.assertTrue(login)
+1 -3
View File
@@ -888,8 +888,7 @@ class ChallengesTest(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@patch('rowers.tasks.create_engine', side_effect=mocked_sqlalchemy_courses) def test_virtualevent_check_view(self):
def notest_virtualevent_check_view(self,mocked_sqlalchemy_courses):
res = tasks.handle_check_race_course( res = tasks.handle_check_race_course(
self.wthyro.csvfilename, self.wthyro.csvfilename,
@@ -898,7 +897,6 @@ class ChallengesTest(TestCase):
self.result.id, self.result.id,
self.wthyro.user.user.email, self.wthyro.user.user.email,
self.wthyro.user.user.first_name, self.wthyro.user.user.first_name,
mode='coursetest',
) )
self.assertEqual(res,1) self.assertEqual(res,1)
+2
View File
@@ -249,6 +249,8 @@ urlpatterns = [
path('404/', TemplateView.as_view(template_name='404.html'), name='404'), path('404/', TemplateView.as_view(template_name='404.html'), name='404'),
path('400/', TemplateView.as_view(template_name='400.html'), name='400'), path('400/', TemplateView.as_view(template_name='400.html'), name='400'),
path('403/', TemplateView.as_view(template_name='403.html'), name='403'), 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, re_path(r'^exportallworkouts/?/$', views.workouts_summaries_email_view,
name='workouts_summaries_email_view'), name='workouts_summaries_email_view'),
path('failedjobs/', views.failed_queue_view, name='failed_queue_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 # Process Rojabo callback
@login_required() @login_required()
def rower_process_rojabocallback(request): # prgrma: no cover def rower_process_rojabocallback(request): # pragma: no cover
# do stuff # do stuff
try: try:
code = request.GET.get('code', None) code = request.GET.get('code', None)
+25 -19
View File
@@ -1409,7 +1409,7 @@ def save_plan_yaml(request, userid=0):
steps = ps.steps steps = ps.steps
steps['filename'] = "" steps['filename'] = ""
workouts.append(steps) workouts.append(steps)
else: else: # pragma: no cover
if ps.sessionmode == 'distance': if ps.sessionmode == 'distance':
ps.interval_string = '{d}m'.format(d=ps.sessionvalue) ps.interval_string = '{d}m'.format(d=ps.sessionvalue)
elif ps.sessionmode == 'time': elif ps.sessionmode == 'time':
@@ -2994,7 +2994,8 @@ def rower_create_trainingplan(request, id=0):
redirect_field_name=None) redirect_field_name=None)
def stepadder(request, id=0): def stepadder(request, id=0):
is_ajax = request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest' is_ajax = request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
if not is_ajax:
if not is_ajax: # pragma: no cover
return JSONResponse( return JSONResponse(
status=403, data={ status=403, data={
'status': 'false', 'status': 'false',
@@ -3005,7 +3006,7 @@ def stepadder(request, id=0):
is_save = request.GET.get('save',0) is_save = request.GET.get('save',0)
if request.method != 'POST': if request.method != 'POST': # pragma: no cover
message = {'status': 'false', message = {'status': 'false',
'message': 'this view cannot be accessed through GET'} 'message': 'this view cannot be accessed through GET'}
return JSONResponse(status=403, data=message) return JSONResponse(status=403, data=message)
@@ -3013,14 +3014,16 @@ def stepadder(request, id=0):
try: try:
json_data = json.loads(request.body) json_data = json.loads(request.body)
post_data = json_data post_data = json_data
except (KeyError, JSONDecodeError): except (KeyError, JSONDecodeError): # pragma: no cover
q = request.POST q = request.POST
post_data = {k: q.getlist(k) if len( post_data = {k: q.getlist(k) if len(
q.getlist(k)) > 1 else v for k, v in q.items()} q.getlist(k)) > 1 else v for k, v in q.items()}
# only allow local host # only allow local host
hostt = request.get_host().split(':') 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 = {'status': 'false',
'message': 'permission denied for host '+hostt[0]} 'message': 'permission denied for host '+hostt[0]}
return JSONResponse(status=403, data=message) return JSONResponse(status=403, data=message)
@@ -3028,7 +3031,7 @@ def stepadder(request, id=0):
if ps.steps: if ps.steps:
filename = ps.steps.get('filename','') filename = ps.steps.get('filename','')
sport = ps.steps.get('sport','rowing') sport = ps.steps.get('sport','rowing')
else: else: # pragma: no cover
filename = '' filename = ''
sport = 'rowing' sport = 'rowing'
@@ -3045,10 +3048,10 @@ def stepadder(request, id=0):
d = step.asdict() d = step.asdict()
d['stepId'] = nr d['stepId'] = nr
steps['steps'].append(d) steps['steps'].append(d)
except PlannedSessionStep.DoesNotExist: except PlannedSessionStep.DoesNotExist: # pragma: no cover
pass pass
if is_save: if is_save: # pragma: no cover
# save the darn thing # save the darn thing
ps.steps = steps ps.steps = steps
@@ -3066,9 +3069,12 @@ def stepdelete(request, id=0):
step.delete() step.delete()
backid = request.GET.get('id') backid = request.GET.get('id',0)
url = reverse(stepeditor,kwargs={'id':backid}) if backid: # pragma: no cover
url = reverse(stepeditor,kwargs={'id':backid})
else:
url = reverse('plannedsessions_view')
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@@ -3079,7 +3085,7 @@ def stepedit(request, id=0, psid=0):
step = get_object_or_404(PlannedSessionStep, pk=id) step = get_object_or_404(PlannedSessionStep, pk=id)
try: try:
ps = PlannedSession.objects.get(id=psid) ps = PlannedSession.objects.get(id=psid)
except PlannedSession.DoesNotExist: except PlannedSession.DoesNotExist: # pragma: no cover
ps = None ps = None
form = StepEditorForm(instance=step) form = StepEditorForm(instance=step)
@@ -3094,7 +3100,7 @@ def stepedit(request, id=0, psid=0):
ee = ss.copy() ee = ss.copy()
ee.pop('stepId') ee.pop('stepId')
if (dd == ee): if (dd == ee): # pragma: no cover
ss['durationType'] = form.cleaned_data['durationtype'] ss['durationType'] = form.cleaned_data['durationtype']
ss['durationValue'] = form.cleaned_data['durationvalue'] ss['durationValue'] = form.cleaned_data['durationvalue']
ss['targetType'] = form.cleaned_data['targettype'] ss['targetType'] = form.cleaned_data['targettype']
@@ -3126,15 +3132,15 @@ def stepedit(request, id=0, psid=0):
step.name = form.cleaned_data['name'] step.name = form.cleaned_data['name']
step.description = form.cleaned_data['description'] step.description = form.cleaned_data['description']
if step.durationtype == 'Time': if step.durationtype == 'Time': # pragma: no cover
step.durationvalue *= 60000 step.durationvalue *= 60000
elif step.durationtype == 'Distance': elif step.durationtype == 'Distance': # pragma: no cover
step.durationvalue *= 100 step.durationvalue *= 100
step.save() step.save()
if step.durationtype == 'Time': if step.durationtype == 'Time': # pragma: no cover
form.fields['durationvalue'].initial = step.durationvalue / 60000 form.fields['durationvalue'].initial = step.durationvalue / 60000
elif step.durationtype == 'Distance': elif step.durationtype == 'Distance':
form.fields['durationvalue'].initial = step.durationvalue / 100 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] stepdescription = step_to_string(step.asdict(), short=False)[0]
if request.method == 'POST': 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}) url = reverse('stepeditor',kwargs = {'id': ps.id})
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@@ -3204,7 +3210,7 @@ def stepeditor(request, id=0):
targetvaluehigh = targetvaluehigh, targetvaluehigh = targetvaluehigh,
intensity = intensity, intensity = intensity,
) )
if not archived_steps.count() and durationvalue != 0: if not archived_steps.count():
s = PlannedSessionStep( s = PlannedSessionStep(
manager = request.user, manager = request.user,
durationtype = durationtype, durationtype = durationtype,
@@ -3217,14 +3223,14 @@ def stepeditor(request, id=0):
name = step.get('wkt_step_name','Step') name = step.get('wkt_step_name','Step')
) )
s.save() s.save()
else: else: # pragma: no cover
s = archived_steps[0] s = archived_steps[0]
currentsteps.append(s) currentsteps.append(s)
form = StepEditorForm() form = StepEditorForm()
if request.method == 'POST': if request.method == 'POST': # pragma: no cover
form = StepEditorForm(request.POST) form = StepEditorForm(request.POST)
if form.is_valid(): if form.is_valid():
step = form.save(commit=False) step = form.save(commit=False)
+2 -1
View File
@@ -110,7 +110,7 @@ from rowers.forms import (
VideoAnalysisMetricsForm, SurveyForm, HistorySelectForm, VideoAnalysisMetricsForm, SurveyForm, HistorySelectForm,
StravaChartForm, FitnessFitForm, PerformanceManagerForm, StravaChartForm, FitnessFitForm, PerformanceManagerForm,
TrainingPlanBillingForm, InstantPlanSelectForm, TrainingPlanBillingForm, InstantPlanSelectForm,
TrainingZonesForm, TrainingZonesForm, InstrokeForm
) )
from django.urls import reverse, reverse_lazy 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.plannedsessions import *
from rowers.tasks import handle_makeplot, handle_otwsetpower, handle_sendemailtcx, handle_sendemailcsv from rowers.tasks import handle_makeplot, handle_otwsetpower, handle_sendemailtcx, handle_sendemailcsv
from rowers.tasks import ( from rowers.tasks import (
instroke_static,
fetch_rojabo_session, fetch_rojabo_session,
handle_sendemail_unrecognized, handle_sendemailnewcomment, handle_sendemail_unrecognized, handle_sendemailnewcomment,
handle_request_post, 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() instrokemetrics = rowdata.get_instroke_columns()
if metric in instrokemetrics: if metric in instrokemetrics:
f1 = w.csvfilename[6:-4] job = myqueue(queuelow,
timestr = strftime("%Y%m%d-%H%M%S") instroke_static,w, metric)
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')
r = getrower(request.user) r = getrower(request.user)
url = reverse(r.defaultlandingpage, url = reverse(r.defaultlandingpage,
@@ -2943,9 +2915,132 @@ def instroke_chart(request, id=0, metric=''): # pragma: no cover
return HttpResponseRedirect(url) 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 # erase column
@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) @permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True)
def workout_erase_column_view(request, id=0, column=''): def workout_erase_column_view(request, id=0, column=''):
r = getrower(request.user) r = getrower(request.user)