From 04aa1a146d3102bbb9af64cb1b0514d4b3a8ad15 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 7 Oct 2022 09:28:16 +0200 Subject: [PATCH 1/9] adding condition description --- rowers/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rowers/models.py b/rowers/models.py index 2eeefe77..d4af5780 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -1481,6 +1481,9 @@ class Alert(models.Model): value1=self.measured.value1, ) + for condition in self.filter.all(): + description += ' '+str(condition)+';' + return description def shortdescription(self): # pragma: no cover From fba2d9e4c09d7802ca794a0eebeb9653f4dad7bb Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 7 Oct 2022 11:43:45 +0200 Subject: [PATCH 2/9] updating filter str --- rowers/models.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/rowers/models.py b/rowers/models.py index d4af5780..221e11f9 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -1378,12 +1378,20 @@ class Condition(models.Model): max_length=20, choices=conditionchoices, null=True) def __str__(self): - str = 'Condition: {metric} {condition} {value1} {value2}'.format( + str = 'Condition: {metric} {condition} {value1}'.format( metric=self.metric, condition=self.condition, value1 = self.value1, - value2 = self.value2, ) + if self.condition == 'between': + str = 'Condition: {metric} between {value1} and {value2}'.format( + metric=self.metric, + condition=self.condition, + value1 = self.value1, + value2 = self.value2, + ) + + return str From ee2d2120a41d37c2dbe1d418eb5a5a48d110f1d5 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 7 Oct 2022 12:01:44 +0200 Subject: [PATCH 3/9] better breakthrough email --- rowers/models.py | 10 ++++++++++ rowers/tasks.py | 1 + rowers/templates/breakthroughemail.html | 2 +- rowers/tests/test_async_tasks.py | 1 + 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/rowers/models.py b/rowers/models.py index 221e11f9..8e6cfba1 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -4966,3 +4966,13 @@ class ShareKey(models.Model): @property def expiration_date(self): # pragma: no cover return self.creation_date + datetime.timedelta(self.expiration_seconds) + +class InStrokeAnalysis(models.Model): + workout = models.ForeignKey(Workout, on_delete=models.CASCADE) + name = models.CharField(max_length=150, blank=True, null=True) + date = models.DateField(blank=True, null=True) + notes = models.TextField(blank=True) + start_second = models.IntegerField(default=0) + end_second = models.IntegerField(default=3600) + min_spm = models.IntegerField(default=10) + max_spm = models.IntegerField(default=45) diff --git a/rowers/tasks.py b/rowers/tasks.py index bff93d3e..5cf8866b 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -1715,6 +1715,7 @@ def handle_sendemail_breakthrough(workoutid, useremail, tablevalues = [ {'delta': t.delta, + 'time': str(timedelta(seconds=t.delta)), 'cpvalue': t.cpvalues, 'pwr': t.pwr } for t in btvalues.itertuples() diff --git a/rowers/templates/breakthroughemail.html b/rowers/templates/breakthroughemail.html index beadc132..d4280b5c 100644 --- a/rowers/templates/breakthroughemail.html +++ b/rowers/templates/breakthroughemail.html @@ -40,7 +40,7 @@ {% for set in btvalues %} - {{ set["delta"] }} + {{ set["time"] }} {{ set["cpvalue"] }} {{ set["pwr"] }} diff --git a/rowers/tests/test_async_tasks.py b/rowers/tests/test_async_tasks.py index daa642e6..43718b36 100644 --- a/rowers/tests/test_async_tasks.py +++ b/rowers/tests/test_async_tasks.py @@ -306,6 +306,7 @@ class AsyncTaskTests(TestCase): btvalues = pd.DataFrame({ 'delta':[3,1,3], + 'time': str(timedelta(seconds=t) for t in [3,1,3]), 'cpvalues':[100,200,300], 'pwr':[100,200,300] }).to_json() From cf7d6c7c7970b15983809d70661162195128d233 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 7 Oct 2022 13:23:15 +0200 Subject: [PATCH 4/9] first version analyses collection --- rowers/forms.py | 4 ++ rowers/interactiveplots.py | 21 ++++++++- rowers/models.py | 6 ++- rowers/templates/instroke_analysis.html | 55 ++++++++++++++++++++++ rowers/templates/instroke_interactive.html | 3 +- rowers/templates/laboratory.html | 2 +- rowers/urls.py | 2 + rowers/views/analysisviews.py | 26 ++++++++++ rowers/views/statements.py | 2 +- rowers/views/workoutviews.py | 21 +++++++++ 10 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 rowers/templates/instroke_analysis.html diff --git a/rowers/forms.py b/rowers/forms.py index 8610dab5..bad4b1b6 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -150,6 +150,7 @@ class InstantPlanSelectForm(forms.Form): # Instroke Metrics interactive chart form class InstrokeForm(forms.Form): + name = forms.CharField(initial="", max_length=200,required=False) metric = forms.ChoiceField(label='metric',choices=(('a','a'),('b','b'))) individual_curves = forms.BooleanField(label='individual curves',initial=False, required=False) @@ -159,6 +160,9 @@ class InstrokeForm(forms.Form): required=False, initial=0, widget=forms.HiddenInput()) activeminutesmax = forms.IntegerField( required=False, initial=0, widget=forms.HiddenInput()) + notes = forms.CharField(required=False, + max_length=200, label='Notes', + widget=forms.Textarea) def __init__(self, *args, **kwargs): # pragma: no cover choices = kwargs.pop('choices', []) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 063ff6f0..93366b79 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -4080,7 +4080,8 @@ def interactive_streamchart(id=0, promember=0): def instroke_interactive_chart(df,metric, workout, spm_min, spm_max, activeminutesmin, activeminutesmax, - individual_curves): + individual_curves, + name='',notes=''): df_pos = (df+abs(df))/2. @@ -4184,6 +4185,24 @@ def instroke_interactive_chart(df,metric, workout, spm_min, spm_max, plot.add_layout(label) plot.add_layout(label2) + if name: + namelabel = Label(x=50, y=480, x_units='screen', y_units='screen', + text=name, + background_fill_alpha=0.7, + background_fill_color='white', + text_color='black', + ) + plot.add_layout(namelabel) + + if notes: + noteslabel = Label(x=50, y=50, x_units='screen', y_units='screen', + text=notes, + background_fill_alpha=0.7, + background_fill_color='white', + text_color='black', + ) + plot.add_layout(noteslabel) + if individual_curves: for index,row in df.iterrows(): plot.line(xvals,row,color='lightgray',line_width=1) diff --git a/rowers/models.py b/rowers/models.py index 8e6cfba1..3881e269 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -4969,10 +4969,12 @@ class ShareKey(models.Model): class InStrokeAnalysis(models.Model): workout = models.ForeignKey(Workout, on_delete=models.CASCADE) + rower = models.ForeignKey(Rower, on_delete=models.CASCADE) + metric = models.CharField(max_length=140, blank=True, null=True) name = models.CharField(max_length=150, blank=True, null=True) date = models.DateField(blank=True, null=True) notes = models.TextField(blank=True) start_second = models.IntegerField(default=0) end_second = models.IntegerField(default=3600) - min_spm = models.IntegerField(default=10) - max_spm = models.IntegerField(default=45) + spm_min = models.IntegerField(default=10) + spm_max = models.IntegerField(default=45) diff --git a/rowers/templates/instroke_analysis.html b/rowers/templates/instroke_analysis.html new file mode 100644 index 00000000..5e98c069 --- /dev/null +++ b/rowers/templates/instroke_analysis.html @@ -0,0 +1,55 @@ +{% extends "newbase.html" %} +{% load static %} +{% load rowerfilters %} + +{% block title %}Rowsandall - Analysis {% endblock %} + +{% block main %} + +

In-Stroke Analysis for {{ rower.user.first_name }} {{ rower.user.last_name }}

+ + +
    + {% if analyses %} + {% for analysis in analyses %} +
  • +

    {{ analysis.name }}

    + + + +
    +

    + {{ analysis.notes }} +

    +

    + Workout: {{ analysis.workout }} +

    +

    + {{ analysis.spm_min }} - {{ analysis.spm_max }} SPM, + {{ analysis.start_second|secondstotimestring }} - {{ analysis.end_second|secondstotimestring }} +

    +

    + + {{ analysis.date }} + +

    +
  • + {% endfor %} + {% else %} +
  • +

    You have not saved any analyses for {{ rower.user.first_name }}

    +
  • + {% endif %} +
+ + + +{% endblock %} + +{% block scripts %} +{% endblock %} + +{% block sidebar %} +{% include 'menu_analytics.html' %} +{% endblock %} diff --git a/rowers/templates/instroke_interactive.html b/rowers/templates/instroke_interactive.html index db6aaa5d..7eec7665 100644 --- a/rowers/templates/instroke_interactive.html +++ b/rowers/templates/instroke_interactive.html @@ -143,7 +143,8 @@ $( function() {

- +

+

  • diff --git a/rowers/templates/laboratory.html b/rowers/templates/laboratory.html index 15b27e02..8eeab9c3 100644 --- a/rowers/templates/laboratory.html +++ b/rowers/templates/laboratory.html @@ -12,7 +12,7 @@

    Rower: {{ rower.user.first_name }}

    - Try out Alerts + Try out In-Stroke Analysis

    {% endblock %} diff --git a/rowers/urls.py b/rowers/urls.py index 51364854..a43558f1 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -828,6 +828,8 @@ urlpatterns = [ re_path(r'^errormessage/(?P[\w\ ]+.*)/$', views.errormessage_view, name='errormessage_view'), re_path(r'^analysis/$', views.analysis_view, name='analysis'), + re_path(r'^analysis/instrokeanalysis/$', views.instrokeanalysis_view, + name='instrokeanalysis_view'), re_path(r'^promembership', TemplateView.as_view( template_name='promembership.html'), name='promembership'), re_path(r'^checkout/(?P\d+)/$', diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index 66f96a62..6251ead6 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -1845,6 +1845,32 @@ def agegrouprecordview(request, sex='male', weightcategory='hwt', }) +@login_required +@permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True) +def instrokeanalysis_view(request, userid=0): + r = getrequestrower(request, userid=userid) + + analyses = InStrokeAnalysis.objects.filter(rower=r).order_by("-date") + + breadcrumbs = [ + { + 'url': '/rowers/analysis', + 'name': 'Analysis' + }, + { + 'url': reverse('instrokeanalysis_view'), + 'name': 'In-Stroke Analysis', + }, + ] + + return render(request, 'instroke_analysis.html', + { + 'breadcrumbs': breadcrumbs, + 'analyses': analyses, + 'rower': r, + }) + + @login_required @permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True) def alerts_view(request, userid=0): diff --git a/rowers/views/statements.py b/rowers/views/statements.py index 01709aec..628087b1 100644 --- a/rowers/views/statements.py +++ b/rowers/views/statements.py @@ -153,7 +153,7 @@ from rowers.models import ( VideoAnalysis, ShareKey, StandardCollection, CourseStandard, VirtualRaceFollower, TombStone, InstantPlan, - PlannedSessionStep, + PlannedSessionStep,InStrokeAnalysis, ) from rowers.models import ( RowerPowerForm, RowerHRZonesForm, RowerForm, RowerCPForm, GraphImage, AdvancedWorkoutForm, diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index a3155e52..1e9f7cda 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -2981,6 +2981,8 @@ def instroke_chart_interactive(request, id=0): metric = instrokemetrics[0] spm_min = 15 spm_max = 45 + name = '' + notes = '' activeminutesmax = int(rowdata.duration/60.) activeminutesmin = 0 @@ -2996,6 +2998,24 @@ def instroke_chart_interactive(request, id=0): activeminutesmin = form.cleaned_data['activeminutesmin'] activeminutesmax = form.cleaned_data['activeminutesmax'] individual_curves = form.cleaned_data['individual_curves'] + notes = form.cleaned_data['notes'] + name = form.cleaned_data['name'] + + if "_save" in request.POST: + instroke_analysis = InStrokeAnalysis( + workout = w, + metric = metric, + name = name, + date = timezone.now().date(), + notes = notes, + start_second = 60*activeminutesmin, + end_second = 60*activeminutesmax, + spm_min = spm_min, + spm_max = spm_max, + rower=w.user, + ) + instroke_analysis.save() + messages.info(request,'In-Stroke Analysis saved') activesecondsmin = 60.*activeminutesmin @@ -3016,6 +3036,7 @@ def instroke_chart_interactive(request, id=0): activeminutesmin, activeminutesmax, individual_curves, + name=name,notes=notes, ) # change to range spm_min to spm_max From 1e5b6f6cd860c059185e09efef0602f276350db6 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 7 Oct 2022 13:46:36 +0200 Subject: [PATCH 5/9] better analysis list view --- rowers/templates/instroke_analysis.html | 49 ++++++++++++++++--------- rowers/views/analysisviews.py | 2 +- static/css/rowsandall2.css | 8 ++++ 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/rowers/templates/instroke_analysis.html b/rowers/templates/instroke_analysis.html index 5e98c069..ff533b4a 100644 --- a/rowers/templates/instroke_analysis.html +++ b/rowers/templates/instroke_analysis.html @@ -12,28 +12,43 @@