From 22c69653de35ba67b7f2c74ec9fdabc62f1ff54b Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 4 Dec 2016 17:26:19 +0100 Subject: [PATCH 01/13] Callouts hotfix on flexchart (pace only) --- rowers/interactiveplots.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index d7a53f19..8ccb03f1 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1166,6 +1166,7 @@ def interactive_flex_chart2(id=0,promember=0, var time1 = data['time'] var pace1 = data['pace'] var hr1 = data['hr'] + var fpace1 = data['fpace'] var distance1 = data['distance'] var power1 = data['power'] var xname = data['xname'][0] @@ -1187,6 +1188,7 @@ def interactive_flex_chart2(id=0,promember=0, data2['time'] = [] data2['pace'] = [] data2['hr'] = [] + data2['fpace'] = [] data2['distance'] = [] data2['power'] = [] data2['x1mean'] = [] @@ -1204,6 +1206,7 @@ def interactive_flex_chart2(id=0,promember=0, data2['y2'].push(y2[i]) data2['spm'].push(spm1[i]) data2['time'].push(time1[i]) + data2['fpace'].push(fpace1[i]) data2['pace'].push(pace1[i]) data2['hr'].push(hr1[i]) data2['distance'].push(distance1[i]) From 9f39ae596fc91fcbf1eb9ab4c430e5cb1e1ac713 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 5 Dec 2016 12:43:45 +0100 Subject: [PATCH 02/13] Bug fix for small rows (low nr of strokes) --- rowers/dataprep.py | 4 ++-- rowers/views.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 4faf1137..38fafec0 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -276,7 +276,7 @@ def dataprep(rowdatadf,id=0,bands=True,barchart=True,otwpower=True, if windowsize <= 3: windowsize = 5 - if windowsize > 3: + if windowsize > 3 and windowsize 3: + if windowsize > 3 and windowsize 3: + if windowsize > 3 and windowsize < len(velo): velo2 = savgol_filter(velo,windowsize,3) else: velo2=velo @@ -652,7 +652,7 @@ def add_workout_from_stdata(user,importid,data): df['originalvelo'] = velo - if windowsize > 3: + if windowsize > 3 and windowsize 3: + if windowsize > 3 and windowsize Date: Tue, 6 Dec 2016 12:33:40 +0100 Subject: [PATCH 03/13] checked fpace/ftime on customJS --- rowers/interactiveplots.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 96fa3a25..5317e0c5 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -132,7 +132,7 @@ def interactive_histoall(theworkouts): def googlemap_chart(lat,lon,name=""): # plot tools - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,resize,crosshair' + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,resize' map_options = GMapOptions(lat = lat.mean(),lng=lon.mean(), map_type="roadmap",zoom=11) From bd4f9cbe4c9172a20ff5e311e2651fbb025e9cec Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 6 Dec 2016 13:35:35 +0100 Subject: [PATCH 04/13] nothing --- rowers/models.py | 6 ++++++ rowers/views.py | 11 ++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/rowers/models.py b/rowers/models.py index be9e55c0..685a687f 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -80,6 +80,12 @@ class Rower(models.Model): def __str__(self): return self.user.username +class FavoriteChart(models.Model): + yparam1 = models.CharField(max_length=50) + yparam2 = models.CharField(max_length=50) + xparam = models.CharField(max_length=50) + user = models.ForeignKey(Rower) + class Workout(models.Model): workouttypes = ( ('water','On-water'), diff --git a/rowers/views.py b/rowers/views.py index 11b653d6..4b3cf209 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -2573,10 +2573,19 @@ def workout_comparison_view2(request,id1=0,id2=0,xparam='distance', -def workout_flexchart3_view(request,id=0,xparam='distance',yparam1='pace', +def workout_flexchart3_view(request,id=0,#*args,**kwargs): + xparam='distance',yparam1='pace', yparam2='hr',plottype='line', promember=0): + +# print args +# try: +# id = args[0] +# except: +# pass +# if 'xparam' in kwargs: +# print "found it" if request.method == 'POST': workstrokesonly = request.POST['workstrokesonly'] if workstrokesonly == 'True': From bfc05a7de68e10e5d3f55cd0d8115142f61c90de Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 6 Dec 2016 17:45:05 +0100 Subject: [PATCH 05/13] favorites on ote flex - need to do otw flex --- rowers/models.py | 93 ++++++++++- rowers/templates/favoritecharts.html | 35 ++++ rowers/templates/flexchart3.html | 31 ++++ rowers/urls.py | 1 + rowers/views.py | 147 ++++++++++++++--- static/js/jquery.formset.js | 231 +++++++++++++++++++++++++++ 6 files changed, 515 insertions(+), 23 deletions(-) create mode 100644 rowers/templates/favoritecharts.html create mode 100644 static/js/jquery.formset.js diff --git a/rowers/models.py b/rowers/models.py index 685a687f..827c4585 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -6,6 +6,7 @@ from django import forms from django.forms import ModelForm from django.dispatch import receiver from django.forms.widgets import SplitDateTimeWidget +from django.forms.formsets import BaseFormSet from datetimewidget.widgets import DateTimeWidget import os @@ -81,11 +82,95 @@ class Rower(models.Model): return self.user.username class FavoriteChart(models.Model): - yparam1 = models.CharField(max_length=50) - yparam2 = models.CharField(max_length=50) - xparam = models.CharField(max_length=50) - user = models.ForeignKey(Rower) + y1params = ( + ('hr','Heart Rate'), + ('pace','Pace'), + ('spm','SPM'), + ('driveenergy','Work per Stroke'), + ('power','Power'), + ('drivelength','Drivelength'), + ('averageforce','Average Force'), + ('peakforce','Peak Force'), + ('forceratio','Average/Peak Force Ratio'), + ('drivespeed','Drive Speed'), + ('wash','Wash'), + ('slip','Slip'), + ('catch','Catch Angle'), + ('finish','Finish Angle'), + ('peakforceangle','Peak Force Angle') + ) + y2params = ( + ('hr','Heart Rate'), + ('spm','SPM'), + ('driveenergy','Work per Stroke'), + ('power','Power'), + ('drivelength','Drivelength'), + ('averageforce','Average Force'), + ('peakforce','Peak Force'), + ('forceratio','Average/Peak Force Ratio'), + ('drivespeed','Drive Speed'), + ('wash','Wash'), + ('slip','Slip'), + ('catch','Catch Angle'), + ('finish','Finish Angle'), + ('peakforceangle','Peak Force Angle'), + ('None','None') + ) + + xparams = ( + ('time','Time'), + ('distance','Distance'), + ('hr','Heart Rate'), + ('spm','SPM'), + ('driveenergy','Work per Stroke'), + ('power','Power'), + ('drivelength','Drivelength'), + ('averageforce','Average Force'), + ('peakforce','Peak Force'), + ('forceratio','Average/Peak Force Ratio'), + ('drivespeed','Drive Speed'), + ('wash','Wash'), + ('slip','Slip'), + ('catch','Catch Angle'), + ('finish','Finish Angle'), + ('peakforceangle','Peak Force Angle'), + ) + + plottypes = ( + ('line','Line Chart'), + ('scatter','Scatter Chart') + ) + + yparam1 = models.CharField(max_length=50,choices=y1params,verbose_name='Y1') + yparam2 = models.CharField(max_length=50,choices=y2params,verbose_name='Y2') + xparam = models.CharField(max_length=50,choices=xparams,verbose_name='X') + plottype = models.CharField(max_length=50,choices=plottypes,default='line') + user = models.ForeignKey(Rower) + +class FavoriteForm(ModelForm): + class Meta: + model = FavoriteChart + fields = ['xparam','yparam1','yparam2','plottype'] + +class BaseFavoriteFormSet(BaseFormSet): + def clean(self): + if any(self.errors): + return + + for form in self.forms: + if form.cleaned_data: + xparam = form.cleaned_data['xparam'] + yparam1 = form.cleaned_data['yparam1'] + yparam2 = form.cleaned_data['yparam2'] + plottype = form.cleaned_data['plottype'] + + if not xparam: + raise forms.ValidationError( + 'Must have x parameter.', + code='missing_xparam' + ) + class Workout(models.Model): workouttypes = ( ('water','On-water'), diff --git a/rowers/templates/favoritecharts.html b/rowers/templates/favoritecharts.html new file mode 100644 index 00000000..4a6406a8 --- /dev/null +++ b/rowers/templates/favoritecharts.html @@ -0,0 +1,35 @@ +{% extends "base.html" %} + +{% block title %}Change Favorite Charts{% endblock %} + +{% block content %} +
+ {% csrf_token %} + {{ favorites_formset.management_form }} + + {% for favorites_form in favorites_formset %} +
+

Chart {{ forloop.counter }}

+ + {{ favorites_form.as_table }} +
+
+ {% endfor %} +
+
+

+
+
+
+ + + + + + +{% endblock %} diff --git a/rowers/templates/flexchart3.html b/rowers/templates/flexchart3.html index 67d9716b..1a023259 100644 --- a/rowers/templates/flexchart3.html +++ b/rowers/templates/flexchart3.html @@ -163,6 +163,37 @@ +
+
+ {% if maxfav >= 0 %} + Manage Favorites + {% else %} +   + {% endif %} +
+
+ {% if favoritenr > 0 %} + < + {% else %} +

 

+ {% endif %} +
+
+
+ {% csrf_token %} + + +
+
+
+ {% if favoritenr < maxfav %} + > + {% else %} +

 

+ {% endif %} +
+
+ {% endblock %} {% endlocaltime %} diff --git a/rowers/urls.py b/rowers/urls.py index 1fccd429..7c3d8e06 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -146,6 +146,7 @@ urlpatterns = [ url(r'^me/sporttracksauthorize/$',views.rower_sporttracks_authorize), url(r'^me/sporttracksrefresh/$',views.rower_sporttracks_token_refresh), url(r'^me/c2refresh/$',views.rower_c2_token_refresh), + url(r'^me/favoritecharts/$',views.rower_favoritecharts_view), url(r'^email/send/$', views.sendmail), url(r'^email/thankyou/$', TemplateView.as_view(template_name='thankyou.html'), name='thankyou'), url(r'^email/$', TemplateView.as_view(template_name='email.html'), name='email'), diff --git a/rowers/views.py b/rowers/views.py index 4b3cf209..7869f470 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -2,6 +2,7 @@ import time import operator from django.views.generic.base import TemplateView from django.db.models import Q +from django.db import IntegrityError, transaction from django.shortcuts import render from django.http import HttpResponse, HttpResponseRedirect from django.contrib.auth import authenticate, login, logout @@ -16,8 +17,10 @@ from django.core.mail import send_mail, BadHeaderError from rowers.forms import EmailForm, RegistrationForm, RegistrationFormTermsOfService,RegistrationFormUniqueEmail,CNsummaryForm,UpdateWindForm,UpdateStreamForm from rowers.forms import PredictedPieceForm,DateRangeForm,DeltaDaysForm from rowers.forms import SummaryStringForm,IntervalUpdateForm,StrokeDataForm -from rowers.models import Workout, User, Rower, WorkoutForm +from rowers.models import Workout, User, Rower, WorkoutForm,FavoriteChart from rowers.models import RowerPowerForm,RowerForm,GraphImage,AdvancedWorkoutForm +from rowers.models import FavoriteForm,BaseFavoriteFormSet +from django.forms.formsets import formset_factory import StringIO from django.contrib.auth.decorators import login_required,user_passes_test from time import strftime,strptime,mktime,time,daylight @@ -2573,27 +2576,29 @@ def workout_comparison_view2(request,id1=0,id2=0,xparam='distance', -def workout_flexchart3_view(request,id=0,#*args,**kwargs): - xparam='distance',yparam1='pace', - yparam2='hr',plottype='line', - promember=0): +def workout_flexchart3_view(request,*args,**kwargs): +# xparam='distance',yparam1='pace', +# yparam2='hr',plottype='line', +# promember=0): -# print args -# try: -# id = args[0] -# except: -# pass -# if 'xparam' in kwargs: -# print "found it" - if request.method == 'POST': - workstrokesonly = request.POST['workstrokesonly'] - if workstrokesonly == 'True': - workstrokesonly = True - else: - workstrokesonly = False + + try: + id = kwargs['id'] + except KeyError: + return HttpResponse("Invalid workout number") + + if 'promember' in kwargs: + promember = kwargs['promember'] else: - workstrokesonly = False + promember = 0 + + try: + favoritenr = int(request.GET['favoritechart']) + except: + favoritenr = 0 + + row = Workout.objects.get(id=id) promember=0 @@ -2606,6 +2611,56 @@ def workout_flexchart3_view(request,id=0,#*args,**kwargs): if request.user == row.user.user: mayedit=1 + + favorites = FavoriteChart.objects.filter(user=r).order_by("id") + maxfav = len(favorites)-1 + + 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'] + else: + if favorites: + yparam2 = favorites[favoritenr].yparam2 + else: + yparam2 = 'hr' + + if 'plottype' in kwargs: + plottype = kwargs['plottype'] + else: + if favorites: + plottype = favorites[favoritenr].plottype + else: + plottype = 'line' + + if request.method == 'POST' and 'savefavorite' in request.POST: + f = FavoriteChart(user=r,xparam=xparam, + yparam1=yparam1,yparam2=yparam2, + plottype=plottype) + f.save() + if request.method == 'POST' and 'workstrokesonly' in request.POST: + workstrokesonly = request.POST['workstrokesonly'] + if workstrokesonly == 'True': + workstrokesonly = True + else: + workstrokesonly = False + else: + workstrokesonly = False + # create interactive plot res = interactive_flex_chart2(id,xparam=xparam,yparam1=yparam1, yparam2=yparam2, @@ -2631,6 +2686,8 @@ def workout_flexchart3_view(request,id=0,#*args,**kwargs): 'mayedit':mayedit, 'promember':promember, 'workstrokesonly': not workstrokesonly, + 'favoritenr':favoritenr, + 'maxfav':maxfav, }) else: return render(request, @@ -2647,6 +2704,8 @@ def workout_flexchart3_view(request,id=0,#*args,**kwargs): 'mayedit':mayedit, 'promember':promember, 'workstrokesonly': not workstrokesonly, + 'favoritenr':favoritenr, + 'maxfav':maxfav, }) def testbokeh(request): @@ -4433,6 +4492,56 @@ def workout_summary_edit_view(request,id,message="",successmessage="" }) +@login_required() +def rower_favoritecharts_view(request): + message = '' + successmessage = '' + r = Rower.objects.get(user=request.user) + favorites = FavoriteChart.objects.filter(user=r).order_by('id') + favorites_data = [{'yparam1':f.yparam1, + 'yparam2':f.yparam2, + 'xparam':f.xparam, + 'plottype':f.plottype} + for f in favorites] + FavoriteChartFormSet = formset_factory(FavoriteForm,formset=BaseFavoriteFormSet) + + if request.method == 'POST': + favorites_formset = FavoriteChartFormSet(request.POST) + + if favorites_formset.is_valid(): + new_instances = [] + for favorites_form in favorites_formset: + yparam1 = favorites_form.cleaned_data.get('yparam1') + yparam2 = favorites_form.cleaned_data.get('yparam2') + xparam = favorites_form.cleaned_data.get('xparam') + plottype = favorites_form.cleaned_data.get('plottype') + new_instances.append(FavoriteChart(user=r, + yparam1=yparam1, + yparam2=yparam2, + xparam=xparam, + plottype=plottype)) + try: + with transaction.atomic(): + FavoriteChart.objects.filter(user=r).delete() + FavoriteChart.objects.bulk_create(new_instances) + successmessage = "You have updated your favorites" + + except IntegrityError: + message = "something went wrong" + + else: + favorites_formset = FavoriteChartFormSet(initial=favorites_data) + + + context = { + 'favorites_formset':favorites_formset, + 'message':message, + 'successmessage':successmessage, + } + + + + return render(request,'favoritecharts.html',context) @login_required() def rower_edit_view(request,message=""): diff --git a/static/js/jquery.formset.js b/static/js/jquery.formset.js new file mode 100644 index 00000000..9957b77d --- /dev/null +++ b/static/js/jquery.formset.js @@ -0,0 +1,231 @@ +/** + * jQuery Formset 1.3-pre + * @author Stanislaus Madueke (stan DOT madueke AT gmail DOT com) + * @requires jQuery 1.2.6 or later + * + * Copyright (c) 2009, Stanislaus Madueke + * All rights reserved. + * + * Licensed under the New BSD License + * See: http://www.opensource.org/licenses/bsd-license.php + */ +;(function($) { + $.fn.formset = function(opts) + { + var options = $.extend({}, $.fn.formset.defaults, opts), + flatExtraClasses = options.extraClasses.join(' '), + totalForms = $('#id_' + options.prefix + '-TOTAL_FORMS'), + maxForms = $('#id_' + options.prefix + '-MAX_NUM_FORMS'), + minForms = $('#id_' + options.prefix + '-MIN_NUM_FORMS'), + childElementSelector = 'input,select,textarea,label,div', + $$ = $(this), + + applyExtraClasses = function(row, ndx) { + if (options.extraClasses) { + row.removeClass(flatExtraClasses); + row.addClass(options.extraClasses[ndx % options.extraClasses.length]); + } + }, + + updateElementIndex = function(elem, prefix, ndx) { + var idRegex = new RegExp(prefix + '-(\\d+|__prefix__)-'), + replacement = prefix + '-' + ndx + '-'; + if (elem.attr("for")) elem.attr("for", elem.attr("for").replace(idRegex, replacement)); + if (elem.attr('id')) elem.attr('id', elem.attr('id').replace(idRegex, replacement)); + if (elem.attr('name')) elem.attr('name', elem.attr('name').replace(idRegex, replacement)); + }, + + hasChildElements = function(row) { + return row.find(childElementSelector).length > 0; + }, + + showAddButton = function() { + return maxForms.length == 0 || // For Django versions pre 1.2 + (maxForms.val() == '' || (maxForms.val() - totalForms.val() > 0)); + }, + + /** + * Indicates whether delete link(s) can be displayed - when total forms > min forms + */ + showDeleteLinks = function() { + return minForms.length == 0 || // For Django versions pre 1.7 + (minForms.val() == '' || (totalForms.val() - minForms.val() > 0)); + }, + + insertDeleteLink = function(row) { + var delCssSelector = $.trim(options.deleteCssClass).replace(/\s+/g, '.'), + addCssSelector = $.trim(options.addCssClass).replace(/\s+/g, '.'); + if (row.is('TR')) { + // If the forms are laid out in table rows, insert + // the remove button into the last table cell: + row.children(':last').append('' + options.deleteText + ''); + } else if (row.is('UL') || row.is('OL')) { + // If they're laid out as an ordered/unordered list, + // insert an
  • after the last list item: + row.append('
  • ' + options.deleteText +'
  • '); + } else { + // Otherwise, just insert the remove button as the + // last child element of the form's container: + row.append('' + options.deleteText +''); + } + // Check if we're under the minimum number of forms - not to display delete link at rendering + if (!showDeleteLinks()){ + row.find('a.' + delCssSelector).hide(); + } + + row.find('a.' + delCssSelector).click(function() { + var row = $(this).parents('.' + options.formCssClass), + del = row.find('input:hidden[id $= "-DELETE"]'), + buttonRow = row.siblings("a." + addCssSelector + ', .' + options.formCssClass + '-add'), + forms; + if (del.length) { + // We're dealing with an inline formset. + // Rather than remove this form from the DOM, we'll mark it as deleted + // and hide it, then let Django handle the deleting: + del.val('on'); + row.hide(); + forms = $('.' + options.formCssClass).not(':hidden'); + } else { + row.remove(); + // Update the TOTAL_FORMS count: + forms = $('.' + options.formCssClass).not('.formset-custom-template'); + totalForms.val(forms.length); + } + for (var i=0, formCount=forms.length; i