Private
Public Access
1
0

Merge branch 'release/v5.71'

This commit is contained in:
Sander Roosendaal
2018-01-23 15:24:57 +01:00
11 changed files with 418 additions and 36 deletions

View File

@@ -1,7 +1,7 @@
from django import forms from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple from django.contrib.admin.widgets import FilteredSelectMultiple
from rowers.models import Workout 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.forms import UserCreationForm
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.admin.widgets import AdminDateWidget from django.contrib.admin.widgets import AdminDateWidget
@@ -46,6 +46,16 @@ class TeamInviteCodeForm(forms.Form):
class StrokeDataForm(forms.Form): class StrokeDataForm(forms.Form):
strokedata = forms.CharField(label='payload',widget=forms.Textarea) 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 # The form used for uploading files
class DocumentsForm(forms.Form): class DocumentsForm(forms.Form):
title = forms.CharField(required=False) title = forms.CharField(required=False)

View File

@@ -479,10 +479,6 @@ class Rower(models.Model):
runkeepertoken = models.CharField(default='',max_length=200, runkeepertoken = models.CharField(default='',max_length=200,
blank=True,null=True) blank=True,null=True)
# runkeepertokenexpirydate = models.DateTimeField(blank=True,null=True)
# runkeeperrefreshtoken = models.CharField(default='',max_length=200,
# blank=True,null=True)
# Plan # Plan
plans = ( plans = (
('basic','basic'), ('basic','basic'),
@@ -856,12 +852,15 @@ class GraphImage(models.Model):
def __str__(self): def __str__(self):
return self.filename return self.filename
# delete related file object when image is deleted # delete related file object when image is deleted
@receiver(models.signals.post_delete,sender=GraphImage) @receiver(models.signals.post_delete,sender=GraphImage)
def auto_delete_image_on_delete(sender,instance, **kwargs): def auto_delete_image_on_delete(sender,instance, **kwargs):
if instance.filename: if instance.filename:
if os.path.isfile(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: else:
print "couldn't find the file "+instance.filename print "couldn't find the file "+instance.filename

View File

@@ -1,11 +1,11 @@
import time import time
import gzip import gzip
import shutil import shutil
import hashlib
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
def format_pace_tick(x,pos=None): def format_pace_tick(x,pos=None):
min=int(x/60) min=int(x/60)
sec=int(x-min*60.) sec=int(x-min*60.)
sec_str=str(sec).zfill(2) sec_str=str(sec).zfill(2)
template='%d:%s' template='%d:%s'
@@ -45,6 +45,14 @@ def format_time(x,pos=None):
return str1 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): def validate_file_extension(value):
import os import os
ext = os.path.splitext(value.name)[1] ext = os.path.splitext(value.name)[1]
@@ -62,6 +70,34 @@ def must_be_csv(value):
raise ValidationError(u'File not supported!') 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): def handle_uploaded_file(f):
fname = f.name fname = f.name
timestr = time.strftime("%Y%m%d-%H%M%S") timestr = time.strftime("%Y%m%d-%H%M%S")

View File

@@ -0,0 +1 @@
E408191@CZ27LT9RCGN72.1800:1516641451

View File

@@ -0,0 +1,216 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}File loading{% endblock %}
{% block meta %}
<script type='text/javascript'
src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'>
</script>
<script type='text/javascript'
src='https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js'>
</script>
<script>
</script>
{% endblock %}
{% block content %}
<div id="id_dropregion" class="grid_12 alpha watermark invisible">
<p>Drag and drop files here </p>
</div>
<div id="id_drop-files" class="grid_12 alpha drop-files">
<form id="file_form" enctype="multipart/form-data" action="{{ formloc }}" method="post">
<div id="left" class="grid_6 alpha">
<h1>Upload Image</h1>
{% if form.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
<table>
{{ form.as_table }}
</table>
{% csrf_token %}
<div id="formbutton" class="grid_1 prefix_4 suffix_1">
<input class="button green" type="submit" value="Submit">
</div>
</div>
<div id="right" class="grid_6 omega">
&nbsp;
</div>
</form>
</div>
{% endblock %}
{% block scripts %}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script>
var td = new FormData();
var formdatasetok = false;
try {
td.set('aap','noot');
formdatasetok = true;
console.log('FormData.set OK');
}
catch(err) {
console.log('FormData.set not OK');
formdatasetok = false;
}
if (!formdatasetok) {
$("#id_dropregion").remove();
}
if (formdatasetok) {
$(document).ready(function() {
var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
console.log("CSRF token",csrftoken);
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
console.log("Loading dropper");
jQuery.event.props.push('dataTransfer');
$(window).on('dragenter', function() {
$("#id_drop-files").css("background-color","#E9E9E4");
$("#id_dropregion").addClass("watermark").removeClass("invisible");})
$(window).on('dragleave', function() {
$("#id_drop-files").css("background-color","#FFFFFF");
$("#id_dropregion").removeClass("watermark").addClass("invisible");})
var frm = $("#file_form");
if( window.FormData === undefined ) {
console.log('no formdata');
alert("No FormData");
} else {
console.log('we have formdata');
}
var data = new FormData(frm[0]);
$('#id_file').on('change', function(evt) {
var f = this.files[0];
console.log(f);
var istcx = false;
var isgzip = false;
var size1 = 10485760;
var size2 = 1048576;
if ((/\.(tcx|TCX)/i).test(f.name)) {
istcx = true;
console.log('tcx');
if ((/\.(gz|GZ)/i).test(f.name)) {
isgzip = true;
console.log('gzip');
size1 /= 5;
size2 /= 5;
}
}
console.log(size1)
console.log(size2)
if (f.size > size1) {
alert("File Size must be smaller than 10 MB");
this.value = null;
} else {
if (f.size > size2) {
$('#id_offline').val('True');
$('#id_offline').prop('checked','True');
data.set($('#id_offline').attr('name'),$('#id_offline').prop('checked'));
console.log("Set offline to True");
}
}
});
$('input').each(function( i ) {
$(this).change(function() {
if ($(this).attr('type') == 'checkbox') {
data.set($(this).attr('name'),$(this).prop('checked'));
console.log($(this).attr('id'),$(this).attr('name'),$(this).prop('checked'));
} else {
data.set($(this).attr('name'),$(this).val());
if ($(this).attr('id') == 'id_file') {
data.set("file",this.files[0]);
}
console.log($(this).attr('name'),$(this).val());
};
});});
$('select').each(function( i ) {
console.log($(this).attr('name'),$(this).val());
$(this).change(function() {
data.set($(this).attr('name'),$(this).val());
console.log($(this).attr('id'),$(this).attr('name'),$(this).val());
});
});
$('#id_drop-files').bind({
drop: function(e) {
e.preventDefault();
console.log("you dropped something");
var files = e.dataTransfer.files;
console.log(files[0]);
var f = files[0];
var istcx = false;
var isgzip = false;
var size1 = 10485760;
var size2 = 1048576;
if ((/\.(tcx|TCX)/i).test(f.name)) {
istcx = true;
console.log('tcx');
if ((/\.(gz|GZ)/i).test(f.name)) {
isgzip = true;
console.log('gzip');
size1 /= 5;
size2 /= 5;
}
}
console.log(f);
console.log(size1)
console.log(size2)
if (f.size > size1) {
alert("File Size must be smaller than 10 MB");
$("#id_file").value = 0;
return false;
}
data.set("file",f);
// data.append("file",f);
$("#id_file").replaceWith('<div id="id_file">'+files[0].name+'&nbsp; <a class="remove" href="javascript:void(0);"><b><font color="red">X</font></b></a></div>');
},
mouseenter:function(){$("#id_drop-files").css("background-color","#E9E9E4");},
mouseleave:function(){$("#id_drop-files").css("background-color","#FFFFFF");},
dragover:function(e){
e.preventDefault();
$("#id_drop-files").css("background-color","#E9E9E4");},
dragleave:function(e){ e.preventDefault();},
});
$(document).on("click", "a.remove", function() {
$(this).parent().replaceWith('<td><input id="id_file" name="file" type="file" /></td>');
});
});
};
</script>
{% endblock %}

View File

@@ -0,0 +1,5 @@
<div class="grid_2 alpha">
<p>
<a class="button blue small" href="/rowers/workout/{{ workout.id }}/image">Attach Image</a>
</p>
</div>

View File

@@ -137,8 +137,12 @@
</p> </p>
</div> </div>
<div class="grid_6 alpha"> <div class="grid_6 alpha">
<div class="grid_2 alpha">
<div class="grid_2 prefix_4 alpha"> <p>
<a class="button blue small" href="/rowers/workout/{{ workout.id }}/image">Attach Image</a>
</p>
</div>
<div class="grid_2 prefix_2 omega">
<p> <p>
<a class="button blue small" href="/rowers/workout/{{ workout.id }}/addpowerpiechart">Power Pie Chart</a> <a class="button blue small" href="/rowers/workout/{{ workout.id }}/addpowerpiechart">Power Pie Chart</a>
</p> </p>

View File

@@ -280,11 +280,16 @@ def make_plot(r,w,f1,f2,plottype,title,imagename='',plotnr=0):
width = 1200 width = 1200
height = 600 height = 600
i = GraphImage(workout=w, imgs = GraphImage.objects.filter(workout=w)
creationdatetime=timezone.now(), if len(imgs) < 7:
filename=fullpathimagename, i = GraphImage(workout=w,
width=width,height=height) creationdatetime=timezone.now(),
i.save() 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 return i.id,job.id

View File

@@ -249,6 +249,7 @@ urlpatterns = [
url(r'^workout/(?P<id>\d+)/otwsetpower$',views.workout_otwsetpower_view), url(r'^workout/(?P<id>\d+)/otwsetpower$',views.workout_otwsetpower_view),
url(r'^workout/(?P<id>\d+)/interactiveotwplot$',views.workout_otwpowerplot_view), url(r'^workout/(?P<id>\d+)/interactiveotwplot$',views.workout_otwpowerplot_view),
url(r'^workout/(?P<id>\d+)/wind$',views.workout_wind_view), url(r'^workout/(?P<id>\d+)/wind$',views.workout_wind_view),
url(r'^workout/(?P<id>\d+)/image$',views.workout_uploadimage_view),
url(r'^workout/(?P<id>\d+)/darkskywind$',views.workout_downloadwind_view), url(r'^workout/(?P<id>\d+)/darkskywind$',views.workout_downloadwind_view),
url(r'^workout/(?P<id>\d+)/metar/(?P<airportcode>\w+)$',views.workout_downloadmetar_view), url(r'^workout/(?P<id>\d+)/metar/(?P<airportcode>\w+)$',views.workout_downloadmetar_view),
url(r'^workout/(?P<id>\d+)/stream$',views.workout_stream_view), url(r'^workout/(?P<id>\d+)/stream$',views.workout_stream_view),

View File

@@ -42,6 +42,7 @@ workflowleftpanel = (
('panel_stats.html','Workout Statistics Button'), ('panel_stats.html','Workout Statistics Button'),
('panel_flexchart.html','Flex Chart'), ('panel_flexchart.html','Flex Chart'),
('panel_staticchart.html','Create Static Charts Buttons'), ('panel_staticchart.html','Create Static Charts Buttons'),
('panel_uploadimage.html','Attach Image'),
('panel_geekyheader.html','Geeky Header'), ('panel_geekyheader.html','Geeky Header'),
('panel_editwind.html','Edit Wind Data'), ('panel_editwind.html','Edit Wind Data'),
('panel_editstream.html','Edit Stream Data'), ('panel_editstream.html','Edit Stream Data'),
@@ -57,6 +58,7 @@ defaultleft = [
'panel_editintervals.html', 'panel_editintervals.html',
'panel_stats.html', 'panel_stats.html',
'panel_staticchart.html', 'panel_staticchart.html',
'panel_uploadimage.html',
] ]
coxes_calls = [ coxes_calls = [
@@ -65,6 +67,9 @@ coxes_calls = [
"Almost there. Give me ten strokes on the legs!", "Almost there. Give me ten strokes on the legs!",
"Let it run!", "Let it run!",
"Don't rush the slides!", "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.", "We're going for the line now. Power ten on the next.",
] ]

View File

@@ -27,7 +27,7 @@ from django.http import (
) )
from django.contrib.auth import authenticate, login, logout from django.contrib.auth import authenticate, login, logout
from rowers.forms import ( from rowers.forms import (
LoginForm,DocumentsForm,UploadOptionsForm, LoginForm,DocumentsForm,UploadOptionsForm,ImageForm,
TeamUploadOptionsForm,WorkFlowLeftPanelForm,WorkFlowMiddlePanelForm, TeamUploadOptionsForm,WorkFlowLeftPanelForm,WorkFlowMiddlePanelForm,
WorkFlowLeftPanelElement,WorkFlowMiddlePanelElement, WorkFlowLeftPanelElement,WorkFlowMiddlePanelElement,
LandingPageForm, LandingPageForm,
@@ -103,7 +103,7 @@ import requests
import json import json
from rest_framework.renderers import JSONRenderer from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser 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_makeplot,handle_otwsetpower,handle_sendemailtcx,handle_sendemailcsv
from rowers.tasks import ( from rowers.tasks import (
handle_sendemail_unrecognized,handle_sendemailnewcomment, handle_sendemail_unrecognized,handle_sendemailnewcomment,
@@ -116,7 +116,7 @@ from rowers.tasks import (
from scipy.signal import savgol_filter from scipy.signal import savgol_filter
from django.shortcuts import render_to_response from django.shortcuts import render_to_response
from Cookie import SimpleCookie from Cookie import SimpleCookie
from shutil import copyfile from shutil import copyfile,move
import types import types
from rowingdata import rower as rrower from rowingdata import rower as rrower
from rowingdata import main as rmain from rowingdata import main as rmain
@@ -7112,13 +7112,17 @@ def instroke_chart(request,id=0,metric=''):
width = 1200 width = 1200
height = 600 height = 600
i = GraphImage(workout=w, imgs = GraphImage.objects.filter(workout=w)
creationdatetime=timezone.now(), if len(imgs) < 7:
filename=fullpathimagename, i = GraphImage(workout=w,
width=width,height=height) creationdatetime=timezone.now(),
filename=fullpathimagename,
i.save() width=width,height=height)
print i.id,'aap'
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) r = getrower(request.user)
url = reverse(r.defaultlandingpage, url = reverse(r.defaultlandingpage,
@@ -8832,6 +8836,96 @@ def workout_edit_view_navionics(request,id=0,message="",successmessage=""):
return HttpResponseRedirect(url) 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})
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,
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 # Generic chart creation
@login_required() @login_required()
def workout_add_chart_view(request,id,plotnr=1): def workout_add_chart_view(request,id,plotnr=1):
@@ -8855,10 +8949,13 @@ def workout_add_chart_view(request,id,plotnr=1):
r,w,f1,w.csvfilename,'timeplot',title,plotnr=plotnr, r,w,f1,w.csvfilename,'timeplot',title,plotnr=plotnr,
imagename=imagename imagename=imagename
) )
try: if res == 0:
request.session['async_tasks'] += [(jobid,'make_plot')] messages.error(request,jobid)
except KeyError: else:
request.session['async_tasks'] = [(jobid,'make_plot')] try:
request.session['async_tasks'] += [(jobid,'make_plot')]
except KeyError:
request.session['async_tasks'] = [(jobid,'make_plot')]
try: try:
url = request.session['referer'] url = request.session['referer']
@@ -9501,8 +9598,7 @@ def workout_toggle_ranking(request,id=0):
raise Http404("Workout doesn't exist") raise Http404("Workout doesn't exist")
if not checkworkoutuser(request.user,row): if not checkworkoutuser(request.user,row):
message = "You are not allowed to change this workout" raise PermissionDenied("You are not allowed to change this workout")
messages.error(request,message)
# we are still here - we own the workout # we are still here - we own the workout
row.rankingpiece = not row.rankingpiece row.rankingpiece = not row.rankingpiece
@@ -9739,10 +9835,13 @@ def workout_upload_view(request,
r = getrower(request.user) r = getrower(request.user)
if (make_plot): if (make_plot):
res,jobid = uploads.make_plot(r,w,f1,f2,plottype,t) res,jobid = uploads.make_plot(r,w,f1,f2,plottype,t)
try: if res == 0:
request.session['async_tasks'] += [(jobid,'make_plot')] messages.error(request,jobid)
except KeyError: else:
request.session['async_tasks'] = [(jobid,'make_plot')] try:
request.session['async_tasks'] += [(jobid,'make_plot')]
except KeyError:
request.session['async_tasks'] = [(jobid,'make_plot')]
# upload to C2 # upload to C2
if (upload_to_c2): if (upload_to_c2):
@@ -9988,7 +10087,8 @@ def team_workout_upload_view(request,message="",
r = getrower(request.user) r = getrower(request.user)
if (make_plot): if (make_plot):
id = uploads.make_plot(r,w,f1,f2,plottype,t) id,jobid = uploads.make_plot(r,w,f1,f2,plottype,t)