Private
Public Access
1
0

Merge branch 'feature/kmlapi' into develop

This commit is contained in:
2024-03-01 09:04:46 +01:00
15 changed files with 1331 additions and 878 deletions
+157 -16
View File
@@ -4,6 +4,7 @@ from rowers.models import (
Rower, Workout,
GeoPoint, GeoPolygon, GeoCourse,
course_length, course_coord_center, course_coord_maxmin,
course_coord_crewnerd_navigation,
polygon_coord_center, PlannedSession,
polygon_to_path, coordinate_in_path
)
@@ -22,6 +23,7 @@ import time
from django.db import IntegrityError
import uuid
from django.conf import settings
import math
import geocoder
@@ -120,6 +122,50 @@ xmlns_uris_dict = {'gx': 'http://www.google.com/kml/ext/2.2',
'': "http://www.opengis.net/kml/2.2"}
def get_polar_angle(point, reference_point):
"""
Calculate the polar angle of a point with respect to a reference point.
"""
try:
delta_x = point.longitude - reference_point.longitude
delta_y = point.latitude - reference_point.latitude
except AttributeError:
delta_x = point['longitude'] - reference_point.longitude
delta_y = point['latitude'] - reference_point.latitude
return math.atan2(delta_y, delta_x)
class Coordinate:
def __init__(self, latitude, longitude):
self.latitude = latitude
self.longitude = longitude
def get_coordinates_centerpoint(coordinates):
try:
centroid_latitude = sum(coord.latitude for coord in coordinates) / len(coordinates)
centroid_longitude = sum(coord.longitude for coord in coordinates) / len(coordinates)
centroid = Coordinate(centroid_latitude, centroid_longitude)
except AttributeError:
centroid_latitude = sum(coord['latitude'] for coord in coordinates) / len(coordinates)
centroid_longitude = sum(coord['longitude'] for coord in coordinates) / len(coordinates)
centroid = Coordinate(centroid_latitude, centroid_longitude)
return centroid
def sort_coordinates_ccw(coordinates):
"""
Sort coordinates in counterclockwise order.
"""
# Find the reference point (centroid)
centroid = get_coordinates_centerpoint(coordinates)
# Sort coordinates based on polar angle with respect to the centroid
sorted_coords = sorted(coordinates, key=lambda coord: get_polar_angle(coord, centroid))
return sorted_coords
def crewnerdcourse(doc):
courses = []
for course in doc:
@@ -162,6 +208,8 @@ def get_polygons(polygonpms):
'latitude': float(coordinates[1]),
})
points = sort_coordinates_ccw(points)
polygons.append({
'name': name,
'points': points
@@ -169,34 +217,45 @@ def get_polygons(polygonpms):
return polygons
def crewnerdify(polygons):
polygons[0].name = "Start"
polygons[len(polygons)-1].name = "Finish"
def coursetokml(course):
top = Element('kml')
for prefix, uri in xmlns_uris_dict.items():
if prefix != '':
top.attrib['xmlns:' + prefix] = uri
else:
top.attrib['xmlns'] = uri
if len(polygons) > 2:
for i in range(1,len(polygons)-1):
polygons[i].name = 'WP{n}'.format(n=polygons[i].order_in_course)
document = SubElement(top, 'Document')
name = SubElement(document, 'name')
name.text = 'Courses.kml'
folder = SubElement(document, 'Folder')
foldername = SubElement(folder, 'name')
foldername.text = 'Courses'
folder2 = SubElement(folder, 'Folder')
return polygons
def removewhitespace(s):
regels = s.split('\n')
regels = [regel for regel in regels if regel.strip()]
return "\n".join(regels)
def getcoursefolder(course, document, cn=False):
folder2 = SubElement(document, 'Folder', id="{id}".format(id=course.id))
coursename = SubElement(folder2, 'name')
coursename.text = course.name
open = SubElement(folder2, 'open')
open.text = '1'
openst = SubElement(folder2, 'open')
openst.text = '1'
coursedescription = SubElement(folder2, 'description')
if len(removewhitespace(course.notes)):
coursedescription.text = "Rowsandall.com\n" + removewhitespace(course.notes)
else:
coursedescription.text = "Rowsandall.com " + course.name
polygons = GeoPolygon.objects.filter(
course=course).order_by("order_in_course")
if cn:
polygons = crewnerdify(polygons)
for polygon in polygons:
placemark = SubElement(folder2, 'Placemark')
polygonname = SubElement(placemark, 'name')
polygonname.text = polygon.name
polygonstyle = SubElement(placemark, 'styleUrl')
polygonstyle.text = "#default0"
p = SubElement(placemark, 'Polygon')
tessellate = SubElement(p, 'tessellate')
tessellate.text = '1'
@@ -206,6 +265,7 @@ def coursetokml(course):
coordinates.text = ''
points = GeoPoint.objects.filter(
polygon=polygon).order_by("order_in_poly")
points = sort_coordinates_ccw(points)
for point in points:
coordinates.text += '{lon},{lat},0 '.format(
lat=point.latitude,
@@ -215,6 +275,87 @@ def coursetokml(course):
lat=points[0].latitude,
lon=points[0].longitude,
)
return folder2
def getstyle(document, id):
style = SubElement(document,'Style', id=id)
iconstyle = SubElement(style, 'IconStyle')
scale = SubElement(iconstyle, 'scale')
scale.text = "1.2"
linestyle = SubElement(style, 'LineStyle')
linestylecolor = SubElement(linestyle, 'color')
linestylecolor.text = "ff00ffff"
polystyle = SubElement(style, 'PolyStyle')
polystylecolor = SubElement(polystyle, 'color')
polystylecolor.text = "ff7fffff"
return style
def getstylemap(document, id):
stylemap = SubElement(document, 'StyleMap', id=id)
pair1 = SubElement(stylemap, 'Pair')
pair1key = SubElement(pair1, 'key')
pair1key.text = "normal"
pair1styleurl = SubElement(pair1, 'styleUrl')
pair1styleurl.text = "#default"
pair2 = SubElement(stylemap, 'Pair')
pair2key = SubElement(pair2, 'key')
pair2key.text = "highlight"
pair2styleurl = SubElement(pair2, 'styleUrl')
pair2styleurl.text = "#hl"
return stylemap
def coursestokml(courseids, cn=False):
courses = GeoCourse.objects.filter(id__in=courseids)
top = Element('kml')
for prefix, uri in xmlns_uris_dict.items():
if prefix != '':
top.attrib['xmlns:' + prefix] = uri
else:
top.attrib['xmlns'] = uri
document = SubElement(top, 'Document')
name = SubElement(document, 'name')
name.text = 'courses'
opendoc = SubElement(document,'open')
opendoc.text = '1'
style = getstyle(document, "default")
stylemap = getstylemap(document, 'default0')
stylehl = getstyle(document, "hl")
for course in courses:
coursefolder = getcoursefolder(course, document, cn=cn)
return prettify(top)
def coursetokml(course,cn=False):
top = Element('kml')
for prefix, uri in xmlns_uris_dict.items():
if prefix != '':
top.attrib['xmlns:' + prefix] = uri
else:
top.attrib['xmlns'] = uri
document = SubElement(top, 'Document')
name = SubElement(document, 'name')
name.text = 'courses'
opendoc = SubElement(document,'open')
opendoc.text = '1'
style = getstyle(document, "default")
stylemap = getstylemap(document, 'default0')
stylehl = getstyle(document, "hl")
coursefolder = getcoursefolder(course, document, cn=cn)
return prettify(top)
+3 -1
View File
@@ -33,7 +33,7 @@ import datetime
from rowers import mytypes
from rowers.courses import (
course_coord_center, course_coord_maxmin,
polygon_coord_center
polygon_coord_center, course_coord_crewnerd_navigation,
)
from django.conf import settings
from collections import OrderedDict
@@ -2165,6 +2165,8 @@ def interactive_histoall(theworkouts, histoparam, includereststrokes,
def course_map(course):
latmean, lonmean, coordinates = course_coord_center(course)
if course.with_cn_nav_waypoints:
latmean, lonmean, coordinates = course_coord_crewnerd_navigation(course)
lat_min, lat_max, long_min, long_max = course_coord_maxmin(course)
coordinates = course_spline(coordinates)
+77 -1
View File
@@ -8,7 +8,7 @@ from rowers.utils import (
steps_read_fit, steps_write_fit, ps_dict_order, uniqify
)
from rowers.metrics import axlabels
from rowers.utils import geo_distance
from rowers.utils import geo_distance, move_one_meter
from rowers.formfields import *
from rowers.database import *
import uuid
@@ -594,6 +594,64 @@ def course_spline(coordinates):
return newcoordinates
def polygon_nearest_point(polygon, latitude, longitude,debug=False):
points = GeoPoint.objects.filter(polygon=polygon)
points = sorted(points, key = lambda p: geo_distance(p.latitude, p.longitude, latitude, longitude))
#if debug:
# for p in points:
# print(p,p.latitude, p.longitude, latitude, longitude, geo_distance(p.latitude, p.longitude, latitude, longitude))
return points[0].latitude, points[0].longitude
def polygon_exit_point(polygon, lat1, lon1, lat2, lon2):
dist, bearing = geo_distance(lat1, lon1, lat2, lon2)
dirveclat = (lat2-lat1)/dist
dirveclon = (lon2-lon1)/dist
newlat, newlon = move_one_meter(lat2, lon2, bearing)
path = polygon_to_path(polygon)
while path.contains_points([(newlat, newlon)])[0]:
newlat, newlon = move_one_meter(newlat, newlon, bearing)
return newlat, newlon
def course_coord_crewnerd_navigation(course):
polygons = GeoPolygon.objects.filter(
course=course).order_by("order_in_course")
latitudes = []
longitudes = []
latitude, longitude = polygon_coord_center(polygons[0])
latitudes.append(latitude)
longitudes.append(longitude)
debug = True
for p in polygons[1:]:
oldlat = latitude
oldlon = longitude
latitude, longitude = polygon_nearest_point(p,latitude,longitude, debug=debug)
debug = False
latitudes.append(latitude)
longitudes.append(longitude)
latitude, longitude = polygon_exit_point(p, oldlat, oldlon, latitude, longitude)
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_center(course):
@@ -1590,6 +1648,24 @@ class GeoCourse(models.Model):
def coord(self):
return course_coord_center(self)
@property
def with_cn_nav_waypoints(self):
polygons = GeoPolygon.objects.filter(course=self).order_by("order_in_course")
if polygons[0].name != "Start":
return False
if polygons[len(polygons)-1].name != "Finish":
return False
for i in range(1,len(polygons)-1):
if polygons[i].name[0:2].lower() != 'wp':
return False
try:
getal = float(polygons[i].name[2:])
except ValueError:
return False
return True
class GeoCourseEditForm(ModelForm):
class Meta:
+22 -22
View File
@@ -94,7 +94,7 @@ SITE_URL_DEV = CFG['site_url']
PROGRESS_CACHE_SECRET = CFG['progress_cache_secret']
try:
SETTINGS_NAME = CFG['settings_name']
except KeyError:
except KeyError: # pragma: no cover
SETTINGS_NAME = 'rowsandall_ap.settings'
try:
@@ -321,7 +321,7 @@ def summaryfromsplitdata(splitdata, data, filename, sep='|', workouttype='rower'
return sums, sa, results
@app.task
def handle_post_workout_api(uploadoptions, debug=False, **kwargs):
def handle_post_workout_api(uploadoptions, debug=False, **kwargs): # pragma: no cover
session = requests.session()
newHeaders = {'Content-type': 'application/json', 'Accept': 'text/plain'}
session.headers.update(newHeaders)
@@ -335,14 +335,14 @@ def handle_post_workout_api(uploadoptions, debug=False, **kwargs):
@app.task
def handle_remove_workouts_team(ws, t, debug=False, **kwargs):
def handle_remove_workouts_team(ws, t, debug=False, **kwargs): # pragma: no cover
for w in ws:
w.team.remove(t)
return 1
@app.task
def handle_add_workouts_team(ws, t, debug=False, **kwargs):
def handle_add_workouts_team(ws, t, debug=False, **kwargs): # pragma: no cover
for w in ws:
w.team.add(t)
@@ -350,7 +350,7 @@ def handle_add_workouts_team(ws, t, debug=False, **kwargs):
return 1
def uploadactivity(access_token, filename, description='',
name='Rowsandall.com workout'):
name='Rowsandall.com workout'): # pragma: no cover
data_gz = BytesIO()
try:
@@ -393,7 +393,7 @@ def uploadactivity(access_token, filename, description='',
@app.task
def check_tp_workout_id(workout, location, attempts=5, debug=False, **kwargs):
def check_tp_workout_id(workout, location, attempts=5, debug=False, **kwargs): # pragma: no cover
authorizationstring = str('Bearer ' + workout.user.tptoken)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
@@ -411,7 +411,7 @@ def check_tp_workout_id(workout, location, attempts=5, debug=False, **kwargs):
return 1
@app.task
def handle_workout_tp_upload(w, thetoken, tcxfilename, debug=False, **kwargs):
def handle_workout_tp_upload(w, thetoken, tcxfilename, debug=False, **kwargs): # pragma: no cover
tpid = 0
r = w.user
if not tcxfilename:
@@ -443,7 +443,7 @@ def handle_workout_tp_upload(w, thetoken, tcxfilename, debug=False, **kwargs):
return tpid
@app.task
def instroke_static(w, metric, debug=False, **kwargs):
def instroke_static(w, metric, debug=False, **kwargs): # pragma: no cover
f1 = w.csvfilename[6:-4]
rowdata = rdata(csvfile=w.csvfilename)
@@ -512,7 +512,7 @@ def handle_c2_sync(workoutid, url, headers, data, debug=False, **kwargs):
try:
workout = Workout.objects.get(id=workoutid)
except Workout.DoesNotExist:
except Workout.DoesNotExist: # pragma: no cover
dologging('c2_log.log','failed for c2id {c2id}'.format(c2id=c2id))
return 0
@@ -531,7 +531,7 @@ def handle_c2_sync(workoutid, url, headers, data, debug=False, **kwargs):
return 1
def splitstdata(lijst):
def splitstdata(lijst): # pragma: no cover
t = []
latlong = []
while len(lijst) >= 2:
@@ -543,7 +543,7 @@ def splitstdata(lijst):
@app.task
def handle_sporttracks_workout_from_data(user, importid, source,
workoutsource, debug=False, **kwargs):
workoutsource, debug=False, **kwargs): # pragma: no cover
r = user.rower
authorizationstring = str('Bearer ' + r.sporttrackstoken)
@@ -812,7 +812,7 @@ def handle_strava_sync(stravatoken,
if not failed:
try:
workout = Workout.objects.get(id=workoutid)
except Workout.DoesNotExist:
except Workout.DoesNotExist: # pragma: no cover
return 0
workout.uploadedtostrava = res.id
@@ -1562,7 +1562,7 @@ def handle_calctrimp(id,
try:
workout = Workout.objects.get(id=id)
except Workout.DoesNotExist:
except Workout.DoesNotExist: # pragma: no cover
dologging('metrics.log','Could not find workout {id}'.format(id=id))
return 0
@@ -2037,7 +2037,7 @@ def handle_sendemail_expired(useremail, userfirstname, userlastname, expireddate
return 1
@app.task
def handle_sendemail_newftp(rower,power,mode, **kwargs):
def handle_sendemail_newftp(rower,power,mode, **kwargs): # pragma: no cover
subject = "You may want to update your FTP on rowsandall.com"
from_email = 'Rowsandall <info@rowsandall.com>'
@@ -2069,7 +2069,7 @@ def handle_sendemail_breakthrough(workoutid, useremail,
lastname = ''
if surname:
if surname: # pragma: no cover
lastname = userlastname
tablevalues = [
@@ -2122,7 +2122,7 @@ def handle_sendemail_hard(workoutid, useremail,
]
lastname = ''
if surname:
if surname: # pragma: no cover
lastname = userlastname
# send email with attachment
@@ -3414,7 +3414,7 @@ def handle_nk_async_workout(alldata, userid, nktoken, nkid, delaysec, defaulttim
jsonData = response.json()
try:
strokeData = jsonData[str(nkid)]
except KeyError:
except KeyError: # pragma: no cover
dologging('nklog.log','Could not find strokeData')
return 0
@@ -3424,7 +3424,7 @@ def handle_nk_async_workout(alldata, userid, nktoken, nkid, delaysec, defaulttim
if oarlockSessions:
oarlocksession = oarlockSessions[0]
seatNumber = oarlocksession['seatNumber']
except KeyError:
except KeyError: # pragma: no cover
pass
df = strokeDataToDf(strokeData, seatIndex=seatNumber)
@@ -3439,14 +3439,14 @@ def handle_nk_async_workout(alldata, userid, nktoken, nkid, delaysec, defaulttim
workoutid, error = add_workout_from_data(userid, nkid, data, df)
# dologging('nklog.log','NK Workout ID {id}'.format(id=workoutid))
if workoutid == 0:
if workoutid == 0: # pragma: no cover
return 0
try:
workout = Workout.objects.get(id=workoutid)
newnkid = workout.uploadedtonk
sr = create_or_update_syncrecord(workout.user, workout, nkid=newnkid)
except Workout.DoesNotExist:
except Workout.DoesNotExist: # pragma: no cover
pass
return workoutid
@@ -3775,7 +3775,7 @@ def handle_c2_async_workout(alldata, userid, c2token, c2id, delaysec,
return workoutid
@app.task
def fetch_rojabo_session(id,alldata,userid,rowerid,debug=False, **kwargs):
def fetch_rojabo_session(id,alldata,userid,rowerid,debug=False, **kwargs): # pragma: no cover
try:
item = alldata[id]
except KeyError:
@@ -3880,7 +3880,7 @@ def fetch_strava_workout(stravatoken, oauth_data, stravaid, csvfilename, userid,
wsize = round(5./dt)
velo2 = ewmovingaverage(velo, wsize)
except ValueError:
except ValueError: # pragma: no cover
velo2 = velo
if coords is not None:
+29 -22
View File
@@ -23,30 +23,36 @@
<li class="grid_3">
{% if courses %}
<p>
<table width="100%" class="listtable shortpadded">
<thead>
<tr>
<th> Country</th>
<th> Name</th>
<th> Distance</th>
</tr>
</thead>
<tbody>
{% for course in courses %}
<tr>
<td> {{ course.country }} </td>
<td>
<a href="/rowers/courses/{{ course.id }}/">{{ course.name }}</a>
</td>
<td>
{{ course.distance }} m
</td>
<form enctype="multipart/form-data" method="post">
{% csrf_token %}
<input name="courses" type="submit" value="Download selected courses">
<table width="100%" class="listtable shortpadded">
<thead>
<tr>
<th> Download</th>
<th> Country</th>
<th> Name</th>
<th> Distance</th>
</tr>
</thead>
<tbody>
{% for course in courses %}
<tr>
<td> <input type="checkbox" value={{ course.id }} name="courseid"> </td>
<td> {{ course.country }} </td>
<td>
<a href="/rowers/courses/{{ course.id }}/">{{ course.name }}</a>
</td>
<td>
{{ course.distance }} m
</td>
</tr>
</tr>
{% endfor %}
</tbody>
</table>
{% endfor %}
</tbody>
</table>
</form>
</p>
{% else %}
<p> No courses found </p>
@@ -106,6 +112,7 @@
<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.
There is also a <a href="https://performancephones.com/custom-courses/">written tutorial</a> by CrewNerd.
</p>
<p>
File diff suppressed because it is too large Load Diff
+22
View File
@@ -7,6 +7,7 @@ from __future__ import unicode_literals
from .statements import *
nu = datetime.datetime.now()
import rowers.courses as courses
tested = [
@@ -27,6 +28,14 @@ class URLTests(TestCase):
r = Rower.objects.create(user=u,rowerplan='coach',gdproptin=True, ftpset=True,
gdproptindate=timezone.now())
self.c = Client()
cs = courses.kmltocourse('rowers/tests/testdata/thyro.kml')
course = cs[0]
cname = course['name']
cnotes = course['description']
polygons = course['polygons']
self.ThyroBaantje = courses.createcourse(r,cname,polygons,notes=cnotes)
self.ThyroBaantje.save()
self.nu = datetime.datetime.now()
filename = 'rowers/tests/testdata/testdata.csv'
@@ -168,6 +177,19 @@ class URLTests(TestCase):
'/rowers/workout/upload/team/',
'/rowers/workouts-join/',
'/rowers/workouts-join-select/',
'/rowers/api/courses/',
'/rowers/api/courses/?name=Brno',
'/rowers/api/courses/?course_distance=2000',
'/rowers/api/courses/?course_distance=aap',
'/rowers/api/courses/?distance_from=52&latitude=42&longitude=7',
'/rowers/api/courses/?distance_from=50&latitude=42&longitude=-aap',
'/rowers/api/courses/',
'/rowers/api/courses/1/',
'/rowers/list-workouts/?selectworkouts=true',
'/rowers/api/courses/kml/',
'/rowers/api/courses/kml/?id=1',
'/rowers/api/courses/kml?id=1&id=4/',
'/rowers/api/courses/kml/?id=aap',
]
Binary file not shown.
+3
View File
@@ -251,6 +251,9 @@ urlpatterns = [
name='strokedatajson_v3'),
re_path(r'^api/TCX/workouts/$', views.strokedata_tcx,
name='strokedata_tcx'),
re_path(r'^api/courses/$', views.course_list, name='course_list'),
re_path(r'^api/courses/(?P<id>\d+)/$', views.get_crewnerd_kml, name='get_crewnerd_kml'),
re_path(r'^api/courses/kml/$', views.get_crewnerd_multiple, name='get_crewnerd_multiple'),
re_path(r'^500v/$', views.error500_view, name='error500_view'),
re_path(r'^500q/$', views.servererror_view, name='servererror_view'),
path('502/', TemplateView.as_view(template_name='502.html'), name='502'),
+25
View File
@@ -312,6 +312,31 @@ def geo_distance(lat1, lon1, lat2, lon2):
return [distance, bearing]
def move_one_meter(latitude, longitude, bearing):
# Earth radius in meters
earth_radius = 6371000
# Convert latitude and longitude from degrees to radians
lat_rad = math.radians(latitude)
lon_rad = math.radians(longitude)
# Convert bearing from degrees to radians
bearing_rad = math.radians(bearing)
# Calculate the new latitude
new_lat = math.asin(math.sin(lat_rad) * math.cos(1/earth_radius) +
math.cos(lat_rad) * math.sin(1/earth_radius) * math.cos(bearing_rad))
# Calculate the new longitude
new_lon = lon_rad + math.atan2(math.sin(bearing_rad) * math.sin(1/earth_radius) * math.cos(lat_rad),
math.cos(1/earth_radius) - math.sin(lat_rad) * math.sin(new_lat))
# Convert new latitude and longitude from radians to degrees
new_lat = math.degrees(new_lat)
new_lon = math.degrees(new_lon)
return new_lat, new_lon
def isbreakthrough(delta, cpvalues, p0, p1, p2, p3, ratio):
pwr = abs(p0)/(1+(delta/abs(p2)))
+3 -2
View File
@@ -2680,9 +2680,10 @@ def history_view(request, userid=0):
last28 = timezone.now() - datetime.timedelta(days=28)
today = timezone.now()
lastyear = today - datetime.timedelta(days=365)
lastyear = datetime.datetime(
year=today.year - 1, month=today.month, day=today.day)
#lastyear = datetime.datetime(
# year=today.year - 1, month=today.month, day=today.day)
firstmay = datetime.datetime(
year=today.year, month=5, day=1).astimezone(usertimezone)
+129 -16
View File
@@ -1,6 +1,7 @@
from rowers.views.statements import *
from rowers.tasks import handle_calctrimp
from rowers.opaque import encoder
from rowers.courses import coursetokml, coursestokml
from xml.etree import ElementTree as ET
import arrow
@@ -11,6 +12,8 @@ from rowers.dataroutines import get_workouttype_from_tcx, get_startdate_time_zon
from rest_framework.decorators import parser_classes
from rest_framework.parsers import BaseParser
from rowers.utils import geo_distance
from datetime import datetime as dt
import rowingdata.tcxtools as tcxtools
@@ -24,7 +27,7 @@ class XMLParser(BaseParser):
dologging("apilog.log", "XML Parser")
try:
s = ET.parse(stream).getroot()
except Exception as e:
except Exception as e: # pragma: no cover
dologging("apilog.log",e)
return HttpResponse(status=500)
return s
@@ -228,13 +231,123 @@ def strokedataform_v2(request, id=0):
def part_of_day(hour):
if 5 <= hour < 12:
return "Morning"
elif 12 <= hour < 18:
elif 12 <= hour < 18: # pragma: no cover
return "Afternoon"
elif 18 <= hour < 24:
elif 18 <= hour < 24: # pragma: no cover
return "Evening"
else:
else: # pragma: no cover
return "Night"
# KML API views
"""
- Get a list of courses OK
- Nearby a certain coordinate
- Filtered by
- Get a (KML) course (in response.content rather than as attachment) OK
- Get multiple courses as one KML in response.content OK
- GET with parameters?
Optional, not for CN
- Create one or more new courses from KML
- Should check for duplicates (Placemark ID)
- Update one or more new courses from KML
"""
@api_view(["GET"])
@permission_classes([AllowAny])
def course_list(request):
if request.method != 'GET': # pragma: no cover
dologging('apilog.log','{m} request to KML endpoint'.format(m=request.method))
return HttpResponseNotAllowed("Method not supported")
query_data = {}
name = request.GET.get('name')
distance = request.GET.get('course_distance')
latitude = request.GET.get('latitude')
longitude = request.GET.get('longitude')
distance_from = request.GET.get('distance_from')
if name is not None:
query_data['name__contains'] = name
if distance is not None:
try:
query_data['distance__lte'] = int(distance)+50
query_data['distance__gte'] = int(distance)-50
except:
pass
courses = GeoCourse.objects.filter(**query_data)
if latitude is not None and longitude is not None and distance_from is not None:
try:
newlist = []
for c in courses:
distance = geo_distance(float(latitude), float(longitude), c.coord[0], c.coord[1])[0]
if distance < float(distance_from): # pragma: no cover
newlist.append(c)
courses = newlist
except ValueError:
pass
courselist = []
for c in courses:
d = {
'id': c.id,
'name':c.name,
'country':c.country,
'distance':c.distance,
'notes':c.notes,
'location':(c.coord[0], c.coord[1]),
}
courselist.append(d)
response_dict = {'courses': courselist}
return JsonResponse(response_dict, content_type='application/json; charset=utf8')
@api_view(["GET"])
@permission_classes([AllowAny])
def get_crewnerd_kml(request,id=0):
if request.method != 'GET': # pragma: no cover
dologging('apilog.log','{m} request to CrewNerd KML endpoint'.format(m=request.method))
return HttpResponseNotAllowed("Method not supported")
try:
c = GeoCourse.objects.get(id=id)
except GeoCourse.DoesNotExist: # pragma: no cover
raise Http404("This course does not exist")
kml = coursetokml(c, cn=True)
return HttpResponse(kml)
#@csrf_exempt
@api_view(["GET"])
@permission_classes([AllowAny])
def get_crewnerd_multiple(request):
if request.method != 'GET': # pragma: no cover
dologging('apilog.log','{m} request to CrewNerd KML endpoint'.format(m=request.method))
return HttpResponseNotAllowed("Method not supported")
ids = request.GET.get('id')
if ids is not None:
tdict = dict(request.GET.lists())
idsnew = []
for id in tdict['id']:
try:
idsnew.append(int(id))
except ValueError:
pass
ids = idsnew
else:
gcs = GeoCourse.objects.all()
ids = [c.id for c in gcs]
kml = coursestokml(ids, cn=True)
return HttpResponse(kml)
# Stroke data views
@csrf_exempt
@login_required()
@api_view(["POST"])
@@ -244,11 +357,11 @@ def strokedata_tcx(request):
"""
Upload a TCX file through API
"""
if request.method != 'POST':
if request.method != 'POST': # pragma: no cover
dologging('apilog.log','GET request to TCX endpoint')
return HttpResponseNotAllowed("Method not supported") # pragma: no cover
return HttpResponseNotAllowed("Method not supported")
if 'application/xml' not in request.content_type.lower():
if 'application/xml' not in request.content_type.lower(): # pragma: no cover
dologging('apilog.log','POST data not application/xml, request to TCX endpoint')
dologging('apilog.log', request.content_type.lower())
return HttpResponseNotAllowed("Need application/xml")
@@ -275,7 +388,7 @@ def strokedata_tcx(request):
lap_duration_seconds = float(lap_duration_node.text)
total_duration += lap_duration_seconds
except Exception as e:
except Exception as e: # pragma: no cover
dologging('apilog.log','TCX')
dologging('apilog.log',e)
return HttpResponseNotAllowed("Could not parse TCX data")
@@ -329,7 +442,7 @@ def strokedata_tcx(request):
"workout id": workoutid,
"status": "success",
})
except Exception as e:
except Exception as e: # pragma: no cover
dologging('apilog.log','TCX API endpoint')
dologging('apilog.log',e)
return HttpResponse(status=500)
@@ -382,7 +495,7 @@ def strokedatajson_v3(request):
title = request.data.get('name','')
try:
elapsedTime = request.data['elapsedTime']
except KeyError:
except KeyError: # pragma: no cover
try:
duration = request.data['duration']
try:
@@ -394,7 +507,7 @@ def strokedatajson_v3(request):
return HttpResponse("Missing Elapsed Time", status=400)
try:
totalDistance = request.data['distance']
except KeyError:
except KeyError: # pragma: no cover
return HttpResponse("Missing Total Distance", status=400)
timeZone = request.data.get('timezone','UTC')
workouttype = request.data.get('workouttype','rower')
@@ -415,12 +528,12 @@ def strokedatajson_v3(request):
df = pd.DataFrame()
try:
strokes = request.data['strokes']
except KeyError:
except KeyError: # pragma: no cover
return HttpResponse("No Stroke Data in JSON", status=400)
try:
df = pd.DataFrame(strokes['data'])
except KeyError:
except KeyError: # pragma: no cover
try:
df = pd.DataFrame(request.data['strokedata'])
except:
@@ -431,7 +544,7 @@ def strokedatajson_v3(request):
status, comment, data = api_get_dataframe(startdatetime, df)
if status != 200:
if status != 200: # pragma: no cover
return HttpResponse(comment, status=status)
@@ -541,7 +654,7 @@ def strokedatajson_v2(request, id):
try:
for d in request.data['data']:
dologging('apilog.log',json.dumps(d))
except KeyError:
except KeyError: # pragma: no cover
try:
for d in request.data['strokedata']:
dologging('apilog.log',json.dumps(d))
@@ -565,7 +678,7 @@ def strokedatajson_v2(request, id):
df.sort_index(inplace=True)
status, comment, data = api_get_dataframe(row.startdatetime, df)
if status != 200:
if status != 200: # pragma: no cover
return HttpResponse(comment, status=status)
r = getrower(request.user)
+20 -1
View File
@@ -7,7 +7,7 @@ from django.contrib.gis.geoip2 import GeoIP2
from django import forms
from rowers.plannedsessions import timefield_to_seconds_duration
from rowers.courses import getnearestraces, getnearestcourses
from rowers.courses import getnearestraces, getnearestcourses,coursetokml, coursestokml
# List Courses
@@ -16,6 +16,25 @@ def courses_view(request):
r = getrower(request.user)
g = GeoIP2()
if request.method == 'POST':
try:
tdict = dict(request.POST.lists())
ids = tdict['courseid']
courseids = [int(id) for id in ids]
kmlstring = coursestokml(courseids)
kmlfilename = 'courses.kml'
response = HttpResponse(kmlstring)
response['Content-Disposition'] = 'attachment; filename="{filename}"'.format(
filename=kmlfilename)
response['Content-Type'] = 'application/octet-stream'
return response
except KeyError:
pass
ip = request.META.get('HTTP_X_REAL_IP', '1.1.1.1')
try:
lat_lon = g.lat_lon(ip)
+1 -1
View File
@@ -22,7 +22,7 @@ from scipy.special import lambertw
from io import BytesIO
import rowers.plots as plots
from rowers.permissions import IsOwnerOrNot, IsCompetitorOrNot
from rest_framework.permissions import IsAuthenticated
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.decorators import api_view, renderer_classes, permission_classes
from rest_framework.response import Response
from rq.job import Job
+7 -7
View File
@@ -1664,7 +1664,7 @@ def course_compare_view(request, id=0):
labeldict = {
int(w.id): w.__str__() for w in workouts
}
except:
except: # pragma: no cover
labeldict = {}
res = interactive_multiple_compare_chart(workoutids, xparam, yparam,
@@ -1846,7 +1846,7 @@ def virtualevent_compare_view(request, id=0):
labeldict = {
int(w.id): w.__str__() for w in workouts
}
except:
except: # pragma: no cover
labeldict = {}
res = interactive_multiple_compare_chart(workoutids, xparam, yparam,
@@ -2048,9 +2048,9 @@ def workouts_bulk_actions(request):
w = get_workout_by_opaqueid(request, encid)
if w.user == r:
workouts.append(w)
else:
else: # pragma: no cover
messages.error(request,'Bulk actions are not accessible to coaches')
except KeyError:
except KeyError: # pragma: no cover
pass
if request.method == 'POST':
@@ -2058,7 +2058,7 @@ def workouts_bulk_actions(request):
form = WorkoutMultipleCompareForm(request.POST)
if form.is_valid() and actionform.is_valid():
workouts = form.cleaned_data['workouts']
if len(workouts) == 0:
if len(workouts) == 0: # pragma: no cover
url = reverse('workouts_view')
return HttpResponseRedirect(url)
action = actionform.cleaned_data['action']
@@ -2078,14 +2078,14 @@ def workouts_bulk_actions(request):
'Workout {id} exported to {destination}'.format(
id=encoder.encode_hex(w.id),
destination=destination))
except NoTokenError:
except NoTokenError: # pragma: no cover
messages.error(request,
'Export to {destination} of workout {id} failed'.format(
id=encoder.encode_hex(w.id),
destination=destination))
url = reverse('workouts_view')
return HttpResponseRedirect(url)
else:
else: # pragma: no cover
if len(workouts) == 0:
url = reverse(workouts_view)
return HttpResponseRedirect(url)