From 983b3be86c079ff20e3ac4d32bb13b938ce39d42 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 23 Jan 2018 12:09:41 +0100 Subject: [PATCH 1/3] restricted allowable static images to 6 --- rowers/models.py | 5 +---- rowers/uploads.py | 15 ++++++++++----- rowers/views.py | 43 +++++++++++++++++++++++++++---------------- 3 files changed, 38 insertions(+), 25 deletions(-) diff --git a/rowers/models.py b/rowers/models.py index e37e35e9..7419ae54 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -479,10 +479,6 @@ class Rower(models.Model): runkeepertoken = models.CharField(default='',max_length=200, blank=True,null=True) -# runkeepertokenexpirydate = models.DateTimeField(blank=True,null=True) -# runkeeperrefreshtoken = models.CharField(default='',max_length=200, -# blank=True,null=True) - # Plan plans = ( ('basic','basic'), @@ -856,6 +852,7 @@ class GraphImage(models.Model): def __str__(self): return self.filename + # delete related file object when image is deleted @receiver(models.signals.post_delete,sender=GraphImage) def auto_delete_image_on_delete(sender,instance, **kwargs): diff --git a/rowers/uploads.py b/rowers/uploads.py index e263b7bf..02ac1837 100644 --- a/rowers/uploads.py +++ b/rowers/uploads.py @@ -280,11 +280,16 @@ def make_plot(r,w,f1,f2,plottype,title,imagename='',plotnr=0): width = 1200 height = 600 - i = GraphImage(workout=w, - creationdatetime=timezone.now(), - filename=fullpathimagename, - width=width,height=height) - i.save() + imgs = GraphImage.objects.filter(workout=w) + if len(imgs) < 7: + i = GraphImage(workout=w, + creationdatetime=timezone.now(), + filename=fullpathimagename, + width=width,height=height) + + i.save() + else: + return 0,'You have reached the maximum number of static images for this workout. Delete an image first' return i.id,job.id diff --git a/rowers/views.py b/rowers/views.py index ca4cc7db..a6d27deb 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -7112,13 +7112,17 @@ def instroke_chart(request,id=0,metric=''): width = 1200 height = 600 - i = GraphImage(workout=w, - creationdatetime=timezone.now(), - filename=fullpathimagename, - width=width,height=height) - - i.save() - print i.id,'aap' + imgs = GraphImage.objects.filter(workout=w) + if len(imgs) < 7: + i = GraphImage(workout=w, + creationdatetime=timezone.now(), + filename=fullpathimagename, + width=width,height=height) + + i.save() + else: + messages.error(request,'You have reached the maximum number of static images for this workout. Delete an image first') + r = getrower(request.user) url = reverse(r.defaultlandingpage, @@ -8855,10 +8859,13 @@ def workout_add_chart_view(request,id,plotnr=1): r,w,f1,w.csvfilename,'timeplot',title,plotnr=plotnr, imagename=imagename ) - try: - request.session['async_tasks'] += [(jobid,'make_plot')] - except KeyError: - request.session['async_tasks'] = [(jobid,'make_plot')] + if res == 0: + messages.error(request,jobid) + else: + try: + request.session['async_tasks'] += [(jobid,'make_plot')] + except KeyError: + request.session['async_tasks'] = [(jobid,'make_plot')] try: url = request.session['referer'] @@ -9739,10 +9746,13 @@ def workout_upload_view(request, r = getrower(request.user) if (make_plot): res,jobid = uploads.make_plot(r,w,f1,f2,plottype,t) - try: - request.session['async_tasks'] += [(jobid,'make_plot')] - except KeyError: - request.session['async_tasks'] = [(jobid,'make_plot')] + if res == 0: + messages.error(request,jobid) + else: + try: + request.session['async_tasks'] += [(jobid,'make_plot')] + except KeyError: + request.session['async_tasks'] = [(jobid,'make_plot')] # upload to C2 if (upload_to_c2): @@ -9988,7 +9998,8 @@ def team_workout_upload_view(request,message="", r = getrower(request.user) if (make_plot): - id = uploads.make_plot(r,w,f1,f2,plottype,t) + id,jobid = uploads.make_plot(r,w,f1,f2,plottype,t) + From 22cba46f3bc833b31651eb1bbed74f2d4437b5f3 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 23 Jan 2018 14:37:52 +0100 Subject: [PATCH 2/3] image upload working --- rowers/forms.py | 12 +- rowers/models.py | 4 +- rowers/rows.py | 40 ++++- rowers/templates/image_form.html | 252 +++++++++++++++++++++++++++++++ rowers/urls.py | 1 + rowers/views.py | 95 +++++++++++- 6 files changed, 395 insertions(+), 9 deletions(-) create mode 100644 rowers/templates/image_form.html diff --git a/rowers/forms.py b/rowers/forms.py index f029285d..5a7c4a45 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -1,7 +1,7 @@ from django import forms from django.contrib.admin.widgets import FilteredSelectMultiple from rowers.models import Workout -from rowers.rows import validate_file_extension,must_be_csv +from rowers.rows import validate_file_extension,must_be_csv,validate_image_extension from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.models import User from django.contrib.admin.widgets import AdminDateWidget @@ -46,6 +46,16 @@ class TeamInviteCodeForm(forms.Form): class StrokeDataForm(forms.Form): strokedata = forms.CharField(label='payload',widget=forms.Textarea) +# The form used for uploading images +class ImageForm(forms.Form): + file = forms.FileField(required=False, + validators=[validate_image_extension]) + + def __init__(self, *args, **kwargs): + from django.forms.widgets import HiddenInput + super(ImageForm, self).__init__(*args, **kwargs) + + # The form used for uploading files class DocumentsForm(forms.Form): title = forms.CharField(required=False) diff --git a/rowers/models.py b/rowers/models.py index 7419ae54..2838451b 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -858,7 +858,9 @@ class GraphImage(models.Model): def auto_delete_image_on_delete(sender,instance, **kwargs): if instance.filename: if os.path.isfile(instance.filename): - os.remove(instance.filename) + others = GraphImage.objects.filter(filename=instance.filename) + if len(others) == 0: + os.remove(instance.filename) else: print "couldn't find the file "+instance.filename diff --git a/rowers/rows.py b/rowers/rows.py index 51de9769..20750819 100644 --- a/rowers/rows.py +++ b/rowers/rows.py @@ -1,11 +1,11 @@ import time import gzip import shutil - +import hashlib from django.core.exceptions import ValidationError def format_pace_tick(x,pos=None): - min=int(x/60) + min=int(x/60) sec=int(x-min*60.) sec_str=str(sec).zfill(2) template='%d:%s' @@ -45,6 +45,14 @@ def format_time(x,pos=None): return str1 +def validate_image_extension(value): + import os + ext = os.path.splitext(value.name)[1].lower() + valid_extension = ['.jpg','.jpeg','.png','.gif'] + + if not ext in valid_extension: + raise ValidationError(u'File not supported') + def validate_file_extension(value): import os ext = os.path.splitext(value.name)[1] @@ -62,6 +70,34 @@ def must_be_csv(value): raise ValidationError(u'File not supported!') +def handle_uploaded_image(i): + import StringIO + from PIL import Image, ImageOps + import os + from django.core.files import File + image_str = "" + for c in i.chunks(): + image_str += c + imagefile = StringIO.StringIO(image_str) + image = Image.open(imagefile) + + if image.mode not in ("L", "RGB"): + image = image.convert("RGB") + + basewidth = 600 + wpercent = (basewidth/float(image.size[0])) + hsize = int((float(image.size[1])*float(wpercent))) + image = image.resize((basewidth,hsize), Image.ANTIALIAS) + + + + filename = hashlib.md5(imagefile.getvalue()).hexdigest()+'.jpg' + + filename2 = os.path.join('static/plots/',filename) + image.save(filename2,'JPEG') + return filename,filename2 + + def handle_uploaded_file(f): fname = f.name timestr = time.strftime("%Y%m%d-%H%M%S") diff --git a/rowers/templates/image_form.html b/rowers/templates/image_form.html new file mode 100644 index 00000000..0a77e5d2 --- /dev/null +++ b/rowers/templates/image_form.html @@ -0,0 +1,252 @@ +{% extends "base.html" %} +{% load staticfiles %} +{% load rowerfilters %} + +{% block title %}File loading{% endblock %} + +{% block meta %} + + + +{% endblock %} + +{% block content %} + +
+
+
+

Upload Image

+ {% if form.errors %} +

+ Please correct the error{{ form.errors|pluralize }} below. +

+ {% endif %} + + + {{ form.as_table }} +
+ {% csrf_token %} +
+ +
+
+ + + + +
+
+{% endblock %} + + {% block scripts %} + + + + {% endblock %} diff --git a/rowers/urls.py b/rowers/urls.py index 0b93f9b2..f2d291f9 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -249,6 +249,7 @@ urlpatterns = [ url(r'^workout/(?P\d+)/otwsetpower$',views.workout_otwsetpower_view), url(r'^workout/(?P\d+)/interactiveotwplot$',views.workout_otwpowerplot_view), url(r'^workout/(?P\d+)/wind$',views.workout_wind_view), + url(r'^workout/(?P\d+)/image$',views.workout_uploadimage_view), url(r'^workout/(?P\d+)/darkskywind$',views.workout_downloadwind_view), url(r'^workout/(?P\d+)/metar/(?P\w+)$',views.workout_downloadmetar_view), url(r'^workout/(?P\d+)/stream$',views.workout_stream_view), diff --git a/rowers/views.py b/rowers/views.py index a6d27deb..0429ecda 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -27,7 +27,7 @@ from django.http import ( ) from django.contrib.auth import authenticate, login, logout from rowers.forms import ( - LoginForm,DocumentsForm,UploadOptionsForm, + LoginForm,DocumentsForm,UploadOptionsForm,ImageForm, TeamUploadOptionsForm,WorkFlowLeftPanelForm,WorkFlowMiddlePanelForm, WorkFlowLeftPanelElement,WorkFlowMiddlePanelElement, LandingPageForm, @@ -103,7 +103,7 @@ import requests import json from rest_framework.renderers import JSONRenderer from rest_framework.parsers import JSONParser -from rowers.rows import handle_uploaded_file +from rowers.rows import handle_uploaded_file,handle_uploaded_image from rowers.tasks import handle_makeplot,handle_otwsetpower,handle_sendemailtcx,handle_sendemailcsv from rowers.tasks import ( handle_sendemail_unrecognized,handle_sendemailnewcomment, @@ -116,7 +116,7 @@ from rowers.tasks import ( from scipy.signal import savgol_filter from django.shortcuts import render_to_response from Cookie import SimpleCookie -from shutil import copyfile +from shutil import copyfile,move import types from rowingdata import rower as rrower from rowingdata import main as rmain @@ -8836,6 +8836,92 @@ def workout_edit_view_navionics(request,id=0,message="",successmessage=""): return HttpResponseRedirect(url) +# Image upload +@login_required() +def workout_uploadimage_view(request,id): + is_ajax = False + if request.is_ajax(): + is_ajax = True + + r = getrower(request.user) + + try: + w = Workout.objects.get(id=id) + except Workout.DoesNotExist: + raise Http404("Workout doesn't exist") + + if not checkworkoutuser(request.user,w): + raise PermissionDenied("You are not allowed to edit this workout") + + images = GraphImage.objects.filter(workout=w) + + + if len(images) >= 6: + message = "You have reached the maximum number of static images for this workout" + messages.error(request,message) + url = reverse(r.defaultlandingpage, + kwargs = { + 'id':int(id), + }) + return HttpResponseRedirect(url) + + + if request.method == 'POST': + form = ImageForm(request.POST,request.FILES) + + if form.is_valid(): + f = form.cleaned_data['file'] + + if f is not None: + filename,path_and_filename = handle_uploaded_image(f) + print path_and_filename,'aap' + try: + width,height = Image.open(path_and_filename).size + except: + message = "Not a valid image" + messages.error(request,message) + os.remove(path_and_filename) + url = reverse(workout_uploadimage_view, + kwargs = {'id':id}) + + if is_ajax: + return JSONResponse({'result':0,'url':0}) + else: + return HttpResponseRedirect(url) + + i = GraphImage(workout=w, + creationdatetime=timezone.now(), + filename = path_and_filename, + width=width,height=height) + i.save() + url = reverse(r.defaultlandingpage, + kwargs = {'id':id}) + return HttpResponseRedirect(url) + else: + messages.error(request,'Something went wrong - no file attached') + url = reverse(workout_uploadimage_view, + kwargs = {'id':id}) + + if is_ajax: + return JSONResponse({'result':0,'url':0}) + else: + return HttpResponseRedirect(url) + else: + return HttpResponse("Form is not valid") + + else: + if not is_ajax: + form = ImageForm() + return render(request,'image_form.html', + {'form':form, + 'teams':get_my_teams(request.user), + 'workout': w, + }) + else: + return {'result':0} + + + # Generic chart creation @login_required() def workout_add_chart_view(request,id,plotnr=1): @@ -9508,8 +9594,7 @@ def workout_toggle_ranking(request,id=0): raise Http404("Workout doesn't exist") if not checkworkoutuser(request.user,row): - message = "You are not allowed to change this workout" - messages.error(request,message) + raise PermissionDenied("You are not allowed to change this workout") # we are still here - we own the workout row.rankingpiece = not row.rankingpiece From ed56a4179a89a09e58981b60cc0b21ab151715de Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 23 Jan 2018 15:10:17 +0100 Subject: [PATCH 3/3] added image attach buttons to edit and workflow --- rowers/templates/.#image_form.html | 1 + rowers/templates/image_form.html | 36 ------------------------- rowers/templates/panel_uploadimage.html | 5 ++++ rowers/templates/workout_form.html | 8 ++++-- rowers/utils.py | 5 ++++ rowers/views.py | 6 ++++- 6 files changed, 22 insertions(+), 39 deletions(-) create mode 100644 rowers/templates/.#image_form.html create mode 100644 rowers/templates/panel_uploadimage.html diff --git a/rowers/templates/.#image_form.html b/rowers/templates/.#image_form.html new file mode 100644 index 00000000..7ae08c3c --- /dev/null +++ b/rowers/templates/.#image_form.html @@ -0,0 +1 @@ +E408191@CZ27LT9RCGN72.1800:1516641451 \ No newline at end of file diff --git a/rowers/templates/image_form.html b/rowers/templates/image_form.html index 0a77e5d2..32f7c725 100644 --- a/rowers/templates/image_form.html +++ b/rowers/templates/image_form.html @@ -162,42 +162,6 @@ }); }); - frm.submit(function() { - console.log("Form submission"); - $(data.values()).each(function(value) { - console.log(value); - }); - - $("#id_drop-files").replaceWith( - '
' - ); - $.ajax({ - data: data, - type: $(this).attr('method'), - url: '/rowers/workout/{{ workout.id }}/image', - contentType: false, - processData: false, - error: function(result) { - $("#id_waiting").replaceWith( - '
Your upload failed
' - ); - setTimeout(function() { - location.reload(); - },1000); - }, - success: function(result) { - console.log('got something back'); - console.log(result); - if (result.result == 1) { - window.location.href = result.url; - } else { - console.log(result," reloading"); - location.reload(); - }; - } - }); - return false; - }); $('#id_drop-files').bind({ diff --git a/rowers/templates/panel_uploadimage.html b/rowers/templates/panel_uploadimage.html new file mode 100644 index 00000000..f99652cc --- /dev/null +++ b/rowers/templates/panel_uploadimage.html @@ -0,0 +1,5 @@ +
+

+ Attach Image +

+
diff --git a/rowers/templates/workout_form.html b/rowers/templates/workout_form.html index d4e2985b..e0f87339 100644 --- a/rowers/templates/workout_form.html +++ b/rowers/templates/workout_form.html @@ -137,8 +137,12 @@

- -
+
+

+ Attach Image +

+
+

Power Pie Chart

diff --git a/rowers/utils.py b/rowers/utils.py index 300e95fa..108a670a 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -42,6 +42,7 @@ workflowleftpanel = ( ('panel_stats.html','Workout Statistics Button'), ('panel_flexchart.html','Flex Chart'), ('panel_staticchart.html','Create Static Charts Buttons'), + ('panel_uploadimage.html','Attach Image'), ('panel_geekyheader.html','Geeky Header'), ('panel_editwind.html','Edit Wind Data'), ('panel_editstream.html','Edit Stream Data'), @@ -57,6 +58,7 @@ defaultleft = [ 'panel_editintervals.html', 'panel_stats.html', 'panel_staticchart.html', + 'panel_uploadimage.html', ] coxes_calls = [ @@ -65,6 +67,9 @@ coxes_calls = [ "Almost there. Give me ten strokes on the legs!", "Let it run!", "Don't rush the slides!", + "Quick hands.", + "You are clearing the puddles.", + "Let's push through now. Get me that open water.", "We're going for the line now. Power ten on the next.", ] diff --git a/rowers/views.py b/rowers/views.py index 0429ecda..eabc67fe 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -8894,9 +8894,13 @@ def workout_uploadimage_view(request,id): filename = path_and_filename, width=width,height=height) i.save() + url = reverse(r.defaultlandingpage, kwargs = {'id':id}) - return HttpResponseRedirect(url) + if is_ajax: + return JSONResponse({'result':1,'url':0}) + else: + return HttpResponseRedirect(url) else: messages.error(request,'Something went wrong - no file attached') url = reverse(workout_uploadimage_view,