courses functionality
This commit is contained in:
@@ -9,7 +9,6 @@ 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
|
||||
@@ -18,10 +17,6 @@ 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,
|
||||
@@ -128,10 +123,27 @@ 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)
|
||||
|
||||
def kmltocourse(f):
|
||||
doc = et.parse(f)
|
||||
polygonpms = doc.findall('.//opengis:Placemark[opengis:Polygon]',ns)
|
||||
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
|
||||
@@ -157,6 +169,19 @@ def kmltocourse(f):
|
||||
})
|
||||
|
||||
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()
|
||||
|
||||
@@ -40,6 +40,7 @@ from django.conf import settings
|
||||
|
||||
from courses import (
|
||||
course_coord_center,course_coord_maxmin,
|
||||
polygon_coord_center
|
||||
)
|
||||
|
||||
import datetime
|
||||
@@ -731,6 +732,20 @@ def course_map(course):
|
||||
|
||||
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 = """[
|
||||
"""
|
||||
@@ -818,12 +833,15 @@ def course_map(course):
|
||||
var platlongs = {pcoordinates}
|
||||
var polygons = L.polygon(platlongs, {{color:'blue'}}).addTo(mymap)
|
||||
|
||||
{plabels}
|
||||
|
||||
</script>
|
||||
""".format(
|
||||
latmean=latmean,
|
||||
lonmean=lonmean,
|
||||
scoordinates=scoordinates,
|
||||
pcoordinates=pcoordinates
|
||||
pcoordinates=pcoordinates,
|
||||
plabels = plabels
|
||||
)
|
||||
|
||||
div = """
|
||||
|
||||
@@ -667,8 +667,22 @@ class GeoCourse(models.Model):
|
||||
notes = models.CharField(blank=True,max_length=200,verbose_name='Course Notes')
|
||||
def __unicode__(self):
|
||||
name = self.name
|
||||
return u'{name}'.format(name=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)
|
||||
|
||||
54
rowers/templates/course_edit_view.html
Normal file
54
rowers/templates/course_edit_view.html
Normal 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 %}
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="grid_2">
|
||||
{% if course.manager == rower %}
|
||||
<a class="button small gray" href="/rowers/courses/{{ course.id }}">View Course</a>
|
||||
{% else %}
|
||||
|
||||
{% 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 %}
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
|
||||
@@ -9,21 +9,47 @@
|
||||
{% block og_title %}{{ course.name }} {% endblock %}
|
||||
{% block content %}
|
||||
<div class="grid_12 alpha">
|
||||
{% if nosessions %}
|
||||
<div class="grid_2 alpha">
|
||||
{% if nosessions %}
|
||||
<a class="button small red" href="/rowers/courses/{{ course.id }}/delete">Delete</a>
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="grid_2">
|
||||
{% if course.manager == rower %}
|
||||
<a class="button small gray" href="/rowers/courses/{{ course.id }}/edit">Edit</a>
|
||||
{% else %}
|
||||
|
||||
{% 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>
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
<div class="grid_12">
|
||||
|
||||
<div id="courses_table" class="grid_8 alpha">
|
||||
<h3>Courses</h3>
|
||||
|
||||
<h1>Courses</h1>
|
||||
|
||||
{% if courses %}
|
||||
<table width="100%" class="listtable shortpadded">
|
||||
<thead>
|
||||
@@ -29,27 +29,52 @@
|
||||
<th> Country</th>
|
||||
<th> Name</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for course in courses %}
|
||||
<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>
|
||||
<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> </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">
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="grid_4 omega">
|
||||
@@ -68,40 +93,52 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="grid_4" id="about">
|
||||
<h3>About</h3>
|
||||
<p>This site is a beta site, pioneering rowing data visualization and analysis. No warranties. The site's author is
|
||||
Sander Roosendaal. A Masters rower.
|
||||
|
||||
Read his <a href="http://blog.rowsandall.com/">blog</a>
|
||||
<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>
|
||||
<div style="text-align: right; padding: 2em">
|
||||
<a href="http://blog.rowsandall.com/">
|
||||
<img src="/static/img/sander.jpg" width="80"></a>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="grid_6 alpha">
|
||||
<div class="grid_2 prefix_1 alpha">
|
||||
<a class="button small green" href="/rowers/courses/upload">Add Course</a>
|
||||
</div>
|
||||
<p> </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">
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -22,6 +22,13 @@
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="grid_2">
|
||||
{% if plannedsession.sessiontype == 'coursetest' %}
|
||||
<a class="button small gray" href="/rowers/list-courses">Courses</a>
|
||||
{% else %}
|
||||
|
||||
{% 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>
|
||||
|
||||
|
||||
|
||||
@@ -489,6 +489,7 @@ urlpatterns = [
|
||||
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:
|
||||
|
||||
@@ -62,7 +62,7 @@ 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
|
||||
@@ -6051,9 +6051,20 @@ def boxplot_view(request,userid=0,
|
||||
def courses_view(request):
|
||||
r = getrower(request.user)
|
||||
|
||||
courses = GeoCourse.objects.all().order_by("country")
|
||||
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,
|
||||
@@ -8444,7 +8455,40 @@ def course_edit_view(request,id=0):
|
||||
|
||||
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)
|
||||
|
||||
@@ -8453,7 +8497,8 @@ def course_edit_view(request,id=0):
|
||||
'course':course,
|
||||
'mapscript':script,
|
||||
'mapdiv':div,
|
||||
'nosessions':nosessions,
|
||||
'nosessions':False,
|
||||
'rower':r,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -8945,9 +8990,15 @@ def course_upload_view(request):
|
||||
if f is not None:
|
||||
filename,path_and_filename = handle_uploaded_file(f)
|
||||
|
||||
polygons = courses.kmltocourse(path_and_filename)
|
||||
cs = courses.kmltocourse(path_and_filename)
|
||||
|
||||
course = courses.createcourse(r,name,polygons,notes=notes)
|
||||
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)
|
||||
@@ -12571,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")
|
||||
|
||||
@@ -12623,6 +12680,8 @@ def plannedsession_view(request,id=0,rowerid=0,
|
||||
'plannedsession':ps,
|
||||
'timeperiod':timeperiod,
|
||||
'ranking':ranking,
|
||||
'coursescript': coursescript,
|
||||
'coursediv': coursediv
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user