7110 lines
237 KiB
Python
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
|