From e97d87f2d3113376e8a77a3f4bd243f29e235c72 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 22 Oct 2018 15:29:36 +0200 Subject: [PATCH] close #359 --- rowers/forms.py | 17 ++++- rowers/templates/menu_workout.html | 4 ++ rowers/templates/workout_data.html | 29 +++++++++ rowers/urls.py | 1 + rowers/views.py | 101 +++++++++++++++++++++++++++-- static/css/rowsandall2.css | 35 ++++++++++ static/css/styles2.css | 12 ++++ 7 files changed, 193 insertions(+), 6 deletions(-) create mode 100644 rowers/templates/workout_data.html diff --git a/rowers/forms.py b/rowers/forms.py index 31739429..66051470 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -30,7 +30,7 @@ class EmailForm(forms.Form): botcheck = forms.CharField(max_length=5) message = forms.CharField() - + # Upload the CrewNerd Summary CSV class CNsummaryForm(forms.Form): @@ -105,6 +105,7 @@ from utils import ( defaultleft,defaultmiddle ) + # Form to change Workflow page layout class WorkFlowLeftPanelForm(forms.Form): @@ -533,6 +534,20 @@ ww = list(workouttypes) ww.append(tuple(('all','All'))) workouttypes = tuple(ww) +class DataFrameColumnsForm(forms.Form): + cols = ['ftime','cumdist','fpace','spm', + 'hr','power','driveenergy','drivelength','averageforce', + 'peakforce','distance','drivespeed','workoutstate', + 'catch','finish','peakforceangle','wash','slip','rhythm', + 'effectiveangle','totalangle','distanceperstroke','velo'] + + colchoices = [ + (c, c) for c in cols + ] + + cols = forms.MultipleChoiceField(choices=colchoices, + label='Table Columns') + # form to select modality and boat type for trend flex class TrendFlexModalForm(forms.Form): modality = forms.ChoiceField(choices=workouttypes, diff --git a/rowers/templates/menu_workout.html b/rowers/templates/menu_workout.html index 12179e41..ec62b95e 100644 --- a/rowers/templates/menu_workout.html +++ b/rowers/templates/menu_workout.html @@ -259,6 +259,10 @@  Split Workout +
  • + +  Explore Raw Data +
  • diff --git a/rowers/templates/workout_data.html b/rowers/templates/workout_data.html new file mode 100644 index 00000000..a259bb35 --- /dev/null +++ b/rowers/templates/workout_data.html @@ -0,0 +1,29 @@ +{% extends "newbase.html" %} +{% load staticfiles %} +{% load rowerfilters %} + +{% block title %}Workout Data{% endblock %} + +{% block main %} +

    Workout Data for {{ workout.name }}

    + +
      +
    • +

      +

      + {{ form.as_table }} + {% csrf_token %} + +
      +

      +
    • +
    • + {{ htmltable|safe }} +
    • +
    + +{% endblock %} + +{% block sidebar %} +{% include 'menu_workout.html' %} +{% endblock %} diff --git a/rowers/urls.py b/rowers/urls.py index 04b75c9a..59b9f769 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -246,6 +246,7 @@ urlpatterns = [ url(r'^workout/(?P\d+)/instroke/(?P\w+.*)$',views.instroke_chart), url(r'^workout/(?P\d+)/instroke$',views.instroke_view), url(r'^workout/(?P\d+)/stats$',views.workout_stats_view), + url(r'^workout/(?P\d+)/data$',views.workout_data_view), url(r'^workout/(?P\d+)/otwsetpower$',views.workout_otwsetpower_view), url(r'^workout/(?P\d+)/interactiveotwplot$',views.workout_otwpowerplot_view), url(r'^workout/(?P\d+)/wind$',views.workout_wind_view), diff --git a/rowers/views.py b/rowers/views.py index 8fff77c9..39a35c80 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -44,7 +44,7 @@ from rowers.forms import ( PlannedSessionTeamForm,PlannedSessionTeamMemberForm, VirtualRaceSelectForm,WorkoutRaceSelectForm,CourseSelectForm, RaceResultFilterForm,PowerIntervalUpdateForm,FlexAxesForm, - FlexOptionsForm + FlexOptionsForm,DataFrameColumnsForm, ) from django.core.urlresolvers import reverse, reverse_lazy @@ -8093,6 +8093,100 @@ def cumstats(request,theuser=0, return response + +# data explorer +@login_required() +def workout_data_view(request, id=0): + + r = getrower(request.user) + w = get_workout(id) + + if not checkworkoutuser(request.user,w): + raise PermissionDenied('Access Denied') + + breadcrumbs = [ + { + 'url':'/rowers/list-workouts', + 'name':'Workouts' + }, + { + 'url':get_workout_default_page(request,id), + 'name': str(w.id) + }, + { + 'url':reverse(workout_data_view,kwargs={'id':id}), + 'name': 'Data Explorer' + } + + ] + + + datadf,row = dataprep.getrowdata_db(id=id) + + + datadf.sort_values(['ftime'],inplace=True) + + datadf.drop(labels=[ + 'id','time','hr_an','hr_at','hr_bottom','hr_max', + 'hr_tr','hr_ut1','hr_ut2','x_right', + ],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'] + + + tcols = ['ftime','cumdist','fpace','spm','hr','power'] + + datadf = datadf[cols] + 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. + + + if request.method == 'POST': + form = DataFrameColumnsForm(request.POST) + if form.is_valid(): + tcols = form.cleaned_data['cols'] + + else: + form = DataFrameColumnsForm(initial = {'cols':tcols}) + + datadf = datadf[tcols] + + 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 @login_required() def workout_stats_view(request,id=0,message="",successmessage=""): @@ -8130,10 +8224,7 @@ def workout_stats_view(request,id=0,message="",successmessage=""): # prepare data frame datadf,row = dataprep.getrowdata_db(id=id) if (checkworkoutuser(request.user,row)==False): - message = "You are not allowed to see the stats of this workout" - messages.error(request,message) - url = reverse(workouts_view) - return HttpResponseRedirect(url) + raise PermissionDenied('Access Denied') datadf = dataprep.clean_df_stats(datadf,workstrokesonly=workstrokesonly) diff --git a/static/css/rowsandall2.css b/static/css/rowsandall2.css index 95b02e66..00567460 100644 --- a/static/css/rowsandall2.css +++ b/static/css/rowsandall2.css @@ -972,3 +972,38 @@ a.wh:hover { width: auto !important; max-width: 1px; } + +.mystyle { + font-size: 11pt; + font-family: Arial; + border-collapse: collapse; + border: 1px solid silver; + +} + +.pandastable tr { + display: block; +} + +.pandastable th, table td { + width: 100px; +} + +.pandastable tbody { + display: block; + height: 300px; + overflow: auto; +} + +.pandastable td, th { + padding: 5px; +} + +.pandastable tr:nth-child(even) { + background: #E0E0E0; +} + +.pandastable tr:hover { + background: silver; + cursor: pointer; +} diff --git a/static/css/styles2.css b/static/css/styles2.css index 7a3e30fa..13cbbc5f 100644 --- a/static/css/styles2.css +++ b/static/css/styles2.css @@ -501,6 +501,18 @@ overflow: scroll; } +.maxheightwidth { + position: relative; + max-height: 300px; + overflow: scroll; +} + +.maxheightwidthcontent { + position: relative; + max-width: 700px; + overflow: scroll; +} + .main-content li.grid_4 { grid-column-end: span 1; }