diff --git a/rowers/admin.py b/rowers/admin.py
index e1094553..14599dc5 100644
--- a/rowers/admin.py
+++ b/rowers/admin.py
@@ -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)
diff --git a/rowers/courses.py b/rowers/courses.py
index 2d35363c..9857b230 100644
--- a/rowers/courses.py
+++ b/rowers/courses.py
@@ -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
diff --git a/rowers/forms.py b/rowers/forms.py
index 3d5281d5..c5ffa552 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,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)
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index cbe08d1e..6b3ae2e4 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -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("{name} ");
+
+ """.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 = """
+
+ """.format(
+ latmean=latmean,
+ lonmean=lonmean,
+ scoordinates=scoordinates,
+ pcoordinates=pcoordinates,
+ plabels = plabels
+ )
+
+ div = """
+
+ """
+
+ return script,div
def leaflet_chart(lat,lon,name=""):
if lat.empty or lon.empty:
diff --git a/rowers/models.py b/rowers/models.py
index 6d380cc2..ab4f613f 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -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):
diff --git a/rowers/rows.py b/rowers/rows.py
index 6aa7401a..2aed8e12 100644
--- a/rowers/rows.py
+++ b/rowers/rows.py
@@ -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
diff --git a/rowers/templates/course_edit_view.html b/rowers/templates/course_edit_view.html
new file mode 100644
index 00000000..64c46e59
--- /dev/null
+++ b/rowers/templates/course_edit_view.html
@@ -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 %}
+
+
+ {% if nosessions %}
+
Delete
+ {% else %}
+
+ {% endif %}
+
+
+ {% if course.manager == rower %}
+
View Course
+ {% else %}
+
+ {% endif %}
+
+
+
+
+
+
{{ course.name }}
+
+
+
+ {{ mapdiv|safe }}
+
+
+ {{ mapscript|safe }}
+
+
+
+
+{% endblock %}
diff --git a/rowers/templates/course_form.html b/rowers/templates/course_form.html
new file mode 100644
index 00000000..de1d0db7
--- /dev/null
+++ b/rowers/templates/course_form.html
@@ -0,0 +1,252 @@
+{% extends "base.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block title %}File loading{% endblock %}
+
+{% block meta %}
+
+
+
+{% endblock %}
+
+{% block content %}
+
+
Drag and drop files here
+
+
+
+
+{% endblock %}
+
+ {% block scripts %}
+
+
+
+ {% endblock %}
diff --git a/rowers/templates/course_view.html b/rowers/templates/course_view.html
new file mode 100644
index 00000000..89b8b03d
--- /dev/null
+++ b/rowers/templates/course_view.html
@@ -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 %}
+
+
+ {% if nosessions %}
+
Delete
+ {% else %}
+
+ {% endif %}
+
+
+ {% if course.manager == rower %}
+
Edit
+ {% else %}
+
+ {% endif %}
+
+
+
+
+
+
{{ course.name }}
+
+
+
+
+ Name {{ course.name }}
+
+
+ Country {{ course.country }}
+
+
+ Notes {{ course.notes }}
+
+
+
+
+ {{ mapdiv|safe }}
+
+
+ {{ mapscript|safe }}
+
+
+
+
+{% endblock %}
diff --git a/rowers/templates/list_courses.html b/rowers/templates/list_courses.html
new file mode 100644
index 00000000..6f486448
--- /dev/null
+++ b/rowers/templates/list_courses.html
@@ -0,0 +1,144 @@
+{% extends "base.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block title %}Rowsandall Courses List{% endblock %}
+
+{% block scripts %}
+
+{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+
Courses
+
+ {% if courses %}
+
+
+
+ Country
+ Name
+
+
+
+ {% for course in courses %}
+
+ {{ course.country }}
+
+ {% if course.manager.user == user %}
+ {{ course.name }}
+ {% else %}
+ {{ course.name }}
+ {% endif %}
+
+
+
+
+ {% endfor %}
+
+
+ {% else %}
+
No courses found
+ {% endif %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% if announcements %}
+
What's New?
+ {% for a in announcements %}
+
+
+ {{ a.created }}:
+ {{ a.announcement|urlize }}
+
+
+ {% endfor %}
+
+ {% endif %}
+
+
+
How-to
+
+ 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 Google Earth
+ to mark the start and finish lines using polygons. The process is identical
+ to creating custom courses for the
+ CrewNerd
+ app.
+
+
+
+
CrewNerd has published a nice video tutorial of the process.
+ Click here to see the video. The part
+ we're interested in starts at 2:05.
+
+
+
+ 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.
+
+
+
+
+ Open Google Earth
+ Create Start polygon
+ Optional: Create First "must row through" polygon
+ Optional: Create subsequent "must row through" polygons
+ Create Finish polygon
+ Save Place as KML file
+ Upload the file to rowsandall.com using the "Add Courses" button
+
+
+
+
You are allowed to have multiple courses in one KML file.
+ Your CrewNerd "courses.kml" file works out of the box
+
+
The site doesn't test for duplicate courses.
+
+
+
+
+
+ {% endblock %}
diff --git a/rowers/templates/plannedsessioncreate.html b/rowers/templates/plannedsessioncreate.html
index 6c1e8f58..4b3830e8 100644
--- a/rowers/templates/plannedsessioncreate.html
+++ b/rowers/templates/plannedsessioncreate.html
@@ -57,7 +57,11 @@
{% endfor %}
+
{% endif %}
+
diff --git a/rowers/templates/plannedsessionedit.html b/rowers/templates/plannedsessionedit.html
index 28847759..fe759a50 100644
--- a/rowers/templates/plannedsessionedit.html
+++ b/rowers/templates/plannedsessionedit.html
@@ -53,6 +53,9 @@
{% endif %}
+
diff --git a/rowers/templates/plannedsessions.html b/rowers/templates/plannedsessions.html
index 2e229d97..cb7b1615 100644
--- a/rowers/templates/plannedsessions.html
+++ b/rowers/templates/plannedsessions.html
@@ -56,7 +56,9 @@
{% endif %}
-
+
{% if plannedsessions %}
diff --git a/rowers/templates/plannedsessions_multiclone_select.html b/rowers/templates/plannedsessions_multiclone_select.html
index 773afaa0..c9624212 100644
--- a/rowers/templates/plannedsessions_multiclone_select.html
+++ b/rowers/templates/plannedsessions_multiclone_select.html
@@ -68,12 +68,8 @@
{% include "planningbuttons.html" %}
-
- {% if theteam %}
-
Coach Overview. Team {{ theteam.name }}
- {% else %}
-
Coach Overview
- {% endif %}
+
+
Clone Multiple Sessions
Select Time Period ({{ timeperiod|verbosetimeperiod }})
diff --git a/rowers/templates/plannedsessionview.html b/rowers/templates/plannedsessionview.html
index 2ee8c3d6..9234b030 100644
--- a/rowers/templates/plannedsessionview.html
+++ b/rowers/templates/plannedsessionview.html
@@ -22,6 +22,13 @@
{% endif %}
+
+ {% if plannedsession.sessiontype == 'coursetest' %}
+
Courses
+ {% else %}
+
+ {% endif %}
+
@@ -128,6 +135,16 @@
+
+
+ {% if coursescript %}
+
Course
+ {{ coursediv|safe }}
+
+ {{ coursescript|safe }}
+ {% endif %}
+
+
diff --git a/rowers/urls.py b/rowers/urls.py
index e2772640..27d9f7cf 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -143,6 +143,8 @@ urlpatterns = [
url(r'^u/(?P
\d+)/list-workouts/(?P\w+.*)/(?P\w+.*)$',views.workouts_view),
url(r'^list-workouts/(?P\w+.*)/(?P\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\d+)/(?P\w+.*)/(?P\w+.*)$',views.team_comparison_select),
url(r'^team-compare-select/team/(?P\d+)/$',views.team_comparison_select),
@@ -484,6 +486,10 @@ urlpatterns = [
url(r'^sessions/rower/(?P\d+)$',views.plannedsessions_view),
url(r'^sessions/(?P[\w\ ]+.*)/rower/(?P\d+)$',views.plannedsessions_view),
url(r'^sessions/(?P[\w\ ]+.*)$',views.plannedsessions_view),
+ url(r'^courses/(?P\d+)/edit$',views.course_edit_view,
+ name='course_edit_view'),
+ url(r'^courses/(?P\d+)/delete$',views.course_delete_view),
+ url(r'^courses/(?P\d+)$',views.course_view),
]
if settings.DEBUG:
diff --git a/rowers/views.py b/rowers/views.py
index 5ed987c4..4f7e770f 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,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
}
)
diff --git a/rowers/weather.py b/rowers/weather.py
index d01d0fed..8f5ba3f5 100644
--- a/rowers/weather.py
+++ b/rowers/weather.py
@@ -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)