from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals import hashlib from shutil import copyfile from six import iterlists from rowers.views.statements import * import rowers.teams as teams import rowers.mytypes as mytypes import numpy from rowers.mailprocessing import send_confirm import rowers.uploads as uploads from urllib.parse import urlparse, parse_qs from json.decoder import JSONDecodeError def default(o): 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: 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: 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: 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): 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: 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: 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: 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): 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: 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: 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'] video_id = get_video_id(url) 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: 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: 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: mapscript, mapdiv = leaflet_chart_video(data['latitude'],data['longitude'], w.name) else: mapscript, mapdiv = interactive_chart_video(data) data['longitude'] = data['spm'] data['latitude'] = list(range(len(data['spm']))) breadcrumbs = [ { 'url':'/rowers/list-workouts/', 'name':'Workouts' }, { 'url':get_workout_default_page(request,encoder.encode_hex(w.id)), 'name': w.name }, { 'url':reverse('workout_video_create_view',kwargs={'id':encoder.encode_hex(w.id)}), 'name': 'Video Analysis' } ] analysis = {'delay':delay} template = 'embedded_video.html' return render(request, template, { 'workout':w, 'rower':request.user.rower, 'data': json.dumps(data,default=default), 'mapscript': mapscript, 'mapdiv': mapdiv, 'video_id': video_id, 'form':form, 'metricsform':metricsform, 'analysis':analysis, 'breadcrumbs':breadcrumbs, 'maxtime':maxtime, 'metrics':metrics, 'metricsgroups': metricsgroups, 'locked': False, 'siteurl': settings.SITE_URL, }) # Show the EMpower Oarlock generated Stroke Profile @user_passes_test(ispromember,login_url="/rowers/paidplans/", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality. If you are already a Pro user, please log in to access this functionality", redirect_field_name=None) @permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) def workout_forcecurve_view(request,id=0,workstrokesonly=False): row = get_workoutuser(id, request) promember=0 mayedit=0 r = getrequestrower(request) promember = 1 if r == row.user: mayedit = 1 if request.method == 'POST': form = ForceCurveOptionsForm(request.POST) if form.is_valid(): includereststrokes = form.cleaned_data['includereststrokes'] plottype = form.cleaned_data['plottype'] workstrokesonly = not includereststrokes else: 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(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(w.csvfilename) success = row.use_gpsdata() if success: row.write_csv(w.csvfilename) dataprep.update_strokedata(w.id,row.df) w.impeller = False w.save() messages.info(request,'The distance and speed data are now based on GPS data') else: messages.error(request,'No GPS data found') url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)}) return HttpResponseRedirect(url) # Show Stroke power histogram for a workout @login_required() @permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) def workout_histo_view(request,id=0): w = get_workoutuser(id, request) r = getrequestrower(request) promember = 1 mayedit=0 if w.user == r: mayedit = 1 res = interactive_histoall([w],'power',False) script = res[0] div = res[1] breadcrumbs = [ { 'url':'/rowers/list-workouts/', 'name':'Workouts' }, { 'url':get_workout_default_page(request,id), 'name': w.name }, { 'url':reverse('workout_histo_view',kwargs={'id':id}), 'name': 'Histogram' } ] return render(request, 'histo_single.html', {'interactiveplot':script, 'breadcrumbs':breadcrumbs, 'active':'nav-workouts', 'workout':w, 'rower':r, 'the_div':div, 'id':id, 'mayedit':mayedit, 'teams':get_my_teams(request.user), }) # add a workout manually @login_required() def addmanual_view(request,raceid=0): r = Rower.objects.get(user=request.user) breadcrumbs = [ { 'url':'/rowers/list-workouts/', 'name':'Workouts' }, { 'url':reverse('addmanual_view'), 'name': 'Add Manual Entry' }, ] if request.method == 'POST': # Form was submitted form = WorkoutForm(request.POST) metricsform = MetricsForm(request.POST) if form.is_valid() and metricsform.is_valid(): # Get values from form name = form.cleaned_data['name'] if name == '': 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'] 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'] try: ps = form.cleaned_data['plannedsession'] except KeyError: ps = None try: boattype = request.POST['boattype'] except KeyError: boattype = '1x' try: privacy = request.POST['privacy'] except KeyError: privacy = 'visible' try: rankingpiece = form.cleaned_data['rankingpiece'] except KeyError: rankingpiece = False try: duplicate = form.cleaned_data['duplicate'] except KeyError: duplicate = False if private: 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, weightcategory=weightcategory, adaptiveclass=adaptiveclass, avghr=avghr, rankingpiece=rankingpiece, avgpwr=avgpwr, duplicate=duplicate, avgspm=avgspm, title = name, notes=notes, workouttype=workouttype) if message: messages.error(request,message) if id: w = Workout.objects.get(id=id) w.rankingpiece = rankingpiece w.privacy = privacy w.weightcategory = weightcategory w.adaptiveclass = adaptiveclass w.notes = notes w.plannedsession = ps w.name = name w.workouttype = workouttype w.boattype = boattype 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(): f = iform.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) 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: 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: 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: 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: messages.info(request,c) for er in errors: messages.error(request,er) if result: otherrecords = IndoorVirtualRaceResult.objects.filter( race = race ).exclude(userid=r.id) for otherrecord in otherrecords: try: otheruser = Rower.objects.get(id=otherrecord.userid) othername = otheruser.user.first_name+' '+otheruser.user.last_name registeredname = r.user.first_name+' '+r.user.last_name if otherrecord.emailnotifications: job = myqueue( queue, handle_sendemail_racesubmission, otheruser.user.email, othername, registeredname, race.name, race.id ) except Rower.DoesNotExist: 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: iform = ImageForm() return render(request,'manualadd.html', {'form':form, 'iform':iform, 'metricsform':metricsform, 'breadcrumbs':breadcrumbs, 'active':'nav-workouts', }) initial = { 'workouttype':'rower', 'date':datetime.date.today(), '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', }) @login_required() def fitness_metric_view(request,mode='rower',days=42): r = getrower(request.user) startdate = timezone.now()-datetime.timedelta(days=days) # test if not something already done ms = PowerTimeFitnessMetric.objects.filter(user=request.user) if not ms: url = reverse('workouts_view') return HttpResponseRedirect(url) max_workout_id = max([m.last_workout for m in ms]) last_update_date = max([m.date.strftime('%Y-%m-%d') for m in ms]) now_date = timezone.now().strftime('%Y-%m-%d') if mode == 'rower': workouts = Workout.objects.filter( user=r, workouttype__in=['rower','dynamic','slides'], startdatetime__gte=startdate) else: workouts = Workout.objects.filter( user=r, workouttype__in=['water','coastal'], startdatetime__gte=startdate) theids = [int(w.id) for w in workouts] max_id = max(theids) if last_update_date >= now_date or max_workout_id >= max_id: return HttpResponse("already done today or no new workouts") job = myqueue(queue, handle_updatefitnessmetric, request.user.id,mode,theids, ) return HttpResponse("job queued") @permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) @permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True) @user_passes_test(ispromember,login_url="/rowers/paidplans", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality. If you are already a Pro user, please log in to access this functionality", redirect_field_name=None) def workout_update_cp_view(request,id=0): row = get_workoutuser(id, request) row.rankingpiece = True row.save() r = getrower(request.user) dataprep.runcpupdate(r) if row.workouttype in mytypes.otwtypes: url = reverse('otwrankings_view') else: url = reverse('oterankings_view') return HttpResponseRedirect(url) # 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(filename) if rowdata: row.summary = rowdata.allstats() row.save() successmessage = "Summary Updated" messages.info(request,successmessage) url = reverse('workout_edit_view', kwargs = { 'id':id, }) else: message = "Something went wrong. Could not update summary" messages.error(request,message) url = reverse('workout_edit_view', kwargs = { 'id':id, }) return HttpResponseRedirect(url) # Joining workout @user_passes_test(ispromember,login_url="/rowers/paidplans", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality. If you are already a Pro user, please log in to access this functionality", redirect_field_name=None) @permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True) def workouts_join_view(request,userid=0): promember=0 r = getrequestrower(request,userid=userid) if not request.user.is_anonymous: r = getrower(request.user) result = request.user.is_authenticated and ispromember(request.user) if result: promember=1 if request.method == 'POST' and 'workouts' in request.POST: form = WorkoutMultipleCompareForm(request.POST) paramform = WorkoutJoinParamForm(request.POST) if form.is_valid() and paramform.is_valid(): workout_name = paramform.cleaned_data['workout_name'] set_private = paramform.cleaned_data['set_private'] killparents = paramform.cleaned_data['killparents'] cd = form.cleaned_data workouts = cd['workouts'] ids = [int(w.id) for w in workouts] request.session['ids'] = ids id,message = dataprep.join_workouts(r,ids, title=workout_name, setprivate=set_private, killparents=killparents) if message: messages.error(request,message) url = reverse(r.defaultlandingpage, kwargs = { 'id':encoder.encode_hex(id), }) return HttpResponseRedirect(url) else: return HttpResponse("form is not valid") else: url = reverse('workouts_join_select') return HttpResponseRedirect(url) defaultoptions = { 'includereststrokes': False, 'workouttypes':['rower','dynamic','slides'], 'waterboattype': mytypes.waterboattype, 'rankingonly': False, 'function':'boxplot' } @user_passes_test(ispromember, login_url="/rowers/paidplans", message="This functionality requires a Pro plan or higher", redirect_field_name=None) @permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True) def video_selectworkout(request,userid=0,teamid=0): r = getrequestrower(request, userid=userid) user = r.user userid = user.id workouts = Workout.objects.filter(user=r).order_by('-date') try: theteam = Team.objects.get(id=teamid) except Team.DoesNotExist: theteam = None if 'options' in request.session: options = request.session['options'] else: options=defaultoptions options['userid'] = userid try: workouttypes = options['workouttypes'] except KeyError: workouttypes = ['rower','dynamic','slides'] try: modalities = options['modalities'] modality = modalities[0] except KeyError: modalities = [m[0] for m in mytypes.workouttypes_ordered.items()] modality = 'all' try: rankingonly = options['rankingonly'] except KeyError: rankingonly = False query = request.GET.get('q') 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() 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: id = 0 else: form = WorkoutSingleSelectForm(workouts=workouts) thediv = '' dateform = DateRangeForm(initial={ 'startdate':startdate, 'enddate':enddate, }) negtypes = [] for b in mytypes.boattypes: if b[0] not in waterboattype: negtypes.append(b[0]) startdate = datetime.datetime.combine(startdate,datetime.time()) enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59)) if enddate < startdate: s = enddate enddate = startdate startdate = s negtypes = [] for b in mytypes.boattypes: if b[0] not in waterboattype: negtypes.append(b[0]) if theteam is not None and (theteam.viewing == 'allmembers' or theteam.manager == request.user): workouts = Workout.objects.filter(team=theteam, startdatetime__gte=startdate, startdatetime__lte=enddate, workouttype__in=modalities, ) elif theteam is not None and theteam.viewing == 'coachonly': workouts = Workout.objects.filter(team=theteam,user=r, startdatetime__gte=startdate, startdatetime__lte=enddate, workouttype__in=modalities, ) else: workouts = Workout.objects.filter(user=r, startdatetime__gte=startdate, startdatetime__lte=enddate, workouttype__in=modalities, ) workouts = workouts.order_by( "-date", "-starttime" ).exclude(boattype__in=negtypes) if rankingonly: workouts = workouts.exclude(rankingpiece=False) optionsform = AnalysisOptionsForm(initial={ 'modality':modality, 'waterboattype':waterboattype, 'rankingonly':rankingonly, }) startdatestring = startdate.strftime('%Y-%m-%d') enddatestring = enddate.strftime('%Y-%m-%d') request.session['startdate'] = startdatestring request.session['enddate'] = enddatestring request.session['options'] = options breadcrumbs = [ { 'url':'/rowers/analysis', 'name':'Analysis' }, { 'url':reverse('analysis_new',kwargs={'userid':userid}), 'name': 'Analysis Select' }, ] return render(request, 'video_selectworkout.html', {'workouts': workouts, 'dateform':dateform, 'startdate':startdate, 'enddate':enddate, 'rower':r, 'breadcrumbs':breadcrumbs, 'theuser':user, 'the_div':thediv, 'form':form, 'active':'nav-analysis', 'searchform':searchform, 'teams':get_my_teams(request.user), }) @permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True) @user_passes_test(ispromember,login_url="/rowers/paidplans", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality. If you are already a Pro user, please log in to access this functionality", redirect_field_name=None) def workouts_join_select(request, startdatestring="", enddatestring="", message='', successmessage='', userid=0, startdate=timezone.now()-datetime.timedelta(days=30), enddate=timezone.now()+datetime.timedelta(days=1), teamid=0): 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: 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': modalities = [m[0] for m in mytypes.workouttypes] else: modalities = [modality] if modality != 'water': waterboattype = [b[0] for b in mytypes.boattypes] request.session['modalities'] = modalities request.session['waterboattype'] = waterboattype else: dateform = DateRangeForm(initial={ 'startdate':startdate, 'enddate':enddate, }) negtypes = [] for b in mytypes.boattypes: if b[0] not in waterboattype: negtypes.append(b[0]) startdate = datetime.datetime.combine(startdate,datetime.time()) enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59)) #enddate = enddate+datetime.timedelta(days=1) if startdatestring: startdate = iso8601.parse_date(startdatestring) if enddatestring: enddate = iso8601.parse_date(enddatestring) if enddate < startdate: s = enddate enddate = startdate startdate = s try: theteam = Team.objects.get(id=teamid) except Team.DoesNotExist: theteam = 0 if theteam and (theteam.viewing == 'allmembers' or theteam.manager == request.user): workouts = Workout.objects.filter(team=theteam, startdatetime__gte=startdate, startdatetime__lte=enddate, workouttype__in=modalities).order_by("-date", "-starttime").exclude(boattype__in=negtypes) elif theteam and theteam.viewing == 'coachonly': workouts = Workout.objects.filter(team=theteam,user=r, startdatetime__gte=startdate, startdatetime__lte=enddate, workouttype__in=modalities).order_by("-date","-starttime").exclude(boattype__in=negtypes) else: theteam = None workouts = Workout.objects.filter(user=r, startdatetime__gte=startdate, startdatetime__lte=enddate, workouttype__in=modalities).order_by("-date", "-starttime").exclude(boattype__in=negtypes) query = request.GET.get('q') 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() form = WorkoutMultipleCompareForm() form.fields["workouts"].queryset = workouts if theteam: theid = theteam.id else: theid = 0 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', 'team':theteam, 'form':form, 'joinparamform':joinparamform, 'modalityform':modalityform, 'teams':get_my_teams(request.user), }) # Team comparison @user_passes_test(ispromember,login_url='/rowers/paidplans/', message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality. If you are already a Pro user, please log in to access this functionality", redirect_field_name=None) def team_comparison_select(request, startdatestring="", enddatestring="", message='', successmessage='', userid=0, startdate=timezone.now()-datetime.timedelta(days=30), enddate=timezone.now(), id=0, teamid=0): r = getrequestrower(request,userid=userid) requestrower = getrower(request.user) request.session.pop('ps',None) if 'waterboattype' in request.session: waterboattype = request.session['waterboattype'] else: waterboattype = mytypes.waterboattype if 'rankingonly' in request.session: rankingonly = request.session['rankingonly'] else: rankingonly = False if 'modalities' in request.session: modalities = request.session['modalities'] if len(modalities) > 1: modality = 'all' else: modality = modalities[0] else: modalities = [m[0] for m in mytypes.workouttypes] modality = 'all' if request.method == 'POST': dateform = DateRangeForm(request.POST) if dateform.is_valid(): startdate = dateform.cleaned_data['startdate'] enddate = dateform.cleaned_data['enddate'] startdatestring = startdate.strftime('%Y-%m-%d') enddatestring = enddate.strftime('%Y-%m-%d') request.session['startdate'] = startdatestring request.session['enddate'] = enddatestring modalityform = TrendFlexModalForm(request.POST) if modalityform.is_valid(): modality = modalityform.cleaned_data['modality'] waterboattype = modalityform.cleaned_data['waterboattype'] if modality == 'all': modalities = [m[0] for m in mytypes.workouttypes] else: modalities = [modality] if modality != 'water': waterboattype = [b[0] for b in mytypes.boattypes] if 'rankingonly' in modalityform.cleaned_data: rankingonly = modalityform.cleaned_data['rankingonly'] else: rankingonly = False request.session['modalities'] = modalities request.session['waterboattype'] = waterboattype else: dateform = DateRangeForm(initial={ 'startdate':startdate, 'enddate':enddate, }) modalityform = TrendFlexModalForm(initial={ 'modality':modality, 'waterboattype':waterboattype, 'rankingonly':rankingonly, }) negtypes = [] for b in mytypes.boattypes: if b[0] not in waterboattype: negtypes.append(b[0]) startdate = datetime.datetime.combine(startdate,datetime.time()) enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59)) #enddate = enddate+datetime.timedelta(days=1) if startdatestring: startdate = iso8601.parse_date(startdatestring) if enddatestring: enddate = iso8601.parse_date(enddatestring) if enddate < startdate: s = enddate enddate = startdate startdate = s try: theteam = Team.objects.get(id=teamid) except Team.DoesNotExist: theteam = 0 if theteam and (theteam.viewing == 'allmembers' or theteam.manager == request.user): workouts = Workout.objects.filter(team=theteam, startdatetime__gte=startdate, startdatetime__lte=enddate, workouttype__in=modalities).order_by("-date", "-starttime").exclude(boattype__in=negtypes) elif theteam and theteam.viewing == 'coachonly': workouts = Workout.objects.filter(team=theteam,user=r, startdatetime__gte=startdate, startdatetime__lte=enddate, workouttype__in=modalities).order_by("-date","-starttime").exclude(boattype__in=negtypes) else: theteam = None workouts = Workout.objects.filter(user=r, startdatetime__gte=startdate, startdatetime__lte=enddate, workouttype__in=modalities).order_by("-date", "-starttime").exclude(boattype__in=negtypes) if rankingonly: workouts = workouts.exclude(rankingpiece=False) query = request.GET.get('q') 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() if id: firstworkout = get_workout(id) if not is_workout_team(request.user,firstworkout): raise PermissionDenied("You are not allowed to use this workout") firstworkoutquery = Workout.objects.filter(id=encoder.decode_hex(id)) workouts = firstworkoutquery | workouts else: firstworkout = None form = WorkoutMultipleCompareForm() form.fields["workouts"].queryset = workouts if id: form.fields["workouts"].initial = [firstworkout] if theteam: theid = theteam.id else: theid = 0 chartform = ChartParamChoiceForm(initial={'teamid':0}) messages.info(request,successmessage) messages.error(request,message) if id: breadcrumbs = [ { 'url':'/rowers/list-workouts/', 'name':'Workouts' }, { 'url':get_workout_default_page(request,id), 'name': firstworkout.name }, { 'url':reverse('team_comparison_select',kwargs={'id':id,'teamid':teamid}), 'name':'Compare Select' }, ] else: breadcrumbs = [ { 'url':'/rowers/list-workouts/', 'name':'Workouts' }, { 'url':reverse('team_comparison_select',kwargs={'teamid':teamid}), 'name': 'Compare Select' }, ] return render(request, 'team_compare_select.html', {'workouts': workouts, 'workout':firstworkout, 'dateform':dateform, 'startdate':startdate, 'enddate':enddate, 'team':theteam, 'searchform':searchform, 'form':form, 'rower':r, 'breadcrumbs':breadcrumbs, 'active':'nav-workouts', 'chartform':chartform, 'modalityform':modalityform, 'teams':get_my_teams(request.user), }) def 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: raise Http404("Virtual Challenge does not exist") if race.sessiontype != 'race': 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: 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: 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 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: r = None try: race = VirtualRace.objects.get(id=id) except VirtualRace.DoesNotExist: raise Http404("Virtual Challenge does not exist") if race.sessiontype == 'race': script,div = course_map(race.course) resultobj = VirtualRaceResult else: 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: 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: pass form = WorkoutMultipleCompareForm() form.fields["workouts"].queryset = Workout.objects.filter(id__in=workoutids) form.fields["workouts"].initial = Workout.objects.filter(id__in=workoutids) labeldict = { int(w.id): w.__str__() for w in workouts } chartform = ChartParamChoiceForm( initial = { 'xparam':xparam, 'yparam':yparam, 'plottype':plottype, 'teamid':0 } ) if request.method == 'POST' and 'workouts' in request.POST: form = WorkoutMultipleCompareForm(request.POST) form.fields["workouts"].queryset = Workout.objects.filter(id__in=workoutids) chartform = ChartParamChoiceForm(request.POST) if form.is_valid() and chartform.is_valid(): cd = form.cleaned_data workouts = cd['workouts'] workoutids = [w.id for w in workouts] xparam = chartform.cleaned_data['xparam'] yparam = chartform.cleaned_data['yparam'] plottype = chartform.cleaned_data['plottype'] teamid = chartform.cleaned_data['teamid'] ids = [int(w.id) for w in workouts] request.session['ids'] = ids elif request.method == 'POST': form = WorkoutMultipleCompareForm() form.fields["workouts"].queryset = Workout.objects.filter(id__in=workoutids) request.session['ids'] = workoutids chartform = ChartParamChoiceForm(request.POST) if chartform.is_valid(): xparam = chartform.cleaned_data['xparam'] yparam = chartform.cleaned_data['yparam'] plottype = chartform.cleaned_data['plottype'] teamid = chartform.cleaned_data['teamid'] try: workoutids = request.session['ids'] except KeyError: pass workouts = [] for id in workoutids: try: workouts.append(Workout.objects.get( id=id)) except Workout.DoesNotExist: 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 != '': messages.error(request,errormessage) breadcrumbs = [ { 'url': reverse('virtualevents_view'), 'name': 'Challenges' }, { 'url':reverse('virtualevent_view', kwargs={ 'id':race.id, } ), 'name': race.name }, { 'url':reverse('virtualevent_compare_view', kwargs={ 'id':race.id, } ), 'name': 'Compare' } ] return render(request,'multicompare.html', {'interactiveplot':script, 'the_div':div, 'breadcrumbs':breadcrumbs, 'rower':r, 'race':race, 'results':results, 'active':'nav-racing', 'promember':promember, 'teamid':0, 'chartform':chartform, 'form':form, 'teams':[] }) @permission_required('plannedsession.view_session', fn=get_session_by_pk,raise_exception=True) def plannedsession_compare_view(request,id=0,userid=0): r = getrequestrower(request,userid=userid) try: ps = PlannedSession.objects.get(id=id) except PlannedSession.DoesNotExist: raise Http404("Planned session does not exist") workouts = Workout.objects.filter(plannedsession=ps) ids = [int(w.id) for w in workouts] labeldict = { int(w.id): w.__str__() for w in workouts } xparam = 'time' yparam = 'hr' plottype = 'line' request.session['ids'] = ids request.session['xparam'] = xparam request.session['yparam'] = yparam request.session['plottype'] = plottype request.session['ps'] = ps.id if ids: url = reverse('multi_compare_view', kwargs={'userid':userid,'id':encoder.encode_hex(ids[0])}) else: url = reverse('plannedsession_view',kwargs={'id':ps.id}) return HttpResponseRedirect(url) # Team comparison @login_required() def multi_compare_view(request,id=0,userid=0): promember=0 if not request.user.is_anonymous: r = getrower(request.user) result = request.user.is_authenticated and ispromember(request.user) if result: promember=1 if request.method == 'POST' and 'workouts' in request.POST: form = WorkoutMultipleCompareForm(request.POST) chartform = ChartParamChoiceForm(request.POST) if form.is_valid() and chartform.is_valid(): cd = form.cleaned_data workouts = cd['workouts'] xparam = chartform.cleaned_data['xparam'] yparam = chartform.cleaned_data['yparam'] plottype = chartform.cleaned_data['plottype'] teamid = chartform.cleaned_data['teamid'] ids = [int(w.id) for w in workouts] request.session['ids'] = ids labeldict = { int(w.id): w.__str__() for w in workouts } else: return HttpResponse("Form is not valid") elif request.method == 'POST' and 'ids' in request.session: chartform = ChartParamChoiceForm(request.POST) if chartform.is_valid(): xparam = chartform.cleaned_data['xparam'] yparam = chartform.cleaned_data['yparam'] plottype = chartform.cleaned_data['plottype'] teamid = chartform.cleaned_data['teamid'] ids = request.session['ids'] request.session['ids'] = ids workouts = [] for id in ids: try: workouts.append(Workout.objects.get(id=id)) except Workout.DoesNotExist: pass labeldict = { int(w.id): w.__str__() for w in workouts } elif 'ids' in request.session and 'plottype' in request.session: xparam = request.session['xparam'] yparam = request.session['yparam'] plottype = request.session['plottype'] teamid = 0 ids = request.session['ids'] workouts = [] for id in ids: try: workouts.append(Workout.objects.get(id=id)) except Workout.DoesNotExist: pass labeldict = { int(w.id): w.__str__() for w in workouts } chartform = ChartParamChoiceForm( initial = { 'xparam':xparam, 'yparam':yparam, 'plottype':plottype, 'teamid':teamid } ) else: url = reverse('team_comparison_select', kwargs={ 'id':id, 'teamid':0}) return HttpResponseRedirect(url) res = interactive_multiple_compare_chart(ids,xparam,yparam, promember=promember, plottype=plottype, labeldict=labeldict) script = res[0] div = res[1] errormessage = res[3] if errormessage != '': messages.error(request,errormessage) r = getrower(request.user) breadcrumbs = [ { 'url':'/rowers/list-workouts/', 'name':'Workouts' }, { 'url':reverse('team_comparison_select',kwargs={'teamid':teamid}), 'name': 'Compare Select' }, { 'url':reverse('multi_compare_view'), 'name': 'Comparison Chart' } ] if 'ps' in request.session: ps = PlannedSession.objects.get(id=int(request.session['ps'])) breadcrumbs = [ { 'url': reverse('plannedsessions_view', kwargs={'userid':userid}), 'name': 'Sessions' }, { 'url':reverse('plannedsession_view', kwargs={ 'userid':userid, 'id':ps.id, } ), 'name': ps.id }, { 'url':reverse('plannedsession_compare_view', kwargs={ 'userid':userid, 'id':ps.id, } ), 'name': 'Compare' } ] return render(request,'multicompare.html', {'interactiveplot':script, 'the_div':div, 'breadcrumbs':breadcrumbs, 'rower':r, 'active':'nav-workouts', 'promember':promember, 'teamid':teamid, 'chartform':chartform, 'teams':get_my_teams(request.user), }) # List Workouts @login_required() def workouts_view(request,message='',successmessage='', teamid=0,rankingonly=False,rowerid=0,userid=0): startdate,enddate = get_dates_timeperiod(request,defaulttimeperiod='lastyear') request.session['referer'] = absolute(request)['PATH'] r = getrequestrower(request,rowerid=rowerid,userid=userid) # check if access is allowed startdate = datetime.datetime.combine(startdate,datetime.time()) enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59)) query = None if request.method == 'POST': dateform = DateRangeForm(request.POST) searchform = SearchForm(request.POST) if dateform.is_valid(): startdate = dateform.cleaned_data['startdate'] enddate = dateform.cleaned_data['enddate'] if searchform.is_valid(): query = searchform.cleaned_data['q'] else: dateform = DateRangeForm(initial={ 'startdate':startdate, 'enddate':enddate, }) usertimezone = pytz.timezone(r.defaulttimezone) startdate = datetime.datetime.combine(startdate,datetime.time()).astimezone(usertimezone) enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59)).astimezone(usertimezone) #enddate = enddate+datetime.timedelta(days=1) if enddate < startdate: s = enddate enddate = startdate startdate = s startdatestring = startdate.strftime('%Y-%m-%d') enddatestring = enddate.strftime('%Y-%m-%d') # start date for the small graph activity_startdate = enddate-datetime.timedelta(days=15) try: if enddate > timezone.now(): activity_enddate = timezone.now() activity_enddate = activity_enddate.replace(hour=23,minute=59,second=59).astimezone(usertimezone) activity_startdate = activity_enddate-datetime.timedelta(days=15) activity_startdate = activity_startdate.replace(hour=0,minute=0,second=0) else: activity_enddate = enddate except ValueError: activity_enddate = enddate g_startdate = activity_startdate g_enddate = activity_enddate if teamid: try: theteam = Team.objects.get(id=teamid) except Team.DoesNotExist: 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': workouts = Workout.objects.filter( team=theteam,user=r, startdatetime__gte=startdate, startdatetime__lte=enddate, privacy='visible').order_by("-startdatetime") g_workouts = Workout.objects.filter( team=theteam,user=r, startdatetime__gte=activity_startdate, startdatetime__lte=activity_enddate, duplicate=False, privacy='visible').order_by("-startdatetime") elif request.user != r.user: theteam = None workouts = Workout.objects.filter( user=r, startdatetime__gte=startdate, startdatetime__lte=enddate, privacy='visible').order_by("-date", "-starttime") g_workouts = Workout.objects.filter( user=r, startdatetime__gte=activity_startdate, startdatetime__lte=activity_enddate, duplicate=False, privacy='visible').order_by("-startdatetime") else: theteam = None workouts = Workout.objects.filter( user=r, startdatetime__gte=startdate, startdatetime__lte=enddate).order_by("-date", "-starttime") g_workouts = Workout.objects.filter( user=r, duplicate=False, startdatetime__gte=activity_startdate, startdatetime__lte=activity_enddate).order_by("-startdatetime") if g_workouts.count() == 0: g_workouts = Workout.objects.filter( user=r, startdatetime__gte=timezone.now()-timedelta(days=15)).order_by("-startdatetime") g_enddate = timezone.now() g_startdate = (timezone.now()-timedelta(days=15)) if rankingonly: workouts = workouts.exclude(rankingpiece=False) workoutsnohr = workouts.exclude(averagehr__isnull=False) for w in workoutsnohr: res = dataprep.workout_trimp(w) # ids = [w.id for w in workouts] # df = dataprep.getsmallrowdata_db(['time','power'],ids=ids) # polarization = dataprep.polarization_index(df,r) if query: query_list = query.split() workouts = workouts.filter( reduce(operator.and_, (Q(name__icontains=q) for q in query_list)) | reduce(operator.and_, (Q(notes__icontains=q) for q in query_list)) ) searchform = SearchForm(initial={'q':query}) else: searchform = SearchForm() paginator = Paginator(workouts,20) # show 25 workouts per page page = request.GET.get('page') try: workouts = paginator.page(page) except PageNotAnInteger: workouts = paginator.page(1) except EmptyPage: 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' script,div = interactive_activitychart(g_workouts, g_startdate, g_enddate, stack=stack) totalmeters,totalhours, totalminutes,total_seconds = get_totals(g_workouts) totalminutes = '{totalminutes:02d}'.format(totalminutes=totalminutes) messages.info(request,successmessage) messages.error(request,message) breadcrumbs = [ { 'url':'/rowers/list-workouts/', 'name':'Workouts' }, ] timeperiod = startdate.strftime('%Y-%m-%d')+'/'+enddate.strftime('%Y-%m-%d') return render(request, 'list_workouts.html', {'workouts': workouts, 'active': 'nav-workouts', 'rower':r, 'searchform':searchform, 'breadcrumbs':breadcrumbs, 'dateform':dateform, 'startdate':startdate, 'enddate':enddate, 'announcements':announcements[0:4], 'team':theteam, 'rankingonly':rankingonly, 'teams':get_my_teams(request.user), 'interactiveplot':script, 'the_div':div, 'timeperiod':timeperiod, 'totalmeters':totalmeters, 'totalminutes':totalminutes, 'totalhours':totalhours, }) # List of workouts to compare a selected workout to @user_passes_test(ispromember,login_url="/rowers/paidplans", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality. If you are already a Pro user, please log in to access this functionality", redirect_field_name=None) @permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) def workout_fusion_list(request,id=0, startdatestring="",enddatestring="", startdate=timezone.now()-datetime.timedelta(days=365), enddate=timezone.now()): r = getrequestrower(request) u = r.user if request.method == 'POST': 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, }) if startdatestring: startdate = iso8601.parse_date(startdatestring) if enddatestring: enddate = iso8601.parse_date(enddatestring) startdate = datetime.datetime.combine(startdate,datetime.time()) enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59)) #enddate = enddate+datetime.timedelta(days=1) if enddate < startdate: 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: 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') try: workouts = paginator.page(page) except PageNotAnInteger: workouts = paginator.page(1) except EmptyPage: 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): 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(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: try: width,height = Image.open(i.filename).size i.width = width i.height = height i.save() except: pass # get raceresult intervaldata = {} if raceresult != 0: try: result = VirtualRaceResult.objects.get(id=raceresult) startsecond = result.startsecond endsecond = result.endsecond duration = row.duration durationsecs = duration.hour*3600+duration.minute*60+duration.second itime = [startsecond,endsecond-startsecond] itype = [3,4] intervaldata['itime'] = itime intervaldata['itype'] = itype 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(): hascoordinates = 0 except (KeyError,AttributeError): hascoordinates = 0 else: hascoordinates = 0 if hascoordinates: mapscript,mapdiv = leaflet_chart(rowdata.df[' latitude'], rowdata.df[' longitude'], row.name) 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), 'the_div':div}) # Resets stroke data to raw data (pace) @permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) @user_passes_test(ispromember,login_url="/rowers/paidplans", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality. If you are already a Pro user, please log in to access this functionality", redirect_field_name=None) def workout_undo_smoothenpace_view( request,id=0,message="",successmessage="" ): row = get_workoutuser(id, request) r = getrower(request.user) filename = row.csvfilename row = rdata(filename) if row == 0: return HttpResponse("Error: CSV Data File Not Found") if 'originalvelo' in row.df: velo = row.df['originalvelo'].values row.df[' Stroke500mPace (sec/500m)'] = 500./velo row.write_csv(filename,gzip=True) dataprep.update_strokedata(encoder.decode_hex(id),row.df) url = reverse(r.defaultlandingpage, kwargs = { 'id':id, } ) return HttpResponseRedirect(url) # Data smoothing of pace data @permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) @user_passes_test(ispromember,login_url="/rowers/paidplans", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality. If you are already a Pro user, please log in to access this functionality", redirect_field_name=None) def workout_smoothenpace_view(request,id=0,message="",successmessage=""): row = get_workoutuser(id, request) previousurl = request.META.get('HTTP_REFERER') r = getrower(request.user) filename = row.csvfilename row = rdata(filename) if row == 0: 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: url = previousurl else: url = reverse(r.defaultlandingpage, kwargs = { 'id':id, } ) return HttpResponseRedirect(url) if not 'originalvelo' in row.df: row.df['originalvelo'] = velo velo2 = stravastuff.ewmovingaverage(velo,5) pace2 = 500./abs(velo2) row.df[' Stroke500mPace (sec/500m)'] = pace2 row.df = row.df.fillna(0) row.write_csv(filename,gzip=True) dataprep.update_strokedata(encoder.decode_hex(id),row.df) messages.info(request,'A smoothening filter was applied to your pace data') if previousurl: url = previousurl else: url = reverse(r.defaultlandingpage, kwargs = { 'id':id, } ) return HttpResponseRedirect(url) # Process CrewNerd Summary CSV and update summary @user_passes_test(ispromember,login_url="/rowers/paidplans", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality. If you are already a Pro user, please log in to access this functionality", redirect_field_name=None) def workout_crewnerd_summary_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_crewnerd_summary_view',kwargs={'id':id}), 'name': 'CrewNerd Summary' } ] if request.method == 'POST': form = CNsummaryForm(request.POST,request.FILES) if form.is_valid(): f = request.FILES['file'] res = handle_uploaded_file(f) fname = res[1] try: sumd = summarydata(fname) row.summary = sumd.allstats() row.save() os.remove(fname) successmessage = "CrewNerd summary added" messages.info(request,successmessage) url = reverse('workout_edit_view', kwargs = { 'id':id, }) return HttpResponseRedirect(url) except: try: os.remove(fname) except: pass message = "Something went wrong (workout_crewnerd_summary_view)" messages.error(request,message) url = reverse('workout_edit_view', kwargs = { 'id':id, }) return HttpResponseRedirect(url) else: return render(request, "cn_form.html", {'form':form, 'active':'nav-workouts', 'rower':r, 'workout':row, 'breadcrumbs':breadcrumbs, 'teams':get_my_teams(request.user), 'id':row.id}) else: form = CNsummaryForm() return render(request, "cn_form.html", {'form':form, 'active':'nav-workouts', 'rower':r, 'workout':row, 'breadcrumbs':breadcrumbs, 'teams':get_my_teams(request.user), 'id':row.id}) # 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(f1) if rowdata == 0: return HttpResponse("Error: CSV Data File Not Found") try: bearing = rowdata.df.loc[:,'bearing'].values except KeyError: rowdata.add_bearing() rowdata.write_csv(f1,gzip=True) # get wind try: avglat = rowdata.df[' latitude'].mean() avglon = rowdata.df[' longitude'].mean() avgtime = int(rowdata.df['TimeStamp (sec)'].mean()-rowdata.df.loc[:,'TimeStamp (sec)'].iloc[0]) startdatetime = dateutil.parser.parse("{}, {}".format(row.date, row.starttime)) starttimeunix = int(arrow.get(row.startdatetime).timestamp) #starttimeunix = int(mktime(startdatetime.utctimetuple())) avgtime = starttimeunix+avgtime winddata = get_wind_data(avglat,avglon,avgtime) windspeed = winddata[0] windbearing = winddata[1] message = winddata[2] if message is not None: try: row.notes += "\n"+message except TypeError: 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(f1) if rowdata == 0: return HttpResponse("Error: CSV Data File Not Found") try: bearing = rowdata.df.loc[:,'bearing'].values except KeyError: rowdata.add_bearing() rowdata.write_csv(f1,gzip=True) # get wind try: avglat = rowdata.df[' latitude'].mean() avglon = rowdata.df[' longitude'].mean() airportcode = get_airport_code(avglat,avglon)[0] avgtime = int(rowdata.df['TimeStamp (sec)'].mean()-rowdata.df.loc[:,'TimeStamp (sec)'].iloc[0]) startdatetime = dateutil.parser.parse("{}, {}".format(row.date, row.starttime)) starttimeunix = arrow.get(row.startdatetime).timestamp #starttimeunix = int(mktime(startdatetime.utctimetuple())) avgtime = starttimeunix +avgtime winddata = get_metar_data(airportcode,avgtime) windspeed = winddata[0] windbearing = winddata[1] message = winddata[2] try: row.notes += "\n"+message except TypeError: 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(f1) if row == 0: 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(): hascoordinates = 0 try: bearing = rowdata.df.loc[:,'bearing'].values except KeyError: rowdata.add_bearing() rowdata.write_csv(f1,gzip=True) if hascoordinates: avglat = rowdata.df[' latitude'].mean() avglon = rowdata.df[' longitude'].mean() airportcode,newlat,newlon,airportdistance = get_airport_code(avglat,avglon) airportcode = airportcode.upper() airportdistance = airportdistance[0] else: airportcode = 'UNKNOWN' airportdistance = 0 if request.method == 'POST': # process form form = UpdateWindForm(request.POST) if form.is_valid(): vwind1 = form.cleaned_data['vwind1'] vwind2 = form.cleaned_data['vwind2'] dist1 = form.cleaned_data['dist1'] dist2 = form.cleaned_data['dist2'] winddirection1 = form.cleaned_data['winddirection1'] winddirection2 = form.cleaned_data['winddirection2'] windunit = form.cleaned_data['windunit'] rowdata.update_wind(vwind1,vwind2, winddirection1, winddirection2, dist1,dist2, units=windunit) rowdata.write_csv(f1,gzip=True) else: message = "Invalid Form" messages.error(request,message) kwargs = { 'id':id } url = reverse('workout_wind_view',kwargs=kwargs) response = HttpResponseRedirect(url) else: form = UpdateWindForm() # create interactive plot res = interactive_windchart(encoder.decode_hex(id),promember=1) script = res[0] div = res[1] if hascoordinates: gmscript,gmdiv = leaflet_chart( rowdata.df[' latitude'], rowdata.df[' longitude'], row.name) else: gmscript = "" gmdiv = "No GPS data available" messages.info(request,successmessage) messages.error(request,message) return render(request, 'windedit.html', {'workout':row, 'rower':r, 'breadcrumbs':breadcrumbs, 'active':'nav-workouts', 'teams':get_my_teams(request.user), 'interactiveplot':script, 'form':form, 'airport':airportcode, 'airportdistance':airportdistance, 'the_div':div, 'gmap':gmscript, 'gmapdiv':gmdiv}) # Show form to update River stream data (for river dwellers) @permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) @user_passes_test(ispromember,login_url="/rowers/paidplans",message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality",redirect_field_name=None) def workout_stream_view(request,id=0,message="",successmessage=""): row = get_workoutuser(id, request) r = getrower(request.user) # create interactive plot f1 = row.csvfilename u = row.user.user r = getrower(u) rowdata = rdata(f1) if rowdata == 0: 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: message = "Invalid Form" messages.error(request,message) kwargs = { 'id':id} url = reverse('workout_wind_view',kwargs=kwargs) response = HttpResponseRedirect(url) else: form = UpdateStreamForm() # create interactive plot res = interactive_streamchart(encoder.decode_hex(id),promember=1) script = res[0] div = res[1] breadcrumbs = [ { 'url':'/rowers/list-workouts/', 'name':'Workouts' }, { 'url':get_workout_default_page(request,id), 'name': row.name }, { 'url':reverse('workout_stream_view',kwargs={'id':id}), 'name': 'Stream' } ] messages.info(request,successmessage) messages.error(request,message) return render(request, 'streamedit.html', {'workout':row, 'rower':r, 'breadcrumbs':breadcrumbs, 'active':'nav-workouts', 'teams':get_my_teams(request.user), 'interactiveplot':script, 'form':form, 'the_div':div}) # Form to set average crew weight and boat type, then run power calcs @permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) @user_passes_test(ispromember, login_url="/rowers/paidplans",redirect_field_name=None) def workout_otwsetpower_view(request,id=0,message="",successmessage=""): w = get_workoutuser(id, request) r = getrower(request.user) mayedit = 1 if request.method == 'POST': # process form form = AdvancedWorkoutForm(request.POST) if form.is_valid(): #quick_calc = form.cleaned_data['quick_calc'] #go_service = form.cleaned_data['go_service'] boattype = form.cleaned_data['boattype'] weightvalue = form.cleaned_data['weightvalue'] w.boattype = boattype w.weightvalue = weightvalue w.save() # load row data & create power/wind/bearing columns if not set f1 = w.csvfilename rowdata = rdata(f1) if rowdata == 0: return HttpResponse("Error: CSV Data File Not Found") try: vstream = rowdata.df['vstream'] except KeyError: rowdata.add_stream(0) rowdata.write_csv(f1,gzip=True) try: bearing = rowdata.df['bearing'] except KeyError: rowdata.add_bearing() rowdata.write_csv(f1,gzip=True) try: vwind = rowdata.df['vwind'] except KeyError: rowdata.add_wind(0,0) rowdata.write_csv(f1,gzip=True) # do power calculation (asynchronous) r = w.user u = r.user first_name = u.first_name last_name = u.last_name emailaddress = u.email job = myqueue(queuelow, handle_otwsetpower,f1,boattype, 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: message = "Invalid Form" messages.error(request,message) kwargs = { 'id':id} url = reverse('workout_otwsetpower_view',kwargs=kwargs) response = HttpResponseRedirect(url) else: form = AdvancedWorkoutForm(instance=w) breadcrumbs = [ { 'url':'/rowers/list-workouts/', 'name':'Workouts' }, { 'url':get_workout_default_page(request,id), 'name': w.name }, { 'url':reverse('workout_otwsetpower_view',kwargs={'id':id}), 'name': 'OTW Power' } ] messages.error(request,message) messages.info(request,successmessage) return render(request, 'otwsetpower.html', {'workout':w, 'rower':w, 'mayedit':mayedit, 'active':'nav-workouts', 'breadcrumbs':breadcrumbs, 'teams':get_my_teams(request.user), 'form':form, }) @permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) def instroke_view(request,id=0): w = get_workoutuser(id, request) r = getrower(request.user) mayedit = 1 breadcrumbs = [ { 'url':'/rowers/list-workouts/', 'name':'Workouts' }, { 'url':get_workout_default_page(request,id), 'name': w.name }, { 'url':reverse('instroke_view',kwargs={'id':id}), 'name': 'In-Stroke Metrics' } ] # form = WorkoutForm(instance=row) g = GraphImage.objects.filter(workout=w).order_by("-creationdatetime") # check if user is owner of this workout rowdata = rrdata(csvfile=w.csvfilename) try: instrokemetrics = rowdata.get_instroke_columns() instrokemetrics = [m for m in instrokemetrics if not m in nometrics] except AttributeError: 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=''): w = get_workoutuser(id, request) rowdata = rrdata(csvfile=w.csvfilename) instrokemetrics = rowdata.get_instroke_columns() if metric in instrokemetrics: f1 = w.csvfilename[6:-4] timestr = strftime("%Y%m%d-%H%M%S") imagename = f1+timestr+'.png' fullpathimagename = 'static/plots/'+imagename u = w.user.user r = getrower(u) title = w.name fig1 = rowdata.get_plot_instroke(metric) canvas = FigureCanvas(fig1) canvas.print_figure('static/plots/'+imagename) plt.close(fig1) fig1.clf() gc.collect() try: width,height = Image.open(fullpathimagename).size except: width = 1200 height = 600 imgs = GraphImage.objects.filter(workout=w) if imgs.count() < 7: i = GraphImage(workout=w, creationdatetime=timezone.now(), filename=fullpathimagename, width=width,height=height) i.save() else: messages.error(request,'You have reached the maximum number of static images for this workout. Delete an image first') r = getrower(request.user) url = reverse(r.defaultlandingpage, kwargs = { 'id':id, }) return HttpResponseRedirect(url) # 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: # tcols = list(set(datadf.columns(tolist)+tcols)) try: datadf = datadf[tcols] datadf = datadf.fillna(value=0) except KeyError: pass for col in cols: try: if datadf[col].mean() == 0 and datadf[col].std() == 0: datadf.drop(labels=[col],axis=1,inplace=True) except (TypeError,KeyError): pass # pd.set_option('display.width', 1000) #pd.set_option('colheader_justify', 'left') htmltable = datadf.to_html( bold_rows=True, show_dimensions=True,border=1, classes=['pandastable'],justify='justify' ) return render(request, 'workout_data.html', { 'htmltable': htmltable, 'form':form, 'teams':get_my_teams(request.user), 'workout': w, 'breadcrumbs': breadcrumbs, } ) # Stats page @permission_required('workout.view_workout',fn=get_workout_by_opaqueid,raise_exception=True) def workout_stats_view(request,id=0,message="",successmessage=""): r = getrower(request.user) w = get_workout(id) mayedit = 0 if request.user == w.user.user: mayedit=1 if is_workout_user(request.user,w): mayedit=1 breadcrumbs = [ { 'url':'/rowers/list-workouts/', 'name':'Workouts' }, { 'url':get_workout_default_page(request,id), 'name': w.name }, { 'url':reverse('workout_stats_view',kwargs={'id':id}), 'name': 'Stats' } ] workstrokesonly = True if request.method == 'POST' and 'workstrokesonly' in request.POST: 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() # fielddict.pop('workoutstate') # fielddict.pop('workoutid') try: fielddict.pop('pace') except KeyError: 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: 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: thedict[verbosename2] = 0 cordict[verbosename1] = thedict # additional non-automated stats otherstats = {} # Normalized power & TSS tss,normp = dataprep.workout_rscore(w) 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): hrdrift = int(100*hrdrift)/100. otherstats['hrdrift'] = { 'verbose_name': 'Heart Rate Drift', 'value': hrdrift, 'unit': '%', } except (ZeroDivisionError,ValueError): 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: 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) LeftPanelFormSet = formset_factory(WorkFlowLeftPanelElement,extra=1) if request.method == 'POST': wasmiddle = [1 for key,value in request.POST.items() if 'middlepanel' in key.lower()] wasleft = [1 for key,valye in request.POST.items() if 'leftpanel' in key.lower()] if wasmiddle: middlepanel_formset = MiddlePanelFormSet(request.POST, prefix='middlepanel') newmiddlepanel = [] if middlepanel_formset.is_valid(): for form in middlepanel_formset: value = form.cleaned_data.get('panel') if value != 'None': newmiddlepanel.append(value) newmiddlepanel = [i for i in newmiddlepanel if i != None] r.workflowmiddlepanel = newmiddlepanel try: r.save() except IntegrityError: messages.error(request,'Something went wrong') if wasleft: leftpanel_formset = LeftPanelFormSet(request.POST, prefix='leftpanel') newleftpanel = [] if leftpanel_formset.is_valid(): for form in leftpanel_formset: value = form.cleaned_data.get('panel') if value != 'None': newleftpanel.append(value) newleftpanel = [i for i in newleftpanel if i != None] r.workflowleftpanel = newleftpanel try: r.save() except IntegrityError: messages.error(request,'Something went wrong') leftpanelform_data = [{'panel':panel} for panel in r.workflowleftpanel] middlepanelform_data = [{'panel':panel} for panel in r.workflowmiddlepanel] leftpanel_formset = LeftPanelFormSet(initial=leftpanelform_data, prefix='leftpanel') middlepanel_formset = MiddlePanelFormSet(initial=middlepanelform_data, prefix='middlepanel') tmplt = 'workflowconfig2.html' return render(request,tmplt, { 'rower':r, 'leftpanel_formset':leftpanel_formset, 'middlepanel_formset':middlepanel_formset, 'workoutid': workoutid, }) # Workflow View @login_required() @permission_required('workout.view_workout',fn=get_workout_by_opaqueid,raise_exception=True) def workout_workflow_view(request,id): request.session['referer'] = absolute(request)['PATH'] request.session['lastworkout'] = id request.session[translation.LANGUAGE_SESSION_KEY] = USER_LANGUAGE row = get_workout_by_opaqueid(request,id) r = getrower(request.user) result = request.user.is_authenticated and ispromember(request.user) if result: promember=1 if request.user == row.user.user: mayedit=1 comments = WorkoutComment.objects.filter(workout=row) aantalcomments = len(comments) favorites,maxfav = getfavorites(r,row) charts = get_call() if 'panel_map.html' in r.workflowmiddlepanel and rowhascoordinates(row): rowdata = rdata(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: pass leftTemplates = [] for t in r.workflowleftpanel: try: template.loader.get_template(t) leftTemplates.append(t) except template.TemplateDoesNotExist: 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: raise Http404("Invalid workout number") if 'promember' in kwargs: promember = kwargs['promember'] else: promember = 0 try: favoritenr = int(request.GET['favoritechart']) except: favoritenr = -1 row = get_workout(id) r = getrequestrower(request) promember=0 mayedit=0 if not request.user.is_anonymous: result = ispromember(request.user) if result: promember=1 if request.user == row.user.user: mayedit=1 if is_workout_user(request.user,row): mayedit=1 workouttype = 'ote' if row.workouttype in mytypes.otwtypes: workouttype = 'otw' favorites,maxfav = getfavorites(r,row) # check if favoritenr is not out of range if favorites: try: t = favorites[favoritenr].xparam except IndexError: 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 == '': yparam2 = 'None' else: if favorites: yparam2 = favorites[favoritenr].yparam2 if yparam2 == '': 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: favoritechartnotes = '' else: favoritechartnotes = '' else: favoritechartnotes = '' favoritenr = 0 if 'plottype' in kwargs: plottype = kwargs['plottype'] else: if favorites: plottype = favorites[favoritenr].plottype else: plottype = 'line' if 'workstrokesonly' in kwargs: workstrokesonly = kwargs['workstrokesonly'] else: if favorites: workstrokesonly = not favorites[favoritenr].reststrokes else: workstrokesonly = False if request.method == 'POST' and 'savefavorite' in request.POST: if not request.user.is_anonymous: workstrokesonly = request.POST['workstrokesonlysave'] reststrokes = not workstrokesonly r = getrower(request.user) try: range = metrics.yaxmaxima[xparam] if yparam1 is not None: range = metrics.yaxmaxima[yparam1] if yparam2 is not None: range = metrics.yaxmaxima[yparam2] f = FavoriteChart(user=r,xparam=xparam, yparam1=yparam1,yparam2=yparam2, plottype=plottype,workouttype=workouttype, reststrokes=reststrokes) f.save() except KeyError: 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: xparam = 'time' messages.info(request,'To use '+d['verbose_name']+', you have to be Pro member') if yparam1 == name: yparam1 = 'pace' messages.info(request,'To use '+d['verbose_name']+', you have to be Pro member') if yparam2 == name: 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 try: ( script, div, js_resources, css_resources, workstrokesonly ) = interactive_flex_chart2( encoder.decode_hex(id),xparam=xparam,yparam1=yparam1, yparam2=yparam2, promember=promember,plottype=plottype, workstrokesonly=workstrokesonly ) except ValueError: ( script, div, js_resources, css_resources, workstrokesonly ) = interactive_flex_chart2( encoder.decode_hex(id),xparam=xparam,yparam1=yparam1, yparam2=yparam2, promember=promember,plottype=plottype, workstrokesonly=workstrokesonly ) js_resources = "" css_resources = "" 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(row.csvfilename) try: rowdata.set_instroke_metrics() except (AttributeError,TypeError): pass try: additionalmetrics = rowdata.get_additional_metrics() additionalmetrics = [m for m in additionalmetrics if not m in nometrics] except AttributeError: additionalmetrics = [] # extrametrics = {m.replace('/','_slsh_'):m for m in additionalmetrics} extrametrics = additionalmetrics initial = { 'xaxis':xparam, 'yaxis1':yparam1, 'yaxis2':yparam2, } flexaxesform = FlexAxesForm(request,initial=initial, extrametrics=extrametrics) initial = { 'includereststrokes': not workstrokesonly, 'plottype':plottype } flexoptionsform = FlexOptionsForm(initial=initial) row = Workout.objects.get(id=encoder.decode_hex(id)) breadcrumbs = [ { 'url':'/rowers/list-workouts/', 'name':'Workouts' }, { 'url':get_workout_default_page(request,id), 'name': row.name }, { 'url':reverse('workout_flexchart3_view',kwargs=kwargs), 'name': 'Flex Chart' } ] return render(request, 'flexchart3otw.html', {'the_script':script, 'the_div':div, 'breadcrumbs':breadcrumbs, 'rower':r, 'active':'nav-workouts', 'workout':row, 'chartform':flexaxesform, 'optionsform':flexoptionsform, 'js_res': js_resources, 'css_res':css_resources, 'teams':get_my_teams(request.user), 'id':id, 'xparam':xparam, 'yparam1':yparam1, 'yparam2':yparam2, 'plottype':plottype, 'axchoicesbasic':axchoicesbasic, 'axchoicespro':axchoicespro, 'extrametrics':extrametrics, 'favoritechartnotes':favoritechartnotes, 'noylist':noylist, 'mayedit':mayedit, 'promember':promember, 'workstrokesonly': not workstrokesonly, 'favoritenr':favoritenr, 'maxfav':maxfav, }) # The interactive plot with wind corrected pace for OTW outings def workout_otwpowerplot_view(request,id=0,message="",successmessage=""): w = get_workout(id) r = getrower(request.user) breadcrumbs = [ { 'url':'/rowers/list-workouts/', 'name':'Workouts' }, { 'url':get_workout_default_page(request,id), 'name': w.name }, { 'url':reverse('workout_otwpowerplot_view',kwargs={'id':id}), 'name': 'Interactive OTW Power Plot' } ] # check if user is owner of this workout # create interactive plot f1 = w.csvfilename u = w.user.user # r = getrower(u) promember=0 mayedit = 0 result = request.user.is_authenticated and ispromember(request.user) if result: promember=1 if request.user == w.user.user: mayedit=1 # create interactive plot res = interactive_otw_advanced_pace_chart(encoder.decode_hex(id),promember=promember) script = res[0] div = res[1] messages.error(request,message) messages.info(request,successmessage) return render(request, 'otwinteractive.html', {'workout':w, 'rower':r, 'active':'nav-workouts', 'breadcrumbs':breadcrumbs, 'teams':get_my_teams(request.user), 'interactiveplot':script, 'the_div':div, 'mayedit':mayedit}) # @login_required() def workout_unsubscribe_view(request,id=0): w = get_workout(id) if w.privacy == 'private' and w.user.user != request.user: 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: 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: 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: a_messages.info(r.user,message.encode('ascii','ignore')) res = myqueue(queuehigh, handle_sendemailnewcomment,r.user.first_name, r.user.last_name, r.user.email, request.user.first_name, request.user.last_name, comment,w.name,w.id, emailbounced = r.emailbounced ) commenters = {oc.user for oc in comments if oc.notification} for u in commenters: a_messages.info(u,message) if u != request.user and u != r.user: ocr = Rower.objects.get(user=u) res = myqueue(queuelow, 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: 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: 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: ps = form.cleaned_data['plannedsession'] except KeyError: ps = None try: boattype = request.POST['boattype'] except KeyError: boattype = Workout.objects.get(id=encoder.decode_hex(id)).boattype try: privacy = request.POST['privacy'] except KeyError: privacy = Workout.objects.get(id=row.id).privacy try: rankingpiece = form.cleaned_data['rankingpiece'] except KeyError: rankingpiece =- Workout.objects.get(id=row.id).rankingpiece try: duplicate = form.cleaned_data['duplicate'] except KeyError: duplicate = Workout.objects.get(id=row.id).duplicate if private: privacy = 'private' else: privacy = 'visible' try: startdatetime = datetime.datetime.combine( date,starttime ) except TypeError: startdatetime = datetime.datetime.combine( date,datetime.datetime.min.time() ) try: startdatetime = pytz.timezone(thetimezone).localize( startdatetime ) except UnknownTimeZoneError: pass try: # aware object can be in any timezone out = startdatetime.astimezone(pytz.utc) except (ValueError, TypeError): startdatetime = timezone.make_aware(startdatetime) try: startdatetime = startdatetime.astimezone(pytz.timezone(thetimezone)) except UnknownTimeZoneError: 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.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: row.dragfactor = newdragfactor dragchanged = True try: row.save() except IntegrityError: pass if ps: add_workouts_plannedsession([row],ps,row.user) # change data in csv file datachanged = (dragchanged or timechanged) if datachanged: r = rdata(row.csvfilename) if dragchanged: try: r.change_drag(newdragfactor) except AttributeError: pass if r == 0: 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" if rankingpiece: dataprep.runcpupdate(row.user,type=row.workouttype) messages.info(request,successmessage) else: form = WorkoutForm(instance=row) g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime") for i in g: 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(f1) hascoordinates = 1 if rowdata != 0: try: latitude = rowdata.df[' latitude'] longitude = rowdata.df[' longitude'] if not latitude.std(): hascoordinates = 0 if not longitude.std(): hascoordinates = 0 except (KeyError,AttributeError): hascoordinates = 0 else: hascoordinates = 0 mapscript = "" mapdiv = "" if hascoordinates: try: mapscript,mapdiv = leaflet_chart( rowdata.df[' latitude'], rowdata.df[' longitude'], row.name) except KeyError: pass 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, }) @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(f1) hascoordinates = 1 if rowdata != 0: try: latitude = rowdata.df[' latitude'] if not latitude.std(): hascoordinates = 0 except (KeyError,AttributeError): hascoordinates = 0 else: hascoordinates = 0 if hascoordinates: mapscript,mapdiv = leaflet_chart2(rowdata.df[' latitude'], rowdata.df[' longitude'], w.name) else: mapscript = "" mapdiv = "" mayedit=0 if not request.user.is_anonymous: r = getrower(request.user) result = request.user.is_authenticated and ispromember(request.user) if result: promember=1 if request.user == w.user.user: mayedit=1 return render(request, 'map_view.html', {'mapscript':mapscript, 'workout':w, 'rower':r, 'breadcrumbs':breadcrumbs, 'active':'nav-workouts', 'mapdiv':mapdiv, 'mayedit':mayedit, }) # Image upload @permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) def workout_uploadimage_view(request,id): is_ajax = False if request.is_ajax(): is_ajax = True 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: 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: 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 = False if request.is_ajax(): is_ajax = True row = get_workout_by_opaqueid(request,id) row.rankingpiece = not row.rankingpiece row.save() if is_ajax: response = JSONResponse({'result':row.rankingpiece}, content_type='application/json') return response else: url = reverse('workouts_view') response = HttpResponseRedirect(url) return response # simple POST API for files on local (e.g. in mailbox) @csrf_exempt def workout_upload_api(request): stravaid = '' if request.method != 'POST': 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': 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) try: stravaid = post_data['stravaid'] except KeyError: stravaid = '' if form.is_valid(): t = form.cleaned_data['title'] boattype = form.cleaned_data['boattype'] workouttype = form.cleaned_data['workouttype'] if rowerform.is_valid(): u = rowerform.cleaned_data['user'] r = getrower(u) elif 'useremail' in post_data: us = User.objects.filter(email=post_data['useremail']) if len(us): u = us[0] r = getrower(u) else: message = {'status':'false','message':'could not find user'} return JSONResponse(status=400,data=message) else: message = {'status':'false','message':'invalid user'} return JSONResponse(status=400,data=message) notes = form.cleaned_data['notes'] if optionsform.is_valid(): make_plot = optionsform.cleaned_data['make_plot'] plottype = optionsform.cleaned_data['plottype'] upload_to_c2 = optionsform.cleaned_data['upload_to_C2'] upload_to_strava = optionsform.cleaned_data['upload_to_Strava'] upload_to_st = optionsform.cleaned_data['upload_to_SportTracks'] upload_to_rk = optionsform.cleaned_data['upload_to_RunKeeper'] upload_to_ua = optionsform.cleaned_data['upload_to_MapMyFitness'] upload_to_tp = optionsform.cleaned_data['upload_to_TrainingPeaks'] makeprivate = optionsform.cleaned_data['makeprivate'] else: message = optionsform.errors 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, notes=notes, uploadoptions=post_data, ) if id == 0: 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: message = {'status': 'true', 'message':message} return JSONResponse(status=200,data=message) w = Workout.objects.get(id=id) if make_plot: res, jobid = uploads.make_plot(r,w,f1,f2,plottype,t) uploads.do_sync(w,post_data,quick=True) else: # 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: message = {'status': 'true', 'id':w.id,'message': 'Error deleting temporary file'} statuscode = 500 if r.getemailnotifications and not r.emailbounced: link = settings.SITE_URL+reverse( r.defaultlandingpage, kwargs = { 'id':encoder.encode_hex(w.id), } ) email_sent = send_confirm(r.user, t, link, '') return JSONResponse(status=statuscode,data=message) # This is the main view for processing uploaded files @login_required() def workout_upload_view(request, uploadoptions={ 'makeprivate':False, 'make_plot':False, 'upload_to_C2':False, 'plottype':'timeplot', 'landingpage':'workout_edit_view', }, docformoptions={ 'workouttype':'rower', }, raceid=0): is_ajax = False if request.is_ajax(): is_ajax = True r = getrower(request.user) if r.rowerplan == 'freecoach': url = reverse('team_workout_upload_view') return HttpResponseRedirect(url) breadcrumbs = [ { 'url':'/rowers/list-workouts/', 'name':'Workouts' }, { 'url': reverse('workout_upload_view'), 'name': 'Upload' } ] if 'uploadoptions' in request.session: uploadoptions = request.session['uploadoptions'] try: defaultlandingpage = uploadoptions['landingpage'] except KeyError: uploadoptions['landingpage'] = r.defaultlandingpage defaultlandingpage = r.defaultlandingpage else: request.session['uploadoptions'] = uploadoptions if 'docformoptions' in request.session: docformoptions = request.session['docformoptions'] else: request.session['docformoptions'] = docformoptions try: makeprivate = uploadoptions['makeprivate'] except KeyError: makeprivate = False try: make_plot = uploadoptions['make_plot'] except KeyError: make_plot = False try: workouttype = docformoptions['workouttype'] except KeyError: workouttype = 'rower' try: boattype = docformoptions['boattype'] except KeyError: boattype = '1x' try: notes = docformoptions['notes'] except KeyError: notes = '' try: workoutsource = uploadoptions['workoutsource'] except KeyError: workoutsource = None try: plottype = uploadoptions['plottype'] except KeyError: plottype = 'timeplot' try: landingpage = uploadoptions['landingpage'] except KeyError: landingpage = r.defaultlandingpage uploadoptions['landingpage'] = landingpage try: upload_to_c2 = uploadoptions['upload_to_C2'] except KeyError: upload_to_c2 = False try: upload_to_strava = uploadoptions['upload_to_Strava'] except KeyError: upload_to_strava = False try: upload_to_st = uploadoptions['upload_to_SportTracks'] except KeyError: upload_to_st = False try: upload_to_rk = uploadoptions['upload_to_RunKeeper'] except KeyError: upload_to_rk = False try: upload_to_ua = uploadoptions['upload_to_MapMyFitness'] except KeyError: upload_to_ua = False try: upload_to_tp = uploadoptions['upload_to_TrainingPeaks'] except KeyError: upload_to_tp = False response = {} if request.method == 'POST': form = DocumentsForm(request.POST,request.FILES) optionsform = UploadOptionsForm(request.POST,request=request,raceid=raceid) if form.is_valid(): # f = request.FILES['file'] f = form.cleaned_data['file'] if f is not None: res = handle_uploaded_file(f) else: 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'] 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_rk = optionsform.cleaned_data['upload_to_RunKeeper'] upload_to_ua = optionsform.cleaned_data['upload_to_MapMyFitness'] upload_to_tp = optionsform.cleaned_data['upload_to_TrainingPeaks'] makeprivate = optionsform.cleaned_data['makeprivate'] landingpage = optionsform.cleaned_data['landingpage'] 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_RunKeeper':upload_to_rk, 'upload_to_MapMyFitness':upload_to_ua, 'upload_to_TrainingPeaks':upload_to_tp, 'landingpage':landingpage, 'boattype': boattype, '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, makeprivate=makeprivate, title = t, notes=notes, ) else: workoutsbox = Mailbox.objects.filter(name='workouts')[0] uploadoptions['fromuploadform'] = True bodyyaml = yaml.safe_dump( uploadoptions, default_flow_style=False ) msg = Message(mailbox=workoutsbox, from_header=r.user.email, subject = t,body=bodyyaml) msg.save() f3 = 'media/mailbox_attachments/'+f2[6:] copyfile(f2,f3) f3 = f3[6:] a = MessageAttachment(message=msg,document=f3) a.save() os.remove(f2) messages.info( request, "The file was too large to process in real time. It will be processed in a background process. You will receive an email when it is ready") url = reverse('workout_upload_view') if is_ajax: return JSONResponse({'result':1,'url':url}) else: response = HttpResponseRedirect(url) return response if not id: messages.error(request,message) url = reverse('workout_upload_view') if is_ajax: return JSONResponse({'result':0,'url':url}) else: response = HttpResponseRedirect(url) return response elif id == -1: 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: 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: response = {'result': 1,'url':url} else: response = HttpResponseRedirect(url) r = getrower(request.user) if (make_plot): 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')] # upload to C2 if (upload_to_c2): 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): 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): 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_rk): try: message,id = runkeeperstuff.workout_runkeeper_upload( request.user,w ) except NoTokenError: message = "Please connect to Runkeeper first" id = 0 if id>1: messages.info(request,message) else: messages.error(request,message) if (upload_to_ua): try: message,id = underarmourstuff.workout_ua_upload( request.user,w ) except NoTokenError: message = "Please connect to MapMyFitness first" id = 0 if id>1: messages.info(request,message) else: messages.error(request,message) if (upload_to_tp): 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: 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) if int(registrationid)>0: races = VirtualRace.objects.filter( registration_closure__gt=timezone.now() ) 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]: # indoor race registrations = registrations.filter(id=registrationid) if registrations: race = registrations[0].race result,comments,errors,jobid = add_workout_indoorrace( [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]: # race registrations = registrations2.filter(id=registrationid) if registrations: race = registrations[0].race result,comments,errors,jobid = add_workout_race( [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: 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: url = reverse(landingpage) elif landingpage != 'workout_upload_view': url = reverse(landingpage, kwargs = { 'id':encoder.encode_hex(w.id), }) else: url = reverse(landingpage) if is_ajax: response = {'result':1,'url':url} else: response = HttpResponseRedirect(url) else: if not is_ajax: response = render(request, 'document_form.html', {'form':form, 'teams':get_my_teams(request.user), 'optionsform': optionsform, }) if is_ajax: return JSONResponse(response) else: return response else: if not is_ajax: if r.c2_auto_export and ispromember(r.user): uploadoptions['upload_to_C2'] = True if r.strava_auto_export and ispromember(r.user): uploadoptions['upload_to_Strava'] = True if r.sporttracks_auto_export and ispromember(r.user): uploadoptions['upload_to_SportTracks'] = True if r.runkeeper_auto_export and ispromember(r.user): uploadoptions['upload_to_RunKeeper'] = True if r.trainingpeaks_auto_export and ispromember(r.user): uploadoptions['upload_to_TrainingPeaks'] = True if r.mapmyfitness_auto_export and ispromember(r.user): uploadoptions['upload_to_MapMyFitness'] = 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: return {'result':0} # This is the main view for processing uploaded files @user_passes_test(iscoachmember,login_url="/rowers/paidplans",redirect_field_name=None, message="This functionality requires a Coach plan or higher") def team_workout_upload_view(request,message="", successmessage="", uploadoptions={ 'make_plot':False, 'plottype':'timeplot', }): if 'uploadoptions' in request.session: uploadoptions = request.session['uploadoptions'] else: request.session['uploadoptions'] = uploadoptions breadcrumbs = [ { 'url':'/rowers/list-workouts/', 'name':'Workouts' }, { 'url': reverse('team_workout_upload_view'), 'name': 'Team Upload' } ] myteams = Team.objects.filter(manager=request.user) make_plot = uploadoptions['make_plot'] plottype = uploadoptions['plottype'] r = getrower(request.user) if request.method == 'POST': form = DocumentsForm(request.POST,request.FILES) optionsform = TeamUploadOptionsForm(request.POST) rowerform = TeamInviteForm(request.POST) rowerform.fields.pop('email') rowers = Rower.objects.filter( coachinggroups__in=[r.mycoachgroup] ).exclude( rowerplan='freecoach' ).distinct() if r.rowerplan == 'freecoach': 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: 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): message = 'Please select a rower' messages.error(request,message) messages.info(request,successmessage) response = render(request, 'team_document_form.html', {'form':form, 'teams':get_my_teams(request.user), 'optionsform': optionsform, 'rowerform': rowerform, }) return response workouttype = form.cleaned_data['workouttype'] notes = form.cleaned_data['notes'] if optionsform.is_valid(): make_plot = optionsform.cleaned_data['make_plot'] plottype = optionsform.cleaned_data['plottype'] uploadoptions = { 'makeprivate':False, 'make_plot':make_plot, 'plottype':plottype, 'upload_to_C2':False, } request.session['uploadoptions'] = uploadoptions f1 = res[0] # file name f2 = res[1] # file name incl media directory if not offline: id,message,f2 = dataprep.new_workout_from_file( r,f2, workouttype=workouttype, boattype=boattype, makeprivate=False, title = t, notes='' ) else: job = myqueue( queuehigh, handle_zip_file, r.user.email, t, f2, emailbounced = r.emailbounced ) messages.info( request, "The file was too large to process in real time. It will be processed in a background process. The user will receive an email when it is ready" ) url = reverse('team_workout_upload_view') response = HttpResponseRedirect(url) return response if not id: messages.error(request,message) url = reverse('team_workout_upload_view') response = HttpResponseRedirect(url) return response elif id == -1: 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): 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': 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: 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') try: g = paginator.page(page) except PageNotAnInteger: g = paginator.page(1) except EmptyPage: 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: 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: 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: 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: raise Http404("This graph doesn't exist") except Workout.DoesNotExist: raise Http404("This workout doesn't exist") # Restore original stroke data and summary @permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) def workout_summary_restore_view(request,id,message="",successmessage=""): row = get_workout_by_opaqueid(request,id) s = "" # still here - this is a workout we may edit f1 = row.csvfilename u = row.user.user r = getrower(u) powerperc = 100*np.array([r.pw_ut2, r.pw_ut1, r.pw_at, r.pw_tr,r.pw_an])/r.ftp ftp = float(r.ftp) if row.workouttype in mytypes.otwtypes: ftp = ftp*(100.-r.otwslack)/100. rr = rrower(hrmax=r.max,hrut2=r.ut2, hrut1=r.ut1,hrat=r.at, hrtr=r.tr,hran=r.an,ftp=ftp, powerperc=powerperc,powerzones=r.powerzones) rowdata = rdata(f1,rower=rr) if rowdata == 0: raise Http404("Error: CSV Data File Not Found") rowdata.restoreintervaldata() rowdata.write_csv(f1,gzip=True) dataprep.update_strokedata(encoder.decode_hex(id),rowdata.df) intervalstats = rowdata.allstats() row.summary = intervalstats row.save() # create interactive plot try: res = interactive_chart(encoder.decode_hex(id),promember=1) script = res[0] div = res[1] except ValueError: pass messages.info(request,'Original Interval Data Restored') url = reverse('workout_summary_edit_view', kwargs={ 'id':encoder.encode_hex(row.id), } ) return HttpResponseRedirect(url) # Split a workout @permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) @user_passes_test(ispromember,login_url="/rowers/paidplans", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality", redirect_field_name=None) def workout_split_view(request,id=0): row = get_workout_by_opaqueid(request,id) r = row.user breadcrumbs = [ { 'url':'/rowers/list-workouts/', 'name':'Workouts' }, { 'url':get_workout_default_page(request,id), 'name': row.name }, { 'url':reverse('workout_split_view', kwargs={'id':id}), 'name': 'Chart' } ] if request.method == 'POST': form = WorkoutSplitForm(request.POST) if form.is_valid(): splittime = form.cleaned_data['splittime'] splitsecond = splittime.hour*3600 splitsecond += splittime.minute*60 splitsecond += splittime.second splitsecond += splittime.microsecond/1.e6 splitmode = form.cleaned_data['splitmode'] try: ids,mesgs = dataprep.split_workout( r,row,splitsecond,splitmode ) for message in mesgs: messages.info(request,message) except IndexError: messages.error(request,"Something went wrong in Split") if request.user == r: 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: teamid = teamids[0] url = reverse('workouts_view', kwargs={ 'teamid':int(teamid), } ) else: url = reverse('workouts_view') rowname = row.name try: if isinstance(rowname,unicode): rowname = rowname.encode('utf8') elif isinstance(rowname, str): 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: 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: pass r = getrower(request.user) try: w1 = Workout.objects.get(id=id1) w2 = Workout.objects.get(id=id2) r = w1.user if (is_workout_user(request.user,w1)==False) or \ (is_workout_user(request.user,w2)==False): raise PermissionDenied("You are not allowed to use these workouts") except Workout.DoesNotExist: 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': timeoffset = -timeoffset # Create DataFrame df,forceunit = dataprep.datafusion(id1,id2,columns,timeoffset) idnew,message = dataprep.new_workout_from_df(r,df, title='Fused data', parent=w1, forceunit=forceunit) if message != None: messages.error(request,message) else: successmessage = 'Data fused' messages.info(request,message) url = reverse('workout_edit_view', kwargs={ 'id':encoder.encode_hex(idnew), }) return HttpResponseRedirect(url) else: form = FusionMetricChoiceForm(instance=w2) breadcrumbs = [ { 'url':'/rowers/list-workouts/', 'name':'Workouts' }, { 'url':get_workout_default_page(request,encoder.encode_hex(w1.id)), 'name': encoder.encode_hex(w1.id) }, { 'url':reverse('workout_fusion_list', kwargs={'id':encoder.encode_hex(id1)}), 'name': 'Sensor Fusion' }, { 'url':reverse('workout_fusion_view', kwargs={ 'id1':encoder.encode_hex(id1), 'id2':encoder.encode_hex(id2) }), 'name': encoder.encode_hex(w2.id) } ] return render(request, 'fusion.html', {'form':form, 'teams':get_my_teams(request.user), 'workout':w1, 'rower':r, 'breadcrumbs':breadcrumbs, 'active':'nav-workouts', 'workout1':w1, 'workout2':w2, }) # Edit the splits/summary @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) rowdata = rdata(f1,rower=rr) if rowdata == 0: return HttpResponse("Error: CSV Data File Not Found") intervalstats = rowdata.allstats() try: itime,idist,itype = rowdata.intervalstats_values() except TypeError: 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) data = { 'power': int(normp), 'pace': avpace, 'selector': 'power', 'work': int(normw), 'spm': int(normspm), 'activeminutesmin': 0, 'activeminutesmax': activeminutesmax, } powerorpace = 'power' if normp == 0: data['selector'] = 'pace' powerorpace = 'pace' powerupdateform = PowerIntervalUpdateForm(initial=data) # 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: 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: try: powerorpace = request.POST['powerorpace'] except: powerorpace = 'pace' try: value_pace = request.POST['value_pace'] except: value_pace = avpace try: value_power = request.POST['value_power'] except: value_power = int(normp) try: value_work = request.POST['value_work'] except: value_work = int(normw) try: value_spm = request.POST['value_spm'] except: value_spm = int(normspm) try: activeminutesmin = request.POST['activeminutesmin'] except: activeminutesmin = 0 try: activeminutesmax = request.POST['activeminutesmax'] except: pass try: activesecondsmin = 60.*float(activeminutesmin) activesecondsmax = 60.*float(activeminutesmax) except ValueError: activesecondsmin = 0 activeminutesmin = 0 activeminutesmax = maxminutes activesecondsmax = rowdata.duration if abs(rowdata.duration-activesecondsmax) < 60.: activesecondsmax = rowdata.duration if powerorpace == 'power': try: power = int(value_power) except ValueError: int(normp) elif powerorpace == 'pace': 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': try: work = int(value_work) except ValueError: work = int(normw) elif powerorpace == 'spm': try: spm = int(value_spm) except ValueError: spm = int(normspm) if powerorpace == 'power' and power is not None: try: rowdata.updateinterval_metric( ' Power (watts)',power,mode='larger', debug=False,smoothwindow=15., activewindow=[activesecondsmin,activesecondsmax], ) except: messages.error(request,'Error updating power') elif powerorpace == 'pace': try: velo = 500./pace_secs 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': try: 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': try: rowdata.updateinterval_metric( ' Cadence (stokes/min)',spm,mode='larger', debug=False,smoothwindow=2., activewindow=[activesecondsmin,activesecondsmax],) except: messages.error(request,'Error updating SPM') 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() 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): messages.error(request,'Parsing error in column '+str(err.col)) intervalstats = rowdata.allstats() itime,idist,itype = rowdata.intervalstats_values() nrintervals = len(idist) row.summary = intervalstats #intervalstats = rowdata.allstats() if s: try: row.notes = u'{n} \n {s}'.format( n = row.notes, s = s ) except TypeError: 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.: activesecondsmax = rowdata.duration try: pace_secs = pace.seconds+pace.microseconds/1.0e6 except AttributeError: pace_secs = 120. if powerorpace == 'power' and power is not None: rowdata.updateinterval_metric(' Power (watts)',power,mode='larger', debug=False,smoothwindow=15, activewindow=[activesecondsmin,activesecondsmax], ) elif powerorpace == 'pace': try: velo = 500./pace_secs 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': try: 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': try: rowdata.updateinterval_metric(' Cadence (stokes/min)',spm,mode='larger', debug=False,smoothwindow=2., activewindow=[activesecondsmin,activesecondsmax], ) except: messages.error(request,'Error updating SPM') 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() form = SummaryStringForm() # we are saving the results obtained from the detailed form elif request.method == 'POST' and "savedetailform" in request.POST: savebutton = 'savedetailform' form = SummaryStringForm() nrintervals = int(request.POST['nrintervals']) detailform = IntervalUpdateForm(request.POST,aantal=nrintervals) itime = [] idist = [] itype = [] ivalues = [] iunits = [] itypes = [] iresults = [] for i in range(nrintervals): try: t = datetime.datetime.strptime(request.POST['intervalt_%s' % i],"%H:%M:%S.%f") except ValueError: t = datetime.datetime.strptime(request.POST['intervalt_%s' % i],"%H:%M:%S") timesecs = 3600*t.hour+60*t.minute+t.second+t.microsecond/1.e6 itime += [timesecs] idist += [int(request.POST['intervald_%s' % i])] itype += [int(request.POST['type_%s' % i])] if itype[i] == 3: # rest itypes += ['rest'] ivalues += [timesecs] iresults += [idist[i]] iunits += ['seconds'] if itype[i] == 5 or itype[i] == 2: # distance based work itypes += ['work'] ivalues += [idist[i]] iresults += [timesecs] iunits += ['meters'] if itype[i] == 4 or itype[i] == 1: # time based work itypes += ['work'] ivalues += [timesecs] iresults += [idist[i]] iunits += ['seconds'] rowdata.updateintervaldata(ivalues,iunits,itypes,iresults=iresults) intervalstats = rowdata.allstats() row.summary = intervalstats try: row.notes += "\n"+s except TypeError: 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") form = SummaryStringForm() powerupdateform = PowerIntervalUpdateForm(initial={ 'power': int(normp), 'pace': avpace, 'selector': 'power', 'work': int(normw), 'spm': int(normspm), 'activeminutesmin': 0, 'activeminutesmax': activeminutesmax, }) # we are processing the details form elif request.method == 'POST' and "nrintervals" in request.POST: savebutton = 'savedetailform' nrintervals = int(request.POST['nrintervals']) detailform = IntervalUpdateForm(request.POST,aantal=nrintervals) if detailform.is_valid(): cd = detailform.cleaned_data itime = [] idist = [] itype = [] ivalues = [] iunits = [] itypes = [] iresults = [] for i in range(nrintervals): t = cd['intervalt_%s' % i] timesecs = t.total_seconds() itime += [timesecs] idist += [cd['intervald_%s' % i]] itype += [cd['type_%s' % i]] if itype[i] == '3': # rest itypes += ['rest'] ivalues += [timesecs] iresults += [idist[i]] iunits += ['seconds'] if itype[i] == '5' or itype[i] == '2': # distance based work itypes += ['work'] ivalues += [idist[i]] iresults += [timesecs] iunits += ['meters'] if itype[i] == '4' or itype[i] == '1': # time based work itypes += ['work'] ivalues += [timesecs] iresults += [idist[i]] iunits += ['seconds'] rowdata.updateintervaldata(ivalues,iunits, itypes,iresults=iresults) intervalstats = rowdata.allstats() form = SummaryStringForm() powerupdateform = PowerIntervalUpdateForm() initial = {} for i in range(nrintervals): try: initial['intervald_%s' % i] = idist[i] initial['intervalt_%s' % i] = get_time(itime[i]) initial['type_%s' % i] = itype[i] except IndexError: pass detailform = IntervalUpdateForm(aantal=nrintervals,initial=initial) # 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: script = '' div = '' # render page return render(request, 'summary_edit.html', {'form':form, 'activeminutesmax':activeminutesmax, 'activeminutesmin':activeminutesmin, 'maxminutes': maxminutes, 'detailform':detailform, '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, }) 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: 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): 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: 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): raise PermissionDenied('You are not allowed to delete this chart') return obj def workout_code_delete_view(request,id=0): pk = encoder.decode_hex(id) try: w = Workout.objects.get(pk=pk) url = reverse('workout_delete',kwargs={'pk':pk}) except Workout.DoesNotExist: url = reverse('workout_delete',kwargs={'pk':id}) return HttpResponseRedirect(url) class WorkoutDelete(PermissionRequiredMixin,DeleteView): login_required = True model = Workout template_name = 'workout_delete_confirm.html' permission_required = 'workout.change_workout' # extra parameters def get_context_data(self, **kwargs): context = super(WorkoutDelete, self).get_context_data(**kwargs) breadcrumbs = [ { 'url':'/rowers/list-workouts/', 'name':'Workouts' }, { 'url':get_workout_default_page(self.request,encoder.encode_hex(self.object.id)), 'name': encoder.encode_hex(self.object.id) }, { 'url':reverse('workout_delete',kwargs={'pk':str(self.object.pk)}), 'name': 'Delete' } ] mayedit=0 promember=0 if not self.request.user.is_anonymous: r = getrower(self.request.user) result = self.request.user.is_authenticated and ispromember(self.request.user) if result: promember=1 if self.request.user == self.object.user.user: mayedit=1 context['active'] = 'nav-workouts' context['rower'] = getrower(self.request.user) context['breadcrumbs'] = breadcrumbs context['workout'] = self.object context['mayedit'] = mayedit context['promember'] = promember return context def get_success_url(self): return reverse('workouts_view') def get_object(self, *args, **kwargs): workout_pk = self.kwargs['pk'] try: obj = Workout.objects.get(pk=workout_pk) except (ValueError,Workout.DoesNotExist): workout_pk = encoder.decode_hex(workout_pk) try: obj = Workout.objects.get(pk=workout_pk) except Workout.DoesNotExist: raise Http404("One of the workouts doesn't exist") # obj = super(WorkoutDelete, self).get_object(*args, **kwargs) return obj