Private
Public Access
1
0
Files
rowsandall/rowers/views/workoutviews.py
2024-05-26 14:51:21 +02:00

6867 lines
235 KiB
Python

from shutil import copyfile
from rowers.views.statements import *
import rowers.mytypes as mytypes
from rowers.integrations import importsources
import numpy
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 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(data['latitude'], data['longitude'])
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(data['latitude'], data['longitude'])
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(data['latitude'], data['longitude'])
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",
redirect_field_name=None)
@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True)
def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly=False):
row = get_workoutuser(id, request)
mayedit = 0
r = getrequestrower(request, userid=userid)
if r == row.user:
mayedit = 1
if analysis: # pragma: no cover
try:
forceanalysis = ForceCurveAnalysis.objects.get(id=analysis)
dist_min = forceanalysis.dist_min
dist_max = forceanalysis.dist_max
spm_min = forceanalysis.spm_min
spm_max = forceanalysis.spm_max
work_min = forceanalysis.work_min
work_max = forceanalysis.work_max
notes = forceanalysis.notes
name = forceanalysis.name
plotcircles = forceanalysis.plotcircles
plotlines = forceanalysis.plotlines
includereststrokes = forceanalysis.include_rest_strokes
except (ForceCurveAnalysis.DoesNotExist, ValueError):
pass
else:
dist_min = 0
dist_max = row.distance
spm_min = 15
spm_max = 55
work_min = 0
work_max = 1500
notes = ''
includereststrokes = False
name = ''
plotcircles = True
plotlines = False
form = ForceCurveOptionsForm(initial={
'spm_min': spm_min,
'spm_max': spm_max,
'dist_min': dist_min,
'dist_max': dist_max,
'work_min': work_min,
'work_max': work_max,
'notes': notes,
'name': name,
'plotcircles': plotcircles,
'plotlines': plotlines,
})
if request.method == 'POST':
form = ForceCurveOptionsForm(request.POST)
if form.is_valid():
spm_min = form.cleaned_data['spm_min']
spm_max = form.cleaned_data['spm_max']
dist_min = form.cleaned_data['dist_min']
dist_max = form.cleaned_data['dist_max']
work_min = form.cleaned_data['work_min']
work_max = form.cleaned_data['work_max']
notes = form.cleaned_data['notes']
name = form.cleaned_data['name']
plotlines = form.cleaned_data['plotlines']
plotcircles = form.cleaned_data['plotcircles']
if not name:
name = row.name
includereststrokes = form.cleaned_data['includereststrokes']
workstrokesonly = not includereststrokes
if "_save" in request.POST and "new" not in request.POST: # pragma: no cover
if not analysis:
forceanalysis = ForceCurveAnalysis(
workout = row,
name = name,
date = timezone.now().date(),
notes = notes,
dist_min = dist_min,
dist_max = dist_max,
work_min = work_min,
work_max = work_max,
spm_min = spm_min,
spm_max = spm_max,
rower=row.user,
plotlines = plotlines,
plotcircles = plotcircles,
include_rest_strokes = includereststrokes,
)
else:
forceanalysis.workout = row
forceanalysis.name = name
forceanalysis.date = timezone.now().date()
forceanalysis.notes = notes
forceanalysis.dist_min = dist_min
forceanalysis.dist_max = dist_max
forceanalysis.work_min = work_min
forceanalysis.work_max = work_max
forceanalysis.spm_min = spm_min
forceanalysis.spm_max = spm_max
forceanalysis.plotcircles = plotcircles
forceanalysis.plotlines = plotlines
forceanalysis.include_rest_strokes = includereststrokes
forceanalysis.save()
dosave = True
messages.info(request,'Force Curve analysis saved')
if "_save_as_new" in request.POST:
forceanalysis = ForceCurveAnalysis(
workout = row,
name = name,
date = timezone.now().date(),
notes = notes,
dist_min = dist_min,
dist_max = dist_max,
spm_min = spm_min,
spm_max = spm_max,
work_min = work_min,
work_max = work_max,
rower=row.user,
include_rest_strokes = includereststrokes,
)
forceanalysis.save()
dosave = True
messages.info(request,'Force Curve analysis saved')
else: # pragma: no cover
workstrokesonly = True
script, div = interactive_forcecurve(
[row]
)
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)
if dist_max == 0:
dist_max = row.distance+100
return render(request,
'forcecurve_single.html',
{
'the_script': script,
'rower': r,
'form': form,
'workout': row,
'breadcrumbs': breadcrumbs,
'active': 'nav-workouts',
'spm_min': spm_min,
'spm_max': spm_max,
'dist_min': dist_min,
'dist_max': dist_max,
'work_min': work_min,
'work_max': work_max,
'annotation': notes,
'the_div': div,
'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)
# 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']
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)
seatnumber = form.cleaned_data.get('seatnumber', 1)
boatname = form.cleaned_data.get('boatname', '')
empowerside = form.cleaned_data.get('empowerside','port')
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.name = name
w.rpe = rpe
w.workouttype = workouttype
w.boattype = boattype
w.boatname = boatname
w.empowerside = empowerside
w.seatnumber = seatnumber
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:
_ = 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)
# Selecting duplicates
@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 workouts_duplicates_select_view(request, userid=0):
r = getrequestrower(request, userid=userid)
if request.method == 'POST': # pragma: no cover
form = WorkoutMultipleCompareForm(request.POST)
if form.is_valid():
workouts = form.cleaned_data['workouts']
for w in workouts:
try:
w.delete()
except OperationalError:
pass
workouts = Workout.objects.filter(user=r,duplicate=True).order_by("-startdatetime")[:19]
form = WorkoutMultipleCompareForm()
form.fields["workouts"].queryset = workouts
breadcrumbs = [
{
'url': '/rowers/list-workouts/',
'name': 'Workouts'
},
{'url': reverse('workouts_duplicates_select_view'),
'name': 'Select Duplicates'
}
]
return render(request,'workout_duplicates_select.html',
{
'workouts':workouts,
'rower':r,
'form':form,
'breadcrumbs': breadcrumbs,
})
# 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",
redirect_field_name=None)
@permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True)
def workouts_join_view(request, userid=0):
r = getrequestrower(request, userid=userid)
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,
'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: # pragma: no cover
modalities = options['modalities']
except KeyError: # pragma: no cover
modalities = [m[0] for m in mytypes.workouttypes_ordered.items()]
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:
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)
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",
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])
for b in mytypes.ergtypes:
if b[0] not in waterboattype: # pragma: no cover
negtypes.append(b[0])
startdate = datetime.datetime.combine(startdate, datetime.time())
enddate = datetime.datetime.combine(enddate, datetime.time(23, 59, 59))
if 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)
_ = dataprep.dataplep(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",
redirect_field_name=None)
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: # pragma: no cover
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']
ids = [int(w.id) for w in workouts]
request.session['ids'] = ids
elif request.method == 'POST': # pragma: no cover
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']
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
try:
labeldict = {
int(w.id): w.__str__() for w in workouts
}
except: # pragma: no cover
labeldict = {}
script, div = interactive_multiple_compare_chart(workoutids, xparam, yparam,
promember=promember,
plottype=plottype,
labeldict=labeldict, startenddict=startenddict)
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)
xparam = 'time'
yparam = 'pace'
if request.method in ('GET','HEAD'):
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: # pragma: no cover
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']
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']
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
try:
labeldict = {
int(w.id): w.__str__() for w in workouts
}
except: # pragma: no cover
labeldict = {}
script, div = interactive_multiple_compare_chart(workoutids, xparam, yparam,
promember=promember,
plottype=plottype,
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_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):
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]
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('analysis_new',
kwargs={
'session': ps.id,
'id': encoder.encode_hex(ids[0]),
'function': 'compare'})
else:
url = reverse('plannedsession_view', kwargs={'id': ps.id})
return HttpResponseRedirect(url)
# set RPE for list of workouts
@login_required()
def workouts_setrpe_view(request,userid=0):
today = timezone.now()
startdate = today-timezone.timedelta(days=32)
enddate = today+timezone.timedelta(days=1)
r = getrequestrower(request,userid=userid)
startdate = datetime.datetime.combine(startdate, datetime.time())
enddate = datetime.datetime.combine(enddate, datetime.time(23, 59, 59))
startdate = pytz.utc.localize(startdate)
enddate = pytz.utc.localize(enddate)
workouts = Workout.objects.filter(
user=r,rpe=0,
duplicate=False,
startdatetime__gte=startdate,
startdatetime__lte=enddate
).order_by("-date", "-starttime")[0:20]
dateform = DateRangeForm(initial={
'startdate': startdate,
'enddate': enddate,
})
WorkoutsRPEFormSet = modelformset_factory(
Workout,
form=WorkoutRPEForm,
)
if request.method == 'POST': # pragma: no cover
dateform = DateRangeForm(request.POST)
rpe_formset = WorkoutsRPEFormSet(request.POST,
queryset=Workout.objects.filter(user=r,rpe=0,duplicate=False,
startdatetime__gte=startdate,
startdatetime__lte=enddate
)
)
if dateform.is_valid(): # pragma: no cover
startdate = dateform.cleaned_data['startdate']
enddate = dateform.cleaned_data['enddate']
if rpe_formset.is_valid():
for item in rpe_formset.cleaned_data:
rpe = item['rpe']
workout = item['id']
if workout:
workout.rpe = rpe
workout.save()
workouts = Workout.objects.filter(
user=r,rpe=0,
duplicate=False,
startdatetime__gte=startdate,
startdatetime__lte=enddate
).order_by("-date", "-starttime")[0:20]
rpe_formset = WorkoutsRPEFormSet(queryset=workouts)
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)
if enddate < startdate: # pragma: no cover
s = enddate
enddate = startdate
startdate = s
today = timezone.now()
announcements = SiteAnnouncement.objects.filter(
expires__gte=today
).order_by(
"-created",
"-id"
)
breadcrumbs = [
{
'url': '/rowers/list-workouts/',
'name': 'Workouts'
},
{
'url': '/rowers/workouts/setrpe/',
'name': 'Set RPE'
}
]
return render(request,'workouts_rpe.html',
{
'workouts': workouts,
'active': 'nav-workouts',
'rower': r,
'breadcrumbs': breadcrumbs,
'dateform': dateform,
'startdate': startdate,
'enddate': enddate,
'rpe_formset': rpe_formset,
})
# Workout bulk actions
@login_required()
def workouts_bulk_actions(request):
r = getrower(request.user)
action = request.session.get('action','export')
workoutids = request.session.get('ids',[])
workouts = []
exportchoice = 'strava'
try:
for encid in workoutids:
w = get_workout_by_opaqueid(request, encid)
if w.user == r:
workouts.append(w)
else: # pragma: no cover
messages.error(request,'Bulk actions are not accessible to coaches')
except KeyError: # pragma: no cover
pass
if request.method == 'POST':
actionform = WorkoutBulkActions(request.POST)
form = WorkoutMultipleCompareForm(request.POST)
if form.is_valid() and actionform.is_valid():
workouts = form.cleaned_data['workouts']
if len(workouts) == 0: # pragma: no cover
url = reverse('workouts_view')
return HttpResponseRedirect(url)
action = actionform.cleaned_data['action']
if action == 'remove':
for w in workouts:
messages.info(request,'Removed workout '+str(encoder.encode_hex(w.id)))
w.delete()
elif action == 'export':
exportchoice = ExportChoices(request.POST)
if exportchoice.is_valid():
destination = exportchoice.cleaned_data['destination']
for w in workouts:
integration = importsources[destination](request.user)
try:
id = integration.workout_export(w)
messages.info(request,
'Workout {id} exported to {destination}'.format(
id=encoder.encode_hex(w.id),
destination=destination))
except NoTokenError: # pragma: no cover
messages.error(request,
'Export to {destination} of workout {id} failed'.format(
id=encoder.encode_hex(w.id),
destination=destination))
url = reverse('workouts_view')
return HttpResponseRedirect(url)
else: # pragma: no cover
if len(workouts) == 0:
url = reverse(workouts_view)
return HttpResponseRedirect(url)
else:
exportchoice = ExportChoices()
actionform = WorkoutBulkActions()
actionform.fields["action"].initial = action
form = WorkoutMultipleCompareForm()
form.fields["workouts"].queryset = Workout.objects.filter(id__in=[w.id for w in workouts])
form.fields["workouts"].initial = workouts
return render(request,'workout_bulk_actions.html',
{'action':action,
'exportchoice':exportchoice,
'actionform':actionform,
'form':form,
'workouts':workouts})
# List Workouts
@login_required()
def workouts_view(request, message='', successmessage='',
teamid=0, 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':
if 'selectworkouts' in request.POST:
request.session['action']=request.POST['action']
request.session['ids'] = request.POST.getlist('workoutid')
url = reverse('workouts_bulk_actions')
return HttpResponseRedirect(url)
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)
if enddate < startdate: # pragma: no cover
s = enddate
enddate = startdate
startdate = s
# 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))
workoutsnohr = workouts.exclude(averagehr__isnull=False)
for w in workoutsnohr: # pragma: no cover
_ = dataprep.workout_trimp(w)
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, 12) # 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_activitychart2(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')
norpecount = len([w for w in workouts if w.rpe==0 and not w.duplicate])
if norpecount and r.get_rpe_warnings: # pragma: no cover
messages.info(request,'You have workouts with no RPE value set. \
Click <a href="/rowers/workouts/setrpe">here</a> to update them. \
You can switch off this warning in <a href="/rowers/me/edit">settings</a>.')
actionform = WorkoutBulkActions()
return render(request, 'list_workouts.html',
{'workouts': workouts,
'active': 'nav-workouts',
'rower': r,
'actionform': actionform,
'searchform': searchform,
'breadcrumbs': breadcrumbs,
'dateform': dateform,
'startdate': startdate,
'enddate': enddate,
'announcements': announcements[0:4],
'team': theteam,
'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",
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))
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
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
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
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",
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.",
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 'originalvelo' not in row.df:
row.df['originalvelo'] = velo
velo2 = utils.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:
_ = 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])
starttimeunix = int(arrow.get(row.startdatetime).timestamp())
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:
_ = 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])
starttimeunix = arrow.get(row.startdatetime).timestamp()
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
@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'
}
]
rowdata = rrdata(csvfile=w.csvfilename)
try:
instrokemetrics = rowdata.get_instroke_columns()
instrokemetrics = [m for m in instrokemetrics if m not 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:
job = myqueue(queuelow,
instroke_static,w, metric)
r = getrower(request.user)
url = reverse(r.defaultlandingpage,
kwargs={
'id': id,
})
return HttpResponseRedirect(url)
@user_passes_test(ispromember, login_url="/rowers/paidplans",
message="This functionality requires a Pro plan or higher."
" If you are already a Pro user, please log in to access this functionality",
redirect_field_name=None)
@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True)
def instroke_data(request, metric='', spm_min=15, spm_max=45, activeminutesmin=0, activeminutesmax=0, id=0, ): # pragma: no cover
r = getrequestrower(request, userid=0)
w = get_workoutuser(id, request)
rowdata = rrdata(csvfile=w.csvfilename)
instrokemetrics = rowdata.get_instroke_columns()
if not metric:
metric = instrokemetrics[0]
if activeminutesmax == 0:
activeminutesmax = rowdata.duration/60.
try:
spm_min = float(spm_min)
except ValueError:
pass
try:
spm_max = float(spm_max)
except ValueError:
pass
try:
activeminutesmin = float(activeminutesmin)
except ValueError:
pass
try:
activeminutesmax = float(activeminutesmax)
except ValueError:
pass
factor = 1
if metric == 'boat accelerator curve':
factor = g_acc
data = rowdata.get_instroke_data(metric, spm_min=spm_min,
spm_max=spm_max,
activeminutesmin=activeminutesmin,
activeminutesmax=activeminutesmax,
factor=factor,
)
filename = str(uuid4())+'.csv'
data.to_csv(filename)
with open(filename, 'r') as f:
response = HttpResponse(f)
response['Content-Disposition'] = 'attachment; filename="%s"' % filename
response['Content-Type'] = 'application/octet-stream'
os.remove(filename)
return response
@user_passes_test(ispromember, login_url="/rowers/paidplans",
message="This functionality requires a Pro plan or higher."
" If you are already a Pro user, please log in to access this functionality",
redirect_field_name=None)
@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True)
def instroke_chart_interactive(request, id=0, analysis=0, userid=0):
is_ajax = request_is_ajax(request)
r = getrequestrower(request, userid=userid)
w = get_workoutuser(id, request)
rowdata = rrdata(csvfile=w.csvfilename)
instrokemetrics = rowdata.get_instroke_columns()
form = InstrokeForm(choices=instrokemetrics)
breadcrumbs = [
{
'url': '/rowers/list-workouts/',
'name': 'Workouts'
},
{
'url': get_workout_default_page(request, id),
'name': w.name
},
{
'url': reverse('instroke_chart_interactive', kwargs={'id': id}),
'name': 'In-Stroke Metrics'
}
]
if not instrokemetrics:
script = ''
div = 'No Instroke Metrics available for this workout'
spm_min = 15
spm_max = 45
ds = ''
dd = ''
activeminutesmin = 0
activeminutesmax = int(rowdata.duration/60.)
maxminutes = activeminutesmax
return render(request,
'instroke_interactive.html',
{
'workout': w,
'rower': w.user,
'active': 'nav-workouts',
'breadcrumbs': breadcrumbs,
'teams': get_my_teams(request.user),
'instrokemetrics': instrokemetrics,
'form':form,
'the_script': script,
'the_div': div,
'spm_min': spm_min,
'spm_max': spm_max,
'ds': ds,
'dd': dd,
'activeminutesmin': activeminutesmin,
'activeminutesmax': activeminutesmax,
'maxminutes': maxminutes,
})
if analysis: # pragma: no cover
try:
instroke_analysis = InStrokeAnalysis.objects.get(id=analysis)
if instroke_analysis.rower != r:
analysis = 0
messages.error(request,'Access to this saved analysis denied')
raise ValueError
if instroke_analysis.workout != w:
messages.error(request,'This saved analysis belongs to a different workout')
form = InstrokeForm(
choices=instrokemetrics,
initial={
'metric':instroke_analysis.metric,
'name': instroke_analysis.name,
'notes': instroke_analysis.notes,
'activeminutesmin':int(instroke_analysis.start_second/60.),
'activeminutesmax':int(instroke_analysis.end_second/60.),
'spm_min': instroke_analysis.spm_min,
'spm_max': instroke_analysis.spm_max,
}
)
metric = instroke_analysis.metric
name = instroke_analysis.name
notes = instroke_analysis.notes
activeminutesmin = int(instroke_analysis.start_second/60.)
activeminutesmax = int(instroke_analysis.end_second/60.)
spm_min = instroke_analysis.spm_min
spm_max = instroke_analysis.spm_max
except (InStrokeAnalysis.DoesNotExist, ValueError):
metric = instrokemetrics[0]
spm_min = 15
spm_max = 45
name = ''
notes = ''
activeminutesmax = int(rowdata.duration/60.)
activeminutesmin = 0
else:
metric = instrokemetrics[0]
spm_min = 15
spm_max = 45
name = ''
notes = ''
activeminutesmax = int(rowdata.duration/60.)
activeminutesmin = 0
maxminutes = int(rowdata.duration/60.)
individual_curves = False
script = ''
div = get_call()
dosave = False
if request.method == 'POST':
form = InstrokeForm(request.POST,choices=instrokemetrics)
if form.is_valid():
metric = form.cleaned_data['metric']
spm_min = form.cleaned_data['spm_min']
spm_max = form.cleaned_data['spm_max']
activeminutesmin = form.cleaned_data['activeminutesmin']
activeminutesmax = form.cleaned_data['activeminutesmax']
individual_curves = form.cleaned_data['individual_curves']
notes = form.cleaned_data['notes']
name = form.cleaned_data['name']
if "_save" in request.POST and "new" not in request.POST:
if not analysis:
instroke_analysis = InStrokeAnalysis(
workout = w,
metric = metric,
name = name,
date = timezone.now().date(),
notes = notes,
start_second = 60*activeminutesmin,
end_second = 60*activeminutesmax,
spm_min = spm_min,
spm_max = spm_max,
rower=w.user,
)
else:
instroke_analysis.workout = w
instroke_analysis.metric = metric
instroke_analysis.name = name
instroke_analysis.date = timezone.now().date()
instroke_analysis.notes = notes
instroke_analysis.start_second = 60*activeminutesmin
instroke_analysis.end_second = 60*activeminutesmax
instroke_analysis.spm_min = spm_min
instroke_analysis.spm_max = spm_max
instroke_analysis.rower=w.user
instroke_analysis.save()
dosave = True
messages.info(request,'In-Stroke Analysis saved')
if "_save_as_new" in request.POST:
instroke_analysis = InStrokeAnalysis(
workout = w,
metric = metric,
name = name,
date = timezone.now().date(),
notes = notes,
start_second = 60*activeminutesmin,
end_second = 60*activeminutesmax,
spm_min = spm_min,
spm_max = spm_max,
rower=w.user,
)
instroke_analysis.save()
dosave = True
messages.info(request,'In-Stroke Analysis saved')
activesecondsmin = 60.*activeminutesmin
activesecondsmax = 60.*activeminutesmax
factor = 1
if metric == 'boat accelerator curve':
factor = g_acc
data = rowdata.get_instroke_data(
metric,
spm_min=spm_min,
spm_max=spm_max,
activeminutesmin=activeminutesmin,
activeminutesmax=activeminutesmax,
factor=factor,
)
if metric == 'boat accelerator curve' and data.median().max() < 0.5:
data = 9.81*data
if metric == 'instroke boat speed' and data.median().max() < 0.15:
rowdata.add_instroke_speed()
rowdata.write_csv(w.csvfilename, gzip=True)
data = rowdata.get_instroke_data(
metric,
spm_min=spm_min,
spm_max=spm_max,
activeminutesmin=activeminutesmin,
activeminutesmax=activeminutesmax,
)
script, div = instroke_interactive_chart(
data, metric, w,
spm_min,
spm_max,
activeminutesmin,
activeminutesmax,
individual_curves,
name=name,notes=notes,
)
# change to range spm_min to spm_max
vals, units, typ = rowdata.updateinterval_range(
' Cadence (stokes/min)', spm_min, spm_max,
debug=False, smoothwindow=2.,
activewindow=[activesecondsmin, activesecondsmax])
intervalstats = rowdata.allstats()
itime, idist, itype = rowdata.intervalstats_values()
totaldist = 0
totaltime = 0
avg_speed = 0
for i in range(len(idist)):
if itype[i] == 4:
totaldist += idist[i]
totaltime += itime[i]
if totaltime > 0:
avg_speed = totaldist/totaltime
if dosave:
instroke_analysis.average_boatspeed = avg_speed
instroke_analysis.save()
intervaldata = {
'itime': itime,
'idist': idist,
'itype': itype,
'selector': '',
'normp': 0,
'normv': 0,
}
ds, dd = interactive_chart(encoder.decode_hex(
id), promember=1, intervaldata=intervaldata)
if is_ajax:
response = json.dumps({
'script': script,
'div': div,
'ds': ds,
'dd': dd,
})
return HttpResponse(response, content_type='application/json')
return render(request,
'instroke_interactive.html',
{
'workout': w,
'rower': w.user,
'active': 'nav-workouts',
'breadcrumbs': breadcrumbs,
'teams': get_my_teams(request.user),
'instrokemetrics': instrokemetrics,
'form':form,
'metric': metric,
'the_script': script,
'the_div': div,
'spm_min': spm_min,
'spm_max': spm_max,
'ds': ds,
'dd': dd,
'activeminutesmin': activeminutesmin,
'activeminutesmax': activeminutesmax,
'maxminutes': maxminutes,
})
# erase column
@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True)
def workout_erase_column_view(request, id=0, column=''):
r = getrower(request.user)
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.read_data([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:
_ = 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
try:
os.remove(w.csvfilename+'.gz')
except FileNotFoundError:
try:
os.remove(w.csvfilename)
except FileNotFoundError:
pass
row.write_csv(w.csvfilename, gzip=True)
row, workout = dataprep.getrowdata(id=w.id)
_ = dataprep.dataplep(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,
'rower': r,
'breadcrumbs': breadcrumbs,
}
)
# resample to 1s intervals
@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True)
def workout_resample_view(request, id=0):
r = getrower(request.user)
w = get_workoutuser(id, request)
form = ResampleForm()
if request.method == 'POST':
form = ResampleForm(request.POST)
if form.is_valid():
overwrite = form.cleaned_data['resamplechoice']
datadf, id, msgs = dataprep.resample(
encoder.decode_hex(id), r, w, overwrite=overwrite)
for message in msgs:
messages.info(request, message)
url = get_workout_default_page(request, encoder.encode_hex(id))
messages.info(
request, 'The workout has been resampled: <a href="{url}">here</a>'.format(url=url))
breadcrumbs = [
{
'url': '/rowers/list-workouts/',
'name': 'Workouts'
},
{
'url': get_workout_default_page(request, id),
'name': w.name
},
{
'url': reverse('workout_resample_view', kwargs={'id': id}),
'name': 'Resample Data'
}
]
return render(request,
'workout_resample.html',
{
'form': form,
'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_all = [
'id', 'time', 'hr_an', 'hr_at', 'hr_bottom', 'hr_max',
'hr_tr', 'hr_ut1', 'hr_ut2', 'x_right',
]
to_be_dropped = []
for c in to_be_dropped_all:
if c in columns:
to_be_dropped.append(c)
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
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,
'rower': r,
'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")
# 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
datadf = datadf.select_dtypes([np.number])
# 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': int(trimp),
'unit': ''
}
otherstats['hrScore'] = {
'verbose_name': 'rScore (HR)',
'value': int(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']
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 is not 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
row = get_workout_by_opaqueid(request, id)
r = getrower(request.user)
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_chart(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:
_ = favorites[favoritenr].xparam
except IndexError: # pragma: no cover
favoritenr = 0
except AssertionError:
favoritenr = 0
except ValueError:
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 'trendline' in kwargs: # pragma: no cover
trendline = kwargs['trendline']
else:
trendline = False
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:
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']
trendline = cd['trendline']
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, workstrokesonly
) = interactive_flex_chart2(
encoder.decode_hex(id), request.user.rower,
xparam=xparam, yparam1=yparam1,
yparam2=yparam2,
promember=promember, plottype=plottype,
workstrokesonly=workstrokesonly,
trendline=trendline,
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 m not 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)
initial = {
'includereststrokes': not workstrokesonly,
'plottype': plottype,
'trendline': trendline,
}
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,
'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
) = interactive_flexchart_stacked(
encoder.decode_hex(id), r, xparam=xparam,
yparam1=yparam1,
yparam2=yparam2,
yparam3=yparam3,
yparam4=yparam4,
mode=workout.workouttype,
)
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,
'id': id,
'xparam': xparam,
'yparam1': yparam1,
'yparam2': yparam2,
'yparam3': yparam3,
'yparam4': yparam4,
}
)
# The interactive plot with wind corrected pace for OTW outings
#
@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'))
_ = 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)
_ = 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['referer'] = absolute(request)['PATH']
row = get_workoutuser(id, request)
if request.user.rower.rowerplan == 'basic' and 'speedcoach2' in row.workoutsource: # pragma: no cover
data = read_data(['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 = read_data(['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)
seatnumber = form.cleaned_data.get('seatnumber', 1)
boatname = form.cleaned_data.get('boatname', '')
empowerside = form.cleaned_data.get('empowerside','port')
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
_ = 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
row.boatname = boatname
row.empowerside = empowerside
row.seatnumber = seatnumber
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['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_chart(rowdata.df[' latitude'],
rowdata.df[' longitude'],
w.name)
else:
mapscript = ""
mapdiv = ""
mayedit = 0
if not request.user.is_anonymous:
r = getrower(request.user)
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):
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:
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:
dologging('own_api.log','Missing credentials')
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:
dologging('own_api.log','no filename given')
message = {'status': 'false', 'message': 'no filename given'}
return JSONResponse(status=400, data=message)
except FileNotFoundError:
dologging('own_api.log','could not find file')
message = {'status': 'false', 'message': 'could not find file'}
return JSONResponse(status=400, data=message)
# sync related IDs
sporttracksid = post_data.get('sporttracksid','')
c2id = post_data.get('c2id', '')
garminid = post_data.get('garminid','')
workoutid = post_data.get('id','')
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)
seatnumber = post_data.get('seatNumber', 1)
boatname = post_data.get('boatName','')
portStarboard = post_data.get('portStarboard', 1)
empowerside = 'port'
if portStarboard == 1:
empowerside = 'starboard'
totalDistance = post_data.get('totalDistance', None)
elapsedTime = post_data.get('elapsedTime', None)
summary = post_data.get('summary', None)
timezone = post_data.get('timezone', 'UTC')
s = 'Posting c2id {c2id} to Rowsandall. Startdatetime {startdatetime}, time zone {timezone}'.format(
c2id=c2id,
startdatetime=startdatetime,
timezone=timezone,
)
dologging('c2_log.log', s)
r = None
if form.is_valid():
t = form.cleaned_data['title']
t = re.sub('\r',' ',t)
t = re.sub('\n',' ',t)
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
dologging('own_api.log','could not find user by email')
message = {'status': 'false',
'message': 'could not find user'}
return JSONResponse(status=400, data=message)
if r is None: # pragma: no cover
dologging('own_api.log','invalid user')
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']
makeprivate = optionsform.cleaned_data['makeprivate']
else: # pragma: no cover
dologging('own_api.log','invalid options form')
dologging('own_api.log',json.dumps(optionsform.errors))
message = optionsform.errors
return JSONResponse(status=400, data=message)
if r is None: # pragma: no cover
dologging('own_api.log','r is None')
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,
workoutid=workoutid,
empowerside=empowerside,
boatname=boatname,
seatnumber=seatnumber,
)
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
dologging('own_api.log','Invalid DocumentsForm')
dologging('own_api.log',json.dumps(form.errors))
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),
}
)
_ = 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:
_ = uploadoptions['landingpage']
except KeyError: # pragma: no cover
uploadoptions['landingpage'] = 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:
uploadoptions['secret'] = settings.UPLOAD_SERVICE_SECRET
uploadoptions['user'] = r.user.id
uploadoptions['title'] = t
uploadoptions['file'] = f2
url = settings.UPLOAD_SERVICE_URL
_ = myqueue(queuehigh,
handle_request_post,
url,
uploadoptions
)
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 is not 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:
c2integration = C2Integration(request.user)
id = c2integration.workout_export(w)
except NoTokenError:
id = 0
message = "Something went wrong with the Concept2 sync"
messages.error(request, message)
if (upload_to_strava): # pragma: no cover
strava_integration = StravaIntegration(request.user)
try:
id = strava_integration.workout_export(w)
except NoTokenError:
id = 0
message = "Please connect to Strava first"
messages.error(request, message)
if (upload_to_st): # pragma: no cover
st_integration = SportTracksIntegration(request.user)
try:
id = st_integration.workout_export(w)
except NoTokenError:
message = "Please connect to SportTracks first"
id = 0
messages.error(request, message)
if (upload_to_tp): # pragma: no cover
tp_integration = TPIntegration(request.user)
try:
id = tp_integration.workout_export(w)
except NoTokenError:
message = "Please connect to TrainingPeaks first"
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, userid=0, 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'
}
]
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']
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
_ = 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(userid=userid)
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)
# 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()
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) is False) or \
(is_workout_user(request.user, w2) is 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 is not None: # pragma: no cover
messages.error(request, message)
else:
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: # pragma: no cover
# 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: # pragma: no cover
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 = 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: # pragma: no cover
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:
try:
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})
except KeyError:
messages.error(request, "Nope, you were not lucky")
# 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
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:
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):
try:
workout_pk = self.kwargs['pk']
except KeyError:
workout_pk = self.kwargs['id']
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