diff --git a/rowers/dataprep.py b/rowers/dataprep.py
index abfde140..05d18aad 100644
--- a/rowers/dataprep.py
+++ b/rowers/dataprep.py
@@ -828,7 +828,13 @@ def new_workout_from_file(r,f2,
makeprivate=False,
notes=''):
message = None
- fileformat = get_file_type(f2)
+ try:
+ fileformat = get_file_type(f2)
+ except IOError:
+ os.remove(f2)
+ message = "Rowsandall could not process this file. The extension is supported but the file seems corrupt. Contact info@rowsandall.com if you think this is incorrect."
+ return (0,message,f2)
+
summary = ''
oarlength = 2.89
inboard = 0.88
@@ -860,6 +866,13 @@ def new_workout_from_file(r,f2,
os.remove(f2)
message = "It looks like this file doesn't contain stroke data."
return (0,message,f2)
+
+ # Some people upload corrupted zip files
+ if fileformat == 'notgzip':
+ os.remove(f2)
+ message = "Rowsandall could not process this file. The extension is supported but the file seems corrupt. Contact info@rowsandall.com if you think this is incorrect."
+ return (0,message,f2)
+
# Some people try to upload RowPro summary logs
if fileformat == 'rowprolog':
diff --git a/rowers/forms.py b/rowers/forms.py
index 1e9974e2..3f2ab3ca 100644
--- a/rowers/forms.py
+++ b/rowers/forms.py
@@ -1,4 +1,5 @@
from django import forms
+from django.contrib.admin.widgets import FilteredSelectMultiple
from rowers.models import Workout
from rowers.rows import validate_file_extension,must_be_csv
from django.contrib.auth.forms import UserCreationForm
@@ -57,7 +58,77 @@ class DocumentsForm(forms.Form):
class Meta:
fields = ['title','file','workouttype','fileformat']
+from utils import (
+ workflowleftpanel,workflowmiddlepanel,
+ defaultleft,defaultmiddle
+ )
+
+# Form to change Workflow page layout
+class WorkFlowLeftPanelForm(forms.Form):
+ panels = defaultleft
+
+ leftpanel = forms.MultipleChoiceField(
+ label='',
+ choices=workflowleftpanel,
+ initial=panels,
+ widget=FilteredSelectMultiple(
+ ('elements'),
+ False
+ )
+ )
+
+ class Media:
+ css = {
+ 'all':['admin/css/widgets.css',]
+# 'css/uid-manage-form.css'],
+ }
+ js = ['/admin/jsi18n/']
+
+
+ def __init__(self, *args, **kwargs):
+ if 'instance' in kwargs:
+ r = kwargs.pop('instance')
+ panels = r.workflowleftpanel
+ self.base_fields['leftpanel'].initial=panels
+ else:
+ panels = defaultleft
+
+
+ super(WorkFlowLeftPanelForm,self).__init__(*args, **kwargs)
+
+
+class WorkFlowMiddlePanelForm(forms.Form):
+ panels = defaultmiddle
+
+
+ middlepanel = forms.MultipleChoiceField(
+ label='',
+ choices=workflowmiddlepanel,
+ initial=panels,
+ widget=FilteredSelectMultiple(
+ ('elements'),
+ False
+ )
+ )
+
+ class Media:
+ css = {
+ 'all':['admin/css/widgets.css',]
+# 'css/uid-manage-form.css'],
+ }
+ js = ['/admin/jsi18n/']
+
+ def __init__(self, *args, **kwargs):
+ if 'instance' in kwargs:
+ r = kwargs.pop('instance')
+ panels = r.workflowmiddlepanel
+ self.base_fields['middlepanel'].initial=panels
+ else:
+ panels = defaultmiddle
+
+ super(WorkFlowMiddlePanelForm,self).__init__(*args, **kwargs)
+
# The form to indicate additional actions to be performed immediately
# after a successful upload
class UploadOptionsForm(forms.Form):
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index 29675cd0..242e6a53 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -235,7 +235,7 @@ def interactive_activitychart(workouts,startdate,enddate,stack='type'):
dates.append(dd)
durations.append(du)
types.append(w.workouttype)
- rowers.append(w.user.user.first_name)
+ rowers.append(w.user.user.first_name[0]+w.user.user.last_name[0])
try:
d = utc.localize(startdate)
diff --git a/rowers/models.py b/rowers/models.py
index 83e699e3..5b7a89ac 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -61,6 +61,47 @@ timezones = (
class UserFullnameChoiceField(forms.ModelChoiceField):
def label_from_instance(self,obj):
return obj.get_full_name()
+
+# model for configurable template field
+class TemplateListField(models.TextField):
+ def __init__(self, *args, **kwargs):
+ self.token = kwargs.pop('token',',')
+ super(TemplateListField, self).__init__(*args, **kwargs)
+
+ def to_python(self, value):
+ if not value: return
+ if isinstance(value, list):
+ return value
+ # remove double quotes and brackets
+ value = re.sub(r'u\"','',value)
+ value = re.sub(r'u\'','',value)
+ value = re.sub(r'\\','',value)
+ value = re.sub(r'\"','',value)
+ value = re.sub(r'\'','',value)
+ value = re.sub(r'\[','',value)
+ value = re.sub(r'\]','',value)
+ value = re.sub(r'\[\[','[',value)
+ value = re.sub(r'\]\]',']',value)
+ value = re.sub(r'\ \ ',' ',value)
+ value = re.sub(r', ',',',value)
+
+ return value.split(self.token)
+
+ def from_db_value(self,value, expression, connection, context):
+ if value is None:
+ return value
+ if isinstance(value, list):
+ return value
+ return value.split(self.token)
+
+ def get_db_prep_value(self, value, connection, prepared=False):
+ if not value: return
+ assert(isinstance(value, list) or isinstance(value, tuple))
+ return self.token.join([unicode(s) for s in value])
+
+ def value_to_string(self, obj):
+ value = self._get_val_from_obj(obj)
+ return self.get_deb_prep_value(value)
# model for Power Zone names
class PowerZonesField(models.TextField):
@@ -155,7 +196,11 @@ class TeamRequest(models.Model):
issuedate = models.DateField(default=timezone.now)
code = models.CharField(max_length=150,unique=True)
-
+from utils import (
+ workflowleftpanel,workflowmiddlepanel,
+ defaultleft,defaultmiddle
+ )
+
# Extension of User with rowing specific data
class Rower(models.Model):
weightcategories = (
@@ -234,6 +279,11 @@ class Rower(models.Model):
'Pwr TR',
'Pwr AN'])
+ # Site Settings
+ workflowleftpanel = TemplateListField(default=defaultleft)
+
+ workflowmiddlepanel = TemplateListField(default=defaultmiddle)
+
# Access tokens
c2token = models.CharField(default='',max_length=200,blank=True,null=True)
tokenexpirydate = models.DateTimeField(blank=True,null=True)
diff --git a/rowers/templates/panel_advancededit.html b/rowers/templates/panel_advancededit.html
new file mode 100644
index 00000000..58f0182c
--- /dev/null
+++ b/rowers/templates/panel_advancededit.html
@@ -0,0 +1,5 @@
+
diff --git a/rowers/templates/panel_editbuttons.html b/rowers/templates/panel_editbuttons.html
index a79cb80a..45f04ddf 100644
--- a/rowers/templates/panel_editbuttons.html
+++ b/rowers/templates/panel_editbuttons.html
@@ -1,18 +1,5 @@
-
-
Navigation
-
-
-
diff --git a/rowers/templates/panel_editintervals.html b/rowers/templates/panel_editintervals.html
new file mode 100644
index 00000000..c29472b2
--- /dev/null
+++ b/rowers/templates/panel_editintervals.html
@@ -0,0 +1,5 @@
+
diff --git a/rowers/templates/panel_editstream.html b/rowers/templates/panel_editstream.html
new file mode 100644
index 00000000..d237c47c
--- /dev/null
+++ b/rowers/templates/panel_editstream.html
@@ -0,0 +1,5 @@
+
diff --git a/rowers/templates/panel_editwind.html b/rowers/templates/panel_editwind.html
new file mode 100644
index 00000000..38969060
--- /dev/null
+++ b/rowers/templates/panel_editwind.html
@@ -0,0 +1,5 @@
+
diff --git a/rowers/templates/panel_geekyheader.html b/rowers/templates/panel_geekyheader.html
new file mode 100644
index 00000000..0195aff3
--- /dev/null
+++ b/rowers/templates/panel_geekyheader.html
@@ -0,0 +1,3 @@
+
+
Geeky Stuff
+
diff --git a/rowers/templates/panel_navigationheader.html b/rowers/templates/panel_navigationheader.html
new file mode 100644
index 00000000..42faa204
--- /dev/null
+++ b/rowers/templates/panel_navigationheader.html
@@ -0,0 +1,3 @@
+
+
Navigation
+
diff --git a/rowers/templates/panel_otwpower.html b/rowers/templates/panel_otwpower.html
new file mode 100644
index 00000000..974536e3
--- /dev/null
+++ b/rowers/templates/panel_otwpower.html
@@ -0,0 +1,5 @@
+
diff --git a/rowers/templates/workflow.html b/rowers/templates/workflow.html
index a7de9675..ebd65dd1 100644
--- a/rowers/templates/workflow.html
+++ b/rowers/templates/workflow.html
@@ -31,6 +31,11 @@
{% block left_panel %}
+
{% for templateName in leftTemplates %}
{% include templateName %}
{% endfor %}
diff --git a/rowers/templates/workflowconfig.html b/rowers/templates/workflowconfig.html
new file mode 100644
index 00000000..d5795eb4
--- /dev/null
+++ b/rowers/templates/workflowconfig.html
@@ -0,0 +1,86 @@
+{% extends "base.html" %}
+
+{% block title %}Change Rower Export Settings{% endblock %}
+
+{% block content %}
+
+
+
+{{ formmiddle.media }}
+
+
+ {% if workoutid %}
+
+ {% endif %}
+
Workflow Page Configuration
+
On this page, you can add and remove elements from the left and middle
+ panels of your Workflow page.
+ Use the arrows on the right to move selected elements up and down to
+ determine the order.
+ In this way, you can configure the page to
+ streamline your regular workout analysis workflow.
+
Left Panel
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/rowers/urls.py b/rowers/urls.py
index 7e6b9fee..2331bc34 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -312,6 +312,7 @@ urlpatterns = [
url(r'^me/tprefresh/$',views.rower_tp_token_refresh),
url(r'^me/c2refresh/$',views.rower_c2_token_refresh),
url(r'^me/favoritecharts/$',views.rower_favoritecharts_view),
+ url(r'^me/workflowconfig$',views.workout_workflow_config_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/utils.py b/rowers/utils.py
index d0b1732a..719df3d4 100644
--- a/rowers/utils.py
+++ b/rowers/utils.py
@@ -5,6 +5,39 @@ import colorsys
lbstoN = 4.44822
+workflowmiddlepanel = (
+ ('panel_statcharts.html','Static Charts'),
+ ('flexthumbnails.html','Flex Charts'),
+ ('panel_summary.html','Summary'),
+ )
+
+defaultmiddle = ['panel_statcharts.html',
+ 'flexthumbnails.html',
+ 'panel_summary.html']
+
+workflowleftpanel = (
+ ('panel_navigationheader.html','Navigation Header'),
+ ('panel_editbuttons.html','Edit Workout Button'),
+ ('panel_advancededit.html','Advanced Workout Edit Button'),
+ ('panel_editintervals.html','Edit Intervals Button'),
+ ('panel_stats.html','Workout Statistics Button'),
+ ('panel_staticchart.html','Create Static Charts Buttons'),
+ ('panel_geekyheader.html','Geeky Header'),
+ ('panel_editwind.html','Edit Wind Data'),
+ ('panel_editstream.html','Edit Stream Data'),
+ ('panel_otwpower.html','Run OTW Power Calculations')
+ )
+
+defaultleft = [
+ 'panel_navigationheader.html',
+ 'panel_editbuttons.html',
+ 'panel_advancededit.html',
+ 'panel_editintervals.html'
+ 'panel_stats.html',
+ 'panel_staticchart.html',
+]
+
+
def absolute(request):
urls = {
'ABSOLUTE_ROOT': request.build_absolute_uri('/')[:-1].strip("/"),
diff --git a/rowers/views.py b/rowers/views.py
index 141307f1..3b84a33c 100644
--- a/rowers/views.py
+++ b/rowers/views.py
@@ -22,7 +22,7 @@ from django.http import (
from django.contrib.auth import authenticate, login, logout
from rowers.forms import (
LoginForm,DocumentsForm,UploadOptionsForm,
- TeamUploadOptionsForm,
+ TeamUploadOptionsForm,WorkFlowLeftPanelForm,WorkFlowMiddlePanelForm,
)
from django.core.urlresolvers import reverse
from django.core.exceptions import PermissionDenied
@@ -5852,11 +5852,50 @@ def workout_comparison_view2(request,id1=0,id2=0,xparam='distance',
'promember':promember,
})
+# Workflow Configuration
+@login_required()
+def workout_workflow_config_view(request):
+ request.session['referer'] = absolute(request)['PATH']
+ request.session[translation.LANGUAGE_SESSION_KEY] = USER_LANGUAGE
+ try:
+ workoutid = request.session['lastworkout']
+ except KeyError:
+ workoutid = 0
+
+
+ r = getrower(request.user)
+ if request.method == 'POST' and 'leftpanel' in request.POST:
+ formleft = WorkFlowLeftPanelForm(request.POST)
+ if formleft.is_valid():
+ leftpanel = formleft.cleaned_data['leftpanel']
+ r.workflowleftpanel = leftpanel
+ r.save()
+ if request.method == 'POST' and 'middlepanel' in request.POST:
+ formmiddle = WorkFlowMiddlePanelForm(request.POST)
+ if formmiddle.is_valid():
+ middlepanel = formmiddle.cleaned_data['middlepanel']
+ r.workflowmiddlepanel = middlepanel
+ r.save()
-# Flex thumbnails
+ formleft = WorkFlowLeftPanelForm(instance=r)
+ formmiddle = WorkFlowMiddlePanelForm(instance=r)
+
+
+ # Add processing of POST data
+
+ return render(request,'workflowconfig.html',
+ {
+ 'rower':r,
+ 'formleft':formleft,
+ 'formmiddle':formmiddle,
+ 'workoutid': workoutid,
+ })
+
+# Workflow View
@login_required()
def workout_workflow_view(request,id):
request.session['referer'] = absolute(request)['PATH']
+ request.session['lastworkout'] = id
request.session[translation.LANGUAGE_SESSION_KEY] = USER_LANGUAGE
try:
row = Workout.objects.get(id=id)
@@ -5892,13 +5931,9 @@ def workout_workflow_view(request,id):
statcharts = GraphImage.objects.filter(workout=row)
# This will be user configurable in the future
- middleTemplates = ['panel_statcharts.html','flexthumbnails.html',
- 'panel_summary.html']
- leftTemplates = [
- 'panel_editbuttons.html',
- 'panel_stats.html',
- 'panel_staticchart.html',
- ]
+ middleTemplates = r.workflowmiddlepanel
+
+ leftTemplates = r.workflowleftpanel
return render(request,
'workflow.html',
diff --git a/static/img/movedn.gif b/static/img/movedn.gif
new file mode 100644
index 00000000..a9cbca3a
Binary files /dev/null and b/static/img/movedn.gif differ
diff --git a/static/img/moveup.gif b/static/img/moveup.gif
new file mode 100644
index 00000000..78fb28fe
Binary files /dev/null and b/static/img/moveup.gif differ
diff --git a/static/js/reorderSelect.js b/static/js/reorderSelect.js
new file mode 100644
index 00000000..d3a75878
--- /dev/null
+++ b/static/js/reorderSelect.js
@@ -0,0 +1,27 @@
+
+function moveUp(selectId) {
+ var selectList = document.getElementById(selectId);
+ var selectOptions = selectList.getElementsByTagName('option');
+ for (var i = 1; i < selectOptions.length; i++) {
+ var opt = selectOptions[i];
+ if (opt.selected) {
+ selectList.removeChild(opt);
+ selectList.insertBefore(opt, selectOptions[i - 1]);
+ }
+ }
+}
+
+function moveDown(selectId) {
+ var selectList = document.getElementById(selectId);
+ var selectOptions = selectList.getElementsByTagName('option');
+ for (var i = selectOptions.length - 2; i >= 0; i--) {
+ var opt = selectOptions[i];
+ if (opt.selected) {
+ var nextOpt = selectOptions[i + 1];
+ opt = selectList.removeChild(opt);
+ nextOpt = selectList.replaceChild(opt, nextOpt);
+ selectList.insertBefore(nextOpt, opt);
+ }
+ }
+}
+