From 57a5da5186261500774fe2d76da79581cac840bb Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 3 Dec 2017 21:51:21 +0100 Subject: [PATCH 1/6] fixes issue #236 - reverts to include rest strokes if workout is only rest strokes, stats shown for entire workout --- rowers/views.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rowers/views.py b/rowers/views.py index 7b64ab0a..7b8296e5 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -6879,7 +6879,11 @@ def workout_stats_view(request,id=0,message="",successmessage=""): if datadf.empty: - return HttpResponse("CSV data file not found") + datadf,row = dataprep.getrowdata_db(id=id) + datadf = dataprep.clean_df_stats(datadf,workstrokesonly=False) + workstrokesonly=False + if datadf.empty: + return HttpResponse("CSV data file not found") workoutstateswork = [1,4,5,8,9,6,7] workoutstatesrest = [3] From e6cb0916e95ef0c0598bfbf13b6aadb4f0341e10 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 4 Dec 2017 09:54:59 +0100 Subject: [PATCH 2/6] comment in ascii --- rowers/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rowers/views.py b/rowers/views.py index 7b8296e5..c7ded93e 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -7806,7 +7806,7 @@ def workout_comment_view(request,id=0): url = url, ) if request.user != r.user: - a_messages.info(r.user,message) + a_messages.info(r.user,message.encode('ascii','ignore')) res = myqueue(queuehigh, handle_sendemailnewcomment,r.user.first_name, From 2f7fcf444b7d0ab029a38f649d1ad963e3f7dd16 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Thu, 7 Dec 2017 08:39:13 +0100 Subject: [PATCH 3/6] added birth date & sex to user model --- rowers/forms.py | 30 ++++++++++++++++++++++++++++++ rowers/models.py | 21 ++++++++++++++++++++- rowers/views.py | 18 ++++++++++++++---- 3 files changed, 64 insertions(+), 5 deletions(-) diff --git a/rowers/forms.py b/rowers/forms.py index aa8cf8ed..f029285d 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -354,6 +354,7 @@ class RegistrationFormTermsOfService(RegistrationForm): tos = forms.BooleanField(widget=forms.CheckboxInput, label='I have read and agree to the Terms of Service', error_messages={'required': "You must agree to the terms to register"}) + class RegistrationFormUniqueEmail(RegistrationFormTermsOfService): @@ -370,6 +371,35 @@ class RegistrationFormUniqueEmail(RegistrationFormTermsOfService): raise forms.ValidationError("This email address is already in use. Please supply a different email address.") return self.cleaned_data['email'] +class RegistrationFormSex(RegistrationFormUniqueEmail): + sexcategories = ( + ('female','female'), + ('male','male'), + ('not specified','not specified'), + ) + + weightcategories = ( + ('hwt','heavy-weight'), + ('lwt','light-weight'), + ) + + birthdate = forms.DateTimeField(widget=SelectDateWidget( + years=range(timezone.now().year-100,timezone.now().year-10)), + initial = datetime.date(year=1970, + month=4, + day=15)) + + sex = forms.ChoiceField(required=True, + choices=sexcategories, + initial='not specified', + label='Sex') + + weightcategory = forms.ChoiceField(label='Weight Category', + choices=weightcategories) + +# def __init__(self, *args, **kwargs): +# self.fields['sex'].initial = 'not specified' + # Time field supporting microseconds. Not used, I believe. class MyTimeField(forms.TimeField): diff --git a/rowers/models.py b/rowers/models.py index 7c67f0f1..28993bd1 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -8,6 +8,7 @@ from django import forms from django.forms import ModelForm from django.dispatch import receiver from django.forms.widgets import SplitDateTimeWidget +from django.forms.extras.widgets import SelectDateWidget from django.forms.formsets import BaseFormSet from datetimewidget.widgets import DateTimeWidget from django.core.validators import validate_email @@ -210,6 +211,12 @@ class Rower(models.Model): ('lwt','light-weight'), ) + sexcategories = ( + ('male','male'), + ('female','female'), + ('not specified','not specified'), + ) + stravatypes = ( ('Ride','Ride'), ('Kitesurf','Kitesurf'), @@ -257,6 +264,11 @@ class Rower(models.Model): max_length=30, choices=weightcategories) + sex = models.CharField(default="not specified", + max_length=30, + choices=sexcategories) + + birthdate = models.DateField(null=True,blank=True) # Power Zone Data ftp = models.IntegerField(default=226,verbose_name="Functional Threshold Power") @@ -939,10 +951,17 @@ class RowerPowerZonesForm(ModelForm): class AccountRowerForm(ModelForm): class Meta: model = Rower - fields = ['weightcategory','getemailnotifications', + fields = ['sex','birthdate','weightcategory','getemailnotifications', 'defaulttimezone','showfavoritechartnotes', 'defaultlandingpage'] + widgets = { + 'birthdate': SelectDateWidget( + years=range( + timezone.now().year-100,timezone.now().year-10)), + } + + def clean_email(self): email = self.cleaned_data.get('email') diff --git a/rowers/views.py b/rowers/views.py index 1c1837f5..87708063 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -44,7 +44,8 @@ from rowers.forms import ( SummaryStringForm,IntervalUpdateForm,StrokeDataForm, StatsOptionsForm,PredictedPieceForm,DateRangeForm,DeltaDaysForm, EmailForm, RegistrationForm, RegistrationFormTermsOfService, - RegistrationFormUniqueEmail,CNsummaryForm,UpdateWindForm, + RegistrationFormUniqueEmail,RegistrationFormSex, + CNsummaryForm,UpdateWindForm, UpdateStreamForm,WorkoutMultipleCompareForm,ChartParamChoiceForm, FusionMetricChoiceForm,BoxPlotChoiceForm,MultiFlexChoiceForm, TrendFlexModalForm,WorkoutSplitForm,WorkoutJoinParamForm, @@ -772,20 +773,25 @@ def add_defaultfavorites(r): # User registration def rower_register_view(request): if request.method == 'POST': - form = RegistrationFormUniqueEmail(request.POST) + #form = RegistrationFormUniqueEmail(request.POST) + form = RegistrationFormSex(request.POST) if form.is_valid(): first_name = form.cleaned_data['first_name'] last_name = form.cleaned_data['last_name'] email = form.cleaned_data['email'] password = form.cleaned_data['password1'] username = form.cleaned_data['username'] + sex = form.cleaned_data['sex'] + birthdate = form.cleaned_data['birthdate'] + weightcategory = form.cleaned_data['weightcategory'] theuser = User.objects.create_user(username,password=password) theuser.first_name = first_name theuser.last_name = last_name theuser.email = email theuser.save() - therower = Rower(user=theuser) + therower = Rower(user=theuser,sex=sex,birthdate=birthdate, + weightcategory=weightcategory) therower.save() @@ -843,7 +849,7 @@ Oh, one more thing. The site is currently in beta and is developing fast. Bear w "registration_form.html", {'form':form}) else: - form = RegistrationFormUniqueEmail() + form = RegistrationFormSex() return render(request, "registration_form.html", {'form':form,}) @@ -10226,8 +10232,10 @@ def rower_edit_view(request,message=""): first_name = ucd['first_name'] last_name = ucd['last_name'] email = ucd['email'] + sex = cd['sex'] defaultlandingpage = cd['defaultlandingpage'] weightcategory = cd['weightcategory'] + birthdate = cd['birthdate'] showfavoritechartnotes = cd['showfavoritechartnotes'] getemailnotifications = cd['getemailnotifications'] defaulttimezone=cd['defaulttimezone'] @@ -10246,6 +10254,8 @@ def rower_edit_view(request,message=""): r.getemailnotifications = getemailnotifications r.defaultlandingpage = defaultlandingpage r.showfavoritechartnotes = showfavoritechartnotes + r.sex = sex + r.birthdate = birthdate r.save() form = RowerForm(instance=r) powerform = RowerPowerForm(instance=r) From 9721aa6a9cb0ebd14839eeb434a02f0abed4e42f Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Thu, 7 Dec 2017 09:19:36 +0100 Subject: [PATCH 4/6] TRIMP working --- rowers/metrics.py | 15 +++++++++++++++ rowers/views.py | 12 ++++++++++++ 2 files changed, 27 insertions(+) diff --git a/rowers/metrics.py b/rowers/metrics.py index 9c1fcfa0..bf1e790b 100644 --- a/rowers/metrics.py +++ b/rowers/metrics.py @@ -1,4 +1,5 @@ from utils import lbstoN +import numpy as np rowingmetrics = ( ('time',{ @@ -301,3 +302,17 @@ dropping to 8m for race pace in the single.""", This value should be fairly constant across all stroke rates.""", }, ) + +def calc_trimp(df,sex,hrmax,hrmin): + if sex == 'male': + f = 1.92 + else: + f = 1.67 + + dt = df['time'].diff()/6.e4 + + hrr = (df['hr']-hrmin)/(hrmax-hrmin) + trimpdata = dt*hrr*0.64*np.exp(f*hrr) + trimp = trimpdata.sum() + + return trimp diff --git a/rowers/views.py b/rowers/views.py index 87708063..8903a9d6 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -59,6 +59,7 @@ from rowers.models import ( ) from rowers.models import FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement from rowers.metrics import rowingmetrics,defaultfavoritecharts +from rowers import metrics import rowers.uploads as uploads from django.forms.formsets import formset_factory import StringIO @@ -6891,6 +6892,8 @@ def workout_stats_view(request,id=0,message="",successmessage=""): if datadf.empty: return HttpResponse("CSV data file not found") + + workoutstateswork = [1,4,5,8,9,6,7] workoutstatesrest = [3] workoutstatetransition = [0,2,10,11,12,13] @@ -6987,6 +6990,15 @@ def workout_stats_view(request,id=0,message="",successmessage=""): except ZeroDivisionError,ValueError: pass + # TRIMP + if datadf['hr'].mean() > 0 and r.sex != 'not specified': + trimp = metrics.calc_trimp(datadf,r.sex,r.max,r.rest) + trimp = int(trimp) + otherstats['trimp'] = { + 'verbose_name': 'TRIMP', + 'value': trimp, + 'unit': 'minutes' + } return render(request, 'workoutstats.html', From d7d1f03674636786fe054523bc3e4916781a6e34 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Thu, 7 Dec 2017 09:44:31 +0100 Subject: [PATCH 5/6] modified new user registration test to account for new fields --- rowers/tests.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rowers/tests.py b/rowers/tests.py index fb8a67fd..ce4ca872 100644 --- a/rowers/tests.py +++ b/rowers/tests.py @@ -307,6 +307,9 @@ class NewUserRegistrationTest(TestCase): 'password1':'aapindewei2', 'password2':'aapindewei2', 'tos':True, + 'weightcategory':'hwt', + 'sex':'male', + 'birthdate':datetime.datetime(year=1970,month=4,day=2) } form = RegistrationFormUniqueEmail(form_data) From 1140c674695644fceff68f471667a2dbb4409b25 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Thu, 7 Dec 2017 14:40:24 +0100 Subject: [PATCH 6/6] reads excel template workout summary --- rowers/dataprep.py | 7 ++++++- rowers/rows.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 7f7fac93..d7c6e6a1 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -32,7 +32,7 @@ from rowingdata import ( MysteryParser, BoatCoachOTWParser,QuiskeParser, painsledDesktopParser, speedcoachParser, ErgStickParser, SpeedCoach2Parser, FITParser, fitsummarydata, - make_cumvalues,cumcpdata, + make_cumvalues,cumcpdata,ExcelTemplate, summarydata, get_file_type, ) @@ -996,6 +996,11 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', def parsenonpainsled(fileformat,f2,summary): # handle RowPro: + if (fileformat == 'xls'): + row = ExcelTemplate(f2) + hasrecognized = True + + if (fileformat == 'rp'): row = RowProParser(f2) hasrecognized = True diff --git a/rowers/rows.py b/rowers/rows.py index acf7c27a..51de9769 100644 --- a/rowers/rows.py +++ b/rowers/rows.py @@ -50,7 +50,7 @@ def validate_file_extension(value): ext = os.path.splitext(value.name)[1] valid_extensions = ['.tcx','.csv','.TCX', '.CSV','.fit','.FIT','.zip','.ZIP', - '.gz','.GZ'] + '.gz','.GZ','.xls'] if not ext in valid_extensions: raise ValidationError(u'File not supported!')