Private
Public Access
1
0

Merge branch 'develop' into feature/restapi

This commit is contained in:
Sander Roosendaal
2016-12-08 11:59:43 +01:00
12 changed files with 758 additions and 166 deletions

View File

@@ -162,8 +162,13 @@ def prepmultipledata(ids,verbose=False):
res = list(itertools.chain.from_iterable(res.fetchall())) res = list(itertools.chain.from_iterable(res.fetchall()))
conn.close() conn.close()
engine.dispose() engine.dispose()
res = list(set(ids)-set(res)) try:
ids2 = [int(id) for id in ids]
except ValueError:
ids2 = ids
res = list(set(ids2)-set(res))
for id in res: for id in res:
rowdata,row = getrowdata(id=id) rowdata,row = getrowdata(id=id)
if verbose: if verbose:
@@ -196,7 +201,8 @@ def read_cols_df_sql(ids,columns):
columns = cls, columns = cls,
ids = tuple(ids), ids = tuple(ids),
)) ))
connection = engine.raw_connection()
df = pd.read_sql_query(query,engine) df = pd.read_sql_query(query,engine)
engine.dispose() engine.dispose()
return df return df
@@ -276,7 +282,7 @@ def dataprep(rowdatadf,id=0,bands=True,barchart=True,otwpower=True,
if windowsize <= 3: if windowsize <= 3:
windowsize = 5 windowsize = 5
if windowsize > 3: if windowsize > 3 and windowsize<len(hr):
spm = savgol_filter(spm,windowsize,3) spm = savgol_filter(spm,windowsize,3)
hr = savgol_filter(hr,windowsize,3) hr = savgol_filter(hr,windowsize,3)
drivelength = savgol_filter(drivelength,windowsize,3) drivelength = savgol_filter(drivelength,windowsize,3)
@@ -348,7 +354,7 @@ def dataprep(rowdatadf,id=0,bands=True,barchart=True,otwpower=True,
driveenergy = rowdatadf.ix[:,'driveenergy'] driveenergy = rowdatadf.ix[:,'driveenergy']
drivelength = driveenergy/(averageforce*4.44822) drivelength = driveenergy/(averageforce*4.44822)
slip = rowdatadf.ix[:,'slip'] slip = rowdatadf.ix[:,'slip']
if windowsize > 3: if windowsize > 3 and windowsize<len(slip):
wash = savgol_filter(wash,windowsize,3) wash = savgol_filter(wash,windowsize,3)
slip = savgol_filter(slip,windowsize,3) slip = savgol_filter(slip,windowsize,3)
catch = savgol_filter(catch,windowsize,3) catch = savgol_filter(catch,windowsize,3)

View File

@@ -48,6 +48,64 @@ import stravastuff
from rowers.dataprep import rdata from rowers.dataprep import rdata
import rowers.dataprep as dataprep import rowers.dataprep as dataprep
axlabels = {
'time': 'Time',
'distance': 'Distance (m)',
'cumdist': 'Distance (m)',
'hr': 'Heart Rate (bpm)',
'spm': 'Stroke Rate (spm)',
'pace': 'Pace (/500m)',
'power': 'Power (Watt)',
'averageforce': 'Average Drive Force (lbs)',
'drivelength': 'Drive Length (m)',
'peakforce': 'Peak Drive Force (lbs)',
'forceratio': 'Average/Peak Drive Force Ratio',
'driveenergy': 'Work per Stroke (J)',
'drivespeed': 'Drive Speed (m/s)',
'slip': 'Slip (degrees)',
'catch': 'Catch (degrees)',
'finish': 'Finish (degrees)',
'wash': 'Wash (degrees)',
'peakforceangle': 'Peak Force Angle (degrees)',
'None': '',
}
yaxminima = {
'hr':100,
'spm':15,
'pace': 1.0e3*210,
'power': 0,
'averageforce': 0,
'peakforce': 0,
'forceratio':0,
'drivelength':0.5,
'driveenergy': 0,
'drivespeed': 0,
'slip': 0,
'catch': -40,
'finish': 20,
'wash': 0,
'peakforceangle': -20,
}
yaxmaxima = {
'hr':200,
'spm':45,
'pace': 1.0e3*75,
'power': 600,
'averageforce':200,
'peakforce':400,
'forceratio':1,
'drivelength':2.0,
'driveenergy': 1000,
'drivespeed':4,
'slip': 15,
'catch': -75,
'finish': 55,
'wash': 30,
'peakforceangle': 20,
}
def tailwind(bearing,vwind,winddir): def tailwind(bearing,vwind,winddir):
""" Calculates head-on head/tailwind in direction of rowing """ Calculates head-on head/tailwind in direction of rowing
@@ -132,7 +190,7 @@ def interactive_histoall(theworkouts):
def googlemap_chart(lat,lon,name=""): def googlemap_chart(lat,lon,name=""):
# plot tools # 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_options = GMapOptions(lat = lat.mean(),lng=lon.mean(),
map_type="roadmap",zoom=11) map_type="roadmap",zoom=11)
@@ -497,7 +555,7 @@ def interactive_chart(id=0,promember=0):
TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
columns = ['time','pace','hr'] columns = ['time','pace','hr','fpace','ftime']
datadf = dataprep.getsmallrowdata_db(columns,ids=[id]) datadf = dataprep.getsmallrowdata_db(columns,ids=[id])
row = Workout.objects.get(id=id) row = Workout.objects.get(id=id)
if datadf.empty: if datadf.empty:
@@ -554,7 +612,6 @@ def interactive_chart(id=0,promember=0):
('Pace','@fpace'), ('Pace','@fpace'),
('HR','@hr{int}'), ('HR','@hr{int}'),
('SPM','@spm{1.1}'), ('SPM','@spm{1.1}'),
('Distance','@cumdist{1.1}'),
]) ])
hover.mode = 'mouse' hover.mode = 'mouse'
@@ -579,52 +636,10 @@ def interactive_cum_flex_chart2(theworkouts,promember=0,
ids = [int(w.id) for w in theworkouts] ids = [int(w.id) for w in theworkouts]
datadf = dataprep.getsmallrowdata_db([xparam,yparam1,yparam2],ids=ids) datadf = dataprep.getsmallrowdata_db([xparam,yparam1,yparam2],ids=ids)
axlabels = {
'time': 'Time',
'distance': 'Distance (m)',
'hr': 'Heart Rate (bpm)',
'spm': 'Stroke Rate (spm)',
'pace': 'Pace (/500m)',
'power': 'Power (Watt)',
'averageforce': 'Average Drive Force (lbs)',
'drivelength': 'Drive Length (m)',
'peakforce': 'Peak Drive Force (lbs)',
'forceratio': 'Average/Peak Drive Force Ratio',
'driveenergy': 'Work per Stroke (J)',
'drivespeed': 'Drive Speed (m/s)',
'None': '',
}
yparamname1 = axlabels[yparam1] yparamname1 = axlabels[yparam1]
yparamname2 = axlabels[yparam2] yparamname2 = axlabels[yparam2]
yaxminima = {
'hr':100,
'spm':15,
'pace': 1.0e3*210,
'power': 0,
'averageforce': 0,
'peakforce': 0,
'forceratio':0,
'drivelength':0.5,
'driveenergy': 0,
'drivespeed': 0,
}
yaxmaxima = {
'hr':200,
'spm':45,
'pace':1.0e3*90,
'power': 600,
'averageforce':200,
'peakforce':400,
'forceratio':1,
'drivelength':2.0,
'driveenergy': 1000,
'drivespeed':4,
}
datadf = datadf[datadf[yparam1] > 0] datadf = datadf[datadf[yparam1] > 0]
@@ -880,63 +895,6 @@ def interactive_flex_chart2(id=0,promember=0,
workstrokesonly=False): workstrokesonly=False):
axlabels = {
'time': 'Time',
'distance': 'Distance (m)',
'cumdist': 'Distance (m)',
'hr': 'Heart Rate (bpm)',
'spm': 'Stroke Rate (spm)',
'pace': 'Pace (/500m)',
'power': 'Power (Watt)',
'averageforce': 'Average Drive Force (lbs)',
'drivelength': 'Drive Length (m)',
'peakforce': 'Peak Drive Force (lbs)',
'forceratio': 'Average/Peak Drive Force Ratio',
'driveenergy': 'Work per Stroke (J)',
'drivespeed': 'Drive Speed (m/s)',
'slip': 'Slip (degrees)',
'catch': 'Catch (degrees)',
'finish': 'Finish (degrees)',
'wash': 'Wash (degrees)',
'peakforceangle': 'Peak Force Angle (degrees)',
'None': '',
}
yaxminima = {
'hr':100,
'spm':15,
'pace': 1.0e3*210,
'power': 0,
'averageforce': 0,
'peakforce': 0,
'forceratio':0,
'drivelength':0.5,
'driveenergy': 0,
'drivespeed': 0,
'slip': 0,
'catch': -70,
'finish': 30,
'wash': 0,
'peakforceangle': -20,
}
yaxmaxima = {
'hr':200,
'spm':45,
'pace': 1.0e3*75,
'power': 600,
'averageforce':200,
'peakforce':400,
'forceratio':1,
'drivelength':2.0,
'driveenergy': 1000,
'drivespeed':4,
'slip': 30,
'catch': -30,
'finish': 70,
'wash': 30,
'peakforceangle': 20,
}
#rowdata,row = dataprep.getrowdata_db(id=id) #rowdata,row = dataprep.getrowdata_db(id=id)
columns = [xparam,yparam1,yparam2, columns = [xparam,yparam1,yparam2,
@@ -1174,6 +1132,7 @@ def interactive_flex_chart2(id=0,promember=0,
var time1 = data['time'] var time1 = data['time']
var pace1 = data['pace'] var pace1 = data['pace']
var hr1 = data['hr'] var hr1 = data['hr']
var fpace1 = data['fpace']
var distance1 = data['distance'] var distance1 = data['distance']
var power1 = data['power'] var power1 = data['power']
var xname = data['xname'][0] var xname = data['xname'][0]
@@ -1195,6 +1154,7 @@ def interactive_flex_chart2(id=0,promember=0,
data2['time'] = [] data2['time'] = []
data2['pace'] = [] data2['pace'] = []
data2['hr'] = [] data2['hr'] = []
data2['fpace'] = []
data2['distance'] = [] data2['distance'] = []
data2['power'] = [] data2['power'] = []
data2['x1mean'] = [] data2['x1mean'] = []
@@ -1212,6 +1172,7 @@ def interactive_flex_chart2(id=0,promember=0,
data2['y2'].push(y2[i]) data2['y2'].push(y2[i])
data2['spm'].push(spm1[i]) data2['spm'].push(spm1[i])
data2['time'].push(time1[i]) data2['time'].push(time1[i])
data2['fpace'].push(fpace1[i])
data2['pace'].push(pace1[i]) data2['pace'].push(pace1[i])
data2['hr'].push(hr1[i]) data2['hr'].push(hr1[i])
data2['distance'].push(distance1[i]) data2['distance'].push(distance1[i])

View File

@@ -6,6 +6,7 @@ from django import forms
from django.forms import ModelForm from django.forms import ModelForm
from django.dispatch import receiver from django.dispatch import receiver
from django.forms.widgets import SplitDateTimeWidget from django.forms.widgets import SplitDateTimeWidget
from django.forms.formsets import BaseFormSet
from datetimewidget.widgets import DateTimeWidget from datetimewidget.widgets import DateTimeWidget
import os import os
@@ -80,6 +81,120 @@ class Rower(models.Model):
def __str__(self): def __str__(self):
return self.user.username return self.user.username
class FavoriteChart(models.Model):
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'),
)
workouttypechoices = (
('ote','Erg/SkiErg'),
('otw','On The Water'),
('both','both')
)
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',default='None',blank=True)
xparam = models.CharField(max_length=50,choices=xparams,verbose_name='X')
plottype = models.CharField(max_length=50,choices=plottypes,
default='line',
verbose_name='Chart Type')
workouttype = models.CharField(max_length=50,choices=workouttypechoices,
default='both',
verbose_name='Workout Type')
reststrokes = models.BooleanField(default=True,verbose_name="Incl. Rest")
user = models.ForeignKey(Rower)
class FavoriteForm(ModelForm):
class Meta:
model = FavoriteChart
fields = ['xparam','yparam1','yparam2',
'plottype','workouttype','reststrokes']
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']
reststrokes = form.cleaned_data['reststrokes']
if not xparam:
raise forms.ValidationError(
'Must have x parameter.',
code='missing_xparam'
)
if not yparam1:
raise forms.ValidationError(
'Must have Y1 parameter.',
code='missing_yparam1'
)
if not yparam2:
yparam2 = 'None'
class Workout(models.Model): class Workout(models.Model):
workouttypes = ( workouttypes = (
('water','On-water'), ('water','On-water'),

View File

@@ -142,6 +142,7 @@ You will be taken to the secure PayPal payment site.
<p> <p>
<ul> <ul>
<li>2016-12-07 Favorite Flex Charts for Premium users</li>
<li>2016-12-01 Support for NK Empower Oarlock parameters (catch and <li>2016-12-01 Support for NK Empower Oarlock parameters (catch and
finish angles, slip and wash, and power as measured by the Oarlock</li> finish angles, slip and wash, and power as measured by the Oarlock</li>
<li>2016-11-10 Power based Pie Charts</li> <li>2016-11-10 Power based Pie Charts</li>

View File

@@ -44,7 +44,7 @@
{% if user.rower.rowerplan == 'pro' %} {% if user.rower.rowerplan == 'pro' %}
<a class="button blue small" href="/rowers/histo">Power Histogram</a> <a class="button blue small" href="/rowers/histo">Power Histogram</a>
{% else %} {% else %}
<a class="button blue small" href="/rowers/about">Power Histogram</a> <a class="button blue small" href="/rowers/promembership">Power Histogram</a>
{% endif %} {% endif %}
</p> </p>
<p> <p>
@@ -73,4 +73,4 @@
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -0,0 +1,40 @@
{% 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_4 alpha">
<h2>Chart {{ forloop.counter }}</h2>
<table>
{{ favorites_form.as_table }}
</table>
</div>
{% endfor %}
<div class="grid_12 alpha">
<p>&nbsp;</p>
</div>
<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,43 @@
</div> </div>
{% if user.rower.rowerplan == 'pro' %}
<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 %}
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart?favoritechart={{ maxfav }}">&lt</a>
{% 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">
{% if workstrokesonly %}
<input type="hidden" name="workstrokesonlysave" value="False">
{% else %}
<input type="hidden" name="workstrokesonlysave" value="True">
{% endif %}
<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 %}
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart?favoritechart=0">&gt</a>
{% endif %}
</div>
</div>
{% endif %}
{% endblock %} {% endblock %}
{% endlocaltime %} {% endlocaltime %}

View File

@@ -198,6 +198,43 @@
</div> </div>
{% if user.rower.rowerplan == 'pro' %}
<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 %}
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart?favoritechart={{ maxfav }}">&lt</a>
{% 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">
{% if workstrokesonly %}
<input type="hidden" name="workstrokesonlysave" value="False">
{% else %}
<input type="hidden" name="workstrokesonlysave" value="True">
{% endif %}
<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 %}
<a class="button blue small" href="/rowers/workout/{{ id }}/flexchart?favoritechart=0">&gt</a>
{% endif %}
</div>
</div>
{% endif %}
{% endblock %} {% endblock %}
{% endlocaltime %} {% endlocaltime %}

View File

@@ -3,37 +3,45 @@
{% block title %}Change Rower {% endblock %} {% block title %}Change Rower {% endblock %}
{% block content %} {% block content %}
{% if form.errors %} {% if form.errors %}
<p style="color: red;"> <p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below. Please correct the error{{ form.errors|pluralize }} below.
</p> </p>
{% endif %} {% endif %}
<div class="grid_6 alpha"> <div class="grid_6 alpha">
<h1>Heart Rate Bands</h1> <h1>Heart Rate Bands</h1>
<form enctype="multipart/form-data" action="" method="post">
<table>
{{ form.as_table }}
</table>
{% csrf_token %}
<div class="grid_2 prefix_2 suffix_2">
<input class="button green" type="submit" value="Save">
</form>
</div>
</div>
<div class="grid_6 omega">
<h1>Functional Threshold Power</h1>
<p>
<form enctype="multipart/form-data" action="" method="post"> <form enctype="multipart/form-data" action="" method="post">
<table> <table>
{{ form.as_table }}
</table>
{% csrf_token %}
<div class="grid_2 prefix_2 suffix_2">
<input class="button green" type="submit" value="Save">
</form>
</div>
</div>
<div class="grid_6 omega">
<h1>Functional Threshold Power</h1>
<p>
<form enctype="multipart/form-data" action="" method="post">
<table>
{{ powerform.as_table }} {{ powerform.as_table }}
</table> </table>
{% csrf_token %} {% csrf_token %}
<div class="grid_2 prefix_2 suffix_2"> <div class="grid_2 prefix_2 suffix_2">
<input class="button green" type="submit" value="Save"> <input class="button green" type="submit" value="Save">
</form> </form>
</div> </div>
</p> </p>
</div> </div>
{% endblock %}
<div class="grid_6 prefix_6 alpha">
<div class="grid_2 suffix_4 alpha">
<a class="button gray small" href="/rowers/me/favoritecharts">Manage Favorite Charts</a>
</div>
</div>
{% endblock %}

View File

@@ -146,6 +146,7 @@ urlpatterns = [
url(r'^me/sporttracksauthorize/$',views.rower_sporttracks_authorize), url(r'^me/sporttracksauthorize/$',views.rower_sporttracks_authorize),
url(r'^me/sporttracksrefresh/$',views.rower_sporttracks_token_refresh), url(r'^me/sporttracksrefresh/$',views.rower_sporttracks_token_refresh),
url(r'^me/c2refresh/$',views.rower_c2_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/send/$', views.sendmail),
url(r'^email/thankyou/$', TemplateView.as_view(template_name='thankyou.html'), name='thankyou'), 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'), url(r'^email/$', TemplateView.as_view(template_name='email.html'), name='email'),

View File

@@ -2,6 +2,7 @@ import time
import operator import operator
from django.views.generic.base import TemplateView from django.views.generic.base import TemplateView
from django.db.models import Q from django.db.models import Q
from django.db import IntegrityError, transaction
from django.shortcuts import render from django.shortcuts import render
from django.http import ( from django.http import (
HttpResponse, HttpResponseRedirect, HttpResponse, HttpResponseRedirect,
@@ -20,8 +21,10 @@ from django.core.mail import send_mail, BadHeaderError
from rowers.forms import EmailForm, RegistrationForm, RegistrationFormTermsOfService,RegistrationFormUniqueEmail,CNsummaryForm,UpdateWindForm,UpdateStreamForm from rowers.forms import EmailForm, RegistrationForm, RegistrationFormTermsOfService,RegistrationFormUniqueEmail,CNsummaryForm,UpdateWindForm,UpdateStreamForm
from rowers.forms import PredictedPieceForm,DateRangeForm,DeltaDaysForm from rowers.forms import PredictedPieceForm,DateRangeForm,DeltaDaysForm
from rowers.forms import SummaryStringForm,IntervalUpdateForm,StrokeDataForm 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 RowerPowerForm,RowerForm,GraphImage,AdvancedWorkoutForm
from rowers.models import FavoriteForm,BaseFavoriteFormSet
from django.forms.formsets import formset_factory
import StringIO import StringIO
from django.contrib.auth.decorators import login_required,user_passes_test from django.contrib.auth.decorators import login_required,user_passes_test
from time import strftime,strptime,mktime,time,daylight from time import strftime,strptime,mktime,time,daylight
@@ -424,7 +427,7 @@ def add_workout_from_strokedata(user,importid,data,strokedata,source='c2'):
df['originalvelo'] = velo df['originalvelo'] = velo
if windowsize > 3: if windowsize > 3 and windowsize < len(velo):
velo2 = savgol_filter(velo,windowsize,3) velo2 = savgol_filter(velo,windowsize,3)
else: else:
velo2=velo velo2=velo
@@ -656,7 +659,7 @@ def add_workout_from_stdata(user,importid,data):
df['originalvelo'] = velo df['originalvelo'] = velo
if windowsize > 3: if windowsize > 3 and windowsize<len(velo):
velo2 = savgol_filter(velo,windowsize,3) velo2 = savgol_filter(velo,windowsize,3)
else: else:
velo2 = velo velo2 = velo
@@ -1450,7 +1453,7 @@ def histo(request,theuser=0,
promember=1 promember=1
if not promember: if not promember:
return HttpResponseRedirect("/rowers/about/") return HttpResponseRedirect("/rowers/promembership/")
# get all indoor rows of in date range # get all indoor rows of in date range
@@ -1913,7 +1916,7 @@ def workouts_view(request,message='',successmessage='',
except Rower.DoesNotExist: except Rower.DoesNotExist:
return HttpResponse("User has no rower instance") return HttpResponse("User has no rower instance")
@user_passes_test(promember,login_url="/login") @user_passes_test(promember,login_url="/",redirect_field_name=None)
def workout_comparison_list(request,id=0,message='',successmessage='', def workout_comparison_list(request,id=0,message='',successmessage='',
startdatestring="",enddatestring="", startdatestring="",enddatestring="",
startdate=timezone.now()-datetime.timedelta(days=365), startdate=timezone.now()-datetime.timedelta(days=365),
@@ -2027,7 +2030,7 @@ def workout_view(request,id=0):
return HttpResponseNotFound("Workout doesn't exist") return HttpResponseNotFound("Workout doesn't exist")
@user_passes_test(promember,login_url="/login") @user_passes_test(promember,login_url="/",redirect_field_name=None)
def workout_undo_smoothenpace_view(request,id=0,message="",successmessage=""): def workout_undo_smoothenpace_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id) row = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,row)==False): if (checkworkoutuser(request.user,row)==False):
@@ -2054,7 +2057,7 @@ def workout_undo_smoothenpace_view(request,id=0,message="",successmessage=""):
@user_passes_test(promember,login_url="/login") @user_passes_test(promember,login_url="/",redirect_field_name=None)
def workout_smoothenpace_view(request,id=0,message="",successmessage=""): def workout_smoothenpace_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id) row = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,row)==False): if (checkworkoutuser(request.user,row)==False):
@@ -2089,7 +2092,7 @@ def workout_smoothenpace_view(request,id=0,message="",successmessage=""):
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@user_passes_test(promember,login_url="/login") @user_passes_test(promember,login_url="/",redirect_field_name=None)
def workout_crewnerd_summary_view(request,id=0,message="",successmessage=""): def workout_crewnerd_summary_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id) row = Workout.objects.get(id=id)
if request.method == 'POST': if request.method == 'POST':
@@ -2133,7 +2136,7 @@ def workout_crewnerd_summary_view(request,id=0,message="",successmessage=""):
{'form':form, {'form':form,
'id':row.id}) 'id':row.id})
@user_passes_test(promember,login_url="/login") @user_passes_test(promember,login_url="/",redirect_field_name=None)
def workout_downloadwind_view(request,id=0,message="",successmessage=""): def workout_downloadwind_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id) row = Workout.objects.get(id=id)
f1 = row.csvfilename f1 = row.csvfilename
@@ -2190,7 +2193,7 @@ def workout_downloadwind_view(request,id=0,message="",successmessage=""):
return response return response
@user_passes_test(promember,login_url="/login") @user_passes_test(promember,login_url="/",redirect_field_name=None)
def workout_wind_view(request,id=0,message="",successmessage=""): def workout_wind_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id) row = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,row)==False): if (checkworkoutuser(request.user,row)==False):
@@ -2286,7 +2289,7 @@ def workout_wind_view(request,id=0,message="",successmessage=""):
'gmapdiv':gmdiv}) 'gmapdiv':gmdiv})
@user_passes_test(promember,login_url="/login") @user_passes_test(promember,login_url="/",redirect_field_name=None)
def workout_stream_view(request,id=0,message="",successmessage=""): def workout_stream_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id) row = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,row)==False): if (checkworkoutuser(request.user,row)==False):
@@ -2348,7 +2351,7 @@ def workout_stream_view(request,id=0,message="",successmessage=""):
'the_div':div}) 'the_div':div})
@user_passes_test(promember, login_url="/login") @user_passes_test(promember, login_url="/",redirect_field_name=None)
def workout_otwsetpower_view(request,id=0,message="",successmessage=""): def workout_otwsetpower_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id) row = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,row)==False): if (checkworkoutuser(request.user,row)==False):
@@ -2478,7 +2481,7 @@ def workout_geeky_view(request,id=0,message="",successmessage=""):
'interactiveplot':script, 'interactiveplot':script,
'the_div':div}) 'the_div':div})
#@user_passes_test(promember,login_url="/login") #@user_passes_test(promember,login_url="/",redirect_field_name=None)
@login_required() @login_required()
def workout_advanced_view(request,id=0,message="",successmessage=""): def workout_advanced_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id) row = Workout.objects.get(id=id)
@@ -2577,18 +2580,24 @@ 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,*args,**kwargs):
yparam2='hr',plottype='line',
promember=0):
if request.method == 'POST': try:
workstrokesonly = request.POST['workstrokesonly'] id = kwargs['id']
if workstrokesonly == 'True': except KeyError:
workstrokesonly = True return HttpResponse("Invalid workout number")
else:
workstrokesonly = False if 'promember' in kwargs:
promember = kwargs['promember']
else: else:
workstrokesonly = False promember = 0
try:
favoritenr = int(request.GET['favoritechart'])
except:
favoritenr = 0
row = Workout.objects.get(id=id) row = Workout.objects.get(id=id)
promember=0 promember=0
@@ -2601,6 +2610,82 @@ def workout_flexchart3_view(request,id=0,xparam='distance',yparam1='pace',
if request.user == row.user.user: if request.user == row.user.user:
mayedit=1 mayedit=1
workouttype = 'ote'
if row.workouttype == 'water':
workouttype = 'otw'
favorites = FavoriteChart.objects.filter(user=r,
workouttype__in=[workouttype,'both']).order_by("id")
maxfav = len(favorites)-1
# check if favoritenr is not out of range
if favorites:
try:
t = favorites[favoritenr].xparam
except IndexError:
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 '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:
workstrokesonly = request.POST['workstrokesonlysave']
reststrokes = not workstrokesonly
f = FavoriteChart(user=r,xparam=xparam,
yparam1=yparam1,yparam2=yparam2,
plottype=plottype,workouttype=workouttype,
reststrokes=reststrokes)
f.save()
if request.method == 'POST' and 'workstrokesonly' in request.POST:
workstrokesonly = request.POST['workstrokesonly']
if workstrokesonly == 'True':
workstrokesonly = True
else:
workstrokesonly = False
# create interactive plot # create interactive plot
res = interactive_flex_chart2(id,xparam=xparam,yparam1=yparam1, res = interactive_flex_chart2(id,xparam=xparam,yparam1=yparam1,
yparam2=yparam2, yparam2=yparam2,
@@ -2626,6 +2711,8 @@ def workout_flexchart3_view(request,id=0,xparam='distance',yparam1='pace',
'mayedit':mayedit, 'mayedit':mayedit,
'promember':promember, 'promember':promember,
'workstrokesonly': not workstrokesonly, 'workstrokesonly': not workstrokesonly,
'favoritenr':favoritenr,
'maxfav':maxfav,
}) })
else: else:
return render(request, return render(request,
@@ -2642,6 +2729,8 @@ def workout_flexchart3_view(request,id=0,xparam='distance',yparam1='pace',
'mayedit':mayedit, 'mayedit':mayedit,
'promember':promember, 'promember':promember,
'workstrokesonly': not workstrokesonly, 'workstrokesonly': not workstrokesonly,
'favoritenr':favoritenr,
'maxfav':maxfav,
}) })
def testbokeh(request): def testbokeh(request):
@@ -2735,7 +2824,7 @@ def testbokeh(request):
'css_res':css_resources, 'css_res':css_resources,
}) })
#@user_passes_test(promember,login_url="/login") #@user_passes_test(promember,login_url="/",redirect_field_name=None)
def workout_biginteractive_view(request,id=0,message="",successmessage=""): def workout_biginteractive_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id) row = Workout.objects.get(id=id)
# check if user is owner of this workout # check if user is owner of this workout
@@ -2977,7 +3066,7 @@ def workout_edit_view(request,id=0,message="",successmessage=""):
'workout_form.html', 'workout_form.html',
{'form':form}) {'form':form})
@user_passes_test(promember,login_url="/login") @user_passes_test(promember,login_url="/",redirect_field_name=None)
def workout_add_otw_powerplot_view(request,id): def workout_add_otw_powerplot_view(request,id):
w = Workout.objects.get(id=id) w = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,w)==False): if (checkworkoutuser(request.user,w)==False):
@@ -3199,7 +3288,7 @@ def workout_add_distanceplot_view(request,id):
url = "/rowers/workout/"+str(w.id)+"/edit" url = "/rowers/workout/"+str(w.id)+"/edit"
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@user_passes_test(promember,login_url="/login") @user_passes_test(promember,login_url="/",redirect_field_name=None)
def workout_add_distanceplot2_view(request,id): def workout_add_distanceplot2_view(request,id):
w = Workout.objects.get(id=id) w = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,w)==False): if (checkworkoutuser(request.user,w)==False):
@@ -3243,7 +3332,7 @@ def workout_add_distanceplot2_view(request,id):
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@user_passes_test(promember,login_url="/login") @user_passes_test(promember,login_url="/",redirect_field_name=None)
def workout_add_timeplot2_view(request,id): def workout_add_timeplot2_view(request,id):
w = Workout.objects.get(id=id) w = Workout.objects.get(id=id)
if (checkworkoutuser(request.user,w)==False): if (checkworkoutuser(request.user,w)==False):
@@ -3639,7 +3728,7 @@ def workout_upload_view(request,message=""):
if not 'originalvelo' in row.df: if not 'originalvelo' in row.df:
row.df['originalvelo'] = velo row.df['originalvelo'] = velo
if windowsize > 3: if windowsize > 3 and windowsize<len(velo):
velo2 = savgol_filter(velo,windowsize,3) velo2 = savgol_filter(velo,windowsize,3)
else: else:
velo2 = velo velo2 = velo
@@ -3711,7 +3800,7 @@ def workout_upload_view(request,message=""):
w.save() w.save()
# put stroke data in database # put stroke data in database
res = dataprep.dataprep(row.df,id=w.id,bands=True,barchart=True,otwpower=True,empower=True) res = dataprep.dataprep(row.df,id=w.id,bands=True,barchart=True,otwpower=True,empower=True)
# Make Plot # Make Plot
if (make_plot): if (make_plot):
imagename = f1[:-4]+'.png' imagename = f1[:-4]+'.png'
@@ -4428,6 +4517,72 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
}) })
@user_passes_test(promember,login_url="/rowers/me/edit",redirect_field_name=None)
def rower_favoritecharts_view(request):
message = ''
successmessage = ''
r = Rower.objects.get(user=request.user)
favorites = FavoriteChart.objects.filter(user=r).order_by('id')
aantal = len(favorites)
favorites_data = [{'yparam1':f.yparam1,
'yparam2':f.yparam2,
'xparam':f.xparam,
'plottype':f.plottype,
'workouttype':f.workouttype,
'reststrokes':f.reststrokes}
for f in favorites]
FavoriteChartFormSet = formset_factory(FavoriteForm,formset=BaseFavoriteFormSet,extra=0)
if aantal==0:
FavoriteChartFormSet = formset_factory(FavoriteForm,formset=BaseFavoriteFormSet,extra=1)
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')
workouttype = favorites_form.cleaned_data.get('workouttype')
reststrokes = favorites_form.cleaned_data.get('reststrokes')
new_instances.append(FavoriteChart(user=r,
yparam1=yparam1,
yparam2=yparam2,
xparam=xparam,
plottype=plottype,
workouttype=workouttype,
reststrokes=reststrokes))
try:
with transaction.atomic():
FavoriteChart.objects.filter(user=r).delete()
FavoriteChart.objects.bulk_create(new_instances)
successmessage = "You have updated your favorites"
FavoriteChartFormSet=formset_factory(FavoriteForm,formset=BaseFavoriteFormSet)
print new_instances
print "aap",len(new_instances)
if len(new_instances)==0:
FavoriteChartFormSet=formset_factory(FavoriteForm,formset=BaseFavoriteFormSet,extra=1)
favorites_formset = FavoriteChartFormSet()
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() @login_required()
def rower_edit_view(request,message=""): 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);