diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 42bd8d9e..3cf6b670 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -19,7 +19,7 @@ from bokeh.layouts import layout,widgetbox from bokeh.layouts import row as layoutrow from bokeh.layouts import column as layoutcolumn from bokeh.models import LinearAxis,LogAxis,Range1d,DatetimeTickFormatter,HoverTool -from bokeh.io import output_file, show +from bokeh.io import output_file, show, vplot from bokeh.models import ( GMapPlot, GMapOptions, ColumnDataSource, Circle, DataRange1d, PanTool, WheelZoomTool, BoxSelectTool, @@ -1604,10 +1604,6 @@ def interactive_multiple_compare_chart(ids,xparam,yparam,plottype='line', if datadf.empty: return ['','

No non-zero data in selection

','',''] - - - - if xparam != 'distance' and xparam != 'time' and xparam != 'cumdist': xaxmax = yaxmaxima[xparam] xaxmin = yaxminima[xparam] @@ -1673,17 +1669,29 @@ def interactive_multiple_compare_chart(ids,xparam,yparam,plottype='line', group ) + TIPS = OrderedDict([ + ('time','@ftime'), + ('pace','@fpace'), + ('hr','@hr'), + ('spm','@spm{1.1}'), + ('distance','@distance{5}'), + ]) + + hover = plot.select(type=HoverTool) + hover.tooltips = TIPS + if labeldict: legend=labeldict[id] else: legend=str(id) if plottype=='line': - plot.line('x','y',source=source,color=color,legend=legend) + l1 = plot.line('x','y',source=source,color=color,legend=legend) else: - plot.scatter('x','y',source=source,color=color,legend=legend, + l1 = plot.scatter('x','y',source=source,color=color,legend=legend, fill_alpha=0.4,line_color=None) + plot.add_tools(HoverTool(renderers=[l1],tooltips=TIPS)) cntr += 1 plot.legend.location='bottom_right' @@ -1725,7 +1733,7 @@ def interactive_multiple_compare_chart(ids,xparam,yparam,plottype='line', def interactive_comparison_chart(id1=0,id2=0,xparam='distance',yparam='spm', - promember=0,plottype='line'): + promember=0,plottype='line'): @@ -1743,8 +1751,8 @@ def interactive_comparison_chart(id1=0,id2=0,xparam='distance',yparam='spm', rowdata2 = dataprep.getsmallrowdata_db(columns,ids=[id2]) row1 = Workout.objects.get(id=id1) row2 = Workout.objects.get(id=id2) - - + + if rowdata1.empty: return "","CSV Data File Not Found" else: @@ -1797,32 +1805,39 @@ def interactive_comparison_chart(id1=0,id2=0,xparam='distance',yparam='spm', TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' - data = pd.DataFrame( + data1 = pd.DataFrame( dict( x1=x1, - x2=x2, y1=y1, - y2=y2, ftime1=ftime1, - ftime2=ftime2, fpace1=fpace1, - fpace2=fpace2, hr1 = hr1, - hr2 = hr2, spm1 = spm1, - spm2 = spm2, distance1=distance1, + ) + ).dropna() + + data2 = pd.DataFrame( + dict( + x2=x2, + y2=y2, + ftime2=ftime2, + fpace2=fpace2, + hr2 = hr2, + spm2 = spm2, distance2=distance2, ) ).dropna() - - source = ColumnDataSource( - data + + source1 = ColumnDataSource( + data1 ) - + source2 = ColumnDataSource( + data2 + ) # create interactive plot plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type, @@ -1830,22 +1845,52 @@ def interactive_comparison_chart(id1=0,id2=0,xparam='distance',yparam='spm', plot_width=920, toolbar_sticky=False) + TIPS = OrderedDict([ + ('time','@ftime1'), + ('pace','@fpace1'), + ('hr','@hr1'), + ('spm','@spm1{1.1}'), + ('distance','@distance1{5}'), + ]) + TIPS2 = OrderedDict([ + ('time','@ftime2'), + ('pace','@fpace2'), + ('hr','@hr2'), + ('spm','@spm2{1.1}'), + ('distance','@distance2{5}'), + ]) + + + hover1 = plot.select(type=HoverTool) + hover1.tooltips = TIPS + hover2 = plot.select(type=HoverTool) + hover2.tooltips = TIPS2 + + if plottype=='line': - plot.line('x1','y1',source=source,color="blue",legend=row1.name) - plot.line('x2','y2',source=source,color="red",legend=row2.name) + l1 = plot.line('x1','y1',source=source1, + color="blue",legend=row1.name, + ) + l2 = plot.line('x2','y2',source=source2, + color="red",legend=row2.name, + ) elif plottype=='scatter': - plot.scatter('x1','y1',source=source,legend=row1.name,fill_alpha=0.4, - line_color=None) - plot.scatter('x2','y2',source=source,legend=row2.name,fill_alpha=0.4, - line_color=None,color="red") + l1 = plot.scatter('x1','y1',source=source1,legend=row1.name, + fill_alpha=0.4, + line_color=None) + l2 = plot.scatter('x2','y2',source=source2,legend=row2.name, + fill_alpha=0.4, + line_color=None,color="red") + plot.add_tools(HoverTool(renderers=[l1],tooltips=TIPS)) + plot.add_tools(HoverTool(renderers=[l2],tooltips=TIPS2)) plot.legend.location = "bottom_right" plot.title.text = row1.name+' vs '+row2.name plot.title.text_font_size=value("1.2em") plot.xaxis.axis_label = axlabels[xparam] - plot.yaxis.axis_label = axlabels[yparam] + if xparam == 'time': plot.xaxis[0].formatter = DatetimeTickFormatter( @@ -1863,27 +1908,10 @@ def interactive_comparison_chart(id1=0,id2=0,xparam='distance',yparam='spm', seconds = ["%S"], minutes = ["%M"] ) + plot.y_range = Range1d(ymin,ymax) - #plot.y_range = Range1d(ymin,ymax) - hover = plot.select(dict(type=HoverTool)) - - - hover.tooltips = OrderedDict([ - ('time1','@ftime1'), - ('time2','@ftime2'), - ('pace1','@fpace1'), - ('pace2','@fpace2'), - ('hr1','@hr1'), - ('hr2','@hr2'), - ('spm1','@spm1{1.1}'), - ('spm2','@spm2{1.1}'), - ('distance1','@distance1{5}'), - ('distance2','@distance2{5}'), - ]) - - hover.mode = 'mouse' - + script, div = components(plot) return [script,div] diff --git a/rowers/models.py b/rowers/models.py index 11a13f40..e9dc8228 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -891,6 +891,7 @@ class WorkoutComment(models.Model): comment = models.TextField(max_length=300) created = models.DateTimeField(default=timezone.now) read = models.BooleanField(default=False) + notification = models.BooleanField(default=False,verbose_name="Send me notifications") user = models.ForeignKey(User) workout = models.ForeignKey(Workout) @@ -905,7 +906,7 @@ class WorkoutComment(models.Model): class WorkoutCommentForm(ModelForm): class Meta: model = WorkoutComment - fields = ['comment',] + fields = ['comment','notification'] widgets = { 'comment': forms.Textarea, } diff --git a/rowers/tasks.py b/rowers/tasks.py index e0367862..40d8747b 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -273,6 +273,37 @@ def handle_sendemail_invite(email,name,code,teamname,manager): return 1 +@app.task +def handle_sendemailnewresponse(first_name,last_name, + email, + commenter_first_name, + commenter_last_name, + comment, + workoutname,workoutid,commentid): + fullemail = first_name+' '+last_name+' <'+email+'>' + subject = 'New comment on workout '+workoutname + message = 'Dear '+first_name+',\n\n' + message += commenter_first_name+' '+commenter_last_name + message += ' has written a new comment on the workout ' + message += workoutname+'\n\n' + message += comment + message += '\n\n' + message += 'You can read the comment here:\n' + message += 'https://rowsandall.com/rowers/workout/'+str(workoutid)+'/comment' + message += '\n\n' + message += 'You are receiving this email because you are subscribed ' + message += 'to comments on this workout. To unsubscribe, follow this link:\n' + message += 'https://rowsandall.com/rowers/workout/unsubscribe/'+str(workoutid)+'/' + + email = EmailMessage(subject, message, + 'Rowsandall ', + [fullemail]) + + + res = email.send() + + return 1 + @app.task def handle_sendemailnewcomment(first_name, last_name, diff --git a/rowers/templates/team_compare_select.html b/rowers/templates/team_compare_select.html index d0f36f9a..c52bcb1f 100644 --- a/rowers/templates/team_compare_select.html +++ b/rowers/templates/team_compare_select.html @@ -9,7 +9,7 @@
- {% include "teambuttons.html" with teamid=teamid%} + {% include "teambuttons.html" with teamid=team.id %}
diff --git a/rowers/templates/workout_comments.html b/rowers/templates/workout_comments.html index 438388a5..f9dbfe7c 100644 --- a/rowers/templates/workout_comments.html +++ b/rowers/templates/workout_comments.html @@ -57,7 +57,7 @@ {% endfor %}
-
+ {{ form.as_table }}
diff --git a/rowers/urls.py b/rowers/urls.py index c756414f..24945f8e 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -151,6 +151,7 @@ urlpatterns = [ url(r'^workout/upload/(.+.*)$',views.workout_upload_view), url(r'^workout/(?P\d+)/histo$',views.workout_histo_view), url(r'^workout/(?P\d+)/forcecurve$',views.workout_forcecurve_view), + url(r'^workout/(?P\d+)/unsubscribe$',views.workout_unsubscribe_view), url(r'^workout/(?P\d+)/export/c/(?P\w+.*)/s/(?P\w+.*)$',views.workout_export_view), url(r'^workout/(?P\d+)/export/c/(?P\w+.*)$',views.workout_export_view), url(r'^workout/(?P\d+)/export/s/(?P\w+.*)$',views.workout_export_view), diff --git a/rowers/views.py b/rowers/views.py index 929edfb7..aae91ae6 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -60,7 +60,8 @@ from rest_framework.parsers import JSONParser from rowers.rows import handle_uploaded_file from rowers.tasks import handle_makeplot,handle_otwsetpower,handle_sendemailtcx,handle_sendemailcsv from rowers.tasks import ( - handle_sendemail_unrecognized,handle_sendemailnewcomment + handle_sendemail_unrecognized,handle_sendemailnewcomment, + handle_sendemailnewresponse, ) from scipy.signal import savgol_filter @@ -2064,10 +2065,6 @@ def multi_compare_view(request): if result: promember=1 - if 'ids' in request.session: - print request.session['ids'] - - print request.POST if request.method == 'POST' and 'workouts' in request.POST: form = WorkoutMultipleCompareForm(request.POST) @@ -3586,6 +3583,37 @@ def workout_export_view(request,id=0, message="", successmessage=""): 'c2userid':c2userid, }) +# +@login_required() +def workout_unsubscribe_view(request,id=0): + try: + w = Workout.objects.get(id=id) + except Workout.DoesNotExist: + raise Http404("Workout doesn't exist") + + if w.privacy == 'private' and w.user.user != request.user: + return HttpResponseForbidden("Permission error") + + comments = WorkoutComment.objects.filter(workout=w, + user=request.user).order_by("created") + + for c in comments: + c.notify = False + c.save() + + form = WorkoutCommentForm() + + message = 'You have been unsubscribed from new comment notifications for this workout' + + return render(request, + 'workout_comments.html', + {'workout':w, + 'comments':comments, + 'successmessage':message, + 'form':form, + }) + + # list of comments to a workout @login_required() def workout_comment_view(request,id=0): @@ -3597,6 +3625,8 @@ def workout_comment_view(request,id=0): if w.privacy == 'private' and w.user.user != request.user: return HttpResponseForbidden("Permission error") + comments = WorkoutComment.objects.filter(workout=w).order_by("created") + # ok we're permitted if request.method == 'POST': r = w.user @@ -3604,7 +3634,9 @@ def workout_comment_view(request,id=0): if form.is_valid(): cd = form.cleaned_data comment = cd['comment'] - c = WorkoutComment(workout=w,user=request.user,comment=comment) + notification = cd['notification'] + c = WorkoutComment(workout=w,user=request.user,comment=comment, + notification=notification) c.save() if settings.DEBUG: res = handle_sendemailnewcomment.delay(r.user.first_name, @@ -3615,6 +3647,7 @@ def workout_comment_view(request,id=0): comment,w.name, w.id) + elif request.user != r.user: res = queuehigh.enqueue(handle_sendemailnewcomment,r.user.first_name, r.user.last_name, @@ -3622,9 +3655,30 @@ def workout_comment_view(request,id=0): request.user.first_name, request.user.last_name, comment,w.name,w.id) - - comments = WorkoutComment.objects.filter(workout=w).order_by("created") + commenters = {oc.user for oc in comments if oc.notification} + for u in commenters: + if settings.DEBUG: + res = handle_sendemailnewresponse.delay(u.first_name, + u.last_name, + u.email, + request.user.first_name, + request.user.last_name, + comment, + w.name, + w.id, + c.id) + else: + res = queuelow.enqueue(handle_sendemailnewresponse, + u.first_name, + u.last_name, + u.email, + request.user.first_name, + request.user.last_name, + comment, + w.name, + w.id, + c.id) form = WorkoutCommentForm() diff --git a/static/css/rowsandall.css b/static/css/rowsandall.css index 09e1f881..24a757de 100644 --- a/static/css/rowsandall.css +++ b/static/css/rowsandall.css @@ -799,9 +799,10 @@ a.wh:hover { /* talk bubble contents */ .talktext{ - padding: 1em; - text-align: left; - line-height: 1.5em; + padding: 1em; + text-align: left; + line-height: 1.5em; + word-wrap: break-word; } .talktext p{ /* remove webkit p margins */