From aa6574218af65537fb702bb5af901c105c56bd99 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 31 Oct 2017 20:55:22 +0100 Subject: [PATCH 1/7] removed try except around email in tasks.py --- rowers/tasks.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/rowers/tasks.py b/rowers/tasks.py index 1427f0f9..237f9be4 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -382,13 +382,10 @@ def handle_otwsetpower(f1, boattype, weightvalue, res, btvalues, res2 = utils.isbreakthrough( delta, cpvalues, ps[0], ps[1], ps[2], ps[3], ratio) if res: - try: - handle_sendemail_breakthrough( - workoutid, email, - first_name, - last_name, btvalues=btvalues.to_json()) - except: - pass + handle_sendemail_breakthrough( + workoutid, email, + first_name, + last_name, btvalues=btvalues.to_json()) # send email fullemail = first_name + " " + last_name + " " + "<" + email + ">" @@ -404,12 +401,9 @@ def handle_otwsetpower(f1, boattype, weightvalue, message += "Please report any bugs/inconsistencies/unexpected results at rowsandall.slack.com or by reply to this email.\n\n" message += "Best Regards, The Rowsandall Physics Department." - try: - send_mail(subject, message, + send_mail(subject, message, 'Rowsandall Physics Department ', [fullemail]) - except: - return 0 return 1 From fb85e5f53b8ded0900450ab068e8ce1d019b4233 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 1 Nov 2017 00:16:05 +0100 Subject: [PATCH 2/7] task progress monitoring celery works on client --- rowers/longtask.py | 61 ++++++++++++++++++++++++++++++++++++++++++++++ rowers/tasks.py | 11 +++++++-- rowers/urls.py | 1 + rowers/views.py | 35 +++++++++++++++++++++----- 4 files changed, 100 insertions(+), 8 deletions(-) create mode 100644 rowers/longtask.py diff --git a/rowers/longtask.py b/rowers/longtask.py new file mode 100644 index 00000000..0a22bf0a --- /dev/null +++ b/rowers/longtask.py @@ -0,0 +1,61 @@ +import numpy as np +import time + +from redis import StrictRedis,Redis +from celery import result as celery_result +import json + +redis_connection = StrictRedis() + +import redis +import threading + +class Listener(threading.Thread): + def __init__(self, r, channels): + threading.Thread.__init__(self) + self.redis = r + self.pubsub = self.redis.pubsub() + self.pubsub.subscribe(channels) + + def work(self, item): + print item['channel'], ":", item['data'] + + def run(self): + for item in self.pubsub.listen(): + if item['data'] == "KILL": + self.pubsub.unsubscribe() + print self, "unsubscribed and finished" + break + else: + self.work(item) + + +def longtask(aantal,jobid=None,debug=False): + if jobid: + if debug: + job = celery_result.AsyncResult(jobid) + else: + job = Job.fetch(jobid,connection=redis_connection) + counter = 0 + channel = 'tasks' + for i in range(aantal): + time.sleep(1) + counter += 1 + if counter > 10: + counter = 0 + if debug: + progress = 100.*i/aantal + print progress + if jobid != None: + redis_connection.publish(channel,json.dumps( + { + 'done':i, + 'total':aantal, + 'id':jobid, + } + )) + + + redis_connection.publish(channel,'KILL') + + return 1 diff --git a/rowers/tasks.py b/rowers/tasks.py index 237f9be4..03e77c27 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -35,6 +35,8 @@ from django.db.utils import OperationalError import datautils import utils +import longtask + # testing task @@ -42,9 +44,14 @@ import utils def add(x, y): return x + y + +@app.task(bind=True) +def long_test_task(self,aantal,debug=False,job=None): + job = self.request + print job.id + return longtask.longtask(aantal,jobid=job.id,debug=debug) + # create workout - - @app.task def handle_new_workout_from_file(r, f2, workouttype='rower', diff --git a/rowers/urls.py b/rowers/urls.py index 84a78222..f592717a 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -141,6 +141,7 @@ urlpatterns = [ url(r'^list-jobs/$',views.session_jobs_view), url(r'^jobs-status/$',views.session_jobs_status), url(r'^job-kill/(?P.*)$',views.kill_async_job), + url(r'^test-job/(?P\d+)$',views.test_job_view), url(r'^list-graphs/$',views.graphs_view), url(r'^(?P\d+)/ote-bests/(?P\w+.*)/(?P\w+.*)$',views.rankings_view), url(r'^(?P\d+)/ote-bests/(?P\d+)$',views.rankings_view), diff --git a/rowers/views.py b/rowers/views.py index 0ad77a0f..9fdbce12 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -99,7 +99,7 @@ from rowers.tasks import handle_makeplot,handle_otwsetpower,handle_sendemailtcx, from rowers.tasks import ( handle_sendemail_unrecognized,handle_sendemailnewcomment, handle_sendemailnewresponse, handle_updatedps, - handle_updatecp + handle_updatecp,long_test_task ) from scipy.signal import savgol_filter @@ -138,6 +138,12 @@ from rq import Queue,cancel_job queuefailed = Queue("failed",connection=Redis()) redis_connection = StrictRedis() +r = Redis() +from .longtask import Listener +client = Listener(r,['tasks']) +client.start() + + rq_registry = StartedJobRegistry(queue.name,connection=redis_connection) rq_registryhigh = StartedJobRegistry(queuehigh.name,connection=redis_connection) rq_registrylow = StartedJobRegistry(queuelow.name,connection=redis_connection) @@ -188,7 +194,7 @@ def remove_asynctask(request,id): request.session['async_tasks'] = newtasks def get_job_result(jobid): - if settings.EBUG: + if settings.DEBUG: result = celery_result.AsyncResult(jobid).result else: running_job_ids = rq_registry.get_job_ids() @@ -210,12 +216,16 @@ verbose_job_status = { 'updatecpwater': 'Critical Power Calculation for OTW Workouts', 'otwsetpower': 'Rowing Physics OTW Power Calculation', 'make_plot': 'Create static chart', + 'long_test_task': 'Long Test Task', } def get_job_status(jobid): if settings.DEBUG: job = celery_result.AsyncResult(jobid) jobresult = job.result + channel = 'task:<'+job.id+'>:progress' + channel = 'noot' + if 'fail' in job.status.lower(): jobresult = '0' summary = { @@ -264,7 +274,20 @@ def kill_async_job(request,id='aap'): url = reverse(session_jobs_status) return HttpResponseRedirect(url) - + +@login_required() +def test_job_view(request,aantal=100): + + job = myqueue(queuehigh,long_test_task,int(aantal)) + try: + request.session['async_tasks'] += [(job.id,'long_test_task')] + except KeyError: + request.session['async_tasks'] = [(job.id,'long_test_task')] + + url = reverse(session_jobs_status) + + return HttpResponseRedirect(url) + def get_all_queued_jobs(userid=0): r = StrictRedis() @@ -6528,7 +6551,6 @@ def workout_flexchart3_view(request,*args,**kwargs): if request.user == row.user.user: mayedit=1 - workouttype = 'ote' if row.workouttype in ('water','coastal'): workouttype = 'otw' @@ -6578,8 +6600,9 @@ def workout_flexchart3_view(request,*args,**kwargs): else: yparam2 = 'hr' - if favoritenr>=0 and r.showfavoritechartnotes: - favoritechartnotes = favorites[favoritenr].notes + if not request.user.is_anonymous(): + if favoritenr>=0 and r.showfavoritechartnotes: + favoritechartnotes = favorites[favoritenr].notes else: favoritechartnotes = '' favoritenr = 0 From ac5500a1d832d8551531d5cdee6b7c1887f8e114 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 1 Nov 2017 17:16:08 +0100 Subject: [PATCH 3/7] status progress bar works on develop --- rowers/longtask.py | 50 ++++++++--- rowers/tasks.py | 7 +- rowers/templates/async_tasks.html | 56 +++++++++++- rowers/templatetags/rowerfilters.py | 7 ++ rowers/utils.py | 3 + rowers/views.py | 129 ++++++++++++++++++++++------ rowsandall_app/settings.py | 1 + 7 files changed, 212 insertions(+), 41 deletions(-) diff --git a/rowers/longtask.py b/rowers/longtask.py index 0a22bf0a..b1ac3042 100644 --- a/rowers/longtask.py +++ b/rowers/longtask.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import numpy as np import time @@ -10,6 +11,24 @@ redis_connection = StrictRedis() import redis import threading +def getvalue(data): + perc = 0 + total = 1 + done = 0 + id = 0 + session_key = 'noot' + for i in data.iteritems(): + if i[0] == 'total': + total = float(i[1]) + if i[0] == 'done': + done = float(i[1]) + if i[0] == 'id': + id = i[1] + if i[0] == 'session_key': + session_key = i[1] + + return total,done,id,session_key + class Listener(threading.Thread): def __init__(self, r, channels): threading.Thread.__init__(self) @@ -18,8 +37,15 @@ class Listener(threading.Thread): self.pubsub.subscribe(channels) def work(self, item): - print item['channel'], ":", item['data'] - + try: + data = json.loads(item['data']) + total,done,id,session_key = getvalue(data) + perc = 100.*done/total + print perc, '%' + print session_key, id + except TypeError: + print "invalid data" + def run(self): for item in self.pubsub.listen(): if item['data'] == "KILL": @@ -30,13 +56,15 @@ class Listener(threading.Thread): self.work(item) -def longtask(aantal,jobid=None,debug=False): - if jobid: - if debug: - job = celery_result.AsyncResult(jobid) - else: - job = Job.fetch(jobid,connection=redis_connection) - counter = 0 +def longtask(aantal,jobid=None,debug=False, + session_key=None): + counter = 0 +# if jobid: +# if debug: +# job = celery_result.AsyncResult(jobid) +# else: +# job = Job.fetch(jobid,connection=redis_connection) + channel = 'tasks' for i in range(aantal): time.sleep(1) @@ -51,11 +79,11 @@ def longtask(aantal,jobid=None,debug=False): { 'done':i, 'total':aantal, - 'id':jobid, + 'id':jobid, + 'session_key':session_key, } )) - redis_connection.publish(channel,'KILL') return 1 diff --git a/rowers/tasks.py b/rowers/tasks.py index 03e77c27..ae656006 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -46,10 +46,11 @@ def add(x, y): @app.task(bind=True) -def long_test_task(self,aantal,debug=False,job=None): +def long_test_task(self,aantal,debug=False,job=None,session_key=None): job = self.request - print job.id - return longtask.longtask(aantal,jobid=job.id,debug=debug) + + return longtask.longtask(aantal,jobid=job.id,debug=debug, + session_key=session_key) # create workout @app.task diff --git a/rowers/templates/async_tasks.html b/rowers/templates/async_tasks.html index 1986acfb..dedf883d 100644 --- a/rowers/templates/async_tasks.html +++ b/rowers/templates/async_tasks.html @@ -4,6 +4,53 @@ {% block title %}Rowsandall - Tasks {% endblock %} +{% block meta %} + + + + + +{% endblock %} + {% block content %} @@ -16,7 +63,8 @@ ID - Task + Task + Progress Status Action @@ -31,6 +79,12 @@ {{ task|lookup:'verbose' }} +
+
{{ task|lookup:'progress' }}
+
+ + + {{ task|lookup:'status' }} {% if task|lookup:'failed' %} diff --git a/rowers/templatetags/rowerfilters.py b/rowers/templatetags/rowerfilters.py index 123faa9e..eebaaaf3 100644 --- a/rowers/templatetags/rowerfilters.py +++ b/rowers/templatetags/rowerfilters.py @@ -1,6 +1,8 @@ from django import template +from django.utils.safestring import mark_safe from time import strftime import dateutil.parser +import json register = template.Library() @@ -65,6 +67,11 @@ def deltatimeprint(d): return strfdeltah(d) +@register.filter(is_safe=True) +def jsdict(dict,key): + s = dict.get(key) + return mark_safe(json.dumps(s)) + @register.filter def lookup(dict, key): s = dict.get(key) diff --git a/rowers/utils.py b/rowers/utils.py index b6b81eec..03c59140 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -5,6 +5,7 @@ import colorsys from django.conf import settings + lbstoN = 4.44822 landingpages = ( @@ -226,3 +227,5 @@ def myqueue(queue,function,*args,**kwargs): job = queue.enqueue(function,*args,**kwargs) return job + + diff --git a/rowers/views.py b/rowers/views.py index 9fdbce12..adfe26fd 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -14,6 +14,9 @@ from django.views.generic.base import TemplateView from django.db.models import Q from django import template from django.db import IntegrityError, transaction +#from django.contrib.sessions.backends.db import SessionStore +from importlib import import_module +from django.contrib.sessions.models import Session from django.shortcuts import render from django.http import ( HttpResponse, HttpResponseRedirect, @@ -130,17 +133,71 @@ import django_rq queue = django_rq.get_queue('default') queuelow = django_rq.get_queue('low') queuehigh = django_rq.get_queue('low') - + +import redis +import threading from redis import StrictRedis,Redis from rq.exceptions import NoSuchJobError from rq.registry import StartedJobRegistry from rq import Queue,cancel_job +from django.core.cache import cache + +# Redis related +session_engine = import_module(settings.SESSION_ENGINE) + + +def getvalue(data): + perc = 0 + total = 1 + done = 0 + id = 0 + session_key = 'noot' + for i in data.iteritems(): + if i[0] == 'total': + total = float(i[1]) + if i[0] == 'done': + done = float(i[1]) + if i[0] == 'id': + id = i[1] + if i[0] == 'session_key': + session_key = i[1] + + return total,done,id,session_key + +class SessionTaskListener(threading.Thread): + def __init__(self, r, channels): + threading.Thread.__init__(self) + self.redis = r + self.pubsub = self.redis.pubsub() + self.pubsub.subscribe(channels) + + def work(self, item): + + try: + data = json.loads(item['data']) + total,done,id,session_key = getvalue(data) + perc = 100.*done/total + cache.set(id,perc) + + except TypeError: + pass + + def run(self): + for item in self.pubsub.listen(): + if item['data'] == "KILL": + self.pubsub.unsubscribe() + print self, "unsubscribed and finished" + break + else: + self.work(item) + + queuefailed = Queue("failed",connection=Redis()) redis_connection = StrictRedis() r = Redis() -from .longtask import Listener -client = Listener(r,['tasks']) + +client = SessionTaskListener(r,['tasks']) client.start() @@ -187,9 +244,8 @@ def remove_asynctask(request,id): newtasks = [] for task in oldtasks: - print task[0] if id not in task[0]: - newtasks += [(task[0],task[1])] + newtasks += [(task[0],task[1],task[2])] request.session['async_tasks'] = newtasks @@ -223,8 +279,6 @@ def get_job_status(jobid): if settings.DEBUG: job = celery_result.AsyncResult(jobid) jobresult = job.result - channel = 'task:<'+job.id+'>:progress' - channel = 'noot' if 'fail' in job.status.lower(): jobresult = '0' @@ -271,6 +325,7 @@ def kill_async_job(request,id='aap'): pass remove_asynctask(request,id) + cache.delete(id) url = reverse(session_jobs_status) return HttpResponseRedirect(url) @@ -278,11 +333,16 @@ def kill_async_job(request,id='aap'): @login_required() def test_job_view(request,aantal=100): - job = myqueue(queuehigh,long_test_task,int(aantal)) + session_key = request.session._session_key + + job = myqueue(queuehigh,long_test_task,int(aantal), + session_key=session_key) + + try: - request.session['async_tasks'] += [(job.id,'long_test_task')] + request.session['async_tasks'] += [(job.id,'long_test_task',0)] except KeyError: - request.session['async_tasks'] = [(job.id,'long_test_task')] + request.session['async_tasks'] = [(job.id,'long_test_task',0)] url = reverse(session_jobs_status) @@ -333,15 +393,32 @@ def get_stored_tasks_status(request): except KeyError: taskids = [] - taskstatus = [{ - 'id':id, - 'status':get_job_status(id)['status'], - 'failed':get_job_status(id)['failed'], - 'finished':get_job_status(id)['finished'], - 'func_name':func_name, - 'verbose': verbose_job_status[func_name] - } for id,func_name in taskids] + taskstatus = [] + for id,func_name,session_progress in taskids: + progress = 0 + cached_progress = cache.get(id) + finished = get_job_status(id)['finished'] + if finished: + cache.set(id,100) + progress = 100 + elif cached_progress>0: + progress = cached_progress + else: + progress = session_progress + this_task_status = { + 'id':id, + 'status':get_job_status(id)['status'], + 'failed':get_job_status(id)['failed'], + 'finished':get_job_status(id)['finished'], + 'func_name':func_name, + 'verbose': verbose_job_status[func_name], + 'progress': progress, + } + + taskstatus.append(this_task_status) + + return taskstatus @login_required() @@ -3268,9 +3345,9 @@ def otwrankings_view(request,theuser=0, ) request.session['job_id'] = job.id try: - request.session['async_tasks'] += [(job.id,'updatecpwater')] + request.session['async_tasks'] += [(job.id,'updatecpwater',0)] except KeyError: - request.session['async_tasks'] = [(job.id,'updatecpwater')] + request.session['async_tasks'] = [(job.id,'updatecpwater',0)] messages.info(request,'New calculation queued. Refresh page or resubmit the date form to get the result') powerdf = pd.DataFrame({ @@ -3525,9 +3602,9 @@ def oterankings_view(request,theuser=0, ) request.session['job_id'] = job.id try: - request.session['async_tasks'] += [(job.id,'updatecp')] + request.session['async_tasks'] += [(job.id,'updatecp',0)] except KeyError: - request.session['async_tasks'] = [(job.id,'updatecp')] + request.session['async_tasks'] = [(job.id,'updatecp',0)] messages.info(request,'New calculation queued.') powerdf = pd.DataFrame({ @@ -5659,9 +5736,9 @@ def workout_otwsetpower_view(request,id=0,message="",successmessage=""): ratio=r.cpratio) try: - request.session['async_tasks'] += [(job.id,'otwsetpower')] + request.session['async_tasks'] += [(job.id,'otwsetpower',0)] except KeyError: - request.session['async_tasks'] = [(job.id,'otwsetpower')] + request.session['async_tasks'] = [(job.id,'otwsetpower',0)] successmessage = 'Your calculations have been submitted. You will receive an email when they are done. You can check the status of your calculations here' messages.info(request,successmessage) @@ -7437,9 +7514,9 @@ def workout_add_chart_view(request,id,plotnr=1): imagename=imagename ) try: - request.session['async_tasks'] += [(jobid,'make_plot')] + request.session['async_tasks'] += [(jobid,'make_plot',0)] except KeyError: - request.session['async_tasks'] = [(jobid,'make_plot')] + request.session['async_tasks'] = [(jobid,'make_plot',0)] try: url = request.session['referer'] diff --git a/rowsandall_app/settings.py b/rowsandall_app/settings.py index beadcf86..7f8a63b2 100644 --- a/rowsandall_app/settings.py +++ b/rowsandall_app/settings.py @@ -287,6 +287,7 @@ RQ_QUEUES = { #SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies" #SESSION_ENGINE = "django.contrib.sessions.backends.cached_db" SESSION_ENGINE = "django.contrib.sessions.backends.cache" +SESSION_SAVE_EVERY_REQUEST = True # admin stuff for error reporting SERVER_EMAIL='admin@rowsandall.com' From 1847ab37e390da0a2fd4c3413df18ace19d50013 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 1 Nov 2017 17:34:46 +0100 Subject: [PATCH 4/7] cleaned up --- rowers/longtask.py | 31 ------------------------------- rowers/views.py | 36 +++++++++++++++--------------------- 2 files changed, 15 insertions(+), 52 deletions(-) diff --git a/rowers/longtask.py b/rowers/longtask.py index b1ac3042..e0033967 100644 --- a/rowers/longtask.py +++ b/rowers/longtask.py @@ -29,41 +29,11 @@ def getvalue(data): return total,done,id,session_key -class Listener(threading.Thread): - def __init__(self, r, channels): - threading.Thread.__init__(self) - self.redis = r - self.pubsub = self.redis.pubsub() - self.pubsub.subscribe(channels) - - def work(self, item): - try: - data = json.loads(item['data']) - total,done,id,session_key = getvalue(data) - perc = 100.*done/total - print perc, '%' - print session_key, id - except TypeError: - print "invalid data" - - def run(self): - for item in self.pubsub.listen(): - if item['data'] == "KILL": - self.pubsub.unsubscribe() - print self, "unsubscribed and finished" - break - else: - self.work(item) def longtask(aantal,jobid=None,debug=False, session_key=None): counter = 0 -# if jobid: -# if debug: -# job = celery_result.AsyncResult(jobid) -# else: -# job = Job.fetch(jobid,connection=redis_connection) channel = 'tasks' for i in range(aantal): @@ -73,7 +43,6 @@ def longtask(aantal,jobid=None,debug=False, counter = 0 if debug: progress = 100.*i/aantal - print progress if jobid != None: redis_connection.publish(channel,json.dumps( { diff --git a/rowers/views.py b/rowers/views.py index adfe26fd..0a83f959 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -14,9 +14,7 @@ from django.views.generic.base import TemplateView from django.db.models import Q from django import template from django.db import IntegrityError, transaction -#from django.contrib.sessions.backends.db import SessionStore -from importlib import import_module -from django.contrib.sessions.models import Session + from django.shortcuts import render from django.http import ( HttpResponse, HttpResponseRedirect, @@ -143,10 +141,6 @@ from rq import Queue,cancel_job from django.core.cache import cache -# Redis related -session_engine = import_module(settings.SESSION_ENGINE) - - def getvalue(data): perc = 0 total = 1 @@ -178,7 +172,7 @@ class SessionTaskListener(threading.Thread): data = json.loads(item['data']) total,done,id,session_key = getvalue(data) perc = 100.*done/total - cache.set(id,perc) + cache.set(id,perc,3600) except TypeError: pass @@ -245,7 +239,7 @@ def remove_asynctask(request,id): newtasks = [] for task in oldtasks: if id not in task[0]: - newtasks += [(task[0],task[1],task[2])] + newtasks += [(task[0],task[1])] request.session['async_tasks'] = newtasks @@ -340,9 +334,9 @@ def test_job_view(request,aantal=100): try: - request.session['async_tasks'] += [(job.id,'long_test_task',0)] + request.session['async_tasks'] += [(job.id,'long_test_task')] except KeyError: - request.session['async_tasks'] = [(job.id,'long_test_task',0)] + request.session['async_tasks'] = [(job.id,'long_test_task')] url = reverse(session_jobs_status) @@ -394,7 +388,7 @@ def get_stored_tasks_status(request): taskids = [] taskstatus = [] - for id,func_name,session_progress in taskids: + for id,func_name in taskids: progress = 0 cached_progress = cache.get(id) finished = get_job_status(id)['finished'] @@ -404,7 +398,7 @@ def get_stored_tasks_status(request): elif cached_progress>0: progress = cached_progress else: - progress = session_progress + progress = 0 this_task_status = { 'id':id, @@ -3345,9 +3339,9 @@ def otwrankings_view(request,theuser=0, ) request.session['job_id'] = job.id try: - request.session['async_tasks'] += [(job.id,'updatecpwater',0)] + request.session['async_tasks'] += [(job.id,'updatecpwater')] except KeyError: - request.session['async_tasks'] = [(job.id,'updatecpwater',0)] + request.session['async_tasks'] = [(job.id,'updatecpwater')] messages.info(request,'New calculation queued. Refresh page or resubmit the date form to get the result') powerdf = pd.DataFrame({ @@ -3602,9 +3596,9 @@ def oterankings_view(request,theuser=0, ) request.session['job_id'] = job.id try: - request.session['async_tasks'] += [(job.id,'updatecp',0)] + request.session['async_tasks'] += [(job.id,'updatecp')] except KeyError: - request.session['async_tasks'] = [(job.id,'updatecp',0)] + request.session['async_tasks'] = [(job.id,'updatecp')] messages.info(request,'New calculation queued.') powerdf = pd.DataFrame({ @@ -5736,9 +5730,9 @@ def workout_otwsetpower_view(request,id=0,message="",successmessage=""): ratio=r.cpratio) try: - request.session['async_tasks'] += [(job.id,'otwsetpower',0)] + request.session['async_tasks'] += [(job.id,'otwsetpower')] except KeyError: - request.session['async_tasks'] = [(job.id,'otwsetpower',0)] + request.session['async_tasks'] = [(job.id,'otwsetpower')] successmessage = 'Your calculations have been submitted. You will receive an email when they are done. You can check the status of your calculations here' messages.info(request,successmessage) @@ -7514,9 +7508,9 @@ def workout_add_chart_view(request,id,plotnr=1): imagename=imagename ) try: - request.session['async_tasks'] += [(jobid,'make_plot',0)] + request.session['async_tasks'] += [(jobid,'make_plot')] except KeyError: - request.session['async_tasks'] = [(jobid,'make_plot',0)] + request.session['async_tasks'] = [(jobid,'make_plot')] try: url = request.session['referer'] From e1a2c509e7a54c3a935b6d361daf64bedf5934f8 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 1 Nov 2017 17:37:11 +0100 Subject: [PATCH 5/7] percentage as int --- rowers/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rowers/views.py b/rowers/views.py index 0a83f959..7f0729ec 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -171,7 +171,7 @@ class SessionTaskListener(threading.Thread): try: data = json.loads(item['data']) total,done,id,session_key = getvalue(data) - perc = 100.*done/total + perc = int(100.*done/total) cache.set(id,perc,3600) except TypeError: From 75ce5612891650d98565bf278982ffcf7eacf25e Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 1 Nov 2017 19:57:49 +0100 Subject: [PATCH 6/7] fixed bug in flex chart --- rowers/templates/workout_form.html | 4 ++-- rowers/views.py | 3 +++ static/img/ajax_loader_blue_350.gif | Bin 0 -> 33210 bytes 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 static/img/ajax_loader_blue_350.gif diff --git a/rowers/templates/workout_form.html b/rowers/templates/workout_form.html index 26edc221..6c9c6b25 100644 --- a/rowers/templates/workout_form.html +++ b/rowers/templates/workout_form.html @@ -147,14 +147,14 @@ {% elif forloop.counter == 3 %} diff --git a/rowers/views.py b/rowers/views.py index 7f0729ec..a56b1f81 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -6672,8 +6672,11 @@ def workout_flexchart3_view(request,*args,**kwargs): yparam2 = 'hr' if not request.user.is_anonymous(): + r = getrower(request.user) if favoritenr>=0 and r.showfavoritechartnotes: favoritechartnotes = favorites[favoritenr].notes + else: + favoritechartnotes = '' else: favoritechartnotes = '' favoritenr = 0 diff --git a/static/img/ajax_loader_blue_350.gif b/static/img/ajax_loader_blue_350.gif new file mode 100644 index 0000000000000000000000000000000000000000..4d45f5fbf1611f77aa9a27d36262c9e84e31b3f0 GIT binary patch literal 33210 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 z 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 N@VFAxXT&J-zW^Np<{JP2 literal 0 HcmV?d00001 From 1a91612142d512a9acf75725f9d3e3734fbefd1a Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 1 Nov 2017 20:10:07 +0100 Subject: [PATCH 7/7] replaced waiting png with animation --- rowers/templates/dashboard.html | 12 +++--- rowers/templates/list_graphs.html | 4 +- rowers/templates/panel_statcharts.html | 2 +- rowers/templates/workout_comments.html | 12 +++--- rowers/templates/workout_form.html | 8 ++-- rowers/templates/workout_view.html | 51 +++++++++++++------------- 6 files changed, 45 insertions(+), 44 deletions(-) diff --git a/rowers/templates/dashboard.html b/rowers/templates/dashboard.html index 757a852d..8c37989b 100644 --- a/rowers/templates/dashboard.html +++ b/rowers/templates/dashboard.html @@ -54,14 +54,14 @@ {% elif forloop.counter == 3 %} @@ -69,7 +69,7 @@ {% endif %} @@ -81,14 +81,14 @@ {% elif forloop.counter == 3 %} @@ -96,7 +96,7 @@ {% endif %} diff --git a/rowers/templates/list_graphs.html b/rowers/templates/list_graphs.html index 82415789..dc05fc5e 100644 --- a/rowers/templates/list_graphs.html +++ b/rowers/templates/list_graphs.html @@ -23,7 +23,7 @@

{{ graph.filename }}

{{ graph.workout.name }}

@@ -46,7 +46,7 @@

{{ graph.filename }}

{{ graph.workout.name }}

diff --git a/rowers/templates/panel_statcharts.html b/rowers/templates/panel_statcharts.html index e1e44ea9..ac1f0a52 100644 --- a/rowers/templates/panel_statcharts.html +++ b/rowers/templates/panel_statcharts.html @@ -4,7 +4,7 @@ {% endfor %} diff --git a/rowers/templates/workout_comments.html b/rowers/templates/workout_comments.html index f9dbfe7c..d806ef63 100644 --- a/rowers/templates/workout_comments.html +++ b/rowers/templates/workout_comments.html @@ -80,14 +80,14 @@ {% elif forloop.counter == 3 %} @@ -95,7 +95,7 @@ {% endif %} @@ -107,14 +107,14 @@ {% elif forloop.counter == 3 %} @@ -122,7 +122,7 @@ {% endif %} diff --git a/rowers/templates/workout_form.html b/rowers/templates/workout_form.html index 6c9c6b25..3aa03f88 100644 --- a/rowers/templates/workout_form.html +++ b/rowers/templates/workout_form.html @@ -162,7 +162,7 @@ {% endif %} @@ -174,14 +174,14 @@ {% elif forloop.counter == 3 %} @@ -189,7 +189,7 @@ {% endif %} diff --git a/rowers/templates/workout_view.html b/rowers/templates/workout_view.html index ad3069c1..00b070f5 100644 --- a/rowers/templates/workout_view.html +++ b/rowers/templates/workout_view.html @@ -94,30 +94,31 @@
-

Images linked to this workout

- {% if graphs1 %} - - {% for graph in graphs1 %} - {% if forloop.counter == 1 %} -
+

Images linked to this workout

+ {% if graphs1 %} + + {% for graph in graphs1 %} + {% if forloop.counter == 1 %} +
+ + {{ graph.filename }} +
+ {% elif forloop.counter == 3 %} +
+ + {{ graph.filename }} +
+ + {% else %} +
{{ graph.filename }} -
- {% elif forloop.counter == 3 %} -
- - {{ graph.filename }} -
- - {% else %} -
- - {{ graph.filename }}
{% endif %} @@ -129,14 +130,14 @@ {% elif forloop.counter == 3 %} @@ -144,7 +145,7 @@ {% endif %}