Private
Public Access
1
0

Merge branch 'release/v13.32'

This commit is contained in:
Sander Roosendaal
2020-07-11 17:15:05 +02:00
10 changed files with 570 additions and 54 deletions

View File

@@ -203,7 +203,7 @@ class ImageForm(forms.Form):
# The form used for uploading images
class CourseForm(forms.Form):
name = forms.CharField(max_length=150,label='Course Name')
name = forms.CharField(max_length=150,label='Course Name',required=False)
file = forms.FileField(required=False,
validators=[validate_kml])
notes = forms.CharField(required=False,
@@ -214,6 +214,13 @@ class CourseForm(forms.Form):
from django.forms.widgets import HiddenInput
super(CourseForm, self).__init__(*args, **kwargs)
class CourseConfirmForm(forms.Form):
BOOL_CHOICES = ((True, 'Yes'), (False, 'No'))
doupdate = forms.TypedChoiceField(
initial=False,
coerce=lambda x: x =='True', choices=((False, 'No'), (True, 'Yes')), widget=forms.RadioSelect,
label='Update Course with new markers?')
# The form used for uploading files
class StandardsForm(forms.Form):
name = forms.CharField(max_length=150,label='Course Name')

View File

@@ -121,7 +121,7 @@ def handle_uploaded_image(i):
try:
if exif[orientation] == 3:
image=image.rotate(180, expand=True)
mage=image.rotate(180, expand=True)
elif exif[orientation] == 6:
image=image.rotate(270, expand=True)
elif exif[orientation] == 8:

View File

@@ -24,7 +24,7 @@
<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">
<form id="file_form" enctype="multipart/form-data" method="post">
{% if form.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
@@ -242,6 +242,8 @@
$("#id_waiting").replaceWith(
'<div id="id_failed" class="grid_12 alpha message">Your upload failed</div>'
);
console.log(data);
setTimeout(1000);
setTimeout(function() {
location.reload();
},1000);

View File

@@ -0,0 +1,319 @@
{% extends "newbase.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 main %}
<h1>Upload KML Course File</h1>
<ul class="main-content">
<li class="grid_4">
<div id="id_dropregion 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" method="post">
{% if form.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
<table>
{{ form.as_table }}
</table>
{% csrf_token %}
<p>
<input class="button" type="submit" value="Submit">
</p>
</form>
</div>
</li>
<li class="grid_4">
<h2>How-to</h2>
<p>
Courses allow you to mark the start & finish lines of your
test pieces and measure the time spent on the course (as opposed
to the total duration of a workout). This allows you to row and rank
marked courses.
To create a course, you use <a href="https://www.google.com/earth/">Google Earth</a>
to mark the start and finish lines using polygons. The process is identical
to creating custom courses for the
<a href="http://performancephones.com/crewnerd/">CrewNerd</a>
app.
</p>
<p>CrewNerd has published a nice video tutorial of the process.
<a href="https://youtu.be/whhWFmMJbhM">Click here</a> to see the video. The part
we're interested in starts at 2:05.
</p>
<p>
In addition to start and finish areas, on rowsandall.com you can add additional
polygons to mark areas that you must pass (in that order). This allows for
courses with turns around buoys, respecting buoy lines, or respecting traffic
patterns on rivers and lakes.
</p>
<p>
<ul>
<li>Open Google Earth</li>
<li>Create a folder "Courses" under "Temporary Places" or under "My Places"</li>
<li>Create a folder for each Course under "Courses", and for each course:</li>
<li>Create Start polygon</li>
<li>Optional: Create First "must row through" polygon</li>
<li>Optional: Create subsequent "must row through" polygons</li>
<li>Create Finish polygon</li>
<li>Save "Courses" as KML file</li>
<li>Upload the file to rowsandall.com using the "Add Courses" button</li>
</ul>
</p>
<p>You are allowed to have multiple courses in one KML file.
Your CrewNerd "courses.kml" file works out of the box</p>
<p>The site doesn't test for duplicate courses.</p>
</li>
</ul>
{% endblock %}
{% block sidebar %}
{% include 'menu_racing.html' %}
{% 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());
console.log(data)
};
});});
$('textarea').each(function( i ) {
$(this).change(function() {
data.set($(this).attr('name'),$(this).val());
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());
});
});
frm.submit(function() {
console.log(data)
console.log("Form submission");
$(data.values()).each(function(value) {
console.log(value);
});
$("#id_drop-files").replaceWith(
'<div id="id_waiting"><img src="/static/img/rowingtimer.gif" width="60" height="50" style="width:60px">'
);
$.ajax({
data: data,
type: $(this).attr('method'),
url: '/rowers/courses/{{ course.id }}/update/',
contentType: false,
processData: false,
error: function(result) {
$("#id_waiting").replaceWith(
'<div id="id_failed" class="grid_12 alpha message">Your upload failed</div>'
);
console.log(data);
setTimeout(1000);
setTimeout(function() {
location.reload();
},1000);
},
success: function(result) {
console.log('got something back');
console.log(result);
if (result.result == 1) {
setTimeout(1000);
window.location.href = result.url;
} else {
console.log(result," reloading");
location.reload();
};
}
});
return false;
});
$('#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

@@ -30,7 +30,7 @@
{{ form.as_table }}
</table>
{% csrf_token %}
<input class="button green" type="submit" value="Submit">
<input class="button" type="submit" value="Submit">
</form>
</li>
<li class="grid_2">

View File

@@ -0,0 +1,58 @@
{% extends "newbase.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% load leaflet_tags %}
{% block meta %}
{% leaflet_js %}
{% leaflet_css %}
{% endblock %}
{% block scripts %}
{% include "monitorjobs.html" %}
{% endblock %}
{% block title %}{{ course.name }} {% endblock %}
{% block og_title %}{{ course.name }} {% endblock %}
{% block main %}
<h1>Replace {{ course.name }}</h1>
<ul class="main-content">
<li class="grid_2">
<p>
This updates the course {{ course.name }} with the course markers as shown on
the map below.
</p>
<form id="course_form" method="post">
<form id="course_form" method="post">
<table>
{{ form.as_table }}
</table>
{% csrf_token %}
<input class="button" type="submit" value="Submit">
</form>
</li>
<li class="grid_2">
<div class="mapdiv">
{{ mapdiv|safe }}
{{ mapscript|safe }}
</div>
</li>
<li class="grid_4">
<div class="mapdiv">
{{ mapdiv|safe }}
{{ mapscript|safe }}
</div>
</li>
</ul>
{% endblock %}
{% block sidebar %}
{% include 'menu_racing.html' %}
{% endblock %}

View File

@@ -128,7 +128,7 @@
</li>
{% endif %}
<li id="course-view">
<a href="/rowers/courses/{{ course.id }}/replace/">
<a href="/rowers/courses/{{ course.id }}/update/">
<i class="fas fa-map-marked-alt fa-fw"></i>&nbsp;Update Markers</a>
</li>
{% endif %}

View File

@@ -205,6 +205,8 @@ urlpatterns = [
re_path(r'^list-courses/$',views.courses_view,name='courses_view'),
re_path(r'^list-standards/$',views.standards_view,name='standards_view'),
re_path(r'^courses/upload/$',views.course_upload_view,name='course_upload_view'),
re_path(r'^courses/(?P<id>\d+)/update/(?P<newid>\d+)/',views.course_update_confirm,name='course_update_confirm'),
re_path(r'^courses/(?P<id>\d+)/update/',views.course_upload_replace_view,name='course_upload_replace_view'),
re_path(r'^standards/upload/$',views.standards_upload_view,name='standards_upload_view'),
re_path(r'^standards/upload/(?P<id>\d+)/$',views.standards_upload_view,name='standards_upload_view'),
re_path(r'^workout/addmanual/(?P<raceid>\d+)/$',views.addmanual_view,name='addmanual_view'),

View File

@@ -8,6 +8,7 @@ from rowsandall_app.settings import SITE_URL
from rowers.scoring import *
from django.contrib.gis.geoip2 import GeoIP2
from django import forms
# distance of course from lat_lon in km
def howfaris(lat_lon,course):
@@ -559,6 +560,132 @@ def virtualevent_uploadimage_view(request,id=0):
})
@login_required()
@permission_required('course.change_course',fn=get_course_by_pk,raise_exception=True)
def course_upload_replace_view(request,id=0):
is_ajax = False
if request.is_ajax():
is_ajax = True
r = getrower(request.user)
course = get_object_or_404(GeoCourse,pk=id)
if request.method == 'POST':
form = CourseForm(request.POST,request.FILES)
if form.is_valid():
f = form.cleaned_data['file']
notes = form.cleaned_data['notes']
if f is not None:
filename, path_and_filename = handle_uploaded_file(f)
cs = courses.kmltocourse(path_and_filename)
os.remove(path_and_filename)
if cs and len(cs) > 1:
messages.info(request,'File contained multiple courses. We use the first one.')
if cs:
course = cs[0]
cname = course['name']
cnotes = notes+'\n\n'+course['description']
polygons = course['polygons']
course = courses.createcourse(r,cname,polygons,notes=cnotes)
url = reverse(course_update_confirm,
kwargs = {
'newid':course.id,
'id':id,
}
)
if is_ajax:
return JSONResponse({'result':1,'url':url})
else:
return HttpResponseRedirect(url)
else:
messages.error(request,"File does not contain a course")
else:
messages.error(request,"No file attached")
else:
messages.error(request,"Form is not valid")
else:
form = CourseForm()
form.fields['name'].widget = forms.HiddenInput()
if not is_ajax:
return render(request,'course_form_update.html',
{'form':form,
'course':course,
'active':'nav-racing',
})
else:
return {'result':0}
@login_required()
@permission_required('course.change_course',fn=get_course_by_pk,raise_exception=True)
def course_update_confirm(request,id=0,newid=0):
course = get_object_or_404(GeoCourse,pk=id)
course2 = get_object_or_404(GeoCourse,pk=newid)
r = getrower(request.user)
if request.method == 'POST':
form = CourseConfirmForm(request.POST)
if form.is_valid():
doupdate = form.cleaned_data['doupdate']
if doupdate:
res = courses.replacecourse(course,course2)
messages.info(request,'All challenges with this course are updated')
url = reverse(course_view,
kwargs = {
'id':course2.id,
})
return HttpResponseRedirect(url)
else:
course2.delete()
url = reverse(course_view,
kwargs = {
'id':course.id,
})
return HttpResponseRedirect(url)
else:
form = CourseConfirmForm()
# GET call or invalid form
script, div = course_map(course2)
breadcrumbs = [
{
'url': reverse('virtualevents_view'),
'name': 'Challenges'
},
{
'url': reverse(courses_view),
'name': 'Courses'
},
{
'url': reverse(course_view,kwargs={'id':course.id}),
'name': course.name
},
{
'url': reverse(course_replace_view,kwargs={'id':course.id}),
'name': 'Replace Markers'
}
]
return render(request,
'course_replace_confirm.html',
{'course':course,
'form':form,
'active':'nav-racing',
'breadcrumbs':breadcrumbs,
'rower':r,
'mapdiv':div,
'mapscript':script,
})
# Course upload
@login_required()
def course_upload_view(request):

View File

@@ -64,6 +64,7 @@ from django.contrib.auth import authenticate, login, logout
from rowers.forms import (
ForceCurveOptionsForm,HistoForm,TeamMessageForm,
LoginForm,DocumentsForm,UploadOptionsForm,ImageForm,CourseForm,
CourseConfirmForm,
TeamUploadOptionsForm,WorkFlowLeftPanelForm,WorkFlowMiddlePanelForm,
WorkFlowLeftPanelElement,WorkFlowMiddlePanelElement,
LandingPageForm,PlannedSessionSelectForm,WorkoutSessionSelectForm,