Private
Public Access
1
0
Files
rowsandall/rowers/views/analysisviews.py
2022-06-20 21:48:23 +02:00

2486 lines
79 KiB
Python

from rowers.forms import analysischoices
from rowers.views.statements import *
from jinja2 import Environment, FileSystemLoader
from rowers.rower_rules import can_view_session
def floatformat(x, prec=2): # pragma: no cover
return '{x}'.format(x=round(x, prec))
env = Environment(loader=FileSystemLoader(["rowers/templates"]))
env.filters['floatformat'] = floatformat
# generic Analysis view -
defaultoptions = {
'includereststrokes': False,
'workouttypes': ['rower', 'dynamic', 'slides'],
'waterboattype': mytypes.waterboattype,
'function': 'boxplot',
'ranking': False,
}
@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)
@permission_required('rower.is_coach',
fn=get_user_by_userid,
raise_exception=True)
def analysis_new(request,
userid=0,
function='boxplot', teamid=0, id='', session=0):
r = getrequestrower(request, userid=userid)
user = r.user
userid = user.id
worldclass = False
firstworkout = None
if id:
firstworkout = get_workout(id)
if not is_workout_team(request.user, firstworkout): # pragma: no cover
raise PermissionDenied("You are not allowed to use this workout")
firstworkoutquery = Workout.objects.filter(id=encoder.decode_hex(id))
try:
theteam = Team.objects.get(id=teamid)
except Team.DoesNotExist:
theteam = None
try:
thesession = PlannedSession.objects.get(id=session)
if not can_view_session(user, thesession):
raise PermissionDenied("you cannot view this session")
except PlannedSession.DoesNotExist:
thesession = None
if 'options' in request.session:
options = request.session['options']
else:
options = defaultoptions
options['userid'] = userid
try:
modalities = options['modalities']
modality = modalities[0]
except KeyError:
modalities = [m[0] for m in mytypes.workouttypes_ordered.items()]
modality = 'all'
try:
ranking = options['ranking']
except KeyError:
ranking = False
try:
worldclass = options['cpoverlay']
except KeyError:
worldclass = False
if 'startdate' in request.session:
startdate = iso8601.parse_date(request.session['startdate'])
else:
startdate = timezone.now()-datetime.timedelta(days=42)
if function not in [c[0] for c in analysischoices]: # pragma: no cover
function = 'boxplot'
if 'enddate' in request.session:
enddate = iso8601.parse_date(request.session['enddate'])
else:
enddate = timezone.now()
waterboattype = mytypes.waterboattype
if request.method == 'POST':
thediv = get_call()
dateform = DateRangeForm(request.POST)
if dateform.is_valid():
startdate = dateform.cleaned_data['startdate']
enddate = dateform.cleaned_data['enddate']
startdatestring = startdate.strftime('%Y-%m-%d')
enddatestring = enddate.strftime('%Y-%m-%d')
request.session['startdate'] = startdatestring
request.session['enddate'] = enddatestring
optionsform = AnalysisOptionsForm(request.POST)
if optionsform.is_valid():
for key, value in optionsform.cleaned_data.items():
options[key] = value
modality = optionsform.cleaned_data['modality']
waterboattype = optionsform.cleaned_data['waterboattype']
ranking = optionsform.cleaned_data['ranking']
if modality == 'all':
modalities = [m[0] for m in mytypes.workouttypes]
else: # pragma: no cover
modalities = [modality]
if modality != 'water':
waterboattype = [b[0] for b in mytypes.boattypes]
options['modalities'] = modalities
options['waterboattype'] = waterboattype
options['ranking'] = ranking
try:
worldclass = options['cpoverlay']
except KeyError:
worldclass = False
options['cpoverlay'] = worldclass
chartform = AnalysisChoiceForm(request.POST)
if chartform.is_valid():
for key, value in chartform.cleaned_data.items():
options[key] = value
form = WorkoutMultipleCompareForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
selectedworkouts = cd['workouts']
ids = [int(w.id) for w in selectedworkouts]
options['ids'] = ids
else: # pragma: no cover
ids = []
options['ids'] = ids
else:
thediv = ''
dateform = DateRangeForm(initial={
'startdate': startdate,
'enddate': enddate,
})
negtypes = []
for b in mytypes.boattypes:
if b[0] not in waterboattype: # pragma: no cover
negtypes.append(b[0])
startdate = datetime.datetime.combine(startdate, datetime.time())
enddate = datetime.datetime.combine(enddate, datetime.time(23, 59, 59))
if enddate < startdate: # pragma: no cover
s = enddate
enddate = startdate
startdate = s
# make sure the dates are not naive
try:
startdate = pytz.utc.localize(startdate)
except (ValueError, AttributeError): # pragma: no cover
pass
try:
enddate = pytz.utc.localize(enddate)
except (ValueError, AttributeError): # pragma: no cover
pass
negtypes = []
for b in mytypes.boattypes:
if b[0] not in waterboattype: # pragma: no cover
negtypes.append(b[0])
rankingtypes = [False,True]
if ranking:
rankingtypes = [True]
if theteam is not None and (theteam.viewing == 'allmembers' or theteam.manager == request.user): # pragma: no cover
workouts = Workout.objects.filter(team=theteam,
startdatetime__gte=startdate,
startdatetime__lte=enddate,
workouttype__in=modalities,
rankingpiece__in=rankingtypes,
)
elif theteam is not None and theteam.viewing == 'coachonly': # pragma: no cover
workouts = Workout.objects.filter(team=theteam, user=r,
startdatetime__gte=startdate,
startdatetime__lte=enddate,
workouttype__in=modalities,
rankingpiece__in=rankingtypes,
)
elif thesession is not None:
workouts = get_workouts_session(r, thesession)
else:
workouts = Workout.objects.filter(user=r,
startdatetime__gte=startdate,
startdatetime__lte=enddate,
workouttype__in=modalities,
rankingpiece__in=rankingtypes,
)
if firstworkout:
workouts = firstworkoutquery | workouts
workouts = workouts.order_by(
"-date", "-starttime"
).exclude(boattype__in=negtypes)
query = request.POST.get('q')
if query: # pragma: no cover
query_list = query.split()
try:
workouts = workouts.filter(
reduce(operator.and_,
(Q(name__icontains=q) for q in query_list)) |
reduce(operator.and_,
(Q(notes__icontains=q) for q in query_list))
)
searchform = SearchForm(initial={'q': query})
except TypeError:
searchform = SearchForm()
else:
searchform = SearchForm()
if request.method != 'POST':
form = WorkoutMultipleCompareForm()
if id:
form.fields["workouts"].initial = [firstworkout]
chartform = AnalysisChoiceForm(initial={'function': function})
selectedworkouts = Workout.objects.none()
else:
selectedworkouts = Workout.objects.filter(id__in=ids)
form.fields["workouts"].queryset = workouts | selectedworkouts
optionsform = AnalysisOptionsForm(initial={
'modality': modality,
'waterboattype': waterboattype,
'ranking': ranking,
})
if r.birthdate:
age = calculate_age(r.birthdate)
else:
age = 0
startdatestring = startdate.strftime('%Y-%m-%d')
enddatestring = enddate.strftime('%Y-%m-%d')
request.session['startdate'] = startdatestring
request.session['enddate'] = enddatestring
request.session['options'] = options
breadcrumbs = [
{
'url': '/rowers/analysis',
'name': 'Analysis'
},
{
'url': reverse('analysis_new', kwargs={'userid': userid}),
'name': 'Analysis Select'
},
]
return render(request, 'user_analysis_select.html',
{'workouts': workouts,
'dateform': dateform,
'startdate': startdate,
'enddate': enddate,
'rower': r,
'breadcrumbs': breadcrumbs,
'theuser': user,
'the_div': thediv,
'form': form,
'active': 'nav-analysis',
'chartform': chartform,
'searchform': searchform,
'optionsform': optionsform,
'worldclass': worldclass,
'age': age,
'sex': r.sex,
'weightcategory': r.weightcategory,
'teams': get_my_teams(request.user),
})
def trendflexdata(workouts, options, userid=0):
includereststrokes = options['includereststrokes']
palette = options['palette']
groupby = options['groupby']
binsize = options['binsize']
xparam = options['xparam']
yparam = options['yparam']
spmmin = options['spmmin']
spmmax = options['spmmax']
workmin = options['workmin']
workmax = options['workmax']
ploterrorbars = options['ploterrorbars']
ids = options['ids']
workstrokesonly = not includereststrokes
fieldlist, fielddict = dataprep.getstatsfields()
fieldlist = [xparam, yparam, groupby,
'workoutid', 'spm', 'driveenergy',
'workoutstate']
# prepare data frame
datadf, extracols = dataprep.read_cols_df_sql(ids, fieldlist)
if xparam == groupby: # pragma: no cover
datadf['groupby'] = datadf[xparam]
groupby = 'groupby'
datadf = dataprep.clean_df_stats(datadf, workstrokesonly=workstrokesonly)
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)
datadf.dropna(axis=0, how='any', inplace=True)
datemapping = {
w.id: w.date for w in workouts
}
datadf['date'] = datadf['workoutid']
datadf['date'].replace(datemapping, inplace=True)
today = timezone.now()
try:
datadf['days ago'] = list(map(lambda x: x.days, datadf.date - today))
except TypeError:
datadf['days ago'] = 0
if groupby != 'date':
try:
bins = np.arange(datadf[groupby].min()-binsize,
datadf[groupby].max()+binsize,
binsize)
groups = datadf.groupby(
pd.cut(datadf[groupby], bins, labels=False))
except (ValueError, AttributeError): # pragma: no cover
return ('', 'Error: not enough data')
else: # pragma: no cover
bins = np.arange(datadf['days ago'].min()-binsize,
datadf['days ago'].max()+binsize,
binsize,
)
groups = datadf.groupby(pd.cut(datadf['days ago'], bins,
labels=False))
xvalues = groups.mean()[xparam]
yvalues = groups.mean()[yparam]
xerror = groups.std()[xparam]
yerror = groups.std()[yparam]
groupsize = groups.count()[xparam]
mask = groupsize <= min([0.01*groupsize.sum(), 0.2*groupsize.mean()])
xvalues.loc[mask] = np.nan
yvalues.loc[mask] = np.nan
xerror.loc[mask] = np.nan
yerror.loc[mask] = np.nan
groupsize.loc[mask] = np.nan
xvalues.dropna(inplace=True)
yvalues.dropna(inplace=True)
xerror.dropna(inplace=True)
yerror.dropna(inplace=True)
groupsize.dropna(inplace=True)
if len(groupsize) == 0: # pragma: no cover
messages.error('No data in selection')
url = reverse(user_multiflex_select)
return HttpResponseRedirect(url)
else:
groupsize = 30.*np.sqrt(groupsize/float(groupsize.max()))
df = pd.DataFrame({
xparam: xvalues,
yparam: yvalues,
'x': xvalues,
'y': yvalues,
'xerror': xerror,
'yerror': yerror,
'groupsize': groupsize,
})
if yparam == 'pace':
df['y'] = dataprep.paceformatsecs(df['y']/1.0e3)
aantal = len(df)
if groupby != 'date':
try:
df['groupval'] = groups.mean()[groupby]
df.loc[mask, 'groupval'] = np.nan
groupcols = df['groupval']
except (ValueError, AttributeError): # pragma: no cover
df['groupval'] = groups.mean()[groupby].fillna(value=0)
df.loc[mask, 'groupval'] = np.nan
groupcols = df['groupval']
except KeyError: # pragma: no cover
messages.error(request, 'Data selection error')
url = reverse(user_multiflex_select)
return HttpResponseRedirect(url)
else: # pragma: no cover
try:
dates = groups.min()[groupby]
dates.loc[mask] = np.nan
dates.dropna(inplace=True)
df['groupval'] = [x.strftime("%Y-%m-%d") for x in dates]
df['groupval'].loc[mask] = np.nan
groupcols = 100.*np.arange(aantal)/float(aantal)
except AttributeError:
df['groupval'] = groups.mean()['days ago'].fillna(value=0)
groupcols = 100.*np.arange(aantal)/float(aantal)
groupcols = (groupcols-groupcols.min())/(groupcols.max()-groupcols.min())
if aantal == 1: # pragma: no cover
groupcols = np.array([1.])
colors = range_to_color_hex(groupcols, palette=palette)
df['color'] = colors
clegendx = np.arange(0, 1.2, .2)
legcolors = range_to_color_hex(clegendx, palette=palette)
if groupby != 'date':
clegendy = df['groupval'].min()+clegendx * \
(df['groupval'].max()-df['groupval'].min())
else: # pragma: no cover
clegendy = df.index.min()+clegendx*(df.index.max()-df.index.min())
colorlegend = zip(range(6), clegendy, legcolors)
if userid == 0:
extratitle = ''
else: # pragma: no cover
u = User.objects.get(id=userid)
extratitle = ' '+u.first_name+' '+u.last_name
script, div = interactive_multiflex(df, xparam, yparam,
groupby,
extratitle=extratitle,
ploterrorbars=ploterrorbars,
binsize=binsize,
colorlegend=colorlegend,
spmmin=spmmin, spmmax=spmmax,
workmin=workmin, workmax=workmax)
scripta = script.split('\n')[2:-1]
script = ''.join(scripta)
return(script, div)
def flexalldata(workouts, options):
includereststrokes = options['includereststrokes']
xparam = options['xaxis']
yparam1 = options['yaxis1']
yparam2 = options['yaxis2']
promember = True
workstrokesonly = not includereststrokes
res = interactive_cum_flex_chart2(workouts, xparam=xparam,
yparam1=yparam1,
yparam2=yparam2,
promember=promember,
workstrokesonly=workstrokesonly,
)
script = res[0]
div = res[1]
scripta = script.split('\n')[2:-1]
script = ''.join(scripta)
return(script, div)
def histodata(workouts, options):
includereststrokes = options['includereststrokes']
plotfield = options['plotfield']
spmmin = options['spmmin']
spmmax = options['spmmax']
workmin = options['workmin']
workmax = options['workmax']
script, div = interactive_histoall(workouts, plotfield, includereststrokes,
spmmin=spmmin, spmmax=spmmax,
workmin=workmin, workmax=workmax)
scripta = script.split('\n')[2:-1]
script = ''.join(scripta)
return(script, div)
def cpdata(workouts, options):
userid = options['userid']
cpfit = options['cpfit']
cpoverlay = options['cpoverlay']
u = User.objects.get(id=userid)
r = u.rower
delta, cpvalue, avgpower, workoutnames, urls = dataprep.fetchcp_new(
r, workouts)
powerdf = pd.DataFrame({
'Delta': delta,
'CP': cpvalue,
'workout': workoutnames,
'url': urls,
})
if powerdf.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)
rowername = r.user.first_name+" "+r.user.last_name
wcdurations = []
wcpower = []
if len(powerdf) != 0:
datefirst = pd.Series(w.date for w in workouts).min()
datelast = pd.Series(w.date for w in workouts).max()
title = 'CP chart for {name}, from {d1} to {d2}'.format(
name=rowername,
d1=datefirst,
d2=datelast,
)
wtype = 'water'
if workouts[0].workouttype in mytypes.otetypes: # pragma: no cover
wtype = 'erg'
if workouts[0].workouttype == 'bikeerg': # pragma: no cover
# for Mike
wtype = 'erg'
if cpoverlay:
if r.birthdate:
age = calculate_age(r.birthdate)
else: # pragma: no cover
age = 0
agerecords = CalcAgePerformance.objects.filter(
age=age,
sex=r.sex,
weightcategory=r.weightcategory
)
if len(agerecords) == 0:
wcpower = []
wcdurations = []
else: # pragma: no cover
wcdurations = []
wcpower = []
for record in agerecords:
recordpower = record.power
if wtype == 'water':
recordpower = record.power*(100.-r.otwslack)/100.
wcdurations.append(record.duration)
wcpower.append(recordpower)
res = interactive_otwcpchart(powerdf, promember=True, rowername=rowername, r=r,
cpfit=cpfit, title=title, type=wtype,
cpoverlay=cpoverlay,
wcdurations=wcdurations, wcpower=wcpower)
script = res[0]
div = res[1]
p1 = res[2]
ratio = res[3]
else: # pragma: no cover
script = ''
div = '<p>No ranking pieces found.</p>'
p1 = [1, 1, 1, 1]
ratio = 1
scripta = script.split('\n')[2:-1]
script = ''.join(scripta)
minutes = options['piece']
if minutes != 0:
# minutes = 77
try:
hourvalue, tvalue = divmod(minutes, 60)
except: # pragma: no cover
hourvalue = 0
tvalue = minutes
# hourvalue = 1, tvalue = 17
try:
hourvalue = int(hourvalue)
except TypeError: # pragma: no cover
hourvalue = 0
try:
minutevalue = int(tvalue)
except TypeError: # pragma: no cover
minutevalue = 0
tvalue = int(60*(tvalue-minutevalue))
if hourvalue >= 24: # pragma: no cover
hourvalue = 23
pieceduration = datetime.time(
minute=minutevalue,
hour=hourvalue,
second=tvalue,
)
pieceseconds = 3600.*pieceduration.hour+60. * \
pieceduration.minute+pieceduration.second
# CP model
pwr = p1[0]/(1+pieceseconds/p1[2])
pwr += p1[1]/(1+pieceseconds/p1[3])
if pwr <= 0: # pragma: no cover
pwr = 50.
if not np.isnan(pwr):
try:
pwr2 = pwr*ratio
except: # pragma: no cover
pwr2 = pwr
duration = timedeltaconv(pieceseconds)
power = int(pwr)
upper = int(pwr2)
else: # pragma: no cover
duration = timedeltaconv(0)
power = 0
upper = 0
htmly = env.get_template('otwcp.html')
html_content = htmly.render({
'script': script,
'the_div': div,
'duration': duration,
'power': power,
'upper': upper,
})
return (script, html_content)
def statsdata(workouts, options):
includereststrokes = options['includereststrokes']
ids = options['ids']
workstrokesonly = not includereststrokes
ids = [w.id for w in workouts]
fieldlist, fielddict = dataprep.getstatsfields()
# prepare data frame
datadf, extracols = dataprep.read_cols_df_sql(ids, fieldlist)
datadf = dataprep.clean_df_stats(datadf, workstrokesonly=workstrokesonly)
try:
datadf['pace'] = datadf['pace']/1000.
except KeyError: # pragma: no cover
pass
# Create stats
stats = {}
# fielddict.pop('workoutstate')
# fielddict.pop('workoutid')
for field, verbosename in fielddict.items():
try:
thedict = {
'mean': datadf[field].mean(),
'min': datadf[field].min(),
'std': datadf[field].std(),
'max': datadf[field].max(),
'median': datadf[field].median(),
'firstq': datadf[field].quantile(q=0.25),
'thirdq': datadf[field].quantile(q=0.75),
'verbosename': verbosename,
}
stats[field] = thedict
except KeyError: # pragma: no cover
pass
# Create a dict with correlation values
cor = datadf.corr(method='spearman')
cor.fillna(value=0, inplace=True)
cordict = {}
for field1, verbosename1 in fielddict.items():
thedict = {}
for field2, verbosename2 in fielddict.items():
try:
thedict[verbosename2] = cor.loc[field1, field2]
except KeyError: # pragma: no cover
thedict[verbosename2] = 0
cordict[verbosename1] = thedict
context = {
'stats': stats,
'cordict': cordict,
}
htmly = env.get_template('statsdiv.html')
html_content = htmly.render(context)
return('', html_content)
def comparisondata(workouts, options):
includereststrokes = options['includereststrokes']
xparam = options['xaxis']
yparam1 = options['yaxis1']
plottype = options['plottype']
promember = True
workstrokesonly = not includereststrokes
ids = [w.id for w in workouts]
labeldict = {
int(w.id): w.__str__() for w in workouts
}
res = interactive_multiple_compare_chart(ids, xparam, yparam1,
promember=promember,
plottype=plottype,
workstrokesonly=workstrokesonly,
labeldict=labeldict)
script = res[0]
div = res[1]
scripta = script.split('\n')[2:-1]
script = ''.join(scripta)
return(script, div)
def boxplotdata(workouts, options):
includereststrokes = options['includereststrokes']
spmmin = options['spmmin']
spmmax = options['spmmax']
workmin = options['workmin']
workmax = options['workmax']
ids = options['ids']
userid = options['userid']
plotfield = options['plotfield']
workstrokesonly = not includereststrokes
datemapping = {
w.id: w.date for w in workouts
}
fieldlist, fielddict = dataprep.getstatsfields()
fieldlist = [plotfield, 'workoutid', 'spm', 'driveenergy',
'workoutstate']
ids = [w.id for w in workouts]
# prepare data frame
datadf, extracols = dataprep.read_cols_df_sql(ids, fieldlist)
datadf = dataprep.clean_df_stats(datadf, workstrokesonly=workstrokesonly)
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)
datadf.dropna(axis=0, how='any', inplace=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'])
if userid == 0: # pragma: no cover
extratitle = ''
else:
u = User.objects.get(id=userid)
extratitle = ' '+u.first_name+' '+u.last_name
script, div = interactive_boxchart(datadf, plotfield,
extratitle=extratitle,
spmmin=spmmin, spmmax=spmmax,
workmin=workmin, workmax=workmax)
scripta = script.split('\n')[2:-1]
script = ''.join(scripta)
return(script, div)
@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)
@permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True)
def analysis_view_data(request, userid=0):
is_ajax = request_is_ajax(request)
if settings.TESTING:
is_ajax = True
if not is_ajax: # pragma: no cover
url = reverse('analysis_new')
return HttpResponseRedirect(url)
if 'options' in request.session:
options = request.session['options']
else: # pragma: no cover
options = defaultoptions
if userid == 0:
userid = request.user.id
workouts = []
try:
ids = options['ids']
except KeyError: # pragma: no cover
return JSONResponse({
"script": '',
"div": 'No data found'
})
function = options['function']
if not ids: # pragma: no cover
return JSONResponse({
"script": '',
"div": 'No data found'
})
for id in ids:
try:
workouts.append(Workout.objects.get(id=id))
except Workout.DoesNotExist: # pragma: no cover
pass
if function == 'boxplot':
script, div = boxplotdata(workouts, options)
elif function == 'trendflex': # pragma: no cover
script, div = trendflexdata(workouts, options, userid=userid)
elif function == 'histo': # pragma: no cover
script, div = histodata(workouts, options)
elif function == 'flexall': # pragma: no cover
script, div = flexalldata(workouts, options)
elif function == 'stats': # pragma: no cover
script, div = statsdata(workouts, options)
elif function == 'compare': # pragma: no cover
script, div = comparisondata(workouts, options)
elif function == 'cp': # pragma: no cover
script, div = cpdata(workouts, options)
else: # pragma: no cover
script = ''
div = 'Unknown analysis functions'
return JSONResponse({
"script": script,
"div": div,
})
def planrequired_view(request):
messages.info(
request, "This functionality requires Coach or Self-Coach membership")
return HttpResponseRedirect(reverse('paidplans_view'))
@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 create_marker_workouts_view(request, userid=0,
startdate=timezone.now()-timezone.timedelta(days=42),
enddate=timezone.now()):
therower = getrequestrower(request, userid=userid)
theuser = therower.user
workouts = Workout.objects.filter(user=theuser.rower, date__gte=startdate,
date__lte=enddate,
workouttype__in=mytypes.rowtypes,
duplicate=False).order_by('date')
for workout in workouts:
_ = dataprep.check_marker(workout)
url = reverse('goldmedalscores_view',
kwargs={
'userid': request.user.id,
})
return HttpResponseRedirect(url)
@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 goldmedalscores_view(request, userid=0,
startdate=timezone.now()-timezone.timedelta(days=365),
enddate=timezone.now()):
is_ajax = request_is_ajax(request)
therower = getrequestrower(request, userid=userid)
theuser = therower.user
if request.method == 'POST':
form = DateRangeForm(request.POST)
if form.is_valid():
startdate = form.cleaned_data['startdate']
enddate = form.cleaned_data['enddate']
if startdate > enddate: # pragma: no cover
s = enddate
enddate = startdate
startdate = s
else:
form = DateRangeForm(initial={
'startdate': startdate,
'enddate': enddate,
})
script, div, ids = goldmedalscorechart(
theuser, startdate=startdate, enddate=enddate,
)
bestworkouts = Workout.objects.filter(id__in=ids).order_by('-date')
breadcrumbs = [
{
'url': '/rower/analysis',
'name': 'Analysis',
},
{
'url': reverse(goldmedalscores_view),
'name': 'Gold Medal Scores'
}
]
if is_ajax: # pragma: no cover
response = json.dumps({
'script': script,
'div': div,
})
return(HttpResponse(response, content_type='application/json'))
return render(request, 'goldmedalscores.html',
{
'rower': therower,
'active': 'nav-analysis',
'chartscript': script,
'breadcrumbs': breadcrumbs,
'the_div': div,
'form': form,
'bestworkouts': bestworkouts,
})
@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)
@permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True)
def trainingzones_view(request, userid=0):
r = getrequestrower(request, userid=userid)
enddate = timezone.now()
startdate = enddate-datetime.timedelta(days=42)
zones = 'hr'
date_agg = 'week'
yaxis = 'percentage'
form = TrainingZonesForm({
'startdate': startdate,
'enddate': enddate,
'zones': zones,
'dates': date_agg,
'yaxis': yaxis,
})
if request.method == 'POST': # pragma: no cover
form = TrainingZonesForm(request.POST)
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())
else: # pragma: no cover
startdate = startdate - datetime.timedelta(days=(startdate.day-1))
form = TrainingZonesForm(initial={
'startdate': startdate,
'enddate': enddate,
'zones': zones,
'dates': date_agg,
'yaxis': yaxis,
})
script = ''
div = get_call()
breadcrumbs = [
{
'url': '/rowers/analysis',
'name': 'Analysis'
},
{
'url': reverse('trainingzones_view'),
'name': 'Training Zones'
}
]
return render(request, 'trainingzones.html',
{
'active': 'nav-analysis',
'breadcrumbs': breadcrumbs,
'rower': r,
'the_script': script,
'the_div': div,
'form': form,
'startdate': startdate,
'enddate': enddate,
'zones': zones,
'dates': date_agg,
'yaxis': yaxis,
}
)
@login_required()
def trainingzones_view_data(request, userid=0):
r = getrequestrower(request, userid=userid)
startdate = timezone.now()-datetime.timedelta(days=365)
enddate = timezone.now()
zones = 'hr'
date_agg = 'week'
yaxis = 'percentage'
zones = request.GET.get('zones', zones)
date_agg = request.GET.get('dates', date_agg)
yaxis = request.GET.get('yaxis', yaxis)
if request.GET.get('startdate'):
startdate = datetime.datetime.strptime(
request.GET.get('startdate'), "%Y-%m-%d")
startdate = arrow.get(startdate).datetime
if request.GET.get('enddate'):
enddate = datetime.datetime.strptime(
request.GET.get('enddate'), "%Y-%m-%d")
enddate = arrow.get(enddate).datetime
data = get_zones_report(r, startdate, enddate,
trainingzones=zones, date_agg=date_agg, yaxis=yaxis)
script, div = interactive_zoneschart(
r, data, startdate, enddate, trainingzones=zones, date_agg=date_agg, yaxis=yaxis)
return JSONResponse({
'script': script,
'div': div,
})
@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)
@permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True)
def performancemanager_view(request, userid=0, mode='rower',
startdate=timezone.now()-timezone.timedelta(days=365),
enddate=timezone.now()):
is_ajax = request_is_ajax(request)
therower = getrequestrower(request, userid=userid)
theuser = therower.user
kfitness = therower.kfit
kfatigue = therower.kfatigue
metricchoice = 'hrtss'
doform = therower.showfresh
dofatigue = therower.showfit
if request.method == 'POST':
form = PerformanceManagerForm(request.POST)
if form.is_valid():
startdate = form.cleaned_data['startdate']
enddate = form.cleaned_data['enddate']
metricchoice = form.cleaned_data['metricchoice']
dofatigue = form.cleaned_data['dofatigue']
doform = form.cleaned_data['doform']
therower.showfresh = doform
therower.showfatigue = dofatigue
therower.save()
else:
form = PerformanceManagerForm(initial={
'doform': doform,
'dofatigue': dofatigue,
})
script, thediv, endfitness, endfatigue, endform, ids = performance_chart(
theuser, startdate=startdate, enddate=enddate,
kfitness=kfitness,
kfatigue=kfatigue,
metricchoice=metricchoice,
doform=doform,
dofatigue=dofatigue,
showtests=True,
)
ids = pd.Series(ids, dtype='int').dropna().values
bestworkouts = Workout.objects.filter(id__in=ids).order_by('-date')
breadcrumbs = [
{
'url': '/rowers/analysis',
'name': 'Analysis'
},
{
'url': reverse('performancemanager_view'),
'name': 'Performance Manager'
}
]
if is_ajax: # pragma: no cover
response = json.dumps({
'script': script,
'div': thediv,
'endform': int(endform),
'endfitness': int(endfitness),
'endfatigue': int(endfatigue),
})
return(HttpResponse(response, content_type='application/json'))
return render(request, 'performancemanager.html',
{
'rower': therower,
'active': 'nav-analysis',
'chartscript': script,
'breadcrumbs': breadcrumbs,
'the_div': thediv,
'mode': mode,
'form': form,
'endfitness': int(endfitness),
'endfatigue': int(endfatigue),
'endform': int(endform),
'bestworkouts': bestworkouts,
})
@login_required()
def ajax_agegrouprecords(request,
age=25,
sex='female',
weightcategory='hwt',
userid=0):
durations = [1, 4, 30, 60]
distances = [100, 500, 1000, 2000, 5000, 6000, 10000, 21097, 42195]
df = pd.DataFrame(
list(
C2WorldClassAgePerformance.objects.filter(
sex=sex,
weightcategory=weightcategory
).values()
)
)
jsondf = df.to_json()
job = myqueue(queue,
handle_getagegrouprecords,
jsondf, distances, durations, age, sex, weightcategory,
)
return JSONResponse(
{
'job': job.id
}
)
# 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:
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()
job = myqueue(queue,
handle_getagegrouprecords,
jsondf, distances, durations, age, r.sex, r.weightcategory)
try:
request.session['async_tasks'] += [(job.id, 'agegrouprecords')]
except KeyError:
request.session['async_tasks'] = [(job.id, 'agegrouprecords')]
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):
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):
if not duration:
df = pd.DataFrame(
list(
C2WorldClassAgePerformance.objects.filter(
distance=distance,
sex=sex,
weightcategory=weightcategory
).values()
)
)
else:
duration = int(duration) * 60
df = pd.DataFrame(
list(
C2WorldClassAgePerformance.objects.filter(
duration=duration,
sex=sex,
weightcategory=weightcategory
).values()
)
)
script, div = interactive_agegroup_plot(df, sex=sex, distance=distance,
duration=duration,
weightcategory=weightcategory)
return render(request, 'agegroupchart.html',
{
'interactiveplot': script,
'active': 'nav-analysis',
'the_div': div,
})
@login_required
@permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True)
def alerts_view(request, userid=0):
r = getrequestrower(request, userid=userid)
alerts = Alert.objects.filter(rower=r).order_by('next_run')
stats = []
for alert in alerts: # pragma: no cover
stats.append(alert_get_stats(alert))
breadcrumbs = [
{
'url': '/rowers/analysis',
'name': 'Analysis'
},
{
'url': reverse('alerts_view'),
'name': 'Alerts',
},
]
return render(request, 'alerts.html',
{
'breadcrumbs': breadcrumbs,
'alerts': alerts,
'rower': r,
'stats': stats,
})
# alert create view
@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)
@permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True)
def alert_create_view(request, userid=0):
r = getrequestrower(request, userid=userid)
FilterFormSet = formset_factory(
ConditionEditForm, formset=BaseConditionFormSet, extra=1)
filter_formset = FilterFormSet()
if request.method == 'POST':
form = AlertEditForm(request.POST)
measuredform = ConditionEditForm(request.POST)
filter_formset = FilterFormSet(request.POST)
if form.is_valid() and measuredform.is_valid() and filter_formset.is_valid():
ad = form.cleaned_data
measured = measuredform.cleaned_data
period = ad['period']
emailalert = ad['emailalert']
reststrokes = ad['reststrokes']
workouttype = ad['workouttype']
boattype = ad['boattype']
name = ad['name']
filters = []
for filter_form in filter_formset:
metric = filter_form.cleaned_data.get('metric')
condition = filter_form.cleaned_data.get('condition')
value1 = filter_form.cleaned_data.get('value1')
value2 = filter_form.cleaned_data.get('value2')
filters.append(
{
'metric': metric,
'condition': condition,
'value1': value1,
'value2': value2,
}
)
result, message = create_alert(request.user, r, measured,
period=period, emailalert=emailalert,
reststrokes=reststrokes, workouttype=workouttype,
boattype=boattype,
filter=filters,
name=name)
if result:
messages.info(request, message)
url = reverse('alert_edit_view', kwargs={'id': result})
return HttpResponseRedirect(url)
else:
form = AlertEditForm()
measuredform = ConditionEditForm()
breadcrumbs = [
{
'url': '/rowers/analysis',
'name': 'Analysis'
},
{
'url': reverse('alerts_view'),
'name': 'Alerts',
},
{
'url': reverse('alert_create_view'),
'name': 'Create'
}
]
return render(request, 'alert_create.html',
{
'breadcrumbs': breadcrumbs,
'formset': filter_formset,
'rower': r,
'form': form,
'measuredform': measuredform,
})
# alert report view
@login_required()
@permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True)
def alert_report_view(request, id=0, userid=0, nperiod=0):
r = getrequestrower(request, userid=userid)
if userid == 0: # pragma: no cover
userid = request.user.id
alert = Alert.objects.get(id=id)
nperiod = int(nperiod)
try:
alert = Alert.objects.get(id=id)
except Alert.DoesNotExist: # pragma: no cover
raise Http404("This alert doesn't exist")
if not checkalertowner(alert, request.user): # pragma: no cover
raise PermissionDenied('You are not allowed to edit this Alert')
stats = alert_get_stats(alert, nperiod=nperiod)
is_ajax = request_is_ajax(request)
if not is_ajax:
return JSONResponse({
"stats": stats,
})
breadcrumbs = [
{
'url': '/rowers/analysis',
'name': 'Analysis'
},
{
'url': reverse('alerts_view'),
'name': 'Alerts',
},
{
'url': reverse('alert_edit_view',
kwargs={'userid': userid, 'id': alert.id}),
'name': alert.name,
},
{
'url': reverse('alert_report_view',
kwargs={'userid': userid, 'id': alert.id}),
'name': 'Report',
},
] # pragma: no cover
return render(request, 'alert_stats.html',
{
'breadcrumbs': breadcrumbs,
'stats': stats,
'rower': r,
'alert': alert,
'nperiod': nperiod,
}) # pragma: no cover
# alert edit view
@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)
@permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True)
def alert_edit_view(request, id=0, userid=0):
r = getrequestrower(request, userid=userid)
try:
alert = Alert.objects.get(id=id)
except Alert.DoesNotExist: # pragma: no cover
raise Http404("This alert doesn't exist")
if alert.manager != request.user: # pragma: no cover
raise PermissionDenied('You are not allowed to edit this Alert')
FilterFormSet = formset_factory(
ConditionEditForm, formset=BaseConditionFormSet, extra=0)
if len(alert.filter.all()) == 0: # pragma: no cover
FilterFormSet = formset_factory(
ConditionEditForm, formset=BaseConditionFormSet, extra=1)
filter_data = [{'metric': m.metric,
'value1': m.value1,
'value2': m.value2,
'condition': m.condition}
for m in alert.filter.all()]
if request.method == 'POST':
form = AlertEditForm(request.POST)
measuredform = ConditionEditForm(request.POST)
filter_formset = FilterFormSet(request.POST)
if form.is_valid() and measuredform.is_valid() and filter_formset.is_valid():
ad = form.cleaned_data
measured = measuredform.cleaned_data
period = ad['period']
emailalert = ad['emailalert']
reststrokes = ad['reststrokes']
workouttype = ad['workouttype']
boattype = ad['boattype']
name = ad['name']
m = alert.measured
m.metric = measured['metric']
m.value1 = measured['value1']
m.value2 = measured['value2']
m.condition = measured['condition']
m.save()
alert.period = period
alert.emailalert = emailalert
alert.reststrokes = reststrokes
alert.workouttype = workouttype
alert.boattype = boattype
alert.name = name
alert.save()
filters = []
for filter_form in filter_formset:
metric = filter_form.cleaned_data.get('metric')
condition = filter_form.cleaned_data.get('condition')
value1 = filter_form.cleaned_data.get('value1')
value2 = filter_form.cleaned_data.get('value2')
filters.append(
{
'metric': metric,
'condition': condition,
'value1': value1,
'value2': value2,
}
)
_ = alert_add_filters(alert, filters)
messages.info(request, 'Alert was changed')
else:
form = AlertEditForm(instance=alert)
measuredform = ConditionEditForm(instance=alert.measured)
filter_formset = FilterFormSet(initial=filter_data)
breadcrumbs = [
{
'url': '/rowers/analysis',
'name': 'Analysis'
},
{
'url': reverse('alerts_view'),
'name': 'Alerts',
},
{
'url': reverse('alert_edit_view',
kwargs={'userid': userid, 'id': alert.id}),
'name': alert.name,
},
]
return render(request, 'alert_edit.html',
{
'breadcrumbs': breadcrumbs,
'rower': r,
'form': form,
'measuredform': measuredform,
'formset': filter_formset,
'alert': alert,
})
# alert delete view
class AlertDelete(DeleteView):
login_required = True
model = Alert
template_name = 'alert_delete_confirm.html'
# extra parameters
def get_context_data(self, **kwargs):
context = super(AlertDelete, self).get_context_data(**kwargs)
if 'userid' in kwargs: # pragma: no cover
userid = kwargs['userid']
else:
userid = 0
context['rower'] = getrequestrower(self.request, userid=userid)
context['alert'] = self.object
breadcrumbs = [
{
'url': '/rowers/analysis',
'name': 'Analysis'
},
{
'url': reverse('alerts_view'),
'name': 'Alerts',
},
{
'url': reverse('alert_edit_view',
kwargs={'userid': userid, 'id': self.object.pk}),
'name': 'Alert',
},
{
'url': reverse('alert_delete_view', kwargs={'pk': self.object.pk}),
'name': 'Delete'
}
]
context['breadcrumbs'] = breadcrumbs
return context
def get_success_url(self):
return reverse('alerts_view')
def get_object(self, *args, **kwargs):
obj = super(AlertDelete, self).get_object(*args, **kwargs)
if obj.manager != self.request.user: # pragma: no cover
raise PermissionDenied("You are not allowed to delete this Alert")
# some checks
return obj
@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)
@permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True)
def history_view(request, userid=0):
r = getrequestrower(request, userid=userid)
usertimezone = pytz.timezone(r.defaulttimezone)
time_min = datetime.time(hour=0, minute=0, second=0)
time_max = datetime.time(hour=23, minute=59, second=59)
if request.GET.get('startdate'):
startdate, enddate = get_dates_timeperiod(request)
sstartdate = startdate
senddate = enddate
activity_startdate = usertimezone.localize(
timezone.datetime.combine(startdate, time_min))
activity_enddate = usertimezone.localize(
timezone.datetime.combine(enddate, time_max))
else:
activity_enddate = timezone.now()
activity_enddate = usertimezone.localize(
timezone.datetime.combine(activity_enddate.date(), time_max))
startdate = timezone.now() - datetime.timedelta(days=14)
activity_startdate = usertimezone.localize(
timezone.datetime.combine(startdate.date(), time_min))
sstartdate = activity_startdate.date
senddate = activity_enddate.date
startdate = sstartdate
enddate = senddate
typeselect = 'All'
if request.GET.get('workouttype'):
typeselect = request.GET.get('workouttype')
yaxis = request.GET.get('yaxis', 'duration')
if typeselect not in mytypes.checktypes:
typeselect = 'All'
form = HistorySelectForm(initial={'startdate': activity_startdate,
'enddate': activity_enddate,
'workouttype': typeselect,
'yaxis': yaxis})
g_workouts = Workout.objects.filter(
user=r,
startdatetime__gte=activity_startdate,
startdatetime__lte=activity_enddate,
duplicate=False,
privacy='visible'
).order_by("-startdatetime")
tscript, tdiv = interactive_workouttype_piechart(g_workouts)
totalmeters, totalhours, totalminutes, totalseconds = get_totals(
g_workouts)
# meters, duration per workout type
wtypes = list(set([w.workouttype for w in g_workouts]))
typechoices = [("All", "All")]
for wtype in wtypes:
if wtype in mytypes.checktypes:
typechoices.append((wtype, mytypes.workouttypes_ordered[wtype]))
form.fields['workouttype'].choices = typechoices
listofdicts = []
for wtype in wtypes:
a_workouts = g_workouts.filter(workouttype=wtype)
wmeters, whours, wminutes, wseconds = get_totals(a_workouts)
ddict = {}
ddict['id'] = wtype
try:
ddict['wtype'] = mytypes.workouttypes_ordered[wtype]
except KeyError: # pragma: no cover
ddict['wtype'] = wtype
ddict['distance'] = wmeters
ddict['duration'] = "{whours}:{wminutes:02d}:{wseconds:02d}".format(
whours=whours,
wminutes=wminutes,
wseconds=wseconds,
)
ddict['distance'] = wmeters
ddict['nrworkouts'] = a_workouts.count()
listofdicts.append(ddict)
# interactive hr pie chart
totalscript = ''
totaldiv = get_call()
totalsdict = {}
totalsdict['duration'] = "{totalhours:02d}:{totalminutes:02d}:{totalseconds:02d}".format(
totalhours=totalhours,
totalminutes=totalminutes,
totalseconds=totalseconds,
)
totalsdict['distance'] = totalmeters
totalsdict['nrworkouts'] = g_workouts.count()
breadcrumbs = [
{
'url': '/rowers/analysis',
'name': 'Analysis',
},
{
'url': reverse('history_view'),
'name': 'History',
},
]
lastseven = timezone.now() - datetime.timedelta(days=7)
lastfourteen = timezone.now() - datetime.timedelta(days=14)
last28 = timezone.now() - datetime.timedelta(days=28)
today = timezone.now()
lastyear = datetime.datetime(
year=today.year - 1, month=today.month, day=today.day)
firstmay = datetime.datetime(
year=today.year, month=5, day=1).astimezone(usertimezone)
if firstmay > today: # pragma: no cover
firstmay = datetime.datetime(year=today.year - 1, month=5, day=1)
return render(request, 'history.html',
{
'tscript': tscript,
'tdiv': tdiv,
'rower': r,
'breadcrumbs': breadcrumbs,
'active': 'nav-analysis',
'totalsdict': totalsdict,
'typedicts': listofdicts,
'totalscript': totalscript,
'totaldiv': totaldiv,
'form': form,
'startdate': activity_startdate,
'enddate': activity_enddate,
'lastseven': lastseven,
'lastfourteen': lastfourteen,
'last28': last28,
'lastyear': lastyear,
'today': today,
'workouttype': typeselect,
'yaxis': yaxis,
'firstmay': firstmay,
'sstartdate': sstartdate,
'senddate': senddate,
})
@login_required()
def history_view_data(request, userid=0):
r = getrequestrower(request, userid=userid)
usertimezone = pytz.timezone(r.defaulttimezone)
time_min = datetime.time(hour=0, minute=0, second=0)
time_max = datetime.time(hour=23, minute=59, second=59)
startdate = timezone.now() - datetime.timedelta(days=14)
enddate = timezone.now()
activity_enddate = usertimezone.localize(
timezone.datetime.combine(enddate.date(), time_max))
activity_startdate = usertimezone.localize(
timezone.datetime.combine(startdate.date(), time_min))
if request.GET.get('startdate'):
startdate = datetime.datetime.strptime(
request.GET.get('startdate'), "%Y-%m-%d")
activity_startdate = usertimezone.localize(
timezone.datetime.combine(startdate, time_min))
if request.GET.get('enddate'):
enddate = datetime.datetime.strptime(
request.GET.get('enddate'), "%Y-%m-%d")
activity_enddate = usertimezone.localize(
timezone.datetime.combine(enddate, time_max))
typeselect = 'All'
if request.GET.get('workouttype'):
typeselect = request.GET.get('workouttype')
if typeselect not in mytypes.checktypes:
typeselect = 'All'
yaxis = request.GET.get('yaxis', 'duration')
if yaxis.lower() not in ['duration', 'rscore', 'trimp', 'distance']: # pragma: no cover
yaxis = 'duration'
g_workouts = Workout.objects.filter(
user=r,
startdatetime__gte=activity_startdate,
startdatetime__lte=activity_enddate,
duplicate=False,
privacy='visible'
).order_by("-startdatetime")
ids = [w.id for w in g_workouts]
columns = ['hr', 'power', 'time']
df = getsmallrowdata_db(columns, ids=ids)
try:
df['deltat'] = df['time'].diff().clip(lower=0)
except KeyError:
pass
df = dataprep.clean_df_stats(df, workstrokesonly=True,
ignoreadvanced=True, ignorehr=False)
totalmeters, totalhours, totalminutes, totalseconds = get_totals(
g_workouts)
# meters, duration per workout type
wtypes = list(set([w.workouttype for w in g_workouts]))
typechoices = [("All", "All")]
for wtype in wtypes:
if wtype in mytypes.checktypes:
typechoices.append((wtype, mytypes.workouttypes_ordered[wtype]))
listofdicts = []
for wtype in wtypes:
a_workouts = g_workouts.filter(workouttype=wtype)
wmeters, whours, wminutes, wseconds = get_totals(a_workouts)
ddict = {}
try: # pragma: no cover
ddict['wtype'] = mytypes.workouttypes_ordered[wtype]
except KeyError: # pragma: no cover
ddict['wtype'] = wtype
ddict['id'] = wtype
ddict['distance'] = wmeters
ddict['duration'] = "{whours}:{wminutes:02d}:{wseconds:02d}".format(
whours=whours,
wminutes=wminutes, wseconds=wseconds,
)
ddf = getsmallrowdata_db(columns, ids=[w.id for w in a_workouts])
try:
ddf['deltat'] = ddf['time'].diff().clip(lower=0)
except KeyError: # pragma: no cover
pass
ddf = dataprep.clean_df_stats(ddf, workstrokesonly=False,
ignoreadvanced=True)
ddict['hrmean'] = int(wavg(ddf, 'hr', 'deltat'))
try:
ddict['hrmax'] = ddf['hr'].max().astype(int)
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:
ddict['powermax'] = 0
ddict['nrworkouts'] = a_workouts.count()
listofdicts.append(ddict)
totalsdict = {}
totalsdict['duration'] = "{totalhours}:{totalminutes}".format(
totalhours=totalhours,
totalminutes=totalminutes
)
totalsdict['distance'] = totalmeters
try:
totalsdict['powermean'] = int(wavg(df, 'power', 'deltat'))
totalsdict['powermax'] = df['power'].max().astype(int)
except KeyError:
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['hrmean'] = 0
totalsdict['hrmax'] = 0
totalsdict['nrworkouts'] = g_workouts.count()
# activity chart
activity_script, activity_div = interactive_activitychart2(
g_workouts, startdate, enddate, yaxis=yaxis)
# interactive hr pie chart
if typeselect == 'All':
totalseconds = 3600 * totalhours + 60 * totalminutes + totalseconds
totalscript, totaldiv = interactive_hr_piechart(df, r, 'All Workouts',
totalseconds=totalseconds)
else:
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])
try:
ddf['deltat'] = ddf['time'].diff().clip(lower=0)
except KeyError:
pass
ddf = dataprep.clean_df_stats(ddf, workstrokesonly=True,
ignoreadvanced=True)
totalscript, totaldiv = interactive_hr_piechart(
ddf, r, mytypes.workouttypes_ordered[typeselect],
totalseconds=totalseconds)
return JSONResponse({
'script': totalscript,
'div': totaldiv,
'totalsdict': totalsdict,
'listofdicts': listofdicts,
'activities_script': activity_script,
'activities_chart': activity_div,
})