Private
Public Access
1
0
Files
rowsandall/rowers/views/workoutviews.py
2021-12-14 16:36:22 +01:00

7110 lines
237 KiB
Python

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import hashlib
from shutil import copyfile
from six import iterlists
from rowers.views.statements import *
import rowers.teams as teams
import rowers.mytypes as mytypes
import numpy
from rowers.mailprocessing import send_confirm
import rowers.uploads as uploads
import rowers.utils as utils
from rowers.utils import intervals_to_string
from urllib.parse import urlparse, parse_qs
from json.decoder import JSONDecodeError
import ruptures as rpt
from rowers.courses import getnearestraces, getnearestcourses
def default(o): # pragma: no cover
if isinstance(o, numpy.int64): return int(o)
if isinstance(o, numpy.int32): return int(o)
raise TypeError
def get_video_id(url):
"""Returns Video_ID extracting from the given url of Youtube
Examples of URLs:
Valid:
'http://youtu.be/_lOT2p_FCvA',
'www.youtube.com/watch?v=_lOT2p_FCvA&feature=feedu',
'http://www.youtube.com/embed/_lOT2p_FCvA',
'http://www.youtube.com/v/_lOT2p_FCvA?version=3&hl=en_US',
'https://www.youtube.com/watch?v=rTHlyTphWP0&index=6&list=PLjeDyYvG6-40qawYNR4juzvSOg-ezZ2a6',
'youtube.com/watch?v=_lOT2p_FCvA',
Invalid:
'youtu.be/watch?v=_lOT2p_FCvA',
"""
if url.startswith(('youtu', 'www')):
url = 'http://' + url
elif 'http' not in url: # pragma: no cover
# not sure if this is a valid case at all
return url
query = urlparse(url)
if 'youtube' in query.hostname:
if query.path == '/watch':
return parse_qs(query.query)['v'][0]
elif query.path.startswith(('/embed/', '/v/')):
return query.path.split('/')[2]
elif 'youtu.be' in query.hostname:
return query.path[1:]
else: # pragma: no cover
raise ValueError
# Show a video compared with data
def workout_video_view_mini(request,id=''):
try:
id = encoder.decode_hex(id)
analysis = VideoAnalysis.objects.get(id=id)
except (VideoAnalysis.DoesNotExist,ValueError): # pragma: no cover
raise Http404("Video Analysis does not exist")
w = analysis.workout
delay = analysis.delay
if w.workouttype in mytypes.otwtypes:
mode = 'water'
else:
mode = 'erg'
if request.user.is_authenticated:
mayedit = is_workout_user(request.user,w) and is_promember(request.user)
rower = request.user.rower
else:
mayedit = False
rower = None
# get video ID and offset
if mayedit and request.method == 'POST':
form = VideoAnalysisCreateForm(request.POST)
metricsform = VideoAnalysisMetricsForm(request.POST,mode=mode)
if form.is_valid() and metricsform.is_valid():
video_id = form.cleaned_data['url']
try:
video_id = get_video_id(form.cleaned_data['url'])
except (TypeError,ValueError): # pragma: no cover
pass
delay = form.cleaned_data['delay']
metricsgroups = metricsform.cleaned_data['groups']
if 'save_button' in request.POST:
analysis.name = form.cleaned_data['name']
analysis.video_id = video_id
analysis.delay = delay
analysis.metricsgroups = metricsgroups
analysis.save()
else: # pragma: no cover
# invalid forms
video_id = id
delay = 0
elif mayedit:
form = VideoAnalysisCreateForm(
initial = {
'name':analysis.name,
'delay': analysis.delay,
'url': analysis.video_id,
}
)
metricsform = VideoAnalysisMetricsForm(initial={'groups':analysis.metricsgroups},
mode=mode)
metricsgroups = analysis.metricsgroups
video_id = analysis.video_id
else:
form = None
metricsform = None
metricsgroups = analysis.metricsgroups
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)
else:
mapscript, mapdiv = interactive_chart_video(data)
data['longitude'] = data['spm']
data['latitude'] = list(range(len(data['spm'])))
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,encoder.encode_hex(w.id)),
'name': w.name
},
{
'url':reverse('workout_video_view_mini',kwargs={'id':encoder.encode_hex(analysis.id)}),
'name': 'Video Analysis'
}
]
return render(request,
'embedded_video_mini.html',
{
'workout':w,
'rower':rower,
'data': json.dumps(data,default=default),
'mapscript': mapscript,
'mapdiv': mapdiv,
'video_id': analysis.video_id,
'form':form,
'breadcrumbs':breadcrumbs,
'analysis':analysis,
'maxtime':maxtime,
'metrics':metrics,
'locked': True,
'metricsform':metricsform,
'metricsgroups': metricsgroups,
'siteurl': settings.SITE_URL,
})
# Show a video compared with data
def workout_video_view(request,id=''):
try:
id = encoder.decode_hex(id)
analysis = VideoAnalysis.objects.get(id=id)
except (VideoAnalysis.DoesNotExist,ValueError): # pragma: no cover
raise Http404("Video Analysis does not exist")
w = analysis.workout
delay = analysis.delay
if w.workouttype in mytypes.otwtypes:
mode = 'water'
else:
mode = 'erg'
if request.user.is_authenticated:
mayedit = is_promember(request.user) and is_workout_user(request.user,w)
rower = request.user.rower
else:
mayedit = False
rower = None
# get video ID and offset
if mayedit and request.method == 'POST':
form = VideoAnalysisCreateForm(request.POST)
metricsform = VideoAnalysisMetricsForm(request.POST,mode=mode)
if form.is_valid() and metricsform.is_valid():
video_id = form.cleaned_data['url']
try:
video_id = get_video_id(form.cleaned_data['url'])
except (TypeError,ValueError): # pragma: no cover
pass
delay = form.cleaned_data['delay']
metricsgroups = metricsform.cleaned_data['groups']
if 'save_button' in request.POST:
analysis.name = form.cleaned_data['name']
analysis.video_id = video_id
analysis.delay = delay
analysis.metricsgroups = metricsgroups
analysis.save()
else: # pragma: no cover
video_id = id
delay = 0
elif mayedit:
form = VideoAnalysisCreateForm(
initial = {
'name':analysis.name,
'delay': analysis.delay,
'url': analysis.video_id,
}
)
metricsform = VideoAnalysisMetricsForm(initial={'groups':analysis.metricsgroups},
mode=mode)
metricsgroups = analysis.metricsgroups
video_id = analysis.video_id
else:
form = None
metricsform = None
metricsgroups = analysis.metricsgroups
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)
else:
mapscript, mapdiv = interactive_chart_video(data)
data['longitude'] = data['spm']
data['latitude'] = list(range(len(data['spm'])))
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,encoder.encode_hex(w.id)),
'name': w.name
},
{
'url':reverse('workout_video_view',kwargs={'id':encoder.encode_hex(analysis.id)}),
'name': 'Video Analysis'
}
]
return render(request,
'embedded_video.html',
{
'workout':w,
'rower':rower,
'data': json.dumps(data,default=default),
'mapscript': mapscript,
'mapdiv': mapdiv,
'video_id': analysis.video_id,
'form':form,
'breadcrumbs':breadcrumbs,
'analysis':analysis,
'maxtime':maxtime,
'metrics':metrics,
'locked': True,
'metricsform':metricsform,
'metricsgroups': metricsgroups,
'siteurl': settings.SITE_URL,
})
# Create a video compared with 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",
redirect_field_name=None)
def workout_video_create_view(request,id=0):
# get workout
w = get_workout_by_opaqueid(request,id)
if w.workouttype in mytypes.otwtypes:
mode = 'water'
else:
mode = 'erg'
# get video ID and offset
if request.method == 'POST':
form = VideoAnalysisCreateForm(request.POST)
metricsform = VideoAnalysisMetricsForm(request.POST,mode=mode)
if form.is_valid() and metricsform.is_valid():
url = form.cleaned_data['url']
delay = form.cleaned_data['delay']
metricsgroups = metricsform.cleaned_data['groups']
try:
video_id = get_video_id(url)
except ValueError: # pragma: no cover
messages.error(request,"Not a valid YouTube video link")
video_id = None
if 'save_button' in request.POST:
analysis = VideoAnalysis(
workout=w,
name=form.cleaned_data['name'],
video_id = video_id,
delay=delay,
metricsgroups=metricsgroups
)
try:
analysis.save()
url = reverse('workout_video_view',
kwargs={'id':encoder.encode_hex(analysis.id)})
return HttpResponseRedirect(url)
except IntegrityError: # pragma: no cover
messages.error(request,'You cannot save two video analysis with the same YouTube video and Workout. Redirecting to your existing analysis')
analysis = VideoAnalysis.objects.filter(workout=w,video_id=video_id)
if analysis:
url = reverse('workout_video_view',
kwargs={'id':encoder.encode_hex(analysis[0].id)})
else:
url = reverse('workout_video_create_view',
kwargs={'id':encoder.encode_hex(w.id)})
return HttpResponseRedirect(url)
else: # pragma: no cover
video_id = None
delay = 0
metricsgroups = ['basic']
else:
form = VideoAnalysisCreateForm()
metricsform = VideoAnalysisMetricsForm(initial={'groups':['basic']},mode=mode)
video_id = None
delay = 0
metricsgroups = ['basic']
# 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)
else:
mapscript, mapdiv = interactive_chart_video(data)
data['longitude'] = data['spm']
data['latitude'] = list(range(len(data['spm'])))
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,encoder.encode_hex(w.id)),
'name': w.name
},
{
'url':reverse('workout_video_create_view',kwargs={'id':encoder.encode_hex(w.id)}),
'name': 'Video Analysis'
}
]
analysis = {'delay':delay}
template = 'embedded_video.html'
return render(request,
template,
{
'workout':w,
'rower':request.user.rower,
'data': json.dumps(data,default=default),
'mapscript': mapscript,
'mapdiv': mapdiv,
'video_id': video_id,
'form':form,
'metricsform':metricsform,
'analysis':analysis,
'breadcrumbs':breadcrumbs,
'maxtime':maxtime,
'metrics':metrics,
'metricsgroups': metricsgroups,
'locked': False,
'siteurl': settings.SITE_URL,
})
# Show the EMpower Oarlock generated Stroke Profile
@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. If you are already a Pro user, please log in to access this functionality",
redirect_field_name=None)
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_forcecurve_view(request,id=0,workstrokesonly=False):
row = get_workoutuser(id, request)
promember=0
mayedit=0
r = getrequestrower(request)
promember = 1
if r == row.user:
mayedit = 1
if request.method == 'POST':
form = ForceCurveOptionsForm(request.POST)
if form.is_valid():
includereststrokes = form.cleaned_data['includereststrokes']
plottype = form.cleaned_data['plottype']
workstrokesonly = not includereststrokes
else: # pragma: no cover
workstrokesonly = True
plottype = 'line'
else:
form = ForceCurveOptionsForm()
plottype = 'line'
script,div,js_resources,css_resources = interactive_forcecurve(
[row],
workstrokesonly=workstrokesonly,
plottype=plottype,
)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,id),
'name': row.name
},
{
'url':reverse('workout_forcecurve_view',kwargs={'id':id}),
'name': 'Empower Force Curve'
}
]
r = getrower(request.user)
return render(request,
'forcecurve_single.html',
{
'the_script':script,
'rower':r,
'form':form,
'workout':row,
'breadcrumbs':breadcrumbs,
'active':'nav-workouts',
'the_div':div,
'js_res': js_resources,
'css_res':css_resources,
'id':id,
'mayedit':mayedit,
'teams':get_my_teams(request.user),
})
# Switch from GPS to Impeller (only for SpeedCoach 2, if impeller data)
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def otw_use_impeller(request,id=0):
w = get_workoutuser(id, request)
row = rdata(csvfile=w.csvfilename)
success = row.use_impellerdata()
if success:
row.write_csv(w.csvfilename)
dataprep.update_strokedata(w.id,row.df)
w.impeller = True
w.save()
messages.info(request,'The distance and speed data are now based on Impeller data')
else:
messages.error(request,'No impeller data found')
url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)})
return HttpResponseRedirect(url)
# Switch from Impeller to GPS (only for SpeedCoach 2, if impeller data)
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def otw_use_gps(request,id=0):
w = get_workoutuser(id, request)
row = rdata(csvfile=w.csvfilename)
success = row.use_gpsdata()
if success:
row.write_csv(w.csvfilename)
dataprep.update_strokedata(w.id,row.df)
w.impeller = False
w.save()
messages.info(request,'The distance and speed data are now based on GPS data')
else:
messages.error(request,'No GPS data found')
url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)})
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)
promember = 1
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()
def addmanual_view(request,raceid=0):
r = Rower.objects.get(user=request.user)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':reverse('addmanual_view'),
'name': 'Add Manual Entry'
},
]
if request.method == 'POST':
# Form was submitted
form = WorkoutForm(request.POST)
metricsform = MetricsForm(request.POST)
if form.is_valid() and metricsform.is_valid():
# Get values from form
name = form.cleaned_data['name']
if name == '': # pragma: no cover
name = 'Manual Entry'
date = form.cleaned_data['date']
starttime = form.cleaned_data['starttime']
workouttype = form.cleaned_data['workouttype']
duration = form.cleaned_data['duration']
weightcategory = form.cleaned_data['weightcategory']
adaptiveclass = form.cleaned_data['adaptiveclass']
distance = form.cleaned_data['distance']
try:
rpe = form.cleaned_data['rpe']
if not rpe:
rpe = -1
except KeyError: # pragma: no cover
rpe = -1
notes = form.cleaned_data['notes']
thetimezone = form.cleaned_data['timezone']
private = form.cleaned_data['private']
avghr = metricsform.cleaned_data['avghr']
avgpwr = metricsform.cleaned_data['avgpwr']
avgspm = metricsform.cleaned_data['avgspm']
#ps = form.cleaned_data.get('plannedsession',None)
boattype = form.cleaned_data.get('boattype','1x')
privacy = form.cleaned_data.get('privacy','visible')
rankingpiece = form.cleaned_data.get('rankingpiece',False)
duplicate = form.cleaned_data.get('duplicate',False)
if private: # pragma: no cover
privacy = 'private'
else:
privacy = 'visible'
startdatetime = (str(date) + ' ' + str(starttime))
startdatetime = datetime.datetime.strptime(startdatetime,
"%Y-%m-%d %H:%M:%S")
startdatetime = timezone.make_aware(startdatetime)
startdatetime = startdatetime.astimezone(
pytz.timezone(thetimezone)
)
id,message = dataprep.create_row_df(r,
distance,
duration,startdatetime,
rpe=rpe,
weightcategory=weightcategory,
adaptiveclass=adaptiveclass,
avghr=avghr,
rankingpiece=rankingpiece,
avgpwr=avgpwr,
duplicate=duplicate,
avgspm=avgspm,
title = name,
notes=notes,
workouttype=workouttype)
if message: # pragma: no cover
messages.error(request,message)
if id:
w = Workout.objects.get(id=id)
w.rankingpiece = rankingpiece
w.privacy = privacy
w.weightcategory = weightcategory
w.adaptiveclass = adaptiveclass
w.notes = notes
#w.plannedsession = ps
w.name = name
w.rpe = rpe
w.workouttype = workouttype
w.boattype = boattype
w.distance = distance
w.duration = duration
w.save()
#if ps:
# add_workouts_plannedsession([w],ps,w.user)
messages.info(request,'New workout created')
iform = ImageForm(request.POST,request.FILES)
if iform.is_valid(): # this works but cannot get the tests to work
f = iform.cleaned_data['file']
if f is not None: # pragma: no cover
filename,path_and_filename = handle_uploaded_image(f)
try:
width, height = Image.open(path_and_filename).size
except:
message = "Not a valid image"
messages.error(request,message)
os.remove(path_and_filename)
i = GraphImage(workout=w,
creationdatetime=timezone.now(),
filename = path_and_filename,
width=width,height=height)
i.save()
if raceid != 0:
try:
race = VirtualRace.objects.get(id=raceid)
except VirtualRace.DoesNotExist: # pragma: no cover
messages.error(request,"Race does not exist")
url = reverse('workout_edit_view',
kwargs = {'id':encoder.encode_hex(id)})
return HttpResponseRedirect(url)
can_submit = race_can_submit(r,race) or race_can_resubmit(r,race)
can_submit = can_submit and race.sessiontype == 'indoorrace'
if not can_submit: # pragma: no cover
messages.error(request,'You cannot submit a result for this race')
if can_submit:
records = IndoorVirtualRaceResult.objects.filter(race=race,userid=r.id)
if not records: # pragma: no cover
messages.error(request,'You have to register for the race first')
url = reverse('virtualevent_view',kwargs = {'id':race.id})
return HttpResponseRedirect(url)
recordid = records[0].id
result, comments, errors, jobid = add_workout_indoorrace(
[w],race,r,recordid=recordid
)
for c in comments: # pragma: no cover
messages.info(request,c)
for er in errors: # pragma: no cover
messages.error(request,er)
if result:
otherrecords = IndoorVirtualRaceResult.objects.filter(
race = race
).exclude(userid=r.id)
for otherrecord in otherrecords:
try: # pragma: no cover
otheruser = Rower.objects.get(id=otherrecord.userid)
othername = otheruser.user.first_name+' '+otheruser.user.last_name
registeredname = r.user.first_name+' '+r.user.last_name
if otherrecord.emailnotifications:
job = myqueue(
queue,
handle_sendemail_racesubmission,
otheruser.user.email, othername,
registeredname,
race.name,
race.id
)
except Rower.DoesNotExist: # pragma: no cover
pass
url = reverse('virtualevent_view',kwargs = {'id':race.id})
return HttpResponseRedirect(url)
url = reverse(
'workout_edit_view',
kwargs={'id':encoder.encode_hex(id)}
)
return HttpResponseRedirect(url)
else: # pragma: no cover
iform = ImageForm()
return render(request,'manualadd.html',
{'form':form,
'iform':iform,
'metricsform':metricsform,
'breadcrumbs':breadcrumbs,
'active':'nav-workouts',
})
initial = {
'workouttype':'rower',
'date':timezone.now(),
'starttime':timezone.now(),
'timezone':r.defaulttimezone,
'duration':datetime.timedelta(minutes=2),
'distance':500,
}
form = WorkoutForm(initial=initial)
iform = ImageForm()
metricsform = MetricsForm()
return render(request,'manualadd.html',
{'form':form,
'iform':iform,
'metricsform':metricsform,
'breadcrumbs':breadcrumbs,
'active':'nav-workouts',
})
# Reload the workout and calculate the summary from the stroke data (lapIDx)
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_recalcsummary_view(request,id=0):
row = get_workoutuser(id, request)
filename = row.csvfilename
rowdata = rdata(csvfile=filename)
if rowdata:
row.summary = rowdata.allstats()
row.save()
successmessage = "Summary Updated"
messages.info(request,successmessage)
url = reverse('workout_edit_view',
kwargs = {
'id':id,
})
else:
message = "Something went wrong. Could not update summary"
messages.error(request,message)
url = reverse('workout_edit_view',
kwargs = {
'id':id,
})
return HttpResponseRedirect(url)
# Joining workout
@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. 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 workouts_join_view(request,userid=0):
promember=0
r = getrequestrower(request,userid=userid)
if not request.user.is_anonymous:
r = getrower(request.user)
result = request.user.is_authenticated and ispromember(request.user)
if result:
promember=1
if request.method == 'POST' and 'workouts' in request.POST:
form = WorkoutMultipleCompareForm(request.POST)
paramform = WorkoutJoinParamForm(request.POST)
if form.is_valid() and paramform.is_valid():
workout_name = paramform.cleaned_data['workout_name']
set_private = paramform.cleaned_data['set_private']
killparents = paramform.cleaned_data['killparents']
cd = form.cleaned_data
workouts = cd['workouts']
ids = [int(w.id) for w in workouts]
request.session['ids'] = ids
id,message = dataprep.join_workouts(r,ids,
title=workout_name,
setprivate=set_private,
killparents=killparents)
if message: # pragma: no cover
messages.error(request,message)
url = reverse(r.defaultlandingpage,
kwargs = {
'id':encoder.encode_hex(id),
})
return HttpResponseRedirect(url)
else: # pragma: no cover
messages.error("Form is not valid")
url = reverse('workouts_join_select')
return HttpResponseRedirect(url)
defaultoptions = {
'includereststrokes': False,
'workouttypes':['rower','dynamic','slides'],
'waterboattype': mytypes.waterboattype,
'rankingonly': False,
'function':'boxplot'
}
@user_passes_test(ispromember, login_url="/rowers/paidplans",
message="This functionality requires a Pro plan or higher",
redirect_field_name=None)
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def video_selectworkout(request,userid=0):
r = getrequestrower(request, userid=userid)
user = r.user
userid = user.id
workouts = Workout.objects.filter(user=r).order_by('-date')
if 'options' in request.session:
options = request.session['options']
else:
options=defaultoptions
options['userid'] = userid
try:
workouttypes = options['workouttypes']
except KeyError: # pragma: no cover
workouttypes = ['rower','dynamic','slides']
try: # pragma: no cover
modalities = options['modalities']
modality = modalities[0]
except KeyError: # pragma: no cover
modalities = [m[0] for m in mytypes.workouttypes_ordered.items()]
modality = 'all'
try:
rankingonly = options['rankingonly']
except KeyError: # pragma: no cover
rankingonly = False
query = request.GET.get('q')
if query: # pragma: no cover
query_list = query.split()
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})
else:
searchform = SearchForm()
if 'startdate' in request.session:
startdate = iso8601.parse_date(request.session['startdate'])
else:
startdate=timezone.now()-datetime.timedelta(days=42)
if 'enddate' in request.session:
enddate = iso8601.parse_date(request.session['enddate'])
else:
enddate = timezone.now()
workouts = workouts.filter(date__gte=startdate,date__lte=enddate)
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
form = WorkoutSingleSelectForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
selectedworkout = cd['workout']
url = reverse('workout_video_create_view',
kwargs={'id':encoder.encode_hex(selectedworkout.id)})
return HttpResponseRedirect(url)
else: # pragma: no cover
id = 0
else:
form = WorkoutSingleSelectForm(workouts=workouts)
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))
# make sure the dates are not naive
startdate = pytz.utc.localize(startdate)
enddate = pytz.utc.localize(enddate)
if enddate < startdate: # pragma: no cover
s = enddate
enddate = startdate
startdate = s
negtypes = []
for b in mytypes.boattypes:
if b[0] not in waterboattype: # pragma: no cover
negtypes.append(b[0])
workouts = Workout.objects.filter(user=r,
startdatetime__gte=startdate,
startdatetime__lte=enddate,
workouttype__in=modalities,
)
workouts = workouts.order_by(
"-date", "-starttime"
).exclude(boattype__in=negtypes)
if rankingonly: # pragma: no cover
workouts = workouts.exclude(rankingpiece=False)
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, 'video_selectworkout.html',
{'workouts': workouts,
'dateform':dateform,
'startdate':startdate,
'enddate':enddate,
'rower':r,
'breadcrumbs':breadcrumbs,
'theuser':user,
'the_div':thediv,
'form':form,
'active':'nav-analysis',
'searchform':searchform,
'teams':get_my_teams(request.user),
})
@permission_required('rower.is_coach',fn=get_user_by_userid,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. If you are already a Pro user, please log in to access this functionality",
redirect_field_name=None)
def workouts_join_select(request,
startdatestring="",
enddatestring="",
message='',
successmessage='',
userid=0,
startdate=timezone.now()-datetime.timedelta(days=30),
enddate=timezone.now()+datetime.timedelta(days=1),
):
r = getrequestrower(request,userid=userid)
if 'waterboattype' in request.session:
waterboattype = request.session['waterboattype']
else:
waterboattype = mytypes.waterboattype
if 'modalities' in request.session:
modalities = request.session['modalities']
if len(modalities) > 1: # pragma: no cover
modality = 'all'
else:
modality = modalities[0]
else:
modalities = [m[0] for m in mytypes.workouttypes]
modality = 'all'
if request.method == 'POST':
dateform = DateRangeForm(request.POST)
modalityform = TrendFlexModalForm(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
if modalityform.is_valid():
modality = modalityform.cleaned_data['modality']
waterboattype = modalityform.cleaned_data['waterboattype']
if modality == 'all': # pragma: no cover
modalities = [m[0] for m in mytypes.workouttypes]
else:
modalities = [modality]
if modality != 'water': # pragma: no cover
waterboattype = [b[0] for b in mytypes.boattypes]
request.session['modalities'] = modalities
request.session['waterboattype'] = waterboattype
else:
dateform = DateRangeForm(initial={
'startdate':startdate,
'enddate':enddate,
})
negtypes = []
for b in mytypes.boattypes:
if b[0] not in waterboattype:
negtypes.append(b[0])
startdate = datetime.datetime.combine(startdate,datetime.time())
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
#enddate = enddate+datetime.timedelta(days=1)
if startdatestring:
startdate = iso8601.parse_date(startdatestring)
if enddatestring:
enddate = iso8601.parse_date(enddatestring)
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:
pass
try:
enddate = pytz.utc.localize(enddate)
except ValueError:
pass
workouts = Workout.objects.filter(user=r,
startdatetime__gte=startdate,
startdatetime__lte=enddate,
workouttype__in=modalities).order_by("-date", "-starttime").exclude(boattype__in=negtypes)
query = request.GET.get('q')
if query: # pragma: no cover
query_list = query.split()
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})
else:
searchform = SearchForm()
form = WorkoutMultipleCompareForm()
form.fields["workouts"].queryset = workouts
joinparamform = WorkoutJoinParamForm()
modalityform = TrendFlexModalForm(initial={
'modality':modality,
'waterboattype':waterboattype
})
messages.info(request,successmessage)
messages.error(request,message)
return render(request, 'workout_join_select.html',
{'workouts': workouts,
'dateform':dateform,
'searchform':searchform,
'startdate':startdate,
'enddate':enddate,
'active':'nav-workouts',
'form':form,
'joinparamform':joinparamform,
'modalityform':modalityform,
'teams':get_my_teams(request.user),
})
@login_required()
def remove_power_confirm_view(request,id=0):
r = getrower(request.user)
workout = get_workout_by_opaqueid(request,id)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,encoder.encode_hex(workout.id)),
'name': encoder.encode_hex(workout.id)
},
{ 'url':reverse('remove_power_confirm_view',
kwargs={'id':encoder.encode_hex(workout.id)}),
'name': 'Delete'
}
]
return render(request,
'workout_remove_power_confirm.html',
{
'workout':workout,
'rower':r,
'breadcrumbs':breadcrumbs,
})
@login_required()
def remove_power_view(request,id=0):
r = getrower(request.user)
workout = get_workout_by_opaqueid(request,id)
try:
os.remove('media/cpdata_{id}.parquet.gz'.format(id=workout.id))
except OSError: # pragma: no cover
pass
f = workout.csvfilename
powerperc = 100*np.array([r.pw_ut2,
r.pw_ut1,
r.pw_at,
r.pw_tr,r.pw_an])/r.ftp
rr = rrower(hrmax=r.max, hrut2=r.ut2,
hrut1=r.ut1, hrat=r.at,
hrtr=r.tr, hran=r.an, ftp=r.ftp,
powerperc=powerperc, powerzones=r.powerzones,
hrzones=r.hrzones)
row = rdata(csvfile=f,rower=rr)
row.df[' Power (watts)'] = 0
row.write_csv(f)
res = dataprep.dataprep(row.df, id=workout.id)
cpdf,delta,cpvalues = dataprep.setcp(workout)
workout.normp = 0
workout.rscore = 0
workout.save()
dataprep.initiate_cp(r)
url = reverse(r.defaultlandingpage,
kwargs = {
'id':id,
}
)
return HttpResponseRedirect(url)
# Team comparison
@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. If you are already a Pro user, please log in to access this functionality",
redirect_field_name=None)
def team_comparison_select(request,
startdatestring="",
enddatestring="",
message='',
successmessage='',
userid=0,
startdate=timezone.now()-datetime.timedelta(days=30),
enddate=timezone.now(),
id=0,
teamid=0):
r = getrequestrower(request,userid=userid)
requestrower = getrower(request.user)
request.session.pop('ps',None)
if 'waterboattype' in request.session:
waterboattype = request.session['waterboattype']
else:
waterboattype = mytypes.waterboattype
if 'rankingonly' in request.session: # pragma: no cover
rankingonly = request.session['rankingonly']
else:
rankingonly = False
if 'modalities' in request.session:
modalities = request.session['modalities']
if len(modalities) > 1: # pragma: no cover
modality = 'all'
else:
modality = modalities[0]
else:
modalities = [m[0] for m in mytypes.workouttypes]
modality = 'all'
if request.method == 'POST':
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
modalityform = TrendFlexModalForm(request.POST)
if modalityform.is_valid():
modality = modalityform.cleaned_data['modality']
waterboattype = modalityform.cleaned_data['waterboattype']
if modality == 'all': # pragma: no cover
modalities = [m[0] for m in mytypes.workouttypes]
else:
modalities = [modality]
if modality != 'water': # pragma: no cover
waterboattype = [b[0] for b in mytypes.boattypes]
if 'rankingonly' in modalityform.cleaned_data:
rankingonly = modalityform.cleaned_data['rankingonly']
else: # pragma: no cover
rankingonly = False
request.session['modalities'] = modalities
request.session['waterboattype'] = waterboattype
else:
dateform = DateRangeForm(initial={
'startdate':startdate,
'enddate':enddate,
})
modalityform = TrendFlexModalForm(initial={
'modality':modality,
'waterboattype':waterboattype,
'rankingonly':rankingonly,
})
negtypes = []
for b in mytypes.boattypes:
if b[0] not in waterboattype:
negtypes.append(b[0])
startdate = datetime.datetime.combine(startdate,datetime.time())
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
#enddate = enddate+datetime.timedelta(days=1)
if startdatestring:
startdate = iso8601.parse_date(startdatestring)
if enddatestring:
enddate = iso8601.parse_date(enddatestring)
if enddate < startdate: # pragma: no cover
s = enddate
enddate = startdate
startdate = s
try:
theteam = Team.objects.get(id=teamid)
except Team.DoesNotExist:
theteam = 0
# make sure the dates are not naive
try:
startdate = pytz.utc.localize(startdate)
except ValueError:
pass
try:
enddate = pytz.utc.localize(enddate)
except ValueError:
pass
if theteam and (theteam.viewing == 'allmembers' or theteam.manager == request.user):
workouts = Workout.objects.filter(team=theteam,
startdatetime__gte=startdate,
startdatetime__lte=enddate,
workouttype__in=modalities).order_by("-date", "-starttime").exclude(boattype__in=negtypes)
elif theteam and theteam.viewing == 'coachonly': # pragma: no cover
workouts = Workout.objects.filter(team=theteam,user=r,
startdatetime__gte=startdate,
startdatetime__lte=enddate,
workouttype__in=modalities).order_by("-date","-starttime").exclude(boattype__in=negtypes)
else:
theteam = None
workouts = Workout.objects.filter(user=r,
startdatetime__gte=startdate,
startdatetime__lte=enddate,
workouttype__in=modalities).order_by("-date", "-starttime").exclude(boattype__in=negtypes)
if rankingonly: # pragma: no cover
workouts = workouts.exclude(rankingpiece=False)
query = request.GET.get('q')
if query: # pragma: no cover
query_list = query.split()
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})
else:
searchform = SearchForm()
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))
workouts = firstworkoutquery | workouts
else:
firstworkout = None
form = WorkoutMultipleCompareForm()
form.fields["workouts"].queryset = workouts
if id:
form.fields["workouts"].initial = [firstworkout]
if theteam:
theid = theteam.id
else:
theid = 0
chartform = ChartParamChoiceForm(initial={'teamid':0})
messages.info(request,successmessage)
messages.error(request,message)
if id:
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,id),
'name': firstworkout.name
},
{
'url':reverse('team_comparison_select',kwargs={'id':id,'teamid':teamid}),
'name':'Compare Select'
},
]
else:
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':reverse('team_comparison_select',kwargs={'teamid':teamid}),
'name': 'Compare Select'
},
]
return render(request, 'team_compare_select.html',
{'workouts': workouts,
'workout':firstworkout,
'dateform':dateform,
'startdate':startdate,
'enddate':enddate,
'team':theteam,
'searchform':searchform,
'form':form,
'rower':r,
'breadcrumbs':breadcrumbs,
'active':'nav-workouts',
'chartform':chartform,
'modalityform':modalityform,
'teams':get_my_teams(request.user),
})
def course_mapcompare_view(request,id=0):
results = []
r = None
if not request.user.is_anonymous:
r = getrower(request.user)
try:
course = GeoCourse.objects.get(id=id)
except GeoCourse.DoesNotExist: # pragma: no cover
raise Http404("Course does not exist")
results = VirtualRaceResult.objects.filter(
course=course,
workoutid__isnull=False,
coursecompleted=True,
).order_by("distance","duration")
workoutids = [result.workoutid for result in results]
startenddict = {}
for result in results:
startenddict[result.workoutid] = (result.startsecond,result.endsecond)
if len(workoutids) == 0: # pragma: no cover
url = reverse('course_view',
kwargs={
'id':course.id,
})
messages.info(request,'There are no results to display')
return HttpResponseRedirect(url)
workouts = []
for id in workoutids:
try:
workouts.append(Workout.objects.get(id=id))
except Workout.DoesNotExist: # pragma: no cover
pass
labeldict = {
int(w.id): w.__str__() for w in workouts
}
script,div = leaflet_chart_compare(course,workoutids,
labeldict=labeldict,
startenddict=startenddict)
breadcrumbs = [
{
'url': reverse('courses_view'),
'name': 'Courses'
},
{
'url':reverse('course_view',
kwargs={
'id':course.id,
}
),
'name': course.name
},
{
'url':reverse('course_mapcompare_view',
kwargs={
'id':course.id,
}
),
'name': 'Course Compare'
}
]
return render(request,'mapcompare.html',
{'mapscript':script,
'mapdiv':div,
'breadcrumbs':breadcrumbs,
'rower':r,
'course':course,
'results':results,
'active':'nav-racing',
'teamid':0,
'teams':[]
})
def virtualevent_mapcompare_view(request,id=0):
results = []
r = None
if not request.user.is_anonymous:
r = getrower(request.user)
try:
race = VirtualRace.objects.get(id=id)
except VirtualRace.DoesNotExist: # pragma: no cover
raise Http404("Virtual Challenge does not exist")
if race.sessiontype != 'race': # pragma: no cover
url = reverse(virtualevent_view,kwargs={'id':id})
messages.error(request,"This challenge doesn't have map data")
return HttpResponseRedirect(request)
results = VirtualRaceResult.objects.filter(
race=race,
workoutid__isnull=False,
).order_by("distance","duration")
workoutids = [result.workoutid for result in results]
startenddict = {}
if race.sessiontype == 'race':
for result in results:
startenddict[result.workoutid] = (result.startsecond,result.endsecond)
if len(workoutids) == 0: # pragma: no cover
url = reverse('virtualevent_view',
kwargs={
'id':race.id,
})
messages.info(request,'There are no results to display')
return HttpResponseRedirect(url)
workouts = []
for id in workoutids:
try:
workouts.append(Workout.objects.get(id=id))
except Workout.DoesNotExist: # pragma: no cover
pass
labeldict = {
int(w.id): w.__str__() for w in workouts
}
script,div = leaflet_chart_compare(race.course,workoutids,
labeldict=labeldict,
startenddict=startenddict)
breadcrumbs = [
{
'url': reverse('virtualevents_view'),
'name': 'Challenges'
},
{
'url':reverse('virtualevent_view',
kwargs={
'id':race.id,
}
),
'name': race.name
},
{
'url':reverse('virtualevent_mapcompare_view',
kwargs={
'id':race.id,
}
),
'name': 'Course Compare'
}
]
return render(request,'mapcompare.html',
{'mapscript':script,
'mapdiv':div,
'breadcrumbs':breadcrumbs,
'rower':r,
'race':race,
'results':results,
'active':'nav-racing',
'teamid':0,
'teams':[]
})
def course_compare_view(request,id=0):
results = []
promember = 0
if not request.user.is_anonymous:
r = getrower(request.user)
result = request.user.is_authenticated and ispromember(request.user)
if result:
promember=1
else: # pragma: no cover
r = None
try:
course = GeoCourse.objects.get(id=id)
except GeoCourse.DoesNotExist: # pragma: no cover
raise Http404("Course does not exist")
script,div = course_map(course)
resultobj = VirtualRaceResult
results = resultobj.objects.filter(
course=course,
workoutid__isnull=False,
coursecompleted=True,
).order_by("duration","-distance")
workoutids = [result.workoutid for result in results]
startenddict = {}
for result in results:
startenddict[result.workoutid] = (result.startsecond,result.endsecond)
if len(workoutids) == 0: # pragma: no cover
url = reverse('course_view',
kwargs={
'id':course.id,
})
messages.info(request,'There are no results to display')
return HttpResponseRedirect(url)
if request.method == 'GET':
xparam = 'distance'
yparam = 'pace'
plottype = 'line'
request.session['ids'] = workoutids
request.session['plottype'] = plottype
request.session['xparam'] = xparam
request.session['yparam'] = yparam
workouts = []
for id in workoutids:
try:
workouts.append(Workout.objects.get(id=id))
except Workout.DoesNotExist: # pragma: no cover
pass
form = WorkoutMultipleCompareForm()
form.fields["workouts"].queryset = Workout.objects.filter(id__in=workoutids)
form.fields["workouts"].initial = Workout.objects.filter(id__in=workoutids)
labeldict = {
int(w.id): w.__str__() for w in workouts
}
chartform = ChartParamChoiceForm(
initial = {
'xparam':xparam,
'yparam':yparam,
'plottype':plottype,
'teamid':0
}
)
if request.method == 'POST' and 'workouts' in request.POST:
form = WorkoutMultipleCompareForm(request.POST)
form.fields["workouts"].queryset = Workout.objects.filter(id__in=workoutids)
chartform = ChartParamChoiceForm(request.POST)
if form.is_valid() and chartform.is_valid():
cd = form.cleaned_data
workouts = cd['workouts']
workoutids = [w.id for w in workouts]
xparam = chartform.cleaned_data['xparam']
yparam = chartform.cleaned_data['yparam']
plottype = chartform.cleaned_data['plottype']
teamid = chartform.cleaned_data['teamid']
ids = [int(w.id) for w in workouts]
request.session['ids'] = ids
elif request.method == 'POST':
form = WorkoutMultipleCompareForm()
form.fields["workouts"].queryset = Workout.objects.filter(id__in=workoutids)
request.session['ids'] = workoutids
chartform = ChartParamChoiceForm(request.POST)
if chartform.is_valid():
xparam = chartform.cleaned_data['xparam']
yparam = chartform.cleaned_data['yparam']
plottype = chartform.cleaned_data['plottype']
teamid = chartform.cleaned_data['teamid']
try:
workoutids = request.session['ids']
except KeyError: # pragma: no cover
pass
workouts = []
for id in workoutids:
try:
workouts.append(Workout.objects.get(
id=id))
except Workout.DoesNotExist: # pragma: no cover
pass
labeldict = {
int(w.id): w.__str__() for w in workouts
}
res = 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 = [
{
'url': reverse('courses_view'),
'name': 'Courses'
},
{
'url':reverse('course_view',
kwargs={
'id':course.id,
}
),
'name': course.name
},
{
'url':reverse('virtualevent_compare_view',
kwargs={
'id':course.id,
}
),
'name': 'Compare'
}
]
return render(request,'multicompare.html',
{'interactiveplot':script,
'the_div':div,
'breadcrumbs':breadcrumbs,
'rower':r,
'course':course,
'results':results,
'active':'nav-racing',
'promember':promember,
'teamid':0,
'chartform':chartform,
'form':form,
'teams':[]
})
def virtualevent_compare_view(request,id=0):
results = []
promember = 0
if not request.user.is_anonymous:
r = getrower(request.user)
result = request.user.is_authenticated and ispromember(request.user)
if result:
promember=1
else: # pragma: no cover
r = None
try:
race = VirtualRace.objects.get(id=id)
except VirtualRace.DoesNotExist: # pragma: no cover
raise Http404("Virtual Challenge does not exist")
if race.sessiontype == 'race':
script,div = course_map(race.course)
resultobj = VirtualRaceResult
else: # pragma: no cover
script = ''
div = ''
resultobj = IndoorVirtualRaceResult
results = resultobj.objects.filter(
race=race,
workoutid__isnull=False,
coursecompleted=True,
).order_by("duration","-distance")
workoutids = [result.workoutid for result in results]
startenddict = {}
if race.sessiontype == 'race':
for result in results:
startenddict[result.workoutid] = (result.startsecond,result.endsecond)
if len(workoutids) == 0: # pragma: no cover
url = reverse('virtualevent_view',
kwargs={
'id':race.id,
})
messages.info(request,'There are no results to display')
return HttpResponseRedirect(url)
if request.method == 'GET':
xparam = 'time'
if race.sessionmode == 'distance':
xparam = 'cumdist'
yparam = 'pace'
plottype = 'line'
request.session['ids'] = workoutids
request.session['plottype'] = plottype
request.session['xparam'] = xparam
request.session['yparam'] = yparam
workouts = []
for id in workoutids:
try:
workouts.append(Workout.objects.get(id=id))
except Workout.DoesNotExist: # pragma: no cover
pass
form = WorkoutMultipleCompareForm()
form.fields["workouts"].queryset = Workout.objects.filter(id__in=workoutids)
form.fields["workouts"].initial = Workout.objects.filter(id__in=workoutids)
labeldict = {
int(w.id): w.__str__() for w in workouts
}
chartform = ChartParamChoiceForm(
initial = {
'xparam':xparam,
'yparam':yparam,
'plottype':plottype,
'teamid':0
}
)
if request.method == 'POST' and 'workouts' in request.POST:
form = WorkoutMultipleCompareForm(request.POST)
form.fields["workouts"].queryset = Workout.objects.filter(id__in=workoutids)
chartform = ChartParamChoiceForm(request.POST)
if form.is_valid() and chartform.is_valid():
cd = form.cleaned_data
workouts = cd['workouts']
workoutids = [w.id for w in workouts]
xparam = chartform.cleaned_data['xparam']
yparam = chartform.cleaned_data['yparam']
plottype = chartform.cleaned_data['plottype']
teamid = chartform.cleaned_data['teamid']
ids = [int(w.id) for w in workouts]
request.session['ids'] = ids
elif request.method == 'POST':
form = WorkoutMultipleCompareForm()
form.fields["workouts"].queryset = Workout.objects.filter(id__in=workoutids)
request.session['ids'] = workoutids
chartform = ChartParamChoiceForm(request.POST)
if chartform.is_valid():
xparam = chartform.cleaned_data['xparam']
yparam = chartform.cleaned_data['yparam']
plottype = chartform.cleaned_data['plottype']
teamid = chartform.cleaned_data['teamid']
try:
workoutids = request.session['ids']
except KeyError: # pragma: no cover
pass
workouts = []
for id in workoutids:
try:
workouts.append(Workout.objects.get(
id=id))
except Workout.DoesNotExist: # pragma: no cover
pass
labeldict = {
int(w.id): w.__str__() for w in workouts
}
res = 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 = [
{
'url': reverse('virtualevents_view'),
'name': 'Challenges'
},
{
'url':reverse('virtualevent_view',
kwargs={
'id':race.id,
}
),
'name': race.name
},
{
'url':reverse('virtualevent_compare_view',
kwargs={
'id':race.id,
}
),
'name': 'Compare'
}
]
return render(request,'multicompare.html',
{'interactiveplot':script,
'the_div':div,
'breadcrumbs':breadcrumbs,
'rower':r,
'race':race,
'results':results,
'active':'nav-racing',
'promember':promember,
'teamid':0,
'chartform':chartform,
'form':form,
'teams':[]
})
@permission_required('plannedsession.view_session',
fn=get_session_by_pk,raise_exception=True)
def plannedsession_compare_view(request,id=0,userid=0):
r = getrequestrower(request,userid=userid)
try:
ps = PlannedSession.objects.get(id=id)
except PlannedSession.DoesNotExist: # pragma: no cover
raise Http404("Planned session does not exist")
workouts = Workout.objects.filter(plannedsession=ps)
ids = [int(w.id) for w in workouts]
labeldict = {
int(w.id): w.__str__() for w in workouts
}
xparam = 'time'
yparam = 'hr'
plottype = 'line'
request.session['ids'] = ids
request.session['xparam'] = xparam
request.session['yparam'] = yparam
request.session['plottype'] = plottype
request.session['ps'] = ps.id
if ids:
url = reverse('multi_compare_view',
kwargs={'userid':userid,'id':encoder.encode_hex(ids[0])})
else:
url = reverse('plannedsession_view',kwargs={'id':ps.id})
return HttpResponseRedirect(url)
# Team comparison
@login_required()
def multi_compare_view(request,id=0,userid=0):
promember=0
if not request.user.is_anonymous:
r = getrower(request.user)
result = request.user.is_authenticated and ispromember(request.user)
if result:
promember=1
if request.method == 'POST' and 'workouts' in request.POST:
form = WorkoutMultipleCompareForm(request.POST)
chartform = ChartParamChoiceForm(request.POST)
if form.is_valid() and chartform.is_valid():
cd = form.cleaned_data
workouts = cd['workouts']
xparam = chartform.cleaned_data['xparam']
yparam = chartform.cleaned_data['yparam']
plottype = chartform.cleaned_data['plottype']
teamid = chartform.cleaned_data['teamid']
ids = [int(w.id) for w in workouts]
request.session['ids'] = ids
labeldict = {
int(w.id): w.__str__() for w in workouts
}
else: # pragma: no cover
return HttpResponse("Form is not valid")
elif request.method == 'POST' and 'ids' in request.session:
chartform = ChartParamChoiceForm(request.POST)
if chartform.is_valid():
xparam = chartform.cleaned_data['xparam']
yparam = chartform.cleaned_data['yparam']
plottype = chartform.cleaned_data['plottype']
teamid = chartform.cleaned_data['teamid']
ids = request.session['ids']
request.session['ids'] = ids
workouts = []
for id in ids:
try:
workouts.append(Workout.objects.get(id=id))
except Workout.DoesNotExist: # pragma: no cover
pass
labeldict = {
int(w.id): w.__str__() for w in workouts
}
elif 'ids' in request.session and 'plottype' in request.session:
xparam = request.session['xparam']
yparam = request.session['yparam']
plottype = request.session['plottype']
teamid = 0
ids = request.session['ids']
workouts = []
for id in ids:
try:
workouts.append(Workout.objects.get(id=id))
except Workout.DoesNotExist: # pragma: no cover
pass
labeldict = {
int(w.id): w.__str__() for w in workouts
}
chartform = ChartParamChoiceForm(
initial = {
'xparam':xparam,
'yparam':yparam,
'plottype':plottype,
'teamid':teamid
}
)
else:
url = reverse('team_comparison_select',
kwargs={
'id':id,
'teamid':0})
return HttpResponseRedirect(url)
res = interactive_multiple_compare_chart(ids,xparam,yparam,
promember=promember,
plottype=plottype,
labeldict=labeldict)
script = res[0]
div = res[1]
errormessage = res[3]
if errormessage != '': # pragma: no cover
messages.error(request,errormessage)
r = getrower(request.user)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':reverse('team_comparison_select',kwargs={'teamid':teamid}),
'name': 'Compare Select'
},
{
'url':reverse('multi_compare_view'),
'name': 'Comparison Chart'
}
]
if 'ps' in request.session: # pragma: no cover
ps = PlannedSession.objects.get(id=int(request.session['ps']))
breadcrumbs = [
{
'url': reverse('plannedsessions_view',
kwargs={'userid':userid}),
'name': 'Sessions'
},
{
'url':reverse('plannedsession_view',
kwargs={
'userid':userid,
'id':ps.id,
}
),
'name': ps.id
},
{
'url':reverse('plannedsession_compare_view',
kwargs={
'userid':userid,
'id':ps.id,
}
),
'name': 'Compare'
}
]
return render(request,'multicompare.html',
{'interactiveplot':script,
'the_div':div,
'breadcrumbs':breadcrumbs,
'rower':r,
'active':'nav-workouts',
'promember':promember,
'teamid':teamid,
'chartform':chartform,
'teams':get_my_teams(request.user),
})
# List Workouts
@login_required()
def workouts_view(request,message='',successmessage='',
teamid=0,rankingonly=False,rowerid=0,userid=0):
startdate,enddate = get_dates_timeperiod(request,defaulttimeperiod='lastyear')
request.session['referer'] = absolute(request)['PATH']
r = getrequestrower(request,rowerid=rowerid,userid=userid)
# check if access is allowed
startdate = datetime.datetime.combine(startdate,datetime.time())
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
query = None
if request.method == 'POST':
dateform = DateRangeForm(request.POST)
searchform = SearchForm(request.POST)
if dateform.is_valid(): # pragma: no cover
startdate = dateform.cleaned_data['startdate']
enddate = dateform.cleaned_data['enddate']
if searchform.is_valid():
query = searchform.cleaned_data['q']
else:
dateform = DateRangeForm(initial={
'startdate':startdate,
'enddate':enddate,
})
usertimezone = pytz.timezone(r.defaulttimezone)
startdate = datetime.datetime.combine(startdate,datetime.time()).astimezone(usertimezone)
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59)).astimezone(usertimezone)
#enddate = enddate+datetime.timedelta(days=1)
if enddate < startdate: # pragma: no cover
s = enddate
enddate = startdate
startdate = s
startdatestring = startdate.strftime('%Y-%m-%d')
enddatestring = enddate.strftime('%Y-%m-%d')
# start date for the small graph
activity_startdate = enddate-datetime.timedelta(days=15)
try:
if enddate > timezone.now():
activity_enddate = timezone.now()
activity_enddate = activity_enddate.replace(hour=23,minute=59,second=59).astimezone(usertimezone)
activity_startdate = activity_enddate-datetime.timedelta(days=15)
activity_startdate = activity_startdate.replace(hour=0,minute=0,second=0)
else: # pragma: no cover
activity_enddate = enddate
except (ValueError, AttributeError): # pragma: no cover
activity_enddate = enddate
g_startdate = activity_startdate
g_enddate = activity_enddate
if teamid:
try:
theteam = Team.objects.get(id=teamid)
except Team.DoesNotExist: # pragma: no cover
raise Http404("Team doesn't exist")
if theteam.viewing == 'allmembers' or theteam.manager == request.user:
workouts = Workout.objects.filter(
team=theteam,
startdatetime__gte=startdate,
startdatetime__lte=enddate,
privacy='visible').order_by("-date","-starttime")
g_workouts = Workout.objects.filter(
team=theteam,
startdatetime__gte=activity_startdate,
startdatetime__lte=activity_enddate,
duplicate=False,
privacy='visible').order_by("-date", "-starttime")
elif theteam.viewing == 'coachonly': # pragma: no cover
workouts = Workout.objects.filter(
team=theteam,user=r,
startdatetime__gte=startdate,
startdatetime__lte=enddate,
privacy='visible').order_by("-startdatetime")
g_workouts = Workout.objects.filter(
team=theteam,user=r,
startdatetime__gte=activity_startdate,
startdatetime__lte=activity_enddate,
duplicate=False,
privacy='visible').order_by("-startdatetime")
elif request.user != r.user:
theteam = None
workouts = Workout.objects.filter(
user=r,
startdatetime__gte=startdate,
startdatetime__lte=enddate,
privacy='visible').order_by("-date", "-starttime")
g_workouts = Workout.objects.filter(
user=r,
startdatetime__gte=activity_startdate,
startdatetime__lte=activity_enddate,
duplicate=False,
privacy='visible').order_by("-startdatetime")
else:
theteam = None
workouts = Workout.objects.filter(
user=r,
startdatetime__gte=startdate,
startdatetime__lte=enddate).order_by("-date", "-starttime")
g_workouts = Workout.objects.filter(
user=r,
duplicate=False,
startdatetime__gte=activity_startdate,
startdatetime__lte=activity_enddate).order_by("-startdatetime")
if g_workouts.count() == 0:
g_workouts = Workout.objects.filter(
user=r,
startdatetime__gte=timezone.now()-timedelta(days=15)).order_by("-startdatetime")
g_enddate = timezone.now()
g_startdate = (timezone.now()-timedelta(days=15))
if rankingonly:
workouts = workouts.exclude(rankingpiece=False)
workoutsnohr = workouts.exclude(averagehr__isnull=False)
for w in workoutsnohr: # pragma: no cover
res = 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(
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})
else:
searchform = SearchForm()
paginator = Paginator(workouts,20) # show 25 workouts per page
page = request.GET.get('page',1)
try:
workouts = paginator.page(page)
except EmptyPage: # pragma: no cover
workouts = paginator.page(paginator.num_pages)
today = timezone.now()
announcements = SiteAnnouncement.objects.filter(
expires__gte=today
).order_by(
"-created",
"-id"
)
if theteam:
stack='rower'
else:
stack='type'
yaxis = request.GET.get('yaxis','duration')
if yaxis not in ['duration','trimp','rscore']: # pragma: no cover
yaxis = 'duration'
script,div = interactive_activitychart(g_workouts,
g_startdate,
g_enddate,
stack=stack,
yaxis=yaxis)
totalmeters,totalhours, totalminutes,total_seconds = get_totals(g_workouts)
totalminutes = '{totalminutes:02d}'.format(totalminutes=totalminutes)
messages.info(request,successmessage)
messages.error(request,message)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
]
timeperiod = startdate.strftime('%Y-%m-%d')+'/'+enddate.strftime('%Y-%m-%d')
return render(request, 'list_workouts.html',
{'workouts': workouts,
'active': 'nav-workouts',
'rower':r,
'searchform':searchform,
'breadcrumbs':breadcrumbs,
'dateform':dateform,
'startdate':startdate,
'enddate':enddate,
'announcements':announcements[0:4],
'team':theteam,
'rankingonly':rankingonly,
'teams':get_my_teams(request.user),
'interactiveplot':script,
'the_div':div,
'timeperiod':timeperiod,
'totalmeters':totalmeters,
'totalminutes':totalminutes,
'totalhours':totalhours,
})
# List of workouts to compare a selected workout to
@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. If you are already a Pro user, please log in to access this functionality",
redirect_field_name=None)
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_fusion_list(request,id=0,
startdate=timezone.now()-datetime.timedelta(days=365),
enddate=timezone.now()):
r = getrequestrower(request)
u = r.user
if request.method == 'POST': # pragma: no cover
dateform = DateRangeForm(request.POST)
if dateform.is_valid():
startdate = dateform.cleaned_data['startdate']
enddate = dateform.cleaned_data['enddate']
else:
dateform = DateRangeForm(initial={
'startdate':startdate,
'enddate':enddate,
})
startdate = datetime.datetime.combine(startdate,datetime.time())
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
#enddate = enddate+datetime.timedelta(days=1)
startdate = arrow.get(startdate).datetime
enddate = arrow.get(enddate).datetime
if enddate < startdate: # pragma: no cover
s = enddate
enddate = startdate
startdate = s
if id:
theid = encoder.decode_hex(id)
w = get_workoutuser(id, request)
r = w.user
workouts = Workout.objects.filter(user=r,
startdatetime__gte=startdate,
startdatetime__lte=enddate).order_by("-date", "-starttime").exclude(id=theid)
query = request.GET.get('q')
if query: # pragma: no cover
query_list = query.split()
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})
else:
searchform = SearchForm()
paginator = Paginator(workouts,15) # show 25 workouts per page
page = request.GET.get('page',1)
try:
workouts = paginator.page(page)
except EmptyPage: # pragma: no cover
workouts = paginator.page(paginator.num_pages)
row = get_workoutuser(id, request)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,encoder.encode_hex(row.id)),
'name': row.name
},
{
'url':reverse('workout_fusion_list',kwargs={'id':id}),
'name': 'Sensor Fusion'
}
]
return render(request, 'fusion_list.html',
{'id':id,
'workout':row,
'rower':r,
'searchform':searchform,
'active':'nav-workouts',
'breadcrumbs':breadcrumbs,
'workouts': workouts,
'last_name':u.last_name,
'first_name':u.first_name,
'dateform':dateform,
'startdate':startdate,
'enddate':enddate,
'teams':get_my_teams(request.user),
})
# Basic view of workout
@permission_required('workout.view_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_view(request,id=0,raceresult=0,sessionresult=0,nocourseraceresult=0):
request.session['referer'] = absolute(request)['PATH']
if not request.user.is_anonymous:
rower = getrower(request.user)
else:
rower = None
# get row
row = get_workout_by_opaqueid(request,id)
f1 = row.csvfilename
rowdata = rdata(csvfile=f1)
summary = row.summary
comments = WorkoutComment.objects.filter(workout=row)
aantalcomments = len(comments)
g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime")
for i in g: # pragma: no cover
try:
width,height = Image.open(i.filename).size
i.width = width
i.height = height
i.save()
except:
pass
# get raceresult or session result
intervaldata = {}
if sessionresult != 0: # pragma: no cover
try:
result = CourseTestResult.objects.get(id=sessionresult)
startsecond = result.startsecond
endsecond = result.endsecond
duration = row.duration
durationsecs = duration.hour*3600+duration.minute*60+duration.second
itime = [startsecond,endsecond-startsecond]
itype = [3,4]
intervaldata['itime'] = itime
intervaldata['itype'] = itype
vals, units, typ = rowdata.updateinterval_metric(' AverageBoatSpeed (m/s)',0.1,mode='larger',
debug=False,smoothwindow=15.,
activewindow = [startsecond,endsecond])
summary = rowdata.allstats()
except CourseTestResult.DoesNotExist:
pass
if nocourseraceresult != 0: # pragma: no cover
try:
result = IndoorVirtualRaceResult.objects.get(id=nocourseraceresult)
startsecond = result.startsecond
endsecond = result.endsecond
duration = row.duration
durationsecs = duration.hour*3600+duration.minute*60+duration.second
itime = [startsecond,endsecond-startsecond]
itype = [3,4]
intervaldata['itime'] = itime
intervaldata['itype'] = itype
vals, units, typ = rowdata.updateinterval_metric(' AverageBoatSpeed (m/s)',0.1,mode='larger',
debug=False,smoothwindow=15.,
activewindow = [startsecond,endsecond])
summary = rowdata.allstats()
except CourseTestResult.DoesNotExist:
pass
if raceresult != 0: # pragma: no cover
try:
result = VirtualRaceResult.objects.get(id=raceresult)
startsecond = result.startsecond
endsecond = result.endsecond
duration = row.duration
durationsecs = duration.hour*3600+duration.minute*60+duration.second
itime = [startsecond,endsecond-startsecond]
itype = [3,4]
intervaldata['itime'] = itime
intervaldata['itype'] = itype
vals, units, typ = rowdata.updateinterval_metric(' AverageBoatSpeed (m/s)',0.1,mode='larger',
debug=False,smoothwindow=15.,
activewindow = [startsecond,endsecond])
summary = rowdata.allstats()
except VirtualRaceResult.DoesNotExist:
pass
# create interactive plot
res = interactive_chart(encoder.decode_hex(id),intervaldata=intervaldata)
script = res[0]
div = res[1]
# create map
hascoordinates = 1
if rowdata != 0:
try:
latitude = rowdata.df[' latitude']
if not latitude.std(): # pragma: no cover
hascoordinates = 0
except (KeyError,AttributeError):
hascoordinates = 0
else: # pragma: no cover
hascoordinates = 0
courses = []
if hascoordinates:
if intervaldata: # pragma: no cover
rowdata.df['reltime'] = rowdata.df['TimeStamp (sec)']-rowdata.df.loc[0,'TimeStamp (sec)']
mask = (rowdata.df['reltime']>startsecond) & (rowdata.df['reltime']<endsecond)
latitudes = rowdata.df.loc[mask,' latitude']
longitudes = rowdata.df.loc[mask,' longitude']
else:
latitudes = rowdata.df[' latitude']
longitudes = rowdata.df[' longitude']
mapscript,mapdiv = leaflet_chart(latitudes,longitudes,row.name,raceresult=raceresult)
records = VirtualRaceResult.objects.filter(workoutid=row.id,userid=row.user.user.id,coursecompleted=True)
if records.count()>0: # pragma: no cover
courses = list(set([record.course for record in records]))
else:
mapscript = ""
mapdiv = ""
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':reverse('workout_view',kwargs={'id':id}),
'name': row.name,
}
]
u = row.user.user
recordsindoor = IndoorVirtualRaceResult.objects.filter(workoutid= row.id)
records = VirtualRaceResult.objects.filter(workoutid= row.id)
return render(request, 'workout_view.html',
{'workout':row,
'rower':rower,
'breadcrumbs':breadcrumbs,
'active':'nav-workouts',
'graphs':g,
'last_name':u.last_name,
'records':records,
'summary':summary,
'recordsindoor':recordsindoor,
'first_name':u.first_name,
'interactiveplot':script,
'aantalcomments':aantalcomments,
'mapscript':mapscript,
'mapdiv':mapdiv,
'teams':get_my_teams(request.user),
'courses':courses,
'the_div':div})
# Resets stroke data to raw data (pace)
@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. If you are already a Pro user, please log in to access this functionality",
redirect_field_name=None)
def workout_undo_smoothenpace_view(
request,id=0,message="",successmessage=""
):
row = get_workoutuser(id, request)
r = getrower(request.user)
filename = row.csvfilename
row = rdata(csvfile=filename)
if row == 0: # pragma: no cover
return HttpResponse("Error: CSV Data File Not Found")
if 'originalvelo' in row.df:
velo = row.df['originalvelo'].values
row.df[' Stroke500mPace (sec/500m)'] = 500./velo
row.write_csv(filename,gzip=True)
dataprep.update_strokedata(encoder.decode_hex(id),row.df)
url = reverse(r.defaultlandingpage,
kwargs = {
'id':id,
}
)
return HttpResponseRedirect(url)
# Data smoothing of pace 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. If you are already a Pro user, please log in to access this functionality",
redirect_field_name=None)
def workout_smoothenpace_view(request,id=0,message="",successmessage=""):
row = get_workoutuser(id, request)
previousurl = request.META.get('HTTP_REFERER')
r = getrower(request.user)
filename = row.csvfilename
row = rdata(csvfile=filename)
if row == 0: # pragma: no cover
return HttpResponse("Error: CSV Data File Not Found")
try:
pace = row.df[' Stroke500mPace (sec/500m)'].values
velo = 500./pace
except KeyError:
messages.error(request,'Unable to find the data')
if previousurl: # pragma: no cover
url = previousurl
else:
url = reverse(r.defaultlandingpage,
kwargs = {
'id':id,
}
)
return HttpResponseRedirect(url)
if not 'originalvelo' in row.df:
row.df['originalvelo'] = velo
velo2 = stravastuff.ewmovingaverage(velo,5)
pace2 = 500./abs(velo2)
row.df[' Stroke500mPace (sec/500m)'] = pace2
row.df = row.df.fillna(0)
row.write_csv(filename,gzip=True)
dataprep.update_strokedata(encoder.decode_hex(id),row.df)
messages.info(request,'A smoothening filter was applied to your pace data')
if previousurl: # pragma: no cover
url = previousurl
else:
url = reverse(r.defaultlandingpage,
kwargs = {
'id':id,
}
)
return HttpResponseRedirect(url)
# Get weather for given location and date/time
@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_downloadwind_view(request,id=0,
airportcode=None,
message="",successmessage=""):
row = get_workoutuser(id, request)
f1 = row.csvfilename
# create bearing
rowdata = rdata(csvfile=f1)
if rowdata == 0: # pragma: no cover
return HttpResponse("Error: CSV Data File Not Found")
try:
bearing = rowdata.df.loc[:,'bearing'].values
except KeyError:
rowdata.add_bearing()
rowdata.write_csv(f1,gzip=True)
# get wind
try:
avglat = rowdata.df[' latitude'].mean()
avglon = rowdata.df[' longitude'].mean()
avgtime = int(rowdata.df['TimeStamp (sec)'].mean()-rowdata.df.loc[:,'TimeStamp (sec)'].iloc[0])
startdatetime = dateutil.parser.parse("{}, {}".format(row.date,
row.starttime))
starttimeunix = int(arrow.get(row.startdatetime).timestamp())
#starttimeunix = int(mktime(startdatetime.utctimetuple()))
avgtime = starttimeunix+avgtime
winddata = get_wind_data(avglat,avglon,avgtime)
windspeed = winddata[0]
windbearing = winddata[1]
message = winddata[2]
if message is not None:
try:
row.notes += "\n"+message
except TypeError: # pragma: no cover
if message is not None and row.notes is not None:
row.notes += message
row.save()
rowdata.add_wind(windspeed,windbearing)
rowdata.write_csv(f1,gzip=True)
messages.info(request,message)
kwargs = {
'id':id}
url = reverse('workout_wind_view',kwargs=kwargs)
response = HttpResponseRedirect(url)
except KeyError:
message = "No latitude/longitude data"
messages.error(request,message)
kwargs = {
'id':id
}
url = reverse('workout_wind_view',kwargs=kwargs)
response = HttpResponseRedirect(url)
return response
# Get weather for given location and date/time
@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_downloadmetar_view(request,id=0,
airportcode=None,
message="",successmessage=""):
row = get_workoutuser(id, request)
f1 = row.csvfilename
# create bearing
rowdata = rdata(csvfile=f1)
if rowdata == 0: # pragma: no cover
return HttpResponse("Error: CSV Data File Not Found")
try:
bearing = rowdata.df.loc[:,'bearing'].values
except KeyError:
rowdata.add_bearing()
rowdata.write_csv(f1,gzip=True)
# get wind
try:
avglat = rowdata.df[' latitude'].mean()
avglon = rowdata.df[' longitude'].mean()
airportcode = get_airport_code(avglat,avglon)[0]
avgtime = int(rowdata.df['TimeStamp (sec)'].mean()-rowdata.df.loc[:,'TimeStamp (sec)'].iloc[0])
startdatetime = dateutil.parser.parse("{}, {}".format(row.date,
row.starttime))
starttimeunix = arrow.get(row.startdatetime).timestamp()
#starttimeunix = int(mktime(startdatetime.utctimetuple()))
avgtime = starttimeunix +avgtime
winddata = get_metar_data(airportcode,avgtime)
windspeed = winddata[0]
windbearing = winddata[1]
message = winddata[2]
try:
row.notes += "\n"+message
except TypeError: # pragma: no cover
if message is not None:
try:
row.notes += message
except TypeError:
pass
row.save()
rowdata.add_wind(windspeed,windbearing)
rowdata.write_csv(f1,gzip=True)
messages.info(request,message)
kwargs = {
'id':id}
url = reverse('workout_wind_view',kwargs=kwargs)
response = HttpResponseRedirect(url)
except KeyError:
message = "No latitude/longitude data"
messages.error(request,message)
kwargs = {
'id':id
}
url = reverse('workout_wind_view',kwargs=kwargs)
response = HttpResponseRedirect(url)
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:
bearing = 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)
response = 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)
response = 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():
#quick_calc = form.cleaned_data['quick_calc']
#go_service = form.cleaned_data['go_service']
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:
vstream = rowdata.df['vstream']
except KeyError:
rowdata.add_stream(0)
rowdata.write_csv(f1,gzip=True)
try:
bearing = rowdata.df['bearing']
except KeyError:
rowdata.add_bearing()
rowdata.write_csv(f1,gzip=True)
try:
vwind = 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)
def instroke_view(request,id=0):
w = get_workoutuser(id, request)
r = getrower(request.user)
mayedit = 1
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,id),
'name': w.name
},
{
'url':reverse('instroke_view',kwargs={'id':id}),
'name': 'In-Stroke Metrics'
}
]
# form = WorkoutForm(instance=row)
g = GraphImage.objects.filter(workout=w).order_by("-creationdatetime")
# check if user is owner of this workout
rowdata = rrdata(csvfile=w.csvfilename)
try:
instrokemetrics = rowdata.get_instroke_columns()
instrokemetrics = [m for m in instrokemetrics if not m in nometrics]
except AttributeError: # pragma: no cover
instrokemetrics = []
return render(request,
'instroke.html',
{'workout':w,
'rower':r,
'active':'nav-workouts',
'breadcrumbs':breadcrumbs,
'mayedit':mayedit,
'teams':get_my_teams(request.user),
'instrokemetrics':instrokemetrics,
})
# generate instroke chart
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def instroke_chart(request,id=0,metric=''): # pragma: no cover
w = get_workoutuser(id, request)
rowdata = rrdata(csvfile=w.csvfilename)
instrokemetrics = rowdata.get_instroke_columns()
if metric in instrokemetrics:
f1 = w.csvfilename[6:-4]
timestr = strftime("%Y%m%d-%H%M%S")
imagename = f1+timestr+'.png'
fullpathimagename = 'static/plots/'+imagename
u = w.user.user
r = getrower(u)
title = w.name
fig1 = rowdata.get_plot_instroke(metric)
canvas = FigureCanvas(fig1)
canvas.print_figure('static/plots/'+imagename)
plt.close(fig1)
fig1.clf()
gc.collect()
try:
width,height = Image.open(fullpathimagename).size
except:
width = 1200
height = 600
imgs = GraphImage.objects.filter(workout=w)
if imgs.count() < 7:
i = GraphImage(workout=w,
creationdatetime=timezone.now(),
filename=fullpathimagename,
width=width,height=height)
i.save()
else:
messages.error(request,'You have reached the maximum number of static images for this workout. Delete an image first')
r = getrower(request.user)
url = reverse(r.defaultlandingpage,
kwargs = {
'id':id,
})
return HttpResponseRedirect(url)
# erase column
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_erase_column_view(request, id=0,column=''):
r = getrower(request.user)
w = get_workoutuser(id,request)
protected = ['time','pace','velo','cumdist','ftime','fpace',]
if column in protected: # pragma: no cover
messages.error(request,'You cannot erase this protected column')
url = reverse('workout_data_view',kwargs={
'id':encoder.encode_hex(w.id)
})
return HttpResponseRedirect(url)
try:
data = dataprep.getsmallrowdata_db([column],ids=[w.id])
except: # pragma: no cover
messages.error(request,'Invalid column')
url = reverse('workout_data_view',kwargs={
'id':encoder.encode_hex(w.id)
})
return HttpResponseRedirect(url)
try:
cdata = data[column]
except KeyError: # pragma: no cover
url = reverse('workout_data_view',kwargs={
'id':encoder.encode_hex(w.id)
})
return HttpResponseRedirect(url)
if not column: # pragma: no cover
url = reverse('workout_data_view',kwargs={
'id':encoder.encode_hex(w.id)
})
return HttpResponseRedirect(url)
if request.method == 'POST':
mms = {}
for m in rowingmetrics:
mms[m[0]] = m[1]
try:
defaultvalue = mms[column]['default']
except KeyError:
if not mms[column]['null']: # pragma: no cover
messages.error(request,'You cannot erase this protected column')
url = reverse('workout_data_view',kwargs={
'id':encoder.encode_hex(w.id)
})
return HttpResponseRedirect(url)
defaultvalue = 0
try:
columnl = dataprep.columndict[column]
except KeyError: # pragma: no cover
messages.error(request,'You cannot erase this column')
url = reverse('workout_data_view',kwargs={
'id':encoder.encode_hex(w.id)
})
return HttpResponseRedirect(url)
row,workout = dataprep.getrowdata(id=w.id)
row.df[columnl] = defaultvalue
os.remove(w.csvfilename+'.gz')
row.write_csv(w.csvfilename,gzip=True)
row,workout = dataprep.getrowdata(id=w.id)
datadf = dataprep.dataprep(row.df,id=w.id)
if column == 'hr':
w.hrtss = 0
w.trimp = 0
w.save()
if column == 'power': # pragma: no cover
w.rscore = 0
w.normp = 0
w.goldmedalstandard = -1
w.goldmedalseconds = 0
w.save()
trimp,hrtss = dataprep.workout_trimp(w,reset=True)
rscore,normp = dataprep.workout_rscore(w,reset=True)
goldstandard,goldstandardduration = dataprep.workout_goldmedalstandard(w,reset=True)
messages.info(request,'Data for column '+column+' have been erased')
url = reverse('workout_data_view',kwargs={
'id':encoder.encode_hex(w.id)
})
return HttpResponseRedirect(url)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,id),
'name': w.name
},
{
'url':reverse('workout_data_view',kwargs={'id':id}),
'name': 'Data Explorer'
}
]
return render(request,
'workout_erase_column.html',
{
'column':column,
'teams':get_my_teams(request.user),
'workout': w,
'breadcrumbs': breadcrumbs,
}
)
# data explorer
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_data_view(request, id=0):
r = getrower(request.user)
w = get_workoutuser(id, request)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,id),
'name': w.name
},
{
'url':reverse('workout_data_view',kwargs={'id':id}),
'name': 'Data Explorer'
}
]
datadf,row = dataprep.getrowdata_db(id=encoder.decode_hex(id))
try:
datadf.sort_values(['ftime'],inplace=True)
except KeyError:
pass
columns = datadf.columns.values
to_be_dropped = [
'id','time','hr_an','hr_at','hr_bottom','hr_max',
'hr_tr','hr_ut1','hr_ut2','x_right',
]
to_be_dropped = [c for c in to_be_dropped if c in columns]
datadf.drop(labels=to_be_dropped,inplace=True,axis=1)
cols = ['ftime','cumdist','fpace','spm',
'hr','power','driveenergy','drivelength','averageforce',
'peakforce','distance','drivespeed','workoutstate',
'catch','finish','peakforceangle','wash','slip','rhythm',
'effectiveangle','totalangle','distanceperstroke','velo']
cols = [c for c in cols if c in datadf.columns]
tcols = ['ftime','cumdist','fpace','spm','hr','power']
datadf = datadf[cols]
try:
datadf.loc[:,'hr'] = datadf['hr'].astype('int')
datadf.loc[:,'power'] = datadf['power'].astype('int')
datadf.loc[:,'distance'] = datadf['distance'].astype('int')
datadf.loc[:,'spm'] = 10*datadf['spm'].astype('int')/10.
except KeyError:
pass
if request.method == 'POST':
form = DataFrameColumnsForm(request.POST)
if form.is_valid():
tcols = form.cleaned_data['cols']
else:
form = DataFrameColumnsForm(initial = {'cols':tcols})
try:
datadf = datadf[tcols]
except KeyError: # pragma: no cover
# tcols = list(set(datadf.columns(tolist)+tcols))
try:
datadf = datadf[tcols]
datadf = datadf.fillna(value=0)
except KeyError:
pass
for col in cols:
try:
if datadf[col].mean() == 0 and datadf[col].std() == 0:
datadf.drop(labels=[col],axis=1,inplace=True)
except (TypeError,KeyError):
pass
# pd.set_option('display.width', 1000)
#pd.set_option('colheader_justify', 'left')
htmltable = datadf.to_html(
bold_rows=True,
show_dimensions=True,border=1,
classes=['pandastable'],justify='justify'
)
return render(request,
'workout_data.html',
{
'htmltable': htmltable,
'data':datadf,
'cols':datadf.columns,
'form':form,
'teams':get_my_teams(request.user),
'workout': w,
'breadcrumbs': breadcrumbs,
}
)
# Stats page
@permission_required('workout.view_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_stats_view(request,id=0,message="",successmessage=""):
r = getrower(request.user)
w = get_workout(id)
mayedit = 0
if request.user == w.user.user:
mayedit=1
if is_workout_user(request.user,w):
mayedit=1
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,id),
'name': w.name
},
{
'url':reverse('workout_stats_view',kwargs={'id':id}),
'name': 'Stats'
}
]
workstrokesonly = True
if request.method == 'POST' and 'workstrokesonly' in request.POST: # pragma: no cover
workstrokesonly = str2bool(request.POST['workstrokesonly'])
# prepare data frame
datadf,row = dataprep.getrowdata_db(id=encoder.decode_hex(id))
datadf = dataprep.clean_df_stats(datadf,workstrokesonly=workstrokesonly,
ignoreadvanced=False)
if datadf.empty:
datadf,row = dataprep.getrowdata_db(id=encoder.decode_hex(id))
datadf = dataprep.clean_df_stats(datadf,workstrokesonly=False,
ignoreadvanced=True)
workstrokesonly=False
if datadf.empty:
return HttpResponse("CSV data file not found")
#datadf['deltat'] = datadf['time'].diff()
workoutstateswork = [1,4,5,8,9,6,7]
workoutstatesrest = [3]
workoutstatetransition = [0,2,10,11,12,13]
# Create stats
stats = {}
fieldlist,fielddict = dataprep.getstatsfields()
try:
fielddict.pop('pace')
except KeyError: # pragma: no cover
pass
for field,verbosename in fielddict.items():
try:
thedict = {
'mean':datadf[field].mean(),
'wmean': wavg(datadf, field, 'deltat'),
'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
# additional non-automated stats
otherstats = {}
# Normalized power & TSS
tss,normp = dataprep.workout_rscore(w)
goldmedalstandard,goldmedalseconds = dataprep.workout_goldmedalstandard(w)
if not np.isnan(goldmedalstandard) and goldmedalstandard > 0:
otherstats['goldmedalstandard'] = {
'verbose_name': 'Gold Medal Standard',
'value': int(goldmedalstandard),
'unit': '%',
}
if not np.isnan(goldmedalseconds) and goldmedalseconds > 0:
otherstats['goldmedalseconds'] = {
'verbose_name': 'Gold Medal Standard Duration',
'value': utils.totaltime_sec_to_string(goldmedalseconds,shorten=True),
'unit': '',
}
if not np.isnan(tss) and tss != 0:
otherstats['tss'] = {
'verbose_name':'rScore',
'value':int(tss),
'unit':''
}
if not np.isnan(normp):
otherstats['np'] = {
'verbose_name':'rPower',
'value':int(10*normp)/10.,
'unit':'Watt'
}
# HR Drift
tmax = datadf['time'].max()
tmin = datadf['time'].min()
thalf = tmin+0.5*(tmax-tmin)
mask1 = datadf['time'] < thalf
mask2 = datadf['time'] > thalf
hr1 = datadf.loc[mask1,'hr'].mean()
hr2 = datadf.loc[mask2,'hr'].mean()
pwr1 = datadf.loc[mask1,'power'].mean()
pwr2 = datadf.loc[mask2,'power'].mean()
try:
hrdrift = ((pwr1/hr1)-(pwr2/hr2))/(pwr1/hr1)
hrdrift *= 100.
if not np.isnan(hrdrift):
try:
hrdrift = int(100*hrdrift)/100.
except: # pragma: no cover
hrdrift = 0
otherstats['hrdrift'] = {
'verbose_name': 'Heart Rate Drift',
'value': hrdrift,
'unit': '%',
}
except (ZeroDivisionError,ValueError): # pragma: no cover
pass
# TRIMP
trimp,hrtss = dataprep.workout_trimp(w)
otherstats['trimp'] = {
'verbose_name': 'TRIMP',
'value': trimp,
'unit': ''
}
otherstats['hrScore'] = {
'verbose_name': 'rScore (HR)',
'value': hrtss,
'unit':''
}
return render(request,
'workoutstats.html',
{
'stats':stats,
'teams':get_my_teams(request.user),
'workout':w,
'rower':r,
'mayedit':mayedit,
'breadcrumbs':breadcrumbs,
'active':'nav-workouts',
'workstrokesonly':workstrokesonly,
'cordict':cordict,
'otherstats':otherstats,
})
# Change default landing page
@login_required()
def workflow_default_view(request):
r = getrower(request.user)
if r.defaultlandingpage == 'workout_edit_view':
r.defaultlandingpage = 'workout_workflow_view'
else: # pragma: no cover
r.defaultlandingpage = 'workout_edit_view'
r.save()
url = reverse('workout_workflow_config2_view')
return HttpResponseRedirect(url)
# Workflow configuration
@login_required()
def workout_workflow_config2_view(request,userid=0):
request.session['referer'] = absolute(request)['PATH']
request.session[translation.LANGUAGE_SESSION_KEY] = USER_LANGUAGE
try:
workoutid = request.session['lastworkout']
except KeyError:
workoutid = 0
r = getrequestrower(request,userid=userid,notpermanent=True)
MiddlePanelFormSet = formset_factory(WorkFlowMiddlePanelElement,extra=1)
if request.method == 'POST':
middlepanel_formset = MiddlePanelFormSet(request.POST,
prefix='middlepanel')
newmiddlepanel = []
if middlepanel_formset.is_valid():
for form in middlepanel_formset:
value = form.cleaned_data.get('panel')
if value != 'None':
newmiddlepanel.append(value)
newmiddlepanel = [i for i in newmiddlepanel if i != None]
r.workflowmiddlepanel = newmiddlepanel
try:
r.save()
except IntegrityError: # pragma: no cover
messages.error(request,'Something went wrong')
middlepanelform_data = [{'panel':panel}
for panel in r.workflowmiddlepanel]
middlepanel_formset = MiddlePanelFormSet(initial=middlepanelform_data,
prefix='middlepanel')
tmplt = 'workflowconfig2.html'
return render(request,tmplt,
{
'rower':r,
'middlepanel_formset':middlepanel_formset,
'workoutid': workoutid,
})
# Workflow View
@login_required()
@permission_required('workout.view_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_workflow_view(request,id):
request.session['referer'] = absolute(request)['PATH']
request.session['lastworkout'] = id
request.session[translation.LANGUAGE_SESSION_KEY] = USER_LANGUAGE
row = get_workout_by_opaqueid(request,id)
r = getrower(request.user)
result = request.user.is_authenticated and ispromember(request.user)
if result:
promember=1
if request.user == row.user.user:
mayedit=1
comments = WorkoutComment.objects.filter(workout=row)
aantalcomments = len(comments)
favorites,maxfav = getfavorites(r,row)
charts = get_call()
if 'panel_map.html' in r.workflowmiddlepanel and rowhascoordinates(row):
rowdata = rdata(csvfile=row.csvfilename)
mapscript,mapdiv = leaflet_chart2(rowdata.df[' latitude'],
rowdata.df[' longitude'],
row.name)
else:
mapscript = ''
mapdiv = ''
statcharts = GraphImage.objects.filter(workout=row)
middleTemplates = []
for t in r.workflowmiddlepanel:
try:
template.loader.get_template(t)
middleTemplates.append(t)
except template.TemplateDoesNotExist: # pragma: no cover
pass
leftTemplates = []
for t in r.workflowleftpanel:
try:
template.loader.get_template(t)
leftTemplates.append(t)
except template.TemplateDoesNotExist: # pragma: no cover
pass
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,id),
'name': row.name
},
{
'url':reverse('workout_workflow_view',kwargs={'id':id}),
'name': 'View'
}
]
return render(request,
'workflow.html',
{
'middleTemplates':middleTemplates,
'leftTemplates':leftTemplates,
'active':'nav-workouts',
'breadcrumbs':breadcrumbs,
'charts':charts,
'workout':row,
'mapscript':mapscript,
'mapdiv':mapdiv,
'statcharts':statcharts,
'rower':r,
'aantalcomments':aantalcomments,
})
# The famous flex chart
@login_required()
@permission_required('workout.view_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_flexchart3_view(request,*args,**kwargs):
try:
id = kwargs['id']
except KeyError: # pragma: no cover
raise Http404("Invalid workout number")
if 'promember' in kwargs: # pragma: no cover
promember = kwargs['promember']
else:
promember = 0
try:
favoritenr = int(request.GET['favoritechart'])
except:
favoritenr = -1
row = get_workout(id)
r = getrequestrower(request)
promember=0
mayedit=0
if not request.user.is_anonymous:
result = ispromember(request.user)
if result:
promember=1
if request.user == row.user.user:
mayedit=1
if is_workout_user(request.user,row):
mayedit=1
workouttype = 'ote'
if row.workouttype in mytypes.otwtypes:
workouttype = 'otw'
favorites,maxfav = getfavorites(r,row)
# check if favoritenr is not out of range
if favorites:
try:
t = favorites[favoritenr].xparam
except IndexError: # pragma: no cover
favoritenr=0
except AssertionError:
favoritenr=0
if 'xparam' in kwargs:
xparam = kwargs['xparam']
else:
if favorites:
xparam = favorites[favoritenr].xparam
else:
xparam = 'distance'
if 'yparam1' in kwargs:
yparam1 = kwargs['yparam1']
else:
if favorites:
yparam1 = favorites[favoritenr].yparam1
else:
yparam1 = 'pace'
if 'yparam2' in kwargs:
yparam2 = kwargs['yparam2']
if yparam2 == '': # pragma: no cover
yparam2 = 'None'
else:
if favorites:
yparam2 = favorites[favoritenr].yparam2
if yparam2 == '': # pragma: no cover
yparam2 = 'None'
else:
yparam2 = 'hr'
if not request.user.is_anonymous:
r = getrower(request.user)
if favoritenr>=0 and r.showfavoritechartnotes:
try:
favoritechartnotes = favorites[favoritenr].notes
except IndexError: # pragma: no cover
favoritechartnotes = ''
else:
favoritechartnotes = ''
else: # pragma: no cover
favoritechartnotes = ''
favoritenr = 0
if 'plottype' in kwargs: # pragma: no cover
plottype = kwargs['plottype']
else:
if favorites:
plottype = favorites[favoritenr].plottype
else:
plottype = 'line'
if 'workstrokesonly' in kwargs: # pragma: no cover
workstrokesonly = kwargs['workstrokesonly']
else:
if favorites:
workstrokesonly = not favorites[favoritenr].reststrokes
else:
workstrokesonly = False
if request.method == 'POST' and 'savefavorite' in request.POST:
if not request.user.is_anonymous:
workstrokesonly = request.POST['workstrokesonlysave']
reststrokes = not workstrokesonly
r = getrower(request.user)
try:
range = metrics.yaxmaxima[xparam]
if yparam1 is not None:
range = metrics.yaxmaxima[yparam1]
if yparam2 is not None:
range = metrics.yaxmaxima[yparam2]
f = FavoriteChart(user=r,xparam=xparam,
yparam1=yparam1,yparam2=yparam2,
plottype=plottype,workouttype=workouttype,
reststrokes=reststrokes)
f.save()
except KeyError: # pragma: no cover
messages.error(request,'We cannot save the ad hoc metrics in a favorite chart')
if request.method == 'POST' and 'xaxis' in request.POST:
flexoptionsform = FlexOptionsForm(request.POST)
if flexoptionsform.is_valid():
cd = flexoptionsform.cleaned_data
includereststrokes = cd['includereststrokes']
plottype = cd['plottype']
workstrokesonly = not includereststrokes
flexaxesform = FlexAxesForm(request,request.POST)
if flexaxesform.is_valid():
cd = flexaxesform.cleaned_data
xparam = cd['xaxis']
yparam1 = cd['yaxis1']
yparam2 = cd['yaxis2']
else:
pass
if not promember:
for name,d in rowingmetrics:
if d['type'] != 'basic':
if xparam == name: # pragma: no cover
xparam = 'time'
messages.info(request,'To use '+d['verbose_name']+', you have to be Pro member')
if yparam1 == name: # pragma: no cover
yparam1 = 'pace'
messages.info(request,'To use '+d['verbose_name']+', you have to be Pro member')
if yparam2 == name: # pragma: no cover
yparam2 = 'spm'
messages.info(request,'To use '+d['verbose_name']+', you have to be Pro member')
# bring back slashes
# yparam1 = yparam1.replace('_slsh_','/')
# yparam2 = yparam2.replace('_slsh_','/')
# xparam = xparam.replace('_slsh_','/')
# create interactive plot
(
script, div, js_resources, css_resources, workstrokesonly
) = interactive_flex_chart2(
encoder.decode_hex(id),request.user.rower,
xparam=xparam,yparam1=yparam1,
yparam2=yparam2,
promember=promember,plottype=plottype,
workstrokesonly=workstrokesonly,mode=row.workouttype
)
axchoicesbasic = {ax[0]:ax[1] for ax in axes if ax[4]=='basic'}
axchoicespro = {ax[0]:ax[1] for ax in axes if ax[4]=='pro'}
noylist = ["time","distance"]
axchoicesbasic.pop("cumdist")
if row.workouttype in mytypes.otwtypes:
for name,d in rowingmetrics:
if d['mode'] == 'erg':
axchoicespro.pop(name)
else:
for name,d in rowingmetrics:
if d['mode'] == 'water':
axchoicespro.pop(name)
from rowers.metrics import nometrics
rowdata = rdata(csvfile=row.csvfilename)
try:
rowdata.set_instroke_metrics()
except (AttributeError,TypeError): # pragma: no cover
pass
try:
additionalmetrics = rowdata.get_additional_metrics()
additionalmetrics = [m for m in additionalmetrics if not m in nometrics]
except AttributeError: # pragma: no cover
additionalmetrics = []
# extrametrics = {m.replace('/','_slsh_'):m for m in additionalmetrics}
extrametrics = additionalmetrics
initial = {
'xaxis':xparam,
'yaxis1':yparam1,
'yaxis2':yparam2,
}
flexaxesform = FlexAxesForm(request,initial=initial,
extrametrics=extrametrics)
initial = {
'includereststrokes': not workstrokesonly,
'plottype':plottype
}
flexoptionsform = FlexOptionsForm(initial=initial)
row = Workout.objects.get(id=encoder.decode_hex(id))
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,id),
'name': row.name
},
{
'url':reverse('workout_flexchart3_view',kwargs=kwargs),
'name': 'Flex Chart'
}
]
return render(request,
'flexchart3otw.html',
{'the_script':script,
'the_div':div,
'breadcrumbs':breadcrumbs,
'rower':r,
'active':'nav-workouts',
'workout':row,
'chartform':flexaxesform,
'optionsform':flexoptionsform,
'js_res': js_resources,
'css_res':css_resources,
'teams':get_my_teams(request.user),
'id':id,
'xparam':xparam,
'yparam1':yparam1,
'yparam2':yparam2,
'plottype':plottype,
'axchoicesbasic':axchoicesbasic,
'axchoicespro':axchoicespro,
'extrametrics':extrametrics,
'favoritechartnotes':favoritechartnotes,
'noylist':noylist,
'mayedit':mayedit,
'promember':promember,
'workstrokesonly': not workstrokesonly,
'favoritenr':favoritenr,
'maxfav':maxfav,
})
@login_required()
@permission_required('workout.view_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_flexchart_stacked_view(request,*args,**kwargs):
try:
id = kwargs['id']
except KeyError: # pragma: no cover
raise Http404("Invalid workout number")
workout = get_workout(id)
r = getrequestrower(request)
xparam = 'time'
yparam1 = 'pace'
yparam2 = 'power'
yparam3 = 'hr'
yparam4 = 'spm'
if request.method == 'POST':
flexaxesform = StravaChartForm(request,request.POST)
if flexaxesform.is_valid():
cd = flexaxesform.cleaned_data
xparam = cd['xaxis']
yparam1 = cd['yaxis1']
yparam2 = cd['yaxis2']
yparam3 = cd['yaxis3']
yparam4 = cd['yaxis4']
(
script, div, js_resources, css_resources, comment
) = interactive_flexchart_stacked(
encoder.decode_hex(id),r,xparam=xparam,
yparam1=yparam1,
yparam2=yparam2,
yparam3=yparam3,
yparam4=yparam4,
mode=workout.workouttype,
)
if comment is not None: # pragma: no cover
messages.error(request,comment)
initial = {
'xaxis':xparam,
'yaxis1':yparam1,
'yaxis2':yparam2,
'yaxis3':yparam3,
'yaxis4':yparam4,
}
flexaxesform = StravaChartForm(request,initial=initial,
)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,id),
'name': workout.name
},
{
'url':reverse('workout_flexchart_stacked_view',kwargs=kwargs),
'name': 'Stacked Flex Chart'
}
]
return render(request,
'flexchartstacked.html',
{
'the_script':script,
'the_div':div,
'breadcrumbs':breadcrumbs,
'rower':r,
'active':'nav-workouts',
'workout':workout,
'chartform':flexaxesform,
'js_res':js_resources,
'css_res':css_resources,
'id':id,
'xparam':xparam,
'yparam1':yparam1,
'yparam2':yparam2,
'yparam3':yparam3,
'yparam4':yparam4,
}
)
# 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
f1 = w.csvfilename
u = w.user.user
# r = getrower(u)
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})
#
@login_required()
def workout_unsubscribe_view(request,id=0):
w = get_workout(id)
if w.privacy == 'private' and w.user.user != request.user: # pragma: no cover
return HttpResponseForbidden("Permission error")
comments = WorkoutComment.objects.filter(workout=w,
user=request.user).order_by("created")
for c in comments:
c.notification = False
c.save()
form = WorkoutCommentForm()
successmessage = 'You have been unsubscribed from new comment notifications for this workout'
messages.info(request,successmessage)
return render(request,
'workout_comments.html',
{'workout':w,
'teams':get_my_teams(request.user),
'comments':comments,
'form':form,
})
# list of comments to a workout
@login_required()
def workout_comment_view(request,id=0):
w = get_workout(id)
if w.privacy == 'private' and w.user.user != request.user: # pragma: no cover
return HttpResponseForbidden("Permission error")
comments = WorkoutComment.objects.filter(workout=w).order_by("created")
# ok we're permitted
if request.method == 'POST':
r = w.user
form = WorkoutCommentForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
comment = cd['comment']
comment = bleach.clean(comment)
try: # pragma: no cover
if isinstance(comment,unicode):
comment = comment.encode('utf8')
elif isinstance(comment, str):
comment = comment.decode('utf8')
except:
pass
notification = cd['notification']
c = WorkoutComment(workout=w,user=request.user,comment=comment,
notification=notification)
c.save()
url = reverse('workout_comment_view',
kwargs={
'id':id,
})
message = '{name} says: <a href="{url}">{comment}</a>'.format(
name = request.user.first_name,
comment = comment,
url = url,
)
if request.user != r.user: # pragma: no cover
a_messages.info(r.user,message.encode('ascii','ignore'))
res = myqueue(queuehigh,
handle_sendemailnewcomment,r.user.first_name,
r.user.last_name,
r.user.email,
request.user.first_name,
request.user.last_name,
comment,w.name,w.id,
emailbounced = r.emailbounced
)
commenters = {oc.user for oc in comments if oc.notification}
for u in commenters: # pragma: no cover
a_messages.info(u,message)
if u != request.user and u != r.user:
ocr = Rower.objects.get(user=u)
res = myqueue(queue,
handle_sendemailnewresponse,
u.first_name,
u.last_name,
u.email,
request.user.first_name,
request.user.last_name,
comment,
w.name,
w.id,
c.id,
emailbounced = ocr.emailbounced
)
url = reverse('workout_comment_view',
kwargs = {
'id':id})
return HttpResponseRedirect(url)
form = WorkoutCommentForm()
g = GraphImage.objects.filter(workout=w).order_by("-creationdatetime")
for i in g: # pragma: no cover
try:
width,height = Image.open(i.filename).size
i.width = width
i.height = height
i.save()
except:
pass
rower = getrower(request.user)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,id),
'name': w.name
},
{
'url':reverse('workout_comment_view',kwargs={'id':id}),
'name': 'Comments'
}
]
return render(request,
'workout_comments.html',
{'workout':w,
'rower':rower,
'breadcrumbs':breadcrumbs,
'active':'nav-workouts',
'teams':get_my_teams(request.user),
'graphs':g,
'comments':comments,
'form':form,
})
# The basic edit page
@login_required()
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_edit_view(request,id=0,message="",successmessage=""):
request.session[translation.LANGUAGE_SESSION_KEY] = USER_LANGUAGE
request.session['referer'] = absolute(request)['PATH']
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)])
try:
if data['wash'].std() != 0:
url = reverse('paidplans_view')
messages.info(
request,
'Some Empower Oarlock data are only available to users with a <a href="{u}">paid plan</a>'.format(u=url)
)
except:
pass
if request.user.rower.rowerplan == 'basic' and 'nklinklogbook' in row.workoutsource: # pragma: no cover
data = getsmallrowdata_db(['wash'],ids=[encoder.decode_hex(id)])
try:
if data['wash'].std() != 0:
url = reverse('paidplans_view')
messages.info(
request,
'Some Empower Oarlock data are only available to users with a <a href="{u}">paid plan</a>'.format(u=url)
)
except:
pass
form = WorkoutForm(instance=row)
if request.method == 'POST':
# Form was submitted
form = WorkoutForm(request.POST,instance=row)
if form.is_valid():
# Get values from form
name = form.cleaned_data['name']
date = form.cleaned_data['date']
starttime = form.cleaned_data['starttime']
workouttype = form.cleaned_data['workouttype']
weightcategory = form.cleaned_data['weightcategory']
adaptiveclass = form.cleaned_data['adaptiveclass']
duration = form.cleaned_data['duration']
distance = form.cleaned_data['distance']
private = form.cleaned_data['private']
notes = form.cleaned_data['notes']
newdragfactor = form.cleaned_data['dragfactor']
thetimezone = form.cleaned_data['timezone']
try:
rpe = form.cleaned_data['rpe']
if not rpe: # pragma: no cover
rpe = -1
except KeyError: # pragma: no cover
rpe = -1
ps = form.cleaned_data.get('plannedsession',None)
boattype = request.POST.get('boattype',Workout.objects.get(id=encoder.decode_hex(id)).boattype)
privacy = request.POST.get('privacy',Workout.objects.get(id=encoder.decode_hex(id)).privacy)
rankingpiece = form.cleaned_data.get('rankingpiece',Workout.objects.get(id=row.id).rankingpiece)
duplicate = form.cleaned_data.get('duplicate',Workout.objects.get(id=row.id).duplicate)
if private:
privacy = 'private'
else: # pragma: no cover
privacy = 'visible'
try:
startdatetime = datetime.datetime.combine(
date,starttime
)
except TypeError: # pragma: no cover
startdatetime = datetime.datetime.combine(
date,datetime.datetime.min.time()
)
try:
startdatetime = pytz.timezone(thetimezone).localize(
startdatetime
)
except UnknownTimeZoneError: # pragma: no cover
pass
try:
# aware object can be in any timezone
out = startdatetime.astimezone(pytz.utc)
except (ValueError, TypeError): # pragma: no cover
startdatetime = timezone.make_aware(startdatetime)
try:
startdatetime = startdatetime.astimezone(pytz.timezone(thetimezone))
except UnknownTimeZoneError: # pragma: no cover
thetimezone = 'UTC'
timechanged = (startdatetime != row.startdatetime)
row.name = name
row.date = date
row.starttime = starttime
row.startdatetime = startdatetime
row.workouttype = workouttype
row.weightcategory = weightcategory
row.adaptiveclass = adaptiveclass
row.notes = notes
row.rpe = rpe
row.duration = duration
row.distance = distance
row.boattype = boattype
row.duplicate = duplicate
row.privacy = privacy
row.rankingpiece = rankingpiece
row.timezone = thetimezone
row.plannedsession = ps
dragchanged = False
if newdragfactor != row.dragfactor: # pragma: no cover
row.dragfactor = newdragfactor
dragchanged = True
try:
row.save()
except IntegrityError: # pragma: no cover
pass
if ps: # pragma: no cover
add_workouts_plannedsession([row],ps,row.user)
# change data in csv file
datachanged = (dragchanged or timechanged)
if datachanged:
r = rdata(csvfile=row.csvfilename)
if dragchanged:
try: # pragma: no cover
r.change_drag(newdragfactor)
except AttributeError: # pragma: no cover
pass
if r == 0: # pragma: no cover
return HttpResponse("Error: CSV Data File Not Found")
r.rowdatetime = startdatetime
r.write_csv(row.csvfilename,gzip=True)
dataprep.update_strokedata(encoder.decode_hex(id),r.df)
successmessage = "Changes saved"
messages.info(request,successmessage)
else:
form = WorkoutForm(instance=row)
g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime")
for i in g: # pragma: no cover
try:
width,height = Image.open(i.filename).size
i.width = width
i.height = height
i.save()
except:
pass
videos = VideoAnalysis.objects.filter(workout=row)
# create interactive plot
f1 = row.csvfilename
u = row.user.user
r = getrower(u)
rowdata = rdata(csvfile=f1)
hascoordinates = 1
courses = []
if rowdata != 0:
try:
latitude = rowdata.df[' latitude']
longitude = rowdata.df[' longitude']
if not latitude.std(): # pragma: no cover
hascoordinates = 0
if not longitude.std():
hascoordinates = 0
except (KeyError,AttributeError):
hascoordinates = 0
else: # pragma: no cover
hascoordinates = 0
mapscript = ""
mapdiv = ""
if hascoordinates:
try:
mapscript,mapdiv = leaflet_chart(
rowdata.df[' latitude'],
rowdata.df[' longitude'],
row.name)
except KeyError: # pragma: no cover
pass
records = VirtualRaceResult.objects.filter(workoutid=row.id,userid=row.user.user.id,coursecompleted=True)
if records.count()>0: # pragma: no cover
courses = list(set([record.course for record in records]))
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,encoder.encode_hex(row.id)),
'name': row.name
},
{
'url':reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(row.id)}),
'name': 'Edit'
}
]
if row.workouttype in mytypes.otetypes:
indoorraces = get_indoorraces(row)
else:
indoorraces = []
r = row.user
# render page
return render(request, 'workout_form.html',
{'form':form,
'workout':row,
'teams':get_my_teams(request.user),
'graphs':g,
'videos':videos,
'breadcrumbs':breadcrumbs,
'rower':r,
'indoorraces':indoorraces,
'active':'nav-workouts',
'mapscript':mapscript,
'mapdiv':mapdiv,
'rower':r,
'courses':courses,
})
@login_required()
def workout_map_view(request,id=0):
request.session[translation.LANGUAGE_SESSION_KEY] = USER_LANGUAGE
request.session['referer'] = absolute(request)['PATH']
w = get_workout(id)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,id),
'name': w.name
},
{
'url':reverse('workout_map_view',kwargs={'id':id}),
'name': 'Map'
}
]
# create interactive plot
f1 = w.csvfilename
u = w.user.user
r = getrower(u)
rowdata = rdata(csvfile=f1)
hascoordinates = 1
if rowdata != 0:
try:
latitude = rowdata.df[' latitude']
if not latitude.std(): # pragma: no cover
hascoordinates = 0
except (KeyError,AttributeError):
hascoordinates = 0
else: # pragma: no cover
hascoordinates = 0
if hascoordinates:
mapscript,mapdiv = leaflet_chart2(rowdata.df[' latitude'],
rowdata.df[' longitude'],
w.name)
else:
mapscript = ""
mapdiv = ""
mayedit=0
if not request.user.is_anonymous:
r = getrower(request.user)
result = request.user.is_authenticated and ispromember(request.user)
if result:
promember=1
if request.user == w.user.user:
mayedit=1
return render(request, 'map_view.html',
{'mapscript':mapscript,
'workout':w,
'rower':r,
'breadcrumbs':breadcrumbs,
'active':'nav-workouts',
'mapdiv':mapdiv,
'mayedit':mayedit,
})
# Image upload
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_uploadimage_view(request,id): # pragma: no cover
is_ajax = request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
r = getrower(request.user)
w = get_workoutuser(id, request)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,id),
'name': w.name
},
{
'url':reverse('workout_uploadimage_view',kwargs={'id':id}),
'name': 'Upload Image'
}
]
images = GraphImage.objects.filter(workout=w)
if images.count() >= 6: # pragma: no cover
message = "You have reached the maximum number of static images for this workout"
messages.error(request,message)
url = reverse(r.defaultlandingpage,
kwargs = {
'id':id,
})
return HttpResponseRedirect(url)
if request.method == 'POST':
form = ImageForm(request.POST,request.FILES)
if form.is_valid():
f = form.cleaned_data['file']
if f is not None:
filename,path_and_filename = handle_uploaded_image(f)
try:
width,height = Image.open(path_and_filename).size
except:
message = "Not a valid image"
messages.error(request,message)
os.remove(path_and_filename)
url = reverse('workout_uploadimage_view',
kwargs = {'id':id})
if is_ajax:
return JSONResponse({'result':0,'url':0})
else:
return HttpResponseRedirect(url)
i = GraphImage(workout=w,
creationdatetime=timezone.now(),
filename = path_and_filename,
width=width,height=height)
i.save()
url = reverse(r.defaultlandingpage,
kwargs = {'id':id})
if is_ajax:
return JSONResponse({'result':1,'url':url})
else:
return HttpResponseRedirect(url)
else:
messages.error(request,'Something went wrong - no file attached')
url = reverse('workout_uploadimage_view',
kwargs = {'id':id})
if is_ajax:
return JSONResponse({'result':0,'url':0})
else:
return HttpResponseRedirect(url)
else:
return HttpResponse("Form is not valid")
else:
if not is_ajax:
form = ImageForm()
return render(request,'image_form.html',
{'form':form,
'rower':r,
'active':'nav-workouts',
'breadcrumbs':breadcrumbs,
'teams':get_my_teams(request.user),
'workout': w,
})
else:
return {'result':0}
# Generic chart creation
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_add_chart_view(request,id,plotnr=1):
w = get_workoutuser(id, request)
r = getrower(request.user)
plotnr = int(plotnr)
f1 = w.csvfilename[6:-4]
timestr = strftime("%Y%m%d-%H%M%S")
imagename = f1+timestr+'.png'
u = w.user.user
r = getrower(u)
title = w.name
res,jobid = uploads.make_plot(
r,w,f1,w.csvfilename,'timeplot',title,plotnr=plotnr,
imagename=imagename
)
if res == 0: # pragma: no cover
messages.error(request,jobid)
else:
try:
request.session['async_tasks'] += [(jobid,'make_plot')]
except KeyError:
request.session['async_tasks'] = [(jobid,'make_plot')]
url = reverse(r.defaultlandingpage,kwargs={'id':encoder.encode_hex(w.id)})
return HttpResponseRedirect(url)
@login_required
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_toggle_ranking(request,id=0):
is_ajax = request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
row = get_workout_by_opaqueid(request,id)
row.rankingpiece = not row.rankingpiece
row.save()
if is_ajax: # pragma: no cover
response = JSONResponse({'result':row.rankingpiece},
content_type='application/json')
return response
else:
url = reverse('workouts_view')
response = HttpResponseRedirect(url)
return response
# simple POST API for files on local (e.g. in mailbox)
@csrf_exempt
def workout_upload_api(request):
stravaid = ''
if request.method != 'POST': # pragma: no cover
message = {'status':'false','message':'this view cannot be accessed through GET'}
return JSONResponse(status=403,data=message)
# test if JSON
try:
json_data = json.loads(request.body)
secret = json_data['secret']
post_data = json_data
except (KeyError,JSONDecodeError):
q = request.POST
post_data = {k: q.getlist(k) if len(q.getlist(k))>1 else v for k, v in q.items()}
# only allow local host
hostt = request.get_host().split(':')
if hostt[0] not in ['localhost','127.0.0.1','dev.rowsandall.com','rowsandall.com']:
message = {'status':'false','message':'permission denied for host '+hostt[0]}
return JSONResponse(status=403,data=message)
# check credentials here
try:
secret = post_data['secret']
except KeyError:
message = {'status': 'false', 'message':'missing credentials'}
return JSONResponse(status=400,data=message)
if secret != settings.UPLOAD_SERVICE_SECRET:
message = {'status':'false','message':'invalid credentials'}
return JSONResponse(status=403,data=message)
form = DocumentsForm(post_data)
optionsform = TeamUploadOptionsForm(post_data)
rowerform = TeamInviteForm(post_data)
rowerform.fields.pop('email')
try:
fstr = post_data['file']
nn, ext = os.path.splitext(fstr)
if ext== '.gz': # pragma: no cover
nn, ext2 = os.path.splitext(nn)
ext = ext2+ext
f1 = uuid.uuid4().hex[:10]+'-'+time.strftime("%Y%m%d-%H%M%S")+ext
f2 = 'media/'+f1
copyfile(fstr,f2)
except KeyError:
message = {'status':'false','message':'no filename given'}
return JSONResponse(status=400,data=message)
except FileNotFoundError:
message = {'status':'false','message':'could not find file'}
return JSONResponse(status=400,data=message)
# sync related IDs
stravaid = post_data.get('stravaid','')
c2id = post_data.get('c2id','')
nkid = post_data.get('nkid','')
rp3id = post_data.get('rp3id','')
garminid = post_data.get('garminid',0)
startdatetime = post_data.get('startdatetime','')
oarlockfirmware = post_data.get('oarlockfirmware',None)
inboard = post_data.get('inboard',None)
oarlength = post_data.get('oarlength',None)
useImpeller = post_data.get('useImpeller',False)
totalDistance = post_data.get('totalDistance',None)
elapsedTime = post_data.get('elapsedTime',None)
summary = post_data.get('summary',None)
timezone = post_data.get('timezone',None)
s = 'Posting c2id {c2id} to Rowsandall. Startdatetime {startdatetime}, time zone {timezone}'.format(
c2id=c2id,
startdatetime=startdatetime,
timezone=timezone,
)
dologging('debuglog.log',s)
r = None
if form.is_valid():
t = form.cleaned_data['title']
boattype = form.cleaned_data['boattype']
workouttype = form.cleaned_data['workouttype']
try:
rpe = form.cleaned_data['rpe']
try:
rpe = int(rpe)
except ValueError: # pragma: no cover
rpe = 0
except KeyError: # pragma: no cover
rpe = -1
if rowerform.is_valid():
u = rowerform.cleaned_data['user']
r = getrower(u)
if 'useremail' in post_data:
us = User.objects.filter(email=post_data['useremail'])
if len(us): # pragma: no cover
u = us[0]
r = getrower(u)
else:
r = None
for rwr in Rower.objects.all():
if rwr.emailalternatives is not None:
if post_data['useremail'] in rwr.emailalternatives:
r = rwr
break
if r is not None and r.emailalternatives is not None:
if post_data['useremail'] not in r.emailalternatives: # pragma: no cover
message = {'status':'false','message':'could not find user'}
return JSONResponse(status=400,data=message)
if r is None: # pragma: no cover
message = {'status':'false','message':'invalid user'}
return JSONResponse(status=400,data=message)
notes = form.cleaned_data['notes']
if optionsform.is_valid():
make_plot = optionsform.cleaned_data['make_plot']
plottype = optionsform.cleaned_data['plottype']
upload_to_c2 = optionsform.cleaned_data['upload_to_C2']
upload_to_strava = optionsform.cleaned_data['upload_to_Strava']
upload_to_st = optionsform.cleaned_data['upload_to_SportTracks']
upload_to_tp = optionsform.cleaned_data['upload_to_TrainingPeaks']
makeprivate = optionsform.cleaned_data['makeprivate']
else: # pragma: no cover
message = optionsform.errors
return JSONResponse(status=400,data=message)
if r is None: # pragma: no cover
message = {'status':'false','message':'something went wrong'}
return JSONResponse(status=400,data=message)
id, message, f2 = dataprep.new_workout_from_file(
r,f2,
workouttype=workouttype,
workoutsource=None,
boattype=boattype,
makeprivate=makeprivate,
title = t,
rpe=rpe,
notes=notes,
uploadoptions=post_data,
startdatetime=startdatetime,
oarlockfirmware=oarlockfirmware,
inboard=inboard,
oarlength=oarlength,
impeller=useImpeller,
)
if id == 0: # pragma: no cover
if message is not None:
message = {'status':'false','message':'unable to process file: '+message}
else:
message = {'status': 'false', 'message': 'unable to process file'}
return JSONResponse(status=400,data=message)
if id == -1: # pragma: no cover
message = {'status': 'true', 'message':message}
return JSONResponse(status=200,data=message)
w = Workout.objects.get(id=id)
if timezone is not None: # pragma: no cover
w.startdatetime = w.startdatetime.astimezone(pytz.timezone(timezone))
w.workoutdate = w.startdatetime.strftime('%Y-%m-%d')
w.starttime = w.startdatetime.strftime('%H:%M:%S')
w.timezone = timezone
dologging('debuglog.log','Start Date Time set to {s}'.format(s=w.startdatetime))
dologging('debuglog.log','Workout Date set to {s}'.format(s=w.workoutdate))
dologging('debuglog.log','Time zone set to {zone}'.format(zone=w.timezone))
w.save()
if make_plot: # pragma: no cover
res, jobid = uploads.make_plot(r,w,f1,f2,plottype,t)
elif r.staticchartonupload != 'None': # pragma: no cover
plottype = r.staticchartonupload
res, jobid = uploads.make_plot(r,w,f1,f2,plottype,t)
if inboard: # pragma: no cover
w.inboard = inboard
w.save()
if oarlength: # pragma: no cover
w.oarlength = oarlength
w.save()
if totalDistance: # pragma: no cover
w.distance = totalDistance
w.save()
if elapsedTime: # pragma: no cover
w.duration = totaltime_sec_to_string(elapsedTime)
if summary: # pragma: no cover
w.summary = summary
w.save()
uploads.do_sync(w,post_data,quick=True)
else: # pragma: no cover
# form invalid
if fstr:
os.remove(fstr)
message = form.errors
return JSONResponse(status=400,data=message)
message = {'status': 'true','id':w.id}
statuscode = 200
if fstr:
try:
os.remove(fstr)
except FileNotFoundError: # pragma: no cover
message = {'status': 'true', 'id':w.id,'message': 'Error deleting temporary file'}
statuscode = 500
if r.getemailnotifications and not r.emailbounced: # pragma: no cover
link = settings.SITE_URL+reverse(
r.defaultlandingpage,
kwargs = {
'id':encoder.encode_hex(w.id),
}
)
email_sent = send_confirm(r.user, t, link, '')
return JSONResponse(status=statuscode,data=message)
# This is the main view for processing uploaded files
@login_required()
def workout_upload_view(request,
uploadoptions={
'makeprivate':False,
'make_plot':False,
'upload_to_C2':False,
'plottype':'timeplot',
'landingpage':'workout_edit_view',
},
docformoptions={
'workouttype':'rower',
},
raceid=0):
is_ajax = request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
if settings.TESTING:
is_ajax = False
r = getrower(request.user)
if r.rowerplan == 'freecoach': # pragma: no cover
url = reverse('team_workout_upload_view')
return HttpResponseRedirect(url)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url': reverse('workout_upload_view'),
'name': 'Upload'
}
]
if 'uploadoptions' in request.session:
uploadoptions = request.session['uploadoptions']
try:
defaultlandingpage = uploadoptions['landingpage']
except KeyError: # pragma: no cover
uploadoptions['landingpage'] = r.defaultlandingpage
defaultlandingpage = r.defaultlandingpage
else:
request.session['uploadoptions'] = uploadoptions
if 'docformoptions' in request.session:
docformoptions = request.session['docformoptions']
else:
request.session['docformoptions'] = docformoptions
makeprivate = uploadoptions.get('makeprivate',False)
make_plot = uploadoptions.get('make_plot',False)
workouttype = uploadoptions.get('WorkoutType','rower')
boattype = docformoptions.get('boattype','1x')
try:
rpe = docformoptions['rpe']
try: # pragma: no cover
rpe = int(rpe)
except ValueError: # pragma: no cover
rpe = 0
if not rpe: # pragma: no cover
rpe = -1
except KeyError:
rpe = -1
notes = docformoptions.get('notes','')
workoutsource = uploadoptions.get('workoutsource',None)
plottype = uploadoptions.get('plottype','timeplot')
landingpage = uploadoptions.get('landingpage',r.defaultlandingpage)
upload_to_c2 = uploadoptions.get('upload_to_C2',False)
upload_to_strava = uploadoptions.get('upload_to_Strava',False)
upload_to_st = uploadoptions.get('upload_to_SportTracks',False)
upload_to_tp = uploadoptions.get('upload_to_TrainingPeaks',False)
response = {}
if request.method == 'POST':
form = DocumentsForm(request.POST,request.FILES)
optionsform = UploadOptionsForm(request.POST,request=request)
if form.is_valid():
# f = request.FILES['file']
f = form.cleaned_data['file']
if f is not None:
res = handle_uploaded_file(f)
else: # pragma: no cover
messages.error(request,
"Something went wrong - no file attached")
url = reverse('workout_upload_view')
if is_ajax:
return JSONResponse({'result':0,'url':0})
else:
return HttpResponseRedirect(url)
t = form.cleaned_data['title']
workouttype = form.cleaned_data['workouttype']
boattype = form.cleaned_data['boattype']
try:
rpe = form.cleaned_data['rpe']
try:
rpe = int(rpe)
except ValueError:
rpe = 0
except KeyError: # pragma: no cover
rpe = -1
request.session['docformoptions'] = {
'workouttype':workouttype,
'boattype': boattype,
}
notes = form.cleaned_data['notes']
offline = form.cleaned_data['offline']
registrationid = 0
if optionsform.is_valid():
make_plot = optionsform.cleaned_data['make_plot']
plottype = optionsform.cleaned_data['plottype']
upload_to_c2 = optionsform.cleaned_data['upload_to_C2']
upload_to_strava = optionsform.cleaned_data['upload_to_Strava']
upload_to_st = optionsform.cleaned_data['upload_to_SportTracks']
upload_to_tp = optionsform.cleaned_data['upload_to_TrainingPeaks']
makeprivate = optionsform.cleaned_data['makeprivate']
landingpage = optionsform.cleaned_data['landingpage']
raceid = optionsform.cleaned_data['raceid']
try:
registrationid = optionsform.cleaned_data['submitrace']
except KeyError:
registrationid = 0
uploadoptions = {
'makeprivate':makeprivate,
'make_plot':make_plot,
'plottype':plottype,
'upload_to_C2':upload_to_c2,
'upload_to_Strava':upload_to_strava,
'upload_to_SportTracks':upload_to_st,
'upload_to_TrainingPeaks':upload_to_tp,
'landingpage':landingpage,
'boattype': boattype,
'rpe':rpe,
'workouttype': workouttype,
}
request.session['uploadoptions'] = uploadoptions
f1 = res[0] # file name
f2 = res[1] # file name incl media directory
if not offline:
id,message,f2 = dataprep.new_workout_from_file(
r,f2,
workouttype=workouttype,
workoutsource=workoutsource,
boattype=boattype,
rpe=rpe,
makeprivate=makeprivate,
title = t,
notes=notes,
)
else: # pragma: no cover
workoutsbox = Mailbox.objects.filter(name='workouts')[0]
uploadoptions['fromuploadform'] = True
bodyyaml = yaml.safe_dump(
uploadoptions,
default_flow_style=False
)
msg = Message(mailbox=workoutsbox,
from_header=r.user.email,
subject = t,body=bodyyaml)
msg.save()
f3 = 'media/mailbox_attachments/'+f2[6:]
copyfile(f2,f3)
f3 = f3[6:]
a = MessageAttachment(message=msg,document=f3)
try:
a.save()
except DataError:
pass
os.remove(f2)
messages.info(
request,
"The file was too large to process in real time. It will be processed in a background process. You will receive an email when it is ready")
url = reverse('workout_upload_view')
if is_ajax: # pragma: no cover
return JSONResponse({'result':1,'url':url})
else:
response = HttpResponseRedirect(url)
return response
if not id: # pragma: no cover
messages.error(request,message)
url = reverse('workout_upload_view')
if is_ajax: # pragma: no cover
return JSONResponse({'result':0,'url':url})
else:
response = HttpResponseRedirect(url)
return response
elif id == -1: # pragma: no cover
message = 'The zip archive will be processed in the background. The files in the archive will only be uploaded without the extra actions. You will receive email when the workouts are ready.'
messages.info(request,message)
url = reverse('workout_upload_view')
if is_ajax:
return JSONResponse({'result':1,'url':url})
else:
response = HttpResponseRedirect(url)
return response
else:
if message: # pragma: no cover
messages.error(request,message)
w = Workout.objects.get(id=id)
url = reverse('workout_edit_view',
kwargs = {
'id':encoder.encode_hex(w.id),
})
if is_ajax: # pragma: no cover
response = {'result': 1,'url':url}
else:
response = HttpResponseRedirect(url)
r = getrower(request.user)
if (make_plot): # pragma: no cover
res,jobid = uploads.make_plot(r,w,f1,f2,plottype,t)
if res == 0:
messages.error(request,jobid)
else:
try:
request.session['async_tasks'] += [(jobid,'make_plot')]
except KeyError:
request.session['async_tasks'] = [(jobid,'make_plot')]
elif r.staticchartonupload != None:
plottype = r.staticchartonupload
res, jobid = uploads.make_plot(r,w,f1,f2,plottype,t)
# upload to C2
if (upload_to_c2): # pragma: no cover
try:
message,id = c2stuff.workout_c2_upload(request.user,w)
except NoTokenError:
id = 0
message = "Something went wrong with the Concept2 sync"
if id>1:
messages.info(request,message)
else:
messages.error(request,message)
if (upload_to_strava): # pragma: no cover
try:
message,id = stravastuff.workout_strava_upload(
request.user,w,
)
except NoTokenError:
id = 0
message = "Please connect to Strava first"
if id>1:
messages.info(request,message)
else:
messages.error(request,message)
if (upload_to_st): # pragma: no cover
try:
message,id = sporttracksstuff.workout_sporttracks_upload(
request.user,w
)
except NoTokenError:
message = "Please connect to SportTracks first"
id = 0
if id>1:
messages.info(request,message)
else:
messages.error(request,message)
if (upload_to_tp): # pragma: no cover
try:
message,id = tpstuff.workout_tp_upload(
request.user,w
)
except NoTokenError:
message = "Please connect to TrainingPeaks first"
id = 0
if id>1:
messages.info(request,message)
else:
messages.error(request,message)
if int(registrationid) < 0: # pragma: no cover
race = VirtualRace.objects.get(id=-int(registrationid))
if race.sessiontype == 'race':
result,comments,errors,jobid = add_workout_race(
[w], race,r,doregister=True,
)
if result:
messages.info(
request,
"We have submitted your workout to the race")
for c in comments:
messages.info(request,c)
for er in errors:
messages.error(request,er)
elif race.sessiontype == 'indoorrace':
result,comments,errors,jobid = add_workout_indoorrace(
[w],race,r,doregister=True,
)
if result:
messages.info(
request,
"We have submitted your workout to the race")
for c in comments:
messages.info(request,c)
for er in errors:
messages.error(request,er)
elif race.sessiontype in ['fastest_time','fastest_distance']:
result,comments,errors,jobid = add_workout_fastestrace(
[w], race,r,doregister=True,
)
if result:
messages.info(request,"We have submitted your workout to the race")
for c in comments:
messages.info(request,c)
for er in errors:
messages.error(request,er)
if int(registrationid)>0: # pragma: no cover
races = VirtualRace.objects.filter(
registration_closure__gt=timezone.now()
)
if raceid != 0:
races = VirtualRace.objects.filter(
registration_closure__gt=timezone.now(),
id=raceid,
)
registrations = IndoorVirtualRaceResult.objects.filter(
race__in = races,
id=registrationid,
userid = r.id,
)
registrations2 = VirtualRaceResult.objects.filter(
race__in = races,
id=registrationid,
userid=r.id,
)
if int(registrationid) in [r.id for r in registrations]: # pragma: no cover
# indoor race
registrations = registrations.filter(id=registrationid)
if registrations:
race = registrations[0].race
if race.sessiontype == 'indoorrace':
result,comments,errors,jobid = add_workout_indoorrace(
[w],race,r,recordid=registrations[0].id
)
elif race.sessiontype in ['fastest_time','fastest_distance']:
result,comments, errors,jobid = add_workout_fastestrace(
[w],race,r,recordid=registrations[0].id
)
if result:
messages.info(
request,
"We have submitted your workout to the race")
for c in comments:
messages.info(request,c)
for er in errors:
messages.error(request,er)
if int(registrationid) in [r.id for r in registrations2]: # pragma: no cover
# race
registrations = registrations2.filter(id=registrationid)
if registrations:
race = registrations[0].race
if race.sessiontype == 'race':
result,comments,errors,jobid = add_workout_race(
[w], race,r,recordid=registrations[0].id
)
elif race.sessiontype in ['fastest_time','fastest_distance']:
result, comments, errors, jobid = add_workout_fastestrace(
[w],race,r,recordid=registrations[0].id
)
if result:
messages.info(
request,
"We have submitted your workout to the race")
for c in comments:
messages.info(request,c)
for er in errors:
messages.error(request,er)
if registrationid != 0: # pragma: no cover
try:
url = reverse('virtualevent_view',
kwargs = {
'id':race.id,
})
except UnboundLocalError:
if landingpage != 'workout_upload_view':
url = reverse(landingpage,
kwargs = {
'id':encoder.encode_hex(w.id),
})
else: # pragma: no cover
url = reverse(landingpage)
elif landingpage != 'workout_upload_view': # pragma: no cover
url = reverse(landingpage,
kwargs = {
'id':encoder.encode_hex(w.id),
})
else: # pragma: no cover
url = reverse(landingpage)
if is_ajax: # pragma: no cover
response = {'result':1,'url':url}
else:
response = HttpResponseRedirect(url)
else:
if not is_ajax: # pragma: no cover
response = render(request,
'document_form.html',
{'form':form,
'teams':get_my_teams(request.user),
'optionsform': optionsform,
})
if is_ajax: # pragma: no cover
return JSONResponse(response)
else:
return response
else:
if not is_ajax:
if r.c2_auto_export and ispromember(r.user): # pragma: no cover
uploadoptions['upload_to_C2'] = True
if r.strava_auto_export and ispromember(r.user): # pragma: no cover
uploadoptions['upload_to_Strava'] = True
if r.sporttracks_auto_export and ispromember(r.user): # pragma: no cover
uploadoptions['upload_to_SportTracks'] = True
if r.trainingpeaks_auto_export and ispromember(r.user): # pragma: no cover
uploadoptions['upload_to_TrainingPeaks'] = True
form = DocumentsForm(initial=docformoptions)
optionsform = UploadOptionsForm(initial=uploadoptions,
request=request,raceid=raceid)
return render(request, 'document_form.html',
{'form':form,
'active':'nav-workouts',
'breadcrumbs':breadcrumbs,
'teams':get_my_teams(request.user),
'optionsform': optionsform,
})
else: # pragma: no cover
return {'result':0}
# This is the main view for processing uploaded files
@user_passes_test(iscoachmember,login_url="/rowers/paidplans",redirect_field_name=None,
message="This functionality requires a Coach plan or higher")
def team_workout_upload_view(request,message="",
successmessage="",
uploadoptions={
'make_plot':False,
'plottype':'timeplot',
}):
if 'uploadoptions' in request.session:
uploadoptions = request.session['uploadoptions']
else:
request.session['uploadoptions'] = uploadoptions
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url': reverse('team_workout_upload_view'),
'name': 'Team Upload'
}
]
myteams = Team.objects.filter(manager=request.user)
make_plot = uploadoptions['make_plot']
plottype = uploadoptions['plottype']
r = getrower(request.user)
if request.method == 'POST':
form = DocumentsForm(request.POST,request.FILES)
optionsform = TeamUploadOptionsForm(request.POST)
rowerform = TeamInviteForm(request.POST)
rowerform.fields.pop('email')
rowers = Rower.objects.filter(
coachinggroups__in=[r.mycoachgroup]
).exclude(
rowerplan='freecoach'
).distinct()
if r.rowerplan == 'freecoach': # pragma: no cover
rowers = rowers.exclude(rowerplan='basic')
rowerform.fields['user'].queryset = User.objects.filter(rower__in=rowers).distinct()
rowerform.fields['user'].required = True
if form.is_valid() and rowerform.is_valid():
f = request.FILES.get('file',False)
if f:
res = handle_uploaded_file(f)
else: # pragma: no cover
messages.error(request,'No file attached')
response = render(request,
'team_document_form.html',
{'form':form,
'teams':get_my_teams(request.user),
'optionsform':optionsform,
'rowerform':rowerform,
})
return response
t = form.cleaned_data['title']
offline = form.cleaned_data['offline']
boattype = form.cleaned_data['boattype']
workouttype = form.cleaned_data['workouttype']
if rowerform.is_valid():
u = rowerform.cleaned_data['user']
r = getrower(u)
if not can_add_workout_member(request.user,r): # pragma: no cover
message = 'Please select a rower'
messages.error(request,message)
messages.info(request,successmessage)
response = render(request,
'team_document_form.html',
{'form':form,
'teams':get_my_teams(request.user),
'optionsform': optionsform,
'rowerform': rowerform,
})
return response
workouttype = form.cleaned_data['workouttype']
notes = form.cleaned_data['notes']
if optionsform.is_valid():
make_plot = optionsform.cleaned_data['make_plot']
plottype = optionsform.cleaned_data['plottype']
uploadoptions = {
'makeprivate':False,
'make_plot':make_plot,
'plottype':plottype,
'upload_to_C2':False,
}
request.session['uploadoptions'] = uploadoptions
f1 = res[0] # file name
f2 = res[1] # file name incl media directory
if not offline:
id,message,f2 = dataprep.new_workout_from_file(
r,f2,
workouttype=workouttype,
boattype=boattype,
makeprivate=False,
title = t,
notes=''
)
else: # pragma: no cover
job = myqueue(
queuehigh,
handle_zip_file,
r.user.email,
t,
f2,
emailbounced = r.emailbounced
)
messages.info(
request,
"The file was too large to process in real time. It will be processed in a background process. The user will receive an email when it is ready"
)
url = reverse('team_workout_upload_view')
response = HttpResponseRedirect(url)
return response
if not id: # pragma: no cover
messages.error(request,message)
url = reverse('team_workout_upload_view')
response = HttpResponseRedirect(url)
return response
elif id == -1: # pragma: no cover
message = 'The zip archive will be processed in the background. The files in the archive will only be uploaded without the extra actions. You will receive email when the workouts are ready.'
messages.info(request,message)
url = reverse('team_workout_upload_view')
response = HttpResponseRedirect(url)
return response
else:
successmessage = "The workout was added to the user's account"
messages.info(request,successmessage)
url = reverse('team_workout_upload_view')
response = HttpResponseRedirect(url)
w = Workout.objects.get(id=id)
r = getrower(request.user)
if (make_plot): # pragma: no cover
id,jobid = uploads.make_plot(r,w,f1,f2,plottype,t)
elif r.staticchartonupload:
plottype = r.staticchartonupload
id,jobid = uploads.make_plot(r,w,f1,f2,plottype,t)
else:
response = render(request,
'team_document_form.html',
{'form':form,
'teams':get_my_teams(request.user),
'active': 'nav-workouts',
'breadcrumbs':breadcrumbs,
'optionsform': optionsform,
'rowerform': rowerform,
})
return response
else:
form = DocumentsForm()
optionsform = TeamUploadOptionsForm(initial=uploadoptions)
rowerform = TeamInviteForm()
rowerform.fields.pop('email')
rowers = Rower.objects.filter(
coachinggroups__in=[r.mycoachgroup]
).exclude(
rowerplan='freecoach'
).distinct()
if r.rowerplan == 'freecoach': # pragma: no cover
rowers = rowers.exclude(rowerplan='basic')
rowerform.fields['user'].queryset = User.objects.filter(rower__in=rowers).distinct()
return render(request, 'team_document_form.html',
{'form':form,
# 'teams':get_my_teams(request.user),
'optionsform': optionsform,
'active': 'nav-workouts',
'breadcrumbs':breadcrumbs,
# 'rower':r,
'rowerform':rowerform,
})
# A page with all the recent graphs (searchable on workout name)
@login_required()
def list_videos(request,userid=0):
r = getrequestrower(request,userid=userid)
workouts = Workout.objects.filter(user=r).order_by("-date", "-starttime")
query = request.GET.get('q')
if query: # pragma: no cover
query_list = query.split()
if query:
query_list = query.split()
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})
else:
searchform = SearchForm()
g = VideoAnalysis.objects.filter(workout__in=workouts).order_by("-workout__date")
paginator = Paginator(g,8)
page = request.GET.get('page',1)
try:
g = paginator.page(page)
except EmptyPage: # pragma: no cover
g = paginator.page(paginator.num_pages)
return render(request, 'list_videos.html',
{'analyses': g,
'searchform':searchform,
'active':'nav-analysis',
'rower':r,
'teams':get_my_teams(request.user),
})
@login_required()
def graphs_view(request,userid=0):
request.session['referer'] = reverse('graphs_view')
r = getrequestrower(request,userid=userid)
workouts = Workout.objects.filter(user=r).order_by("-date", "-starttime")
query = request.GET.get('q')
if query: # pragma: no cover
query_list = query.split()
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})
else:
searchform = SearchForm()
g = GraphImage.objects.filter(workout__in=workouts).order_by("-creationdatetime")
paginator = Paginator(g,8)
page = request.GET.get('page')
try:
g = paginator.page(page)
except PageNotAnInteger:
g = paginator.page(1)
except EmptyPage: # pragma: no cover
g = paginator.page(paginator.num_pages)
return render(request, 'list_graphs.html',
{'graphs': g,
'searchform':searchform,
'active':'nav-workouts',
'teams':get_my_teams(request.user),
'rower':r,
})
# Show the chart (png image)
def graph_show_view(request,id):
try:
g = GraphImage.objects.get(id=id)
try: # pragma: no cover
width,height = Image.open(g.filename).size
g.width = width
g.height = height
g.save()
except:
pass
w = Workout.objects.get(id=g.workout.id)
r = Rower.objects.get(id=w.user.id)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,encoder.encode_hex(w.id)),
'name': w.name
},
{
'url':reverse('graph_show_view',kwargs={'id':id}),
'name': 'Chart'
}
]
return render(request,'show_graph.html',
{'graph':g,
'teams':get_my_teams(request.user),
'workout':w,
'breadcrumbs':breadcrumbs,
'active':'nav-workouts',
'rower':r,})
except GraphImage.DoesNotExist: # pragma: no cover
raise Http404("This graph doesn't exist")
except Workout.DoesNotExist: # pragma: no cover
raise Http404("This workout doesn't exist")
# Restore original stroke data and summary
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_summary_restore_view(request,id,message="",successmessage=""):
row = get_workout_by_opaqueid(request,id)
s = ""
# still here - this is a workout we may edit
f1 = row.csvfilename
u = row.user.user
r = getrower(u)
powerperc = 100*np.array([r.pw_ut2,
r.pw_ut1,
r.pw_at,
r.pw_tr,r.pw_an])/r.ftp
ftp = float(r.ftp)
if row.workouttype in mytypes.otwtypes:
ftp = ftp*(100.-r.otwslack)/100.
rr = rrower(hrmax=r.max,hrut2=r.ut2,
hrut1=r.ut1,hrat=r.at,
hrtr=r.tr,hran=r.an,ftp=ftp,
powerperc=powerperc,powerzones=r.powerzones,
hrzones=r.hrzones)
rowdata = rdata(csvfile=f1,rower=rr)
if rowdata == 0: # pragma: no cover
raise Http404("Error: CSV Data File Not Found")
rowdata.restoreintervaldata()
rowdata.write_csv(f1,gzip=True)
dataprep.update_strokedata(encoder.decode_hex(id),rowdata.df)
intervalstats = rowdata.allstats()
row.summary = intervalstats
row.save()
# create interactive plot
try:
res = interactive_chart(encoder.decode_hex(id),promember=1)
script = res[0]
div = res[1]
except ValueError: # pragma: no cover
pass
messages.info(request,'Original Interval Data Restored')
url = reverse('workout_summary_edit_view',
kwargs={
'id':encoder.encode_hex(row.id),
}
)
return HttpResponseRedirect(url)
# Split a workout
@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_split_view(request,id=0):
row = get_workout_by_opaqueid(request,id)
r = row.user
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,id),
'name': row.name
},
{
'url':reverse('workout_split_view',
kwargs={'id':id}),
'name': 'Chart'
}
]
if request.method == 'POST':
form = WorkoutSplitForm(request.POST)
if form.is_valid():
splittime = form.cleaned_data['splittime']
splitsecond = splittime.hour*3600
splitsecond += splittime.minute*60
splitsecond += splittime.second
splitsecond += splittime.microsecond/1.e6
splitmode = form.cleaned_data['splitmode']
try:
ids,mesgs = dataprep.split_workout(
r,row,splitsecond,splitmode
)
for message in mesgs:
messages.info(request,message)
except IndexError: # pragma: no cover
messages.error(request,"Something went wrong in Split")
if request.user == r: # pragma: no cover
url = reverse('workouts_view')
else:
mgrids = [team.id for team in Team.objects.filter(manager=request.user)]
rwrids = [team.id for team in r.team.all()]
teamids = list(set(mgrids) & set(rwrids))
if len(teamids) > 0: # pragma: no cover
teamid = teamids[0]
url = reverse('workouts_view',
kwargs={
'teamid':int(teamid),
}
)
else:
url = reverse('workouts_view')
rowname = row.name
try:
if isinstance(rowname,unicode): # pragma: no cover
rowname = rowname.encode('utf8')
elif isinstance(rowname, str): # pragma: no cover
rowname = rowname.decode('utf8')
except:
pass
qdict = {'q':rowname}
url+='?'+urllib.parse.urlencode(qdict)
return HttpResponseRedirect(url)
form = WorkoutSplitForm()
# create interactive plot
try:
res = interactive_chart(encoder.decode_hex(id),promember=1)
script = res[0]
div = res[1]
except ValueError: # pragma: no cover
pass
return render(request, 'splitworkout.html',
{'form':form,
'rower':r,
'breadcrumbs':breadcrumbs,
'active':'nav-workouts',
'workout':row,
'thediv':script,
'thescript':div,
})
# Fuse two workouts
@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_fusion_view(request,id1=0,id2=1):
try:
id1 = encoder.decode_hex(id1)
id2 = encoder.decode_hex(id2)
except: # pragma: no cover
pass
r = getrower(request.user)
try:
w1 = Workout.objects.get(id=id1)
w2 = Workout.objects.get(id=id2)
r = w1.user
if (is_workout_user(request.user,w1)==False) or \
(is_workout_user(request.user,w2)==False): # pragma: no cover
raise PermissionDenied("You are not allowed to use these workouts")
except Workout.DoesNotExist: # pragma: no cover
raise Http404("One of the workouts doesn't exist")
if request.method == 'POST':
form = FusionMetricChoiceForm(request.POST,instance=w2)
if form.is_valid():
cd = form.cleaned_data
columns = cd['columns']
timeoffset = cd['offset']
posneg = cd['posneg']
if posneg == 'neg': # pragma: no cover
timeoffset = -timeoffset
# Create DataFrame
df,forceunit = dataprep.datafusion(id1,id2,columns,timeoffset)
idnew,message = dataprep.new_workout_from_df(r,df,
title='Fused data',
parent=w1,
forceunit=forceunit)
if message != None: # pragma: no cover
messages.error(request,message)
else:
successmessage = 'Data fused'
messages.info(request,message)
url = reverse('workout_edit_view',
kwargs={
'id':encoder.encode_hex(idnew),
})
return HttpResponseRedirect(url)
else:
form = FusionMetricChoiceForm(instance=w2)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,encoder.encode_hex(w1.id)),
'name': encoder.encode_hex(w1.id)
},
{
'url':reverse('workout_fusion_list',
kwargs={'id':encoder.encode_hex(id1)}),
'name': 'Sensor Fusion'
},
{
'url':reverse('workout_fusion_view',
kwargs={
'id1':encoder.encode_hex(id1),
'id2':encoder.encode_hex(id2)
}),
'name': encoder.encode_hex(w2.id)
}
]
return render(request, 'fusion.html',
{'form':form,
'teams':get_my_teams(request.user),
'workout':w1,
'rower':r,
'breadcrumbs':breadcrumbs,
'active':'nav-workouts',
'workout1':w1,
'workout2':w2,
})
# See attached courses
@login_required()
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid, raise_exception=True)
def workout_course_view(request, id):
row = get_workout_by_opaqueid(request,id)
r = getrower(request.user)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,encoder.encode_hex(row.id)),
'name': row.name
},
{
'url':reverse('workout_course_view',kwargs={'id':id}),
'name': 'Measured Courses'
}
]
courses = []
courseselectform = CourseSelectForm()
has_latlon,lat_mean,lon_mean = dataprep.workout_has_latlon(row.id)
if has_latlon:
courses = getnearestcourses([lat_mean,lon_mean],GeoCourse.objects.all(),whatisnear=25,
strict=True)
courseselectform = CourseSelectForm(choices=courses)
if request.method == 'POST':
courseselectform = CourseSelectForm(request.POST,choices=courses)
if courseselectform.is_valid():
course = courseselectform.cleaned_data['course']
# get or create a record
records = VirtualRaceResult.objects.filter(
userid=r.id,
course=course,
workoutid=row.id
)
if records:
record = records[0]
else:
# create record
record = VirtualRaceResult(
userid = r.id,
username = r.user.first_name+' '+r.user.last_name,
workoutid = row.id,
weightcategory = r.weightcategory,
adaptiveclass = r.adaptiveclass,
course = course,
distance = course.distance,
boatclass = row.workouttype,
boattype = row.boattype,
sex = r.sex,
age = calculate_age(r.birthdate),
)
record.save()
job = myqueue(
queuehigh,
handle_check_race_course,
row.csvfilename,
row.id,
course.id,
record.id,
r.user.email,
r.user.first_name,
summary=True,
successemail=True,
)
try:
request.session['async_tasks'] += [(job.id,'check_race_course')]
except KeyError:
request.session['async_tasks'] = [(job.id,'check_race_course')]
messages.info(request,'We are checking your time on the course in the background. You will receive an email when the check is complete. You can check the status <a href="/rowers/jobs-status/" target="_blank">here</a>')
# get results
records = VirtualRaceResult.objects.filter(
course__isnull=False,
workoutid=row.id,
coursecompleted=True).order_by("duration","-distance")
return render(request, 'workout_courses.html',
{'workout':row,
'rower':r,
'breadcrumbs':breadcrumbs,
'active':'nav-workouts',
'teams':get_my_teams(request.user),
'courses':courses,
'courseselectform':courseselectform,
'records':records,
})
# Edit the splits/summary
@login_required()
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_summary_edit_view(request,id,message="",successmessage=""
):
row = get_workout_by_opaqueid(request,id)
r = getrower(request.user)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,encoder.encode_hex(row.id)),
'name': row.name
},
{
'url':reverse('workout_summary_edit_view',kwargs={'id':id}),
'name': 'Edit Intervals'
}
]
s = ""
# still here - this is a workout we may edit
f1 = row.csvfilename
u = row.user.user
r = getrower(u)
powerperc = 100*np.array([r.pw_ut2,
r.pw_ut1,
r.pw_at,
r.pw_tr,r.pw_an])/r.ftp
ftp = float(r.ftp)
if row.workouttype in mytypes.otwtypes:
ftp = ftp*(100.-r.otwslack)/100.
rr = rrower(hrmax=r.max,hrut2=r.ut2,
hrut1=r.ut1,hrat=r.at,
hrtr=r.tr,hran=r.an,ftp=ftp,
powerperc=powerperc,powerzones=r.powerzones,
hrzones=r.hrzones)
rowdata = rdata(csvfile=f1,rower=rr)
if rowdata == 0: # pragma: no cover
return HttpResponse("Error: CSV Data File Not Found")
#intervalstats = rowdata.allstats()
intervalstats = row.summary
try:
itime,idist,itype = rowdata.intervalstats_values()
except TypeError: # pragma: no cover
return HttpResponse("Error: CSV Data File Not Found")
nrintervals = len(idist)
activeminutesmax = int(rowdata.duration/60.)
activeminutesmin = 0
maxminutes = activeminutesmax
savebutton = 'nosavebutton'
formvalues = {}
form = SummaryStringForm()
tss = row.rscore
normp = row.normp
normv = row.normv
normw = row.normw
try:
normspm = rowdata.df[' Cadence (stokes/min)'].mean()
except KeyError:
normspm = 0
if tss == -1:
tss,normp = dataprep.workout_rscore(row)
normv,normw = dataprep.workout_normv(row,pp=8.0)
work = int(normw)
power = int(normp)
try:
pace_secs = int(500./normv)
except (OverflowError, ZeroDivisionError):
pace_secs = 140.
try:
avpace = datetime.timedelta(seconds=int(500./normv))
except (OverflowError, ZeroDivisionError):
avpace = datetime.timedelta(seconds=130)
try:
normspm = int(normspm)
except ValueError: # pragma: no cover
normspm = 18
try:
normw = int(normw)
except ValueError: # pragma: no cover
normw = 100
try:
normp = int(normp)
except ValueError: # pragma: no cover
normp = 100
data = {
'power': normp,
'pace': avpace,
'selector': 'power',
'work': normw,
'spm': normspm,
'activeminutesmin': 0,
'activeminutesmax': activeminutesmax,
}
powerorpace = 'power'
if normp == 0:
data['selector'] = 'pace'
powerorpace = 'pace'
# looking for courses
courses = []
courseselectform = CourseSelectForm()
has_latlon,lat_mean,lon_mean = dataprep.workout_has_latlon(row.id)
if has_latlon:
courses = getnearestcourses([lat_mean,lon_mean],GeoCourse.objects.all(),whatisnear=25,
strict=True)
courseselectform = CourseSelectForm(choices=courses)
powerupdateform = PowerIntervalUpdateForm(initial=data)
if request.method == 'POST' and "course" in request.POST:
courseselectform = CourseSelectForm(request.POST,choices=courses)
if courseselectform.is_valid():
course = courseselectform.cleaned_data['course']
# get or create a record
records = VirtualRaceResult.objects.filter(
userid=r.id,
course=course,
workoutid=row.id
)
if records:
record = records[0]
else:
# create record
record = VirtualRaceResult(
userid = r.id,
username = r.user.first_name+' '+r.user.last_name,
workoutid = row.id,
weightcategory = r.weightcategory,
adaptiveclass = r.adaptiveclass,
course = course,
distance = course.distance,
boatclass = row.workouttype,
boattype = row.boattype,
sex = r.sex,
age = calculate_age(r.birthdate),
)
record.save()
job = myqueue(
queuehigh,
handle_check_race_course,
row.csvfilename,
row.id,
course.id,
record.id,
r.user.email,
r.user.first_name,
summary=True,
successemail=True,
)
try:
request.session['async_tasks'] += [(job.id,'check_race_course')]
except KeyError:
request.session['async_tasks'] = [(job.id,'check_race_course')]
messages.info(request,'We are checking your time on the course in the background. You will receive an email when the check is complete. You can check the status <a href="/rowers/jobs-status/" target="_blank">here</a>')
vals = None
# feeling lucky / ruptures
if request.method == 'POST' and "ruptures" in request.POST:
df = pd.DataFrame({
'spm':rowdata.df[' Cadence (stokes/min)'],
'power':rowdata.df[' Power (watts)'],
'v':rowdata.df[' AverageBoatSpeed (m/s)']
})
algo = rpt.Pelt(model="rbf").fit(df.values)
result = algo.predict(pen=10)
df['time'] = rowdata.df['TimeStamp (sec)'].values
try:
timeprev = int(df['time'].values[0])
timenext = int(df['time'].values[result[0]])
s = '{delta}sec'.format(delta=timenext-timeprev)
except IndexError: # pragma: no cover
s = '0sec'
for i in range(len(result)-1):
timeprev = int(df['time'].values[result[i]-1])
timenext = int(df['time'].values[result[i+1]-1])
interval = '+{delta}sec'.format(delta=timenext-timeprev)
s += interval
try:
rowdata.updateinterval_string(s)
except: # pragma: no cover
messages.error(request,"Nope, you were not lucky")
intervalstats = rowdata.allstats()
itime, idist, itype = rowdata.intervalstats_values()
nrintervals = len(idist)
savebutton = 'savestringform'
intervalString = s
form = SummaryStringForm(initial={'intervalstring':intervalString})
# We have submitted the mini language interpreter
if request.method == 'POST' and "intervalstring" in request.POST:
form = SummaryStringForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
s = cd["intervalstring"]
try:
rowdata.updateinterval_string(s)
except: # pragma: no cover
messages.error(request,'Parsing error')
intervalstats = rowdata.allstats()
itime,idist,itype = rowdata.intervalstats_values()
nrintervals = len(idist)
savebutton = 'savestringform'
powerupdateform = PowerIntervalUpdateForm(initial=data)
# we are saving the results obtained from the split by power/pace interpreter
elif request.method == 'POST' and "savepowerpaceform" in request.POST:
powerorpace = request.POST.get('powerorpace','pace')
value_pace = request.POST.get('value_pace',avpace)
value_power = request.POST.get('value_power',int(normp))
value_work = request.POST.get('value_work',int(normw))
value_spm = request.POST.get('value_spm',int(normspm))
activeminutesmin = request.POST.get('activeminutesmin',0)
try:
activeminutesmax = request.POST['activeminutesmax']
except: # pragma: no cover
pass
try:
activesecondsmin = 60.*float(activeminutesmin)
activesecondsmax = 60.*float(activeminutesmax)
except ValueError: # pragma: no cover
activesecondsmin = 0
activeminutesmin = 0
activeminutesmax = maxminutes
activesecondsmax = rowdata.duration
if abs(rowdata.duration-activesecondsmax) < 60.: # pragma: no cover
activesecondsmax = rowdata.duration
if powerorpace == 'power':
try:
power = int(value_power)
except ValueError: # pragma: no cover
int(normp)
elif powerorpace == 'pace': # pragma: no cover
try:
pace_secs = float(value_pace)
except ValueError:
try:
pace_secs = float(value_pace.replace(',','.'))
except ValueError:
pace_secs = int(500./normv)
elif powerorpace == 'work': # pragma: no cover
try:
work = int(value_work)
except ValueError:
work = int(normw)
elif powerorpace == 'spm': # pragma: no cover
try:
spm = int(value_spm)
except ValueError:
spm = int(normspm)
if powerorpace == 'power' and power is not None:
try:
vals, units, typ = rowdata.updateinterval_metric(
' Power (watts)',power,mode='larger',
debug=False,smoothwindow=15.,
activewindow=[activesecondsmin,activesecondsmax],
)
except: # pragma: no cover
messages.error(request,'Error updating power')
elif powerorpace == 'pace': # pragma: no cover
try:
velo = 500./pace_secs
vals, units, typ = rowdata.updateinterval_metric(
' AverageBoatSpeed (m/s)',velo,mode='larger',
debug=False,smoothwindow=15.,
activewindow=[activesecondsmin,activesecondsmax],
)
except:
messages.error(request,'Error updating pace')
elif powerorpace == 'work': # pragma: no cover
try:
vals, units, typ = rowdata.updateinterval_metric(
'driveenergy',work,mode='larger',
debug=False,smoothwindow=15.,
activewindow=[activesecondsmin,activesecondsmax],
)
except:
messages.error(request,'Error updating Work per Stroke')
elif powerorpace == 'spm': # pragma: no cover
try:
vals, units, typ = rowdata.updateinterval_metric(
' Cadence (stokes/min)',spm,mode='larger',
debug=False,smoothwindow=2.,
activewindow=[activesecondsmin,activesecondsmax],)
except:
messages.error(request,'Error updating SPM')
intervalString = ''
if vals is not None:
intervalString = intervals_to_string(vals, units, typ)
intervalstats = rowdata.allstats()
itime,idist,itype = rowdata.intervalstats_values()
nrintervals = len(idist)
row.summary = intervalstats
row.save()
rowdata.write_csv(f1,gzip=True)
dataprep.update_strokedata(encoder.decode_hex(id),rowdata.df)
messages.info(request,"Updated interval data saved")
data = {
'power': power,
'pace': datetime.timedelta(seconds=int(pace_secs)),
'work': work,
'selector': powerorpace,
'spm': int(normspm),
'activeminutesmin': activeminutesmin,
'activeminutesmax': activeminutesmax,
}
form = SummaryStringForm(initial={'intervalstring':intervalString})
powerupdateform = PowerIntervalUpdateForm(initial=data)
savebutton = 'savepowerpaceform'
formvalues = {
'powerorpace': powerorpace,
'value_power': power,
'value_pace': pace_secs,
'value_work':work,
}
# we are saving the results obtained from the mini language interpreter
elif request.method == 'POST' and "savestringform" in request.POST:
s = request.POST["savestringform"]
try:
rowdata.updateinterval_string(s)
except (ParseException,err): # pragma: no cover
messages.error(request,'Parsing error in column '+str(err.col))
intervalstats = rowdata.allstats()
itime,idist,itype = rowdata.intervalstats_values()
nrintervals = len(idist)
row.summary = intervalstats
#intervalstats = rowdata.allstats()
if s:
try:
row.notes = u'{n} \n {s}'.format(
n = row.notes,
s = s
)
except TypeError: # pragma: no cover
pass
row.save()
rowdata.write_csv(f1,gzip=True)
dataprep.update_strokedata(encoder.decode_hex(id),rowdata.df)
messages.info(request,"Updated interval data saved")
data = {'intervalstring':s}
form = SummaryStringForm(initial=data)
powerupdateform = PowerIntervalUpdateForm(initial={
'power': int(normp),
'pace': avpace,
'selector': 'power',
'work': int(normw),
'spm': int(normspm),
'activeminutesmin': 0,
'activeminutesmax': activeminutesmax,
})
savebutton = 'savestringform'
# we are saving the results obtained from the power update form
elif request.method == 'POST' and 'selector' in request.POST:
powerupdateform = PowerIntervalUpdateForm(request.POST)
if powerupdateform.is_valid():
cd = powerupdateform.cleaned_data
powerorpace = cd['selector']
power = cd['power']
pace = cd['pace']
work = cd['work']
spm = cd['spm']
activeminutesmin = cd['activeminutesmin']
activeminutesmax = cd['activeminutesmax']
activesecondsmin = 60.*activeminutesmin
activesecondsmax = 60.*activeminutesmax
if abs(rowdata.duration-activesecondsmax) < 60.: # pragma: no cover
activesecondsmax = rowdata.duration
try:
pace_secs = pace.seconds+pace.microseconds/1.0e6
except AttributeError: # pragma: no cover
pace_secs = 120.
if powerorpace == 'power' and power is not None:
vals, units, typ = rowdata.updateinterval_metric(' Power (watts)',power,mode='larger',
debug=False,smoothwindow=15,
activewindow=[activesecondsmin,activesecondsmax],
)
elif powerorpace == 'pace': # pragma: no cover
try:
velo = 500./pace_secs
vals, units, typ = rowdata.updateinterval_metric(' AverageBoatSpeed (m/s)',velo,mode='larger',
debug=False,smoothwindow=15,
activewindow=[activesecondsmin,activesecondsmax],
)
except:
messages.error(request,'Error updating pace')
elif powerorpace == 'work': # pragma: no cover
try:
vals, units, typ = rowdata.updateinterval_metric(
'driveenergy',work,mode='larger',
debug=False,smoothwindow=15.,
activewindow=[activesecondsmin,activesecondsmax],)
except:
messages.error(request,'Error updating Work per Stroke')
elif powerorpace == 'spm': # pragma: no cover
try:
vals, units, typ = rowdata.updateinterval_metric(' Cadence (stokes/min)',spm,mode='larger',
debug=False,smoothwindow=2.,
activewindow=[activesecondsmin,activesecondsmax],
)
except:
messages.error(request,'Error updating SPM')
intervalString = ''
if vals is not None:
intervalString = intervals_to_string(vals, units, typ)
intervalstats = rowdata.allstats()
itime,idist,itype = rowdata.intervalstats_values()
nrintervals = len(idist)
savebutton = 'savepowerpaceform'
formvalues = {
'powerorpace': powerorpace,
'value_power': power,
'value_pace': pace_secs,
'value_work': work,
'value_spm': spm,
'activeminutesmin': activeminutesmin,
'activeminutesmax': activeminutesmax,
}
powerupdateform = PowerIntervalUpdateForm(initial=cd)
form = SummaryStringForm(initial={'intervalstring':intervalString})
# create interactive plot
try:
intervaldata = {
'itime':itime,
'idist':idist,
'itype':itype,
'selector': powerorpace,
'normp': normp,
'normv': normv,
}
res = interactive_chart(encoder.decode_hex(id),promember=1,intervaldata=intervaldata)
script = res[0]
div = res[1]
except ValueError: # pragma: no cover
script = ''
div = ''
# render page
return render(request, 'summary_edit.html',
{'form':form,
'activeminutesmax':activeminutesmax,
'activeminutesmin':activeminutesmin,
'maxminutes': maxminutes,
'powerupdateform':powerupdateform,
'workout':row,
'rower':r,
'breadcrumbs':breadcrumbs,
'active':'nav-workouts',
'teams':get_my_teams(request.user),
'intervalstats':intervalstats,
'nrintervals':nrintervals,
'interactiveplot':script,
'the_div':div,
'intervalstring':s,
'savebutton':savebutton,
'formvalues':formvalues,
'courses':courses,
'courseselectform':courseselectform,
})
class VideoDelete(DeleteView):
login_required = True
model = VideoAnalysis
template_name = 'video_delete_confirm.html'
# extra parameters
def get_context_data(self, **kwargs):
context = super(VideoDelete, self).get_context_data(**kwargs)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(
self.request,
encoder.encode_hex(self.object.workout.id)),
'name': self.object.workout.name
},
{
'url':reverse('workout_video_view',kwargs={'id':encoder.encode_hex(self.object.id)}),
'name': 'Video Analysis'
},
{ 'url':reverse('video_delete',kwargs={'pk':str(self.object.pk)}),
'name': 'Delete'
}
]
context['active'] = 'nav-workouts'
context['rower'] = getrower(self.request.user)
context['breadcrumbs'] = breadcrumbs
return context
def get_success_url(self):
w = self.object.workout
try:
w = Workout.objects.get(id=w.id)
except Workout.DoesNotExist: # pragma: no cover
return reverse('workouts_view')
return reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)})
def get_object(self, *args, **kwargs):
obj = super(VideoDelete, self).get_object(*args, **kwargs)
if not is_coach_user(self.request.user,obj.workout.user.user): # pragma: no cover
raise PermissionDenied('You are not allowed to delete this analysis')
return obj
class GraphDelete(DeleteView):
login_required = True
model = GraphImage
template_name = 'graphimage_delete_confirm.html'
# extra parameters
def get_context_data(self, **kwargs):
context = super(GraphDelete, self).get_context_data(**kwargs)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(
self.request,
encoder.encode_hex(self.object.workout.id)),
'name': self.object.workout.name
},
{
'url':reverse('graph_show_view',kwargs={'id':self.object.pk}),
'name': 'Chart'
},
{ 'url':reverse('graph_delete',kwargs={'pk':str(self.object.pk)}),
'name': 'Delete'
}
]
context['active'] = 'nav-workouts'
context['rower'] = getrower(self.request.user)
context['breadcrumbs'] = breadcrumbs
return context
def get_success_url(self):
w = self.object.workout
try:
w = Workout.objects.get(id=w.id)
except Workout.DoesNotExist: # pragma: no cover
return reverse('workouts_view')
return reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)})
def get_object(self, *args, **kwargs):
obj = super(GraphDelete, self).get_object(*args, **kwargs)
if not is_workout_user(self.request.user,obj.workout): # pragma: no cover
raise PermissionDenied('You are not allowed to delete this chart')
return obj
class WorkoutDelete(PermissionRequiredMixin,DeleteView):
login_required = True
model = Workout
template_name = 'workout_delete_confirm.html'
permission_required = 'workout.change_workout'
# extra parameters
def get_context_data(self, **kwargs):
context = super(WorkoutDelete, self).get_context_data(**kwargs)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(self.request,encoder.encode_hex(self.object.id)),
'name': encoder.encode_hex(self.object.id)
},
{ 'url':reverse('workout_delete',kwargs={'pk':str(self.object.pk)}),
'name': 'Delete'
}
]
mayedit=0
promember=0
if not self.request.user.is_anonymous:
r = getrower(self.request.user)
result = self.request.user.is_authenticated and ispromember(self.request.user)
if result:
promember=1
if self.request.user == self.object.user.user:
mayedit=1
context['active'] = 'nav-workouts'
context['rower'] = getrower(self.request.user)
context['breadcrumbs'] = breadcrumbs
context['workout'] = self.object
context['mayedit'] = mayedit
context['promember'] = promember
return context
def get_success_url(self):
return reverse('workouts_view')
def get_object(self, *args, **kwargs):
workout_pk = self.kwargs['pk']
try:
obj = Workout.objects.get(pk=workout_pk)
except (ValueError,Workout.DoesNotExist):
workout_pk = encoder.decode_hex(workout_pk)
try:
obj = Workout.objects.get(pk=workout_pk)
except Workout.DoesNotExist: # pragma: no cover
raise Http404("One of the workouts doesn't exist")
# obj = super(WorkoutDelete, self).get_object(*args, **kwargs)
return obj