Merge branch 'release/v5.92'
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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> </p></div>
|
||||
"""
|
||||
|
||||
return script,div
|
||||
|
||||
def leaflet_chart(lat,lon,name=""):
|
||||
if lat.empty or lon.empty:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
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 %}
|
||||
252
rowers/templates/course_form.html
Normal file
252
rowers/templates/course_form.html
Normal 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">
|
||||
|
||||
</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+' <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 %}
|
||||
56
rowers/templates/course_view.html
Normal file
56
rowers/templates/course_view.html
Normal 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 %}
|
||||
|
||||
{% 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>
|
||||
|
||||
{% endblock %}
|
||||
144
rowers/templates/list_courses.html
Normal file
144
rowers/templates/list_courses.html
Normal 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> </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">
|
||||
<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> </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 %}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
182
rowers/views.py
182
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
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user