From 6b793724edceff2763abab08794ef3b9a5bd00a8 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Fri, 27 Oct 2017 11:52:47 +0200
Subject: [PATCH 1/4] redis monitor for CP update jobs
Stores job id in a cookie. Upon reload, checks if job is still busy. If
busy, no new jobs are started. Some messaging for users to give them
information about the job status.
---
rowers/celery.py | 3 +
rowers/dataprep.py | 15 +++--
rowers/models.py | 9 +++
rowers/templates/otwrankings.html | 16 +++++-
rowers/views.py | 96 +++++++++++++++++++++++++++++--
5 files changed, 125 insertions(+), 14 deletions(-)
diff --git a/rowers/celery.py b/rowers/celery.py
index a9865a5d..8ecc91a9 100644
--- a/rowers/celery.py
+++ b/rowers/celery.py
@@ -3,6 +3,7 @@ from __future__ import absolute_import
import os
from celery import Celery
+from celery import result
# Only used for testing with Celery on localhost. RQ is not available
# on Windows, so I use Celery on my notebook.
@@ -17,6 +18,7 @@ app = Celery('tasks',
broker='redis://localhost',
backend='redis://localhost',)
+
class Config:
CELERY_TIMEZONE = 'Europe/Prague'
@@ -31,3 +33,4 @@ database_url = 'sqlite:///db.sqlite3'
@app.task(bind=True)
def debug_task(self):
print('Request: {0!r}'.format(self.request))
+
diff --git a/rowers/dataprep.py b/rowers/dataprep.py
index c604b265..8799f25c 100644
--- a/rowers/dataprep.py
+++ b/rowers/dataprep.py
@@ -473,9 +473,11 @@ def updatecpdata_sql(rower_id,delta,cp,table='cpdata',distance=[]):
engine.dispose()
-def runcpupdate(rower,type='water'):
- startdate = timezone.now()-datetime.timedelta(days=365)
- enddate = timezone.now()+datetime.timedelta(days=5)
+def runcpupdate(
+ rower,type='water',
+ startdate=timezone.now()-datetime.timedelta(days=365),
+ enddate=timezone.now()+datetime.timedelta(days=5)
+):
if type == 'water':
theworkouts = Workout.objects.filter(
user=rower,rankingpiece=True,
@@ -499,11 +501,12 @@ def runcpupdate(rower,type='water'):
theids = [w.id for w in theworkouts]
-
if settings.DEBUG:
- res = handle_updatecp.delay(rower.id,theids,debug=True,table=table)
+ job = handle_updatecp.delay(rower.id,theids,debug=True,table=table)
else:
- res = queue.enqueue(handle_updatecp,rower.id,theids,table=table)
+ job = queue.enqueue(handle_updatecp,rower.id,theids,table=table)
+
+ return job
def fetchcperg(rower,theworkouts):
theids = [int(w.id) for w in theworkouts]
diff --git a/rowers/models.py b/rowers/models.py
index bfc190cc..ef3d25e0 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -263,6 +263,15 @@ class Rower(models.Model):
p2 = models.FloatField(default=1.0,verbose_name="CP p3")
p3 = models.FloatField(default=1.0,verbose_name="CP p4")
cpratio = models.FloatField(default=1.0,verbose_name="CP fit ratio")
+
+
+ ep0 = models.FloatField(default=1.0,verbose_name="erg CP p1")
+ ep1 = models.FloatField(default=1.0,verbose_name="erg CP p2")
+ ep2 = models.FloatField(default=1.0,verbose_name="erg CP p3")
+ ep3 = models.FloatField(default=1.0,verbose_name="erg CP p4")
+ ecpratio = models.FloatField(default=1.0,verbose_name="erg CP fit ratio")
+
+
otwslack = models.IntegerField(default=0,verbose_name="OTW Power slack")
diff --git a/rowers/templates/otwrankings.html b/rowers/templates/otwrankings.html
index d31eed1c..35c6069d 100644
--- a/rowers/templates/otwrankings.html
+++ b/rowers/templates/otwrankings.html
@@ -50,7 +50,11 @@
{% for member in user|team_members %}
+ {% if workouttype == 'water' %}
{{ member.first_name }} {{ member.last_name }}
+ {% else %}
+
{{ member.first_name }} {{ member.last_name }}
+ {% endif %}
{% endfor %}
{% else %}
@@ -64,14 +68,22 @@
between {{ startdate|date }} and {{ enddate|date }}
Direct link for other users:
- https://rowsandall.com/rowers/{{ id }}/otw-bests/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}
+ {% if workouttype == 'water' %}
+ https://rowsandall.com/rowers/{{ id }}/otw-bests/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}
+ {% else %}
+ https://rowsandall.com/rowers/{{ id }}/ote-ranking/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}
+ {% endif %}
- The table gives the OTW efforts you marked as Ranking Piece.
+
The table gives the efforts you marked as Ranking Piece.
The graph shows the best segments from those pieces, plotted as
average power (over the segment) vs the duration of the segment/
In other words: How long you can hold that power.
+
+ When you change the date range, the algorithm calculates new
+ parameters in a background process. You may have to reload the
+ page to get an updated prediction.
At the bottom of the page, you will find predictions derived from the model.
diff --git a/rowers/views.py b/rowers/views.py
index d5acc762..ac1a4750 100644
--- a/rowers/views.py
+++ b/rowers/views.py
@@ -131,6 +131,16 @@ queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('low')
+from redis import StrictRedis
+from rq.exceptions import NoSuchJobError
+from rq.registry import StartedJobRegistry
+from rq import Queue
+
+redis_connection = StrictRedis()
+rq_registry = StartedJobRegistry(queue.name,connection=redis_connection)
+
+from rq.job import Job
+
from rest_framework_swagger.views import get_swagger_view
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
@@ -155,10 +165,30 @@ from scipy.interpolate import griddata
USER_LANGUAGE = 'en-US'
from interactiveplots import *
+from rowers.celery import result as celery_result
# Define the API documentation
schema_view = get_swagger_view(title='Rowsandall API')
+def get_job_result(jobid):
+ if settings.DEBUG:
+ result = celery_result.AsyncResult(jobid).result
+ else:
+ running_job_ids = rq_registry.get_job_ids()
+ if len(running_job_ids) and jobid in running_job_ids:
+ # job is running
+ return None
+ else:
+ # job is ready
+ try:
+ job = Job.fetch(jobid,connection=redis_connection)
+ result = job.result
+ except NoSuchJobError:
+ return None
+
+ return result
+
+
# Test if row data include candidates
def rowhascoordinates(row):
# create interactive plot
@@ -3076,6 +3106,32 @@ def otwrankings_view(request,theuser=0,
delta,cpvalue,avgpower = dataprep.fetchcp(r,theworkouts)
+
+ runningjob = 0
+
+ try:
+ jobid = request.session['job_id']
+ if jobid:
+ result = get_job_result(jobid)
+ if result:
+ messages.info(request,'Your calculation is ready')
+ runningjob = 1
+ request.session['job_id'] = 0
+ else:
+ runningjob = 1
+ messages.info(request,'Your job is still running')
+ except KeyError:
+ pass
+
+
+ if not runningjob:
+ job = dataprep.runcpupdate(
+ r,type='water',
+ startdate=startdate,
+ enddate=enddate
+ )
+ request.session['job_id'] = job.id
+ messages.info(request,'New calculation queued. Refresh page or resubmit the date form to get the result')
powerdf = pd.DataFrame({
'Delta':delta,
@@ -3181,6 +3237,7 @@ def otwrankings_view(request,theuser=0,
'startdate':startdate,
'enddate':enddate,
'teams':get_my_teams(request.user),
+ 'workouttype':'water',
})
# Show ranking distances including predicted paces
@user_passes_test(ispromember,login_url="/",redirect_field_name=None)
@@ -3298,7 +3355,33 @@ def oterankings_view(request,theuser=0,
delta,cpvalue,avgpower = dataprep.fetchcp(
r,theworkouts,table='cpergdata'
)
+
+ runningjob = 0
+ try:
+ jobid = request.session['job_id']
+ if jobid:
+ result = get_job_result(jobid)
+ if result:
+ messages.info(request,'Your calculation is ready')
+ runningjob = 1
+ request.session['job_id'] = 0
+ else:
+ runningjob = 1
+ messages.info(request,'Your job is still running')
+ except KeyError:
+ pass
+
+
+ if not runningjob:
+ job = dataprep.runcpupdate(
+ r,type='rower',
+ startdate=startdate,
+ enddate=enddate
+ )
+ request.session['job_id'] = job.id
+ messages.info(request,'New calculation queued. Refresh page or resubmit the date form to get the result')
+
powerdf = pd.DataFrame({
'Delta':delta,
'CP':cpvalue,
@@ -3320,11 +3403,11 @@ def oterankings_view(request,theuser=0,
div = res[1]
p1 = res[2]
ratio = res[3]
- r.p0 = p1[0]
- r.p1 = p1[1]
- r.p2 = p1[2]
- r.p3 = p1[3]
- r.cpratio = ratio
+ r.ep0 = p1[0]
+ r.ep1 = p1[1]
+ r.ep2 = p1[2]
+ r.ep3 = p1[3]
+ r.ecpratio = ratio
r.save()
paulslope = 1
paulintercept = 1
@@ -3403,6 +3486,7 @@ def oterankings_view(request,theuser=0,
'startdate':startdate,
'enddate':enddate,
'teams':get_my_teams(request.user),
+ 'workouttype':'rower',
})
# Reload the workout and calculate the summary from the stroke data (lapIDx)
@@ -5427,7 +5511,7 @@ def workout_otwsetpower_view(request,id=0,message="",successmessage=""):
ratio=r.cpratio)
else:
res = queuelow.enqueue(handle_otwsetpower,f1,boattype,
- weightvalue,
+ weightvalue,
first_name,last_name,emailaddress,id,
ps=[r.p0,r.p1,r.p2,r.p3],
ratio=r.cpratio)
From 60447b8d30683be1de45b2c0761070f0f87f30b7 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Sun, 29 Oct 2017 20:52:24 +0100
Subject: [PATCH 2/4] default landing page toggle button on workflow config
---
rowers/templates/show_graph.html | 11 ++++++++++-
rowers/templates/workflowconfig2.html | 16 ++++++++++++++--
rowers/urls.py | 1 +
rowers/views.py | 22 +++++++++++++++++++---
4 files changed, 44 insertions(+), 6 deletions(-)
diff --git a/rowers/templates/show_graph.html b/rowers/templates/show_graph.html
index c14bd873..00cdab68 100644
--- a/rowers/templates/show_graph.html
+++ b/rowers/templates/show_graph.html
@@ -43,7 +43,16 @@
{% endif %}
-
+
+ {% if user.is_authenticated and user == rower.user %}
+
+ Workflow View
+
+ {% else %}
+
+ {% endif %}
+
+
{% if user.is_authenticated and user == rower.user %}
Delete Chart
diff --git a/rowers/templates/workflowconfig2.html b/rowers/templates/workflowconfig2.html
index 86b1e7f5..abdfe052 100644
--- a/rowers/templates/workflowconfig2.html
+++ b/rowers/templates/workflowconfig2.html
@@ -21,7 +21,7 @@
On this page, you can configure the content of your "Workflow" page
for each workout. If you want to remove an element, change it to "None".
You can add one new element at a time.
-
+
Left Panel
-
+
Middle Panel
+
+
+ {% if rower.defaultlandingpage == 'workout_edit_view' %}
+
Default landing page is Edit View. Set default landing page to
+
Workflow View
+ {% else %}
+
Default landing page is Workflow View. Set default landing page to
+
Edit View
+ {% endif %}
+
+
+
diff --git a/rowers/urls.py b/rowers/urls.py
index 5ba765db..9a4653f9 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -320,6 +320,7 @@ urlpatterns = [
url(r'^me/favoritecharts/$',views.rower_favoritecharts_view),
url(r'^me/workflowconfig$',views.workout_workflow_config_view),
url(r'^me/workflowconfig2$',views.workout_workflow_config2_view),
+ url(r'^me/workflowdefault$',views.workflow_default_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/views.py b/rowers/views.py
index ac1a4750..858cbba5 100644
--- a/rowers/views.py
+++ b/rowers/views.py
@@ -6147,7 +6147,23 @@ def workout_comparison_view2(request,id1=0,id2=0,xparam='distance',
'plottype':plottype,
'promember':promember,
})
-
+
+# Change default landing page
+@login_required()
+def workflow_default_view(request):
+ r = getrower(request.user)
+ if r.defaultlandingpage == 'workout_edit_view':
+ r.defaultlandingpage = 'workout_workflow_view'
+ else:
+ r.defaultlandingpage = 'workout_edit_view'
+
+ r.save()
+
+ url = reverse(workout_workflow_config2_view)
+
+ return HttpResponseRedirect(url)
+
+
# Workflow Configuration
@login_required()
def workout_workflow_config_view(request):
@@ -6257,10 +6273,10 @@ def workout_workflow_config2_view(request):
tmplt = 'workflowconfig2.html'
-
+
return render(request,tmplt,
{
- 'rower':r,
+ 'rower':getrower(request.user),
'leftpanel_formset':leftpanel_formset,
'middlepanel_formset':middlepanel_formset,
'workoutid': workoutid,
From 13fc8e1fed262251e01a6e724e4816ec44eafe44 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Sun, 29 Oct 2017 21:00:40 +0100
Subject: [PATCH 3/4] import now defaults to default landing page
---
rowers/views.py | 20 +++++++++++++++-----
1 file changed, 15 insertions(+), 5 deletions(-)
diff --git a/rowers/views.py b/rowers/views.py
index 858cbba5..76a705e1 100644
--- a/rowers/views.py
+++ b/rowers/views.py
@@ -7660,7 +7660,9 @@ def workout_getstravaworkout_view(request,stravaid):
messages.error(request,message)
- url = reverse(workout_edit_view,
+ r = getrower(request.user)
+
+ url = reverse(r.defaultlandingpage,
kwargs = {
'id':int(id),
})
@@ -7681,7 +7683,9 @@ def workout_getrunkeeperworkout_view(request,runkeeperid):
if message:
messages.error(request,message)
- url = reverse(workout_edit_view,
+ r = getrower(request.user)
+
+ url = reverse(r.defaultlandingpage,
kwargs = {
'id':int(id),
})
@@ -7700,7 +7704,9 @@ def workout_getunderarmourworkout_view(request,underarmourid):
if message:
messages.error(request,message)
- url = reverse(workout_edit_view,
+ r = getrower(request.user)
+
+ url = reverse(r.defaultlandingpage,
kwargs = {
'id':int(id),
})
@@ -7725,7 +7731,9 @@ def workout_getsporttracksworkout_view(request,sporttracksid):
if message:
messages.error(request,message)
- url = reverse(workout_edit_view,
+ r = getrower(request.user)
+
+ url = reverse(r.defaultlandingpage,
kwargs = {
'id':int(id),
})
@@ -7869,7 +7877,9 @@ def workout_getc2workout_view(request,c2id):
if message:
messages.error(request,message)
- url = reverse(workout_edit_view,
+ r = getrower(request.user)
+
+ url = reverse(r.defaultlandingpage,
kwargs = {
'id':int(id),
})
From 40fedbe4c454295976bfb9d166d000408f77044a Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Sun, 29 Oct 2017 21:04:31 +0100
Subject: [PATCH 4/4] bug fix nr of comments on workflow middle panel
---
rowers/views.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/rowers/views.py b/rowers/views.py
index 76a705e1..e44ba678 100644
--- a/rowers/views.py
+++ b/rowers/views.py
@@ -6303,6 +6303,10 @@ def workout_workflow_view(request,id):
if request.user == row.user.user:
mayedit=1
+ comments = WorkoutComment.objects.filter(workout=row)
+
+ aantalcomments = len(comments)
+
workouttype = 'ote'
if row.workouttype in ('water','coastal'):
workouttype = 'otw'
@@ -6364,6 +6368,7 @@ def workout_workflow_view(request,id):
'mapdiv':mapdiv,
'statcharts':statcharts,
'rower':r,
+ 'aantalcomments':aantalcomments,
})
# The famous flex chart