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 %} +
+
+ Courses +
+
+
+ +

{{ course.name }}

+ +
+
+ + {{ form.as_table }} +
+ {% csrf_token %} +
+ +
+
+
+
+ {{ 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 %} + +
+
+
+

Upload KML Course File

+ {% if form.errors %} +

+ Please correct the error{{ form.errors|pluralize }} below. +

+ {% endif %} + + + {{ form.as_table }} +
+ {% csrf_token %} +
+ +
+
+ + + + +
+ +{% 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 %} +
+
+ Courses +
+
+
+ +

{{ 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 %} + + + + + + + + + {% for course in courses %} + + + + + + + {% endfor %} + +
Country Name
{{ course.country }} + {% if course.manager.user == user %} + {{ course.name }} + {% else %} + {{ course.name }} + {% endif %} +
+ {% 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 %} +
+ Courses +
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 %} +
+ Courses +
{% endif %} - +
+ Courses +
{% 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

+
+ {% 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)