from shutil import copyfile from rowers.views.statements import * import rowers.mytypes as mytypes 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_video(data['latitude'], data['longitude'], w.name) else: mapscript, mapdiv = interactive_chart_video(data) data['longitude'] = data['spm'] data['latitude'] = list(range(len(data['spm']))) breadcrumbs = [ { 'url': '/rowers/list-workouts/', 'name': 'Workouts' }, { 'url': get_workout_default_page(request, encoder.encode_hex(w.id)), 'name': w.name }, { 'url': reverse('workout_video_view_mini', kwargs={'id': encoder.encode_hex(analysis.id)}), 'name': 'Video Analysis' } ] return render(request, 'embedded_video_mini.html', { 'workout': w, 'rower': rower, 'data': json.dumps(data, default=default), 'mapscript': mapscript, 'mapdiv': mapdiv, 'video_id': analysis.video_id, 'form': form, 'breadcrumbs': breadcrumbs, 'analysis': analysis, 'maxtime': maxtime, 'metrics': metrics, 'locked': True, 'metricsform': metricsform, 'metricsgroups': metricsgroups, 'siteurl': settings.SITE_URL, }) # Show a video compared with data def workout_video_view(request, id=''): try: id = encoder.decode_hex(id) analysis = VideoAnalysis.objects.get(id=id) except (VideoAnalysis.DoesNotExist, ValueError): # pragma: no cover raise Http404("Video Analysis does not exist") w = analysis.workout delay = analysis.delay if w.workouttype in mytypes.otwtypes: mode = 'water' else: mode = 'erg' if request.user.is_authenticated: mayedit = is_promember( request.user) and is_workout_user(request.user, w) rower = request.user.rower else: mayedit = False rower = None # get video ID and offset if mayedit and request.method == 'POST': form = VideoAnalysisCreateForm(request.POST) metricsform = VideoAnalysisMetricsForm(request.POST, mode=mode) if form.is_valid() and metricsform.is_valid(): video_id = form.cleaned_data['url'] try: video_id = get_video_id(form.cleaned_data['url']) except (TypeError, ValueError): # pragma: no cover pass delay = form.cleaned_data['delay'] metricsgroups = metricsform.cleaned_data['groups'] if 'save_button' in request.POST: analysis.name = form.cleaned_data['name'] analysis.video_id = video_id analysis.delay = delay analysis.metricsgroups = metricsgroups analysis.save() else: # pragma: no cover video_id = id delay = 0 elif mayedit: form = VideoAnalysisCreateForm( initial={ 'name': analysis.name, 'delay': analysis.delay, 'url': analysis.video_id, } ) metricsform = VideoAnalysisMetricsForm(initial={'groups': analysis.metricsgroups}, mode=mode) metricsgroups = analysis.metricsgroups video_id = analysis.video_id else: form = None metricsform = None metricsgroups = analysis.metricsgroups data, metrics, maxtime = dataprep.get_video_data( w, groups=metricsgroups, mode=mode) hascoordinates = pd.Series(data['latitude']).std() > 0 # create map if hascoordinates and mode == 'water': mapscript, mapdiv = leaflet_chart_video(data['latitude'], data['longitude'], w.name) else: mapscript, mapdiv = interactive_chart_video(data) data['longitude'] = data['spm'] data['latitude'] = list(range(len(data['spm']))) breadcrumbs = [ { 'url': '/rowers/list-workouts/', 'name': 'Workouts' }, { 'url': get_workout_default_page(request, encoder.encode_hex(w.id)), 'name': w.name }, { 'url': reverse('workout_video_view', kwargs={'id': encoder.encode_hex(analysis.id)}), 'name': 'Video Analysis' } ] return render(request, 'embedded_video.html', { 'workout': w, 'rower': rower, 'data': json.dumps(data, default=default), 'mapscript': mapscript, 'mapdiv': mapdiv, 'video_id': analysis.video_id, 'form': form, 'breadcrumbs': breadcrumbs, 'analysis': analysis, 'maxtime': maxtime, 'metrics': metrics, 'locked': True, 'metricsform': metricsform, 'metricsgroups': metricsgroups, 'siteurl': settings.SITE_URL, }) # Create a video compared with data @permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) @user_passes_test(ispromember, login_url="/rowers/paidplans/", message="This functionality requires a Pro plan or higher", redirect_field_name=None) def workout_video_create_view(request, id=0): # get workout w = get_workout_by_opaqueid(request, id) if w.workouttype in mytypes.otwtypes: mode = 'water' else: mode = 'erg' # get video ID and offset if request.method == 'POST': form = VideoAnalysisCreateForm(request.POST) metricsform = VideoAnalysisMetricsForm(request.POST, mode=mode) if form.is_valid() and metricsform.is_valid(): url = form.cleaned_data['url'] delay = form.cleaned_data['delay'] metricsgroups = metricsform.cleaned_data['groups'] try: video_id = get_video_id(url) except ValueError: # pragma: no cover messages.error(request, "Not a valid YouTube video link") video_id = None if 'save_button' in request.POST: analysis = VideoAnalysis( workout=w, name=form.cleaned_data['name'], video_id=video_id, delay=delay, metricsgroups=metricsgroups ) try: analysis.save() url = reverse('workout_video_view', kwargs={'id': encoder.encode_hex(analysis.id)}) return HttpResponseRedirect(url) except IntegrityError: # pragma: no cover messages.error( request, 'You cannot save two video analysis with the same YouTube video and Workout." \ " Redirecting to your existing analysis') analysis = VideoAnalysis.objects.filter( workout=w, video_id=video_id) if analysis: url = reverse('workout_video_view', kwargs={'id': encoder.encode_hex(analysis[0].id)}) else: url = reverse('workout_video_create_view', kwargs={'id': encoder.encode_hex(w.id)}) return HttpResponseRedirect(url) else: # pragma: no cover video_id = None delay = 0 metricsgroups = ['basic'] else: form = VideoAnalysisCreateForm() metricsform = VideoAnalysisMetricsForm( initial={'groups': ['basic']}, mode=mode) video_id = None delay = 0 metricsgroups = ['basic'] # get data data, metrics, maxtime = dataprep.get_video_data( w, groups=metricsgroups, mode=mode) hascoordinates = pd.Series(data['latitude']).std() > 0 # create map if hascoordinates and mode == 'water': mapscript, mapdiv = leaflet_chart_video(data['latitude'], data['longitude'], w.name) else: mapscript, mapdiv = interactive_chart_video(data) data['longitude'] = data['spm'] data['latitude'] = list(range(len(data['spm']))) breadcrumbs = [ { 'url': '/rowers/list-workouts/', 'name': 'Workouts' }, { 'url': get_workout_default_page(request, encoder.encode_hex(w.id)), 'name': w.name }, { 'url': reverse('workout_video_create_view', kwargs={'id': encoder.encode_hex(w.id)}), 'name': 'Video Analysis' } ] analysis = {'delay': delay} template = 'embedded_video.html' return render(request, template, { 'workout': w, 'rower': request.user.rower, 'data': json.dumps(data, default=default), 'mapscript': mapscript, 'mapdiv': mapdiv, 'video_id': video_id, 'form': form, 'metricsform': metricsform, 'analysis': analysis, 'breadcrumbs': breadcrumbs, 'maxtime': maxtime, 'metrics': metrics, 'metricsgroups': metricsgroups, 'locked': False, 'siteurl': settings.SITE_URL, }) # Show the EMpower Oarlock generated Stroke Profile @user_passes_test(ispromember, login_url="/rowers/paidplans/", message="This functionality requires a Pro plan or higher." " If you are already a Pro user, please log in to access this functionality", redirect_field_name=None) @permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) def workout_forcecurve_view(request, id=0, workstrokesonly=False): row = get_workoutuser(id, request) mayedit = 0 r = getrequestrower(request) if r == row.user: mayedit = 1 if request.method == 'POST': form = ForceCurveOptionsForm(request.POST) if form.is_valid(): includereststrokes = form.cleaned_data['includereststrokes'] plottype = form.cleaned_data['plottype'] workstrokesonly = not includereststrokes else: # pragma: no cover workstrokesonly = True plottype = 'line' else: form = ForceCurveOptionsForm() plottype = 'line' script, div, js_resources, css_resources = interactive_forcecurve( [row], workstrokesonly=workstrokesonly, plottype=plottype, ) breadcrumbs = [ { 'url': '/rowers/list-workouts/', 'name': 'Workouts' }, { 'url': get_workout_default_page(request, id), 'name': row.name }, { 'url': reverse('workout_forcecurve_view', kwargs={'id': id}), 'name': 'Empower Force Curve' } ] r = getrower(request.user) return render(request, 'forcecurve_single.html', { 'the_script': script, 'rower': r, 'form': form, 'workout': row, 'breadcrumbs': breadcrumbs, 'active': 'nav-workouts', 'the_div': div, 'js_res': js_resources, 'css_res': css_resources, 'id': id, 'mayedit': mayedit, 'teams': get_my_teams(request.user), }) # Switch from GPS to Impeller (only for SpeedCoach 2, if impeller data) @permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) def otw_use_impeller(request, id=0): w = get_workoutuser(id, request) row = rdata(csvfile=w.csvfilename) success = row.use_impellerdata() if success: row.write_csv(w.csvfilename) dataprep.update_strokedata(w.id, row.df) w.impeller = True w.save() messages.info( request, 'The distance and speed data are now based on Impeller data') else: messages.error(request, 'No impeller data found') url = reverse('workout_edit_view', kwargs={'id': encoder.encode_hex(w.id)}) return HttpResponseRedirect(url) # Switch from Impeller to GPS (only for SpeedCoach 2, if impeller data) @permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) def otw_use_gps(request, id=0): w = get_workoutuser(id, request) row = rdata(csvfile=w.csvfilename) success = row.use_gpsdata() if success: row.write_csv(w.csvfilename) dataprep.update_strokedata(w.id, row.df) w.impeller = False w.save() messages.info( request, 'The distance and speed data are now based on GPS data') else: messages.error(request, 'No GPS data found') url = reverse('workout_edit_view', kwargs={'id': encoder.encode_hex(w.id)}) return HttpResponseRedirect(url) # Show Stroke power histogram for a workout @login_required() @permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) def workout_histo_view(request, id=0): w = get_workoutuser(id, request) r = getrequestrower(request) mayedit = 0 if w.user == r: mayedit = 1 res = interactive_histoall([w], 'power', False) script = res[0] div = res[1] breadcrumbs = [ { 'url': '/rowers/list-workouts/', 'name': 'Workouts' }, { 'url': get_workout_default_page(request, id), 'name': w.name }, { 'url': reverse('workout_histo_view', kwargs={'id': id}), 'name': 'Histogram' } ] return render(request, 'histo_single.html', {'interactiveplot': script, 'breadcrumbs': breadcrumbs, 'active': 'nav-workouts', 'workout': w, 'rower': r, 'the_div': div, 'id': id, 'mayedit': mayedit, 'teams': get_my_teams(request.user), }) # add a workout manually @login_required() def addmanual_view(request, raceid=0): r = Rower.objects.get(user=request.user) breadcrumbs = [ { 'url': '/rowers/list-workouts/', 'name': 'Workouts' }, { 'url': reverse('addmanual_view'), 'name': 'Add Manual Entry' }, ] if request.method == 'POST': # Form was submitted form = WorkoutForm(request.POST) metricsform = MetricsForm(request.POST) if form.is_valid() and metricsform.is_valid(): # Get values from form name = form.cleaned_data['name'] if name == '': # pragma: no cover name = 'Manual Entry' date = form.cleaned_data['date'] starttime = form.cleaned_data['starttime'] workouttype = form.cleaned_data['workouttype'] duration = form.cleaned_data['duration'] weightcategory = form.cleaned_data['weightcategory'] adaptiveclass = form.cleaned_data['adaptiveclass'] distance = form.cleaned_data['distance'] try: rpe = form.cleaned_data['rpe'] if not rpe: rpe = -1 except KeyError: # pragma: no cover rpe = -1 notes = form.cleaned_data['notes'] thetimezone = form.cleaned_data['timezone'] private = form.cleaned_data['private'] avghr = metricsform.cleaned_data['avghr'] avgpwr = metricsform.cleaned_data['avgpwr'] avgspm = metricsform.cleaned_data['avgspm'] boattype = form.cleaned_data.get('boattype', '1x') privacy = form.cleaned_data.get('privacy', 'visible') rankingpiece = form.cleaned_data.get('rankingpiece', False) duplicate = form.cleaned_data.get('duplicate', False) if private: # pragma: no cover privacy = 'private' else: privacy = 'visible' startdatetime = (str(date) + ' ' + str(starttime)) startdatetime = datetime.datetime.strptime(startdatetime, "%Y-%m-%d %H:%M:%S") startdatetime = timezone.make_aware(startdatetime) startdatetime = startdatetime.astimezone( pytz.timezone(thetimezone) ) id, message = dataprep.create_row_df(r, distance, duration, startdatetime, rpe=rpe, weightcategory=weightcategory, adaptiveclass=adaptiveclass, avghr=avghr, rankingpiece=rankingpiece, avgpwr=avgpwr, duplicate=duplicate, avgspm=avgspm, title=name, notes=notes, workouttype=workouttype) if message: # pragma: no cover messages.error(request, message) if id: w = Workout.objects.get(id=id) w.rankingpiece = rankingpiece w.privacy = privacy w.weightcategory = weightcategory w.adaptiveclass = adaptiveclass w.notes = notes w.name = name w.rpe = rpe w.workouttype = workouttype w.boattype = boattype w.distance = distance w.duration = duration w.save() # if ps: # add_workouts_plannedsession([w],ps,w.user) messages.info(request, 'New workout created') iform = ImageForm(request.POST, request.FILES) if iform.is_valid(): # this works but cannot get the tests to work f = iform.cleaned_data['file'] if f is not None: # pragma: no cover filename, path_and_filename = handle_uploaded_image(f) try: width, height = Image.open(path_and_filename).size except: message = "Not a valid image" messages.error(request, message) os.remove(path_and_filename) i = GraphImage(workout=w, creationdatetime=timezone.now(), filename=path_and_filename, width=width, height=height) i.save() if raceid != 0: try: race = VirtualRace.objects.get(id=raceid) except VirtualRace.DoesNotExist: # pragma: no cover messages.error(request, "Race does not exist") url = reverse('workout_edit_view', kwargs={'id': encoder.encode_hex(id)}) return HttpResponseRedirect(url) can_submit = race_can_submit( r, race) or race_can_resubmit(r, race) can_submit = can_submit and race.sessiontype == 'indoorrace' if not can_submit: # pragma: no cover messages.error( request, 'You cannot submit a result for this race') if can_submit: records = IndoorVirtualRaceResult.objects.filter( race=race, userid=r.id) if not records: # pragma: no cover messages.error( request, 'You have to register for the race first') url = reverse('virtualevent_view', kwargs={'id': race.id}) return HttpResponseRedirect(url) recordid = records[0].id result, comments, errors, jobid = add_workout_indoorrace( [w], race, r, recordid=recordid ) for c in comments: # pragma: no cover messages.info(request, c) for er in errors: # pragma: no cover messages.error(request, er) if result: otherrecords = IndoorVirtualRaceResult.objects.filter( race=race ).exclude(userid=r.id) for otherrecord in otherrecords: try: # pragma: no cover otheruser = Rower.objects.get( id=otherrecord.userid) othername = otheruser.user.first_name+' '+otheruser.user.last_name registeredname = r.user.first_name+' '+r.user.last_name if otherrecord.emailnotifications: _ = 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): r = getrequestrower(request) if request.method == 'POST': 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]) 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.dataprep(row.df, id=workout.id) cpdf, delta, cpvalues = dataprep.setcp(workout) workout.normp = 0 workout.rscore = 0 workout.save() dataprep.initiate_cp(r) url = reverse(r.defaultlandingpage, kwargs={ 'id': id, } ) return HttpResponseRedirect(url) # Team comparison @user_passes_test(ispromember, login_url='/rowers/paidplans/', message="This functionality requires a Pro plan or higher." " If you are already a Pro user, please log in to access this functionality", 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 labeldict = { int(w.id): w.__str__() for w in workouts } res = interactive_multiple_compare_chart(workoutids, xparam, yparam, promember=promember, plottype=plottype, labeldict=labeldict, startenddict=startenddict) script = res[0] div = res[1] errormessage = res[3] if errormessage != '': # pragma: no cover messages.error(request, errormessage) breadcrumbs = [ { 'url': reverse('courses_view'), 'name': 'Courses' }, { 'url': reverse('course_view', kwargs={ 'id': course.id, } ), 'name': course.name }, { 'url': reverse('virtualevent_compare_view', kwargs={ 'id': course.id, } ), 'name': 'Compare' } ] return render(request, 'multicompare.html', {'interactiveplot': script, 'the_div': div, 'breadcrumbs': breadcrumbs, 'rower': r, 'course': course, 'results': results, 'active': 'nav-racing', 'promember': promember, 'teamid': 0, 'chartform': chartform, 'form': form, 'teams': [] }) def virtualevent_compare_view(request, id=0): results = [] promember = 0 if not request.user.is_anonymous: r = getrower(request.user) result = request.user.is_authenticated and ispromember(request.user) if result: promember = 1 else: # pragma: no cover r = None try: race = VirtualRace.objects.get(id=id) except VirtualRace.DoesNotExist: # pragma: no cover raise Http404("Virtual Challenge does not exist") if race.sessiontype == 'race': script, div = course_map(race.course) resultobj = VirtualRaceResult else: # pragma: no cover script = '' div = '' resultobj = IndoorVirtualRaceResult results = resultobj.objects.filter( race=race, workoutid__isnull=False, coursecompleted=True, ).order_by("duration", "-distance") workoutids = [result.workoutid for result in results] startenddict = {} if race.sessiontype == 'race': for result in results: startenddict[result.workoutid] = ( result.startsecond, result.endsecond) if len(workoutids) == 0: # pragma: no cover url = reverse('virtualevent_view', kwargs={ 'id': race.id, }) messages.info(request, 'There are no results to display') return HttpResponseRedirect(url) if request.method == 'GET': xparam = 'time' if race.sessionmode == 'distance': xparam = 'cumdist' yparam = 'pace' plottype = 'line' request.session['ids'] = workoutids request.session['plottype'] = plottype request.session['xparam'] = xparam request.session['yparam'] = yparam workouts = [] for id in workoutids: try: workouts.append(Workout.objects.get(id=id)) except Workout.DoesNotExist: # pragma: no cover pass form = WorkoutMultipleCompareForm() form.fields["workouts"].queryset = Workout.objects.filter( id__in=workoutids) form.fields["workouts"].initial = Workout.objects.filter( id__in=workoutids) labeldict = { int(w.id): w.__str__() for w in workouts } chartform = ChartParamChoiceForm( initial={ 'xparam': xparam, 'yparam': yparam, 'plottype': plottype, 'teamid': 0 } ) if request.method == 'POST' and 'workouts' in request.POST: # 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 labeldict = { int(w.id): w.__str__() for w in workouts } res = interactive_multiple_compare_chart(workoutids, xparam, yparam, promember=promember, plottype=plottype, labeldict=labeldict, startenddict=startenddict) script = res[0] div = res[1] errormessage = res[3] if errormessage != '': # pragma: no cover messages.error(request, errormessage) breadcrumbs = [ { 'url': reverse('virtualevents_view'), 'name': 'Challenges' }, { 'url': reverse('virtualevent_view', kwargs={ 'id': race.id, } ), 'name': race.name }, { 'url': reverse('virtualevent_compare_view', kwargs={ 'id': race.id, } ), 'name': 'Compare' } ] return render(request, 'multicompare.html', {'interactiveplot': script, 'the_div': div, 'breadcrumbs': breadcrumbs, 'rower': r, 'race': race, 'results': results, 'active': 'nav-racing', 'promember': promember, 'teamid': 0, 'chartform': chartform, 'form': form, 'teams': [] }) @permission_required('plannedsession.view_session', fn=get_session_by_pk, raise_exception=True) def plannedsession_compare_view(request, id=0, userid=0): 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) # 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': 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) # ids = [w.id for w in workouts] # df = dataprep.getsmallrowdata_db(['time','power'],ids=ids) # polarization = dataprep.polarization_index(df,r) if query: # pragma: no cover query_list = query.split() workouts = workouts.filter( reduce(operator.and_, (Q(name__icontains=q) for q in query_list)) | reduce(operator.and_, (Q(notes__icontains=q) for q in query_list)) ) searchform = SearchForm(initial={'q': query}) else: searchform = SearchForm() paginator = Paginator(workouts, 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_activitychart(g_workouts, g_startdate, g_enddate, stack=stack, yaxis=yaxis) totalmeters, totalhours, totalminutes, total_seconds = get_totals( g_workouts) totalminutes = '{totalminutes:02d}'.format(totalminutes=totalminutes) messages.info(request, successmessage) messages.error(request, message) breadcrumbs = [ { 'url': '/rowers/list-workouts/', 'name': 'Workouts' }, ] timeperiod = startdate.strftime( '%Y-%m-%d')+'/'+enddate.strftime('%Y-%m-%d') return render(request, 'list_workouts.html', {'workouts': workouts, 'active': 'nav-workouts', 'rower': r, 'searchform': searchform, 'breadcrumbs': breadcrumbs, 'dateform': dateform, 'startdate': startdate, 'enddate': enddate, 'announcements': announcements[0:4], 'team': theteam, '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 = stravastuff.ewmovingaverage(velo, 5) pace2 = 500./abs(velo2) row.df[' Stroke500mPace (sec/500m)'] = pace2 row.df = row.df.fillna(0) row.write_csv(filename, gzip=True) dataprep.update_strokedata(encoder.decode_hex(id), row.df) messages.info( request, 'A smoothening filter was applied to your pace data') if previousurl: # pragma: no cover url = previousurl else: url = reverse(r.defaultlandingpage, kwargs={ 'id': id, } ) return HttpResponseRedirect(url) # Get weather for given location and date/time @permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) @user_passes_test(ispromember, login_url="/rowers/paidplans", message="This functionality requires a Pro plan or higher." " If you are already a Pro user, please log in to access this functionality", redirect_field_name=None) def workout_downloadwind_view(request, id=0, airportcode=None, message="", successmessage=""): row = get_workoutuser(id, request) f1 = row.csvfilename # create bearing rowdata = rdata(csvfile=f1) if rowdata == 0: # pragma: no cover return HttpResponse("Error: CSV Data File Not Found") try: _ = 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 # Show form to update wind data @permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) @user_passes_test(ispromember, login_url="/rowers/paidplans", message="This functionality requires a Pro plan or higher." " If you are already a Pro user, please log in to access this functionality", redirect_field_name=None) def workout_wind_view(request, id=0, message="", successmessage=""): row = get_workoutuser(id, request) r = getrower(request.user) breadcrumbs = [ { 'url': '/rowers/list-workouts/', 'name': 'Workouts' }, { 'url': get_workout_default_page(request, id), 'name': row.name }, { 'url': reverse('workout_wind_view', kwargs={'id': id}), 'name': 'Wind' } ] # get data f1 = row.csvfilename u = row.user.user r = getrower(u) # create bearing rowdata = rdata(csvfile=f1) if row == 0: # pragma: no cover return HttpResponse("Error: CSV Data File Not Found") hascoordinates = 1 try: latitude = rowdata.df.loc[:, ' latitude'] except KeyError: hascoordinates = 0 if hascoordinates and not latitude.std(): # pragma: no cover hascoordinates = 0 try: _ = rowdata.df.loc[:, 'bearing'].values except KeyError: rowdata.add_bearing() rowdata.write_csv(f1, gzip=True) if hascoordinates: avglat = rowdata.df[' latitude'].mean() avglon = rowdata.df[' longitude'].mean() airportcode, newlat, newlon, airportdistance = get_airport_code( avglat, avglon) airportcode = airportcode.upper() airportdistance = airportdistance[0] else: airportcode = 'UNKNOWN' airportdistance = 0 if request.method == 'POST': # process form form = UpdateWindForm(request.POST) if form.is_valid(): vwind1 = form.cleaned_data['vwind1'] vwind2 = form.cleaned_data['vwind2'] dist1 = form.cleaned_data['dist1'] dist2 = form.cleaned_data['dist2'] winddirection1 = form.cleaned_data['winddirection1'] winddirection2 = form.cleaned_data['winddirection2'] windunit = form.cleaned_data['windunit'] rowdata.update_wind(vwind1, vwind2, winddirection1, winddirection2, dist1, dist2, units=windunit) rowdata.write_csv(f1, gzip=True) else: # pragma: no cover message = "Invalid Form" messages.error(request, message) kwargs = { 'id': id } url = reverse('workout_wind_view', kwargs=kwargs) _ = HttpResponseRedirect(url) else: form = UpdateWindForm() # create interactive plot res = interactive_windchart(encoder.decode_hex(id), promember=1) script = res[0] div = res[1] if hascoordinates: gmscript, gmdiv = leaflet_chart( rowdata.df[' latitude'], rowdata.df[' longitude'], row.name) else: gmscript = "" gmdiv = "No GPS data available" messages.info(request, successmessage) messages.error(request, message) return render(request, 'windedit.html', {'workout': row, 'rower': r, 'breadcrumbs': breadcrumbs, 'active': 'nav-workouts', 'teams': get_my_teams(request.user), 'interactiveplot': script, 'form': form, 'airport': airportcode, 'airportdistance': airportdistance, 'the_div': div, 'gmap': gmscript, 'gmapdiv': gmdiv}) # Show form to update River stream data (for river dwellers) @permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) @user_passes_test(ispromember, login_url="/rowers/paidplans", message="This functionality requires a Pro plan or higher." " If you are already a Pro user, please log in to access this functionality", redirect_field_name=None) def workout_stream_view(request, id=0, message="", successmessage=""): row = get_workoutuser(id, request) r = getrower(request.user) # create interactive plot f1 = row.csvfilename u = row.user.user r = getrower(u) rowdata = rdata(csvfile=f1) if rowdata == 0: # pragma: no cover messages.info(request, "Error: CSV data file not found") url = reverse('workout_edit_view', kwargs={ 'id': encoder.encode_hex(row.id)}) return HttpResponseRedirect(url) if request.method == 'POST': # process form form = UpdateStreamForm(request.POST) if form.is_valid(): dist1 = form.cleaned_data['dist1'] dist2 = form.cleaned_data['dist2'] stream1 = form.cleaned_data['stream1'] stream2 = form.cleaned_data['stream2'] streamunit = form.cleaned_data['streamunit'] rowdata.update_stream(stream1, stream2, dist1, dist2, units=streamunit) rowdata.write_csv(f1, gzip=True) else: # pragma: no cover message = "Invalid Form" messages.error(request, message) kwargs = { 'id': id} url = reverse('workout_wind_view', kwargs=kwargs) _ = HttpResponseRedirect(url) else: form = UpdateStreamForm() # create interactive plot res = interactive_streamchart(encoder.decode_hex(id), promember=1) script = res[0] div = res[1] breadcrumbs = [ { 'url': '/rowers/list-workouts/', 'name': 'Workouts' }, { 'url': get_workout_default_page(request, id), 'name': row.name }, { 'url': reverse('workout_stream_view', kwargs={'id': id}), 'name': 'Stream' } ] messages.info(request, successmessage) messages.error(request, message) return render(request, 'streamedit.html', {'workout': row, 'rower': r, 'breadcrumbs': breadcrumbs, 'active': 'nav-workouts', 'teams': get_my_teams(request.user), 'interactiveplot': script, 'form': form, 'the_div': div}) # Form to set average crew weight and boat type, then run power calcs @permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) @user_passes_test(ispromember, login_url="/rowers/paidplans", redirect_field_name=None) def workout_otwsetpower_view(request, id=0, message="", successmessage=""): w = get_workoutuser(id, request) r = getrower(request.user) mayedit = 1 if request.method == 'POST': # process form form = AdvancedWorkoutForm(request.POST) if form.is_valid(): boattype = form.cleaned_data['boattype'] weightvalue = form.cleaned_data['weightvalue'] coastalbrand = form.cleaned_data['boatbrand'] boatclass = w.workouttype w.boattype = boattype w.weightvalue = weightvalue w.boatbrand = coastalbrand w.save() # load row data & create power/wind/bearing columns if not set f1 = w.csvfilename rowdata = rdata(csvfile=f1) if rowdata == 0: # pragma: no cover return HttpResponse("Error: CSV Data File Not Found") try: _ = rowdata.df['vstream'] except KeyError: rowdata.add_stream(0) rowdata.write_csv(f1, gzip=True) try: _ = rowdata.df['bearing'] except KeyError: rowdata.add_bearing() rowdata.write_csv(f1, gzip=True) try: _ = rowdata.df['vwind'] except KeyError: rowdata.add_wind(0, 0) rowdata.write_csv(f1, gzip=True) # do power calculation (asynchronous) r = w.user u = r.user first_name = u.first_name last_name = u.last_name emailaddress = u.email job = myqueue(queue, handle_otwsetpower, f1, boattype, boatclass, coastalbrand, weightvalue, first_name, last_name, emailaddress, encoder.decode_hex( id), ps=[r.p0, r.p1, r.p2, r.p3], ratio=r.cpratio, # quick_calc = quick_calc, # go_service=go_service, emailbounced=r.emailbounced ) try: request.session['async_tasks'] += [(job.id, 'otwsetpower')] except KeyError: request.session['async_tasks'] = [(job.id, 'otwsetpower')] successmessage = 'Your calculations have been submitted." \ " You will receive an email when they are done." \ " You can check the status of your calculations" \ " here' messages.info(request, successmessage) kwargs = { 'id': id} try: url = request.session['referer'] except KeyError: url = reverse('workout_edit_view', kwargs=kwargs) response = HttpResponseRedirect(url) return response else: # pragma: no cover message = "Invalid Form" messages.error(request, message) kwargs = { 'id': id} url = reverse('workout_otwsetpower_view', kwargs=kwargs) response = HttpResponseRedirect(url) else: form = AdvancedWorkoutForm(instance=w) breadcrumbs = [ { 'url': '/rowers/list-workouts/', 'name': 'Workouts' }, { 'url': get_workout_default_page(request, id), 'name': w.name }, { 'url': reverse('workout_otwsetpower_view', kwargs={'id': id}), 'name': 'OTW Power' } ] messages.error(request, message) messages.info(request, successmessage) return render(request, 'otwsetpower.html', {'workout': w, 'rower': w, 'mayedit': mayedit, 'active': 'nav-workouts', 'breadcrumbs': breadcrumbs, 'teams': get_my_teams(request.user), 'form': form, }) @permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) def instroke_view(request, id=0): w = get_workoutuser(id, request) r = getrower(request.user) mayedit = 1 breadcrumbs = [ { 'url': '/rowers/list-workouts/', 'name': 'Workouts' }, { 'url': get_workout_default_page(request, id), 'name': w.name }, { 'url': reverse('instroke_view', kwargs={'id': id}), 'name': 'In-Stroke Metrics' } ] 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: f1 = w.csvfilename[6:-4] timestr = strftime("%Y%m%d-%H%M%S") imagename = f1+timestr+'.png' fullpathimagename = 'static/plots/'+imagename u = w.user.user r = getrower(u) fig1 = rowdata.get_plot_instroke(metric) canvas = FigureCanvas(fig1) canvas.print_figure('static/plots/'+imagename) plt.close(fig1) fig1.clf() gc.collect() try: width, height = Image.open(fullpathimagename).size except: width = 1200 height = 600 imgs = GraphImage.objects.filter(workout=w) if imgs.count() < 7: i = GraphImage(workout=w, creationdatetime=timezone.now(), filename=fullpathimagename, width=width, height=height) i.save() else: messages.error( request, 'You have reached the maximum number of static images for this workout. Delete an image first') r = getrower(request.user) url = reverse(r.defaultlandingpage, kwargs={ 'id': id, }) return HttpResponseRedirect(url) # erase column @permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) def workout_erase_column_view(request, id=0, column=''): r = getrower(request.user) w = get_workoutuser(id, request) protected = ['time', 'pace', 'velo', 'cumdist', 'ftime', 'fpace', ] if column in protected: # pragma: no cover messages.error(request, 'You cannot erase this protected column') url = reverse('workout_data_view', kwargs={ 'id': encoder.encode_hex(w.id) }) return HttpResponseRedirect(url) try: data = dataprep.getsmallrowdata_db([column], ids=[w.id]) except: # pragma: no cover messages.error(request, 'Invalid column') url = reverse('workout_data_view', kwargs={ 'id': encoder.encode_hex(w.id) }) return HttpResponseRedirect(url) try: _ = data[column] except KeyError: # pragma: no cover url = reverse('workout_data_view', kwargs={ 'id': encoder.encode_hex(w.id) }) return HttpResponseRedirect(url) if not column: # pragma: no cover url = reverse('workout_data_view', kwargs={ 'id': encoder.encode_hex(w.id) }) return HttpResponseRedirect(url) if request.method == 'POST': mms = {} for m in rowingmetrics: mms[m[0]] = m[1] try: defaultvalue = mms[column]['default'] except KeyError: if not mms[column]['null']: # pragma: no cover messages.error( request, 'You cannot erase this protected column') url = reverse('workout_data_view', kwargs={ 'id': encoder.encode_hex(w.id) }) return HttpResponseRedirect(url) defaultvalue = 0 try: columnl = dataprep.columndict[column] except KeyError: # pragma: no cover messages.error(request, 'You cannot erase this column') url = reverse('workout_data_view', kwargs={ 'id': encoder.encode_hex(w.id) }) return HttpResponseRedirect(url) row, workout = dataprep.getrowdata(id=w.id) row.df[columnl] = defaultvalue os.remove(w.csvfilename+'.gz') row.write_csv(w.csvfilename, gzip=True) row, workout = dataprep.getrowdata(id=w.id) _ = dataprep.dataprep(row.df, id=w.id) if column == 'hr': w.hrtss = 0 w.trimp = 0 w.save() if column == 'power': # pragma: no cover w.rscore = 0 w.normp = 0 w.goldmedalstandard = -1 w.goldmedalseconds = 0 w.save() trimp, hrtss = dataprep.workout_trimp(w, reset=True) rscore, normp = dataprep.workout_rscore(w, reset=True) goldstandard, goldstandardduration = dataprep.workout_goldmedalstandard( w, reset=True) messages.info(request, 'Data for column '+column+' have been erased') url = reverse('workout_data_view', kwargs={ 'id': encoder.encode_hex(w.id) }) return HttpResponseRedirect(url) breadcrumbs = [ { 'url': '/rowers/list-workouts/', 'name': 'Workouts' }, { 'url': get_workout_default_page(request, id), 'name': w.name }, { 'url': reverse('workout_data_view', kwargs={'id': id}), 'name': 'Data Explorer' } ] return render(request, 'workout_erase_column.html', { 'column': column, 'teams': get_my_teams(request.user), 'workout': w, '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: here'.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 = [ 'id', 'time', 'hr_an', 'hr_at', 'hr_bottom', 'hr_max', 'hr_tr', 'hr_ut1', 'hr_ut2', 'x_right', ] to_be_dropped = [c for c in to_be_dropped if c in columns] datadf.drop(labels=to_be_dropped, inplace=True, axis=1) cols = ['ftime', 'cumdist', 'fpace', 'spm', 'hr', 'power', 'driveenergy', 'drivelength', 'averageforce', 'peakforce', 'distance', 'drivespeed', 'workoutstate', 'catch', 'finish', 'peakforceangle', 'wash', 'slip', 'rhythm', 'effectiveangle', 'totalangle', 'distanceperstroke', 'velo'] cols = [c for c in cols if c in datadf.columns] tcols = ['ftime', 'cumdist', 'fpace', 'spm', 'hr', 'power'] datadf = datadf[cols] try: datadf.loc[:, 'hr'] = datadf['hr'].astype('int') datadf.loc[:, 'power'] = datadf['power'].astype('int') datadf.loc[:, 'distance'] = datadf['distance'].astype('int') datadf.loc[:, 'spm'] = 10*datadf['spm'].astype('int')/10. except KeyError: pass if request.method == 'POST': form = DataFrameColumnsForm(request.POST) if form.is_valid(): tcols = form.cleaned_data['cols'] else: form = DataFrameColumnsForm(initial={'cols': tcols}) try: datadf = datadf[tcols] except KeyError: # pragma: no cover # tcols = list(set(datadf.columns(tolist)+tcols)) try: datadf = datadf[tcols] datadf = datadf.fillna(value=0) except KeyError: pass for col in cols: try: if datadf[col].mean() == 0 and datadf[col].std() == 0: datadf.drop(labels=[col], axis=1, inplace=True) except (TypeError, KeyError): pass 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") # datadf['deltat'] = datadf['time'].diff() # workoutstateswork = [1, 4, 5, 8, 9, 6, 7] # workoutstatesrest = [3] # workoutstatetransition = [0, 2, 10, 11, 12, 13] # Create stats stats = {} fieldlist, fielddict = dataprep.getstatsfields() try: fielddict.pop('pace') except KeyError: # pragma: no cover pass for field, verbosename in fielddict.items(): try: thedict = { 'mean': datadf[field].mean(), 'wmean': wavg(datadf, field, 'deltat'), 'min': datadf[field].min(), 'std': datadf[field].std(), 'max': datadf[field].max(), 'median': datadf[field].median(), 'firstq': datadf[field].quantile(q=0.25), 'thirdq': datadf[field].quantile(q=0.75), 'verbosename': verbosename, } stats[field] = thedict except KeyError: # pragma: no cover pass # Create a dict with correlation values cor = datadf.corr(method='spearman') cor.fillna(value=0, inplace=True) cordict = {} for field1, verbosename1 in fielddict.items(): thedict = {} for field2, verbosename2 in fielddict.items(): try: thedict[verbosename2] = cor.loc[field1, field2] except KeyError: # pragma: no cover thedict[verbosename2] = 0 cordict[verbosename1] = thedict # additional non-automated stats otherstats = {} # Normalized power & TSS tss, normp = dataprep.workout_rscore(w) goldmedalstandard, goldmedalseconds = dataprep.workout_goldmedalstandard(w) if not np.isnan(goldmedalstandard) and goldmedalstandard > 0: otherstats['goldmedalstandard'] = { 'verbose_name': 'Gold Medal Standard', 'value': int(goldmedalstandard), 'unit': '%', } if not np.isnan(goldmedalseconds) and goldmedalseconds > 0: otherstats['goldmedalseconds'] = { 'verbose_name': 'Gold Medal Standard Duration', 'value': utils.totaltime_sec_to_string(goldmedalseconds, shorten=True), 'unit': '', } if not np.isnan(tss) and tss != 0: otherstats['tss'] = { 'verbose_name': 'rScore', 'value': int(tss), 'unit': '' } if not np.isnan(normp): otherstats['np'] = { 'verbose_name': 'rPower', 'value': int(10*normp)/10., 'unit': 'Watt' } # HR Drift tmax = datadf['time'].max() tmin = datadf['time'].min() thalf = tmin+0.5*(tmax-tmin) mask1 = datadf['time'] < thalf mask2 = datadf['time'] > thalf hr1 = datadf.loc[mask1, 'hr'].mean() hr2 = datadf.loc[mask2, 'hr'].mean() pwr1 = datadf.loc[mask1, 'power'].mean() pwr2 = datadf.loc[mask2, 'power'].mean() try: hrdrift = ((pwr1/hr1)-(pwr2/hr2))/(pwr1/hr1) hrdrift *= 100. if not np.isnan(hrdrift): try: hrdrift = int(100*hrdrift)/100. except: # pragma: no cover hrdrift = 0 otherstats['hrdrift'] = { 'verbose_name': 'Heart Rate Drift', 'value': hrdrift, 'unit': '%', } except (ZeroDivisionError, ValueError): # pragma: no cover pass # TRIMP trimp, hrtss = dataprep.workout_trimp(w) otherstats['trimp'] = { 'verbose_name': 'TRIMP', 'value': trimp, 'unit': '' } otherstats['hrScore'] = { 'verbose_name': 'rScore (HR)', 'value': hrtss, 'unit': '' } return render(request, 'workoutstats.html', { 'stats': stats, 'teams': get_my_teams(request.user), 'workout': w, 'rower': r, 'mayedit': mayedit, 'breadcrumbs': breadcrumbs, 'active': 'nav-workouts', 'workstrokesonly': workstrokesonly, 'cordict': cordict, 'otherstats': otherstats, }) # Change default landing page @login_required() def workflow_default_view(request): r = getrower(request.user) if r.defaultlandingpage == 'workout_edit_view': r.defaultlandingpage = 'workout_workflow_view' else: # pragma: no cover r.defaultlandingpage = 'workout_edit_view' r.save() url = reverse('workout_workflow_config2_view') return HttpResponseRedirect(url) # Workflow configuration @login_required() def workout_workflow_config2_view(request, userid=0): request.session['referer'] = absolute(request)['PATH'] request.session[translation.LANGUAGE_SESSION_KEY] = USER_LANGUAGE try: workoutid = request.session['lastworkout'] except KeyError: workoutid = 0 r = getrequestrower(request, userid=userid, notpermanent=True) MiddlePanelFormSet = formset_factory(WorkFlowMiddlePanelElement, extra=1) if request.method == 'POST': middlepanel_formset = MiddlePanelFormSet(request.POST, prefix='middlepanel') newmiddlepanel = [] if middlepanel_formset.is_valid(): for form in middlepanel_formset: value = form.cleaned_data.get('panel') if value != 'None': newmiddlepanel.append(value) newmiddlepanel = [i for i in newmiddlepanel if i 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 request.session[translation.LANGUAGE_SESSION_KEY] = USER_LANGUAGE 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_chart2(rowdata.df[' latitude'], rowdata.df[' longitude'], row.name) else: mapscript = '' mapdiv = '' statcharts = GraphImage.objects.filter(workout=row) middleTemplates = [] for t in r.workflowmiddlepanel: try: template.loader.get_template(t) middleTemplates.append(t) except template.TemplateDoesNotExist: # pragma: no cover pass leftTemplates = [] for t in r.workflowleftpanel: try: template.loader.get_template(t) leftTemplates.append(t) except template.TemplateDoesNotExist: # pragma: no cover pass breadcrumbs = [ { 'url': '/rowers/list-workouts/', 'name': 'Workouts' }, { 'url': get_workout_default_page(request, id), 'name': row.name }, { 'url': reverse('workout_workflow_view', kwargs={'id': id}), 'name': 'View' } ] return render(request, 'workflow.html', { 'middleTemplates': middleTemplates, 'leftTemplates': leftTemplates, 'active': 'nav-workouts', 'breadcrumbs': breadcrumbs, 'charts': charts, 'workout': row, 'mapscript': mapscript, 'mapdiv': mapdiv, 'statcharts': statcharts, 'rower': r, 'aantalcomments': aantalcomments, }) # The famous flex chart @login_required() @permission_required('workout.view_workout', fn=get_workout_by_opaqueid, raise_exception=True) def workout_flexchart3_view(request, *args, **kwargs): try: id = kwargs['id'] except KeyError: # pragma: no cover raise Http404("Invalid workout number") if 'promember' in kwargs: # pragma: no cover promember = kwargs['promember'] else: promember = 0 try: favoritenr = int(request.GET['favoritechart']) except: favoritenr = -1 row = get_workout(id) r = getrequestrower(request) promember = 0 mayedit = 0 if not request.user.is_anonymous: result = ispromember(request.user) if result: promember = 1 if request.user == row.user.user: mayedit = 1 if is_workout_user(request.user, row): mayedit = 1 workouttype = 'ote' if row.workouttype in mytypes.otwtypes: workouttype = 'otw' favorites, maxfav = getfavorites(r, row) # check if favoritenr is not out of range if favorites: try: _ = favorites[favoritenr].xparam except IndexError: # pragma: no cover favoritenr = 0 except AssertionError: favoritenr = 0 if 'xparam' in kwargs: xparam = kwargs['xparam'] else: if favorites: xparam = favorites[favoritenr].xparam else: xparam = 'distance' if 'yparam1' in kwargs: yparam1 = kwargs['yparam1'] else: if favorites: yparam1 = favorites[favoritenr].yparam1 else: yparam1 = 'pace' if 'yparam2' in kwargs: yparam2 = kwargs['yparam2'] if yparam2 == '': # pragma: no cover yparam2 = 'None' else: if favorites: yparam2 = favorites[favoritenr].yparam2 if yparam2 == '': # pragma: no cover yparam2 = 'None' else: yparam2 = 'hr' if not request.user.is_anonymous: r = getrower(request.user) if favoritenr >= 0 and r.showfavoritechartnotes: try: favoritechartnotes = favorites[favoritenr].notes except IndexError: # pragma: no cover favoritechartnotes = '' else: favoritechartnotes = '' else: # pragma: no cover favoritechartnotes = '' favoritenr = 0 if 'plottype' in kwargs: # pragma: no cover plottype = kwargs['plottype'] else: if favorites: plottype = favorites[favoritenr].plottype else: plottype = 'line' if 'workstrokesonly' in kwargs: # pragma: no cover workstrokesonly = kwargs['workstrokesonly'] else: if favorites: workstrokesonly = not favorites[favoritenr].reststrokes else: workstrokesonly = False if request.method == 'POST' and 'savefavorite' in request.POST: if not request.user.is_anonymous: workstrokesonly = request.POST['workstrokesonlysave'] reststrokes = not workstrokesonly r = getrower(request.user) try: f = FavoriteChart(user=r, xparam=xparam, yparam1=yparam1, yparam2=yparam2, plottype=plottype, workouttype=workouttype, reststrokes=reststrokes) f.save() except KeyError: # pragma: no cover messages.error( request, 'We cannot save the ad hoc metrics in a favorite chart') if request.method == 'POST' and 'xaxis' in request.POST: flexoptionsform = FlexOptionsForm(request.POST) if flexoptionsform.is_valid(): cd = flexoptionsform.cleaned_data includereststrokes = cd['includereststrokes'] plottype = cd['plottype'] workstrokesonly = not includereststrokes flexaxesform = FlexAxesForm(request, request.POST) if flexaxesform.is_valid(): cd = flexaxesform.cleaned_data xparam = cd['xaxis'] yparam1 = cd['yaxis1'] yparam2 = cd['yaxis2'] else: pass if not promember: for name, d in rowingmetrics: if d['type'] != 'basic': if xparam == name: # pragma: no cover xparam = 'time' messages.info( request, 'To use '+d['verbose_name']+', you have to be Pro member') if yparam1 == name: # pragma: no cover yparam1 = 'pace' messages.info( request, 'To use '+d['verbose_name']+', you have to be Pro member') if yparam2 == name: # pragma: no cover yparam2 = 'spm' messages.info( request, 'To use '+d['verbose_name']+', you have to be Pro member') # bring back slashes # yparam1 = yparam1.replace('_slsh_','/') # yparam2 = yparam2.replace('_slsh_','/') # xparam = xparam.replace('_slsh_','/') # create interactive plot ( script, div, js_resources, css_resources, workstrokesonly ) = interactive_flex_chart2( encoder.decode_hex(id), request.user.rower, xparam=xparam, yparam1=yparam1, yparam2=yparam2, promember=promember, plottype=plottype, workstrokesonly=workstrokesonly, mode=row.workouttype ) axchoicesbasic = {ax[0]: ax[1] for ax in axes if ax[4] == 'basic'} axchoicespro = {ax[0]: ax[1] for ax in axes if ax[4] == 'pro'} noylist = ["time", "distance"] axchoicesbasic.pop("cumdist") if row.workouttype in mytypes.otwtypes: for name, d in rowingmetrics: if d['mode'] == 'erg': axchoicespro.pop(name) else: for name, d in rowingmetrics: if d['mode'] == 'water': axchoicespro.pop(name) from rowers.metrics import nometrics rowdata = rdata(csvfile=row.csvfilename) try: rowdata.set_instroke_metrics() except (AttributeError, TypeError): # pragma: no cover pass try: additionalmetrics = rowdata.get_additional_metrics() additionalmetrics = [ m for m in additionalmetrics if 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 } flexoptionsform = FlexOptionsForm(initial=initial) row = Workout.objects.get(id=encoder.decode_hex(id)) breadcrumbs = [ { 'url': '/rowers/list-workouts/', 'name': 'Workouts' }, { 'url': get_workout_default_page(request, id), 'name': row.name }, { 'url': reverse('workout_flexchart3_view', kwargs=kwargs), 'name': 'Flex Chart' } ] return render(request, 'flexchart3otw.html', {'the_script': script, 'the_div': div, 'breadcrumbs': breadcrumbs, 'rower': r, 'active': 'nav-workouts', 'workout': row, 'chartform': flexaxesform, 'optionsform': flexoptionsform, 'js_res': js_resources, 'css_res': css_resources, 'teams': get_my_teams(request.user), 'id': id, 'xparam': xparam, 'yparam1': yparam1, 'yparam2': yparam2, 'plottype': plottype, 'axchoicesbasic': axchoicesbasic, 'axchoicespro': axchoicespro, 'extrametrics': extrametrics, 'favoritechartnotes': favoritechartnotes, 'noylist': noylist, 'mayedit': mayedit, 'promember': promember, 'workstrokesonly': not workstrokesonly, 'favoritenr': favoritenr, 'maxfav': maxfav, }) @login_required() @permission_required('workout.view_workout', fn=get_workout_by_opaqueid, raise_exception=True) def workout_flexchart_stacked_view(request, *args, **kwargs): try: id = kwargs['id'] except KeyError: # pragma: no cover raise Http404("Invalid workout number") workout = get_workout(id) r = getrequestrower(request) xparam = 'time' yparam1 = 'pace' yparam2 = 'power' yparam3 = 'hr' yparam4 = 'spm' if request.method == 'POST': flexaxesform = StravaChartForm(request, request.POST) if flexaxesform.is_valid(): cd = flexaxesform.cleaned_data xparam = cd['xaxis'] yparam1 = cd['yaxis1'] yparam2 = cd['yaxis2'] yparam3 = cd['yaxis3'] yparam4 = cd['yaxis4'] ( script, div, js_resources, css_resources, comment ) = interactive_flexchart_stacked( encoder.decode_hex(id), r, xparam=xparam, yparam1=yparam1, yparam2=yparam2, yparam3=yparam3, yparam4=yparam4, mode=workout.workouttype, ) if comment is not None: # pragma: no cover messages.error(request, comment) initial = { 'xaxis': xparam, 'yaxis1': yparam1, 'yaxis2': yparam2, 'yaxis3': yparam3, 'yaxis4': yparam4, } flexaxesform = StravaChartForm(request, initial=initial, ) breadcrumbs = [ { 'url': '/rowers/list-workouts/', 'name': 'Workouts' }, { 'url': get_workout_default_page(request, id), 'name': workout.name }, { 'url': reverse('workout_flexchart_stacked_view', kwargs=kwargs), 'name': 'Stacked Flex Chart' } ] return render(request, 'flexchartstacked.html', { 'the_script': script, 'the_div': div, 'breadcrumbs': breadcrumbs, 'rower': r, 'active': 'nav-workouts', 'workout': workout, 'chartform': flexaxesform, 'js_res': js_resources, 'css_res': css_resources, 'id': id, 'xparam': xparam, 'yparam1': yparam1, 'yparam2': yparam2, 'yparam3': yparam3, 'yparam4': yparam4, } ) # The interactive plot with wind corrected pace for OTW outings def workout_otwpowerplot_view(request, id=0, message="", successmessage=""): w = get_workout(id) r = getrower(request.user) breadcrumbs = [ { 'url': '/rowers/list-workouts/', 'name': 'Workouts' }, { 'url': get_workout_default_page(request, id), 'name': w.name }, { 'url': reverse('workout_otwpowerplot_view', kwargs={'id': id}), 'name': 'Interactive OTW Power Plot' } ] # check if user is owner of this workout # create interactive plot promember = 0 mayedit = 0 result = request.user.is_authenticated and ispromember(request.user) if result: promember = 1 if request.user == w.user.user: mayedit = 1 # create interactive plot res = interactive_otw_advanced_pace_chart( encoder.decode_hex(id), promember=promember) script = res[0] div = res[1] messages.error(request, message) messages.info(request, successmessage) return render(request, 'otwinteractive.html', {'workout': w, 'rower': r, 'active': 'nav-workouts', 'breadcrumbs': breadcrumbs, 'teams': get_my_teams(request.user), 'interactiveplot': script, 'the_div': div, 'mayedit': mayedit}) # @login_required() def workout_unsubscribe_view(request, id=0): w = get_workout(id) if w.privacy == 'private' and w.user.user != request.user: # pragma: no cover return HttpResponseForbidden("Permission error") comments = WorkoutComment.objects.filter(workout=w, user=request.user).order_by("created") for c in comments: c.notification = False c.save() form = WorkoutCommentForm() successmessage = 'You have been unsubscribed from new comment notifications for this workout' messages.info(request, successmessage) return render(request, 'workout_comments.html', {'workout': w, 'teams': get_my_teams(request.user), 'comments': comments, 'form': form, }) # list of comments to a workout @login_required() def workout_comment_view(request, id=0): w = get_workout(id) if w.privacy == 'private' and w.user.user != request.user: # pragma: no cover return HttpResponseForbidden("Permission error") comments = WorkoutComment.objects.filter(workout=w).order_by("created") # ok we're permitted if request.method == 'POST': r = w.user form = WorkoutCommentForm(request.POST) if form.is_valid(): cd = form.cleaned_data comment = cd['comment'] comment = bleach.clean(comment) try: # pragma: no cover if isinstance(comment, unicode): comment = comment.encode('utf8') elif isinstance(comment, str): comment = comment.decode('utf8') except: pass notification = cd['notification'] c = WorkoutComment(workout=w, user=request.user, comment=comment, notification=notification) c.save() url = reverse('workout_comment_view', kwargs={ 'id': id, }) message = '{name} says: {comment}'.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[translation.LANGUAGE_SESSION_KEY] = USER_LANGUAGE request.session['referer'] = absolute(request)['PATH'] row = get_workoutuser(id, request) if request.user.rower.rowerplan == 'basic' and 'speedcoach2' in row.workoutsource: # pragma: no cover data = getsmallrowdata_db(['wash'], ids=[encoder.decode_hex(id)]) try: if data['wash'].std() != 0: url = reverse('paidplans_view') messages.info( request, 'Some Empower Oarlock data are only available to users with a paid plan'.format( u=url) ) except: pass if request.user.rower.rowerplan == 'basic' and 'nklinklogbook' in row.workoutsource: # pragma: no cover data = getsmallrowdata_db(['wash'], ids=[encoder.decode_hex(id)]) try: if data['wash'].std() != 0: url = reverse('paidplans_view') messages.info( request, 'Some Empower Oarlock data are only available to users with a paid plan'.format( u=url) ) except: pass form = WorkoutForm(instance=row) if request.method == 'POST': # Form was submitted form = WorkoutForm(request.POST, instance=row) if form.is_valid(): # Get values from form name = form.cleaned_data['name'] date = form.cleaned_data['date'] starttime = form.cleaned_data['starttime'] workouttype = form.cleaned_data['workouttype'] weightcategory = form.cleaned_data['weightcategory'] adaptiveclass = form.cleaned_data['adaptiveclass'] duration = form.cleaned_data['duration'] distance = form.cleaned_data['distance'] private = form.cleaned_data['private'] notes = form.cleaned_data['notes'] newdragfactor = form.cleaned_data['dragfactor'] thetimezone = form.cleaned_data['timezone'] try: rpe = form.cleaned_data['rpe'] if not rpe: # pragma: no cover rpe = -1 except KeyError: # pragma: no cover rpe = -1 ps = form.cleaned_data.get('plannedsession', None) boattype = request.POST.get('boattype', Workout.objects.get( id=encoder.decode_hex(id)).boattype) privacy = request.POST.get('privacy', Workout.objects.get( id=encoder.decode_hex(id)).privacy) rankingpiece = form.cleaned_data.get( 'rankingpiece', Workout.objects.get(id=row.id).rankingpiece) duplicate = form.cleaned_data.get( 'duplicate', Workout.objects.get(id=row.id).duplicate) if private: privacy = 'private' else: # pragma: no cover privacy = 'visible' try: startdatetime = datetime.datetime.combine( date, starttime ) except TypeError: # pragma: no cover startdatetime = datetime.datetime.combine( date, datetime.datetime.min.time() ) try: startdatetime = pytz.timezone(thetimezone).localize( startdatetime ) except UnknownTimeZoneError: # pragma: no cover pass try: # aware object can be in any timezone _ = startdatetime.astimezone(pytz.utc) except (ValueError, TypeError): # pragma: no cover startdatetime = timezone.make_aware(startdatetime) try: startdatetime = startdatetime.astimezone( pytz.timezone(thetimezone)) except UnknownTimeZoneError: # pragma: no cover thetimezone = 'UTC' timechanged = (startdatetime != row.startdatetime) row.name = name row.date = date row.starttime = starttime row.startdatetime = startdatetime row.workouttype = workouttype row.weightcategory = weightcategory row.adaptiveclass = adaptiveclass row.notes = notes row.rpe = rpe row.duration = duration row.distance = distance row.boattype = boattype row.duplicate = duplicate row.privacy = privacy row.rankingpiece = rankingpiece row.timezone = thetimezone row.plannedsession = ps dragchanged = False if newdragfactor != row.dragfactor: # pragma: no cover row.dragfactor = newdragfactor dragchanged = True try: row.save() except IntegrityError: # pragma: no cover pass if ps: # pragma: no cover add_workouts_plannedsession([row], ps, row.user) # change data in csv file datachanged = (dragchanged or timechanged) if datachanged: r = rdata(csvfile=row.csvfilename) if dragchanged: try: # pragma: no cover r.change_drag(newdragfactor) except AttributeError: # pragma: no cover pass if r == 0: # pragma: no cover return HttpResponse("Error: CSV Data File Not Found") r.rowdatetime = startdatetime r.write_csv(row.csvfilename, gzip=True) dataprep.update_strokedata(encoder.decode_hex(id), r.df) successmessage = "Changes saved" messages.info(request, successmessage) else: form = WorkoutForm(instance=row) g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime") for i in g: # pragma: no cover try: width, height = Image.open(i.filename).size i.width = width i.height = height i.save() except: pass videos = VideoAnalysis.objects.filter(workout=row) # create interactive plot f1 = row.csvfilename u = row.user.user r = getrower(u) rowdata = rdata(csvfile=f1) hascoordinates = 1 courses = [] if rowdata != 0: try: latitude = rowdata.df[' latitude'] longitude = rowdata.df[' longitude'] if not latitude.std(): # pragma: no cover hascoordinates = 0 if not longitude.std(): hascoordinates = 0 except (KeyError, AttributeError): hascoordinates = 0 else: # pragma: no cover hascoordinates = 0 mapscript = "" mapdiv = "" if hascoordinates: try: mapscript, mapdiv = leaflet_chart( rowdata.df[' latitude'], rowdata.df[' longitude'], row.name) except KeyError: # pragma: no cover pass records = VirtualRaceResult.objects.filter( workoutid=row.id, userid=row.user.user.id, coursecompleted=True) if records.count() > 0: # pragma: no cover courses = list(set([record.course for record in records])) breadcrumbs = [ { 'url': '/rowers/list-workouts/', 'name': 'Workouts' }, { 'url': get_workout_default_page(request, encoder.encode_hex(row.id)), 'name': row.name }, { 'url': reverse('workout_edit_view', kwargs={'id': encoder.encode_hex(row.id)}), 'name': 'Edit' } ] if row.workouttype in mytypes.otetypes: indoorraces = get_indoorraces(row) else: indoorraces = [] r = row.user # render page return render(request, 'workout_form.html', {'form': form, 'workout': row, 'teams': get_my_teams(request.user), 'graphs': g, 'videos': videos, 'breadcrumbs': breadcrumbs, 'rower': r, 'indoorraces': indoorraces, 'active': 'nav-workouts', 'mapscript': mapscript, 'mapdiv': mapdiv, 'rower': r, 'courses': courses, }) @login_required() def workout_map_view(request, id=0): request.session[translation.LANGUAGE_SESSION_KEY] = USER_LANGUAGE request.session['referer'] = absolute(request)['PATH'] w = get_workout(id) breadcrumbs = [ { 'url': '/rowers/list-workouts/', 'name': 'Workouts' }, { 'url': get_workout_default_page(request, id), 'name': w.name }, { 'url': reverse('workout_map_view', kwargs={'id': id}), 'name': 'Map' } ] # create interactive plot f1 = w.csvfilename u = w.user.user r = getrower(u) rowdata = rdata(csvfile=f1) hascoordinates = 1 if rowdata != 0: try: latitude = rowdata.df[' latitude'] if not latitude.std(): # pragma: no cover hascoordinates = 0 except (KeyError, AttributeError): hascoordinates = 0 else: # pragma: no cover hascoordinates = 0 if hascoordinates: mapscript, mapdiv = leaflet_chart2(rowdata.df[' latitude'], rowdata.df[' longitude'], w.name) else: mapscript = "" mapdiv = "" mayedit = 0 if not request.user.is_anonymous: r = getrower(request.user) 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 (KeyError, JSONDecodeError): q = request.POST post_data = {k: q.getlist(k) if len( q.getlist(k)) > 1 else v for k, v in q.items()} # only allow local host hostt = request.get_host().split(':') if hostt[0] not in ['localhost', '127.0.0.1', 'dev.rowsandall.com', 'rowsandall.com']: message = {'status': 'false', 'message': 'permission denied for host '+hostt[0]} return JSONResponse(status=403, data=message) # check credentials here try: secret = post_data['secret'] except KeyError: message = {'status': 'false', 'message': 'missing credentials'} return JSONResponse(status=400, data=message) if secret != settings.UPLOAD_SERVICE_SECRET: message = {'status': 'false', 'message': 'invalid credentials'} return JSONResponse(status=403, data=message) form = DocumentsForm(post_data) optionsform = TeamUploadOptionsForm(post_data) rowerform = TeamInviteForm(post_data) rowerform.fields.pop('email') try: fstr = post_data['file'] nn, ext = os.path.splitext(fstr) if ext == '.gz': # pragma: no cover nn, ext2 = os.path.splitext(nn) ext = ext2+ext f1 = uuid.uuid4().hex[:10]+'-'+time.strftime("%Y%m%d-%H%M%S")+ext f2 = 'media/'+f1 copyfile(fstr, f2) except KeyError: message = {'status': 'false', 'message': 'no filename given'} return JSONResponse(status=400, data=message) except FileNotFoundError: message = {'status': 'false', 'message': 'could not find file'} return JSONResponse(status=400, data=message) # sync related IDs c2id = post_data.get('c2id', '') startdatetime = post_data.get('startdatetime', '') oarlockfirmware = post_data.get('oarlockfirmware', None) inboard = post_data.get('inboard', None) oarlength = post_data.get('oarlength', None) useImpeller = post_data.get('useImpeller', False) totalDistance = post_data.get('totalDistance', None) elapsedTime = post_data.get('elapsedTime', None) summary = post_data.get('summary', None) timezone = post_data.get('timezone', None) s = 'Posting c2id {c2id} to Rowsandall. Startdatetime {startdatetime}, time zone {timezone}'.format( c2id=c2id, startdatetime=startdatetime, timezone=timezone, ) dologging('debuglog.log', s) r = None if form.is_valid(): t = form.cleaned_data['title'] boattype = form.cleaned_data['boattype'] workouttype = form.cleaned_data['workouttype'] try: rpe = form.cleaned_data['rpe'] try: rpe = int(rpe) except ValueError: # pragma: no cover rpe = 0 except KeyError: # pragma: no cover rpe = -1 if rowerform.is_valid(): u = rowerform.cleaned_data['user'] r = getrower(u) if 'useremail' in post_data: us = User.objects.filter(email=post_data['useremail']) if len(us): # pragma: no cover u = us[0] r = getrower(u) else: r = None for rwr in Rower.objects.all(): if rwr.emailalternatives is not None: if post_data['useremail'] in rwr.emailalternatives: r = rwr break if r is not None and r.emailalternatives is not None: if post_data['useremail'] not in r.emailalternatives: # pragma: no cover message = {'status': 'false', 'message': 'could not find user'} return JSONResponse(status=400, data=message) if r is None: # pragma: no cover message = {'status': 'false', 'message': 'invalid user'} return JSONResponse(status=400, data=message) notes = form.cleaned_data['notes'] if optionsform.is_valid(): make_plot = optionsform.cleaned_data['make_plot'] plottype = optionsform.cleaned_data['plottype'] makeprivate = optionsform.cleaned_data['makeprivate'] else: # pragma: no cover message = optionsform.errors return JSONResponse(status=400, data=message) if r is None: # pragma: no cover message = {'status': 'false', 'message': 'something went wrong'} return JSONResponse(status=400, data=message) id, message, f2 = dataprep.new_workout_from_file( r, f2, workouttype=workouttype, workoutsource=None, boattype=boattype, makeprivate=makeprivate, title=t, rpe=rpe, notes=notes, uploadoptions=post_data, startdatetime=startdatetime, oarlockfirmware=oarlockfirmware, inboard=inboard, oarlength=oarlength, impeller=useImpeller, ) if id == 0: # pragma: no cover if message is not None: message = {'status': 'false', 'message': 'unable to process file: '+message} else: message = {'status': 'false', 'message': 'unable to process file'} return JSONResponse(status=400, data=message) if id == -1: # pragma: no cover message = {'status': 'true', 'message': message} return JSONResponse(status=200, data=message) w = Workout.objects.get(id=id) if timezone is not None: # pragma: no cover w.startdatetime = w.startdatetime.astimezone( pytz.timezone(timezone)) w.workoutdate = w.startdatetime.strftime('%Y-%m-%d') w.starttime = w.startdatetime.strftime('%H:%M:%S') w.timezone = timezone dologging('debuglog.log', 'Start Date Time set to {s}'.format( s=w.startdatetime)) dologging('debuglog.log', 'Workout Date set to {s}'.format(s=w.workoutdate)) dologging('debuglog.log', 'Time zone set to {zone}'.format(zone=w.timezone)) w.save() if make_plot: # pragma: no cover res, jobid = uploads.make_plot(r, w, f1, f2, plottype, t) elif r.staticchartonupload != 'None': # pragma: no cover plottype = r.staticchartonupload res, jobid = uploads.make_plot(r, w, f1, f2, plottype, t) if inboard: # pragma: no cover w.inboard = inboard w.save() if oarlength: # pragma: no cover w.oarlength = oarlength w.save() if totalDistance: # pragma: no cover w.distance = totalDistance w.save() if elapsedTime: # pragma: no cover w.duration = totaltime_sec_to_string(elapsedTime) if summary: # pragma: no cover w.summary = summary w.save() uploads.do_sync(w, post_data, quick=True) else: # pragma: no cover # form invalid if fstr: os.remove(fstr) message = form.errors return JSONResponse(status=400, data=message) message = {'status': 'true', 'id': w.id} statuscode = 200 if fstr: try: os.remove(fstr) except FileNotFoundError: # pragma: no cover message = {'status': 'true', 'id': w.id, 'message': 'Error deleting temporary file'} statuscode = 500 if r.getemailnotifications and not r.emailbounced: # pragma: no cover link = settings.SITE_URL+reverse( r.defaultlandingpage, kwargs={ 'id': encoder.encode_hex(w.id), } ) _ = 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: message, id = c2stuff.workout_c2_upload(request.user, w) except NoTokenError: id = 0 message = "Something went wrong with the Concept2 sync" if id > 1: messages.info(request, message) else: messages.error(request, message) if (upload_to_strava): # pragma: no cover try: message, id = stravastuff.workout_strava_upload( request.user, w, ) except NoTokenError: id = 0 message = "Please connect to Strava first" if id > 1: messages.info(request, message) else: messages.error(request, message) if (upload_to_st): # pragma: no cover try: message, id = sporttracksstuff.workout_sporttracks_upload( request.user, w ) except NoTokenError: message = "Please connect to SportTracks first" id = 0 if id > 1: messages.info(request, message) else: messages.error(request, message) if (upload_to_tp): # pragma: no cover try: message, id = tpstuff.workout_tp_upload( request.user, w ) except NoTokenError: message = "Please connect to TrainingPeaks first" id = 0 if id > 1: messages.info(request, message) else: messages.error(request, message) if int(registrationid) < 0: # pragma: no cover race = VirtualRace.objects.get(id=-int(registrationid)) if race.sessiontype == 'race': result, comments, errors, jobid = add_workout_race( [w], race, r, doregister=True, ) if result: messages.info( request, "We have submitted your workout to the race") for c in comments: messages.info(request, c) for er in errors: messages.error(request, er) elif race.sessiontype == 'indoorrace': result, comments, errors, jobid = add_workout_indoorrace( [w], race, r, doregister=True, ) if result: messages.info( request, "We have submitted your workout to the race") for c in comments: messages.info(request, c) for er in errors: messages.error(request, er) elif race.sessiontype in ['fastest_time', 'fastest_distance']: result, comments, errors, jobid = add_workout_fastestrace( [w], race, r, doregister=True, ) if result: messages.info( request, "We have submitted your workout to the race") for c in comments: messages.info(request, c) for er in errors: messages.error(request, er) if int(registrationid) > 0: # pragma: no cover races = VirtualRace.objects.filter( registration_closure__gt=timezone.now() ) if raceid != 0: races = VirtualRace.objects.filter( registration_closure__gt=timezone.now(), id=raceid, ) registrations = IndoorVirtualRaceResult.objects.filter( race__in=races, id=registrationid, userid=r.id, ) registrations2 = VirtualRaceResult.objects.filter( race__in=races, id=registrationid, userid=r.id, ) if int(registrationid) in [r.id for r in registrations]: # pragma: no cover # indoor race registrations = registrations.filter(id=registrationid) if registrations: race = registrations[0].race if race.sessiontype == 'indoorrace': result, comments, errors, jobid = add_workout_indoorrace( [w], race, r, recordid=registrations[0].id ) elif race.sessiontype in ['fastest_time', 'fastest_distance']: result, comments, errors, jobid = add_workout_fastestrace( [w], race, r, recordid=registrations[0].id ) if result: messages.info( request, "We have submitted your workout to the race") for c in comments: messages.info(request, c) for er in errors: messages.error(request, er) if int(registrationid) in [r.id for r in registrations2]: # pragma: no cover # race registrations = registrations2.filter(id=registrationid) if registrations: race = registrations[0].race if race.sessiontype == 'race': result, comments, errors, jobid = add_workout_race( [w], race, r, recordid=registrations[0].id ) elif race.sessiontype in ['fastest_time', 'fastest_distance']: result, comments, errors, jobid = add_workout_fastestrace( [w], race, r, recordid=registrations[0].id ) if result: messages.info( request, "We have submitted your workout to the race") for c in comments: messages.info(request, c) for er in errors: messages.error(request, er) if registrationid != 0: # pragma: no cover try: url = reverse('virtualevent_view', kwargs={ 'id': race.id, }) except UnboundLocalError: if landingpage != 'workout_upload_view': url = reverse(landingpage, kwargs={ 'id': encoder.encode_hex(w.id), }) else: # pragma: no cover url = reverse(landingpage) elif landingpage != 'workout_upload_view': # pragma: no cover url = reverse(landingpage, kwargs={ 'id': encoder.encode_hex(w.id), }) else: # pragma: no cover url = reverse(landingpage) if is_ajax: # pragma: no cover response = {'result': 1, 'url': url} else: response = HttpResponseRedirect(url) else: if not is_ajax: # pragma: no cover response = render(request, 'document_form.html', {'form': form, 'teams': get_my_teams(request.user), 'optionsform': optionsform, }) if is_ajax: # pragma: no cover return JSONResponse(response) else: return response else: if not is_ajax: if r.c2_auto_export and ispromember(r.user): # pragma: no cover uploadoptions['upload_to_C2'] = True if r.strava_auto_export and ispromember(r.user): # pragma: no cover uploadoptions['upload_to_Strava'] = True if r.sporttracks_auto_export and ispromember(r.user): # pragma: no cover uploadoptions['upload_to_SportTracks'] = True if r.trainingpeaks_auto_export and ispromember(r.user): # pragma: no cover uploadoptions['upload_to_TrainingPeaks'] = True form = DocumentsForm(initial=docformoptions) optionsform = UploadOptionsForm(initial=uploadoptions, request=request, raceid=raceid) return render(request, 'document_form.html', {'form': form, 'active': 'nav-workouts', 'breadcrumbs': breadcrumbs, 'teams': get_my_teams(request.user), 'optionsform': optionsform, }) else: # pragma: no cover return {'result': 0} # This is the main view for processing uploaded files @user_passes_test(iscoachmember, login_url="/rowers/paidplans", redirect_field_name=None, message="This functionality requires a Coach plan or higher") def team_workout_upload_view(request, message="", successmessage="", uploadoptions={ 'make_plot': False, 'plottype': 'timeplot', }): if 'uploadoptions' in request.session: uploadoptions = request.session['uploadoptions'] else: request.session['uploadoptions'] = uploadoptions breadcrumbs = [ { 'url': '/rowers/list-workouts/', 'name': 'Workouts' }, { 'url': reverse('team_workout_upload_view'), 'name': 'Team Upload' } ] 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() 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 here') # 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 here') vals = None # feeling lucky / ruptures if request.method == 'POST' and "ruptures" in request.POST: df = pd.DataFrame({ 'spm': rowdata.df[' Cadence (stokes/min)'], 'power': rowdata.df[' Power (watts)'], 'v': rowdata.df[' AverageBoatSpeed (m/s)'] }) algo = rpt.Pelt(model="rbf").fit(df.values) result = algo.predict(pen=10) df['time'] = rowdata.df['TimeStamp (sec)'].values try: timeprev = int(df['time'].values[0]) timenext = int(df['time'].values[result[0]]) s = '{delta}sec'.format(delta=timenext-timeprev) except IndexError: # pragma: no cover s = '0sec' for i in range(len(result)-1): timeprev = int(df['time'].values[result[i]-1]) timenext = int(df['time'].values[result[i+1]-1]) interval = '+{delta}sec'.format(delta=timenext-timeprev) s += interval try: rowdata.updateinterval_string(s) except: # pragma: no cover messages.error(request, "Nope, you were not lucky") intervalstats = rowdata.allstats() itime, idist, itype = rowdata.intervalstats_values() nrintervals = len(idist) savebutton = 'savestringform' intervalString = s form = SummaryStringForm(initial={'intervalstring': intervalString}) # We have submitted the mini language interpreter if request.method == 'POST' and "intervalstring" in request.POST: form = SummaryStringForm(request.POST) if form.is_valid(): cd = form.cleaned_data s = cd["intervalstring"] try: rowdata.updateinterval_string(s) except: # pragma: no cover messages.error(request, 'Parsing error') intervalstats = rowdata.allstats() itime, idist, itype = rowdata.intervalstats_values() nrintervals = len(idist) savebutton = 'savestringform' powerupdateform = PowerIntervalUpdateForm(initial=data) # we are saving the results obtained from the split by power/pace interpreter elif request.method == 'POST' and "savepowerpaceform" in request.POST: powerorpace = request.POST.get('powerorpace', 'pace') value_pace = request.POST.get('value_pace', avpace) value_power = request.POST.get('value_power', int(normp)) value_work = request.POST.get('value_work', int(normw)) value_spm = request.POST.get('value_spm', int(normspm)) activeminutesmin = request.POST.get('activeminutesmin', 0) try: activeminutesmax = request.POST['activeminutesmax'] except: # pragma: no cover pass try: activesecondsmin = 60.*float(activeminutesmin) activesecondsmax = 60.*float(activeminutesmax) except ValueError: # pragma: no cover activesecondsmin = 0 activeminutesmin = 0 activeminutesmax = maxminutes activesecondsmax = rowdata.duration if abs(rowdata.duration-activesecondsmax) < 60.: # pragma: no cover activesecondsmax = rowdata.duration if powerorpace == 'power': try: power = int(value_power) except ValueError: # pragma: no cover int(normp) elif powerorpace == 'pace': # pragma: no cover try: pace_secs = float(value_pace) except ValueError: try: pace_secs = float(value_pace.replace(',', '.')) except ValueError: pace_secs = int(500./normv) elif powerorpace == 'work': # pragma: no cover try: work = int(value_work) except ValueError: work = int(normw) elif powerorpace == 'spm': # pragma: no cover try: spm = int(value_spm) except ValueError: spm = int(normspm) if powerorpace == 'power' and power is not None: try: vals, units, typ = rowdata.updateinterval_metric( ' Power (watts)', power, mode='larger', debug=False, smoothwindow=15., activewindow=[activesecondsmin, activesecondsmax], ) except: # pragma: no cover messages.error(request, 'Error updating power') elif powerorpace == 'pace': # pragma: no cover try: velo = 500./pace_secs vals, units, typ = rowdata.updateinterval_metric( ' AverageBoatSpeed (m/s)', velo, mode='larger', debug=False, smoothwindow=15., activewindow=[activesecondsmin, activesecondsmax], ) except: messages.error(request, 'Error updating pace') elif powerorpace == 'work': # pragma: no cover try: vals, units, typ = rowdata.updateinterval_metric( 'driveenergy', work, mode='larger', debug=False, smoothwindow=15., activewindow=[activesecondsmin, activesecondsmax], ) except: messages.error(request, 'Error updating Work per Stroke') elif powerorpace == 'spm': # pragma: no cover try: vals, units, typ = rowdata.updateinterval_metric( ' Cadence (stokes/min)', spm, mode='larger', debug=False, smoothwindow=2., activewindow=[activesecondsmin, activesecondsmax],) except: messages.error(request, 'Error updating SPM') intervalString = '' if vals is not None: intervalString = intervals_to_string(vals, units, typ) intervalstats = rowdata.allstats() itime, idist, itype = rowdata.intervalstats_values() nrintervals = len(idist) row.summary = intervalstats row.save() rowdata.write_csv(f1, gzip=True) dataprep.update_strokedata(encoder.decode_hex(id), rowdata.df) messages.info(request, "Updated interval data saved") data = { 'power': power, 'pace': datetime.timedelta(seconds=int(pace_secs)), 'work': work, 'selector': powerorpace, 'spm': int(normspm), 'activeminutesmin': activeminutesmin, 'activeminutesmax': activeminutesmax, } form = SummaryStringForm(initial={'intervalstring': intervalString}) powerupdateform = PowerIntervalUpdateForm(initial=data) savebutton = 'savepowerpaceform' formvalues = { 'powerorpace': powerorpace, 'value_power': power, 'value_pace': pace_secs, 'value_work': work, } # we are saving the results obtained from the mini language interpreter elif request.method == 'POST' and "savestringform" in request.POST: s = request.POST["savestringform"] try: rowdata.updateinterval_string(s) except (ParseException, err): # pragma: no cover messages.error(request, 'Parsing error in column '+str(err.col)) intervalstats = rowdata.allstats() itime, idist, itype = rowdata.intervalstats_values() nrintervals = len(idist) row.summary = intervalstats 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): workout_pk = self.kwargs['pk'] try: obj = Workout.objects.get(pk=workout_pk) except (ValueError, Workout.DoesNotExist): workout_pk = encoder.decode_hex(workout_pk) try: obj = Workout.objects.get(pk=workout_pk) except Workout.DoesNotExist: # pragma: no cover raise Http404("One of the workouts doesn't exist") # obj = super(WorkoutDelete, self).get_object(*args, **kwargs) return obj