diff --git a/rowers/dataprep.py b/rowers/dataprep.py index cb226c5f..eeaa2b64 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -95,6 +95,23 @@ from scipy.signal import savgol_filter import datetime +def filter_df(datadf,fieldname,value,largerthan=True): + + try: + x = datadf[fieldname] + except KeyError: + return datadf + + if largerthan: + mask = datadf[fieldname] < value + else: + mask = datadf[fieldname] >= value + + datadf.loc[mask,fieldname] = np.nan + + + return datadf + def clean_df_stats(datadf,workstrokesonly=True,ignorehr=True, ignoreadvanced=False): # clean data remove zeros and negative values diff --git a/rowers/forms.py b/rowers/forms.py index 69b6870f..061ec014 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -300,6 +300,18 @@ parchoices = list(sorted(formaxlabels.items(), key = lambda x:x[1])) class BoxPlotChoiceForm(forms.Form): yparam = forms.ChoiceField(choices=parchoices,initial='spm', label='Metric') + spmmin = forms.FloatField(initial=15, + required=False,label = 'Min SPM') + spmmax = forms.FloatField(initial=55, + required=False,label = 'Max SPM') + workmin = forms.FloatField(initial=0, + required=False,label = 'Min Work per Stroke') + workmax = forms.FloatField(initial=1500, + required=False,label = 'Max Work per Stroke') + + includereststrokes = forms.BooleanField(initial=False, + required=False, + label='Include Rest Strokes') class ChartParamChoiceForm(forms.Form): plotchoices = ( diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 920b4677..2993b728 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -88,12 +88,19 @@ from rowers.dataprep import nicepaceformat,niceformat from rowers.dataprep import timedeltaconv def interactive_boxchart(datadf,fieldname,extratitle=''): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,resize' + + if datadf.empty: + return '','It looks like there are no data matching your filter' + + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,resize,hover' + plot = BoxPlot(datadf, values=fieldname, label='date', legend=False, title=axlabels[fieldname]+' '+extratitle, outliers=False, tools=TOOLS, + toolbar_location="above", + toolbar_sticky=False, x_mapper_type='datetime') yrange1 = Range1d(start=yaxminima[fieldname],end=yaxmaxima[fieldname]) @@ -133,12 +140,21 @@ def interactive_boxchart(datadf,fieldname,extratitle=''): plot.xaxis.major_label_orientation = pi/4 - + + hover = plot.select(dict(type=HoverTool)) + + hover.tooltips = OrderedDict([ + ('Value','@y'), + ('Date','@x'), + ]) + + hover.mode = 'mouse' + script, div = components(plot) - return script,div + def interactive_forcecurve(theworkouts,workstrokesonly=False): TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' diff --git a/rowers/management/commands/getexpired.py b/rowers/management/commands/getexpired.py new file mode 100644 index 00000000..76eefb58 --- /dev/null +++ b/rowers/management/commands/getexpired.py @@ -0,0 +1,47 @@ +#!/srv/venv/bin/python +import sys +import os +# If you find a solution that does not need the two paths, please comment! +sys.path.append('$path_to_root_of_project$') +sys.path.append('$path_to_root_of_project$/$project_name$') + +os.environ['DJANGO_SETTINGS_MODULE'] = '$project_name$.settings' + +from django.core.management.base import BaseCommand, CommandError +from django.conf import settings +from django.core.mail import send_mail, BadHeaderError,EmailMessage + +import datetime +from rowers.models import Rower +from rowsandall_app.settings import BASE_DIR + +def getexpired(): + rs = Rower.objects.all() + lijst = [] + for r in rs: + if r.planexpires < datetime.datetime.now().date(): + lijst.append(r) + return lijst + + +class Command(BaseCommand): + def handle(self, *args, **options): + expiredrowers = getexpired() + + fullemail = 'roosendaalsander@gmail.com' + subject = "Expired rowers report" + message = "Dear Sander,\n\n" + message += "Best Regards, the Rowsandall Team" + message += "Expired rowers report\n\n" + + for r in expiredrowers: + message += r.user.first_name+" "+r.user.last_name+" "+r.user.username + message += "\n" + + email = EmailMessage(subject, message, + 'Rowsandall ', + [fullemail]) + + + + res = email.send() diff --git a/rowers/templates/boxplot.html b/rowers/templates/boxplot.html index 45b14144..bbb5ebd7 100644 --- a/rowers/templates/boxplot.html +++ b/rowers/templates/boxplot.html @@ -34,28 +34,35 @@ -
-

Box Chart

-
-
-
- {% csrf_token %} - - {{ chartform.as_table }} -
-
-

- -

-
-
-
- - - -
- {{ the_div|safe }} +

Box Chart

+
+
+ {{ the_div|safe }} +
+
+
+
+
+ {% csrf_token %} + + {{ chartform.as_table }} +
+
+

+ +

+
+
+
+
+

+ You can use the form above to change the metric or filter the data. + Set Min SPM and Max SPM to select only strokes in a certain range of + stroke rates. + Set Work per Stroke to a minimum value to remove "paddle" strokes or turns. +

+
diff --git a/rowers/views.py b/rowers/views.py index 82cb4450..fb8ce778 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -3043,6 +3043,15 @@ def boxplot_view(request,userid=0, cd = form.cleaned_data workouts = cd['workouts'] plotfield = chartform.cleaned_data['yparam'] + includereststrokes = chartform.cleaned_data['includereststrokes'] + request.session['includereststrokes'] = includereststrokes + workstrokesonly = not includereststrokes + + spmmin = chartform.cleaned_data['spmmin'] + spmmax = chartform.cleaned_data['spmmax'] + workmin = chartform.cleaned_data['workmin'] + workmax = chartform.cleaned_data['workmax'] + ids = [int(w.id) for w in workouts] request.session['ids'] = ids @@ -3058,12 +3067,25 @@ def boxplot_view(request,userid=0, fieldlist,fielddict = dataprep.getstatsfields() - fieldlist = [plotfield,'workoutid'] + fieldlist = [plotfield,'workoutid','spm','driveenergy', + 'workoutstate'] # prepare data frame datadf = dataprep.read_cols_df_sql(ids,fieldlist) datadf = dataprep.clean_df_stats(datadf,workstrokesonly=workstrokesonly) + + datadf = dataprep.filter_df(datadf,'spm',spmmin, + largerthan=True) + datadf = dataprep.filter_df(datadf,'spm',spmmax, + largerthan=False) + datadf = dataprep.filter_df(datadf,'driveenergy',workmin, + largerthan=True) + datadf = dataprep.filter_df(datadf,'driveneergy',workmax, + largerthan=False) + + datadf.dropna(axis=0,how='any',inplace=True) + datadf['workoutid'].replace(datemapping,inplace=True) datadf.rename(columns={"workoutid":"date"},inplace=True) datadf = datadf.sort_values(['date']) @@ -3093,6 +3115,13 @@ def boxplot_view(request,userid=0, chartform = BoxPlotChoiceForm(request.POST) if chartform.is_valid(): plotfield = chartform.cleaned_data['yparam'] + includereststrokes = chartform.cleaned_data['includereststrokes'] + spmmin = chartform.cleaned_data['spmmin'] + spmmax = chartform.cleaned_data['spmmax'] + workmin = chartform.cleaned_data['workmin'] + workmax = chartform.cleaned_data['workmax'] + request.session['includereststrokes'] = includereststrokes + workstrokesonly = not includereststrokes ids = request.session['ids'] request.session['ids'] = ids workouts = [Workout.objects.get(id=id) for id in ids] @@ -3106,12 +3135,31 @@ def boxplot_view(request,userid=0, } fieldlist,fielddict = dataprep.getstatsfields() - fieldlist = [plotfield,'workoutid'] + fieldlist = [plotfield,'workoutid','spm','driveenergy', + 'workoutstate'] # prepare data frame datadf = dataprep.read_cols_df_sql(ids,fieldlist) + + # dummy values for drive energy + mask = datadf['driveenergy'] == 0 + datadf.loc[mask,'driveenergy'] = 450 - datadf = dataprep.clean_df_stats(datadf,workstrokesonly=workstrokesonly) + datadf = dataprep.clean_df_stats(datadf, + workstrokesonly=workstrokesonly) + + + datadf = dataprep.filter_df(datadf,'spm',spmmin, + largerthan=True) + datadf = dataprep.filter_df(datadf,'spm',spmmax, + largerthan=False) + datadf = dataprep.filter_df(datadf,'driveenergy',workmin, + largerthan=True) + datadf = dataprep.filter_df(datadf,'driveneergy',workmax, + largerthan=False) + + datadf.dropna(axis=0,how='any',inplace=True) + datadf['workoutid'].replace(datemapping,inplace=True) datadf.rename(columns={"workoutid":"date"},inplace=True) datadf = datadf.sort_values(['date']) @@ -3136,7 +3184,7 @@ def boxplot_view(request,userid=0, else: return HttpResponse("invalid form") else: - url = reverse(workouts_view) + url = reverse(user_boxplot_select) return HttpResponseRedirect(url) # List Workouts @@ -5083,7 +5131,10 @@ def workout_edit_view(request,id=0,message="",successmessage=""): row.distance = distance row.boattype = boattype row.privacy = privacy - row.save() + try: + row.save() + except IntegrityError: + pass # change data in csv file r = rdata(row.csvfilename) @@ -5105,9 +5156,9 @@ def workout_edit_view(request,id=0,message="",successmessage=""): url = reverse(workouts_view) response = HttpResponseRedirect(url) - - else: # form not POSTed - form = WorkoutForm(instance=row) + + #else: # form not POSTed + form = WorkoutForm(instance=row) try: