Private
Public Access
1
0

Merge branch 'release/v21.0.0'

This commit is contained in:
2024-05-04 16:18:52 +02:00
79 changed files with 3108 additions and 8662 deletions
+4 -2
View File
@@ -1,6 +1,6 @@
from rowers.models import Alert, Condition, User, Rower, Workout
from rowers.teams import coach_getcoachees
from rowers.dataprep import getsmallrowdata_db, getrowdata_db
from rowers.dataprep import getrowdata_db, read_data, remove_nulls_pl
import datetime
import numpy as np
import math
@@ -101,8 +101,10 @@ def alert_get_stats(alert, nperiod=0): # pragma: no cover
ids = [w.id for w in workouts]
try:
df = getsmallrowdata_db(columns, ids=ids, doclean=True,
df = getsmallrowdata_pd(columns, ids=ids, doclean=True,
workstrokesonly=workstrokesonly)
df.dropna(axis=1,how='all',inplace=True)
df.dropna(axis=0,how='all',inplace=True)
except:
return {
'workouts': workouts.count(),
-57
View File
@@ -1,5 +1,4 @@
from rowers.courseutils import coursetime_paths, coursetime_first, time_in_path
import pandas as pd
from rowers.models import (
Rower, Workout,
GeoPoint, GeoPolygon, GeoCourse,
@@ -420,62 +419,6 @@ def createcourse(
return c
def get_time_course(ws, course): # pragma: no cover
coursetimeseconds = 0.0
coursecompleted = False
w = ws[0]
columns = ['time', ' latitude', ' longitude', 'cum_dist']
rowdata = dataprep.getsmallrowdata_db(
columns,
ids=[w.id],
doclean=False,
workstrokesonly=False
)
rowdata.rename(columns={
' latitude': 'latitude',
' longitude': 'longitude',
}, inplace=True)
rowdata['time'] = rowdata['time']/1000.
rowdata.fillna(method='backfill', inplace=True)
rowdata['time'] = rowdata['time']-rowdata.ix[0, 'time']
# we may want to expand the time (interpolate)
rowdata['dt'] = rowdata['time'].apply(
lambda x: timedelta(seconds=x)
)
rowdata = rowdata.resample('100ms', on='dt').mean()
rowdata = rowdata.interpolate()
# create path
polygons = GeoPolygon.objects.filter(
course=course).order_by("order_in_course")
paths = []
for polygon in polygons:
path = polygon_to_path(polygon)
paths.append(path)
(
coursetimeseconds,
coursemeters,
coursecompleted,
) = coursetime_paths(rowdata, paths)
(
coursetimefirst,
coursemetersfirst,
firstcompleted
) = coursetime_first(
rowdata, paths)
coursetimeseconds = coursetimeseconds-coursetimefirst
coursemeters = coursemeters-coursemetersfirst
return coursetimeseconds, coursemeters, coursecompleted
def replacecourse(course1, course2):
ps = PlannedSession.objects.filter(course=course1)
+81 -105
View File
@@ -8,6 +8,7 @@ from rowers.datautils import p0
from scipy import optimize
from rowers.utils import calculate_age
import datetime
import gzip
from scipy.signal import savgol_filter
from rowers.opaque import encoder
from rowers.database import *
@@ -27,6 +28,8 @@ from fitparse import FitFile
import itertools
import numpy as np
import pandas as pd
import polars as pl
from polars.exceptions import ColumnNotFoundError
from zipfile import BadZipFile
import zipfile
import os
@@ -73,6 +76,7 @@ import pytz
import collections
import pendulum
from rowingdata import rowingdata as rrdata
from rowingdata import rowingdata_pl as rrdata_pl
from rowingdata import rower as rrower
@@ -88,10 +92,9 @@ from rowers.dataroutines import *
from rowers.tasks import (
handle_sendemail_newftp,
handle_sendemail_unrecognized, handle_setcp,
handle_sendemail_unrecognized,
handle_getagegrouprecords, handle_update_wps,
handle_request_post, handle_calctrimp,
handle_updatecp, handle_updateergcp,
handle_sendemail_breakthrough,
handle_sendemail_hard,
)
@@ -123,6 +126,10 @@ from rq import Queue
from rowers.datautils import rpetotss
def rscore_approx(row):
if isinstance(row, pl.DataFrame):
row = {'rscore': row['rscore'][0]}
if isinstance(row, pl.Series):
row = {'rscore': row['rscore'][0]}
if row['rscore'] > 0:
return row['rscore']
if row['rscore'] == 0: # pragma: no cover
@@ -215,18 +222,18 @@ def check_marker(workout):
ids.append(w.id)
gms.append(gmstandard)
df = pd.DataFrame({
df = pl.DataFrame({
'id': ids,
'gms': gms,
})
if df.empty: # pragma: no cover
if df.is_empty(): # pragma: no cover
workout.ranking = True
workout.save()
return workout
indexmax = df['gms'].idxmax()
theid = df.loc[indexmax, 'id']
theid = df.filter(pl.col("gms") == pl.col("gms").max())['id'][0]
wmax = Workout.objects.get(id=theid)
# gms_max = wmax.goldmedalstandard
@@ -322,7 +329,7 @@ def workout_summary_to_df(
goldstandarddurations.append(int(goldstandardduration))
rankingpieces.append(w.rankingpiece)
df = pd.DataFrame({
df = pl.DataFrame({
'ID': ids,
'date': startdatetimes,
'name': names,
@@ -392,7 +399,7 @@ def resample(id, r, parent, overwrite='copy'):
row.write_csv(parent.csvfilename, gzip=True)
_ = dataprep(row.df, id=parent.id, bands=True, barchart=True,
_ = dataplep(row.df, id=parent.id, bands=True, barchart=True,
otwpower=True, empower=True, inboard=parent.inboard)
isbreakthrough, ishard = checkbreakthrough(parent, r)
_ = check_marker(parent)
@@ -414,18 +421,23 @@ def resample(id, r, parent, overwrite='copy'):
def calculate_goldmedalstandard(rower, workout, recurrance=True):
cpfile = 'media/cpdata_{id}.parquet.gz'.format(id=workout.id)
try:
df = pd.read_parquet(cpfile)
df = pl.read_parquet(cpfile)
except:
df = read_data(['power'], ids=[workout.id])
df = remove_nulls_pl(df)
background = True
if settings.TESTING:
background = False
if recurrance:
df, delta, cpvalues = setcp(workout, background=background)
if df.empty:
if df.is_empty():
return 0, 0
else:
return 0,0
if df.empty and recurrance: # pragma: no cover
if df.is_empty() and recurrance: # pragma: no cover
df, delta, cpvalues = setcp(workout, recurrance=False, background=True)
if df.empty:
if df.is_empty():
return 0, 0
age = calculate_age(rower.birthdate, today=workout.date)
@@ -453,7 +465,7 @@ def calculate_goldmedalstandard(rower, workout, recurrance=True):
if getrecords: # pragma: no cover
durations = [1, 4, 30, 60]
distances = [100, 500, 1000, 2000, 5000, 6000, 10000, 21097, 42195]
df2 = pd.DataFrame(
df2 = pl.DataFrame(
list(
C2WorldClassAgePerformance.objects.filter(
sex=rower.sex,
@@ -461,7 +473,7 @@ def calculate_goldmedalstandard(rower, workout, recurrance=True):
).values()
)
)
jsondf = df2.to_json()
jsondf = df2.write_json()
_ = myqueue(queuelow, handle_getagegrouprecords,
jsondf, distances, durations, age, rower.sex, rower.weightcategory)
@@ -489,9 +501,10 @@ def calculate_goldmedalstandard(rower, workout, recurrance=True):
scores = 100.*powers/wcpowers
try:
indexmax = scores.idxmax()
delta = int(df.loc[indexmax, 'delta'])
maxvalue = scores.max()
df = pl.DataFrame({'times': times, 'scores': scores})
df = df.filter(pl.col("scores") == pl.col("scores").max())
delta = df[0, "times"]
maxvalue = df[0, "scores"]
except (ValueError, TypeError, KeyError): # pragma: no cover
indexmax = 0
delta = 0
@@ -504,21 +517,22 @@ def calculate_goldmedalstandard(rower, workout, recurrance=True):
def setcp(workout, background=False, recurrance=True):
try:
filename = 'media/cpdata_{id}.parquet.gz'.format(id=workout.id)
df = pd.read_parquet(filename)
df = pl.read_parquet(filename)
if not df.empty:
if not df.is_empty():
# check dts
tarr = datautils.getlogarr(4000)
if df['delta'][0] in tarr:
return(df, df['delta'], df['cp'])
except:
except Exception as e:
pass
strokesdf = getsmallrowdata_db(
strokesdf = read_data(
['power', 'workoutid', 'time'], ids=[workout.id])
strokesdf = remove_nulls_pl(strokesdf)
if strokesdf.empty:
return pd.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float')
if strokesdf.is_empty():
return pl.DataFrame({'delta': [], 'cp': []}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64)
totaltime = strokesdf['time'].max()
maxt = totaltime/1000.
@@ -533,7 +547,7 @@ def setcp(workout, background=False, recurrance=True):
elif os.path.exists(csvfilename+'.gz'): # pragma: no cover
csvfile = csvfilename+'.gz'
else: # pragma: no cover
return pd.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float')
return pl.DataFrame({'delta': [], 'cp': []}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64)
csvfile = os.path.abspath(csvfile)
@@ -547,7 +561,7 @@ def setcp(workout, background=False, recurrance=True):
grpc.channel_ready_future(channel).result(timeout=10)
except grpc.FutureTimeoutError: # pragma: no cover
dologging('metrics.log','grpc channel time out in setcp')
return pd.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float')
return pl.DataFrame({'delta': [], 'cp': []}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64)
stub = metrics_pb2_grpc.MetricsStub(channel)
req = metrics_pb2.CPRequest(filename = csvfile, filetype = "CSV", tarr = logarr)
@@ -556,25 +570,29 @@ def setcp(workout, background=False, recurrance=True):
response = stub.GetCP(req, timeout=60)
except Exception as e:
dologging('metrics.log', traceback.format_exc())
return pd.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float')
return pl.DataFrame({'delta': [], 'cp': []}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64)
delta = pd.Series(np.array(response.delta))
cpvalues = pd.Series(np.array(response.power))
delta = pl.Series(np.array(response.delta))
cpvalues = pl.Series(np.array(response.power))
powermean = response.avgpower
df = pd.DataFrame({
df = pl.DataFrame({
'delta': delta,
'cp': cpvalues,
'id': workout.id,
})
df.to_parquet(filename, engine='fastparquet', compression='GZIP')
df = df.drop_nulls()
with gzip.open(filename, 'w') as f:
df.write_parquet(f)
#df.to_parquet(filename, engine='fastparquet', compression='GZIP')
if recurrance:
goldmedalstandard, goldmedalduration = calculate_goldmedalstandard(
workout.user, workout)
workout.user, workout, recurrance=False)
workout.goldmedalstandard = goldmedalstandard
workout.goldmedalduration = goldmedalduration
workout.save()
@@ -602,14 +620,10 @@ def update_wps(r, types, mode='water', asynchron=True):
mode
)
df = getsmallrowdata_db(['time', 'driveenergy'], ids=ids)
df = read_data(['time', 'driveenergy'], ids=ids)
try:
mask = df['driveenergy'] > 100
except (KeyError, TypeError):
return False
try:
wps_median = int(df.loc[mask, 'driveenergy'].median())
wps_median = int(df.filter(pl.col("driveenergy")>100)["driveenergy"].median())
if mode == 'water':
r.median_wps = wps_median
else: # pragma: no cover
@@ -618,6 +632,10 @@ def update_wps(r, types, mode='water', asynchron=True):
r.save()
except ValueError: # pragma: no cover
pass
except OverflowError:
pass
except ColumnNotFoundError:
pass
return True
@@ -718,77 +736,28 @@ def fetchcp_new(rower, workouts):
data = []
for workout in workouts:
cpfile = 'media/cpdata_{id}.parquet.gz'.format(id=workout.id)
try:
df, delta, cpvalues = setcp(workout)
df = pd.read_parquet(cpfile)
df['workout'] = str(workout)
df['url'] = workout.url()
data.append(df)
except:
# CP data file doesn't exist yet. has to be created
df, delta, cpvalues = setcp(workout)
df['workout'] = str(workout)
df['url'] = workout.url()
df = df.drop('id')
df = df.with_columns((pl.lit(str(workout))).alias("workout"))
df = df.with_columns((pl.lit(workout.url())).alias("url"))
if not df.is_empty():
data.append(df)
if len(data) == 0:
return pd.Series(dtype='float'), pd.Series(dtype='float'), 0, pd.Series(dtype='float'), pd.Series(dtype='float')
return pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64), 0, pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64)
if len(data) > 1:
df = pd.concat(data, axis=0)
df = pl.concat(data)
try:
df = df[df['cp'] == df.groupby(['delta'])['cp'].transform('max')]
except KeyError: # pragma: no cover
return pd.Series(dtype='float'), pd.Series(dtype='float'), 0, pd.Series(dtype='float'), pd.Series(dtype='float')
df = df.sort_values(['delta']).reset_index()
df = df[df['cp']>20]
df = df.group_by(pl.col("delta")).agg(pl.max("cp"), pl.max("workout"), pl.max("url")).sort("delta")
except (KeyError, ColumnNotFoundError): # pragma: no cover
return pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64), 0, pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64)
df = df.filter(pl.col("cp")>20)
return df['delta'], df['cp'], 0, df['workout'], df['url']
def fetchcp(rower, theworkouts, table='cpdata'): # pragma: no cover
# get all power data from database (plus workoutid)
theids = [int(w.id) for w in theworkouts]
columns = ['power', 'workoutid', 'time']
df = getsmallrowdata_db(columns, ids=theids)
df.dropna(inplace=True, axis=0)
if df.empty:
avgpower2 = {}
for id in theids:
avgpower2[id] = 0
return pd.Series([], dtype='float'), pd.Series([], dtype='float'), avgpower2
try:
dfgrouped = df.groupby(['workoutid'])
except KeyError:
avgpower2 = {}
return pd.Series([], dtype='float'), pd.Series([], dtype='float'), avgpower2
try:
avgpower2 = dict(dfgrouped.mean()['power'].astype(int))
except KeyError:
avgpower2 = {}
for id in theids:
avgpower2[id] = 0
return pd.Series([], dtype='float'), pd.Series([], dtype='float'), avgpower2
cpdf = getcpdata_sql(rower.id, table=table)
if not cpdf.empty:
return cpdf['delta'], cpdf['cp'], avgpower2
else:
_ = myqueue(queuelow,
handle_updatecp,
rower.id,
theids,
table=table)
return pd.Series([], dtype='float'), pd.Series([], dtype='float'), avgpower2
return pd.Series([], dtype='float'), pd.Series([], dtype='float'), avgpower2
def update_rolling_cp(r, types, mode='water', dosend=False):
@@ -801,15 +770,17 @@ def update_rolling_cp(r, types, mode='water', dosend=False):
delta, cp, avgpower, workoutnames, urls = fetchcp_new(r, workouts)
powerdf = pd.DataFrame({
powerdf = pl.DataFrame({
'Delta': delta,
'CP': cp,
})
powerdf = powerdf[powerdf['CP'] > 0]
powerdf.dropna(axis=0, inplace=True)
powerdf.sort_values(['Delta', 'CP'], ascending=[1, 0], inplace=True)
powerdf.drop_duplicates(subset='Delta', keep='first', inplace=True)
powerdf = powerdf.filter(pl.col("CP")>0)
powerdf = powerdf.fill_nan(None).drop_nulls().sort(["Delta", "CP"])
powerdf = powerdf.unique(subset=["Delta"], keep="first")
if powerdf.is_empty():
return False
res2 = datautils.cpfit(powerdf)
p1 = res2[0]
@@ -1051,7 +1022,7 @@ def checkbreakthrough(w, r):
workouttype = w.workouttype
if workouttype in rowtypes:
cpdf, delta, cpvalues = setcp(w)
if not cpdf.empty:
if not cpdf.is_empty():
if workouttype in otwtypes:
try:
res, btvalues, res2 = utils.isbreakthrough(
@@ -1397,7 +1368,12 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower',
w.team.add(t)
# put stroke data in database
_ = dataprep(row.df, id=w.id, bands=True,
try:
row = rrdata_pl(df=pl.from_pandas(row.df))
except:
pass
_ = dataplep(row.df, id=w.id, bands=True,
barchart=True, otwpower=True, empower=True, inboard=inboard)
isbreakthrough, ishard = checkbreakthrough(w, r)
+827 -54
View File
File diff suppressed because it is too large Load Diff
+19 -32
View File
@@ -1,8 +1,10 @@
import pandas as pd
import polars as pl
import numpy as np
from scipy.interpolate import griddata
from scipy import optimize
from rowers.mytypes import otwtypes, otetypes, rowtypes
from rowers.models import Workout
@@ -81,8 +83,9 @@ def cpfit(powerdf, fraclimit=0.0001, nmax=1000):
p1 = p0
thesecs = powerdf['Delta']
theavpower = powerdf['CP']
thesecs = powerdf['Delta'].to_numpy()
theavpower = powerdf['CP'].to_numpy()
if len(thesecs) >= 4:
try:
@@ -96,13 +99,14 @@ def cpfit(powerdf, fraclimit=0.0001, nmax=1000):
factor = fitfunc(p0, thesecs.mean())/theavpower.mean()
p1 = [p0[0]/factor, p0[1]/factor, p0[2], p0[3]]
p1 = [abs(p) for p in p1]
fitt = pd.Series(10**(4*np.arange(100)/100.))
fitpower = fitfunc(p1, fitt)
fitpoints = fitfunc(p1, thesecs)
fitpoints0 = fitpoints.copy()
fitpoints0 = fitpoints
dd = fitpoints-theavpower
ddmin = dd.min()
@@ -356,8 +360,8 @@ def getmaxwattinterval(tt, ww, i):
def getfastest(df, thevalue, mode='distance'):
tt = df['time'].copy()
dd = df['cumdist'].copy()
tt = df['time'].clone()
dd = df['cumdist'].clone()
tmax = tt.max()
if mode == 'distance': # pragma: no cover
@@ -368,40 +372,28 @@ def getfastest(df, thevalue, mode='distance'):
return 0
# if tmax > 500000:
# newlen=int(tmax/2000.)
# newt = np.arange(newlen)*tmax/float(newlen)
# deltat = newt[1]-newt[0]
# else:
# newt = np.arange(0,tmax,10.)
# deltat = 10.
newlen = 1000
newt = np.arange(newlen)*tmax/float(newlen)
deltat = newt[1]-newt[0]
dd = griddata(tt.values,
dd.values, newt, method='linear', rescale=True)
dd = griddata(tt.to_numpy(),
dd.to_numpy(), newt, method='linear', rescale=True)
tt = pd.Series(newt, dtype='float')
dd = pd.Series(dd, dtype='float')
tt = pl.Series(newt, dtype=pl.Float64)
dd = pl.Series(dd, dtype=pl.Float64)
G = pl.concat([pl.Series([0.0]), dd])
G = pd.concat([pd.Series([0]), dd])
# T = pd.concat([pd.Series([0]), dd])
# h = np.mgrid[0:len(tt)+1:1, 0:len(tt)+1:1]
# distances = pd.DataFrame(h[1]-h[0])
ones = 1+np.zeros(len(G))
Ghor = np.outer(ones, G)
# Thor = np.outer(ones, T)
# Tver = np.outer(T, ones)
Gver = np.outer(G, ones)
Gdif = Ghor-Gver
Gdif = np.tril(Gdif.T).T
Gdif = pd.DataFrame(Gdif)
Gdif = pl.DataFrame(Gdif)
F = Gdif
F.fillna(inplace=True, method='ffill', axis=1)
F.fillna(inplace=True, value=0)
F = F.fill_nan(0)
restime = []
distance = []
@@ -412,7 +404,7 @@ def getfastest(df, thevalue, mode='distance'):
restime.append(deltat*i)
cp = np.diag(F, i).max()
loc = np.argmax(np.diag(F, i))
thestarttime = tt[loc]
thestarttime = tt.to_numpy()[loc]
starttimes.append(thestarttime)
distance.append(cp)
@@ -422,10 +414,6 @@ def getfastest(df, thevalue, mode='distance'):
distance = np.array(distance)
starttimes = np.array(starttimes)
# for i in range(len(restime)):
# if restime[i]<thevalue*60*1000:
# print(i,restime[i],distance[i],60*1000*thevalue)
if mode == 'distance':
duration = griddata(distance, restime, [
thevalue], method='linear', rescale=True)
@@ -434,7 +422,6 @@ def getfastest(df, thevalue, mode='distance'):
distance = griddata(distance, distance, [
thevalue], method='linear', rescale=True)
endtime = starttime+duration
# print(duration,starttime,endtime,'aa')
return duration[0]/1000., starttime[0]/1000., endtime[0]/1000.
else: # pragma: no cover
distance = griddata(restime, distance, [
+10 -83
View File
@@ -74,27 +74,6 @@ class ResampleForm(forms.Form):
class TrainingZonesForm(forms.Form):
zoneschoices = (
('power', 'Power Zones'),
('hr', 'Heart Rate Zones')
)
datechoices = (
('month', 'By Month'),
('week', 'By Week'),
)
yaxischoices = (
('time', 'Time'),
('percentage', 'Percentage of Time')
)
zones = forms.ChoiceField(
initial='hr', label='Training Zones', choices=zoneschoices)
dates = forms.ChoiceField(
initial='week', label='Date Aggregation', choices=datechoices)
yaxis = forms.ChoiceField(initial='percentage',
label='Y axis', choices=yaxischoices)
startdate = forms.DateField(
initial=timezone.now()-datetime.timedelta(days=42),
widget=AdminDateWidget(), # format='%Y-%m-%d'),
@@ -778,59 +757,7 @@ class PredictedPieceFormNoDistance(forms.Form):
value = forms.FloatField(initial=10, label='Free ranking piece')
# On the Geeky side, to update stream information for river dwellers
class UpdateStreamForm(forms.Form):
unitchoices = (
('m', 'm/s'),
('f', 'foot/s'),
('k', 'knots'),
('p', 'pace difference (sec/500m)'),
)
dist1 = forms.FloatField(initial=0, label='Distance 1')
dist2 = forms.FloatField(initial=1000, label='Distance 2')
stream1 = forms.FloatField(initial=0, label='Stream velocity 1')
stream2 = forms.FloatField(initial=0, label='Stream velocity 2')
streamunit = forms.ChoiceField(required=True,
choices=unitchoices,
initial='m',
label='Unit')
class Meta:
fields = ['dist1', 'dist2', 'stream1', 'stream2', 'streamunit']
# add wind information to your workout
class UpdateWindForm(forms.Form):
unitchoices = (
('m', 'm/s'),
('k', 'knots'),
('b', 'beaufort'),
('kmh', 'km/h'),
('mph', 'miles/hour'),
)
dist1 = forms.FloatField(initial=0, label='Distance 1')
dist2 = forms.FloatField(initial=1000, label='Distance 2')
vwind1 = forms.FloatField(initial=0, required=False, label='Wind Speed 1')
vwind2 = forms.FloatField(initial=0, required=False, label='Wind Speed 2')
windunit = forms.ChoiceField(required=True,
choices=unitchoices,
initial='m',
label='Unit')
winddirection1 = forms.IntegerField(initial=0, required=False,
label='Wind Direction 1')
winddirection2 = forms.IntegerField(initial=0, required=False,
label='Wind Direction 2')
class Meta:
fields = ['dist1', 'dist2',
'vwind1', 'vwind2',
'windunit',
'winddirection1', 'winddirection2']
# Form to select a data range to show workouts from a certain time period
class DateRangeForm(forms.Form):
startdate = forms.DateField(
initial=timezone.now()-datetime.timedelta(days=365),
@@ -1859,27 +1786,27 @@ class FlexOptionsForm(forms.Form):
class ForceCurveOptionsForm(forms.Form):
includereststrokes = forms.BooleanField(initial=False, required=False,
label='Include Rest Strokes')
label='Include Rest Strokes',
widget=forms.CheckboxInput(attrs={'class':'hidden'}))
spm_min = forms.FloatField(initial=15.0,label='SPM Min',widget=HiddenInput,required=False)
spm_max = forms.FloatField(initial=55.0,label='SPM Max',widget=HiddenInput,required=False)
dist_min = forms.IntegerField(initial=0,label='Dist Min',widget=HiddenInput,required=False)
dist_max = forms.IntegerField(initial=0,label='Dist Max',widget=HiddenInput,required=False)
dist_max = forms.IntegerField(initial=150000,label='Dist Max',widget=HiddenInput,required=False)
work_min = forms.IntegerField(initial=0,label='Work Min',widget=HiddenInput,required=False)
work_max = forms.IntegerField(initial=1500,label='Work Max',widget=HiddenInput,required=False)
notes = forms.CharField(initial="", label='notes', widget=HiddenInput, required=False)
plotchoices = (
('line', 'Force Curve Collection Plot'),
('scatter', 'Peak Force Scatter Plot'),
('none', 'Only aggregrate data')
)
plottype = forms.ChoiceField(choices=plotchoices, initial='line',
label='Individual Stroke Chart Type')
name = forms.CharField(initial="", label='Name',required=False)
plotcircles = forms.BooleanField(initial=False,
widget=forms.CheckboxInput(attrs={'class':'hidden'}),
required=False)
plotlines = forms.BooleanField(initial=False,
widget=forms.CheckboxInput(attrs={'class':'hidden'}),
required=False)
axchoices = list(
(ax[0], ax[1]) for ax in axes if ax[0] not in ['cumdist', 'None']
+1 -1
View File
@@ -2,7 +2,7 @@ from .integrations import SyncIntegration, NoTokenError, create_or_update_syncre
from rowers.models import User, Rower, Workout, TombStone
from django.db.utils import IntegrityError
from rowingdata import rowingdata
from rowingdata import rowingdata, rowingdata_pl
import numpy as np
import datetime
import json
+904 -5759
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -364,6 +364,7 @@ for key, dict in rowingmetrics:
metricsdicts[key] = dict
metricsgroups = list(set([d['group'] for n, d in rowingmetrics]))
dtypes = {}
+6 -1
View File
@@ -3833,8 +3833,11 @@ def auto_delete_file_on_delete(sender, instance, **kwargs):
# remove parquet file
try:
dirname = 'media/strokedata_{id}.parquet.gz'.format(id=instance.id)
shutil.rmtree(dirname)
os.remove(dirname)
except FileNotFoundError:
try:
shutil.rmtree(dirname)
except:
pass
# remove parquet file
@@ -5257,6 +5260,8 @@ class ForceCurveAnalysis(models.Model):
average_spm = models.FloatField(default=23)
average_boatspeed = models.FloatField(default=4.0)
include_rest_strokes = models.BooleanField(default=False)
plotcircles = models.BooleanField(default=False)
plotlines = models.BooleanField(default=False)
def __str__(self):
s = 'Force Curve Analysis {name} ({date})'.format(name = self.name,
+11 -17
View File
@@ -18,13 +18,12 @@ import rowers.dataprep as dataprep
import numpy as np
import rowers.metrics as metrics
import rowers.mytypes as mytypes
from rowers.courses import get_time_course
from rowers.utils import to_pace
from rowers.opaque import encoder
from rowingdata import rower as rrower
from rowingdata import rowingdata as rrdata
import arrow
import pandas as pd
import polars as pl
import json
# Python
@@ -203,16 +202,8 @@ def get_execution_report(rower, startdate, enddate, plan=None):
startdate__gte=enddate)
else: # pragma: no cover
plans = TrainingPlan.objects.filter(rowers__in=[rower])
#plans2 = TrainingPlan.objects.filter(
# enddate__lte=enddate, startdate__lte=enddate, rowers__in=[rower])
#plans = plans | plans2
#plans = plans.exclude(status=False).order_by("-enddate")
if not plans:
# make week cycles here
# get monday before startdate
micros = []
else:
sorted_plans = sorted(plans, key= lambda t: t.overlap(startdate,enddate))
@@ -290,7 +281,7 @@ def get_execution_report(rower, startdate, enddate, plan=None):
else:
plannedscore += 60
actualscore += 0
elif w.hrtss != 0:
elif w.hrtss >= 0:
if ratio > 0:
plannedscore += w.hrtss/ratio
actualscore += w.hrtss
@@ -313,7 +304,7 @@ def get_execution_report(rower, startdate, enddate, plan=None):
planned += [plannedscore]
executed += [actualscore]
data = pd.DataFrame({
data = pl.DataFrame({
'startdate': startdates,
'planned': planned,
'executed': executed,
@@ -444,8 +435,9 @@ def add_workouts_plannedsession(ws, ps, r):
for record in records:
record.delete()
df = dataprep.getsmallrowdata_db(
df = dataprep.read_data(
['time', 'cumdist'], ids=[w.id])
df = dataprep.remove_nulls_pl(df)
fastest_milliseconds, starttime, endtime = datautils.getfastest(
df, ps.sessionvalue, mode='distance')
@@ -475,11 +467,11 @@ def add_workouts_plannedsession(ws, ps, r):
for record in records:
record.delete()
df = dataprep.getsmallrowdata_db(
df = dataprep.read_data(
['time', 'cumdist'], ids=[w.id])
df = dataprep.remove_nulls_pl(df)
fastest_meters, starttime, endtime = datautils.getfastest(
df, ps.sessionvalue, mode='time')
if fastest_meters > 0:
w.plannedsession = ps
w.save()
@@ -1694,8 +1686,9 @@ def add_workout_fastestrace(ws, race, r, recordid=0, doregister=False):
record.coursecompleted = True
record.workoutid = ws[0].id
if race.sessiontype == 'fastest_distance':
df = dataprep.getsmallrowdata_db(
df = dataprep.read_data(
['time', 'cumdist'], ids=[ws[0].id])
df = dataprep.remove_nulls_pl(df)
fastest_milliseconds, startsecond, endsecond = datautils.getfastest(
df, race.sessionvalue, mode='distance')
velo = race.sessionvalue/fastest_milliseconds
@@ -1711,8 +1704,9 @@ def add_workout_fastestrace(ws, race, r, recordid=0, doregister=False):
record.endsecond = endsecond
record.save()
if race.sessiontype == 'fastest_time': # pragma: no cover
df = dataprep.getsmallrowdata_db(
df = dataprep.read_data(
['time', 'cumdist'], ids=[ws[0].id])
df = dataprep.remove_nulls_pl(df)
fastest_meters, startsecond, endsecond = datautils.getfastest(
df, race.sessionvalue, mode='time')
velo = fastest_meters/(60.*race.sessionvalue)
+17 -1
View File
@@ -6,7 +6,7 @@ from rest_framework import serializers
from rowers.models import (
Workout, Rower, FavoriteChart, VirtualRaceResult,
VirtualRace, GeoCourse, StandardCollection, CourseStandard,
GeoPolygon, GeoPoint, PlannedSession,
GeoPolygon, GeoPoint, PlannedSession, ForceCurveAnalysis
)
from django.core.exceptions import PermissionDenied
@@ -297,6 +297,22 @@ class GeoPointSerializer(serializers.ModelSerializer):
)
extra_kwargs = {'id': {'read_only': False, 'required': True}}
class ForceCurveAnalysisSerializer(serializers.ModelSerializer):
class Meta:
model = ForceCurveAnalysis
fields = (
'id',
'name',
'workout',
'dist_min',
'dist_max',
'spm_min',
'spm_max',
'work_min',
'work_max',
'include_rest_strokes'
)
class GeoPolygonSerializer(serializers.ModelSerializer):
points = GeoPointSerializer(many=True)
+37 -117
View File
@@ -56,7 +56,7 @@ from scipy.signal import savgol_filter
from scipy.interpolate import griddata
import rowingdata
from rowingdata import make_cumvalues
from rowingdata import make_cumvalues, make_cumvalues_array
from uuid import uuid4
from rowingdata import rowingdata as rdata
@@ -115,6 +115,9 @@ tpapilocation = TP_API_LOCATION
from requests_oauthlib import OAuth1, OAuth1Session
import pandas as pd
import polars as pl
from polars.exceptions import ColumnNotFoundError
from django_rq import job
from django.utils import timezone
@@ -127,7 +130,8 @@ from rowers import mytypes
from rowers.dataroutines import (
getsmallrowdata_db, updatecpdata_sql, update_c2id_sql,
getsmallrowdata_pd, updatecpdata_sql, update_c2id_sql,
read_data,
#update_workout_field_sql,
update_agegroup_db, update_strokedata,
add_c2_stroke_data_db, totaltime_sec_to_string,
@@ -2549,9 +2553,10 @@ def handle_otwsetpower(self, f1, boattype, boatclass, coastalbrand, weightvalue,
totaltime = totaltime + rowdata.df.loc[0, ' ElapsedTime (sec)']
except KeyError: # pragma: no cover
pass
df = getsmallrowdata_db(
df = getsmallrowdata_pd(
['power', 'workoutid', 'time'], ids=[workoutid],
debug=debug)
thesecs = totaltime
maxt = 1.05 * thesecs
logarr = datautils.getlogarr(maxt)
@@ -2583,53 +2588,6 @@ def handle_otwsetpower(self, f1, boattype, boatclass, coastalbrand, weightvalue,
return 1
@app.task
def handle_updateergcp(rower_id, workoutfilenames, debug=False, **kwargs):
therows = []
for f1 in workoutfilenames:
try:
rowdata = rdata(csvfile=f1)
except IOError: # pragma: no cover
try:
rowdata = rdata(csvfile=f1 + '.csv')
except IOError:
try:
rowdata = rdata(csvfile=f1 + '.gz')
except IOError:
rowdata = 0
if rowdata != 0:
therows.append(rowdata)
cpdata = rowingdata.cumcpdata(therows)
cpdata.columns = cpdata.columns.str.lower()
updatecpdata_sql(rower_id, cpdata['delta'], cpdata['cp'],
table='ergcpdata', distance=cpdata['distance'],
debug=debug)
return 1
@app.task
def handle_updatecp(rower_id, workoutids, debug=False, table='cpdata', **kwargs):
columns = ['power', 'workoutid', 'time']
df = getsmallrowdata_db(columns, ids=workoutids, debug=debug)
if df.empty: # pragma: no cover
return 0
maxt = 1.05*df['time'].max()/1000.
logarr = datautils.getlogarr(maxt)
dfgrouped = df.groupby(['workoutid'])
delta, cpvalue, avgpower = datautils.getcp(dfgrouped, logarr)
updatecpdata_sql(rower_id, delta, cpvalue, debug=debug, table=table)
return 1
@app.task
def handle_makeplot(f1, f2, t, hrdata, plotnr, imagename,
@@ -3179,43 +3137,6 @@ def handle_sendemail_invite_reject(email, name, teamname, managername,
return 1
@app.task
def handle_setcp(strokesdf, filename, workoutid, debug=False, **kwargs):
try:
os.remove(filename)
except FileNotFoundError:
pass
if not strokesdf.empty:
try:
totaltime = strokesdf['time'].max()
except KeyError: # pragma: no cover
return 0
try:
powermean = strokesdf['power'].mean()
except KeyError: # pragma: no cover
powermean = 0
if powermean != 0:
thesecs = totaltime
maxt = 1.05 * thesecs
if maxt > 0:
logarr = datautils.getlogarr(maxt)
dfgrouped = strokesdf.groupby(['workoutid'])
delta, cpvalues, avgpower = datautils.getcp(dfgrouped, logarr)
df = pd.DataFrame({
'delta': delta,
'cp': cpvalues,
'id': workoutid,
})
df.to_parquet(filename, engine='fastparquet',
compression='GZIP')
return 1
return 1 # pragma: no cover
@app.task
def handle_sendemail_invite_accept(email, name, teamname, managername,
@@ -3251,23 +3172,22 @@ graphql_url = "https://rp3rowing-app.com/graphql"
@app.task
def handle_update_wps(rid, types, ids, mode, debug=False, **kwargs):
df = getsmallrowdata_db(['time', 'driveenergy'], ids=ids)
df = read_data(['time', 'driveenergy'], ids=ids)
try:
mask = df['driveenergy'] > 100
except (KeyError, TypeError): # pragma: no cover
return 0
try:
wps_median = int(df.loc[mask, 'driveenergy'].median())
except ValueError: # pragma: no cover
return 0
wps_median = int(df.filter(pl.col("driveenergy")>100)["driveenergy"].median())
rower = Rower.objects.get(id=rid)
if mode == 'water':
rower.median_wps = wps_median
else:
else: # pragma: no cover
rower.median_wps_erg = wps_median
rower.save()
except ValueError: # pragma: no cover
wps_median = 0
except OverflowError:
wps_median = 0
except ColumnNotFoundError:
wps_median = 0
return wps_median
@@ -3901,7 +3821,7 @@ def fetch_strava_workout(stravatoken, oauth_data, stravaid, csvfilename, userid,
pace[np.isinf(pace)] = 0.0
try:
strokedata = pd.DataFrame({'t': 10*t,
strokedata = pl.DataFrame({'t': 10*t,
'd': 10*d,
'p': 10*pace,
'spm': spm,
@@ -3947,18 +3867,18 @@ def fetch_strava_workout(stravatoken, oauth_data, stravaid, csvfilename, userid,
starttimeunix = arrow.get(rowdatetime).timestamp()
res = make_cumvalues(0.1*strokedata['t'])
cum_time = res[0]
lapidx = res[1]
res = make_cumvalues_array(0.1*strokedata['t'].to_numpy())
cum_time = pl.Series(res[0])
lapidx = pl.Series(res[1])
unixtime = cum_time+starttimeunix
seconds = 0.1*strokedata.loc[:, 't']
seconds = 0.1*strokedata['t']
nr_rows = len(unixtime)
try:
latcoord = strokedata.loc[:, 'lat']
loncoord = strokedata.loc[:, 'lon']
latcoord = strokedata['lat']
loncoord = strokedata['lon']
if latcoord.std() == 0 and loncoord.std() == 0 and workouttype == 'water': # pragma: no cover
workouttype = 'rower'
except: # pragma: no cover
@@ -3968,29 +3888,29 @@ def fetch_strava_workout(stravatoken, oauth_data, stravaid, csvfilename, userid,
workouttype = 'rower'
try:
strokelength = strokedata.loc[:, 'strokelength']
strokelength = strokedata['strokelength']
except: # pragma: no cover
strokelength = np.zeros(nr_rows)
dist2 = 0.1*strokedata.loc[:, 'd']
dist2 = 0.1*strokedata['d']
try:
spm = strokedata.loc[:, 'spm']
except KeyError: # pragma: no cover
spm = strokedata['spm']
except (KeyError, ColumnNotFoundError): # pragma: no cover
spm = 0*dist2
try:
hr = strokedata.loc[:, 'hr']
except KeyError: # pragma: no cover
hr = strokedata['hr']
except (KeyError, ColumnNotFoundError): # pragma: no cover
hr = 0*spm
pace = strokedata.loc[:, 'p']/10.
pace = strokedata['p']/10.
pace = np.clip(pace, 0, 1e4)
pace = pace.replace(0, 300)
pace = pl.Series(pace).replace(0, 300)
velo = 500./pace
try:
power = strokedata.loc[:, 'power']
power = strokedata['power']
except KeyError: # pragma: no cover
power = 2.8*velo**3
@@ -3999,7 +3919,7 @@ def fetch_strava_workout(stravatoken, oauth_data, stravaid, csvfilename, userid,
# save csv
# Create data frame with all necessary data to write to csv
df = pd.DataFrame({'TimeStamp (sec)': unixtime,
df = pl.DataFrame({'TimeStamp (sec)': unixtime,
' Horizontal (meters)': dist2,
' Cadence (stokes/min)': spm,
' HRCur (bpm)': hr,
@@ -4019,10 +3939,10 @@ def fetch_strava_workout(stravatoken, oauth_data, stravaid, csvfilename, userid,
'cum_dist': dist2,
})
df.sort_values(by='TimeStamp (sec)', ascending=True)
df.sort('TimeStamp (sec)')
row = rowingdata.rowingdata(df=df)
row.write_csv(csvfilename, gzip=False)
row = rowingdata.rowingdata_pl(df=df)
row.write_csv(csvfilename, compressed=False)
# summary = row.allstats()
# maxdist = df['cum_dist'].max()
-1
View File
@@ -32,7 +32,6 @@
<div class="mapdiv">
{{ mapdiv|safe }}
{{ mapscript|safe }}
</div>
</li>
+2 -1
View File
@@ -37,8 +37,9 @@
<div class="mapdiv">
{{ mapdiv|safe }}
<script>
{{ mapscript|safe }}
</script>
</div>
</li>
</ul>
@@ -45,7 +45,9 @@
<li class="grid_4">
<div class="mapdiv">
{{ mapdiv|safe }}
<script>
{{ mapscript|safe }}
</script>
</div>
</li>
</ul>
-1
View File
@@ -54,7 +54,6 @@
<div class="mapdiv">
{{ mapdiv|safe }}
{{ mapscript|safe }}
</div>
</li>
+3 -6
View File
@@ -102,14 +102,11 @@
</li>
{% endif %}
<li class="grid_2">
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{{ interactiveplot |safe }}
<script src="https://d3js.org/d3.v6.js"></script>
{{ the_div|safe }}
{{ interactiveplot |safe }}
</li>
<li class="grid_4">
+3 -3
View File
@@ -116,7 +116,7 @@ function copyText() {
</li>
{% if user.is_authenticated and user == workout.user.user and not locked %}
<li class="grid_2">
<p>Paste link to you tube video below</p>
<p>Paste link to Youtube video below</p>
<p>Use the slider to locate start point for video on workout map</p>
<p>Playing the video will advance the data in synchonization. Use the regular YouTube
controls
@@ -169,9 +169,8 @@ function copyText() {
</li>
<li class="grid_2">
<div id="player"></div>
<script>
// 1. Code for the map
{{ mapscript | safe }}
<script>
// 2. This code loads the IFrame Player API code asynchronously.
@@ -189,6 +188,7 @@ function copyText() {
var videotime = 0;
var data = JSON.parse('{{ data|safe }}');
{% for id, metric in metrics.items %}
var {{ id }}_values = data["{{ id }}"];
// console.log("{{ id }}_values",{{ id }}_values);
+71
View File
@@ -0,0 +1,71 @@
{% extends "newbase.html" %}
{% load static %}
{% load rowerfilters %}
{% block title %}Rowsandall {% endblock %}
{% block meta %}
<style type="text/css">
circle {
cursor: pointer;
}
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 11px;
}
.tooltip {
padding: 1em;
width: 200px;
height: 200px;
background-color: #f00;
color: #fff;
position: absolute;
top: 0;
left: 0;
z-index: 999;
}
</style>
{% endblock %}
{% block main %}
<script src="https://d3js.org/d3.v3.min.js"></script>
<h1>Film Deaths</h1>
<ul class="main-content">
<li class="grid_4">
<div id="id_chart">
<script>
{{ the_script| safe }}
</script>
{{ the_div|safe }}
</div>
</li>
</ul>
{% endblock %}
{% block scripts %}
<script type='text/javascript'
src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'>
</script>
{% endblock %}
{% block sidebar %}
{% include 'menu_analytics.html' %}
{% endblock %}
+5 -12
View File
@@ -8,16 +8,6 @@
{% localtime on %}
{% block main %}
{{ js_res | safe }}
{{ css_res| safe }}
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.1.1.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{{ the_script |safe }}
<p>
{% if workout|previousworkout:rower.user %}
@@ -34,10 +24,9 @@
<h1>Flexible Chart</h1>
<ul class="main-content">
<canvas hidden id="canvas"></canvas>
<li class="grid_4">
<div id="theplot" class="flexplot">
{{ the_div|safe }}
</div>
</li>
<li class="grid_2">
<form enctype="multipart/form-data"
@@ -96,6 +85,10 @@
</li>
</ul>
<script src="https://d3js.org/d3.v6.js"></script>
{{ the_script |safe }}
{% endblock %}
{% endlocaltime %}
+3 -9
View File
@@ -8,16 +8,8 @@
{% localtime on %}
{% block main %}
{{ js_res | safe }}
{{ css_res| safe }}
<script src="https://d3js.org/d3.v6.js"></script>
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script src="https://cdn.pydata.org/bokeh/release/bokeh-widgets-3.1.1.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{{ the_script |safe }}
<p>
{% if workout|previousworkout:rower.user %}
@@ -34,6 +26,7 @@
<h1>Chart Stack</h1>
<ul class="main-content">
<canvas hidden id="canvas"></canvas>
<li class="grid_4">
<div id="theplot">
{{ the_div|safe }}
@@ -57,6 +50,7 @@
</form>
</li>
</ul>
{{ the_script |safe }}
{% endblock %}
{% endlocaltime %}
+4 -9
View File
@@ -6,25 +6,20 @@
{% block main %}
{{ js_res | safe }}
{{ css_res| safe }}
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="https://d3js.org/d3.v6.js"></script>
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script src="https://cdn.pydata.org/bokeh/release/bokeh-widgets-3.1.1.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{{ the_script |safe }}
<h1>Force Curve Analysis for {{ rower.user.first_name }} {{ rower.user.last_name }}</h1>
<ul class="main-content">
<canvas hidden id="canvas"></canvas>
{% if the_div %}
<li class="grid_4">
<div id="theplot" class="flexplot">
{{ the_div|safe }}
{{ the_script |safe }}
</div>
</li>
{% endif %}
+10 -30
View File
@@ -8,23 +8,17 @@
{% localtime on %}
{% block main %}
{{ js_res | safe }}
{{ css_res| safe }}
<script src="https://d3js.org/d3.v6.js"></script>
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script src="https://cdn.pydata.org/bokeh/release/bokeh-widgets-3.1.1.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{{ the_script |safe }}
<h1>Empower Force Curve</h1>
<ul class="main-content">
<canvas hidden id="canvas"></canvas>
<li class="grid_4">
<div id="theplot" class="flexplot">
{{ the_div|safe }}
{{ the_script |safe }}
</div>
</li>
@@ -52,27 +46,13 @@
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script>
$(document).ready(function() {
var slider_min_spm = Bokeh.documents[0].get_model_by_name('min_spm_slider')
slider_min_spm.value = {{ spm_min }}
console.log('min spm value after:', slider_min_spm.value)
var slider_max_spm = Bokeh.documents[0].get_model_by_name('max_spm_slider')
slider_max_spm.value = {{ spm_max }}
console.log('max spm value after:', slider_max_spm.value)
var slider_min_work = Bokeh.documents[0].get_model_by_name('min_work_slider')
slider_min_work.value = {{ work_min }}
console.log('min work value after:', slider_min_work.value)
var slider_max_work = Bokeh.documents[0].get_model_by_name('max_work_slider')
slider_max_work.value = {{ work_max }}
console.log('max work value after:', slider_max_work.value)
var slider_min_dist = Bokeh.documents[0].get_model_by_name('min_dist_slider')
slider_min_dist.value = {{ dist_min }}
console.log('min dist value after:', slider_min_dist.value)
var slider_max_dist = Bokeh.documents[0].get_model_by_name('max_dist_slider')
slider_max_dist.value = {{ dist_max }}
console.log('max dist value after:', slider_max_dist.value)
var annotation = Bokeh.documents[0].get_model_by_name('annotation')
annotation.value = "{{ annotation }}"
console.log('Annotation set to ', annotation.value)
var label = $('label[for="' + $("#id_plotcircles").attr('id') + '"]');
label.attr("class", "hidden")
var label2 = $('label[for="' + $("#id_plotlines").attr('id') + '"]');
label2.attr("class", "hidden")
var label3 = $('label[for="' + $("#id_includereststrokes").attr('id') + '"]');
label3.attr("class", "hidden")
});
</script>
+6 -11
View File
@@ -60,17 +60,6 @@
{% block main %}
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
<div id="id_script">
{{ chartscript |safe }}
</div>
{% if rower.user %}
<h1>Gold Medal Scores for {{ rower.user.first_name }} </h1>
{% else %}
@@ -79,6 +68,7 @@
<ul class="main-content">
<canvas hidden id="canvas"></canvas>
<li class="grid_4">
<p>Hover over the workout to see details, click on the workout to open in a
separate page.</p>
@@ -136,6 +126,11 @@
</li>
</ul>
<script src="https://d3js.org/d3.v7.min.js"></script>
<div id="id_script">
{{ chartscript |safe }}
</div>
{% endblock %}
+2 -1
View File
@@ -6,6 +6,7 @@
{% block main %}
<script src="https://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="https://d3js.org/d3/v6/js"></script>
<script>
$(function() {
@@ -86,7 +87,6 @@
<li class="grid_4">
<div id="id_chart">
{{ the_div|safe }}
</div>
</li>
@@ -129,6 +129,7 @@
var counter=0;
var script = json.script;
var div = json.div;
console.log(div)
console.log('set vars');
$("#id_sitready").remove();
console.log('sitready removed');
+3 -5
View File
@@ -6,12 +6,9 @@
{% block main %}
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
<script src="https://d3js.org/d3.v6.js"></script>
{{ interactiveplot |safe }}
@@ -24,6 +21,7 @@
</li>
</ul>
{{ interactiveplot |safe }}
{% endif %}
{% endblock %}
+7 -13
View File
@@ -5,25 +5,14 @@
{% block title %}Rowsandall {% endblock %}
{% block main %}
<div id="id_css_res">
<link rel="stylesheet" href="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.css" type="text/css" />
<link rel="stylesheet" href="https://cdn.pydata.org/bokeh/release/bokeh-widgets-3.1.1.min.css" type="text/css" />
</div>
<div id="id_js_res">
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script src="https://cdn.pydata.org/bokeh/release/bokeh-widgets-3.1.1.min.js"></script>
<script src="https://d3js.org/d3.v7.min.js"></script>
</div>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
<div id="id_script">
</div>
<div id="t_script">
{{ tscript|safe}}
</div>
<div id="activities_script">
</div>
@@ -90,6 +79,10 @@
</li>
<li class="grid_2">
{{ tdiv|safe }}
<div id="t_script">
{{ tscript|safe}}
</div>
</li>
<li class="grid_2">
<div id="id_chart">
@@ -157,7 +150,6 @@
var activities_chart = json.activities_chart
$("#id_sitready").remove();
$("#id_chart").append(div);
// $("#id_script").append("<s"+"cript>"+script+"</s"+"cript>");
$("#id_script").append(script);
$("#id_activities").append(activities_chart)
$("#activities_script").append(activities_script)
@@ -165,6 +157,8 @@
$("#total_maxhr").append(totalsdict.hrmax);
$("#total_power").append(totalsdict.powermean);
$("#total_maxpower").append(totalsdict.powermax);
$("#selectors").hide();
listofdicts.forEach(function(item){
var id = "#"+item.id+"_hr";
$(id).append(item.hrmean);
+3 -7
View File
@@ -6,24 +6,20 @@
{% block main %}
{{ js_res | safe }}
{{ css_res| safe }}
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script src="https://cdn.pydata.org/bokeh/release/bokeh-widgets-3.1.1.min.js"></script>
<script src="https://d3js.org/d3.v6.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{{ the_script |safe }}
<h1>In-Stroke Analysis for {{ rower.user.first_name }} {{ rower.user.last_name }}</h1>
<ul class="main-content">
<canvas hidden id="canvas"></canvas>
{% if the_div %}
<li class="grid_4">
<div id="theplot" class="flexplot">
{{ the_div|safe }}
{{ the_script |safe }}
</div>
</li>
{% endif %}
+19 -14
View File
@@ -11,10 +11,8 @@
<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");
@@ -30,11 +28,14 @@
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>');
$("#id_script").replaceWith('<div id="id_script">'+data.script+'</d'+'iv>');
$("#dd").empty();
$("#dd").html(data.dd);
$("#ds").empty();
$('#ds').html(data.ds)
}
});
}
@@ -119,15 +120,10 @@ $( function() {
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">
<canvas hidden id="canvas"></canvas>
<li class="grid_4" >
{% if user.rower|is_basic %}
@@ -144,7 +140,7 @@ $( function() {
</table>
<div id="slider-range"></div>
<p>
<label for="amountspm">Active Range:</label>
<label for="amountspm">Stroke Rate:</label>
<input type="text" id="amountspm" readonly style="border:0; color:#1c75bc; font-weight:bold;">
</p>
<div id="slider-timerange"></div>
@@ -168,14 +164,23 @@ $( function() {
</p>
</li>
<li class="grid_4">
<script src="https://d3js.org/d3.v6.js"></script>
<div id="id_chart" class="flexplot">
{{ the_div|safe }}
</div>
<div id="id_script">
{{ the_script |safe }}
</div>
</li>
<li class="grid_4">
<script src="https://d3js.org/d3.v6.js"></script>
<li class="grid_2" id="interactive">
<div id="dd" class="flexplot">
{{ dd|safe }}
</div>
<div id="ds">
{{ ds |safe }}
</div>
</li>
</ul>
+3 -14
View File
@@ -229,22 +229,11 @@
Total meters: {{ totalmeters }}. Total time {{ totalhours }}:{{ totalminutes }}h.
<a href="/rowers/history/">Dig deeper</a>.
</p>
<p>
Activity chart by
<a href="{{ request.get_path }}?yaxis=trimp">TRIMP</a>,
<a href="{{ request.get_path }}?yaxis=rscore">rScore</a>,
<a href="{{ request.get_path }}?yaxis=duration">Time</a>.
</p>
<link rel="stylesheet" href="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.css" type="text/css" />
<link rel="stylesheet" href="https://cdn.pydata.org/bokeh/release/bokeh-widgets-3.1.1.min.css" type="text/css" />
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script async="true" type="text/text/javascript">
Bokeh.set_log_level("info")
</script>
{{ interactiveplot |safe }}
<script src="https://d3js.org/d3.v7.min.js"></script>
{{ the_div |safe }}
{{ interactiveplot |safe }}
</li>
{% if announcements %}
<li class="grid_4">
-1
View File
@@ -26,7 +26,6 @@
<div style="height:100%;" id="theplot" class="flexplot mapdiv">
{{ mapdiv|safe }}
{{ mapscript|safe }}
</li>
</ul>
+2
View File
@@ -25,7 +25,9 @@
<div class="mapdiv">
{{ mapdiv|safe }}
<script>
{{ mapscript|safe }}
</script>
</div>
-25
View File
@@ -101,11 +101,6 @@
<i class="fas fa-dumbbell fa-fw"></i>&nbsp;Force Curve
</a>
</li>
<li id="chart-otwpower">
<a href="/rowers/workout/{{ workout.id|encode }}/interactiveotwplot/">
<i class="fal fa-calculator-alt fa-fw"></i>&nbsp;Corrected Pace Plot
</a>
</li>
{% endif %}
</ul>
</li>
@@ -294,26 +289,6 @@
<label for="group-advanced">Advanced</label>
<ul>
{% if workout|water %}
<li id="advanced-wind">
<a href="/rowers/workout/{{ workout.id|encode }}/wind/">
<i class="fas fa-pennant fa-fw"></i>&nbsp;Wind
</a>
</li>
<li id="advanced-stream">
<a href="/rowers/workout/{{ workout.id|encode }}/stream/">
<i class="fas fa-stream fa-fw"></i>&nbsp;Stream
</a>
</li>
<li id="advanced-otwpower">
<a href="/rowers/workout/{{ workout.id|encode }}/otwsetpower/">
<i class="fas fa-calculator-alt fa-fw"></i>&nbsp;OTW Power
</a>
</li>
<li id="advanced-otwpower">
<a href="/rowers/workout/{{ workout.id|encode }}/zeropower-confirm/">
<i class="fas fa-eraser fa-fw"></i>&nbsp;Remove Power Data
</a>
</li>
{% if 'speedcoach2' in workout.workoutsource or 'nklinklogbook' in workout.workoutsource %}
<li id="advanced-usegps">
<a href="/rowers/workout/{{ workout.id|encode }}/otwusegps/">
+3 -5
View File
@@ -6,12 +6,9 @@
{% block main %}
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
<script src="https://d3js.org/d3.v6.js"></script>
{{ interactiveplot |safe }}
<h1>Interactive Comparison</h1>
@@ -20,6 +17,7 @@
<li class="grid_4">
<div>
{{ the_div|safe }}
{{ interactiveplot |safe }}
</div>
</li>
-1
View File
@@ -3,7 +3,6 @@
<div class="mapdiv">
{{ mapdiv|safe }}
{{ mapscript|safe }}
</div>
</li>
+18 -14
View File
@@ -8,6 +8,15 @@
<script type='text/javascript'
src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'>
</script>
{% endblock %}
{% block main %}
<script src="https://d3js.org/d3.v6.js">
</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script>
function submit_form() {
@@ -25,11 +34,14 @@
dataType: 'json',
success: function(data) {
console.log(data.div);
// var parsedJSON = $.parseJSON(data); //
$("#id_script").replaceWith('<div id="id_script">'+data.script+'</d'+'iv>');
// $('#id_script').remove()
$("#id_chart").replaceWith('<div id="id_chart">'+data.div+'</d'+'iv>');
$("#id_chart").append(data.script)
$("#endfitness").html(data.endfitness)
$("#endfatigue").html(data.endfatigue)
$("#endform").html(data.endform)
@@ -59,19 +71,6 @@
});
});
</script>
{% endblock %}
{% block main %}
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
<div id="id_script">
{{ chartscript |safe }}
</div>
@@ -83,10 +82,12 @@
<ul class="main-content">
<canvas hidden id="canvas"></canvas>
<li class="grid_4">
<div id="id_chart">
{{ the_div|safe }}
</div>
</li>
<li class="grid_1">
<form id="performanceform" enctype="multipart/form-data" method="post">
@@ -188,6 +189,9 @@
</li>
</ul>
<div id="id_script">
{{ chartscript |safe }}
</div>
{% endblock %}
+53
View File
@@ -0,0 +1,53 @@
{% extends "newbase.html" %}
{% load static %}
{% load rowerfilters %}
{% block title %}Rowsandall {% endblock %}
{% block main %}
<div id="id_css_res">
<link rel="stylesheet" href="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.css" type="text/css" />
<link rel="stylesheet" href="https://cdn.pydata.org/bokeh/release/bokeh-widgets-3.1.1.min.css" type="text/css" />
</div>
<div id="id_js_res">
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.1.1.min.js"></script>
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.1.1.min.js"></script>
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.1.1.min.js"></script>
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-3.1.1.min.js"></script>
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-api-3.1.1.min.js"></script>
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-api-3.1.1.min.js"></script>
</div>
<script src="https://code.jquery.com/jquery-1.9.1.min.js"></script>
<h1>Sleep</h1>
<ul class="main-content">
<li class="grid_4">
<div id="id_chart">
<script>
{{ the_script| safe }}
</script>
{{ the_div|safe }}
</div>
</li>
</ul>
{% endblock %}
{% block scripts %}
<script type='text/javascript'
src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'>
</script>
{% endblock %}
{% block sidebar %}
{% include 'menu_analytics.html' %}
{% endblock %}
+1 -1
View File
@@ -23,7 +23,7 @@
</li>
<li class="grid_2">
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script src="https://d3js.org/d3.v6.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
+3 -6
View File
@@ -89,14 +89,11 @@
</p>
</li>
<li class="grid_2">
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{{ interactiveplot |safe }}
<script src="https://d3js.org/d3.v6.js"></script>
{{ the_div |safe }}
{{ interactiveplot |safe }}
</li>
<li class="grid_2">
<h1>Feeling lucky?</h1>
+5 -8
View File
@@ -6,25 +6,22 @@
{% block main %}
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script src="https://cdn.pydata.org/bokeh/release/bokeh-widgets-3.1.1.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
<script src="https://code.jquery.com/jquery-1.9.1.min.js"></script>
<h1>Training Plan - {{ plan.name }}</h1>
{{ the_script | safe }}
<ul class="main-content">
<li class="grid_4">
<div id="theplot" class="flexplot">
{{ the_div|safe }}
</div>
</li>
</ul>
<script src="https://d3js.org/d3.v6.js"></script>
{{ the_script | safe }}
{% endblock %}
{% block scripts %}
+3 -5
View File
@@ -11,17 +11,15 @@
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
<div id="id_js_res">
<script src="https://d3js.org/d3.v7.min.js"></script>
</div>
<h1>Training Zones</h1>
<div id="id_script">
</div>
<ul class="main-content">
<li class="grid_4">
<div id="id_chart">
+2 -19
View File
@@ -14,6 +14,7 @@
}
</script>
<script src="https://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="https://d3js.org/d3.v6.js"></script>
<script>
$(function() {
@@ -387,29 +388,11 @@
</script>
<div id="id_css_res">
</div>
<div id="id_js_res">
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-3.1.1.min.js"
crossorigin="anonymous"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.1.1.min.js"
crossorigin="anonymous"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.1.1.min.js"
crossorigin="anonymous"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.1.1.min.js"
crossorigin="anonymous"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-3.1.1.min.js"
crossorigin="anonymous"></script>
</div>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
<div id="id_script">
</div>
<ul class="main-content">
<canvas hidden id="canvas"></canvas>
<li class="grid_4">
<div id="id_chart">
{{ the_div|safe }}
+2
View File
@@ -67,7 +67,9 @@
<div class="mapdiv">
{{ gmapdiv |safe }}
<script>
{{ gmap |safe }}
</script>
</div>
</li>
</ul>
+3 -6
View File
@@ -98,14 +98,11 @@
</li>
{% endif %}
<li class="grid_2">
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{{ interactiveplot |safe }}
<script src="https://d3js.org/d3.v6.js"></script>
{{ the_div|safe }}
{{ interactiveplot |safe }}
</li>
<li class="grid_4">
+2 -5
View File
@@ -16,10 +16,6 @@
{% block meta %}
{% leaflet_js %}
{% leaflet_css %}
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{% for chart in charts %}
{{ chart.script |safe }}
@@ -51,7 +47,7 @@
"<a href=\"/rowers/workout/"+workoutid+"/flexchart?favoritechart="+counter+"\">"+element.div+"</a>"+
"</li>");
$("#id_thumbscripts").append(element.script);
$("#id_thumbs").append(element.script);
counter += 1;
});
});
@@ -64,6 +60,7 @@
{% block main %}
<script src="https://d3js.org/d3.v6.js"></script>
<p>
{% if workout|previousworkout:rower.user %}
<a href="/rowers/workout/{{ workout|previousworkout:rower.user }}/workflow"
+2 -2
View File
@@ -217,9 +217,9 @@
<div class="mapdiv">
{{ mapdiv|safe }}
</div>
<script>
{{ mapscript|safe }}
</script>
</li>
{% endif %}
{% for graph in graphs %}
+4 -9
View File
@@ -131,21 +131,16 @@
{{ mapdiv|safe }}
<script>
{{ mapscript|safe }}
</script>
</div>
</li>
{% endif %}
<li class="grid_2">
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{{ interactiveplot |safe }}
<script src="https://d3js.org/d3.v6.js"></script>
{{ the_div|safe }}
{{ interactiveplot |safe }}
</li>
{% for graph in graphs %}
<li>
+18 -6
View File
@@ -38,6 +38,7 @@ from nose.tools import assert_true
from mock import Mock, patch
#from minimocktest import MockTestCase
import pandas as pd
import polars as pl
import arrow
from django.http import HttpResponseRedirect
@@ -271,7 +272,7 @@ def mocked_fetchcperg(*args, **kwargs):
return df
import pandas as pd
def mocked_read_df_sql(id): # pragma: no cover
df = pd.read_csv('rowers/tests/testdata/fake_strokedata.csv')
@@ -300,7 +301,7 @@ def mocked_getrowdata_db(*args, **kwargs):
return df,row
def mocked_getrowdata_uh(*args, **kwargs): # pragma: no cover
df = pd.read_csv('rowers/tests/testdata/uhfull.csv')
df = pl.read_csv('rowers/tests/testdata/uhfull.csv')
id = kwargs['id']
@@ -308,13 +309,19 @@ def mocked_getrowdata_uh(*args, **kwargs): # pragma: no cover
return df, row
def mocked_getsmallrowdata_uh(*args, **kwargs): # pragma: no cover
df = pl.read_csv('rowers/tests/testdata/uhfull.csv')
return df
def mocked_getsmallrowdata_uh_pd(*args, **kwargs): # pragma: no cover
df = pd.read_csv('rowers/tests/testdata/uhfull.csv')
return df
def mocked_getsmallrowdata_forfusion(*args, **kwargs):
df = pd.read_csv('rowers/tests/testdata/getrowdata_mock.csv')
df = pl.read_csv('rowers/tests/testdata/getrowdata_mock.csv')
return df
@@ -335,6 +342,11 @@ def mocked_getsmallrowdata_db(*args, **kwargs):
return df
def mocked_read_data(*args, **kwargs):
df = pl.read_csv('rowers/tests/testdata/colsfromdb.csv')
return df
def mocked_getsmallrowdata_db_updatecp(*args, **kwargs): # pragma: no cover
df = pd.read_csv('rowers/tests/testdata/colsfromdb.csv')
@@ -351,8 +363,8 @@ def mocked_getsmallrowdata_db_water(*args, **kwargs): # pragma: no cover
return df
def mocked_getsmallrowdata_db_wps(*args, **kwargs): # pragma: no cover
df = pd.read_csv('rowers/tests/testdata/driveenergies.csv')
def mocked_read_data_wps(*args, **kwargs): # pragma: no cover
df = pl.read_csv('rowers/tests/testdata/driveenergies.csv')
return df
@@ -384,7 +396,7 @@ def mocked_read_cols_df_sql(*args, **kwargs):
def mock_workout_summaries(*args, **kwargs):
df = pd.read_csv('rowers/tests/testdata/workout_summaries.csv')
df = pl.read_csv('rowers/tests/testdata/workout_summaries.csv')
return df
def mocked_read_df_cols_sql_multi(ids, columns, convertnewtons=True): # pragma: no cover
+1
View File
@@ -65,6 +65,7 @@ from nose.tools import assert_true
from mock import Mock, patch
#from minimocktest import MockTestCase
import pandas as pd
import polars as pl
import rowers.c2stuff as c2stuff
import rowers.rojabo_stuff as rojabo_stuff
+46 -76
View File
@@ -43,10 +43,10 @@ class ListWorkoutTest(TestCase):
pass
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.read_data')
@patch('rowers.dataprep.myqueue')
def test_list_workouts(self, mocked_sqlalchemy,
mocked_getsmallrowdata_db,
mocked_read_data,
mocked_myqueue):
login = self.c.login(username=self.u.username, password=self.password)
@@ -169,8 +169,8 @@ class ForcecurveTest(TestCase):
pass
@patch('rowers.dataprep.getsmallrowdata_db',side_effect = mocked_getempowerdata_db)
def test_forcecurve_plot(self, mocked_getsmallrowdata_db):
@patch('rowers.dataprep.read_data',side_effect = mocked_read_data)
def test_forcecurve_plot(self, mocked_read_data):
login = self.c.login(username=self.u.username, password = self.password)
self.assertTrue(login)
@@ -296,9 +296,9 @@ class WorkoutCompareTestNew(TestCase):
pass
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.read_data')
def test_workouts_compare(self, mocked_sqlalchemy,
mocked_getsmallrowdata_db):
mocked_read_data):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
@@ -309,9 +309,9 @@ class WorkoutCompareTestNew(TestCase):
self.assertEqual(response.status_code,200)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db)
@patch('rowers.dataprep.read_data', side_effect=mocked_read_data)
def test_workouts_compare_submit(self, mocked_sqlalchemy,
mocked_getsmallrowdata_db):
mocked_read_data):
login = self.c.login(username=self.u.username,password=self.password)
self.assertTrue(login)
@@ -398,9 +398,9 @@ class WorkoutBoxPlotTestNew(TestCase):
pass
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.read_data')
def test_workouts_boxplot(self, mocked_sqlalchemy,
mocked_getsmallrowdata_db):
mocked_read_data):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
@@ -411,9 +411,9 @@ class WorkoutBoxPlotTestNew(TestCase):
self.assertEqual(response.status_code,200)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db)
@patch('rowers.dataprep.read_data', side_effect=mocked_read_data)
def test_workouts_boxplot_submit(self, mocked_sqlalchemy,
mocked_getsmallrowdata_db):
mocked_read_data):
login = self.c.login(username=self.u.username,password=self.password)
self.assertTrue(login)
@@ -499,9 +499,9 @@ class WorkoutHistoTestNew(TestCase):
pass
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.read_data')
def test_workouts_histo(self, mocked_sqlalchemy,
mocked_getsmallrowdata_db):
mocked_read_data):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
@@ -512,9 +512,9 @@ class WorkoutHistoTestNew(TestCase):
self.assertEqual(response.status_code,200)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db)
@patch('rowers.dataprep.read_data', side_effect=mocked_read_data)
def test_workouts_histo_submit(self, mocked_sqlalchemy,
mocked_getsmallrowdata_db):
mocked_read_data):
login = self.c.login(username=self.u.username,password=self.password)
self.assertTrue(login)
@@ -600,9 +600,9 @@ class History(TestCase):
pass
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db)
@patch('rowers.dataprep.read_data',side_effect=mocked_read_data)
def test_workouts_history(self, mocked_sqlalchemy,
mocked_getsmallrowdata_db):
mocked_read_data):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
@@ -618,9 +618,9 @@ class History(TestCase):
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db)
@patch('rowers.dataprep.read_data', side_effect=mocked_read_data)
def test_workouts_history_submit(self, mocked_sqlalchemy,
mocked_getsmallrowdata_db):
mocked_read_data):
login = self.c.login(username=self.u.username,password=self.password)
self.assertTrue(login)
@@ -713,9 +713,9 @@ class GoldMedalScores(TestCase):
pass
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.read_data')
def test_workouts_goldmedalscores(self, mocked_sqlalchemy,
mocked_getsmallrowdata_db):
mocked_read_data):
ws = Workout.objects.filter(rankingpiece=True)
self.assertEqual(ws.count(),2)
@@ -729,9 +729,9 @@ class GoldMedalScores(TestCase):
self.assertEqual(response.status_code,200)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db)
@patch('rowers.dataprep.read_data', side_effect=mocked_read_data)
def test_workouts_goldmedalscores_submit(self, mocked_sqlalchemy,
mocked_getsmallrowdata_db):
mocked_read_data):
login = self.c.login(username=self.u.username,password=self.password)
self.assertTrue(login)
@@ -795,9 +795,9 @@ class WorkoutFlexallTestNew(TestCase):
pass
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.read_data')
def test_workouts_flexall(self, mocked_sqlalchemy,
mocked_getsmallrowdata_db):
mocked_read_data):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
@@ -808,9 +808,9 @@ class WorkoutFlexallTestNew(TestCase):
self.assertEqual(response.status_code,200)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db)
@patch('rowers.dataprep.read_data', side_effect=mocked_read_data)
def test_workouts_flexall_submit(self, mocked_sqlalchemy,
mocked_getsmallrowdata_db):
mocked_read_data):
login = self.c.login(username=self.u.username,password=self.password)
self.assertTrue(login)
@@ -903,9 +903,9 @@ class WorkoutStatsTestNew(TestCase):
pass
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.read_data')
def test_workouts_stats(self, mocked_sqlalchemy,
mocked_getsmallrowdata_db):
mocked_read_data):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
@@ -916,11 +916,11 @@ class WorkoutStatsTestNew(TestCase):
self.assertEqual(response.status_code,200)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db)
@patch('rowers.dataprep.read_data', side_effect=mocked_read_data)
@patch('rowers.dataprep.read_cols_df_sql', side_effect=mocked_read_cols_df_sql)
def test_analysis_data(self,
mocked_sqlalchemy,
mocked_getsmallrowdata_db,
mocked_read_data,
mocked_read_cols_df_sql,
):
@@ -979,11 +979,11 @@ class WorkoutStatsTestNew(TestCase):
script, div = comparisondata(workouts,options)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db)
@patch('rowers.dataprep.read_data', side_effect=mocked_read_data)
@patch('rowers.dataprep.read_cols_df_sql', side_effect=mocked_read_cols_df_sql)
def test_analysis_data2(self,
mocked_sqlalchemy,
mocked_getsmallrowdata_db,
mocked_read_data,
mocked_read_cols_df_sql,
):
@@ -1042,11 +1042,11 @@ class WorkoutStatsTestNew(TestCase):
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db)
@patch('rowers.dataprep.read_data', side_effect=mocked_read_data)
@patch('rowers.dataprep.read_cols_df_sql', side_effect=mocked_read_cols_df_sql)
def test_analysis_data2(self,
mocked_sqlalchemy,
mocked_getsmallrowdata_db,
mocked_read_data,
mocked_read_cols_df_sql,
):
@@ -1120,9 +1120,9 @@ class WorkoutStatsTestNew(TestCase):
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db)
@patch('rowers.dataprep.read_data', side_effect=mocked_read_data)
def test_workouts_stats_submit(self, mocked_sqlalchemy,
mocked_getsmallrowdata_db):
mocked_read_data):
login = self.c.login(username=self.u.username,password=self.password)
self.assertTrue(login)
@@ -1241,9 +1241,9 @@ class MarkerPerformanceTest(TestCase):
pass
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.read_data')
def test_create_marker_workouts(self, mocked_sqlalchemy,
mocked_getsmallrowdata_db):
mocked_read_data):
login = self.c.login(username=self.u.username,password=self.password)
self.assertTrue(login)
@@ -1257,8 +1257,8 @@ class MarkerPerformanceTest(TestCase):
self.assertRedirects(response, expected_url=expected_url, status_code=302,target_status_code=200)
@patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_uh)
def test_trainingzones_view(self,mocked_getsmallrowdata_db):
@patch('rowers.dataprep.read_data', side_effect=mocked_getsmallrowdata_uh)
def test_trainingzones_view(self,mocked_getsmallrowdata_uh):
login = self.c.login(username=self.u.username,password=self.password)
self.assertTrue(login)
@@ -1326,9 +1326,9 @@ class MarkerPerformanceTest(TestCase):
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.read_data')
def test_performancemanager_view(self, mocked_sqlalchemy,
mocked_getsmallrowdata_db):
mocked_read_data):
login = self.c.login(username=self.u.username,password=self.password)
self.assertTrue(login)
@@ -1356,36 +1356,6 @@ class MarkerPerformanceTest(TestCase):
response = self.c.post(url,form_data)
self.assertEqual(response.status_code,200)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
def test_ranking_view2(self, mocked_sqlalchemy,
mocked_getsmallrowdata_db):
login = self.c.login(username=self.u.username,password=self.password)
self.assertTrue(login)
startdate = (self.user_workouts[0].startdatetime-datetime.timedelta(days=3)).date()
enddate = (self.user_workouts[0].startdatetime+datetime.timedelta(days=3)).date()
url = reverse('rankings_view2')
response = self.c.get(url)
self.assertEqual(response.status_code,200)
form_data = {
'startdate':startdate.strftime("%Y-%m-%d"),
'enddate': enddate.strftime("%Y-%m-%d"),
'doform': True,
'dofatigue': True,
'metricchoice':'rscore',
'modelchoice': 'coggan',
'daterange': '',
}
form = DateRangeForm(form_data)
if not form.is_valid():
print(form.errors)
self.assertTrue(form.is_valid())
response = self.c.post(url,form_data)
self.assertEqual(response.status_code,200)
class AlertTest(TestCase):
def setUp(self):
@@ -1443,9 +1413,9 @@ class AlertTest(TestCase):
pass
@patch('rowers.dataprep.create_engine')
@patch('rowers.alerts.getsmallrowdata_db')
@patch('rowers.alerts.read_data')
def test_alerts(self, mocked_sqlalchemy,
mocked_getsmallrowdata_db):
mocked_read_data):
login = self.c.login(username=self.u.username,password=self.password)
self.assertTrue(login)
+11 -5
View File
@@ -58,6 +58,8 @@ class OwnApi(TestCase):
response = self.c.get(url)
self.assertEqual(response.status_code,200)
with patch('rowers.dataprep.read_data') as mock_read_data:
mock_read_data.return_value = pl.read_csv('rowers/tests/testdata/colsfromdb.csv')
url = reverse('strokedatajson',kwargs={'id':w.id})
request = self.factory.get(url)
@@ -67,10 +69,11 @@ class OwnApi(TestCase):
self.assertEqual(response.status_code,200)
# response must be json
strokedata = json.loads(response.content)
df = pd.DataFrame(strokedata)
self.assertFalse(df.empty)
strokedata = json.loads(response.content)
df = pl.from_dict(strokedata)
self.assertFalse(df.is_empty())
form_data = {
"distance": [23, 46, 48],
@@ -119,13 +122,16 @@ class OwnApi(TestCase):
request = self.factory.get(url)
request.user = self.u
force_authenticate(request, user=self.u)
with patch('rowers.dataprep.read_data') as mock_read_data:
mock_read_data.return_value = pl.read_csv('rowers/tests/testdata/colsfromdb.csv')
response = strokedatajson_v2(request,id=w.id)
self.assertEqual(response.status_code,200)
# response must be json
strokedata = json.loads(response.content)
df = pd.DataFrame(strokedata)
self.assertFalse(df.empty)
df = pl.from_dicts(strokedata['data'])
self.assertFalse(df.is_empty())
form_data = {
+2 -46
View File
@@ -484,54 +484,10 @@ class AsyncTaskTests(TestCase):
res = tasks.handle_c2_import_stroke_data(c2token,c2id,workoutid,starttimeunix,csvfilename)
self.assertEqual(res,1)
@patch('rowers.tasks.grpc',side_effect=mocked_grpc)
@patch('rowers.tasks.send_template_email',side_effect=mocked_send_template_email)
def test_handle_otwsetpower(self,mocked_send_template_email,mocked_grpc):
f1 = get_random_file(filename='rowers/tests/testdata/sprintervals.csv')['filename']
boattype = '1x'
boatclass = 'water'
coastalbrand = 'other'
weightvalue = 80.
first_name = self.u.first_name
last_name = self.u.last_name
email = self.u.email
workoutid = self.wwater.id
job = fakerequest()
res = tasks.handle_otwsetpower(f1,boattype,boatclass,coastalbrand,
weightvalue,first_name,last_name,email,workoutid,
jobkey='23')
self.assertEqual(res,1)
@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.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.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'
workoutids = 1
res = tasks.handle_setcp(strokesdf,filename,1)
self.assertEqual(res,1)
try:
os.remove(filename)
except FileNotFoundError:
pass
@patch('rowers.dataprep.getsmallrowdata_db')
def test_handle_update_wps(self,mocked_getsmallrowdata_db_wps):
@patch('rowers.dataprep.read_data')
def test_handle_update_wps(self,mocked_read_data_wps):
ids = [1,2,3]
result = tasks.handle_update_wps(self.r.id,['water'],ids,mode='water')
+33 -178
View File
@@ -62,10 +62,10 @@ class ListWorkoutTest(TestCase):
pass
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.read_data')
@patch('rowers.dataprep.myqueue')
def test_list_workouts(self, mocked_sqlalchemy,
mocked_getsmallrowdata_db,
mocked_read_data,
mocked_myqueue):
login = self.c.login(username=self.u.username, password=self.password)
@@ -87,10 +87,10 @@ class ListWorkoutTest(TestCase):
self.assertEqual(response.status_code,200)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.read_data')
@patch('rowers.dataprep.myqueue')
def test_bulk_workouts(self, mocked_sqlalchemy,
mocked_getsmallrowdata_db,
mocked_read_data,
mocked_myqueue):
login = self.c.login(username=self.u.username, password=self.password)
@@ -234,9 +234,9 @@ class WorkoutViewTest(TestCase):
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.read_data')
@patch('rowers.middleware.myqueue')
def test_forcecurve(self, mocked_sqlalchemy, mocked_getsmallrowdata_db,
def test_forcecurve(self, mocked_sqlalchemy, mocked_read_data,
mocked_myqueue):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
@@ -255,9 +255,9 @@ class WorkoutViewTest(TestCase):
self.assertEqual(response.status_code,200)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.read_data')
@patch('rowers.middleware.myqueue')
def test_resample(self, mocked_sqlalchemy, mocked_getsmallrowdata_db,
def test_resample(self, mocked_sqlalchemy, mocked_read_data,
mocked_myqueue):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
@@ -283,9 +283,9 @@ class WorkoutViewTest(TestCase):
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.read_data')
@pytest.mark.django_db(transaction=True)
def test_joins(self, mocked_sqlalchemy, mocked_getsmallrowdata_db):
def test_joins(self, mocked_sqlalchemy, mocked_read_data):
with transaction.atomic():
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
@@ -350,10 +350,10 @@ class WorkoutViewTest(TestCase):
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.read_data')
@patch('rowers.weather.requests.get', side_effect=mocked_requests)
def test_waterworkout_view(self,
mocked_sqlalchemy, mocked_getsmallrowdata_db, mocked_requests):
mocked_sqlalchemy, mocked_read_data, mocked_requests):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
@@ -362,22 +362,6 @@ class WorkoutViewTest(TestCase):
response = self.c.get(url)
self.assertEqual(response.status_code,200)
url = reverse('workout_downloadmetar_view',kwargs={
'id': encoder.encode_hex(self.wwater.id),
'airportcode': 'LKHO'
}
)
response = self.c.get(url,follow=True)
self.assertEqual(response.status_code,200)
url = reverse('workout_downloadwind_view',kwargs={
'id': encoder.encode_hex(self.wwater.id),
}
)
response = self.c.get(url,follow=True)
self.assertEqual(response.status_code,200)
# Stacked Flex Chart
url = reverse('workout_flexchart_stacked_view',kwargs={
@@ -422,8 +406,8 @@ class WorkoutViewTest(TestCase):
self.assertEqual(response.status_code,200)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
def test_smoothen(self, mocked_sqlalchemy, mocked_getsmallrowdata_db):
@patch('rowers.dataprep.read_data')
def test_smoothen(self, mocked_sqlalchemy, mocked_read_data):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
@@ -459,125 +443,12 @@ class WorkoutViewTest(TestCase):
status_code=302,target_status_code=200)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
def test_windform(self, mocked_sqlalchemy, mocked_getsmallrowdata_db):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
url = reverse('workout_wind_view',kwargs={'id':encoder.encode_hex(self.wwater.id)})
response = self.c.get(url)
self.assertEqual(response.status_code,200)
form_data = {
'dist1':1000,
'dist2':2000,
'vwind1':2.0,
'vwind2':1.4,
'windunit':'m',
'winddirection1': 0,
'winddirection2': 90,
}
form = UpdateWindForm(form_data)
self.assertTrue(form.is_valid())
response = self.c.post(url,form_data)
self.assertEqual(response.status_code,200)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
def test_streamform(self, mocked_sqlalchemy, mocked_getsmallrowdata_db):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
url = reverse('workout_stream_view',kwargs={'id':encoder.encode_hex(self.wwater.id)})
response = self.c.get(url)
self.assertEqual(response.status_code,200)
form_data = {
'dist1':1000,
'dist2':2000,
'stream1':2.0,
'stream2':1.4,
'streamunit':'m',
}
form = UpdateStreamForm(form_data)
self.assertTrue(form.is_valid())
response = self.c.post(url,form_data)
self.assertEqual(response.status_code,200)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.middleware.myqueue')
def test_setpowerform(self, mocked_sqlalchemy, mocked_getsmallrowdata_db,
mocked_myqueue):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
url = reverse('instroke_view',kwargs={'id':encoder.encode_hex(self.winstroke.id)})
response = self.c.get(url)
self.assertEqual(response.status_code,200)
rowdata = rowingdata.rowingdata(csvfile=self.winstroke.csvfilename)
instrokemetrics = rowdata.get_instroke_columns()
self.assertTrue(len(instrokemetrics)>0)
url = reverse('instroke_chart',
kwargs={
'id':encoder.encode_hex(self.winstroke.id),
'metric':instrokemetrics[0],
})
url2 = reverse(self.r.defaultlandingpage,kwargs={'id':encoder.encode_hex(self.winstroke.id)})
response = self.c.get(url)
self.assertRedirects(response,
expected_url=url2,
status_code=302,target_status_code=200)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
def test_setpowerform(self, mocked_sqlalchemy, mocked_getsmallrowdata_db):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
url = reverse('workout_otwsetpower_view',kwargs={'id':encoder.encode_hex(self.wwater.id)})
response = self.c.get(url)
self.assertEqual(response.status_code,200)
form_data = {
'quick_calc':True,
'boattype': '1x',
'weightvalue': 75.0,
'boatbrand':'maas',
}
form = AdvancedWorkoutForm(form_data)
self.assertTrue(form.is_valid())
response = self.c.post(url,form_data,follow=True)
self.assertEqual(response.status_code,200)
expected_url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(self.wwater.id)})
self.assertRedirects(response,
expected_url=expected_url,
status_code=302,target_status_code=200)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
def test_commentview(self, mocked_sqlalchemy, mocked_getsmallrowdata_db):
@patch('rowers.dataprep.read_data')
def test_commentview(self, mocked_sqlalchemy, mocked_read_data):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
@@ -602,8 +473,8 @@ class WorkoutViewTest(TestCase):
self.assertEqual(response.status_code,200)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
def test_mapview(self, mocked_sqlalchemy, mocked_getsmallrowdata_db):
@patch('rowers.dataprep.read_data')
def test_mapview(self, mocked_sqlalchemy, mocked_read_data):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
@@ -614,9 +485,9 @@ class WorkoutViewTest(TestCase):
self.assertEqual(response.status_code,200)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.read_data')
def notworking_test_workout_image(self, mocked_sqlalchemy,
mocked_getsmallrowdata_db):
mocked_read_data):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
@@ -642,10 +513,10 @@ class WorkoutViewTest(TestCase):
status_code=302,target_status_code=200)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.read_data')
@patch('rowers.dataprep.getrowdata_db',side_effect=mocked_getrowdata_db)
@pytest.mark.django_db(transaction=True)
def test_workout_split(self, mocked_sqlalchemy, mocked_getsmallrowdata_db,
def test_workout_split(self, mocked_sqlalchemy, mocked_read_data,
mocked_getrowdata_db):
with transaction.atomic():
login = self.c.login(username=self.u.username, password=self.password)
@@ -670,9 +541,9 @@ class WorkoutViewTest(TestCase):
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getrowdata_db',side_effect=mocked_getrowdata_db)
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_forfusion)
@patch('rowers.dataprep.read_data',side_effect=mocked_getsmallrowdata_forfusion)
def test_workout_fusion(self, mocked_sqlalchemy, mocked_getrowdata_db,
mocked_getsmallrowdata_db):
mocked_read_data):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
@@ -699,27 +570,11 @@ class WorkoutViewTest(TestCase):
self.assertEqual(response.status_code,200)
def test_remove_power_view(self):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
url = reverse('remove_power_view',kwargs={'id':encoder.encode_hex(self.wwater.id)})
url2 = reverse(self.r.defaultlandingpage,
kwargs={
'id':encoder.encode_hex(self.wwater.id)
}
)
response = self.c.get(url)
self.assertRedirects(response,
expected_url=url2,
status_code=302,
target_status_code=200
)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.read_data')
@patch('rowers.dataprep.get_video_data',side_effect=mocked_videodata)
def test_workout_video_view(self, mocked_sqlalchemy, mocked_getsmallrowdata_db,
def test_workout_video_view(self, mocked_sqlalchemy, mocked_read_data,
mocked_videodata):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
@@ -779,9 +634,9 @@ class WorkoutViewTest(TestCase):
self.assertRedirects(response,expected_url=expected_url,status_code=302,target_status_code=200)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.read_data')
@patch('rowers.dataprep.get_video_data',side_effect=mocked_videodata)
def test_workout_video_view_erg(self, mocked_sqlalchemy, mocked_getsmallrowdata_db,
def test_workout_video_view_erg(self, mocked_sqlalchemy, mocked_read_data,
mocked_videodata):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
@@ -843,7 +698,7 @@ class WorkoutViewTest(TestCase):
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getrowdata_db',side_effect=mocked_getrowdata_db)
@patch('rowers.dataprep.get_video_data',side_effect=mocked_videodata)
def test_video_selectworkout(self, mocked_sqlalchemy, mocked_getsmallrowdata_db,
def test_video_selectworkout(self, mocked_sqlalchemy, mocked_read_data,
mocked_videodata):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
@@ -874,8 +729,8 @@ class WorkoutViewTest(TestCase):
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
def test_editsummaryview(self, mocked_sqlalchemy, mocked_getsmallrowdata_db):
@patch('rowers.dataprep.read_data')
def test_editsummaryview(self, mocked_sqlalchemy, mocked_read_data):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
@@ -940,8 +795,8 @@ class WorkoutViewTest(TestCase):
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
def test_workout_delete(self, mocked_sqlalchemy, mocked_getsmallrowdata_db):
@patch('rowers.dataprep.read_data')
def test_workout_delete(self, mocked_sqlalchemy, mocked_read_data):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
-62
View File
@@ -166,65 +166,3 @@ class CPChartTest(TestCase):
self.assertEqual(response.status_code,200)
@patch('rowers.dataprep.fetchcperg', side_effect = mocked_fetchcperg)
@patch('rowers.dataprep.create_engine')
def test_rankingpieces(self, mocked_fetchcperg, mocked_sqlalchemy):
url = '/rowers/ote-bests2/'
login = self.c.login(username=self.u.username,password=self.password)
self.assertTrue(login)
# update_records(url='rowers/tests/c2worldrecords.html',verbose=False)
form_data = {
'name': faker.word(),
'date': nu.date(),
'timezone': 'UTC',
'duration': '00:02:00.0',
'starttime': '10:01:43',
'distance': 500,
'workouttype': 'rower',
'boattype': '1x',
'weightcategory': 'hwt',
'adaptiveclass': 'None',
'notes': faker.text(),
'rankingpiece': True,
'duplicate': False,
'avghr': '160',
'avgpwr': 0,
'avgspm': 40,
}
url2 = '/rowers/workout/addmanual/'
response = self.c.post(url2, form_data, follow=True)
self.assertEqual(response.status_code, 200)
form_data['distance'] = 100
form_data['duration'] = '00:00:18.5'
response = self.c.post(url2, form_data, follow=True)
self.assertEqual(response.status_code, 200)
form_data['distance'] = 2000
form_data['duration'] = '00:06:56.5'
response = self.c.post(url2, form_data, follow=True)
self.assertEqual(response.status_code, 200)
form_data['distance'] = 5000
form_data['duration'] = '00:20:48.0'
response = self.c.post(url2, form_data, follow=True)
self.assertEqual(response.status_code, 200)
form_data['distance'] = 6000
form_data['duration'] = '00:22:17.6'
response = self.c.post(url2, form_data, follow=True)
self.assertEqual(response.status_code, 200)
form_data['distance'] = 10000
form_data['duration'] = '00:38:16.5'
response = self.c.post(url2, form_data, follow=True)
self.assertEqual(response.status_code, 200)
response = self.c.get(url)
self.assertEqual(response.status_code, 200)
+8 -8
View File
@@ -55,8 +55,8 @@ class EmailUpload(TestCase):
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db)
def test_uploadapi(self,mocked_sqlalchemy,mocked_getsmallrowdata_db):
@patch('rowers.dataprep.read_data',side_effect=mocked_read_data)
def test_uploadapi(self,mocked_sqlalchemy,mocked_read_data):
form_data = {
'title': 'test',
'workouttype':'rower',
@@ -108,8 +108,8 @@ class EmailUpload(TestCase):
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db)
def test_uploadapi2(self,mocked_sqlalchemy,mocked_getsmallrowdata_db):
@patch('rowers.dataprep.read_data',side_effect=mocked_read_data)
def test_uploadapi2(self,mocked_sqlalchemy,mocked_read_data):
form_data = {"secret": settings.UPLOAD_SERVICE_SECRET,
"user": 1,
"file": 'media/mailbox_attachments/colin3.csv',
@@ -140,8 +140,8 @@ class EmailUpload(TestCase):
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db)
def test_uploadapi3(self,mocked_sqlalchemy,mocked_getsmallrowdata_db):
@patch('rowers.dataprep.read_data',side_effect=mocked_read_data)
def test_uploadapi3(self,mocked_sqlalchemy,mocked_read_data):
with transaction.atomic():
form_data = {
'title': 'test',
@@ -167,8 +167,8 @@ class EmailUpload(TestCase):
self.assertEqual(w.notes,'aap noot mies')
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db)
def test_uploadapi_credentials(self,mocked_sqlalchemy,mocked_getsmallrowdata_db):
@patch('rowers.dataprep.read_data',side_effect=mocked_read_data)
def test_uploadapi_credentials(self,mocked_sqlalchemy,mocked_read_data):
form_data = {
'title': 'test',
'workouttype':'rower',
+4 -4
View File
@@ -103,8 +103,8 @@ class WorkoutViewTest(TestCase):
pass
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
def test_flexchart_water(self, mocked_sqlalechemy, mocked_getsmallrowdata_db):
@patch('rowers.dataprep.read_data')
def test_flexchart_water(self, mocked_sqlalechemy, mocked_read_data):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
@@ -151,8 +151,8 @@ class WorkoutViewTest(TestCase):
self.assertEqual(response.status_code,200)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
def test_flexchart_erg(self, mocked_sqlalechemy, mocked_getsmallrowdata_db):
@patch('rowers.dataprep.read_data')
def test_flexchart_erg(self, mocked_sqlalechemy, mocked_read_data):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
+13 -13
View File
@@ -720,8 +720,8 @@ class NKObjects(DjangoTestCase):
)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db)
def test_nk_intervals(self,mocked_sqlalchemy,mocked_getsmallrowdata_db):
@patch('rowers.dataprep.read_data',side_effect=mocked_read_data)
def test_nk_intervals(self,mocked_sqlalchemy,mocked_read_data):
with open('rowers/tests/testdata/carlos_workout.json','r') as f:
workoutdata = json.load(f)
with open('rowers/tests/testdata/carlos_strokes.json','r') as f:
@@ -817,10 +817,10 @@ class NKObjects(DjangoTestCase):
@patch('rowers.integrations.nk.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.nk.requests.post', side_effect=mocked_requests)
@patch('rowers.nkimportutils.requests.session', side_effect=mocked_session)
@patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db)
@patch('rowers.dataprep.read_data', side_effect=mocked_read_data)
def test_nk_import(self, mock_get, mock_post,
mocked_session,
mocked_getsmallrowdata_db):
mocked_read_data):
integration = NKIntegration(self.u)
result = integration.token_refresh()
@@ -854,10 +854,10 @@ class NKObjects(DjangoTestCase):
@patch('rowers.integrations.nk.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.nk.requests.post', side_effect=mocked_requests)
@patch('rowers.nkimportutils.requests.session', side_effect=mocked_session)
@patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db)
@patch('rowers.dataprep.read_data', side_effect=mocked_read_data)
def test_nk_import_task(self, mock_get, mock_post,
mocked_session,
mocked_getsmallrowdata_db):
mocked_read_data):
alldata = {
469: {'elapsedTime': 3901900,
'totalDistanceImp': 0,
@@ -904,10 +904,10 @@ class NKObjects(DjangoTestCase):
@patch('rowers.integrations.nk.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.nk.requests.post', side_effect=mocked_requests)
@patch('rowers.nkimportutils.requests.session', side_effect=mocked_session)
@patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db)
@patch('rowers.dataprep.read_data', side_effect=mocked_read_data)
def test_nk_import_impeller(self, mock_get, mock_post,
mocked_session,
mocked_getsmallrowdata_db):
mocked_read_data):
integration = NKIntegration(self.u)
result = integration.token_refresh()
@@ -1083,9 +1083,9 @@ class RP3Objects(DjangoTestCase):
@patch('rowers.integrations.rp3.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.rp3.requests.post', side_effect=mocked_requests)
@patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db)
@patch('rowers.dataprep.read_data', side_effect=mocked_read_data)
def test_rp3_import(self, mock_get, mockpost,
mocked_getsmallrowdata_db):
mocked_read_data):
response = self.c.get('/rowers/workout/rp3import/591621',follow=True)
@@ -1268,9 +1268,9 @@ class StravaObjects(DjangoTestCase):
@patch('rowers.utils.requests.get', side_effect=mocked_requests)
@patch('rowers.integrations.strava.requests.post', side_effect=mocked_requests)
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.read_data')
def test_strava_import(self, mock_get, mock_post,
mocked_getsmallrowdata_db):
mocked_read_data):
response = self.c.get('/rowers/workout/stravaimport/12',follow=True)
expected_url = reverse('workout_import_view',kwargs={'source':'strava'})
@@ -1307,7 +1307,7 @@ class STObjects(DjangoTestCase):
self.r.sporttrackstoken = '12'
self.r.sporttracksrefreshtoken = '12'
self.r.sporttroacksrefreshtoken = '12'
self.r.sporttrackstokenexpirydate = arrow.get(datetime.datetime.now()+datetime.timedelta(days=1)).datetime
self.r.save()
+16 -16
View File
@@ -51,16 +51,16 @@ class InteractiveChartTest(TestCase):
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.read_df_sql')
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db)
@patch('rowers.dataprep.read_data',side_effect=mocked_read_data)
def test_interactive_chart1(self, mocked_sqlalchemy,mocked_read_df_sql,
mocked_getsmallrowdata_db):
mocked_read_data):
res = iplots.interactive_chart(self.wote.id)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.read_df_sql')
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db)
@patch('rowers.dataprep.read_data',side_effect=mocked_read_data)
def test_interactive_chart2(self, mocked_sqlalchemy,mocked_read_df_sql,
mocked_getsmallrowdata_db):
mocked_read_data):
res = iplots.interactive_chart(self.wote.id,promember=1)
@@ -80,52 +80,52 @@ class InteractiveChartTest(TestCase):
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.read_df_sql')
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db)
@patch('rowers.dataprep.read_data',side_effect=mocked_read_data)
def test_interactive_chart7(self, mocked_sqlalchemy,mocked_read_df_sql,
mocked_getsmallrowdata_db):
mocked_read_data):
res = iplots.interactive_flex_chart2(self.wote.id,self.r,promember=0,
xparam='time',
yparam1='pace',yparam2='spm',mode='water')
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.read_df_sql')
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db)
@patch('rowers.dataprep.read_data',side_effect=mocked_read_data)
def test_interactive_chart8(self, mocked_sqlalchemy,mocked_read_df_sql,
mocked_getsmallrowdata_db):
mocked_read_data):
res = iplots.interactive_flex_chart2(self.wote.id,self.r,
promember=0,xparam='distance',
yparam1='pace',yparam2='spm',mode='water')
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.read_df_sql')
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db)
@patch('rowers.dataprep.read_data',side_effect=mocked_read_data)
def test_interactive_chart9(self, mocked_sqlalchemy,mocked_read_df_sql,
mocked_getsmallrowdata_db):
mocked_read_data):
res = iplots.interactive_flex_chart2(self.wote.id,self.r,
promember=1,xparam='time',
yparam1='pace',yparam2='hr',mode='water')
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.read_df_sql')
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db)
@patch('rowers.dataprep.read_data',side_effect=mocked_read_data)
def test_interactive_chart10(self, mocked_sqlalchemy,mocked_read_df_sql,
mocked_getsmallrowdata_db):
mocked_read_data):
res = iplots.interactive_flex_chart2(self.wote.id,self.r,
promember=1,xparam='distance',
yparam1='pace',yparam2='hr',mode='water')
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.read_df_sql')
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db)
@patch('rowers.dataprep.read_data',side_effect=mocked_read_data)
def test_interactive_chart11(self, mocked_sqlalchemy,mocked_read_df_sql,
mocked_getsmallrowdata_db):
mocked_read_data):
res = iplots.interactive_flex_chart2(self.wote.id,self.r,
promember=1,xparam='time',
yparam1='pace',yparam2='spm',mode='water')
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.read_df_sql')
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db)
@patch('rowers.dataprep.read_data',side_effect=mocked_read_data)
def test_interactive_chart12(self, mocked_sqlalchemy,mocked_read_df_sql,
mocked_getsmallrowdata_db):
mocked_read_data):
res = iplots.interactive_flex_chart2(self.wote.id,self.r,
promember=1,
xparam='distance',
+8 -8
View File
@@ -911,8 +911,8 @@ class PermissionsViewTests(TestCase):
## Coach can upload on behalf of athlete - if team allows
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db)
def test_coach_edit_athlete_upload(self,mocked_sqlalchemy,mocked_getsmallrowdata_db):
@patch('rowers.dataprep.read_data',side_effect=mocked_read_data)
def test_coach_edit_athlete_upload(self,mocked_sqlalchemy,mocked_read_data):
self.rbasic.team.add(self.teamcoach)
self.rbasic.coachinggroups.add(self.coachinggroup)
@@ -957,8 +957,8 @@ class PermissionsViewTests(TestCase):
## Coach can upload on behalf of athlete - if team allows
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db)
def test_coach_edit_athlete_uploadnot(self,mocked_sqlalchemy,mocked_getsmallrowdata_db):
@patch('rowers.dataprep.read_data',side_effect=mocked_read_data)
def test_coach_edit_athlete_uploadnot(self,mocked_sqlalchemy,mocked_read_data):
self.rbasic.team.add(self.teamcoach)
login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
@@ -1168,8 +1168,8 @@ class PermissionsViewTests(TestCase):
## Self Coach cannot upload on behalf of athlete
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db)
def test_plan_edit_athlete_upload(self,mocked_sqlalchemy,mocked_getsmallrowdata_db):
@patch('rowers.dataprep.read_data',side_effect=mocked_read_data)
def test_plan_edit_athlete_upload(self,mocked_sqlalchemy,mocked_read_data):
self.rpro.team.add(self.teamplan)
login = self.c.login(username=self.uplan2.username, password=self.uplan2password)
@@ -1291,8 +1291,8 @@ class PermissionsViewTests(TestCase):
## Self Coach cannot upload on behalf of athlete
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db)
def test_plan_edit_athlete_upload(self,mocked_sqlalchemy,mocked_getsmallrowdata_db):
@patch('rowers.dataprep.read_data',side_effect=mocked_read_data)
def test_plan_edit_athlete_upload(self,mocked_sqlalchemy,mocked_read_data):
self.rpro.team.add(self.teamplan)
login = self.c.login(username=self.uplan2.username, password=self.uplan2password)
+8 -8
View File
@@ -218,7 +218,7 @@ class PermissionsViewTests(TestCase):
@patch('rowers.integrations.c2.C2Integration.open')
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.read_df_sql')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.read_data')
@patch('requests.get',side_effect=mocked_requests)
@patch('requests.post',side_effect=mocked_requests)
@patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data)
@@ -228,7 +228,7 @@ class PermissionsViewTests(TestCase):
mock_c2open,
mocked_sqlalchemy,
mocked_read_df_sql,
mocked_getsmallrowdata_db,
mocked_read_data,
mock_get,
mock_post,
mocked_get_video_data,
@@ -253,7 +253,7 @@ class PermissionsViewTests(TestCase):
@patch('rowers.integrations.c2.C2Integration.open')
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.read_df_sql')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.read_data')
@patch('requests.get',side_effect=mocked_requests)
@patch('requests.post',side_effect=mocked_requests)
@patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data)
@@ -263,7 +263,7 @@ class PermissionsViewTests(TestCase):
mock_c2open,
mocked_sqlalchemy,
mocked_read_df_sql,
mocked_getsmallrowdata_db,
mocked_read_data,
mock_get,
mock_post,
mocked_get_video_data,
@@ -335,7 +335,7 @@ class PermissionsViewTests(TestCase):
@patch('rowers.integrations.c2.C2Integration.open')
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.read_df_sql')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.read_data')
@patch('requests.get',side_effect=mocked_requests)
@patch('requests.post',side_effect=mocked_requests)
@patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data)
@@ -345,7 +345,7 @@ class PermissionsViewTests(TestCase):
mock_c2open,
mocked_sqlalchemy,
mocked_read_df_sql,
mocked_getsmallrowdata_db,
mocked_read_data,
mock_get,
mock_post,
mocked_get_video_data,
@@ -418,7 +418,7 @@ class PermissionsViewTests(TestCase):
@patch('rowers.integrations.c2.C2Integration.open')
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.read_df_sql')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.read_data')
@patch('requests.get',side_effect=mocked_requests)
@patch('requests.post',side_effect=mocked_requests)
@patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data)
@@ -428,7 +428,7 @@ class PermissionsViewTests(TestCase):
mock_c2open,
mocked_sqlalchemy,
mocked_read_df_sql,
mocked_getsmallrowdata_db,
mocked_read_data,
mock_get,
mock_post,
mocked_get_video_data,
+14 -12
View File
@@ -13,6 +13,7 @@ from django.db import transaction
nu = datetime.datetime.now()
import datetime
import pytz
import polars as pl
# interactive plots
from rowers import interactiveplots
@@ -581,7 +582,7 @@ class DataPrepTests(TestCase):
age = dataprep.calculate_age(born,today=today)
self.assertEqual(age,49)
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_uh)
@patch('rowers.dataprep.read_data',side_effect=mocked_getsmallrowdata_uh)
def test_goldmedalstandard(self,mocked_getsmallrowdata_uh):
maxvalue, delta = dataprep.calculate_goldmedalstandard(self.r,self.wuh_otw)
records = CalcAgePerformance.objects.filter(
@@ -596,8 +597,8 @@ class DataPrepTests(TestCase):
result = getagegrouprecord(25)
self.assertEqual(int(result),590)
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_uh)
def test_get_videodata(self,mocked_getsmallrowdata_uh):
@patch('rowers.dataprep.getsmallrowdata_pd',side_effect=mocked_getsmallrowdata_uh_pd)
def test_get_videodata(self,mocked_getsmallrowdata_uh_pd):
data, metrics, maxtime = dataprep.get_video_data(self.wuh_otw)
self.assertEqual(len(data),9)
@@ -659,7 +660,7 @@ class InteractivePlotTests(TestCase):
pass
def test_interactive_hr_piechart(self):
df = pd.read_csv('rowers/tests/testdata/getrowdata_mock.csv')
df = pl.read_csv('rowers/tests/testdata/getrowdata_mock.csv')
script, div = interactiveplots.interactive_hr_piechart(df, self.r,'')
self.assertFalse(len(script)==0)
@@ -672,7 +673,8 @@ class InteractivePlotTests(TestCase):
self.assertFalse(len(div)==0)
def test_interactive_boxchart(self):
df = pd.read_csv('rowers/tests/testdata/boxplotdata.csv')
df = pl.read_csv('rowers/tests/testdata/boxplotdata.csv')
df = df.with_columns(pl.col("date").str.to_datetime("%Y-%m-%d"))
script, div = interactiveplots.interactive_boxchart(df, 'spm')
@@ -685,12 +687,12 @@ class InteractivePlotTests(TestCase):
startdate = workouts[0].date-datetime.timedelta(days=5)
enddate = workouts2[0].date+datetime.timedelta(days=5)
script, div = interactiveplots.interactive_activitychart(workouts,startdate,enddate)
script, div = interactiveplots.interactive_activitychart2(workouts,startdate,enddate)
self.assertFalse(len(script)==0)
self.assertFalse(len(div)==0)
def test_interactive_otwcpchart(self):
df = pd.read_csv('rowers/tests/testdata/otwcp.csv')
df = pl.read_csv('rowers/tests/testdata/otwcp.csv')
script, div, p1, ratio, message = interactiveplots.interactive_otwcpchart(df,r=self.r,cpfit='data')
self.assertFalse(len(script)==0)
@@ -701,9 +703,9 @@ class InteractivePlotTests(TestCase):
self.assertFalse(len(div)==0)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db)
@patch('rowers.dataprep.read_data', side_effect=mocked_read_data)
def test_interactive_chart(self, mocked_sqlalchemy,
mocked_getsmallrowdata_db):
mocked_read_data):
workout = Workout.objects.filter(user=self.r,workouttype__in=mytypes.rowtypes)[0]
id = workout.id
@@ -733,12 +735,12 @@ class InteractivePlotTests(TestCase):
self.assertFalse(len(div)==0)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db)
@patch('rowers.dataprep.read_data', side_effect=mocked_read_data)
def test_interactive_flexchart_stacked(self, mocked_sqlalchemy,
mocked_getsmallrowdata_db):
mocked_read_data):
workout = Workout.objects.filter(user=self.r,workouttype__in=mytypes.rowtypes)[0]
id = workout.id
script, div, js_res, css_res, comment = interactiveplots.interactive_flexchart_stacked(id,self.r)
script, div = interactiveplots.interactive_flexchart_stacked(id,self.r)
self.assertFalse(len(script)==0)
self.assertFalse(len(div)==0)
+10 -7
View File
@@ -71,9 +71,10 @@ class ForceUnits(TestCase):
w = Workout.objects.get(id=1)
self.assertEqual(w.forceunit,'lbs')
df = dataprep.getsmallrowdata_db(['averageforce'],ids=[13])
df = dataprep.read_data(['averageforce'],ids=[13])
df = dataprep.remove_nulls_pl(df)
average_N = int(df['averageforce'].mean())
self.assertEqual(average_N,398)
self.assertEqual(average_N,400)
data = dataprep.read_df_sql(13)
average_N = int(data['averageforce'].mean())
@@ -119,9 +120,10 @@ class ForceUnits(TestCase):
w = Workout.objects.get(id=13)
self.assertEqual(w.forceunit,'N')
df = dataprep.getsmallrowdata_db(['averageforce'],ids=[13])
df = dataprep.read_data(['averageforce'],ids=[13])
df = dataprep.remove_nulls_pl(df)
average_N = int(df['averageforce'].mean())
self.assertEqual(average_N,263)
self.assertEqual(average_N,271)
def test_upload_speedcoach_colin(self):
login = self.c.login(username=self.u.username, password=self.password)
@@ -155,9 +157,10 @@ class ForceUnits(TestCase):
w = Workout.objects.get(id=13)
self.assertEqual(w.forceunit,'N')
df = dataprep.getsmallrowdata_db(['averageforce'],ids=[13])
df = dataprep.read_data(['averageforce'],ids=[13])
df = dataprep.remove_nulls_pl(df)
average_N = int(df['averageforce'].mean())
self.assertEqual(average_N,105)
self.assertEqual(average_N,122)
@override_settings(TESTING=True)
class TestForceUnit(TestCase):
@@ -206,7 +209,7 @@ class TestForceUnit(TestCase):
rowdata = dataprep.rdata('rowers/tests/testdata/PainsledForce.csv')
df = dataprep.dataprep(rowdata.df)
#df = dataprep.getsmallrowdata_db(['averageforce'],ids=[w[0].id],doclean=False,
#df = dataprep.read_data(['averageforce'],ids=[w[0].id],doclean=False,
# compute=False)
try:
average_N = int(df['averageforce'].mean())
+4 -8
View File
@@ -29,8 +29,8 @@ class ViewTest(TestCase):
self.nu = datetime.datetime.now()
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db)
def test_upload_view_sled(self, mocked_sqlalchemy,mocked_getsmallrowdata_db):
@patch('rowers.dataprep.read_data',side_effect=mocked_read_data)
def test_upload_view_sled(self, mocked_sqlalchemy,mocked_read_data):
login = self.c.login(username='john',password='koeinsloot')
self.assertTrue(login)
@@ -74,10 +74,6 @@ class ViewTest(TestCase):
self.assertEqual(response.status_code, 200)
response = self.c.get('/rowers/workout/'+encoded1+'/histo/', form_data, follow=True)
self.assertEqual(response.status_code, 200)
f.close()
@@ -124,8 +120,8 @@ class ViewTest(TestCase):
pass
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db)
def test_upload_view_sled_bg(self, mocked_sqlalchemy,mocked_getsmallrowdata_db):
@patch('rowers.dataprep.read_data',side_effect=mocked_read_data)
def test_upload_view_sled_bg(self, mocked_sqlalchemy,mocked_read_data):
login = self.c.login(username='john',password='koeinsloot')
self.assertTrue(login)
+2 -15
View File
@@ -79,11 +79,6 @@ class URLTests(TestCase):
'/rowers/502/',
'/rowers/about/',
'/rowers/workout/addmanual/',
'/rowers/agegroupcp/30/',
'/rowers/agegroupcp/30/1/',
'/rowers/agegrouprecords/male/hwt/',
'/rowers/agegrouprecords/male/hwt/2000m/',
'/rowers/agegrouprecords/male/hwt/30min/',
'/rowers/ajax_agegroup/45/hwt/male/1/',
'/rowers/analysis/',
'/rowers/analysis/user/1/',
@@ -122,8 +117,6 @@ class URLTests(TestCase):
'/rowers/me/workflowconfig2/',
'/rowers/me/workflowconfig2/user/1/',
'/rowers/me/workflowdefault/',
'/rowers/ote-bests2/',
'/rowers/ote-bests2/user/1/',
'/rowers/partners/',
'/rowers/physics/',
'/rowers/planrequired/',
@@ -148,29 +141,23 @@ class URLTests(TestCase):
'/rowers/workout/'+encoded1+'/addtimeplot/',
'/rowers/workout/'+encoded1+'/addtimeplot2/',
'/rowers/workout/'+encoded1+'/comment/',
'/rowers/workout/'+encoded1+'/darkskywind/',
'/rowers/workout/'+encoded1+'/data/',
'/rowers/workout/'+encoded1+'/edit/',
'/rowers/workout/'+encoded1+'/editintervals/',
'/rowers/workout/'+encoded1+'/flexchart/',
'/rowers/workout/'+encoded1+'/forcecurve/',
'/rowers/workout/'+encoded1+'/get-thumbnails/',
'/rowers/workout/'+encoded1+'/histo/',
'/rowers/workout/'+encoded1+'/image/',
'/rowers/workout/'+encoded1+'/instroke/',
'/rowers/workout/'+encoded1+'/interactiveotwplot/',
'/rowers/workout/'+encoded1+'/map/',
'/rowers/workout/'+encoded1+'/otwsetpower/',
'/rowers/workout/'+encoded1+'/recalcsummary/',
'/rowers/workout/'+encoded1+'/restore/',
'/rowers/workout/'+encoded1+'/smoothenpace/',
'/rowers/workout/'+encoded1+'/split/',
'/rowers/workout/'+encoded1+'/stats/',
'/rowers/workout/'+encoded1+'/stream/',
'/rowers/workout/'+encoded1+'/undosmoothenpace/',
'/rowers/workout/'+encoded1+'/unsubscribe/',
'/rowers/workout/'+encoded1+'/view/',
'/rowers/workout/'+encoded1+'/wind/',
'/rowers/workout/'+encoded1+'/workflow/',
'/rowers/workout/fusion/'+encoded1+'/',
'/rowers/workout/upload/',
@@ -206,7 +193,7 @@ class URLTests(TestCase):
@parameterized.expand(lijst)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.read_df_sql')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.read_data')
@patch('requests.get',side_effect=mocked_requests)
@patch('requests.post',side_effect=mocked_requests)
@patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data)
@@ -214,7 +201,7 @@ class URLTests(TestCase):
def test_url_generator(self,url,expected,
mocked_sqlalchemy,
mocked_read_df_sql,
mocked_getsmallrowdata_db,
mocked_read_data,
mock_get,
mock_post,
mocked_get_video_data,
Binary file not shown.
-10
View File
@@ -1,9 +1,7 @@
,id,view,function,anonymous,anonymous_response,own,own_response,own_nonperm,member,member_response,member_nonperm,coachee,coachee_response,coachee_nonperm,is_staff,userid,workoutid,dotest,realtest,kwargs
0,0,workouts_summaries_email_view,sends summary excel with workouts list and links to data to user,TRUE,302,basic,200,302,FALSE,403,403,FALSE,403,403,FALSE,FALSE,FALSE,TRUE,TRUE,
1,1,rower_update_empower_view,updates old Empower Oarlock files (corrects Power bug),TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
2,2,agegroupcpview,needs age,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
3,4,ajax_agegrouprecords,gets age group records from C2 ,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
5,6,agegrouprecordview,shows ergo age group records,TRUE,200,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
6,7,workouts_view,workouts list,TRUE,302,basic,200,302,basic,200,403,coach,200,403,FALSE,TRUE,FALSE,TRUE,TRUE,
7,8,virtualevents_view,virtual races list,TRUE,200,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
8,9,virtualevent_create_view,create virtual event,TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
@@ -31,7 +29,6 @@
33,35,kill_async_job,kill job,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
34,36,post_progress,post progress,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
35,37,graphs_view,view charts,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,TRUE,FALSE,TRUE,TRUE,
38,40,rankings_view2,view ranking,TRUE,302,pro,200,302,pro,403,302,coach,200,302,FALSE,TRUE,FALSE,TRUE,TRUE,
41,43,cum_flex,flex all chart,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
42,44,analysis_view_data,redirects to analysis direct,TRUE,302,pro,200,302,pro,200,302,coach,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
43,47,cum_flex_data,flex all chart data (json),TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
@@ -43,7 +40,6 @@
52,58,workout_toggle_ranking,toggle ranking,TRUE,302,basic,302,302,basic,403,302,coach,302,302,FALSE,FALSE,TRUE,TRUE,TRUE,
53,59,team_workout_upload_view,upload workout for team member,TRUE,302,coach,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
54,60,workout_upload_view,upload a workout,TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
55,61,workout_histo_view,histogram ,TRUE,302,basic,200,302,basic,403,403,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE,
56,62,workout_forcecurve_view,force curve,TRUE,302,pro,200,302,pro,403,302,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE,
57,63,workout_unsubscribe_view,unsubscribe from comments,TRUE,302,basic,200,302,basic,200,302,basic,200,302,FALSE,FALSE,TRUE,TRUE,TRUE,
58,64,workout_comment_view,comment on workout,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE,
@@ -57,17 +53,11 @@
67,73,instroke_view,Create in stroke chart,TRUE,403,basic,200,302,basic,403,403,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE,
68,74,workout_stats_view,View Workout Stats,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE,
69,75,workout_data_view,shows the detailed data for a workout,TRUE,403,basic,200,403,basic,403,403,coach,200,403,FALSE,FALSE,TRUE,TRUE,TRUE,
70,76,workout_otwsetpower_view,set weight and start offline OTW power calculations,TRUE,403,pro,200,302,pro,403,403,coach,200,403,FALSE,FALSE,TRUE,TRUE,TRUE,
71,77,workout_otwpowerplot_view,generates OTW power chart,TRUE,200,basic,200,200,basic,200,200,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE,
72,78,workout_wind_view,set wind,TRUE,403,pro,200,302,pro,403,403,coach,200,403,FALSE,FALSE,TRUE,TRUE,TRUE,
73,79,workout_uploadimage_view,upload image,TRUE,403,basic,200,403,basic,403,403,coach,200,403,FALSE,FALSE,TRUE,TRUE,TRUE,
74,80,virtualevent_compare_view,compare workouts from a virtual event,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
75,81,virtualevent_uploadimage_view,upload image to virtual event,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
76,82,virtualevent_setlogo_view,set logo of virtual event,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
77,83,logo_delete_view,delete logo,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
78,84,workout_downloadwind_view,download Wind,TRUE,403,pro,302,302,pro,403,403,coach,302,403,FALSE,FALSE,TRUE,FALSE,FALSE,
79,85,workout_downloadmetar_view,download METAR,TRUE,403,pro,200,302,pro,403,403,coach,200,403,FALSE,FALSE,TRUE,FALSE,FALSE,
80,86,workout_stream_view,edit stream (redirects as no data in test suite),TRUE,403,pro,200,302,pro,403,403,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE,
81,87,workout_summary_edit_view,edit workout summary,TRUE,302,basic,200,403,basic,403,403,coach,200,403,FALSE,FALSE,TRUE,TRUE,TRUE,
82,88,workout_summary_restore_view,restore workout summary,TRUE,403,basic,302,403,basic,403,403,coach,302,403,FALSE,FALSE,TRUE,TRUE,TRUE,
83,89,workout_split_view,split workout,TRUE,403,pro,200,302,pro,403,403,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE,
1 id view function anonymous anonymous_response own own_response own_nonperm member member_response member_nonperm coachee coachee_response coachee_nonperm is_staff userid workoutid dotest realtest kwargs
2 0 0 workouts_summaries_email_view sends summary excel with workouts list and links to data to user TRUE 302 basic 200 302 FALSE 403 403 FALSE 403 403 FALSE FALSE FALSE TRUE TRUE
3 1 1 rower_update_empower_view updates old Empower Oarlock files (corrects Power bug) TRUE 302 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE TRUE TRUE
2 2 agegroupcpview needs age TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
4 3 4 ajax_agegrouprecords gets age group records from C2 TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
5 6 agegrouprecordview shows ergo age group records TRUE 200 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE TRUE TRUE
5 6 7 workouts_view workouts list TRUE 302 basic 200 302 basic 200 403 coach 200 403 FALSE TRUE FALSE TRUE TRUE
6 7 8 virtualevents_view virtual races list TRUE 200 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE TRUE TRUE
7 8 9 virtualevent_create_view create virtual event TRUE 302 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE TRUE TRUE
29 33 35 kill_async_job kill job TRUE 302 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
30 34 36 post_progress post progress TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
31 35 37 graphs_view view charts TRUE 302 basic 200 302 basic 200 302 coach 200 302 FALSE TRUE FALSE TRUE TRUE
38 40 rankings_view2 view ranking TRUE 302 pro 200 302 pro 403 302 coach 200 302 FALSE TRUE FALSE TRUE TRUE
32 41 43 cum_flex flex all chart TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
33 42 44 analysis_view_data redirects to analysis direct TRUE 302 pro 200 302 pro 200 302 coach 200 302 FALSE FALSE FALSE TRUE TRUE
34 43 47 cum_flex_data flex all chart data (json) TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
40 52 58 workout_toggle_ranking toggle ranking TRUE 302 basic 302 302 basic 403 302 coach 302 302 FALSE FALSE TRUE TRUE TRUE
41 53 59 team_workout_upload_view upload workout for team member TRUE 302 coach 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE TRUE TRUE
42 54 60 workout_upload_view upload a workout TRUE 302 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE TRUE TRUE
55 61 workout_histo_view histogram TRUE 302 basic 200 302 basic 403 403 coach 200 302 FALSE FALSE TRUE TRUE TRUE
43 56 62 workout_forcecurve_view force curve TRUE 302 pro 200 302 pro 403 302 coach 200 302 FALSE FALSE TRUE TRUE TRUE
44 57 63 workout_unsubscribe_view unsubscribe from comments TRUE 302 basic 200 302 basic 200 302 basic 200 302 FALSE FALSE TRUE TRUE TRUE
45 58 64 workout_comment_view comment on workout TRUE 302 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE TRUE TRUE TRUE
53 67 73 instroke_view Create in stroke chart TRUE 403 basic 200 302 basic 403 403 coach 200 302 FALSE FALSE TRUE TRUE TRUE
54 68 74 workout_stats_view View Workout Stats TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE TRUE TRUE TRUE
55 69 75 workout_data_view shows the detailed data for a workout TRUE 403 basic 200 403 basic 403 403 coach 200 403 FALSE FALSE TRUE TRUE TRUE
70 76 workout_otwsetpower_view set weight and start offline OTW power calculations TRUE 403 pro 200 302 pro 403 403 coach 200 403 FALSE FALSE TRUE TRUE TRUE
71 77 workout_otwpowerplot_view generates OTW power chart TRUE 200 basic 200 200 basic 200 200 coach 200 302 FALSE FALSE TRUE TRUE TRUE
72 78 workout_wind_view set wind TRUE 403 pro 200 302 pro 403 403 coach 200 403 FALSE FALSE TRUE TRUE TRUE
56 73 79 workout_uploadimage_view upload image TRUE 403 basic 200 403 basic 403 403 coach 200 403 FALSE FALSE TRUE TRUE TRUE
57 74 80 virtualevent_compare_view compare workouts from a virtual event TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
58 75 81 virtualevent_uploadimage_view upload image to virtual event TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
59 76 82 virtualevent_setlogo_view set logo of virtual event TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
60 77 83 logo_delete_view delete logo TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
78 84 workout_downloadwind_view download Wind TRUE 403 pro 302 302 pro 403 403 coach 302 403 FALSE FALSE TRUE FALSE FALSE
79 85 workout_downloadmetar_view download METAR TRUE 403 pro 200 302 pro 403 403 coach 200 403 FALSE FALSE TRUE FALSE FALSE
80 86 workout_stream_view edit stream (redirects as no data in test suite) TRUE 403 pro 200 302 pro 403 403 coach 200 302 FALSE FALSE TRUE TRUE TRUE
61 81 87 workout_summary_edit_view edit workout summary TRUE 302 basic 200 403 basic 403 403 coach 200 403 FALSE FALSE TRUE TRUE TRUE
62 82 88 workout_summary_restore_view restore workout summary TRUE 403 basic 302 403 basic 403 403 coach 302 403 FALSE FALSE TRUE TRUE TRUE
63 83 89 workout_split_view split workout TRUE 403 pro 200 302 pro 403 403 coach 200 302 FALSE FALSE TRUE TRUE TRUE
+2 -23
View File
@@ -273,19 +273,13 @@ urlpatterns = [
re_path(r'^exportallworkouts/?/$', views.workouts_summaries_email_view,
name='workouts_summaries_email_view'),
path('failedjobs/', views.failed_queue_view, name='failed_queue_view'),
path('sleep/', views.sleep_view, name='sleep_view'),
path('filmdeaths/', views.filmdeaths_view, name='filmdeaths_view'),
path('failedjobs/empty/', views.failed_queue_empty, name='failed_queue_empty'),
re_path('^failedjobs/(?P<id>\w+.*)/$',
views.failed_job_view, name='failed_job_view'),
re_path(r'^update_empower/$', views.rower_update_empower_view,
name='rower_update_empower_view'),
re_path(r'^agegroupcp/(?P<age>\d+)/$',
views.agegroupcpview, name='agegroupcpview'),
re_path(r'^agegroupcp/(?P<age>\d+)/user/(?P<userid>\d+)/$',
views.agegroupcpview, name='agegroupcpview'),
re_path(r'^agegroupcp/(?P<age>\d+)/(?P<normalize>\d+)/$',
views.agegroupcpview, name='agegroupcpview'),
re_path(r'^agegroupcp/(?P<age>\d+)/(?P<normalize>\d+)/user/(?P<userid>\d+)/$',
views.agegroupcpview, name='agegroupcpview'),
re_path(r'^ajax_agegroup/(?P<age>\d+)/(?P<weightcategory>\w+.*)/(?P<sex>\w+.*)/(?P<userid>\d+)/$',
views.ajax_agegrouprecords, name='ajax_agegrouprecords'),
re_path(r'^agegrouprecords/(?P<sex>\w+.*)/(?P<weightcategory>\w+.*)/(?P<distance>\d+)m/$',
@@ -448,9 +442,6 @@ urlpatterns = [
views.trainingzones_view_data, name="trainingzones_view_data"),
re_path(r'^trainingzones/data/$', views.trainingzones_view_data,
name="trainingzones_view_data"),
re_path(r'^ote-bests2/user/(?P<userid>\d+)/$',
views.rankings_view2, name='rankings_view2'),
re_path(r'^ote-bests2/$', views.rankings_view2, name='rankings_view2'),
re_path(r'^analysisdata/user/(?P<userid>\d+)/$', views.analysis_view_data,
name='analysis_view_data'),
re_path(r'^analysisdata/$', views.analysis_view_data,
@@ -475,8 +466,6 @@ urlpatterns = [
views.workout_upload_view, name='workout_upload_view'),
re_path(r'^workout/upload/$', views.workout_upload_view,
name='workout_upload_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/histo/$', views.workout_histo_view,
name='workout_histo_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/forcecurve/$', views.workout_forcecurve_view,
name='workout_forcecurve_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/forcecurve/(?P<analysis>\d+)/$', views.workout_forcecurve_view,
@@ -513,14 +502,6 @@ urlpatterns = [
name='workout_erase_column_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/zeropower-confirm/$', views.remove_power_confirm_view,
name='remove_power_confirm_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/zeropower/$', views.remove_power_view,
name='remove_power_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/otwsetpower/$',
views.workout_otwsetpower_view, name='workout_otwsetpower_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/interactiveotwplot/$',
views.workout_otwpowerplot_view, name='workout_otwpowerplot_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/wind/$',
views.workout_wind_view, name='workout_wind_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/image/$',
views.workout_uploadimage_view, name='workout_uploadimage_view'),
re_path(r'^virtualevent/(?P<id>\d+)/compare/$',
@@ -543,8 +524,6 @@ urlpatterns = [
views.workout_downloadwind_view, name='workout_downloadwind_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/metar/(?P<airportcode>\w+)/$',
views.workout_downloadmetar_view, name='workout_downloadmetar_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/stream/$',
views.workout_stream_view, name='workout_stream_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/editintervals/$', views.workout_summary_edit_view,
name='workout_summary_edit_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/restore/$',
+20 -21
View File
@@ -6,6 +6,8 @@ from django.utils import timezone
import math
import numpy as np
import pandas as pd
import polars as pl
from polars.exceptions import ColumnNotFoundError
import colorsys
from django.conf import settings
import collections
@@ -155,12 +157,7 @@ def get_call():
call1 = random.choice(coxes_calls)
call2 = random.choice(info_calls)
call = """<div id="id_sitready" class="successmessage">
<p>
%s (%s)
</p>
</div>
""" % (call1, call2)
call = """<div id="id_sitready" class="successmessage"><p>%s (%s)</p></div>""" % (call1, call2)
return call
@@ -347,22 +344,22 @@ def isbreakthrough(delta, cpvalues, p0, p1, p2, p3, ratio):
pwr *= ratio
delta = delta.astype(int, errors='ignore').values
cpvalues = cpvalues.astype(int, errors='ignore').values
pwr = pwr.astype(int, errors='ignore').values
delta = delta.cast(pl.Int32)
cpvalues = cpvalues.cast(pl.Int32)
pwr = pwr.cast(pl.Int32)
res = np.sum(cpvalues > pwr+1)
res2 = np.sum(cpvalues > pwr2+1)
btdf = pl.DataFrame({
'delta': delta,
'cpvalues': cpvalues,
'pwr': pwr,
'pwr2': pwr2
})
btdf = pd.DataFrame(
{
'delta': delta[cpvalues > pwr],
'cpvalues': cpvalues[cpvalues > pwr],
'pwr': pwr[cpvalues > pwr],
}
)
res = btdf.select(pl.col("cpvalues")>pl.col("pwr")+1)['cpvalues'].sum()
res2 = btdf.select(pl.col("cpvalues")> pl.col("pwr2")+1)['cpvalues'].sum()
btdf.sort_values('delta', axis=0, inplace=True)
btdf = btdf.filter(pl.col("cpvalues")>pl.col("pwr"))
btdf = btdf.sort('delta')
return res >= 1, btdf, res2 >= 1
@@ -444,17 +441,19 @@ def wavg(group, avg_name, weight_name):
"""
try:
d = group[avg_name]
except KeyError:
except (KeyError, ColumnNotFoundError):
return 0
try:
w = group[weight_name]
except KeyError:
except (KeyError, ColumnNotFoundError):
return d.mean()
try:
return (d * w).sum() / w.sum()
except ZeroDivisionError: # pragma: no cover
return d.mean()
return 0
from string import Formatter
def totaltime_sec_to_string(totaltime, shorten=False):
+67 -598
View File
@@ -296,7 +296,10 @@ def analysis_new(request,
df = cpdata(tw, options)
options['savedata'] = False
request.session['options'] = options
try:
response = HttpResponse(df.to_csv())
except AttributeError:
response = HttpResponse(df.write_csv())
code = str(uuid4())
filename = code+'.csv'
chartform.fields['savedata'].initial = False
@@ -384,6 +387,9 @@ def trendflexdata(workouts, options, userid=0):
datadf = dataprep.filter_df(datadf, 'driveneergy', workmax,
largerthan=False)
if yparam == 'power':
datadf = dataprep.filter_df(datadf, 'power', 1, largerthan=True)
datadf.dropna(axis=0, how='any', inplace=True)
datemapping = {
@@ -472,8 +478,8 @@ def trendflexdata(workouts, options, userid=0):
'groupsize': groupsize,
})
if yparam == 'pace':
df['y'] = dataprep.paceformatsecs(df['y']/1.0e3)
#if yparam == 'pace':
# df['y'] = dataprep.paceformatsecs(df['y']/1.0e3)
aantal = len(df)
@@ -568,7 +574,7 @@ def flexalldata(workouts, options):
workstrokesonly = not includereststrokes
columns = [xparam, yparam1, yparam2, 'spm', 'driveenergy', 'distance']
ids = [int(w.id) for w in workouts]
df = dataprep.getsmallrowdata_db(columns, ids=ids,
df = dataprep.read_data(columns, ids=ids,
workstrokesonly=workstrokesonly,
doclean=True,
)
@@ -609,7 +615,7 @@ def histodata(workouts, options):
if savedata: # pragma: no cover
workstrokesonly = not includereststrokes
ids = [int(w.id) for w in workouts]
df = dataprep.getsmallrowdata_db([plotfield], ids=ids,
df = dataprep.read_data([plotfield], ids=ids,
workstrokesonly=workstrokesonly,
doclean=True,
)
@@ -625,6 +631,7 @@ def histodata(workouts, options):
def cpdata(workouts, options):
start = timezone.now()
userid = options['userid']
cpfit = options['cpfit']
cpoverlay = options['cpoverlay']
@@ -635,7 +642,7 @@ def cpdata(workouts, options):
delta, cpvalue, avgpower, workoutnames, urls = dataprep.fetchcp_new(
r, workouts)
powerdf = pd.DataFrame({
powerdf = pl.DataFrame({
'Delta': delta,
'CP': cpvalue,
'workout': workoutnames,
@@ -644,16 +651,17 @@ def cpdata(workouts, options):
savedata = options.get('savedata',False)
if savedata: # pragma: no cover
return powerdf
return powerdf.to_pandas()
if powerdf.empty: # pragma: no cover
if powerdf.is_empty(): # pragma: no cover
return('', '<p>No valid data found</p>')
powerdf = powerdf[powerdf['CP'] > 0]
powerdf.dropna(axis=0, inplace=True)
powerdf.sort_values(['Delta', 'CP'], ascending=[1, 0], inplace=True)
powerdf.drop_duplicates(subset='Delta', keep='first', inplace=True)
powerdf = powerdf.lazy().filter(pl.col("CP")>0)
powerdf = powerdf.sort(["Delta", "CP"], descending=[False, True])
powerdf = powerdf.unique(subset="Delta", keep="first")
powerdf = powerdf.fill_nan(None).drop_nulls()
powerdf = powerdf.collect()
rowername = r.user.first_name+" "+r.user.last_name
@@ -880,7 +888,7 @@ def comparisondata(workouts, options):
'time', 'pace', 'workoutstate',
'workoutid']
df = dataprep.getsmallrowdata_db(columns, ids=ids,
df = dataprep.read_data(columns, ids=ids,
workstrokesonly=workstrokesonly,
doclean=True,
)
@@ -923,28 +931,22 @@ def boxplotdata(workouts, options):
ids = [w.id for w in workouts]
# prepare data frame
datadf, extracols = dataprep.read_cols_df_sql(ids, fieldlist)
datadf = dataprep.read_data(fieldlist, ids)
datadf = dataprep.clean_df_stats(datadf, workstrokesonly=workstrokesonly)
datadf = dataprep.remove_nulls_pl(datadf)
datadf = dataprep.filter_df(datadf, 'spm', spmmin,
largerthan=True)
datadf = dataprep.filter_df(datadf, 'spm', spmmax,
largerthan=False)
datadf = dataprep.filter_df(datadf, 'driveenergy', workmin,
largerthan=True)
datadf = dataprep.filter_df(datadf, 'driveneergy', workmax,
largerthan=False)
try:
datadf = datadf.filter(
pl.col("spm")>spmmin,
pl.col("spm")<spmmax,
pl.col("driveenergy")>workmin,
pl.col("driveenergy")<workmax,
)
except ColumnNotFoundError:
pass
datadf.dropna(axis=0, how='any', inplace=True)
datadf = datadf[datadf['workoutid'].isin(ids) == True]
datadf['workoutid'].replace(datemapping, inplace=True)
datadf.rename(columns={"workoutid": "date"}, inplace=True)
datadf['date'] = pd.to_datetime(datadf['date'], errors='coerce')
datadf = datadf.dropna(subset=['date'])
datadf = datadf.sort_values(['date'])
datadf = datadf.with_columns((pl.col("workoutid").apply(lambda x: datemapping[x])).alias("date"))
if userid == 0: # pragma: no cover
extratitle = ''
@@ -954,7 +956,7 @@ def boxplotdata(workouts, options):
savedata = options.get('savedata',False)
if savedata: # pragma: no cover
return datadf
return datadf.to_pandas()
script, div = interactive_boxchart(datadf, plotfield,
extratitle=extratitle,
@@ -1149,9 +1151,6 @@ def trainingzones_view(request, userid=0):
form = TrainingZonesForm({
'startdate': startdate,
'enddate': enddate,
'zones': zones,
'dates': date_agg,
'yaxis': yaxis,
})
if request.method == 'POST': # pragma: no cover
@@ -1160,9 +1159,6 @@ def trainingzones_view(request, userid=0):
if form.is_valid():
startdate = form.cleaned_data['startdate']
enddate = form.cleaned_data['enddate']
zones = form.cleaned_data['zones']
date_agg = form.cleaned_data['dates']
yaxis = form.cleaned_data['yaxis']
if date_agg == 'week':
startdate = startdate - datetime.timedelta(days=startdate.weekday())
@@ -1234,12 +1230,16 @@ def trainingzones_view_data(request, userid=0):
request.GET.get('enddate'), "%Y-%m-%d")
enddate = arrow.get(enddate).datetime
data = get_zones_report(r, startdate, enddate,
if (enddate-startdate).days > 200:
date_agg = "month"
data = get_zones_report_pl(r, startdate, enddate,
trainingzones=zones, date_agg=date_agg, yaxis=yaxis)
script, div = interactive_zoneschart(
script, div = interactive_zoneschart2(
r, data, startdate, enddate, trainingzones=zones, date_agg=date_agg, yaxis=yaxis)
return JSONResponse({
'script': script,
'div': div,
@@ -1384,547 +1384,6 @@ def ajax_agegrouprecords(request,
)
# Show ranking distances including predicted paces
@login_required()
@permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True)
def rankings_view2(request, userid=0,
startdate=timezone.now()-datetime.timedelta(days=365),
enddate=timezone.now(),
deltadays=-1,
startdatestring="",
enddatestring=""):
if deltadays > 0: # pragma: no cover
startdate = enddate-datetime.timedelta(days=int(deltadays))
if startdatestring != "": # pragma: no cover
startdate = iso8601.parse_date(startdatestring)
if enddatestring != "": # pragma: no cover
enddate = iso8601.parse_date(enddatestring)
if enddate < startdate: # pragma: no cover
s = enddate
enddate = startdate
startdate = s
if userid == 0:
userid = request.user.id
else:
lastupdated = "1900-01-01"
promember = 0
r = getrequestrower(request, userid=userid)
theuser = r.user
wcdurations = []
wcpower = []
lastupdated = "1900-01-01"
userid = 0
if 'options' in request.session:
options = request.session['options']
try:
wcdurations = options['wcdurations']
wcpower = options['wcpower']
lastupdated = options['lastupdated']
except KeyError: # pragma: no cover
pass
try:
userid = options['userid']
except KeyError: # pragma: no cover
userid = 0
else:
options = {}
lastupdatedtime = arrow.get(lastupdated).timestamp()
current_time = arrow.utcnow().timestamp()
deltatime_seconds = current_time - lastupdatedtime
recalc = False
if str(userid) != str(theuser) or deltatime_seconds > 3600:
recalc = True
options['lastupdated'] = arrow.utcnow().isoformat()
else: # pragma: no cover
recalc = False
options['userid'] = theuser.id
if r.birthdate:
age = calculate_age(r.birthdate)
else:
age = 0
agerecords = CalcAgePerformance.objects.filter(
age=age,
sex=r.sex,
weightcategory=r.weightcategory)
if len(agerecords) == 0:
recalc = True
wcpower = []
wcdurations = []
else:
wcdurations = []
wcpower = []
for record in agerecords:
wcdurations.append(record.duration)
wcpower.append(record.power)
options['wcpower'] = wcpower
options['wcdurations'] = wcdurations
if theuser:
options['userid'] = theuser.id
request.session['options'] = options
result = request.user.is_authenticated and ispromember(request.user)
if result:
promember = 1
# get all indoor rows in date range
# process form
if request.method == 'POST' and "daterange" in request.POST:
dateform = DateRangeForm(request.POST)
deltaform = DeltaDaysForm(request.POST)
if dateform.is_valid():
startdate = dateform.cleaned_data['startdate']
enddate = dateform.cleaned_data['enddate']
if startdate > enddate: # pragma: no cover
s = enddate
enddate = startdate
startdate = s
elif request.method == 'POST' and "datedelta" in request.POST: # pragma: no cover
deltaform = DeltaDaysForm(request.POST)
if deltaform.is_valid():
deltadays = deltaform.cleaned_data['deltadays']
if deltadays:
enddate = timezone.now()
startdate = enddate-datetime.timedelta(days=deltadays)
if startdate > enddate:
s = enddate
enddate = startdate
startdate = s
dateform = DateRangeForm(initial={
'startdate': startdate,
'enddate': enddate,
})
else:
dateform = DateRangeForm()
deltaform = DeltaDaysForm()
else:
dateform = DateRangeForm(initial={
'startdate': startdate,
'enddate': enddate,
})
deltaform = DeltaDaysForm()
# get all 2k (if any) - this rower, in date range
try:
r = getrower(theuser)
except Rower.DoesNotExist: # pragma: no cover
r = 0
uu = theuser
# test to fix bug
startdate = datetime.datetime.combine(startdate, datetime.time())
enddate = datetime.datetime.combine(enddate, datetime.time(23, 59, 59))
startdate = arrow.get(startdate).datetime
enddate = arrow.get(enddate).datetime
thedistances = []
theworkouts = []
thesecs = []
rankingdistances.sort()
rankingdurations.sort()
for rankingdistance in rankingdistances:
workouts = Workout.objects.filter(
user=r, distance=rankingdistance,
workouttype__in=['rower', 'dynamic', 'slides'],
rankingpiece=True,
startdatetime__gte=startdate,
startdatetime__lte=enddate).order_by('duration')
if workouts:
thedistances.append(rankingdistance)
theworkouts.append(workouts[0])
timesecs = 3600*workouts[0].duration.hour
timesecs += 60*workouts[0].duration.minute
timesecs += workouts[0].duration.second
timesecs += 1.e-6*workouts[0].duration.microsecond
thesecs.append(timesecs)
for rankingduration in rankingdurations:
workouts = Workout.objects.filter(
user=r, duration=rankingduration,
workouttype='rower',
rankingpiece=True,
startdatetime__gte=startdate,
startdatetime__lte=enddate).order_by('-distance')
if workouts:
thedistances.append(workouts[0].distance)
theworkouts.append(workouts[0])
timesecs = 3600*workouts[0].duration.hour
timesecs += 60*workouts[0].duration.minute
timesecs += workouts[0].duration.second
timesecs += 1.e-5*workouts[0].duration.microsecond
thesecs.append(timesecs)
thedistances = np.array(thedistances)
thesecs = np.array(thesecs)
thevelos = thedistances/thesecs
theavpower = 2.8*(thevelos**3)
# create interactive plot
if len(thedistances) != 0:
res = interactive_cpchart(
r, thedistances, thesecs, theavpower,
theworkouts, promember=promember,
wcdurations=wcdurations, wcpower=wcpower
)
script = res[0]
div = res[1]
paulslope = res[2]
paulintercept = res[3]
p1 = res[4]
message = res[5]
else:
script = ''
div = '<p>No ranking pieces found.</p>'
paulslope = 1
paulintercept = 1
p1 = [1, 1, 1, 1]
message = ""
if request.method == 'POST' and "piece" in request.POST: # pragma: no cover
form = PredictedPieceForm(request.POST)
if form.is_valid():
value = form.cleaned_data['value']
hourvalue, value = divmod(value, 60)
if hourvalue >= 24:
hourvalue = 23
pieceunit = form.cleaned_data['pieceunit']
if pieceunit == 'd':
rankingdistances.append(value)
else:
rankingdurations.append(datetime.time(
minute=int(value), hour=int(hourvalue)))
else:
form = PredictedPieceForm()
rankingdistances.sort()
rankingdurations.sort()
predictions = []
cpredictions = []
for rankingdistance in rankingdistances:
# Paul's model
p = paulslope*np.log10(rankingdistance)+paulintercept
velo = 500./p
t = rankingdistance/velo
pwr = 2.8*(velo**3)
try:
pwr = int(pwr)
except (ValueError, AttributeError): # pragma: no cover
pwr = 0
a = {'distance': rankingdistance,
'duration': timedeltaconv(t),
'pace': timedeltaconv(p),
'power': int(pwr)}
predictions.append(a)
# CP model -
pwr2 = p1[0]/(1+t/p1[2])
pwr2 += p1[1]/(1+t/p1[3])
if pwr2 <= 0: # pragma: no cover
pwr2 = 50.
velo2 = (pwr2/2.8)**(1./3.)
if np.isnan(velo2) or velo2 <= 0: # pragma: no cover
velo2 = 1.0
t2 = rankingdistance/velo2
pwr3 = p1[0]/(1+t2/p1[2])
pwr3 += p1[1]/(1+t2/p1[3])
if pwr3 <= 0: # pragma: no cover
pwr3 = 50.
velo3 = (pwr3/2.8)**(1./3.)
if np.isnan(velo3) or velo3 <= 0: # pragma: no cover
velo3 = 1.0
t3 = rankingdistance/velo3
p3 = 500./velo3
a = {'distance': rankingdistance,
'duration': timedeltaconv(t3),
'pace': timedeltaconv(p3),
'power': int(pwr3)}
cpredictions.append(a)
for rankingduration in rankingdurations:
t = 3600.*rankingduration.hour
t += 60.*rankingduration.minute
t += rankingduration.second
t += rankingduration.microsecond/1.e6
# Paul's model
ratio = paulintercept/paulslope
u = ((2**(2+ratio))*(5.**(3+ratio))*t*np.log(10))/paulslope
d = 500*t*np.log(10.)
d = d/(paulslope*lambertw(u))
d = d.real
velo = d/t
p = 500./velo
pwr = 2.8*(velo**3)
try:
a = {'distance': int(d),
'duration': timedeltaconv(t),
'pace': timedeltaconv(p),
'power': int(pwr)}
predictions.append(a)
except: # pragma: no cover
pass
# CP model
pwr = p1[0] / (1 + t / p1[2])
pwr += p1[1] / (1 + t / p1[3])
if pwr <= 0: # pragma: no cover
pwr = 50.
velo = (pwr / 2.8)**(1. / 3.)
if np.isnan(velo) or velo <= 0: # pragma: no cover
velo = 1.0
d = t * velo
p = 500. / velo
a = {'distance': int(d),
'duration': timedeltaconv(t),
'pace': timedeltaconv(p),
'power': int(pwr)}
cpredictions.append(a)
if recalc:
wcdurations = []
wcpower = []
durations = [1, 4, 30, 60]
distances = [100, 500, 1000, 2000, 5000, 6000, 10000, 21097, 42195]
df = pd.DataFrame(
list(
C2WorldClassAgePerformance.objects.filter(
sex=r.sex,
weightcategory=r.weightcategory
).values()
)
)
jsondf = df.to_json()
messages.error(request, message)
return render(request, 'rankings.html',
{'rankingworkouts': theworkouts,
'interactiveplot': script,
'the_div': div,
'predictions': predictions,
'cpredictions': cpredictions,
'nrdata': len(thedistances),
'form': form,
'dateform': dateform,
'deltaform': deltaform,
'id': theuser,
'theuser': uu,
'rower': r,
'active': 'nav-analysis',
'age': age,
'sex': r.sex,
'recalc': recalc,
'weightcategory': r.weightcategory,
'startdate': startdate,
'enddate': enddate,
'teams': get_my_teams(request.user),
})
@login_required()
def otecp_toadmin_view(request, theuser=0,
startdate=timezone.now() - datetime.timedelta(days=365),
enddate=timezone.now(),
startdatestring="",
enddatestring="",
): # pragma: no cover
if startdatestring != "": # pragma: no cover
try:
startdate = iso8601.parse_date(startdatestring)
except ParseError:
pass
if enddatestring != "": # pragma: no cover
try:
enddate = iso8601.parse_date(enddatestring)
except ParseError:
pass
if theuser == 0: # pragma: no cover
theuser = request.user.id
u = User.objects.get(id=theuser)
r = Rower.objects.get(user=u)
startdate = datetime.datetime.combine(startdate, datetime.time())
enddate = datetime.datetime.combine(enddate, datetime.time(23, 59, 59))
theworkouts = Workout.objects.filter(
user=r, rankingpiece=True,
workouttype__in=[
'rower',
'dynamic',
'slides'
],
startdatetime__gte=startdate,
startdatetime__lte=enddate
).order_by("-startdatetime")
delta, cpvalue, avgpower = dataprep.fetchcp(
r, theworkouts, table='cpergdata'
)
powerdf = pd.DataFrame({
'Delta': delta,
'CP': cpvalue,
})
csvfilename = 'CP_data_user_{id}.csv'.format(
id=theuser
)
powerdf = powerdf[powerdf['CP'] > 0]
powerdf.dropna(axis=0, inplace=True)
powerdf.sort_values(['Delta', 'CP'], ascending=[1, 0], inplace=True)
powerdf.drop_duplicates(subset='Delta', keep='first', inplace=True)
powerdf.to_csv(csvfilename)
_ = myqueue(queuehigh,
handle_sendemailfile,
'Sander',
'Roosendaal',
'roosendaalsander@gmail.com',
csvfilename,
delete=True)
successmessage = "The CSV file was sent to the site admin per email"
messages.info(request, successmessage)
response = HttpResponseRedirect('/rowers/list-workouts/')
return response
@login_required()
def otwcp_toadmin_view(request, theuser=0,
startdate=timezone.now() - datetime.timedelta(days=365),
enddate=timezone.now(),
startdatestring="",
enddatestring="",
): # pragma: no cover
if startdatestring != "":
try:
startdate = iso8601.parse_date(startdatestring)
except ParseError:
pass
if enddatestring != "":
try:
enddate = iso8601.parse_date(enddatestring)
except ParseError:
pass
if theuser == 0:
theuser = request.user.id
u = User.objects.get(id=theuser)
r = Rower.objects.get(user=u)
startdate = datetime.datetime.combine(startdate, datetime.time())
enddate = datetime.datetime.combine(enddate, datetime.time(23, 59, 59))
theworkouts = Workout.objects.filter(
user=r, rankingpiece=True,
workouttype='water',
startdatetime__gte=startdate,
startdatetime__lte=enddate
).order_by("-startdatetime")
delta, cpvalue, avgpower = dataprep.fetchcp(
r, theworkouts, table='cpdata'
)
powerdf = pd.DataFrame({
'Delta': delta,
'CP': cpvalue,
})
csvfilename = 'CP_data_user_{id}.csv'.format(
id=theuser
)
powerdf = powerdf[powerdf['CP'] > 0]
powerdf.dropna(axis=0, inplace=True)
powerdf.sort_values(['Delta', 'CP'], ascending=[1, 0], inplace=True)
powerdf.drop_duplicates(subset='Delta', keep='first', inplace=True)
powerdf.to_csv(csvfilename)
_ = myqueue(queuehigh,
handle_sendemailfile,
'Sander',
'Roosendaal',
'roosendaalsander@gmail.com',
csvfilename,
delete=True)
successmessage = "The CSV file was sent to the site admin per email"
messages.info(request, successmessage)
response = HttpResponseRedirect('/rowers/list-workouts/')
return response
def agegroupcpview(request, age, normalize=0, userid=0):
script, div = interactive_agegroupcpchart(age, normalized=normalize)
response = render(request, 'agegroupcp.html',
{
'active': 'nav-analysis',
'interactiveplot': script,
'the_div': div,
}
)
return response
def agegrouprecordview(request, sex='male', weightcategory='hwt',
distance=2000, duration=None):
@@ -2761,15 +2220,16 @@ def history_view_data(request, userid=0):
ids = [w.id for w in g_workouts]
columns = ['hr', 'power', 'time']
columns = ['hr', 'power', 'time', 'workoutstate', 'workoutid']
df = dataprep.read_data(columns, ids=ids)
df = dataprep.remove_nulls_pl(df)
df = getsmallrowdata_db(columns, ids=ids)
try:
df['deltat'] = df['time'].diff().clip(lower=0)
df = df.with_columns(pl.col('time').diff().clip(lower_bound=0).alias("deltat"))
except KeyError: # pragma: no cover
pass
df = dataprep.clean_df_stats(df, workstrokesonly=True,
ignoreadvanced=True, ignorehr=False)
totalmeters, totalhours, totalminutes, totalseconds = get_totals(
g_workouts)
@@ -2798,13 +2258,14 @@ def history_view_data(request, userid=0):
whours=whours,
wminutes=wminutes, wseconds=wseconds,
)
ddf = getsmallrowdata_db(columns, ids=[w.id for w in a_workouts])
ddf = dataprep.read_data(columns, ids=[w.id for w in a_workouts])
ddf = dataprep.remove_nulls_pl(ddf)
try:
ddf['deltat'] = ddf['time'].diff().clip(lower=0)
ddf = ddf.with_columns(pl.col("time").diff().clip(lower_bound=0).alias("deltat"))
except KeyError: # pragma: no cover
pass
ddf = dataprep.clean_df_stats(ddf, workstrokesonly=False,
ddf = dataprep.clean_df_stats_pl(ddf, workstrokesonly=False,
ignoreadvanced=True)
try:
@@ -2812,14 +2273,14 @@ def history_view_data(request, userid=0):
except (KeyError, ValueError, AttributeError): # pragma: no cover
ddict['hrmean'] = 0
try:
ddict['hrmax'] = ddf['hr'].max().astype(int)
ddict['hrmax'] = int(ddf['hr'].max())
except (KeyError, ValueError, AttributeError): # pragma: no cover
ddict['hrmax'] = 0
ddict['powermean'] = int(wavg(ddf, 'power', 'deltat'))
try:
ddict['powermax'] = ddf['power'].max().astype(int)
except KeyError: # pragma: no cover
ddict['powermax'] = int(ddf['power'].max())
except (KeyError, ColumnNotFoundError): # pragma: no cover
ddict['powermax'] = 0
ddict['nrworkouts'] = a_workouts.count()
listofdicts.append(ddict)
@@ -2833,14 +2294,14 @@ def history_view_data(request, userid=0):
totalsdict['distance'] = totalmeters
try:
totalsdict['powermean'] = int(wavg(df, 'power', 'deltat'))
totalsdict['powermax'] = df['power'].max().astype(int)
except KeyError: # pragma: no cover
totalsdict['powermax'] = int(df['power'].max())
except (KeyError, ColumnNotFoundError): # pragma: no cover
totalsdict['powermean'] = 0
totalsdict['powermax'] = 0
try:
totalsdict['hrmean'] = int(wavg(df, 'hr', 'deltat'))
totalsdict['hrmax'] = df['hr'].max().astype(int)
except KeyError: # pragma: no cover
totalsdict['hrmax'] = int(df['hr'].max())
except (KeyError, ColumnNotFoundError): # pragma: no cover
totalsdict['hrmean'] = 0
totalsdict['hrmax'] = 0
@@ -2859,14 +2320,22 @@ def history_view_data(request, userid=0):
a_workouts = g_workouts.filter(workouttype=typeselect)
meters, hours, minutes, seconds = get_totals(a_workouts)
totalseconds = 3600 * hours + 60 * minutes + seconds
ddf = getsmallrowdata_db(columns, ids=[w.id for w in a_workouts])
ddf = dataprep.read_data(columns, ids=[w.id for w in a_workouts])
ddf = dataprep.remove_nulls_pl(ddf)
if ddf.is_empty():
totalscript = ""
totaldiv = "No data"
try:
ddf['deltat'] = ddf['time'].diff().clip(lower=0)
ddf = ddf.with_columns(pl.col("time").diff().clip(lower_bound=0).alias("deltat"))
except KeyError:
pass
except ColumnNotFoundError:
totalscript = ""
totaldiv = "No data"
ddf = dataprep.clean_df_stats(ddf, workstrokesonly=True,
ddf = dataprep.clean_df_stats_pl(ddf, workstrokesonly=True,
ignoreadvanced=True)
totalscript, totaldiv = interactive_hr_piechart(
+64 -61
View File
@@ -69,102 +69,102 @@ def strokedataform(request, id=0):
def api_get_dataframe(startdatetime, df):
try:
time = df['time']/1.e3
except KeyError: # pragma: no cover
except (KeyError, ColumnNotFoundError): # pragma: no cover
try:
time = df['t']/10.
except KeyError:
return 400, "Missing time", pd.DataFrame()
except (KeyError, ColumnNotFoundError):
return 400, "Missing time", pl.DataFrame()
try:
spm = df['spm']
except KeyError: # pragma: no cover
return 400, "Missing spm", pd.DataFrame()
except (KeyError, ColumnNotFoundError): # pragma: no cover
return 400, "Missing spm", pl.DataFrame()
try:
distance = df['distance']
except KeyError: # pragma: no cover
except (KeyError, ColumnNotFoundError): # pragma: no cover
try:
distance = df['d']/10.
except KeyError:
return 400, "Missing distance", pd.DataFrame()
except (KeyError, ColumnNotFoundError):
return 400, "Missing distance", pl.DataFrame()
try:
pace = df['pace']/1.e3
except KeyError: # pragma: no cover
except (KeyError, ColumnNotFoundError): # pragma: no cover
try:
pace = df['p']/10.
except KeyError:
return 400, "Missing pace", pd.DataFrame()
except (KeyError, ColumnNotFoundError):
return 400, "Missing pace", pl.DataFrame()
try:
power = df['power']
except KeyError: # pragma: no cover
except (KeyError, ColumnNotFoundError): # pragma: no cover
power = 0*time
try:
drivelength = df['drivelength']
except KeyError:
except (KeyError, ColumnNotFoundError):
drivelength = 0*time
try:
dragfactor = df['dragfactor']
except KeyError:
except (KeyError, ColumnNotFoundError):
dragfactor = 0*time
try:
drivetime = df['drivetime']
except KeyError:
except (KeyError, ColumnNotFoundError):
drivetime = 0*time
try:
strokerecoverytime = df['strokerecoverytime']
except KeyError:
except (KeyError, ColumnNotFoundError):
strokerecoverytime = 0*time
try:
averagedriveforce = df['averagedriveforce']
except KeyError:
except (KeyError, ColumnNotFoundError):
averagedriveforce = 0*time
try:
peakdriveforce = df['peakdriveforce']
except KeyError:
except (KeyError, ColumnNotFoundError):
peakdriveforce = 0*time
try:
wash = df['wash']
except KeyError:
except (KeyError, ColumnNotFoundError):
wash = 0*time
try:
catch = df['catch']
except KeyError:
except (KeyError, ColumnNotFoundError):
catch = 0*time
try:
finish = df['finish']
except KeyError:
except (KeyError, ColumnNotFoundError):
finish = 0*time
try:
peakforceangle = df['peakforceangle']
except KeyError:
except (KeyError, ColumnNotFoundError):
peakforceangle = 0*time
try:
driveenergy = df['driveenergy']
except KeyError:
except (KeyError, ColumnNotFoundError):
driveenergy = 60.*power/spm
try:
slip = df['slip']
except KeyError:
except (KeyError, ColumnNotFoundError):
slip = 0*time
try:
lapidx = df['lapidx']
except KeyError:
except (KeyError, ColumnNotFoundError):
lapidx = 0*time
try:
hr = df['hr']
except KeyError: # pragma: no cover
except (KeyError, ColumnNotFoundError): # pragma: no cover
hr = 0*df['time']
try:
latitude = df['latitude']
except KeyError:
except (KeyError, ColumnNotFoundError):
latitude = 0*df['time']
try:
longitude = df['longitude']
except KeyError:
except (KeyError, ColumnNotFoundError):
longitude = 0*df['time']
starttime = totimestamp(startdatetime)+time[0]
@@ -172,7 +172,7 @@ def api_get_dataframe(startdatetime, df):
dologging('apilog.log',"(strokedatajson_v2/3 POST - data parsed)")
data = pd.DataFrame({'TimeStamp (sec)': unixtime,
data = pl.DataFrame({'TimeStamp (sec)': unixtime,
' Horizontal (meters)': distance,
' Cadence (stokes/min)': spm,
' HRCur (bpm)': hr,
@@ -526,7 +526,7 @@ def strokedatajson_v3(request):
title = request.data.get('name','')
try:
elapsedTime = request.data['elapsedTime']
except KeyError: # pragma: no cover
except (KeyError, ColumnNotFoundError): # pragma: no cover
try:
duration = request.data['duration']
try:
@@ -538,7 +538,7 @@ def strokedatajson_v3(request):
return HttpResponse("Missing Elapsed Time", status=400)
try:
totalDistance = request.data['distance']
except KeyError: # pragma: no cover
except (KeyError, ColumnNotFoundError): # pragma: no cover
return HttpResponse("Missing Total Distance", status=400)
timeZone = request.data.get('timezone','UTC')
workouttype = request.data.get('workouttype','rower')
@@ -556,23 +556,21 @@ def strokedatajson_v3(request):
dologging('apilog.log',totalDistance)
dologging('apilog.log',elapsedTime)
df = pd.DataFrame()
df = pl.DataFrame()
try:
strokes = request.data['strokes']
except KeyError: # pragma: no cover
except (KeyError, ColumnNotFoundError): # pragma: no cover
return HttpResponse("No Stroke Data in JSON", status=400)
try:
df = pd.DataFrame(strokes['data'])
except KeyError: # pragma: no cover
df = pl.DataFrame(strokes['data'])
except (KeyError, ColumnNotFoundError): # pragma: no cover
try:
df = pd.DataFrame(request.data['strokedata'])
df = pl.DataFrame(request.data['strokedata'])
except:
return HttpResponse("No JSON Object could be decoded", status=400)
df.index = df.index.astype(int)
df.sort_index(inplace=True)
df = df.sort("time")
status, comment, data = api_get_dataframe(startdatetime, df)
if status != 200: # pragma: no cover
@@ -580,7 +578,8 @@ def strokedatajson_v3(request):
csvfilename = 'media/{code}.csv.gz'.format(code=uuid4().hex[:16])
_ = data.to_csv(csvfilename, index_label='index', compression='gzip')
with gzip.open(csvfilename, 'w') as f:
_ = data.write_csv(f)
duration = datetime.time(0,0,1)
w = Workout(
@@ -672,10 +671,11 @@ def strokedatajson_v2(request, id):
if request.method == 'GET':
columns = ['spm', 'time', 'hr', 'pace', 'power', 'distance']
datadf = dataprep.getsmallrowdata_db(columns, ids=[id])
datadf = dataprep.read_data(columns, ids=[id])
datadf = dataprep.remove_nulls_pl(datadf)
dologging('apilog.log',request.user.username+"(strokedatajson_v2 GET)")
data = datadf.to_json(orient='records')
data = datadf.write_json(row_oriented=True)
data2 = json.loads(data)
data2 = {"data": data2}
@@ -686,28 +686,27 @@ def strokedatajson_v2(request, id):
try:
for d in request.data['data']:
dologging('apilog.log',json.dumps(d))
except KeyError: # pragma: no cover
except (KeyError, ColumnNotFoundError): # pragma: no cover
try:
for d in request.data['strokedata']:
dologging('apilog.log',json.dumps(d))
except KeyError:
except (KeyError, ColumnNotFoundError):
dologging('apilog.log','No data in request.data')
checkdata, r = dataprep.getrowdata_db(id=row.id)
if not checkdata.empty: # pragma: no cover
return HttpResponse("Duplicate Error", status=409)
df = pd.DataFrame()
df = pl.DataFrame()
try:
df = pd.DataFrame(request.data['data'])
except KeyError: # pragma: no cover
df = pl.DataFrame(request.data['data'])
except (KeyError, ColumnNotFoundError): # pragma: no cover
try:
df = pd.DataFrame(request.data['strokedata'])
df = pl.DataFrame(request.data['strokedata'])
except:
return HttpResponse("No JSON object could be decoded", status=400)
df.index = df.index.astype(int)
df.sort_index(inplace=True)
df = df.sort("time")
status, comment, data = api_get_dataframe(row.startdatetime, df)
if status != 200: # pragma: no cover
@@ -729,7 +728,8 @@ def strokedatajson_v2(request, id):
row.duplicate = True
row.save()
_ = data.to_csv(csvfilename+'.gz', index_label='index', compression='gzip')
with gzip.open(csvfilename+'.gz', 'w') as f:
_ = data.write_csv(f)
row.csvfilename = csvfilename
row.save()
@@ -748,7 +748,7 @@ def strokedatajson_v2(request, id):
powerperc=powerperc, powerzones=r.powerzones)
rowdata = rdata(csvfile=row.csvfilename, rower=rr).df
datadf = dataprep.dataprep(
datadf = dataprep.dataplep(
rowdata, id=row.id, bands=True, barchart=True, otwpower=True, empower=True)
_ = myqueue(queuehigh, handle_calctrimp, row.id,
@@ -802,7 +802,11 @@ def strokedatajson(request, id=0):
if request.method == 'GET':
# currently only returns a subset.
columns = ['spm', 'time', 'hr', 'pace', 'power', 'distance']
datadf = dataprep.getsmallrowdata_db(columns, ids=[id])
datadf = dataprep.read_data(columns, ids=[id])
datadf = dataprep.remove_nulls_pl(datadf)
datadf = datadf.to_pandas()
dologging("apilog.log",request.user.username+"(strokedatajson GET) ")
return JSONResponse(datadf)
@@ -823,14 +827,13 @@ def strokedatajson(request, id=0):
return HttpResponse("No JSON object could be decoded", status=400)
try:
df = pd.DataFrame(strokedata)
df = pl.DataFrame(strokedata)
except ValueError: # pragma: no cover
return HttpResponse("Arrays must all be same length", status=400)
df.index = df.index.astype(int)
df.sort_index(inplace=True)
df = df.sort("time")
try:
time = df['time']/1.e3
except KeyError: # pragma: no cover
except (KeyError, ColumnNotFoundError): # pragma: no cover
return HttpResponse("There must be time values", status=400)
aantal = len(time)
pace = df['pace']/1.e3
@@ -869,7 +872,7 @@ def strokedatajson(request, id=0):
dologging("apilog.log",request.user.username+"(POST)")
data = pd.DataFrame({'TimeStamp (sec)': unixtime,
data = pl.DataFrame({'TimeStamp (sec)': unixtime,
' Horizontal (meters)': distance,
' Cadence (stokes/min)': spm,
' HRCur (bpm)': hr,
@@ -896,8 +899,8 @@ def strokedatajson(request, id=0):
timestr = row.startdatetime.strftime("%Y%m%d-%H%M%S")
csvfilename = 'media/Import_'+timestr+'.csv'
res = data.to_csv(csvfilename+'.gz', index_label='index',
compression='gzip')
with gzip.open(csvfilename+'.gz','w') as f:
res = data.write_csv(f)
row.csvfilename = csvfilename
row.save()
@@ -916,7 +919,7 @@ def strokedatajson(request, id=0):
powerperc=powerperc, powerzones=r.powerzones)
rowdata = rdata(csvfile=row.csvfilename, rower=rr).df
datadf = dataprep.dataprep(
datadf = dataprep.dataplep(
rowdata, id=row.id, bands=True, barchart=True, otwpower=True, empower=True)
# mangling
+1 -1
View File
@@ -208,7 +208,7 @@ def workouts_summaries_email_view(request):
)
df = dataprep.workout_summary_to_df(
r, startdate=startdate, enddate=enddate)
df.to_csv(filename, encoding='utf-8')
df.write_csv(filename)
_ = myqueue(queuehigh, handle_sendemailsummary,
r.user.first_name,
r.user.last_name,
+23
View File
@@ -1,10 +1,33 @@
from rowers.views.statements import *
from rowers.interactiveplots import sleep
from rq import Queue
from redis import Redis
from rq.job import Job
@login_required()
def sleep_view(request):
script, div = sleep()
return render(request,
"sleep.html",
{
"the_div": div,
"the_script": script,
})
def filmdeaths_view(request):
script, div = filmdeaths()
return render(request,
"filmdeaths.html",
{
"the_div": div,
"the_script": script,
}
)
@login_required()
def download_fit(request, filename=''):
+1 -1
View File
@@ -3569,7 +3569,7 @@ def rower_trainingplan_execution_view(request,
else: # pragma: no cover
data, message = get_execution_report(r, startdate, enddate)
if not data.empty:
if not data.is_empty():
script, div = interactive_planchart(data, startdate, enddate)
else: # pragma: no cover
script = ''
+8 -6
View File
@@ -16,7 +16,7 @@ from rowers.utils import (
from rowers.celery import result as celery_result
from rowers.interactiveplots import *
from scipy.interpolate import griddata
from rowers.dataprep import getsmallrowdata_db
from rowers.dataprep import getsmallrowdata_pd, read_data
from rowers.dataprep import timedeltaconv
from scipy.special import lambertw
from io import BytesIO
@@ -35,6 +35,7 @@ import threading
import redis
import colorsys
import re
import gzip
import zipfile
import bleach
import arrow
@@ -136,9 +137,9 @@ from rowers.forms import (
FitnessMetricForm, PredictedPieceFormNoDistance,
EmailForm, RegistrationForm, RegistrationFormTermsOfService,
RegistrationFormUniqueEmail, RegistrationFormSex,
CNsummaryForm, UpdateWindForm,
CNsummaryForm,
StandardsForm,
UpdateStreamForm, WorkoutMultipleCompareForm, ChartParamChoiceForm,
WorkoutMultipleCompareForm, ChartParamChoiceForm,
FusionMetricChoiceForm, BoxPlotChoiceForm, MultiFlexChoiceForm,
TrendFlexModalForm, WorkoutSplitForm, WorkoutJoinParamForm,
AnalysisOptionsForm, AnalysisChoiceForm,
@@ -205,6 +206,7 @@ from rowers.rojabo_stuff import rojabo_open
from rowers.integrations import *
from polars.exceptions import ColumnNotFoundError
import rowers.ownapistuff as ownapistuff
from rowers.ownapistuff import TEST_CLIENT_ID, TEST_CLIENT_SECRET, TEST_REDIRECT_URI
@@ -251,7 +253,7 @@ from rowers.tasks import (
handle_sendemailfile,
handle_sendemailkml,
handle_sendemailnewresponse, handle_updatedps,
handle_updatecp, long_test_task, long_test_task2,
long_test_task, long_test_task2,
handle_zip_file, handle_getagegrouprecords,
handle_update_empower,
handle_sendemailics,
@@ -1344,9 +1346,9 @@ def trydf(df, aantal, column): # pragma: no cover
s = df[column]
if len(s) != aantal:
return np.zeros(aantal)
if not np.issubdtype(s, np.number):
if not s.dtype in pl.NUMERIC_DTYPES:
return np.zeros(aantal)
except KeyError:
except (KeyError, ColumnNotFoundError):
s = np.zeros(aantal)
return s
+38 -494
View File
@@ -126,8 +126,7 @@ def workout_video_view_mini(request, id=''):
hascoordinates = pd.Series(data['latitude']).std() > 0
# create map
if hascoordinates and mode == 'water':
mapscript, mapdiv = leaflet_chart_video(data['latitude'], data['longitude'],
w.name)
mapscript, mapdiv = leaflet_chart(data['latitude'], data['longitude'])
else:
mapscript, mapdiv = interactive_chart_video(data)
data['longitude'] = data['spm']
@@ -237,8 +236,7 @@ def workout_video_view(request, id=''):
hascoordinates = pd.Series(data['latitude']).std() > 0
# create map
if hascoordinates and mode == 'water':
mapscript, mapdiv = leaflet_chart_video(data['latitude'], data['longitude'],
w.name)
mapscript, mapdiv = leaflet_chart(data['latitude'], data['longitude'])
else:
mapscript, mapdiv = interactive_chart_video(data)
data['longitude'] = data['spm']
@@ -349,12 +347,12 @@ def workout_video_create_view(request, id=0):
# get data
data, metrics, maxtime = dataprep.get_video_data(
w, groups=metricsgroups, mode=mode)
hascoordinates = pd.Series(data['latitude']).std() > 0
# create map
if hascoordinates and mode == 'water':
mapscript, mapdiv = leaflet_chart_video(data['latitude'], data['longitude'],
w.name)
mapscript, mapdiv = leaflet_chart(data['latitude'], data['longitude'])
else:
mapscript, mapdiv = interactive_chart_video(data)
data['longitude'] = data['spm']
@@ -380,6 +378,7 @@ def workout_video_create_view(request, id=0):
template = 'embedded_video.html'
return render(request,
template,
{
@@ -429,12 +428,14 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly
work_max = forceanalysis.work_max
notes = forceanalysis.notes
name = forceanalysis.name
plotcircles = forceanalysis.plotcircles
plotlines = forceanalysis.plotlines
includereststrokes = forceanalysis.include_rest_strokes
except (ForceCurveAnalysis.DoesNotExist, ValueError):
pass
else:
dist_min = 0
dist_max = 0
dist_max = row.distance
spm_min = 15
spm_max = 55
work_min = 0
@@ -442,6 +443,8 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly
notes = ''
includereststrokes = False
name = ''
plotcircles = True
plotlines = False
form = ForceCurveOptionsForm(initial={
'spm_min': spm_min,
@@ -451,10 +454,10 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly
'work_min': work_min,
'work_max': work_max,
'notes': notes,
'plottype': 'line',
'name': name,
'plotcircles': plotcircles,
'plotlines': plotlines,
})
plottype = 'line'
if request.method == 'POST':
@@ -468,10 +471,11 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly
work_max = form.cleaned_data['work_max']
notes = form.cleaned_data['notes']
name = form.cleaned_data['name']
plotlines = form.cleaned_data['plotlines']
plotcircles = form.cleaned_data['plotcircles']
if not name:
name = row.name
includereststrokes = form.cleaned_data['includereststrokes']
plottype = form.cleaned_data['plottype']
workstrokesonly = not includereststrokes
if "_save" in request.POST and "new" not in request.POST: # pragma: no cover
@@ -488,6 +492,8 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly
spm_min = spm_min,
spm_max = spm_max,
rower=row.user,
plotlines = plotlines,
plotcircles = plotcircles,
include_rest_strokes = includereststrokes,
)
else:
@@ -501,6 +507,8 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly
forceanalysis.work_max = work_max
forceanalysis.spm_min = spm_min
forceanalysis.spm_max = spm_max
forceanalysis.plotcircles = plotcircles
forceanalysis.plotlines = plotlines
forceanalysis.include_rest_strokes = includereststrokes
forceanalysis.save()
dosave = True
@@ -526,20 +534,10 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly
else: # pragma: no cover
workstrokesonly = True
plottype = 'line'
script, div, js_resources, css_resources = interactive_forcecurve(
[row],
workstrokesonly=workstrokesonly,
plottype=plottype,
dist_min = dist_min,
dist_max = dist_max,
spm_min = spm_min,
spm_max = spm_max,
work_min = work_min,
work_max = work_max,
notes=notes,
script, div = interactive_forcecurve(
[row]
)
breadcrumbs = [
@@ -580,8 +578,6 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly
'work_max': work_max,
'annotation': notes,
'the_div': div,
'js_res': js_resources,
'css_res': css_resources,
'id': id,
'mayedit': mayedit,
'teams': get_my_teams(request.user),
@@ -633,50 +629,6 @@ def otw_use_gps(request, id=0):
return HttpResponseRedirect(url)
# Show Stroke power histogram for a workout
@login_required()
@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True)
def workout_histo_view(request, id=0):
w = get_workoutuser(id, request)
r = getrequestrower(request)
mayedit = 0
if w.user == r:
mayedit = 1
res = interactive_histoall([w], 'power', False)
script = res[0]
div = res[1]
breadcrumbs = [
{
'url': '/rowers/list-workouts/',
'name': 'Workouts'
},
{
'url': get_workout_default_page(request, id),
'name': w.name
},
{
'url': reverse('workout_histo_view', kwargs={'id': id}),
'name': 'Histogram'
}
]
return render(request,
'histo_single.html',
{'interactiveplot': script,
'breadcrumbs': breadcrumbs,
'active': 'nav-workouts',
'workout': w,
'rower': r,
'the_div': div,
'id': id,
'mayedit': mayedit,
'teams': get_my_teams(request.user),
})
# add a workout manually
@login_required()
@@ -1343,7 +1295,7 @@ def remove_power_view(request, id=0):
row = rdata(csvfile=f, rower=rr)
row.df[' Power (watts)'] = 0
row.write_csv(f)
_ = dataprep.dataprep(row.df, id=workout.id)
_ = dataprep.dataplep(row.df, id=workout.id)
cpdf, delta, cpvalues = dataprep.setcp(workout)
workout.normp = 0
@@ -1667,15 +1619,10 @@ def course_compare_view(request, id=0):
except: # pragma: no cover
labeldict = {}
res = interactive_multiple_compare_chart(workoutids, xparam, yparam,
script, div = interactive_multiple_compare_chart(workoutids, xparam, yparam,
promember=promember,
plottype=plottype,
labeldict=labeldict, startenddict=startenddict)
script = res[0]
div = res[1]
errormessage = res[3]
if errormessage != '': # pragma: no cover
messages.error(request, errormessage)
breadcrumbs = [
{
@@ -1849,15 +1796,10 @@ def virtualevent_compare_view(request, id=0):
except: # pragma: no cover
labeldict = {}
res = interactive_multiple_compare_chart(workoutids, xparam, yparam,
script, div = interactive_multiple_compare_chart(workoutids, xparam, yparam,
promember=promember,
plottype=plottype,
labeldict=labeldict, startenddict=startenddict)
script = res[0]
div = res[1]
errormessage = res[3]
if errormessage != '': # pragma: no cover
messages.error(request, errormessage)
breadcrumbs = [
{
@@ -2237,10 +2179,6 @@ def workouts_view(request, message='', successmessage='',
for w in workoutsnohr: # pragma: no cover
_ = dataprep.workout_trimp(w)
# ids = [w.id for w in workouts]
# df = dataprep.getsmallrowdata_db(['time','power'],ids=ids)
# polarization = dataprep.polarization_index(df,r)
if query: # pragma: no cover
query_list = query.split()
workouts = workouts.filter(
@@ -2278,7 +2216,7 @@ def workouts_view(request, message='', successmessage='',
if yaxis not in ['duration', 'trimp', 'rscore']: # pragma: no cover
yaxis = 'duration'
script, div = interactive_activitychart(g_workouts,
script, div = interactive_activitychart2(g_workouts,
g_startdate,
g_enddate,
stack=stack,
@@ -2823,348 +2761,6 @@ def workout_downloadmetar_view(request, id=0,
return response
# Show form to update wind data
@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True)
@user_passes_test(ispromember,
login_url="/rowers/paidplans",
message="This functionality requires a Pro plan or higher."
" If you are already a Pro user, please log in to access this functionality",
redirect_field_name=None)
def workout_wind_view(request, id=0, message="", successmessage=""):
row = get_workoutuser(id, request)
r = getrower(request.user)
breadcrumbs = [
{
'url': '/rowers/list-workouts/',
'name': 'Workouts'
},
{
'url': get_workout_default_page(request, id),
'name': row.name
},
{
'url': reverse('workout_wind_view', kwargs={'id': id}),
'name': 'Wind'
}
]
# get data
f1 = row.csvfilename
u = row.user.user
r = getrower(u)
# create bearing
rowdata = rdata(csvfile=f1)
if row == 0: # pragma: no cover
return HttpResponse("Error: CSV Data File Not Found")
hascoordinates = 1
try:
latitude = rowdata.df.loc[:, ' latitude']
except KeyError:
hascoordinates = 0
if hascoordinates and not latitude.std(): # pragma: no cover
hascoordinates = 0
try:
_ = rowdata.df.loc[:, 'bearing'].values
except KeyError:
rowdata.add_bearing()
rowdata.write_csv(f1, gzip=True)
if hascoordinates:
avglat = rowdata.df[' latitude'].mean()
avglon = rowdata.df[' longitude'].mean()
airportcode, newlat, newlon, airportdistance = get_airport_code(
avglat, avglon)
airportcode = airportcode.upper()
airportdistance = airportdistance[0]
else:
airportcode = 'UNKNOWN'
airportdistance = 0
if request.method == 'POST':
# process form
form = UpdateWindForm(request.POST)
if form.is_valid():
vwind1 = form.cleaned_data['vwind1']
vwind2 = form.cleaned_data['vwind2']
dist1 = form.cleaned_data['dist1']
dist2 = form.cleaned_data['dist2']
winddirection1 = form.cleaned_data['winddirection1']
winddirection2 = form.cleaned_data['winddirection2']
windunit = form.cleaned_data['windunit']
rowdata.update_wind(vwind1, vwind2,
winddirection1,
winddirection2,
dist1, dist2,
units=windunit)
rowdata.write_csv(f1, gzip=True)
else: # pragma: no cover
message = "Invalid Form"
messages.error(request, message)
kwargs = {
'id': id
}
url = reverse('workout_wind_view', kwargs=kwargs)
_ = HttpResponseRedirect(url)
else:
form = UpdateWindForm()
# create interactive plot
res = interactive_windchart(encoder.decode_hex(id), promember=1)
script = res[0]
div = res[1]
if hascoordinates:
gmscript, gmdiv = leaflet_chart(
rowdata.df[' latitude'],
rowdata.df[' longitude'],
row.name)
else:
gmscript = ""
gmdiv = "No GPS data available"
messages.info(request, successmessage)
messages.error(request, message)
return render(request,
'windedit.html',
{'workout': row,
'rower': r,
'breadcrumbs': breadcrumbs,
'active': 'nav-workouts',
'teams': get_my_teams(request.user),
'interactiveplot': script,
'form': form,
'airport': airportcode,
'airportdistance': airportdistance,
'the_div': div,
'gmap': gmscript,
'gmapdiv': gmdiv})
# Show form to update River stream data (for river dwellers)
@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True)
@user_passes_test(ispromember,
login_url="/rowers/paidplans",
message="This functionality requires a Pro plan or higher."
" If you are already a Pro user, please log in to access this functionality",
redirect_field_name=None)
def workout_stream_view(request, id=0, message="", successmessage=""):
row = get_workoutuser(id, request)
r = getrower(request.user)
# create interactive plot
f1 = row.csvfilename
u = row.user.user
r = getrower(u)
rowdata = rdata(csvfile=f1)
if rowdata == 0: # pragma: no cover
messages.info(request, "Error: CSV data file not found")
url = reverse('workout_edit_view', kwargs={
'id': encoder.encode_hex(row.id)})
return HttpResponseRedirect(url)
if request.method == 'POST':
# process form
form = UpdateStreamForm(request.POST)
if form.is_valid():
dist1 = form.cleaned_data['dist1']
dist2 = form.cleaned_data['dist2']
stream1 = form.cleaned_data['stream1']
stream2 = form.cleaned_data['stream2']
streamunit = form.cleaned_data['streamunit']
rowdata.update_stream(stream1, stream2, dist1, dist2,
units=streamunit)
rowdata.write_csv(f1, gzip=True)
else: # pragma: no cover
message = "Invalid Form"
messages.error(request, message)
kwargs = {
'id': id}
url = reverse('workout_wind_view', kwargs=kwargs)
_ = HttpResponseRedirect(url)
else:
form = UpdateStreamForm()
# create interactive plot
res = interactive_streamchart(encoder.decode_hex(id), promember=1)
script = res[0]
div = res[1]
breadcrumbs = [
{
'url': '/rowers/list-workouts/',
'name': 'Workouts'
},
{
'url': get_workout_default_page(request, id),
'name': row.name
},
{
'url': reverse('workout_stream_view', kwargs={'id': id}),
'name': 'Stream'
}
]
messages.info(request, successmessage)
messages.error(request, message)
return render(request,
'streamedit.html',
{'workout': row,
'rower': r,
'breadcrumbs': breadcrumbs,
'active': 'nav-workouts',
'teams': get_my_teams(request.user),
'interactiveplot': script,
'form': form,
'the_div': div})
# Form to set average crew weight and boat type, then run power calcs
@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True)
@user_passes_test(ispromember, login_url="/rowers/paidplans", redirect_field_name=None)
def workout_otwsetpower_view(request, id=0, message="", successmessage=""):
w = get_workoutuser(id, request)
r = getrower(request.user)
mayedit = 1
if request.method == 'POST':
# process form
form = AdvancedWorkoutForm(request.POST)
if form.is_valid():
boattype = form.cleaned_data['boattype']
weightvalue = form.cleaned_data['weightvalue']
coastalbrand = form.cleaned_data['boatbrand']
boatclass = w.workouttype
w.boattype = boattype
w.weightvalue = weightvalue
w.boatbrand = coastalbrand
w.save()
# load row data & create power/wind/bearing columns if not set
f1 = w.csvfilename
rowdata = rdata(csvfile=f1)
if rowdata == 0: # pragma: no cover
return HttpResponse("Error: CSV Data File Not Found")
try:
_ = rowdata.df['vstream']
except KeyError:
rowdata.add_stream(0)
rowdata.write_csv(f1, gzip=True)
try:
_ = rowdata.df['bearing']
except KeyError:
rowdata.add_bearing()
rowdata.write_csv(f1, gzip=True)
try:
_ = rowdata.df['vwind']
except KeyError:
rowdata.add_wind(0, 0)
rowdata.write_csv(f1, gzip=True)
# do power calculation (asynchronous)
r = w.user
u = r.user
first_name = u.first_name
last_name = u.last_name
emailaddress = u.email
job = myqueue(queue,
handle_otwsetpower, f1, boattype, boatclass, coastalbrand,
weightvalue,
first_name, last_name, emailaddress, encoder.decode_hex(
id),
ps=[r.p0, r.p1, r.p2, r.p3],
ratio=r.cpratio,
# quick_calc = quick_calc,
# go_service=go_service,
emailbounced=r.emailbounced
)
try:
request.session['async_tasks'] += [(job.id, 'otwsetpower')]
except KeyError:
request.session['async_tasks'] = [(job.id, 'otwsetpower')]
successmessage = 'Your calculations have been submitted." \
" You will receive an email when they are done." \
" You can check the status of your calculations" \
" <a href="/rowers/jobs-status/" target="_blank">here</a>'
messages.info(request, successmessage)
kwargs = {
'id': id}
try:
url = request.session['referer']
except KeyError:
url = reverse('workout_edit_view', kwargs=kwargs)
response = HttpResponseRedirect(url)
return response
else: # pragma: no cover
message = "Invalid Form"
messages.error(request, message)
kwargs = {
'id': id}
url = reverse('workout_otwsetpower_view', kwargs=kwargs)
response = HttpResponseRedirect(url)
else:
form = AdvancedWorkoutForm(instance=w)
breadcrumbs = [
{
'url': '/rowers/list-workouts/',
'name': 'Workouts'
},
{
'url': get_workout_default_page(request, id),
'name': w.name
},
{
'url': reverse('workout_otwsetpower_view', kwargs={'id': id}),
'name': 'OTW Power'
}
]
messages.error(request, message)
messages.info(request, successmessage)
return render(request,
'otwsetpower.html',
{'workout': w,
'rower': w,
'mayedit': mayedit,
'active': 'nav-workouts',
'breadcrumbs': breadcrumbs,
'teams': get_my_teams(request.user),
'form': form,
})
@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True)
@@ -3598,7 +3194,7 @@ def workout_erase_column_view(request, id=0, column=''):
return HttpResponseRedirect(url)
try:
data = dataprep.getsmallrowdata_db([column], ids=[w.id])
data = dataprep.read_data([column], ids=[w.id])
except: # pragma: no cover
messages.error(request, 'Invalid column')
url = reverse('workout_data_view', kwargs={
@@ -3654,12 +3250,18 @@ def workout_erase_column_view(request, id=0, column=''):
row, workout = dataprep.getrowdata(id=w.id)
row.df[columnl] = defaultvalue
try:
os.remove(w.csvfilename+'.gz')
except FileNotFoundError:
try:
os.remove(w.csvfilename)
except FileNotFoundError:
pass
row.write_csv(w.csvfilename, gzip=True)
row, workout = dataprep.getrowdata(id=w.id)
_ = dataprep.dataprep(row.df, id=w.id)
_ = dataprep.dataplep(row.df, id=w.id)
if column == 'hr':
w.hrtss = 0
@@ -4141,7 +3743,7 @@ def workout_workflow_view(request, id):
if 'panel_map.html' in r.workflowmiddlepanel and rowhascoordinates(row):
rowdata = rdata(csvfile=row.csvfilename)
mapscript, mapdiv = leaflet_chart2(rowdata.df[' latitude'],
mapscript, mapdiv = leaflet_chart(rowdata.df[' latitude'],
rowdata.df[' longitude'],
row.name)
else:
@@ -4370,7 +3972,7 @@ def workout_flexchart3_view(request, *args, **kwargs):
# create interactive plot
(
script, div, js_resources, css_resources, workstrokesonly
script, div, workstrokesonly
) = interactive_flex_chart2(
encoder.decode_hex(id), request.user.rower,
xparam=xparam, yparam1=yparam1,
@@ -4456,8 +4058,6 @@ def workout_flexchart3_view(request, *args, **kwargs):
'workout': row,
'chartform': flexaxesform,
'optionsform': flexoptionsform,
'js_res': js_resources,
'css_res': css_resources,
'teams': get_my_teams(request.user),
'id': id,
'xparam': xparam,
@@ -4505,7 +4105,7 @@ def workout_flexchart_stacked_view(request, *args, **kwargs):
yparam4 = cd['yaxis4']
(
script, div, js_resources, css_resources, comment
script, div
) = interactive_flexchart_stacked(
encoder.decode_hex(id), r, xparam=xparam,
yparam1=yparam1,
@@ -4515,9 +4115,6 @@ def workout_flexchart_stacked_view(request, *args, **kwargs):
mode=workout.workouttype,
)
if comment is not None: # pragma: no cover
messages.error(request, comment)
initial = {
'xaxis': xparam,
'yaxis1': yparam1,
@@ -4554,8 +4151,6 @@ def workout_flexchart_stacked_view(request, *args, **kwargs):
'active': 'nav-workouts',
'workout': workout,
'chartform': flexaxesform,
'js_res': js_resources,
'css_res': css_resources,
'id': id,
'xparam': xparam,
'yparam1': yparam1,
@@ -4568,57 +4163,6 @@ def workout_flexchart_stacked_view(request, *args, **kwargs):
# The interactive plot with wind corrected pace for OTW outings
def workout_otwpowerplot_view(request, id=0, message="", successmessage=""):
w = get_workout(id)
r = getrower(request.user)
breadcrumbs = [
{
'url': '/rowers/list-workouts/',
'name': 'Workouts'
},
{
'url': get_workout_default_page(request, id),
'name': w.name
},
{
'url': reverse('workout_otwpowerplot_view', kwargs={'id': id}),
'name': 'Interactive OTW Power Plot'
}
]
# check if user is owner of this workout
# create interactive plot
promember = 0
mayedit = 0
result = request.user.is_authenticated and ispromember(request.user)
if result:
promember = 1
if request.user == w.user.user:
mayedit = 1
# create interactive plot
res = interactive_otw_advanced_pace_chart(
encoder.decode_hex(id), promember=promember)
script = res[0]
div = res[1]
messages.error(request, message)
messages.info(request, successmessage)
return render(request,
'otwinteractive.html',
{'workout': w,
'rower': r,
'active': 'nav-workouts',
'breadcrumbs': breadcrumbs,
'teams': get_my_teams(request.user),
'interactiveplot': script,
'the_div': div,
'mayedit': mayedit})
#
@@ -4780,7 +4324,7 @@ def workout_edit_view(request, id=0, message="", successmessage=""):
row = get_workoutuser(id, request)
if request.user.rower.rowerplan == 'basic' and 'speedcoach2' in row.workoutsource: # pragma: no cover
data = getsmallrowdata_db(['wash'], ids=[encoder.decode_hex(id)])
data = read_data(['wash'], ids=[encoder.decode_hex(id)])
try:
if data['wash'].std() != 0:
url = reverse('paidplans_view')
@@ -4793,7 +4337,7 @@ def workout_edit_view(request, id=0, message="", successmessage=""):
pass
if request.user.rower.rowerplan == 'basic' and 'nklinklogbook' in row.workoutsource: # pragma: no cover
data = getsmallrowdata_db(['wash'], ids=[encoder.decode_hex(id)])
data = read_data(['wash'], ids=[encoder.decode_hex(id)])
try:
if data['wash'].std() != 0:
url = reverse('paidplans_view')
@@ -5063,7 +4607,7 @@ def workout_map_view(request, id=0):
hascoordinates = 0
if hascoordinates:
mapscript, mapdiv = leaflet_chart2(rowdata.df[' latitude'],
mapscript, mapdiv = leaflet_chart(rowdata.df[' latitude'],
rowdata.df[' longitude'],
w.name)
else:
+7
View File
@@ -599,6 +599,13 @@ except KeyError: # pragma: no cover
WORKOUTS_FIT_TOKEN = 'aapnootmies'
WORKOUTS_FIT_URL = 'http://localhost:50053/tojson'
try:
ROWSANDALL_CHARTS_TOKEN = CFG['rowsandall_charts_token']
ROWSANDALL_CHARTS_URL = CFG['rowsandall_charts_url']
except KeyError:
ROWSANDALL_CHARTS_TOKEN = 'aapnootmies'
ROWSANDALL_CHARTS_URL = 'http://localhost:3000'
# Recaptcha
try:
+10
View File
@@ -32,6 +32,16 @@
color: red;
}
.chart {
background-image: url(/static/img/logo7.png);
background-size: 200px;
background-repeat: no-repeat;
background-position: top 50px right 100px;
}
.chartlabel {
font-size: 1.2em;
}
.watermark {
position: absolute;