Private
Public Access
1
0

favorites on ote flex - need to do otw flex

This commit is contained in:
Sander Roosendaal
2016-12-06 17:45:05 +01:00
parent bd4f9cbe4c
commit bfc05a7de6
6 changed files with 515 additions and 23 deletions

View File

@@ -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'),

View File

@@ -0,0 +1,35 @@
{% extends "base.html" %}
{% block title %}Change Favorite Charts{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
{{ favorites_formset.management_form }}
{% for favorites_form in favorites_formset %}
<div class="fav-formset grid_6 alpha">
<h2>Chart {{ forloop.counter }}</h2>
<table>
{{ favorites_form.as_table }}
</table>
</div>
{% endfor %}
<div class="grid_12 alpha">
<div class="grid_2">
<p><input type="submit" value="Update Favorites" class="button green small"/></p>
</div>
</div>
</form>
<!-- Include formset plugin - including jQuery dependency -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="/static/js/jquery.formset.js"></script>
<script>
$('.fav-formset').formset({
addText: '<div class="grid_12">&nbsp;</div><div class="button grid_2 green small">add chart</div>',
deleteText: '<div class="grid_12"><p>&nbsp;</p></div><div class="button grid_1 red small">remove</div>'
});
</script>
{% endblock %}

View File

@@ -163,6 +163,37 @@
</div>
<div id="favorites" class="grid_12 alpha">
<div class="grid_2 suffix_4 alpha">
{% if maxfav >= 0 %}
<a class="button gray small" href="/rowers/me/favoritecharts">Manage Favorites</a>
{% else %}
&nbsp;
{% endif %}
</div>
<div class="grid_1">
{% if favoritenr > 0 %}
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart?favoritechart={{ favoritenr|add:-1 }}">&lt</a>
{% else %}
<p>&nbsp;</p>
{% endif %}
</div>
<div class="grid_2">
<form enctype="multipart/form-data" action="{{ formloc }}" method="post">
{% csrf_token %}
<input class="grid_2 alpha button blue small" type="hidden" name="savefavorite" value="True">
<input class="grid_2 alpha button blue small" value="Make Favorite" type="Submit">
</form>
</div>
<div class="grid_1">
{% if favoritenr < maxfav %}
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart?favoritechart={{ favoritenr|add:1 }}">&gt</a>
{% else %}
<p>&nbsp;</p>
{% endif %}
</div>
</div>
{% endblock %}
{% endlocaltime %}

View File

@@ -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'),

View File

@@ -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=""):

231
static/js/jquery.formset.js Normal file
View File

@@ -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('<a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + '</a>');
} else if (row.is('UL') || row.is('OL')) {
// If they're laid out as an ordered/unordered list,
// insert an <li> after the last list item:
row.append('<li><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText +'</a></li>');
} else {
// Otherwise, just insert the remove button as the
// last child element of the form's container:
row.append('<a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText +'</a>');
}
// 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<formCount; i++) {
// Apply `extraClasses` to form rows so they're nicely alternating:
applyExtraClasses(forms.eq(i), i);
if (!del.length) {
// Also update names and IDs for all child controls (if this isn't
// a delete-able inline formset) so they remain in sequence:
forms.eq(i).find(childElementSelector).each(function() {
updateElementIndex($(this), options.prefix, i);
});
}
}
// Check if we've reached the minimum number of forms - hide all delete link(s)
if (!showDeleteLinks()){
$('a.' + delCssSelector).each(function(){$(this).hide();});
}
// Check if we need to show the add button:
if (buttonRow.is(':hidden') && showAddButton()) buttonRow.show();
// If a post-delete callback was provided, call it with the deleted form:
if (options.removed) options.removed(row);
return false;
});
};
$$.each(function(i) {
var row = $(this),
del = row.find('input:checkbox[id $= "-DELETE"]');
if (del.length) {
// If you specify "can_delete = True" when creating an inline formset,
// Django adds a checkbox to each form in the formset.
// Replace the default checkbox with a hidden field:
if (del.is(':checked')) {
// If an inline formset containing deleted forms fails validation, make sure
// we keep the forms hidden (thanks for the bug report and suggested fix Mike)
del.before('<input type="hidden" name="' + del.attr('name') +'" id="' + del.attr('id') +'" value="on" />');
row.hide();
} else {
del.before('<input type="hidden" name="' + del.attr('name') +'" id="' + del.attr('id') +'" />');
}
// Hide any labels associated with the DELETE checkbox:
$('label[for="' + del.attr('id') + '"]').hide();
del.remove();
}
if (hasChildElements(row)) {
row.addClass(options.formCssClass);
if (row.is(':visible')) {
insertDeleteLink(row);
applyExtraClasses(row, i);
}
}
});
if ($$.length) {
var hideAddButton = !showAddButton(),
addButton, template;
if (options.formTemplate) {
// If a form template was specified, we'll clone it to generate new form instances:
template = (options.formTemplate instanceof $) ? options.formTemplate : $(options.formTemplate);
template.removeAttr('id').addClass(options.formCssClass + ' formset-custom-template');
template.find(childElementSelector).each(function() {
updateElementIndex($(this), options.prefix, '__prefix__');
});
insertDeleteLink(template);
} else {
// Otherwise, use the last form in the formset; this works much better if you've got
// extra (>= 1) forms (thnaks to justhamade for pointing this out):
template = $('.' + options.formCssClass + ':last').clone(true).removeAttr('id');
template.find('input:hidden[id $= "-DELETE"]').remove();
// Clear all cloned fields, except those the user wants to keep (thanks to brunogola for the suggestion):
template.find(childElementSelector).not(options.keepFieldValues).each(function() {
var elem = $(this);
// If this is a checkbox or radiobutton, uncheck it.
// This fixes Issue 1, reported by Wilson.Andrew.J:
if (elem.is('input:checkbox') || elem.is('input:radio')) {
elem.attr('checked', false);
} else {
elem.val('');
}
});
}
// FIXME: Perhaps using $.data would be a better idea?
options.formTemplate = template;
if ($$.is('TR')) {
// If forms are laid out as table rows, insert the
// "add" button in a new table row:
var numCols = $$.eq(0).children().length, // This is a bit of an assumption :|
buttonRow = $('<tr><td colspan="' + numCols + '"><a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + '</a></tr>')
.addClass(options.formCssClass + '-add');
$$.parent().append(buttonRow);
if (hideAddButton) buttonRow.hide();
addButton = buttonRow.find('a');
} else {
// Otherwise, insert it immediately after the last form:
$$.filter(':last').after('<a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + '</a>');
addButton = $$.filter(':last').next();
if (hideAddButton) addButton.hide();
}
addButton.click(function() {
var formCount = parseInt(totalForms.val()),
row = options.formTemplate.clone(true).removeClass('formset-custom-template'),
buttonRow = $($(this).parents('tr.' + options.formCssClass + '-add').get(0) || this)
delCssSelector = $.trim(options.deleteCssClass).replace(/\s+/g, '.');
applyExtraClasses(row, formCount);
row.insertBefore(buttonRow).show();
row.find(childElementSelector).each(function() {
updateElementIndex($(this), options.prefix, formCount);
});
totalForms.val(formCount + 1);
// Check if we're above the minimum allowed number of forms -> show all delete link(s)
if (showDeleteLinks()){
$('a.' + delCssSelector).each(function(){$(this).show();});
}
// Check if we've exceeded the maximum allowed number of forms:
if (!showAddButton()) buttonRow.hide();
// If a post-add callback was supplied, call it with the added form:
if (options.added) options.added(row);
return false;
});
}
return $$;
};
/* Setup plugin defaults */
$.fn.formset.defaults = {
prefix: 'form', // The form prefix for your django formset
formTemplate: null, // The jQuery selection cloned to generate new form instances
addText: 'add another', // Text for the add link
deleteText: 'remove', // Text for the delete link
addCssClass: 'add-row', // CSS class applied to the add link
deleteCssClass: 'delete-row', // CSS class applied to the delete link
formCssClass: 'dynamic-form', // CSS class applied to each form in a formset
extraClasses: [], // Additional CSS classes, which will be applied to each form in turn
keepFieldValues: '', // jQuery selector for fields whose values should be kept when the form is cloned
added: null, // Function called each time a new form is added
removed: null // Function called each time a form is deleted
};
})(jQuery);