diff --git a/rowers/forms.py b/rowers/forms.py
index 8610dab5..76e6d66d 100644
--- a/rowers/forms.py
+++ b/rowers/forms.py
@@ -10,7 +10,7 @@ from django.contrib.admin.widgets import FilteredSelectMultiple
from rowers.models import (
Workout, Rower, Team, PlannedSession, GeoCourse,
VirtualRace, VirtualRaceResult, IndoorVirtualRaceResult,
- PaidPlan
+ PaidPlan, InStrokeAnalysis
)
from rowers.rows import validate_file_extension, must_be_csv, validate_image_extension, validate_kml
from django.contrib.auth.forms import UserCreationForm
@@ -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', [])
@@ -1246,6 +1250,13 @@ class WorkoutSingleSelectForm(forms.Form):
self.fields['workout'].queryset = workouts
+class InStrokeMultipleCompareForm(forms.Form):
+ analyses = forms.ModelMultipleChoiceField(
+ queryset=InStrokeAnalysis.objects.all(),
+ widget=forms.CheckboxSelectMultiple()
+ )
+
+
class WorkoutMultipleCompareForm(forms.Form):
workouts = forms.ModelMultipleChoiceField(
queryset=Workout.objects.filter(),
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index 063ff6f0..771a5d79 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -5,7 +5,7 @@ from rowers.metrics import rowingmetrics, metricsdicts
from scipy.spatial import ConvexHull, Delaunay
from scipy.stats import linregress, percentileofscore
from pytz import timezone as tz, utc
-from rowers.models import course_spline, VirtualRaceResult
+from rowers.models import course_spline, VirtualRaceResult, InStrokeAnalysis
from bokeh.palettes import Category20c, Category10
from bokeh.layouts import layout, widgetbox
from bokeh.resources import CDN, INLINE
@@ -4078,9 +4078,85 @@ def interactive_streamchart(id=0, promember=0):
return [script, div]
+def instroke_multi_interactive_chart(selected):
+ df_plot = pd.DataFrame()
+ ids = [analysis.id for analysis in selected]
+ for analysis in selected:
+ #start_second, end_second, spm_min, spm_max, name
+ activeminutesmin = int(analysis.start_second/60.)
+ activeminutesmax = int(analysis.end_second/60.)
+ rowdata = rrdata(csvfile=analysis.workout.csvfilename)
+ data = rowdata.get_instroke_data(
+ analysis.metric,
+ spm_min=analysis.spm_min,
+ spm_max=analysis.spm_max,
+ activeminutesmin=activeminutesmin,
+ activeminutesmax=activeminutesmax,
+ )
+ mean_vals = data.mean()
+ xvals = np.arange(len(mean_vals))
+ xname = 'x_'+str(analysis.id)
+ yname = 'y_'+str(analysis.id)
+ df_plot[xname] = xvals
+ df_plot[yname] = mean_vals
+
+ source = ColumnDataSource(
+ df_plot
+ )
+
+ TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair'
+ plot = Figure(plot_width=920,tools=TOOLS,
+ toolbar_location='above',
+ toolbar_sticky=False)
+
+ plot.sizing_mode = 'stretch_both'
+
+ # add watermark
+ watermarkurl = "/static/img/logo7.png"
+
+ watermarkrange = Range1d(start=0, end=1)
+ watermarkalpha = 0.6
+ watermarkx = 0.99
+ watermarky = 0.01
+ watermarkw = 184
+ watermarkh = 35
+ watermarkanchor = 'bottom_right'
+ plot.extra_y_ranges = {"watermark": watermarkrange}
+ plot.extra_x_ranges = {"watermark": watermarkrange}
+
+ plot.image_url([watermarkurl], watermarkx, watermarky,
+ watermarkw, watermarkh,
+ global_alpha=watermarkalpha,
+ w_units='screen',
+ h_units='screen',
+ anchor=watermarkanchor,
+ dilate=True,
+ x_range_name="watermark",
+ y_range_name="watermark",
+ )
+
+ colors = itertools.cycle(palette)
+
+ try:
+ items = itertools.izip(ids, colors)
+ except AttributeError:
+ items = zip(ids, colors)
+
+ for id, color in items:
+ xname = 'x_'+str(id)
+ yname = 'y_'+str(id)
+ analysis = InStrokeAnalysis.objects.get(id=id)
+ plot.line(xname,yname,source=source,legend_label=analysis.name,
+ line_width=2, color=color)
+
+ script, div = components(plot)
+
+ return (script, div)
+
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 +4260,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 2eeefe77..acfea0e0 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
@@ -1481,6 +1489,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
@@ -4955,3 +4966,21 @@ 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)
+ 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)
+ spm_min = models.IntegerField(default=10)
+ spm_max = models.IntegerField(default=45)
+
+ def __str__(self):
+ s = 'In-Stroke Analysis {name} ({date})'.format(name = self.name,
+ date = self.date)
+
+ return s
diff --git a/rowers/tasks.py b/rowers/tasks.py
index bff93d3e..01f5bc7b 100644
--- a/rowers/tasks.py
+++ b/rowers/tasks.py
@@ -106,6 +106,7 @@ NK_API_LOCATION = CFG["nk_api_location"]
TP_CLIENT_ID = CFG["tp_client_id"]
TP_CLIENT_SECRET = CFG["tp_client_secret"]
+
from requests_oauthlib import OAuth1, OAuth1Session
import pandas as pd
@@ -386,7 +387,7 @@ def instroke_static(w, metric, debug=False, **kwargs):
@app.task
def handle_request_post(url, data, debug=False, **kwargs): # pragma: no cover
if 'localhost' in url:
- url = 'http'+url[5:]
+ url = 'http'+url[4:]
response = requests.post(url, data, verify=False)
dologging('upload_api.log', data)
dologging('upload_api.log', response.status_code)
@@ -1715,6 +1716,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/analysis.html b/rowers/templates/analysis.html
index b1e8ea47..555f58bf 100644
--- a/rowers/templates/analysis.html
+++ b/rowers/templates/analysis.html
@@ -131,18 +131,6 @@
Need to monitor a metric? Set up automatic alerting and see the reports for your workouts.
-
-
- Ranking Pieces
-
-
-

-
-
-
- Analyze your Concept2 ranking pieces over a date range and predict your pace on other pieces.
-
Histogram
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/templates/instroke_analysis.html b/rowers/templates/instroke_analysis.html
new file mode 100644
index 00000000..c63273d3
--- /dev/null
+++ b/rowers/templates/instroke_analysis.html
@@ -0,0 +1,94 @@
+{% extends "newbase.html" %}
+{% load static %}
+{% load rowerfilters %}
+
+{% block title %}Rowsandall - Analysis {% endblock %}
+
+{% block main %}
+
+{{ js_res | safe }}
+{{ css_res| safe }}
+
+
+
+
+
+{{ the_script |safe }}
+
+In-Stroke Analysis for {{ rower.user.first_name }} {{ rower.user.last_name }}
+
+
+
+
+
+{% 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/instrokeanalysis_delete_confirm.html b/rowers/templates/instrokeanalysis_delete_confirm.html
new file mode 100644
index 00000000..9af367be
--- /dev/null
+++ b/rowers/templates/instrokeanalysis_delete_confirm.html
@@ -0,0 +1,29 @@
+{% extends "newbase.html" %}
+{% load static %}
+
+{% block title %}In-Stroke Analysis{% endblock %}
+
+{% block main %}
+Confirm Delete
+This will permanently delete the analysis
+
+
+
+
+
+{% endblock %}
+
+{% block sidebar %}
+{% include 'menu_analytics.html' %}
+{% endblock %}
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/templates/menu_analytics.html b/rowers/templates/menu_analytics.html
index 2ad76f72..03bde62e 100644
--- a/rowers/templates/menu_analytics.html
+++ b/rowers/templates/menu_analytics.html
@@ -51,11 +51,6 @@
Marker Workouts
-
-
-
- Ranking Pieces
-
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()
diff --git a/rowers/urls.py b/rowers/urls.py
index 51364854..e73b05a7 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -252,6 +252,10 @@ urlpatterns = [
path('403/', TemplateView.as_view(template_name='403.html'), name='403'),
re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/instroke/interactive/$',
views.instroke_chart_interactive, name='instroke_chart_interactive'),
+ re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/instroke/interactive/(?P\d+)/$',
+ views.instroke_chart_interactive, name='instroke_chart_interactive'),
+ re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/instroke/interactive/(?P\d+)/user/(?P\d+)/$',
+ views.instroke_chart_interactive, name='instroke_chart_interactive'),
re_path(r'^exportallworkouts/?/$', views.workouts_summaries_email_view,
name='workouts_summaries_email_view'),
path('failedjobs/', views.failed_queue_view, name='failed_queue_view'),
@@ -828,6 +832,10 @@ 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'^analysis/instrokeanalysis/(?P\d+)/delete/$',
+ views.InStrokeAnalysisDelete.as_view(), name='instroke_analysis_delete_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..83b496a7 100644
--- a/rowers/views/analysisviews.py
+++ b/rowers/views/analysisviews.py
@@ -1845,6 +1845,105 @@ def agegrouprecordview(request, sex='male', weightcategory='hwt',
})
+@user_passes_test(ispromember, login_url="/rowers/paidplans",
+ message="This functionality requires a Pro plan or higher."
+ " If you are already a Pro user, please log in to access this functionality",
+ redirect_field_name=None)
+@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","-id")
+
+ script = ""
+ div = ""
+
+ if request.method == 'POST':
+ form = InStrokeMultipleCompareForm(request.POST)
+
+ if form.is_valid():
+ cd = form.cleaned_data
+ selected = cd['analyses']
+ request.session['analyses'] = [a.id for a in selected]
+ # now should redirect to analysis
+ script, div = instroke_multi_interactive_chart(selected)
+
+ 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,
+ 'the_script': script,
+ 'the_div': div,
+ })
+
+#instroke analysis delete view
+class InStrokeAnalysisDelete(DeleteView):
+ login_required = True
+ model = InStrokeAnalysis
+ template_name = 'instrokeanalysis_delete_confirm.html'
+
+ # extra parameters
+ def get_context_data(self, **kwargs):
+ context = super(InStrokeAnalysisDelete, self).get_context_data(**kwargs)
+
+ if 'userid' in kwargs: # pragma: no cover
+ userid = kwargs['userid']
+ else:
+ userid = 0
+
+ context['rower'] = getrequestrower(self.request, userid=userid)
+ context['alert'] = self.object
+
+ breadcrumbs = [
+ {
+ 'url': '/rowers/analysis',
+ 'name': 'Analysis'
+ },
+ {
+ 'url': reverse('instrokeanalysis_view'),
+ 'name': 'In-Stroke Analysis',
+ },
+ {
+ 'url': reverse('instroke_chart_interactive',
+ kwargs={'userid': userid,
+ 'id': encoder.encode_hex(self.object.workout.id),
+ 'analysis': self.object.pk}),
+ 'name': self.object.name,
+ },
+ {
+ 'url': reverse('instroke_analysis_delete_view', kwargs={'pk': self.object.pk}),
+ 'name': 'Delete'
+ }
+ ]
+
+ context['breadcrumbs'] = breadcrumbs
+
+ return context
+
+ def get_success_url(self):
+ return reverse('instrokeanalysis_view')
+
+ def get_object(self, *args, **kwargs):
+ obj = super(InStrokeAnalysisDelete, self).get_object(*args, **kwargs)
+
+ if obj.rower != self.request.user.rower:
+ raise PermissionDenied("You are not allowed to delete this Analysis")
+
+ return obj
+
+
@login_required
@permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True)
def alerts_view(request, userid=0):
@@ -1877,8 +1976,6 @@ def alerts_view(request, userid=0):
})
# alert create view
-
-
@user_passes_test(ispromember, login_url="/rowers/paidplans",
message="This functionality requires a Pro plan or higher."
" If you are already a Pro user, please log in to access this functionality",
@@ -2133,8 +2230,6 @@ def alert_edit_view(request, id=0, userid=0):
})
# alert delete view
-
-
class AlertDelete(DeleteView):
login_required = True
model = Alert
diff --git a/rowers/views/racesviews.py b/rowers/views/racesviews.py
index 3ced8b20..4e926444 100644
--- a/rowers/views/racesviews.py
+++ b/rowers/views/racesviews.py
@@ -1894,20 +1894,24 @@ def virtualevent_addboat_view(request, id=0):
followers = VirtualRaceFollower.objects.filter(race=race)
- for follower in followers:
+ for follower in followers:
othername = ''
if follower.user:
othername = follower.user.first_name+' '+follower.user.last_name
registeredname = r.user.first_name+' '+r.user.last_name
email = follower.emailaddress
- if follower.user.id not in registereduserids:
- _ = myqueue(
- queue,
- handle_sendemail_raceregistration,
- email, othername,
- registeredname, race.name, race.id,
- )
+ try:
+ if follower.user.id not in registereduserids:
+ _ = myqueue(
+ queue,
+ handle_sendemail_raceregistration,
+ email, othername,
+ registeredname, race.name, race.id,
+ )
+ registereduserids.append(follower.user.id)
+ except AttributeError:
+ pass
url = reverse('virtualevent_view',
kwargs={
diff --git a/rowers/views/statements.py b/rowers/views/statements.py
index 01709aec..4a1e3f01 100644
--- a/rowers/views/statements.py
+++ b/rowers/views/statements.py
@@ -111,7 +111,7 @@ from rowers.forms import (
VideoAnalysisMetricsForm, SurveyForm, HistorySelectForm,
StravaChartForm, FitnessFitForm, PerformanceManagerForm,
TrainingPlanBillingForm, InstantPlanSelectForm,
- TrainingZonesForm, InstrokeForm
+ TrainingZonesForm, InstrokeForm, InStrokeMultipleCompareForm
)
from django.urls import reverse, reverse_lazy
@@ -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..469a6d4f 100644
--- a/rowers/views/workoutviews.py
+++ b/rowers/views/workoutviews.py
@@ -2915,11 +2915,17 @@ def instroke_chart(request, id=0, metric=''): # pragma: no cover
return HttpResponseRedirect(url)
+@user_passes_test(ispromember, login_url="/rowers/paidplans",
+ message="This functionality requires a Pro plan or higher."
+ " If you are already a Pro user, please log in to access this functionality",
+ redirect_field_name=None)
@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True)
-def instroke_chart_interactive(request, id=0):
+def instroke_chart_interactive(request, id=0, analysis=0, userid=0):
is_ajax = request_is_ajax(request)
+ r = getrequestrower(request, userid=userid)
+
w = get_workoutuser(id, request)
rowdata = rrdata(csvfile=w.csvfilename)
@@ -2975,17 +2981,64 @@ def instroke_chart_interactive(request, id=0):
'maxminutes': maxminutes,
})
+ if analysis:
+ try:
+ instroke_analysis = InStrokeAnalysis.objects.get(id=analysis)
+ if instroke_analysis.rower != r:
+ analysis = 0
+ messages.error(request,'Access to this saved analysis denied')
+ raise ValueError
+ if instroke_analysis.workout != w:
+ messages.error(request,'This saved analysis belongs to a different workout')
+ form = InstrokeForm(
+ choices=instrokemetrics,
+ initial={
+ 'metric':instroke_analysis.metric,
+ 'name': instroke_analysis.name,
+ 'notes': instroke_analysis.notes,
+ 'activeminutesmin':int(instroke_analysis.start_second/60.),
+ 'activeminutesmax':int(instroke_analysis.end_second/60.),
+ 'spm_min': instroke_analysis.spm_min,
+ 'spm_max': instroke_analysis.spm_max,
+ }
+ )
+ metric = instroke_analysis.metric
+ name = instroke_analysis.name
+ notes = instroke_analysis.notes
+ activeminutesmin = int(instroke_analysis.start_second/60.)
+ activeminutesmax = int(instroke_analysis.end_second/60.)
+ spm_min = instroke_analysis.spm_min
+ spm_max = instroke_analysis.spm_max
+ except (InStrokeAnalysis.DoesNotExist, ValueError):
+ metric = instrokemetrics[0]
+ spm_min = 15
+ spm_max = 45
+ name = ''
+ notes = ''
+ activeminutesmax = int(rowdata.duration/60.)
+ activeminutesmin = 0
+
+ else:
+
+ metric = instrokemetrics[0]
+
+ spm_min = 15
+ spm_max = 45
+ name = ''
+ notes = ''
+
+ activeminutesmax = int(rowdata.duration/60.)
+ activeminutesmin = 0
+
+ maxminutes = int(rowdata.duration/60.)
+ individual_curves = False
+
+
+
script = ''
div = get_call()
- metric = instrokemetrics[0]
- spm_min = 15
- spm_max = 45
- activeminutesmax = int(rowdata.duration/60.)
- activeminutesmin = 0
- maxminutes = activeminutesmax
- individual_curves = False
if request.method == 'POST':
form = InstrokeForm(request.POST,choices=instrokemetrics)
@@ -2996,6 +3049,37 @@ 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:
+ if not analysis:
+ 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,
+ )
+ else:
+ instroke_analysis.workout = w
+ instroke_analysis.metric = metric
+ instroke_analysis.name = name
+ instroke_analysis.date = timezone.now().date()
+ instroke_analysis.notes = notes
+ instroke_analysis.start_second = 60*activeminutesmin
+ instroke_analysis.end_second = 60*activeminutesmax
+ instroke_analysis.spm_min = spm_min
+ instroke_analysis.spm_max = spm_max
+ instroke_analysis.rower=w.user
+
+ instroke_analysis.save()
+ messages.info(request,'In-Stroke Analysis saved')
activesecondsmin = 60.*activeminutesmin
@@ -3016,6 +3100,7 @@ def instroke_chart_interactive(request, id=0):
activeminutesmin,
activeminutesmax,
individual_curves,
+ name=name,notes=notes,
)
# change to range spm_min to spm_max
diff --git a/static/css/rowsandall2.css b/static/css/rowsandall2.css
index a157f4ae..b58cf043 100644
--- a/static/css/rowsandall2.css
+++ b/static/css/rowsandall2.css
@@ -395,6 +395,14 @@ th.rotate > div > span {
margin: 0px;
}
+.analysiscontainer {
+ display: grid;
+ grid-template-columns: 50px repeat(auto-fit, minmax(calc((100% - 100px)/7), 1fr));
+ /* grid-template-columns: 50px repeat(auto-fit, minmax(100px, 1fr)) 50px; ????*/
+ padding: 5px;
+ margin: 0px;
+}
+
.workoutelement {
margin-left: auto;
margin-right: auto;