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 @@ +
+

+ Advanced +

+
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

-

Edit Workout

-
-

- Advanced -

-
-
-

- Edit Intervals -

-
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 @@ +
+

+ Edit Intervals +

+
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 @@ +
+

+ Edit Stream Data +

+
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 @@ +
+

+ Edit Wind Data +

+
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 @@ +
+

+ OTW Power +

+
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 %} +
+

+ Configure this page +

+
{% 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

+
+
+

+ {% if formleft.errors %} +

+ Please correct the error{{ formleft.errors|pluralize }} below. +

+ {% endif %} + + + {{ formleft.as_table }} +
+ {% csrf_token %} +

+
+
+ Move Up
+ Move Down +
+
+ +
+
+ +
+ +
+ +
+

Middle Panel

+
+
+

+ {% if formmiddle.errors %} +

+ Please correct the error{{ formmiddle.errors|pluralize }} below. +

+ {% endif %} + + + {{ formmiddle.as_table }} +
+ {% csrf_token %} +
+
+ Move Up
+ Move Down +
+
+ +
+
+ +
+ +
+ + + +{% 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); + } + } +} +