Private
Public Access
1
0

Merge branch 'release/v5.92'

This commit is contained in:
Sander Roosendaal
2018-02-21 13:03:18 +01:00
18 changed files with 1091 additions and 24 deletions

View File

@@ -6,6 +6,7 @@ from .models import (
Rower, Workout,GraphImage,FavoriteChart,SiteAnnouncement,
Team,TeamInvite,TeamRequest,
WorkoutComment,C2WorldClassAgePerformance,PlannedSession,
GeoCourse,GeoPolygon,GeoPoint,
)
# Register your models here so you can use them in the Admin module
@@ -50,6 +51,16 @@ class PlannedSessionAdmin(admin.ModelAdmin):
class GraphImageAdmin(admin.ModelAdmin):
list_display = ('creationdatetime','workout','filename')
class GeoPolygonInline(admin.StackedInline):
model = GeoPolygon
class GeoCourseAdmin(admin.ModelAdmin):
list_display = ('manager','name')
inlines = (GeoPolygonInline,)
admin.site.unregister(User)
admin.site.register(User,UserAdmin)
admin.site.register(Workout,WorkoutAdmin)
@@ -63,4 +74,4 @@ admin.site.register(WorkoutComment,WorkoutCommentAdmin)
admin.site.register(C2WorldClassAgePerformance,
C2WorldClassAgePerformanceAdmin)
admin.site.register(PlannedSession,PlannedSessionAdmin)
admin.site.register(GeoCourse, GeoCourseAdmin)

View File

@@ -9,14 +9,14 @@ from django.db import IntegrityError
import uuid
from django.conf import settings
from utils import myqueue
from matplotlib import path
import xml.etree.ElementTree as et
import pandas as pd
ns = {'opengis': 'http://www.opengis.net/kml/2.2'}
import django_rq
queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('low')
from rowers.models import (
Rower, Workout,
@@ -31,8 +31,59 @@ class InvalidTrajectoryError(Exception):
def __str__(self):
return repr(self.value)
def polygon_coord_center(polygon):
points = GeoPoint.objects.filter(polygon=polygon).order_by("order_in_poly")
latitudes = pd.Series([p.latitude for p in points])
longitudes = pd.Series([p.longitude for p in points])
return latitudes.mean(), longitudes.mean()
def course_coord_center(course):
polygons = GeoPolygon.objects.filter(course=course).order_by("order_in_course")
latitudes = []
longitudes = []
for p in polygons:
latitude,longitude = polygon_coord_center(p)
latitudes.append(latitude)
longitudes.append(longitude)
latitude = pd.Series(latitudes).median()
longitude = pd.Series(longitudes).median()
coordinates = pd.DataFrame({
'latitude':latitudes,
'longitude':longitudes,
})
return latitude,longitude,coordinates
def course_coord_maxmin(course):
polygons = GeoPolygon.objects.filter(course=course).order_by("order_in_course")
latitudes = []
longitudes = []
for p in polygons:
latitude,longitude = polygon_coord_center(p)
latitudes.append(latitude)
longitudes.append(longitude)
lat_min = pd.Series(latitudes).min()
lat_max = pd.Series(latitudes).max()
long_min = pd.Series(longitudes).min()
long_max = pd.Series(longitudes).max()
return lat_min,lat_max,long_min,long_max
def polygon_to_path(polygon):
points = GeoPoint.objects.filter(polygon==polygon).order_by(order_in_polygon)
points = GeoPoint.objects.filter(polygon==polygon).order_by("order_in_polygon")
s = []
for point in points:
s.append([point.latitude,point.longitude])
@@ -44,7 +95,7 @@ def polygon_to_path(polygon):
def coordinate_in_polygon(latitude,longitude, polygon):
p = polygon_to_path(polygon)
retun p.contains_points([(latitude,longitude)])[0]
return p.contains_points([(latitude,longitude)])[0]
@@ -71,3 +122,96 @@ def time_in_polygon(df,polygon,maxmin='max'):
return time
def crewnerdcourse(doc):
courses = []
for course in doc:
name = course.findall('.//opengis:name',ns)[0].text
try:
description = course.findall('.//opengis:description',ns)[0].text
except IndexError:
description = ''
polygonpms = course.findall('.//opengis:Placemark[opengis:Polygon]',ns)
polygons = get_polygons(polygonpms)
courses.append({
'name':name,
'description':description,
'polygons':polygons
})
return courses
def get_polygons(polygonpms):
polygons = []
for pm in polygonpms:
name = pm.findall('.//opengis:name',ns)[0].text
coordinates = pm.findall('.//opengis:coordinates',ns)
if coordinates:
cc = coordinates[0].text
else:
cc = ''
pointstring = cc.split()
points = []
for s in pointstring:
coordinates = s.split(',')
points.append({
'longitude':float(coordinates[0]),
'latitude':float(coordinates[1]),
})
polygons.append({
'name':name,
'points':points
})
return polygons
def kmltocourse(f):
doc = et.parse(f)
courses = doc.findall('.//opengis:Folder[opengis:Placemark]',ns)
if courses:
return crewnerdcourse(courses)
polygonpms = doc.findall('.//opengis:Placemark[opengis:Polygon]',ns)
return get_polygons(polygonpms)
from geopy.geocoders import Nominatim
geolocator = Nominatim()
def createcourse(
manager,name,polygons,notes=''):
c = GeoCourse(manager=manager,name=name,notes=notes)
c.save()
i = 0
for p in polygons:
pp = GeoPolygon(course=c,order_in_course=i,name=p['name'])
pp.save()
j = 0
for point in p['points']:
if i==0 and j==0:
loc = geolocator.reverse((point['latitude'],point['longitude']))
country = loc.raw['address']['country']
c.country = country
c.save()
obj = GeoPoint(
latitude = point['latitude'],
longitude = point['longitude'],
polygon = pp,
order_in_poly = j
)
obj.save()
j += 1
i += 1
return c

View File

@@ -1,7 +1,7 @@
from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple
from rowers.models import Workout,Rower,Team,PlannedSession
from rowers.rows import validate_file_extension,must_be_csv,validate_image_extension
from rowers.rows import validate_file_extension,must_be_csv,validate_image_extension,validate_kml
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from django.contrib.admin.widgets import AdminDateWidget
@@ -55,7 +55,21 @@ class ImageForm(forms.Form):
from django.forms.widgets import HiddenInput
super(ImageForm, self).__init__(*args, **kwargs)
# The form used for uploading images
class CourseForm(forms.Form):
name = forms.CharField(max_length=150,label='Course Name')
file = forms.FileField(required=False,
validators=[validate_kml])
notes = forms.CharField(required=False,
max_length=200,label='Course Notes',
widget=forms.Textarea)
def __init__(self, *args, **kwargs):
from django.forms.widgets import HiddenInput
super(CourseForm, self).__init__(*args, **kwargs)
# The form used for uploading files
class DocumentsForm(forms.Form):
title = forms.CharField(required=False)

View File

@@ -1,5 +1,8 @@
import colorsys
from rowers.models import Workout, User, Rower, WorkoutForm,RowerForm,GraphImage
from rowers.models import (
Workout, User, Rower, WorkoutForm,RowerForm,
GraphImage,GeoPolygon,GeoCourse,GeoPoint
)
from rowingdata import rower as rrower
from rowingdata import main as rmain
from rowingdata import cumcpdata,histodata
@@ -35,6 +38,11 @@ from bokeh.core.properties import value
from collections import OrderedDict
from django.conf import settings
from courses import (
course_coord_center,course_coord_maxmin,
polygon_coord_center
)
import datetime
import math
import numpy as np
@@ -707,6 +715,140 @@ def interactive_histoall(theworkouts):
script, div = components(plot)
return [script,div]
def course_map(course):
latmean,lonmean,coordinates = course_coord_center(course)
lat_min, lat_max, long_min, long_max = course_coord_maxmin(course)
scoordinates = "["
for index,row in coordinates.iterrows():
scoordinates += """[{x},{y}],
""".format(
x=row['latitude'],
y=row['longitude']
)
scoordinates +="]"
polygons = GeoPolygon.objects.filter(course=course).order_by("order_in_course")
plabels = ''
for p in polygons:
coords = polygon_coord_center(p)
plabels += """
var marker = L.marker([{latbegin}, {longbegin}]).addTo(mymap);
marker.bindPopup("<b>{name}</b>");
""".format(
latbegin = coords[0],
longbegin = coords[1],
name = p.name
)
pcoordinates = """[
"""
for p in polygons:
pcoordinates += """[
["""
points = GeoPoint.objects.filter(polygon=p).order_by("order_in_poly")
for pt in points:
pcoordinates += "[{x},{y}],".format(
x = pt.latitude,
y = pt.longitude
)
# remove last comma
pcoordinates = pcoordinates[:-1]
pcoordinates += """]
],
"""
pcoordinates += """
]"""
script = """
<script>
var streets = L.tileLayer('https://api.tiles.mapbox.com/v4/{{id}}/{{z}}/{{x}}/{{y}}.png?access_token=pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA', {{
maxZoom: 18,
id: 'mapbox.streets'
}}),
satellite = L.tileLayer('https://api.tiles.mapbox.com/v4/{{id}}/{{z}}/{{x}}/{{y}}.png?access_token=pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA', {{
maxZoom: 18,
id: 'mapbox.satellite'
}}),
outdoors = L.tileLayer('https://api.tiles.mapbox.com/v4/{{id}}/{{z}}/{{x}}/{{y}}.png?access_token=pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA', {{
maxZoom: 18,
id: 'mapbox.outdoors'
}});
var mymap = L.map('map_canvas', {{
center: [{latmean}, {lonmean}],
zoom: 13,
layers: [streets, satellite]
}}).setView([{latmean},{lonmean}], 13);
var navionics = new JNC.Leaflet.NavionicsOverlay({{
navKey: 'Navionics_webapi_03205',
chartType: JNC.NAVIONICS_CHARTS.NAUTICAL,
isTransparent: true,
zIndex: 1
}});
var osmUrl2='http://tiles.openseamap.org/seamark/{{z}}/{{x}}/{{y}}.png';
var osmUrl='http://{{s}}.tile.openstreetmap.org/{{z}}/{{x}}/{{y}}.png';
//create two TileLayer
var nautical=new L.TileLayer(osmUrl,{{
maxZoom:18}});
L.control.layers({{
"Streets": streets,
"Satellite": satellite,
"Outdoors": outdoors,
"Nautical": nautical,
}},{{
"Navionics":navionics,
}}).addTo(mymap);
var latlongs = {scoordinates}
var polyline = L.polyline(latlongs, {{color:'red'}}).addTo(mymap)
mymap.fitBounds(polyline.getBounds())
var platlongs = {pcoordinates}
var polygons = L.polygon(platlongs, {{color:'blue'}}).addTo(mymap)
{plabels}
</script>
""".format(
latmean=latmean,
lonmean=lonmean,
scoordinates=scoordinates,
pcoordinates=pcoordinates,
plabels = plabels
)
div = """
<div id="map_canvas" style="width: 100%; height: 400px;"><p>&nbsp;</p></div>
"""
return script,div
def leaflet_chart(lat,lon,name=""):
if lat.empty or lon.empty:

View File

@@ -17,6 +17,7 @@ import os
import twitter
import re
import pytz
from django.conf import settings
from sqlalchemy import create_engine
import sqlalchemy as sa
@@ -31,6 +32,7 @@ from collections import OrderedDict
import types
from rowsandall_app.settings import (
TWEET_ACCESS_TOKEN_KEY,
TWEET_ACCESS_TOKEN_SECRET,
@@ -661,9 +663,28 @@ timezones = (
class GeoCourse(models.Model):
manager = models.ForeignKey(Rower)
name = models.CharField(max_length=150,blank=True)
country = models.CharField(max_length=150,blank=True)
notes = models.CharField(blank=True,max_length=200,verbose_name='Course Notes')
def __unicode__(self):
name = self.name
country = self.country
return u'{country} - {name}'.format(
name=name,
country=country
)
class GeoCourseEditForm(ModelForm):
class Meta:
model = GeoCourse
fields = ['name','notes']
widgets = {
'notes': forms.Textarea,
}
class GeoPolygon(models.Model):
name = models.CharField(max_length=150,blank=True)
course = models.ForeignKey(GeoCourse, blank=True)
order_in_course = models.IntegerField(default=0)
@@ -802,6 +823,7 @@ class PlannedSession(models.Model):
('challenge','Challenge'),
('test','Mandatory Test'),
('cycletarget','Cycle Target'),
('coursetest','OTW test over a course'),
)
sessionmodechoices = (
@@ -830,6 +852,8 @@ class PlannedSession(models.Model):
)
manager = models.ForeignKey(User)
course = models.ForeignKey(GeoCourse,blank=True,null=True,
verbose_name='OTW Course')
name = models.CharField(max_length=150,blank=True,
verbose_name='Name')
@@ -913,6 +937,10 @@ class PlannedSession(models.Model):
self.sessionmode = 'distance'
self.sessionunit = 'm'
self.criterium = 'exact'
if self.sessiontype == 'coursetest':
self.sessionmode = 'distance'
self.sessionunit = 'm'
self.criterium = 'none'
super(PlannedSession,self).save(*args, **kwargs)
@@ -932,6 +960,7 @@ class PlannedSessionForm(ModelForm):
'criterium',
'sessionvalue',
'sessionunit',
'course',
'comment',
]
@@ -946,6 +975,11 @@ class PlannedSessionForm(ModelForm):
'enddate': AdminDateWidget(),
}
def __init__(self,*args,**kwargs):
super(PlannedSessionForm, self).__init__(*args, **kwargs)
if self.instance.sessiontype != 'coursetest':
del self.fields['course']
class PlannedSessionFormSmall(ModelForm):
class Meta:
@@ -976,7 +1010,7 @@ class PlannedSessionFormSmall(ModelForm):
'type':'number'}),
'manager': forms.HiddenInput(),
}
# Workout
class Workout(models.Model):

View File

@@ -69,6 +69,13 @@ def must_be_csv(value):
if not ext in valid_extensions:
raise ValidationError(u'File not supported!')
def validate_kml(value):
import os
ext = os.path.splitext(value.name)[1]
valid_extensions = ['.kml','.KML']
if not ext in valid_extensions:
raise ValidationError(u'File not supported!')
def handle_uploaded_image(i):
import StringIO

View File

@@ -0,0 +1,54 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block scripts %}
{% include "monitorjobs.html" %}
{% endblock %}
{% block title %}{{ course.name }} {% endblock %}
{% block og_title %}{{ course.name }} {% endblock %}
{% block content %}
<div class="grid_12 alpha">
<div class="grid_2 alpha">
{% if nosessions %}
<a class="button small red" href="/rowers/courses/{{ course.id }}/delete">Delete</a>
{% else %}
&nbsp;
{% endif %}
</div>
<div class="grid_2">
{% if course.manager == rower %}
<a class="button small gray" href="/rowers/courses/{{ course.id }}">View Course</a>
{% else %}
&nbsp;
{% endif %}
</div>
<div class="grid_2">
<a class="button small gray" href="/rowers/list-courses">Courses</a>
</div>
</div>
<div class="grid_12 alpha">
<h1>{{ course.name }}</h1>
<div class="grid_6 alpha">
<form id="course_form" method="post">
<table>
{{ form.as_table }}
</table>
{% csrf_token %}
<div id="formbutton" class="grid_1 prefix_4 suffix_1 alpha">
<input class="button green" type="submit" value="Submit">
</div>
</form>
</div>
<div class="grid_6 omega">
{{ mapdiv|safe }}
{{ mapscript|safe }}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,252 @@
{% 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 KML Course File</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>
{% 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());
});
});
frm.submit(function() {
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="120" height="100">'
);
$.ajax({
data: data,
type: $(this).attr('method'),
url: '/rowers/courses/upload',
contentType: false,
processData: false,
error: function(result) {
$("#id_waiting").replaceWith(
'<div id="id_failed" class="grid_12 alpha message">Your upload failed</div>'
);
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({
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,56 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block scripts %}
{% include "monitorjobs.html" %}
{% endblock %}
{% block title %}{{ course.name }} {% endblock %}
{% block og_title %}{{ course.name }} {% endblock %}
{% block content %}
<div class="grid_12 alpha">
<div class="grid_2 alpha">
{% if nosessions %}
<a class="button small red" href="/rowers/courses/{{ course.id }}/delete">Delete</a>
{% else %}
&nbsp;
{% endif %}
</div>
<div class="grid_2">
{% if course.manager == rower %}
<a class="button small gray" href="/rowers/courses/{{ course.id }}/edit">Edit</a>
{% else %}
&nbsp;
{% endif %}
</div>
<div class="grid_2">
<a class="button small gray" href="/rowers/list-courses">Courses</a>
</div>
</div>
<div class="grid_12 alpha">
<h1>{{ course.name }}</h1>
<div class="grid_6 alpha">
<table class="listtable shortpadded" width="100%">
<tr>
<th>Name</th><td>{{ course.name }}</td>
</tr>
<tr>
<th>Country</th><td>{{ course.country }}</td>
</tr>
<tr>
<th>Notes</th><td>{{ course.notes }}</td>
</tr>
</table>
</div>
<div class="grid_6 omega">
{{ mapdiv|safe }}
{{ mapscript|safe }}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,144 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}Rowsandall Courses List{% endblock %}
{% block scripts %}
{% endblock %}
{% block content %}
<style>
#mypointer {
cursor: pointer;
}
</style>
<div class="grid_12">
<div id="courses_table" class="grid_8 alpha">
<h1>Courses</h1>
{% if courses %}
<table width="100%" class="listtable shortpadded">
<thead>
<tr>
<th> Country</th>
<th> Name</th>
</tr>
</thead>
<tbody>
{% for course in courses %}
<tr>
<td> {{ course.country }} </td>
<td>
{% if course.manager.user == user %}
<a href="/rowers/courses/{{ course.id }}/edit">{{ course.name }}</a>
{% else %}
<a href="/rowers/courses/{{ course.id }}">{{ course.name }}</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p> No courses found </p>
{% endif %}
<div class="grid_6 alpha">
<div class="grid_2 prefix_1 alpha">
<a class="button small green" href="/rowers/courses/upload">Add Courses</a>
</div>
<p>&nbsp;</p>
<form id="searchform" action="/rowers/list-courses/"
method="get" accept-charset="utf-8">
<div class="grid_3 prefix_1 alpha">
<input class="searchfield" id="searchbox" name="q" type="text" placeholder="Search">
</div>
<div class="grid_1 omega">
<button class="button blue small" type="submit">
Search
</button>
</div>
</form>
</div>
<div class="grid_2 omega">
&nbsp;
</div>
</div>
<div class="grid_4 omega">
<div class="grid_4" id="announcements">
{% if announcements %}
<h3>What's New?</h3>
{% for a in announcements %}
<div class="site-announcement-box">
<div class="site-announcement">
<i>{{ a.created }}:</i>
{{ a.announcement|urlize }}
</div>
</div>
{% endfor %}
<p>&nbsp;</p>
{% endif %}
</div>
<div class="grid_4" id="about">
<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="earth.google.com">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 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 Place 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>
</div>
</div>
</div>
{% endblock %}

View File

@@ -57,7 +57,11 @@
{% endfor %}
</div>
</div>
{% endif %}
<div class="grid_2 omega">
<a class="button small gray" href="/rowers/list-courses">Courses</a>
</div>
</div>
<div class="grid_12 alpha">

View File

@@ -53,6 +53,9 @@
</a>
</div>
{% endif %}
<div class="grid_2 omega">
<a class="button small gray" href="/rowers/list-courses">Courses</a>
</div>
</div>
<div class="grid_12 alpha">
<div id="right" class="grid_6 alpha">

View File

@@ -56,7 +56,9 @@
</div>
</div>
{% endif %}
<div class="grid_2 omega">
<a class="button small gray" href="/rowers/list-courses">Courses</a>
</div>
<div class="grid_12 alpha">
{% if plannedsessions %}
<p>

View File

@@ -68,12 +68,8 @@
<div class="grid_12 alpha">
{% include "planningbuttons.html" %}
</div>
<div class="grid_4 alpha">
{% if theteam %}
<h1>Coach Overview. Team {{ theteam.name }}</h1>
{% else %}
<h1>Coach Overview</h1>
{% endif %}
<div class="grid_6 alpha">
<h1>Clone Multiple Sessions</h1>
</div>
<div id="timeperiod" class="grid_2 dropdown">
<button class="grid_2 alpha button gray small dropbtn">Select Time Period ({{ timeperiod|verbosetimeperiod }})</button>

View File

@@ -22,6 +22,13 @@
&nbsp;
{% endif %}
</div>
<div class="grid_2">
{% if plannedsession.sessiontype == 'coursetest' %}
<a class="button small gray" href="/rowers/list-courses">Courses</a>
{% else %}
&nbsp;
{% endif %}
</div>
</div>
<div class="grid_12 alpha">
<div id="left" class="grid_6 alpha">
@@ -128,6 +135,16 @@
</tbody>
</table>
</div>
<div class="grid_12 alpha">
<div id="left" class="grid_6 alpha">
{% if coursescript %}
<h1>Course</h1>
{{ coursediv|safe }}
{{ coursescript|safe }}
{% endif %}
</div>
</div>

View File

@@ -143,6 +143,8 @@ urlpatterns = [
url(r'^u/(?P<userid>\d+)/list-workouts/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.workouts_view),
url(r'^list-workouts/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.workouts_view),
url(r'^list-workouts/$',views.workouts_view),
url(r'^list-courses/$',views.courses_view),
url(r'^courses/upload$',views.course_upload_view),
url(r'^addmanual/$',views.addmanual_view),
url(r'^team-compare-select/team/(?P<teamid>\d+)/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.team_comparison_select),
url(r'^team-compare-select/team/(?P<teamid>\d+)/$',views.team_comparison_select),
@@ -484,6 +486,10 @@ urlpatterns = [
url(r'^sessions/rower/(?P<rowerid>\d+)$',views.plannedsessions_view),
url(r'^sessions/(?P<timeperiod>[\w\ ]+.*)/rower/(?P<rowerid>\d+)$',views.plannedsessions_view),
url(r'^sessions/(?P<timeperiod>[\w\ ]+.*)$',views.plannedsessions_view),
url(r'^courses/(?P<id>\d+)/edit$',views.course_edit_view,
name='course_edit_view'),
url(r'^courses/(?P<id>\d+)/delete$',views.course_delete_view),
url(r'^courses/(?P<id>\d+)$',views.course_view),
]
if settings.DEBUG:

View File

@@ -27,7 +27,7 @@ from django.http import (
)
from django.contrib.auth import authenticate, login, logout
from rowers.forms import (
LoginForm,DocumentsForm,UploadOptionsForm,ImageForm,
LoginForm,DocumentsForm,UploadOptionsForm,ImageForm,CourseForm,
TeamUploadOptionsForm,WorkFlowLeftPanelForm,WorkFlowMiddlePanelForm,
WorkFlowLeftPanelElement,WorkFlowMiddlePanelElement,
LandingPageForm,PlannedSessionSelectForm,WorkoutSessionSelectForm,
@@ -62,13 +62,14 @@ from rowers.models import (
Team,TeamForm,TeamInviteForm,TeamInvite,TeamRequest,
WorkoutComment,WorkoutCommentForm,RowerExportForm,
CalcAgePerformance,PowerTimeFitnessMetric,PlannedSessionForm,
PlannedSessionFormSmall,
PlannedSessionFormSmall,GeoCourseEditForm,
)
from rowers.models import (
FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement,BasePlannedSessionFormSet
)
from rowers.metrics import rowingmetrics,defaultfavoritecharts
from rowers import metrics
from rowers import courses
import rowers.uploads as uploads
from django.forms.formsets import formset_factory
from django.forms import modelformset_factory
@@ -6045,6 +6046,32 @@ def boxplot_view(request,userid=0,
})
# List Courses
@login_required()
def courses_view(request):
r = getrower(request.user)
courses = GeoCourse.objects.all().order_by("country","name")
# add search processing
query = request.GET.get('q')
if query:
query_list = query.split()
courses = GeoCourse.objects.filter(
reduce(operator.and_,
(Q(name__icontains=q) for q in query_list)) |
reduce(operator.and_,
(Q(country__icontains=q) for q in query_list)) |
reduce(operator.and_,
(Q(notes__icontains=q) for q in query_list))
)
return render(request,'list_courses.html',
{'courses':courses,
'rower':r,
})
# List Workouts
@login_required()
def workouts_view(request,message='',successmessage='',
@@ -8391,7 +8418,90 @@ def workout_comment_view(request,id=0):
'comments':comments,
'form':form,
})
@login_required()
def course_delete_view(request,id=0):
try:
course = GeoCourse.objects.get(id=id)
except GeoCourse.DoesNotExist:
return Http404("Course doesn't exist")
r = getrower(request.user)
if course.manager != r:
raise PermissionDenied("Access denied")
ps = PlannedSession.objects.filter(course=course)
nosessions = len(ps) == 0
if nosessions:
course.delete()
url = reverse(courses_view)
return HttpResponseRedirect(url)
@login_required()
def course_edit_view(request,id=0):
try:
course = GeoCourse.objects.get(id=id)
except GeoCourse.DoesNotExist:
return Http404("Course doesn't exist")
r = getrower(request.user)
if course.manager != r:
raise PermissionDenied("Access denied")
ps = PlannedSession.objects.filter(course=course)
nosessions = len(ps) == 0
script,div = course_map(course)
if request.method == 'POST':
form = GeoCourseEditForm(request.POST)
if form.is_valid():
name = form.cleaned_data['name']
notes = form.cleaned_data['notes']
course.name = name
course.notes = notes
course.save()
form = GeoCourseEditForm(instance=course)
return render(request, 'course_edit_view.html',
{
'course':course,
'mapscript':script,
'mapdiv':div,
'nosessions':nosessions,
'rower':r,
'form':form,
}
)
@login_required()
def course_view(request,id=0):
try:
course = GeoCourse.objects.get(id=id)
except GeoCourse.DoesNotExist:
return Http404("Course doesn't exist")
r = getrower(request.user)
script,div = course_map(course)
return render(request, 'course_view.html',
{
'course':course,
'mapscript':script,
'mapdiv':div,
'nosessions':False,
'rower':r,
}
)
# The basic edit page
@login_required()
@@ -8861,6 +8971,64 @@ def workout_uploadimage_view(request,id):
else:
return {'result':0}
# Image upload
@login_required()
def course_upload_view(request):
is_ajax = False
if request.is_ajax():
is_ajax = True
r = getrower(request.user)
if request.method == 'POST':
form = CourseForm(request.POST,request.FILES)
if form.is_valid():
f = form.cleaned_data['file']
name = form.cleaned_data['name']
notes = form.cleaned_data['notes']
if f is not None:
filename,path_and_filename = handle_uploaded_file(f)
cs = courses.kmltocourse(path_and_filename)
for course in cs:
cname = name+' - '+course['name']
cnotes = notes+'\n\n'+course['description']
polygons = course['polygons']
course = courses.createcourse(r,cname,polygons,notes=cnotes)
os.remove(path_and_filename)
url = reverse(courses_view)
if is_ajax:
return JSONResponse({'result':1,'url':url})
else:
return HttpResponseRedirect(url)
else:
messages.error(request,'Something went wrong - no file attached')
url = reverse(course_upload_view)
if is_ajax:
return JSONResponse({'result':0,'url':0})
else:
return HttpResponseRedirect(url)
else:
messages.error(request,'Form is not valid')
return render(request,'course_form.html',
{'form':form,
})
else:
if not is_ajax:
form = CourseForm()
return render(request,'course_form.html',
{'form':form,
})
else:
return {'result':0}
# Generic chart creation
@@ -12454,6 +12622,12 @@ def plannedsession_view(request,id=0,rowerid=0,
raise Http404("Planned Session does not exist")
if ps.course:
coursescript,coursediv = course_map(ps.course)
else:
coursescript = ''
coursediv = ''
if ps.manager != request.user and r not in ps.rower.all():
raise PermissionDenied("You do not have access to this session")
@@ -12506,6 +12680,8 @@ def plannedsession_view(request,id=0,rowerid=0,
'plannedsession':ps,
'timeperiod':timeperiod,
'ranking':ranking,
'coursescript': coursescript,
'coursediv': coursediv
}
)

View File

@@ -45,7 +45,12 @@ def get_metar_data(airportcode,unixtime):
url += str(unixtime+3600)
url += "&stationString="+airportcode
s = requests.get(url)
try:
s = requests.get(url)
except:
message = 'Failed to download METAR data'
return [0,0,message,'','']
if s.ok:
doc = etree.fromstring(s.content)