Merge branch 'release/v18.6.0'
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
+425
-2083
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
@@ -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):
|
||||
|
||||
@@ -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,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
@@ -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
@@ -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:
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<ul class="main-content">
|
||||
<li class="grid_4">
|
||||
{% if user.rower.rowerplan == 'basic' %}
|
||||
|
||||
|
||||
<p>
|
||||
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
|
||||
@@ -32,7 +32,7 @@
|
||||
<td>
|
||||
<a href="/rowers/workout/{{ workout.id|encode }}/">https://rowsandall.com/rowers/workout/{{ workout.id|encode }}</a>
|
||||
<td>
|
||||
|
||||
|
||||
</table>
|
||||
</li>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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
|
||||
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>
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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)
|
||||
|
||||
+25
-19
@@ -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)
|
||||
|
||||
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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user