Private
Public Access
1
0

Merge branch 'release/v20.6.1'

This commit is contained in:
2024-03-26 08:32:45 +01:00
28 changed files with 1824 additions and 1431 deletions
+157 -16
View File
@@ -4,6 +4,7 @@ from rowers.models import (
Rower, Workout, Rower, Workout,
GeoPoint, GeoPolygon, GeoCourse, GeoPoint, GeoPolygon, GeoCourse,
course_length, course_coord_center, course_coord_maxmin, course_length, course_coord_center, course_coord_maxmin,
course_coord_crewnerd_navigation,
polygon_coord_center, PlannedSession, polygon_coord_center, PlannedSession,
polygon_to_path, coordinate_in_path polygon_to_path, coordinate_in_path
) )
@@ -22,6 +23,7 @@ import time
from django.db import IntegrityError from django.db import IntegrityError
import uuid import uuid
from django.conf import settings from django.conf import settings
import math
import geocoder 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"} '': "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): def crewnerdcourse(doc):
courses = [] courses = []
for course in doc: for course in doc:
@@ -162,6 +208,8 @@ def get_polygons(polygonpms):
'latitude': float(coordinates[1]), 'latitude': float(coordinates[1]),
}) })
points = sort_coordinates_ccw(points)
polygons.append({ polygons.append({
'name': name, 'name': name,
'points': points 'points': points
@@ -169,34 +217,45 @@ def get_polygons(polygonpms):
return polygons return polygons
def crewnerdify(polygons):
polygons[0].name = "Start"
polygons[len(polygons)-1].name = "Finish"
def coursetokml(course): if len(polygons) > 2:
top = Element('kml') for i in range(1,len(polygons)-1):
for prefix, uri in xmlns_uris_dict.items(): polygons[i].name = 'WP{n}'.format(n=polygons[i].order_in_course)
if prefix != '':
top.attrib['xmlns:' + prefix] = uri
else:
top.attrib['xmlns'] = uri
document = SubElement(top, 'Document') return polygons
name = SubElement(document, 'name')
name.text = 'Courses.kml' def removewhitespace(s):
folder = SubElement(document, 'Folder') regels = s.split('\n')
foldername = SubElement(folder, 'name') regels = [regel for regel in regels if regel.strip()]
foldername.text = 'Courses' return "\n".join(regels)
folder2 = SubElement(folder, 'Folder')
def getcoursefolder(course, document, cn=False):
folder2 = SubElement(document, 'Folder', id="{id}".format(id=course.id))
coursename = SubElement(folder2, 'name') coursename = SubElement(folder2, 'name')
coursename.text = course.name coursename.text = course.name
open = SubElement(folder2, 'open') openst = SubElement(folder2, 'open')
open.text = '1' 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( polygons = GeoPolygon.objects.filter(
course=course).order_by("order_in_course") course=course).order_by("order_in_course")
if cn:
polygons = crewnerdify(polygons)
for polygon in polygons: for polygon in polygons:
placemark = SubElement(folder2, 'Placemark') placemark = SubElement(folder2, 'Placemark')
polygonname = SubElement(placemark, 'name') polygonname = SubElement(placemark, 'name')
polygonname.text = polygon.name polygonname.text = polygon.name
polygonstyle = SubElement(placemark, 'styleUrl')
polygonstyle.text = "#default0"
p = SubElement(placemark, 'Polygon') p = SubElement(placemark, 'Polygon')
tessellate = SubElement(p, 'tessellate') tessellate = SubElement(p, 'tessellate')
tessellate.text = '1' tessellate.text = '1'
@@ -206,6 +265,7 @@ def coursetokml(course):
coordinates.text = '' coordinates.text = ''
points = GeoPoint.objects.filter( points = GeoPoint.objects.filter(
polygon=polygon).order_by("order_in_poly") polygon=polygon).order_by("order_in_poly")
points = sort_coordinates_ccw(points)
for point in points: for point in points:
coordinates.text += '{lon},{lat},0 '.format( coordinates.text += '{lon},{lat},0 '.format(
lat=point.latitude, lat=point.latitude,
@@ -215,6 +275,87 @@ def coursetokml(course):
lat=points[0].latitude, lat=points[0].latitude,
lon=points[0].longitude, 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) return prettify(top)
+81 -31
View File
@@ -31,6 +31,11 @@ from zipfile import BadZipFile
import zipfile import zipfile
import os import os
from rowers.models import strokedatafields from rowers.models import strokedatafields
import grpc
import grpc.experimental
import rowers.rowing_workout_metrics_pb2 as metrics_pb2
import rowers.rowing_workout_metrics_pb2_grpc as metrics_pb2_grpc
import traceback
from rowingdata import ( from rowingdata import (
KinoMapParser, KinoMapParser,
@@ -120,10 +125,10 @@ from rowers.datautils import rpetotss
def rscore_approx(row): def rscore_approx(row):
if row['rscore'] > 0: if row['rscore'] > 0:
return row['rscore'] return row['rscore']
if row['rscore'] == 0: if row['rscore'] == 0: # pragma: no cover
if row['hrtss'] > 0: if row['hrtss'] > 0:
return row['hrtss'] return row['hrtss']
else: else: # pragma: no cover
dd = 3600*row['duration'].hour+60*row['duration'].minute+row['duration'].second dd = 3600*row['duration'].hour+60*row['duration'].minute+row['duration'].second
dd = dd/3600 dd = dd/3600
try: try:
@@ -135,7 +140,7 @@ def rscore_approx(row):
w = Workout.objects.get(id=row['id']) w = Workout.objects.get(id=row['id'])
trimp, hrtss = workout_trimp(w) trimp, hrtss = workout_trimp(w)
rscore, normp = workout_rscore(w) rscore, normp = workout_rscore(w)
if row['rpe'] and row['rpe'] > 0: if row['rpe'] and row['rpe'] > 0: # pragma: no cover
dd = 3600*row['duration'].hour+60*row['duration'].minute+row['duration'].second dd = 3600*row['duration'].hour+60*row['duration'].minute+row['duration'].second
dd = dd/3600 dd = dd/3600
return int(rpetotss[row['rpe']]*dd) return int(rpetotss[row['rpe']]*dd)
@@ -143,7 +148,7 @@ def rscore_approx(row):
minutes = row['duration'].hour*60+row['duration'].minute minutes = row['duration'].hour*60+row['duration'].minute
return minutes return minutes
return 0 return 0 # pragma: no cover
thetimezone = get_current_timezone() thetimezone = get_current_timezone()
@@ -177,7 +182,7 @@ def workout_goldmedalstandard(workout, reset=False):
if workout.workouttype in otwtypes: if workout.workouttype in otwtypes:
try: try:
factor = 100./(100.-workout.user.otwslack) factor = 100./(100.-workout.user.otwslack)
except ZeroDivisionError: except ZeroDivisionError: # pragma: no cover
factor = 1. factor = 1.
workout.user.otwslack = 0 workout.user.otwslack = 0
workout.user.save() workout.user.save()
@@ -186,8 +191,8 @@ def workout_goldmedalstandard(workout, reset=False):
workout.goldmedalseconds = goldmedalseconds workout.goldmedalseconds = goldmedalseconds
workout.save() workout.save()
return goldmedalstandard, goldmedalseconds return goldmedalstandard, goldmedalseconds
else:
return 0, 0 return 0, 0 # pragma: no cover
def check_marker(workout): def check_marker(workout):
@@ -497,53 +502,85 @@ def calculate_goldmedalstandard(rower, workout, recurrance=True):
def setcp(workout, background=False, recurrance=True): def setcp(workout, background=False, recurrance=True):
try:
filename = 'media/cpdata_{id}.parquet.gz'.format(id=workout.id) filename = 'media/cpdata_{id}.parquet.gz'.format(id=workout.id)
df = pd.read_parquet(filename)
if not df.empty:
# check dts
tarr = datautils.getlogarr(4000)
if df['delta'][0] in tarr:
return(df, df['delta'], df['cp'])
except:
pass
strokesdf = getsmallrowdata_db( strokesdf = getsmallrowdata_db(
['power', 'workoutid', 'time'], ids=[workout.id]) ['power', 'workoutid', 'time'], ids=[workout.id])
try: if strokesdf.empty:
if strokesdf['power'].std() == 0:
return pd.DataFrame(), pd.Series(dtype='float'), pd.Series(dtype='float')
except (KeyError, TypeError):
return pd.DataFrame(), pd.Series(dtype='float'), pd.Series(dtype='float')
if background: # pragma: no cover
_ = myqueue(queuelow, handle_setcp, strokesdf, filename, workout.id)
return pd.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float') return pd.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float')
if not strokesdf.empty:
totaltime = strokesdf['time'].max() totaltime = strokesdf['time'].max()
try: maxt = totaltime/1000.
powermean = strokesdf['power'].mean()
except KeyError: # pragma: no cover
powermean = 0
if powermean != 0:
thesecs = totaltime
maxt = 1.05 * thesecs
if maxt > 0:
logarr = datautils.getlogarr(maxt) logarr = datautils.getlogarr(maxt)
dfgrouped = strokesdf.groupby(['workoutid'])
delta, cpvalues, avgpower = datautils.getcp(dfgrouped, logarr) csvfilename = workout.csvfilename
# check what the real file name is
if os.path.exists(csvfilename):
csvfile = csvfilename
elif os.path.exists(csvfilename+'.csv'): # pragma: no cover
csvfile = csvfilename+'.csv'
elif os.path.exists(csvfilename+'.gz'): # pragma: no cover
csvfile = csvfilename+'.gz'
else: # pragma: no cover
return pd.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float')
csvfile = os.path.abspath(csvfile)
with grpc.insecure_channel(
target='localhost:50052',
options=[('grpc.lb_policy_name', 'pick_first'),
('grpc.enable_retries', 0), ('grpc.keepalive_timeout_ms',
10000)]
) as channel:
try:
grpc.channel_ready_future(channel).result(timeout=10)
except grpc.FutureTimeoutError: # pragma: no cover
dologging('metrics.log','grpc channel time out in setcp')
return pd.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float')
stub = metrics_pb2_grpc.MetricsStub(channel)
req = metrics_pb2.CPRequest(filename = csvfile, filetype = "CSV", tarr = logarr)
try:
response = stub.GetCP(req, timeout=60)
except Exception as e:
dologging('metrics.log', traceback.format_exc())
return pd.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float')
delta = pd.Series(np.array(response.delta))
cpvalues = pd.Series(np.array(response.power))
powermean = response.avgpower
df = pd.DataFrame({ df = pd.DataFrame({
'delta': delta, 'delta': delta,
'cp': cpvalues, 'cp': cpvalues,
'id': workout.id, 'id': workout.id,
}) })
df.to_parquet(filename, engine='fastparquet',
compression='GZIP') df.to_parquet(filename, engine='fastparquet', compression='GZIP')
if recurrance: if recurrance:
goldmedalstandard, goldmedalduration = calculate_goldmedalstandard( goldmedalstandard, goldmedalduration = calculate_goldmedalstandard(
workout.user, workout) workout.user, workout)
workout.goldmedalstandard = goldmedalstandard workout.goldmedalstandard = goldmedalstandard
workout.goldmedalduration = goldmedalduration workout.goldmedalduration = goldmedalduration
workout.save() workout.save()
return df, delta, cpvalues return df, delta, cpvalues
return pd.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float')
def update_wps(r, types, mode='water', asynchron=True): def update_wps(r, types, mode='water', asynchron=True):
@@ -679,9 +716,11 @@ def join_workouts(r, ids, title='Joined Workout',
def fetchcp_new(rower, workouts): def fetchcp_new(rower, workouts):
data = [] data = []
for workout in workouts: for workout in workouts:
cpfile = 'media/cpdata_{id}.parquet.gz'.format(id=workout.id) cpfile = 'media/cpdata_{id}.parquet.gz'.format(id=workout.id)
try: try:
df, delta, cpvalues = setcp(workout)
df = pd.read_parquet(cpfile) df = pd.read_parquet(cpfile)
df['workout'] = str(workout) df['workout'] = str(workout)
df['url'] = workout.url() df['url'] = workout.url()
@@ -698,12 +737,15 @@ def fetchcp_new(rower, workouts):
if len(data) > 1: if len(data) > 1:
df = pd.concat(data, axis=0) df = pd.concat(data, axis=0)
try: try:
df = df[df['cp'] == df.groupby(['delta'])['cp'].transform('max')] df = df[df['cp'] == df.groupby(['delta'])['cp'].transform('max')]
except KeyError: # pragma: no cover except KeyError: # pragma: no cover
return pd.Series(dtype='float'), pd.Series(dtype='float'), 0, pd.Series(dtype='float'), pd.Series(dtype='float') return pd.Series(dtype='float'), pd.Series(dtype='float'), 0, pd.Series(dtype='float'), pd.Series(dtype='float')
df = df.sort_values(['delta']).reset_index() df = df.sort_values(['delta']).reset_index()
df = df[df['cp']>20]
return df['delta'], df['cp'], 0, df['workout'], df['url'] return df['delta'], df['cp'], 0, df['workout'], df['url']
@@ -1011,14 +1053,22 @@ def checkbreakthrough(w, r):
cpdf, delta, cpvalues = setcp(w) cpdf, delta, cpvalues = setcp(w)
if not cpdf.empty: if not cpdf.empty:
if workouttype in otwtypes: if workouttype in otwtypes:
try:
res, btvalues, res2 = utils.isbreakthrough( res, btvalues, res2 = utils.isbreakthrough(
delta, cpvalues, r.p0, r.p1, r.p2, r.p3, r.cpratio) delta, cpvalues, r.p0, r.p1, r.p2, r.p3, r.cpratio)
_ = update_rolling_cp(r, otwtypes, 'water') _ = update_rolling_cp(r, otwtypes, 'water')
except ValueError:
res = 0
res2 = 0
elif workouttype in otetypes: elif workouttype in otetypes:
try:
res, btvalues, res2 = utils.isbreakthrough( res, btvalues, res2 = utils.isbreakthrough(
delta, cpvalues, r.ep0, r.ep1, r.ep2, r.ep3, r.ecpratio) delta, cpvalues, r.ep0, r.ep1, r.ep2, r.ep3, r.ecpratio)
_ = update_rolling_cp(r, otetypes, 'erg') _ = update_rolling_cp(r, otetypes, 'erg')
except ValueError:
res = 0
res2 = 0
else: # pragma: no cover else: # pragma: no cover
res = 0 res = 0
res2 = 0 res2 = 0
+8 -9
View File
@@ -1,4 +1,3 @@
import pandas as pd import pandas as pd
import numpy as np import numpy as np
from scipy.interpolate import griddata from scipy.interpolate import griddata
@@ -124,18 +123,18 @@ def cpfit(powerdf, fraclimit=0.0001, nmax=1000):
def getlogarr(maxt): def getlogarr(maxt):
maxlog10 = np.log10(maxt-5) dtmin = 10
maxlog10 = np.log10(maxt)
# print(maxlog10,round(maxlog10)) # print(maxlog10,round(maxlog10))
aantal = 10*round(maxlog10) aantal = 40
logarr = np.arange(aantal+1)/10. logarr = np.arange(aantal+1)/10.
vs = 10.**logarr
res = [] res = []
for la in logarr: for v in vs:
try: if v > dtmin and v<maxt:
v = 5+int(10.**(la)) res.append(int(v))
except ValueError: # pragma: no cover
v = 0
res.append(v)
logarr = pd.Series(res, dtype='float') logarr = pd.Series(res, dtype='float')
logarr.drop_duplicates(keep='first', inplace=True) logarr.drop_duplicates(keep='first', inplace=True)
+7 -4
View File
@@ -33,7 +33,7 @@ import datetime
from rowers import mytypes from rowers import mytypes
from rowers.courses import ( from rowers.courses import (
course_coord_center, course_coord_maxmin, course_coord_center, course_coord_maxmin,
polygon_coord_center polygon_coord_center, course_coord_crewnerd_navigation,
) )
from django.conf import settings from django.conf import settings
from collections import OrderedDict from collections import OrderedDict
@@ -2165,6 +2165,8 @@ def interactive_histoall(theworkouts, histoparam, includereststrokes,
def course_map(course): def course_map(course):
latmean, lonmean, coordinates = course_coord_center(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) lat_min, lat_max, long_min, long_max = course_coord_maxmin(course)
coordinates = course_spline(coordinates) coordinates = course_spline(coordinates)
@@ -4424,6 +4426,7 @@ def instroke_interactive_chart(df,metric, workout, spm_min, spm_max,
df_plot df_plot
) )
TIPS = OrderedDict([ TIPS = OrderedDict([
('x','@x'), ('x','@x'),
('median','@median'), ('median','@median'),
@@ -4513,7 +4516,6 @@ def instroke_interactive_chart(df,metric, workout, spm_min, spm_max,
script = "" script = ""
div = "Something went wrong with the chart" div = "Something went wrong with the chart"
return (script, div) return (script, div)
@@ -4529,6 +4531,7 @@ def interactive_chart(id=0, promember=0, intervaldata={}):
columns = ['time', 'pace', 'hr', 'fpace', 'ftime', 'spm'] columns = ['time', 'pace', 'hr', 'fpace', 'ftime', 'spm']
datadf = dataprep.getsmallrowdata_db(columns, ids=[id]) datadf = dataprep.getsmallrowdata_db(columns, ids=[id])
datadf.dropna(axis=0, how='any', inplace=True) datadf.dropna(axis=0, how='any', inplace=True)
row = Workout.objects.get(id=id) row = Workout.objects.get(id=id)
@@ -4576,6 +4579,7 @@ def interactive_chart(id=0, promember=0, intervaldata={}):
) )
plot.line('time', 'pace', source=source, legend_label="Pace", name="pace") plot.line('time', 'pace', source=source, legend_label="Pace", name="pace")
try: try:
plot.title.text = row.name plot.title.text = row.name
except ValueError: # pragma: no cover except ValueError: # pragma: no cover
@@ -4645,7 +4649,6 @@ def interactive_chart(id=0, promember=0, intervaldata={}):
hover.mode = 'mouse' hover.mode = 'mouse'
# hover.name = ["spm", "pace"] # hover.name = ["spm", "pace"]
script, div = components(plot) script, div = components(plot)
return [script, div] return [script, div]
@@ -5913,7 +5916,7 @@ def interactive_flex_chart2(id, r, promember=0,
popt, pcov = optimize.curve_fit(func, x1, y1) popt, pcov = optimize.curve_fit(func, x1, y1)
ytrend = func(x1, popt[0], popt[1]) ytrend = func(x1, popt[0], popt[1])
rowdata['ytrend'] = ytrend rowdata['ytrend'] = ytrend
except TypeError: except TypeError: # pragma: no cover
rowdata['ytrend'] = y1 rowdata['ytrend'] = y1
# prepare data # prepare data
+80 -1
View File
@@ -8,7 +8,7 @@ from rowers.utils import (
steps_read_fit, steps_write_fit, ps_dict_order, uniqify steps_read_fit, steps_write_fit, ps_dict_order, uniqify
) )
from rowers.metrics import axlabels 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.formfields import *
from rowers.database import * from rowers.database import *
import uuid import uuid
@@ -594,6 +594,64 @@ def course_spline(coordinates):
return newcoordinates 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): def course_coord_center(course):
@@ -1590,6 +1648,24 @@ class GeoCourse(models.Model):
def coord(self): def coord(self):
return course_coord_center(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 GeoCourseEditForm(ModelForm):
class Meta: class Meta:
@@ -1747,6 +1823,7 @@ class InstantPlan(models.Model):
authorizationstring = 'Bearer '+settings.WORKOUTS_FIT_TOKEN authorizationstring = 'Bearer '+settings.WORKOUTS_FIT_TOKEN
url = settings.WORKOUTS_FIT_URL+"/trainingplan/" url = settings.WORKOUTS_FIT_URL+"/trainingplan/"
headers = {'Authorization': authorizationstring} headers = {'Authorization': authorizationstring}
try:
response = requests.post(url=url, headers=headers, data=yamltext) response = requests.post(url=url, headers=headers, data=yamltext)
if response.status_code == 200: if response.status_code == 200:
data = response.json() data = response.json()
@@ -1756,6 +1833,8 @@ class InstantPlan(models.Model):
self.description = data['description'] self.description = data['description']
self.duration = data['duration'] self.duration = data['duration']
self.yaml = None self.yaml = None
except:
pass
super(InstantPlan, self).save(*args, **kwargs) super(InstantPlan, self).save(*args, **kwargs)
+18 -172
View File
@@ -1,185 +1,31 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT! # Generated by the protocol buffer compiler. DO NOT EDIT!
# source: otw-power-calculator.proto # source: otw-power-calculator.proto
# Protobuf Python Version: 4.25.1
from google.protobuf import symbol_database as _symbol_database """Generated protocol buffer code."""
from google.protobuf import reflection as _reflection
from google.protobuf import message as _message
from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor as _descriptor
import sys from google.protobuf import descriptor_pool as _descriptor_pool
_b = sys.version_info[0] < 3 and ( from google.protobuf import symbol_database as _symbol_database
lambda x: x) or (lambda x: x.encode('latin1')) from google.protobuf.internal import builder as _builder
# @@protoc_insertion_point(imports) # @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default() _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='otw-power-calculator.proto',
package='otw_power_calculator',
syntax='proto3',
serialized_options=None,
serialized_pb=_b('\n\x1aotw-power-calculator.proto\x12\x14otw_power_calculator\"\xc0\x01\n\x13WorkoutPowerRequest\x12\x10\n\x08\x66ilename\x18\x01 \x01(\t\x12\x10\n\x08\x62oattype\x18\x02 \x01(\t\x12\x10\n\x08\x63rewmass\x18\x03 \x01(\x01\x12\x15\n\rpowermeasured\x18\x04 \x01(\x08\x12\x13\n\x0bprogressurl\x18\x05 \x01(\t\x12\x0e\n\x06secret\x18\x06 \x01(\t\x12\x0e\n\x06silent\x18\x07 \x01(\x08\x12\x11\n\tboatclass\x18\x08 \x01(\t\x12\x14\n\x0c\x63oastalbrand\x18\t \x01(\t\"#\n\x11\x43\x61lculationResult\x12\x0e\n\x06result\x18\x01 \x01(\x05\x32j\n\x05Power\x12\x61\n\tCalcPower\x12).otw_power_calculator.WorkoutPowerRequest\x1a\'.otw_power_calculator.CalculationResult\"\x00\x62\x06proto3')
)
_WORKOUTPOWERREQUEST = _descriptor.Descriptor( DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1aotw-power-calculator.proto\x12\x14otw_power_calculator\"\xc0\x01\n\x13WorkoutPowerRequest\x12\x10\n\x08\x66ilename\x18\x01 \x01(\t\x12\x10\n\x08\x62oattype\x18\x02 \x01(\t\x12\x10\n\x08\x63rewmass\x18\x03 \x01(\x01\x12\x15\n\rpowermeasured\x18\x04 \x01(\x08\x12\x13\n\x0bprogressurl\x18\x05 \x01(\t\x12\x0e\n\x06secret\x18\x06 \x01(\t\x12\x0e\n\x06silent\x18\x07 \x01(\x08\x12\x11\n\tboatclass\x18\x08 \x01(\t\x12\x14\n\x0c\x63oastalbrand\x18\t \x01(\t\"#\n\x11\x43\x61lculationResult\x12\x0e\n\x06result\x18\x01 \x01(\x05\x32j\n\x05Power\x12\x61\n\tCalcPower\x12).otw_power_calculator.WorkoutPowerRequest\x1a\'.otw_power_calculator.CalculationResult\"\x00\x42\x18Z\x16./otw-power-calculatorb\x06proto3')
name='WorkoutPowerRequest',
full_name='otw_power_calculator.WorkoutPowerRequest',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='filename', full_name='otw_power_calculator.WorkoutPowerRequest.filename', index=0,
number=1, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='boattype', full_name='otw_power_calculator.WorkoutPowerRequest.boattype', index=1,
number=2, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='crewmass', full_name='otw_power_calculator.WorkoutPowerRequest.crewmass', index=2,
number=3, type=1, cpp_type=5, label=1,
has_default_value=False, default_value=float(0),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='powermeasured', full_name='otw_power_calculator.WorkoutPowerRequest.powermeasured', index=3,
number=4, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='progressurl', full_name='otw_power_calculator.WorkoutPowerRequest.progressurl', index=4,
number=5, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='secret', full_name='otw_power_calculator.WorkoutPowerRequest.secret', index=5,
number=6, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='silent', full_name='otw_power_calculator.WorkoutPowerRequest.silent', index=6,
number=7, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='boatclass', full_name='otw_power_calculator.WorkoutPowerRequest.boatclass', index=7,
number=8, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='coastalbrand', full_name='otw_power_calculator.WorkoutPowerRequest.coastalbrand', index=8,
number=9, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=53,
serialized_end=245,
)
_CALCULATIONRESULT = _descriptor.Descriptor(
name='CalculationResult',
full_name='otw_power_calculator.CalculationResult',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='result', full_name='otw_power_calculator.CalculationResult.result', index=0,
number=1, type=5, cpp_type=1, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=247,
serialized_end=282,
)
DESCRIPTOR.message_types_by_name['WorkoutPowerRequest'] = _WORKOUTPOWERREQUEST
DESCRIPTOR.message_types_by_name['CalculationResult'] = _CALCULATIONRESULT
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
WorkoutPowerRequest = _reflection.GeneratedProtocolMessageType('WorkoutPowerRequest', (_message.Message,), {
'DESCRIPTOR': _WORKOUTPOWERREQUEST,
'__module__': 'otw_power_calculator_pb2'
# @@protoc_insertion_point(class_scope:otw_power_calculator.WorkoutPowerRequest)
})
_sym_db.RegisterMessage(WorkoutPowerRequest)
CalculationResult = _reflection.GeneratedProtocolMessageType('CalculationResult', (_message.Message,), {
'DESCRIPTOR': _CALCULATIONRESULT,
'__module__': 'otw_power_calculator_pb2'
# @@protoc_insertion_point(class_scope:otw_power_calculator.CalculationResult)
})
_sym_db.RegisterMessage(CalculationResult)
_POWER = _descriptor.ServiceDescriptor(
name='Power',
full_name='otw_power_calculator.Power',
file=DESCRIPTOR,
index=0,
serialized_options=None,
serialized_start=284,
serialized_end=390,
methods=[
_descriptor.MethodDescriptor(
name='CalcPower',
full_name='otw_power_calculator.Power.CalcPower',
index=0,
containing_service=None,
input_type=_WORKOUTPOWERREQUEST,
output_type=_CALCULATIONRESULT,
serialized_options=None,
),
])
_sym_db.RegisterServiceDescriptor(_POWER)
DESCRIPTOR.services_by_name['Power'] = _POWER
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'otw_power_calculator_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False:
_globals['DESCRIPTOR']._options = None
_globals['DESCRIPTOR']._serialized_options = b'Z\026./otw-power-calculator'
_globals['_WORKOUTPOWERREQUEST']._serialized_start=53
_globals['_WORKOUTPOWERREQUEST']._serialized_end=245
_globals['_CALCULATIONRESULT']._serialized_start=247
_globals['_CALCULATIONRESULT']._serialized_end=282
_globals['_POWER']._serialized_start=284
_globals['_POWER']._serialized_end=390
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)
+31 -9
View File
@@ -1,4 +1,5 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc import grpc
import rowers.otw_power_calculator_pb2 as otw__power__calculator__pb2 import rowers.otw_power_calculator_pb2 as otw__power__calculator__pb2
@@ -25,23 +26,44 @@ class PowerServicer(object):
"""Power service definition """Power service definition
""" """
def CalcPower(self, request, context): # pragma: no cover def CalcPower(self, request, context):
# missing associated documentation comment in .proto file """Missing associated documentation comment in .proto file."""
pass context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_code(grpc.StatusCode.UNIMPLEMENTED) # pragma: no cover context.set_details('Method not implemented!')
context.set_details('Method not implemented!') # pragma: no cover raise NotImplementedError('Method not implemented!')
raise NotImplementedError(
'Method not implemented!') # pragma: no cover
def add_PowerServicer_to_server(servicer, server): # pragma: no cover def add_PowerServicer_to_server(servicer, server):
rpc_method_handlers = { rpc_method_handlers = {
'CalcPower': grpc.unary_unary_rpc_method_handler( 'CalcPower': grpc.unary_unary_rpc_method_handler(
servicer.CalcPower, servicer.CalcPower,
request_deserializer=otw__power__calculator__pb2.WorkoutPowerRequest.FromString, request_deserializer=otw__power__calculator__pb2.WorkoutPowerRequest.FromString,
response_serializer=otw__power__calculator__pb2.CalculationResult.SerializeToString, response_serializer=otw__power__calculator__pb2.CalculationResult.SerializeToString,
), ),
} # pragma: no cover }
generic_handler = grpc.method_handlers_generic_handler( generic_handler = grpc.method_handlers_generic_handler(
'otw_power_calculator.Power', rpc_method_handlers) 'otw_power_calculator.Power', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,)) server.add_generic_rpc_handlers((generic_handler,))
# This class is part of an EXPERIMENTAL API.
class Power(object):
"""Power service definition
"""
@staticmethod
def CalcPower(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(request, target, '/otw_power_calculator.Power/CalcPower',
otw__power__calculator__pb2.WorkoutPowerRequest.SerializeToString,
otw__power__calculator__pb2.CalculationResult.FromString,
options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+22 -197
View File
@@ -1,210 +1,35 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT! # Generated by the protocol buffer compiler. DO NOT EDIT!
# source: rowing-workout-metrics.proto # source: rowing-workout-metrics.proto
# Protobuf Python Version: 4.25.1
from google.protobuf import symbol_database as _symbol_database """Generated protocol buffer code."""
from google.protobuf import reflection as _reflection
from google.protobuf import message as _message
from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor as _descriptor
import sys from google.protobuf import descriptor_pool as _descriptor_pool
_b = sys.version_info[0] < 3 and ( from google.protobuf import symbol_database as _symbol_database
lambda x: x) or (lambda x: x.encode('latin1')) from google.protobuf.internal import builder as _builder
# @@protoc_insertion_point(imports) # @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default() _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='rowing-workout-metrics.proto',
package='rowing_workout_metrics',
syntax='proto3',
serialized_options=None,
serialized_pb=_b('\n\x1crowing-workout-metrics.proto\x12\x16'
'rowing_workout_metrics\"p\n\x15WorkoutMetricsRequest'
'\x12\x10\n\x08\x66ilename'
'\x18\x01 \x01(\t\x12\x0b\n\x03sex'
'\x18\x02 \x01(\t\x12\x0b\n\x03\x66tp\x18\x03 \x01'
'(\x01\x12\r\n\x05hrftp\x18\x04 \x01(\x01\x12\r\n\x05hrmax'
'\x18\x05 \x01(\x01\x12\r\n\x05hrmin\x18\x06 \x01(\x01\"p\n\x16WorkoutMetricsResponse'
'\x12\x0b\n\x03tss\x18\x01 \x01(\x01\x12\r\n\x05normp\x18\x02 \x01(\x01\x12\r\n\x05trimp'
'\x18\x03 \x01(\x01\x12\r\n\x05hrtss\x18\x04 \x01(\x01\x12\r\n\x05normv'
'\x18\x05 \x01(\x01\x12\r\n\x05normw\x18\x06 \x01(\x01\x32w\n\x07Metrics'
'\x12l\n\x0b\x43\x61lcMetrics\x12-.rowing_workout_metrics.WorkoutMetricsRequest'
'\x1a..rowing_workout_metrics.WorkoutMetricsResponseb\x06proto3')
)
_WORKOUTMETRICSREQUEST = _descriptor.Descriptor( DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1crowing-workout-metrics.proto\x12\x16rowing_workout_metrics\"p\n\x15WorkoutMetricsRequest\x12\x10\n\x08\x66ilename\x18\x01 \x01(\t\x12\x0b\n\x03sex\x18\x02 \x01(\t\x12\x0b\n\x03\x66tp\x18\x03 \x01(\x01\x12\r\n\x05hrftp\x18\x04 \x01(\x01\x12\r\n\x05hrmax\x18\x05 \x01(\x01\x12\r\n\x05hrmin\x18\x06 \x01(\x01\"p\n\x16WorkoutMetricsResponse\x12\x0b\n\x03tss\x18\x01 \x01(\x01\x12\r\n\x05normp\x18\x02 \x01(\x01\x12\r\n\x05trimp\x18\x03 \x01(\x01\x12\r\n\x05hrtss\x18\x04 \x01(\x01\x12\r\n\x05normv\x18\x05 \x01(\x01\x12\r\n\x05normw\x18\x06 \x01(\x01\"=\n\tCPRequest\x12\x10\n\x08\x66ilename\x18\x01 \x01(\t\x12\x10\n\x08\x66iletype\x18\x02 \x01(\t\x12\x0c\n\x04tarr\x18\x03 \x03(\x01\"<\n\nCPResponse\x12\r\n\x05\x64\x65lta\x18\x01 \x03(\x01\x12\r\n\x05power\x18\x02 \x03(\x01\x12\x10\n\x08\x61vgpower\x18\x03 \x01(\x01\x32\xc7\x01\n\x07Metrics\x12l\n\x0b\x43\x61lcMetrics\x12-.rowing_workout_metrics.WorkoutMetricsRequest\x1a..rowing_workout_metrics.WorkoutMetricsResponse\x12N\n\x05GetCP\x12!.rowing_workout_metrics.CPRequest\x1a\".rowing_workout_metrics.CPResponseB\x1aZ\x18./rowing-workout-metricsb\x06proto3')
name='WorkoutMetricsRequest',
full_name='rowing_workout_metrics.WorkoutMetricsRequest',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='filename', full_name='rowing_workout_metrics.WorkoutMetricsRequest.filename', index=0,
number=1, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='sex', full_name='rowing_workout_metrics.WorkoutMetricsRequest.sex', index=1,
number=2, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='ftp', full_name='rowing_workout_metrics.WorkoutMetricsRequest.ftp', index=2,
number=3, type=1, cpp_type=5, label=1,
has_default_value=False, default_value=float(0),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='hrftp', full_name='rowing_workout_metrics.WorkoutMetricsRequest.hrftp', index=3,
number=4, type=1, cpp_type=5, label=1,
has_default_value=False, default_value=float(0),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='hrmax', full_name='rowing_workout_metrics.WorkoutMetricsRequest.hrmax', index=4,
number=5, type=1, cpp_type=5, label=1,
has_default_value=False, default_value=float(0),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='hrmin', full_name='rowing_workout_metrics.WorkoutMetricsRequest.hrmin', index=5,
number=6, type=1, cpp_type=5, label=1,
has_default_value=False, default_value=float(0),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=56,
serialized_end=168,
)
_WORKOUTMETRICSRESPONSE = _descriptor.Descriptor(
name='WorkoutMetricsResponse',
full_name='rowing_workout_metrics.WorkoutMetricsResponse',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='tss', full_name='rowing_workout_metrics.WorkoutMetricsResponse.tss', index=0,
number=1, type=1, cpp_type=5, label=1,
has_default_value=False, default_value=float(0),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='normp', full_name='rowing_workout_metrics.WorkoutMetricsResponse.normp', index=1,
number=2, type=1, cpp_type=5, label=1,
has_default_value=False, default_value=float(0),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='trimp', full_name='rowing_workout_metrics.WorkoutMetricsResponse.trimp', index=2,
number=3, type=1, cpp_type=5, label=1,
has_default_value=False, default_value=float(0),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='hrtss', full_name='rowing_workout_metrics.WorkoutMetricsResponse.hrtss', index=3,
number=4, type=1, cpp_type=5, label=1,
has_default_value=False, default_value=float(0),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='normv', full_name='rowing_workout_metrics.WorkoutMetricsResponse.normv', index=4,
number=5, type=1, cpp_type=5, label=1,
has_default_value=False, default_value=float(0),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='normw', full_name='rowing_workout_metrics.WorkoutMetricsResponse.normw', index=5,
number=6, type=1, cpp_type=5, label=1,
has_default_value=False, default_value=float(0),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=170,
serialized_end=282,
)
DESCRIPTOR.message_types_by_name['WorkoutMetricsRequest'] = _WORKOUTMETRICSREQUEST
DESCRIPTOR.message_types_by_name['WorkoutMetricsResponse'] = _WORKOUTMETRICSRESPONSE
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
WorkoutMetricsRequest = _reflection.GeneratedProtocolMessageType('WorkoutMetricsRequest', (_message.Message,), {
'DESCRIPTOR': _WORKOUTMETRICSREQUEST,
'__module__': 'rowing_workout_metrics_pb2'
# @@protoc_insertion_point(class_scope:rowing_workout_metrics.WorkoutMetricsRequest)
})
_sym_db.RegisterMessage(WorkoutMetricsRequest)
WorkoutMetricsResponse = _reflection.GeneratedProtocolMessageType('WorkoutMetricsResponse', (_message.Message,), {
'DESCRIPTOR': _WORKOUTMETRICSRESPONSE,
'__module__': 'rowing_workout_metrics_pb2'
# @@protoc_insertion_point(class_scope:rowing_workout_metrics.WorkoutMetricsResponse)
})
_sym_db.RegisterMessage(WorkoutMetricsResponse)
_METRICS = _descriptor.ServiceDescriptor(
name='Metrics',
full_name='rowing_workout_metrics.Metrics',
file=DESCRIPTOR,
index=0,
serialized_options=None,
serialized_start=284,
serialized_end=403,
methods=[
_descriptor.MethodDescriptor(
name='CalcMetrics',
full_name='rowing_workout_metrics.Metrics.CalcMetrics',
index=0,
containing_service=None,
input_type=_WORKOUTMETRICSREQUEST,
output_type=_WORKOUTMETRICSRESPONSE,
serialized_options=None,
),
])
_sym_db.RegisterServiceDescriptor(_METRICS)
DESCRIPTOR.services_by_name['Metrics'] = _METRICS
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'rowing_workout_metrics_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False:
_globals['DESCRIPTOR']._options = None
_globals['DESCRIPTOR']._serialized_options = b'Z\030./rowing-workout-metrics'
_globals['_WORKOUTMETRICSREQUEST']._serialized_start=56
_globals['_WORKOUTMETRICSREQUEST']._serialized_end=168
_globals['_WORKOUTMETRICSRESPONSE']._serialized_start=170
_globals['_WORKOUTMETRICSRESPONSE']._serialized_end=282
_globals['_CPREQUEST']._serialized_start=284
_globals['_CPREQUEST']._serialized_end=345
_globals['_CPRESPONSE']._serialized_start=347
_globals['_CPRESPONSE']._serialized_end=407
_globals['_METRICS']._serialized_start=410
_globals['_METRICS']._serialized_end=609
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)
+62 -7
View File
@@ -1,4 +1,5 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc import grpc
import rowers.rowing_workout_metrics_pb2 as rowing__workout__metrics__pb2 import rowers.rowing_workout_metrics_pb2 as rowing__workout__metrics__pb2
@@ -19,6 +20,11 @@ class MetricsStub(object):
request_serializer=rowing__workout__metrics__pb2.WorkoutMetricsRequest.SerializeToString, request_serializer=rowing__workout__metrics__pb2.WorkoutMetricsRequest.SerializeToString,
response_deserializer=rowing__workout__metrics__pb2.WorkoutMetricsResponse.FromString, response_deserializer=rowing__workout__metrics__pb2.WorkoutMetricsResponse.FromString,
) )
self.GetCP = channel.unary_unary(
'/rowing_workout_metrics.Metrics/GetCP',
request_serializer=rowing__workout__metrics__pb2.CPRequest.SerializeToString,
response_deserializer=rowing__workout__metrics__pb2.CPResponse.FromString,
)
class MetricsServicer(object): class MetricsServicer(object):
@@ -26,22 +32,71 @@ class MetricsServicer(object):
""" """
def CalcMetrics(self, request, context): def CalcMetrics(self, request, context):
# missing associated documentation comment in .proto file """Missing associated documentation comment in .proto file."""
pass context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_code(grpc.StatusCode.UNIMPLEMENTED) # pragma: no cover context.set_details('Method not implemented!')
context.set_details('Method not implemented!') # pragma: no cover raise NotImplementedError('Method not implemented!')
raise NotImplementedError(
'Method not implemented!') # pragma: no cover def GetCP(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def add_MetricsServicer_to_server(servicer, server): # pragma: no cover def add_MetricsServicer_to_server(servicer, server):
rpc_method_handlers = { rpc_method_handlers = {
'CalcMetrics': grpc.unary_unary_rpc_method_handler( 'CalcMetrics': grpc.unary_unary_rpc_method_handler(
servicer.CalcMetrics, servicer.CalcMetrics,
request_deserializer=rowing__workout__metrics__pb2.WorkoutMetricsRequest.FromString, request_deserializer=rowing__workout__metrics__pb2.WorkoutMetricsRequest.FromString,
response_serializer=rowing__workout__metrics__pb2.WorkoutMetricsResponse.SerializeToString, response_serializer=rowing__workout__metrics__pb2.WorkoutMetricsResponse.SerializeToString,
), ),
'GetCP': grpc.unary_unary_rpc_method_handler(
servicer.GetCP,
request_deserializer=rowing__workout__metrics__pb2.CPRequest.FromString,
response_serializer=rowing__workout__metrics__pb2.CPResponse.SerializeToString,
),
} }
generic_handler = grpc.method_handlers_generic_handler( generic_handler = grpc.method_handlers_generic_handler(
'rowing_workout_metrics.Metrics', rpc_method_handlers) 'rowing_workout_metrics.Metrics', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,)) server.add_generic_rpc_handlers((generic_handler,))
# This class is part of an EXPERIMENTAL API.
class Metrics(object):
"""OTW-metrics service definition
"""
@staticmethod
def CalcMetrics(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(request, target, '/rowing_workout_metrics.Metrics/CalcMetrics',
rowing__workout__metrics__pb2.WorkoutMetricsRequest.SerializeToString,
rowing__workout__metrics__pb2.WorkoutMetricsResponse.FromString,
options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
@staticmethod
def GetCP(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(request, target, '/rowing_workout_metrics.Metrics/GetCP',
rowing__workout__metrics__pb2.CPRequest.SerializeToString,
rowing__workout__metrics__pb2.CPResponse.FromString,
options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+22 -23
View File
@@ -94,7 +94,7 @@ SITE_URL_DEV = CFG['site_url']
PROGRESS_CACHE_SECRET = CFG['progress_cache_secret'] PROGRESS_CACHE_SECRET = CFG['progress_cache_secret']
try: try:
SETTINGS_NAME = CFG['settings_name'] SETTINGS_NAME = CFG['settings_name']
except KeyError: except KeyError: # pragma: no cover
SETTINGS_NAME = 'rowsandall_ap.settings' SETTINGS_NAME = 'rowsandall_ap.settings'
try: try:
@@ -321,7 +321,7 @@ def summaryfromsplitdata(splitdata, data, filename, sep='|', workouttype='rower'
return sums, sa, results return sums, sa, results
@app.task @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() session = requests.session()
newHeaders = {'Content-type': 'application/json', 'Accept': 'text/plain'} newHeaders = {'Content-type': 'application/json', 'Accept': 'text/plain'}
session.headers.update(newHeaders) session.headers.update(newHeaders)
@@ -335,14 +335,14 @@ def handle_post_workout_api(uploadoptions, debug=False, **kwargs):
@app.task @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: for w in ws:
w.team.remove(t) w.team.remove(t)
return 1 return 1
@app.task @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: for w in ws:
w.team.add(t) w.team.add(t)
@@ -350,7 +350,7 @@ def handle_add_workouts_team(ws, t, debug=False, **kwargs):
return 1 return 1
def uploadactivity(access_token, filename, description='', def uploadactivity(access_token, filename, description='',
name='Rowsandall.com workout'): name='Rowsandall.com workout'): # pragma: no cover
data_gz = BytesIO() data_gz = BytesIO()
try: try:
@@ -393,7 +393,7 @@ def uploadactivity(access_token, filename, description='',
@app.task @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) authorizationstring = str('Bearer ' + workout.user.tptoken)
headers = {'Authorization': authorizationstring, headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal', 'user-agent': 'sanderroosendaal',
@@ -411,7 +411,7 @@ def check_tp_workout_id(workout, location, attempts=5, debug=False, **kwargs):
return 1 return 1
@app.task @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 tpid = 0
r = w.user r = w.user
if not tcxfilename: if not tcxfilename:
@@ -443,7 +443,7 @@ def handle_workout_tp_upload(w, thetoken, tcxfilename, debug=False, **kwargs):
return tpid return tpid
@app.task @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] f1 = w.csvfilename[6:-4]
rowdata = rdata(csvfile=w.csvfilename) rowdata = rdata(csvfile=w.csvfilename)
@@ -512,7 +512,7 @@ def handle_c2_sync(workoutid, url, headers, data, debug=False, **kwargs):
try: try:
workout = Workout.objects.get(id=workoutid) 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)) dologging('c2_log.log','failed for c2id {c2id}'.format(c2id=c2id))
return 0 return 0
@@ -531,7 +531,7 @@ def handle_c2_sync(workoutid, url, headers, data, debug=False, **kwargs):
return 1 return 1
def splitstdata(lijst): def splitstdata(lijst): # pragma: no cover
t = [] t = []
latlong = [] latlong = []
while len(lijst) >= 2: while len(lijst) >= 2:
@@ -543,7 +543,7 @@ def splitstdata(lijst):
@app.task @app.task
def handle_sporttracks_workout_from_data(user, importid, source, def handle_sporttracks_workout_from_data(user, importid, source,
workoutsource, debug=False, **kwargs): workoutsource, debug=False, **kwargs): # pragma: no cover
r = user.rower r = user.rower
authorizationstring = str('Bearer ' + r.sporttrackstoken) authorizationstring = str('Bearer ' + r.sporttrackstoken)
@@ -812,7 +812,7 @@ def handle_strava_sync(stravatoken,
if not failed: if not failed:
try: try:
workout = Workout.objects.get(id=workoutid) workout = Workout.objects.get(id=workoutid)
except Workout.DoesNotExist: except Workout.DoesNotExist: # pragma: no cover
return 0 return 0
workout.uploadedtostrava = res.id workout.uploadedtostrava = res.id
@@ -1477,7 +1477,6 @@ def handle_calctrimp(id,
csvfile = csvfilename+'.gz' csvfile = csvfilename+'.gz'
else: # pragma: no cover else: # pragma: no cover
return 0 return 0
csvfile = os.path.abspath(csvfile) csvfile = os.path.abspath(csvfile)
with grpc.insecure_channel( with grpc.insecure_channel(
@@ -1562,7 +1561,7 @@ def handle_calctrimp(id,
try: try:
workout = Workout.objects.get(id=id) 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)) dologging('metrics.log','Could not find workout {id}'.format(id=id))
return 0 return 0
@@ -2037,7 +2036,7 @@ def handle_sendemail_expired(useremail, userfirstname, userlastname, expireddate
return 1 return 1
@app.task @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" subject = "You may want to update your FTP on rowsandall.com"
from_email = 'Rowsandall <info@rowsandall.com>' from_email = 'Rowsandall <info@rowsandall.com>'
@@ -2069,7 +2068,7 @@ def handle_sendemail_breakthrough(workoutid, useremail,
lastname = '' lastname = ''
if surname: if surname: # pragma: no cover
lastname = userlastname lastname = userlastname
tablevalues = [ tablevalues = [
@@ -2122,7 +2121,7 @@ def handle_sendemail_hard(workoutid, useremail,
] ]
lastname = '' lastname = ''
if surname: if surname: # pragma: no cover
lastname = userlastname lastname = userlastname
# send email with attachment # send email with attachment
@@ -3414,7 +3413,7 @@ def handle_nk_async_workout(alldata, userid, nktoken, nkid, delaysec, defaulttim
jsonData = response.json() jsonData = response.json()
try: try:
strokeData = jsonData[str(nkid)] strokeData = jsonData[str(nkid)]
except KeyError: except KeyError: # pragma: no cover
dologging('nklog.log','Could not find strokeData') dologging('nklog.log','Could not find strokeData')
return 0 return 0
@@ -3424,7 +3423,7 @@ def handle_nk_async_workout(alldata, userid, nktoken, nkid, delaysec, defaulttim
if oarlockSessions: if oarlockSessions:
oarlocksession = oarlockSessions[0] oarlocksession = oarlockSessions[0]
seatNumber = oarlocksession['seatNumber'] seatNumber = oarlocksession['seatNumber']
except KeyError: except KeyError: # pragma: no cover
pass pass
df = strokeDataToDf(strokeData, seatIndex=seatNumber) df = strokeDataToDf(strokeData, seatIndex=seatNumber)
@@ -3439,14 +3438,14 @@ def handle_nk_async_workout(alldata, userid, nktoken, nkid, delaysec, defaulttim
workoutid, error = add_workout_from_data(userid, nkid, data, df) workoutid, error = add_workout_from_data(userid, nkid, data, df)
# dologging('nklog.log','NK Workout ID {id}'.format(id=workoutid)) # dologging('nklog.log','NK Workout ID {id}'.format(id=workoutid))
if workoutid == 0: if workoutid == 0: # pragma: no cover
return 0 return 0
try: try:
workout = Workout.objects.get(id=workoutid) workout = Workout.objects.get(id=workoutid)
newnkid = workout.uploadedtonk newnkid = workout.uploadedtonk
sr = create_or_update_syncrecord(workout.user, workout, nkid=newnkid) sr = create_or_update_syncrecord(workout.user, workout, nkid=newnkid)
except Workout.DoesNotExist: except Workout.DoesNotExist: # pragma: no cover
pass pass
return workoutid return workoutid
@@ -3775,7 +3774,7 @@ def handle_c2_async_workout(alldata, userid, c2token, c2id, delaysec,
return workoutid return workoutid
@app.task @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: try:
item = alldata[id] item = alldata[id]
except KeyError: except KeyError:
@@ -3880,7 +3879,7 @@ def fetch_strava_workout(stravatoken, oauth_data, stravaid, csvfilename, userid,
wsize = round(5./dt) wsize = round(5./dt)
velo2 = ewmovingaverage(velo, wsize) velo2 = ewmovingaverage(velo, wsize)
except ValueError: except ValueError: # pragma: no cover
velo2 = velo velo2 = velo
if coords is not None: if coords is not None:
+7
View File
@@ -23,9 +23,13 @@
<li class="grid_3"> <li class="grid_3">
{% if courses %} {% if courses %}
<p> <p>
<form enctype="multipart/form-data" method="post">
{% csrf_token %}
<input name="courses" type="submit" value="Download selected courses">
<table width="100%" class="listtable shortpadded"> <table width="100%" class="listtable shortpadded">
<thead> <thead>
<tr> <tr>
<th> Download</th>
<th> Country</th> <th> Country</th>
<th> Name</th> <th> Name</th>
<th> Distance</th> <th> Distance</th>
@@ -34,6 +38,7 @@
<tbody> <tbody>
{% for course in courses %} {% for course in courses %}
<tr> <tr>
<td> <input type="checkbox" value={{ course.id }} name="courseid"> </td>
<td> {{ course.country }} </td> <td> {{ course.country }} </td>
<td> <td>
<a href="/rowers/courses/{{ course.id }}/">{{ course.name }}</a> <a href="/rowers/courses/{{ course.id }}/">{{ course.name }}</a>
@@ -47,6 +52,7 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</form>
</p> </p>
{% else %} {% else %}
<p> No courses found </p> <p> No courses found </p>
@@ -106,6 +112,7 @@
<p>CrewNerd has published a nice video tutorial of the process. <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 <a href="https://youtu.be/whhWFmMJbhM">Click here</a> to see the video. The part
we're interested in starts at 2:05. 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>
<p> <p>
+1 -1
View File
@@ -198,7 +198,7 @@ class WorkoutFactory(factory.DjangoModelFactory):
startdatetime = FuzzyDateTime(start_dt=one_month_ago, force_year=datetime.date.today().year) startdatetime = FuzzyDateTime(start_dt=one_month_ago, force_year=datetime.date.today().year)
starttime = get_random_file(name=faker.word())['starttime'] starttime = get_random_file(name=faker.word())['starttime']
workouttype='water' workouttype='water'
date=timezone.now().date() date=startdatetime.evaluate(2,None, False).date()
duration=get_random_file(name=faker.word())['duration'] duration=get_random_file(name=faker.word())['duration']
distance=get_random_file(name=faker.word())['totaldist'] distance=get_random_file(name=faker.word())['totaldist']
csvfilename=get_random_file(name=faker.word())['filename'] csvfilename=get_random_file(name=faker.word())['filename']
+123
View File
@@ -25,6 +25,11 @@ class ListWorkoutTest(TestCase):
self.c = Client() self.c = Client()
self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) self.user_workouts = WorkoutFactory.create_batch(5, user=self.r)
for w in self.user_workouts:
if w.date != w.startdatetime.date():
w.date = w.startdatetime.date()
w.save()
self.factory = RequestFactory() self.factory = RequestFactory()
self.password = faker.word() self.password = faker.word()
self.u.set_password(self.password) self.u.set_password(self.password)
@@ -79,6 +84,10 @@ class PlannedSessionTests(TestCase):
self.c = Client() self.c = Client()
self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) self.user_workouts = WorkoutFactory.create_batch(5, user=self.r)
for w in self.user_workouts:
if w.date != w.startdatetime.date():
w.date = w.startdatetime.date()
w.save()
self.factory = RequestFactory() self.factory = RequestFactory()
self.password = faker.word() self.password = faker.word()
self.u.set_password(self.password) self.u.set_password(self.password)
@@ -130,6 +139,22 @@ class ForcecurveTest(TestCase):
self.c = Client() self.c = Client()
self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) self.user_workouts = WorkoutFactory.create_batch(5, user=self.r)
for w in self.user_workouts:
if w.date != w.startdatetime.date():
w.date = w.startdatetime.date()
w.save()
result = get_random_file(filename='rowers/tests/testdata/quiske_in_stroke.csv')
self.winstroke = WorkoutFactory(user=self.r,
csvfilename=result['filename'],
starttime=result['starttime'],
startdatetime=result['startdatetime'],
duration=result['duration'],
distance=result['totaldist'],
workouttype = 'water',
)
self.factory = RequestFactory() self.factory = RequestFactory()
self.password = faker.word() self.password = faker.word()
@@ -143,6 +168,7 @@ class ForcecurveTest(TestCase):
except (IOError, FileNotFoundError,OSError): except (IOError, FileNotFoundError,OSError):
pass pass
@patch('rowers.dataprep.getsmallrowdata_db',side_effect = mocked_getempowerdata_db) @patch('rowers.dataprep.getsmallrowdata_db',side_effect = mocked_getempowerdata_db)
def test_forcecurve_plot(self, mocked_getsmallrowdata_db): def test_forcecurve_plot(self, mocked_getsmallrowdata_db):
login = self.c.login(username=self.u.username, password = self.password) login = self.c.login(username=self.u.username, password = self.password)
@@ -182,7 +208,62 @@ class ForcecurveTest(TestCase):
response = self.c.get(url) response = self.c.get(url)
self.assertEqual(response.status_code,200) self.assertEqual(response.status_code,200)
url = '/rowers/analysis/forcecurveanalysis/1/delete/'
response = self.c.get(url)
self.assertEqual(response.status_code, 200)
form_data = {}
response = self.c.post(url, form_data, follow=True)
expected_url = '/rowers/analysis/forcecurveanalysis/'
self.assertRedirects(response, expected_url=expected_url, status_code=302,target_status_code=200)
def test_instroke_analysis(self):
login = self.c.login(username=self.u.username, password = self.password)
self.assertTrue(login)
encoded1 = str(encoder.encode_hex(self.winstroke.id))
url = '/rowers/workout/'+encoded1+'/instroke/interactive/'
response = self.c.get(url)
self.assertEqual(response.status_code,200)
# saving
form_data = {
'spm_min': 19,
'spm_max': 25,
'activeminutesmin': 0,
'activeminutesmax': 10,
'individual_curves': False,
'notes': 'aap',
'name': 'sdsd',
'metric': 'boat accelerator curve',
'_save_as_new':'Save'
}
form = InstrokeForm(form_data,choices=['boat accelerator curve', 'oar angle velocity curve'])
result = form.is_valid()
if not result:
print(form.errors)
self.assertTrue(form.is_valid())
response = self.c.post(url, form_data)
self.assertEqual(response.status_code,200)
url = '/rowers/analysis/instrokeanalysis/'
response = self.c.get(url)
self.assertEqual(response.status_code,200)
url = '/rowers/analysis/instrokeanalysis/1/delete/'
response = self.c.get(url)
self.assertEqual(response.status_code, 200)
form_data = {}
response = self.c.post(url, form_data, follow=True)
expected_url = '/rowers/analysis/instrokeanalysis/'
self.assertRedirects(response, expected_url=expected_url, status_code=302,target_status_code=200)
@@ -198,6 +279,10 @@ class WorkoutCompareTestNew(TestCase):
self.c = Client() self.c = Client()
self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) self.user_workouts = WorkoutFactory.create_batch(5, user=self.r)
for w in self.user_workouts:
if w.date != w.startdatetime.date():
w.date = w.startdatetime.date()
w.save()
self.factory = RequestFactory() self.factory = RequestFactory()
self.password = faker.word() self.password = faker.word()
self.u.set_password(self.password) self.u.set_password(self.password)
@@ -296,6 +381,10 @@ class WorkoutBoxPlotTestNew(TestCase):
self.c = Client() self.c = Client()
self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) self.user_workouts = WorkoutFactory.create_batch(5, user=self.r)
for w in self.user_workouts:
if w.date != w.startdatetime.date():
w.date = w.startdatetime.date()
w.save()
self.factory = RequestFactory() self.factory = RequestFactory()
self.password = faker.word() self.password = faker.word()
self.u.set_password(self.password) self.u.set_password(self.password)
@@ -393,6 +482,10 @@ class WorkoutHistoTestNew(TestCase):
self.c = Client() self.c = Client()
self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) self.user_workouts = WorkoutFactory.create_batch(5, user=self.r)
for w in self.user_workouts:
if w.date != w.startdatetime.date():
w.date = w.startdatetime.date()
w.save()
self.factory = RequestFactory() self.factory = RequestFactory()
self.password = faker.word() self.password = faker.word()
self.u.set_password(self.password) self.u.set_password(self.password)
@@ -490,6 +583,10 @@ class History(TestCase):
self.c = Client() self.c = Client()
self.user_workouts = WorkoutFactory.create_batch(20, user=self.r) self.user_workouts = WorkoutFactory.create_batch(20, user=self.r)
for w in self.user_workouts:
if w.date != w.startdatetime.date():
w.date = w.startdatetime.date()
w.save()
self.factory = RequestFactory() self.factory = RequestFactory()
self.password = faker.word() self.password = faker.word()
self.u.set_password(self.password) self.u.set_password(self.password)
@@ -577,6 +674,10 @@ class GoldMedalScores(TestCase):
self.c = Client() self.c = Client()
self.user_workouts = WorkoutFactory.create_batch(20, user=self.r) self.user_workouts = WorkoutFactory.create_batch(20, user=self.r)
for w in self.user_workouts:
if w.date != w.startdatetime.date():
w.date = w.startdatetime.date()
w.save()
result = get_random_file(filename='rowers/tests/testdata/onwater2.csv') result = get_random_file(filename='rowers/tests/testdata/onwater2.csv')
self.w1 = WorkoutFactory(user=self.r, self.w1 = WorkoutFactory(user=self.r,
csvfilename=result['filename'], csvfilename=result['filename'],
@@ -677,6 +778,10 @@ class WorkoutFlexallTestNew(TestCase):
self.c = Client() self.c = Client()
self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) self.user_workouts = WorkoutFactory.create_batch(5, user=self.r)
for w in self.user_workouts:
if w.date != w.startdatetime.date():
w.date = w.startdatetime.date()
w.save()
self.factory = RequestFactory() self.factory = RequestFactory()
self.password = faker.word() self.password = faker.word()
self.u.set_password(self.password) self.u.set_password(self.password)
@@ -774,6 +879,10 @@ class WorkoutStatsTestNew(TestCase):
self.c = Client() self.c = Client()
self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) self.user_workouts = WorkoutFactory.create_batch(5, user=self.r)
for w in self.user_workouts:
if w.date != w.startdatetime.date():
w.date = w.startdatetime.date()
w.save()
today = datetime.date.today() today = datetime.date.today()
i = 0 i = 0
@@ -1082,6 +1191,10 @@ class MarkerPerformanceTest(TestCase):
self.c = Client() self.c = Client()
self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) self.user_workouts = WorkoutFactory.create_batch(5, user=self.r)
for w in self.user_workouts:
if w.date != w.startdatetime.date():
w.date = w.startdatetime.date()
w.save()
self.factory = RequestFactory() self.factory = RequestFactory()
self.password = faker.word() self.password = faker.word()
self.u.set_password(self.password) self.u.set_password(self.password)
@@ -1095,6 +1208,8 @@ class MarkerPerformanceTest(TestCase):
workouttype='rower', workouttype='rower',
rankingpiece=True, rankingpiece=True,
) )
self.werg2k.date = self.werg2k.startdatetime.date()
self.werg2k.save()
# need a 2k, 5k, 1h row # need a 2k, 5k, 1h row
self.werg5k = WorkoutFactory( self.werg5k = WorkoutFactory(
@@ -1104,6 +1219,8 @@ class MarkerPerformanceTest(TestCase):
workouttype='rower', workouttype='rower',
rankingpiece=True, rankingpiece=True,
) )
self.werg5k.date = self.werg5k.startdatetime.date()
self.werg5k.save()
# need a 2k, 5k, 1h row # need a 2k, 5k, 1h row
self.werg1h = WorkoutFactory( self.werg1h = WorkoutFactory(
@@ -1113,6 +1230,8 @@ class MarkerPerformanceTest(TestCase):
workouttype='rower', workouttype='rower',
rankingpiece=True, rankingpiece=True,
) )
self.werg1h.date = self.werg1h.startdatetime.date()
self.werg1h.save()
def tearDown(self): def tearDown(self):
for workout in self.user_workouts: for workout in self.user_workouts:
@@ -1280,6 +1399,10 @@ class AlertTest(TestCase):
self.c = Client() self.c = Client()
self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) self.user_workouts = WorkoutFactory.create_batch(5, user=self.r)
for w in self.user_workouts:
if w.date != w.startdatetime.date():
w.date = w.startdatetime.date()
w.save()
self.factory = RequestFactory() self.factory = RequestFactory()
self.password = faker.word() self.password = faker.word()
self.u.set_password(self.password) self.u.set_password(self.password)
+84 -12
View File
@@ -71,8 +71,6 @@ class ListWorkoutTest(TestCase):
login = self.c.login(username=self.u.username, password=self.password) login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login) self.assertTrue(login)
url = reverse('workouts_view') url = reverse('workouts_view')
response = self.c.get(url) response = self.c.get(url)
@@ -88,6 +86,60 @@ class ListWorkoutTest(TestCase):
response = self.c.get(url3) response = self.c.get(url3)
self.assertEqual(response.status_code,200) self.assertEqual(response.status_code,200)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.dataprep.myqueue')
def test_bulk_workouts(self, mocked_sqlalchemy,
mocked_getsmallrowdata_db,
mocked_myqueue):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
url = '/rowers/list-workouts/?selectworkouts=true'
response = self.c.get(url)
self.assertEqual(response.status_code,200)
form_data = {
'action': ['remove'],
'selectworkouts': ['Submit'],
'workoutid': [encoder.encode_hex(1), encoder.encode_hex(2)],
}
response = self.c.post(url, form_data, follow=True)
expected_url = reverse('workouts_bulk_actions')
self.assertRedirects(response,
expected_url=expected_url,
status_code=302,
target_status_code=200)
form_data = {
'workouts': ['1','2'],
'action': ['export'],
'destination': 'strava',
}
response = self.c.post(expected_url, form_data, follow=True)
url = expected_url
expected_url = reverse('workouts_view')
self.assertRedirects(response,
expected_url=expected_url,
status_code=302,
target_status_code=200)
form_data = {
'workouts': ['1','2'],
'action': ['remove'],
}
response = self.c.post(url, form_data, follow=True)
expected_url = reverse('workouts_view')
self.assertRedirects(response,
expected_url=expected_url,
status_code=302,
target_status_code=200)
@override_settings(TESTING=True) @override_settings(TESTING=True)
class WorkoutViewTest(TestCase): class WorkoutViewTest(TestCase):
def setUp(self): def setUp(self):
@@ -118,11 +170,6 @@ class WorkoutViewTest(TestCase):
workouttype = 'water', workouttype = 'water',
) )
# self.comments = CommentFactory.create_batch(5,
# user = self.u,
# created = timezone.now,
# workout = self.wwater)
result = get_random_file(filename='rowers/tests/testdata/quiske_in_stroke.csv') result = get_random_file(filename='rowers/tests/testdata/quiske_in_stroke.csv')
self.winstroke = WorkoutFactory(user=self.r, self.winstroke = WorkoutFactory(user=self.r,
@@ -184,6 +231,8 @@ class WorkoutViewTest(TestCase):
for v in vs: for v in vs:
v.delete() v.delete()
@patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db') @patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.middleware.myqueue') @patch('rowers.middleware.myqueue')
@@ -205,6 +254,34 @@ class WorkoutViewTest(TestCase):
response = self.c.post(url,form_data) response = self.c.post(url,form_data)
self.assertEqual(response.status_code,200) self.assertEqual(response.status_code,200)
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('rowers.middleware.myqueue')
def test_resample(self, mocked_sqlalchemy, mocked_getsmallrowdata_db,
mocked_myqueue):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
url = reverse('workout_resample_view',kwargs={'id':encoder.encode_hex(self.wwater.id)})
response = self.c.get(url)
self.assertEqual(response.status_code,200)
form_data = {
'resamplechoice': 'copy',
}
response = self.c.post(url,form_data)
self.assertEqual(response.status_code,200)
form_data = {
'resamplechoice': 'overwrite',
}
response = self.c.post(url,form_data)
self.assertEqual(response.status_code,200)
@patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db') @patch('rowers.dataprep.getsmallrowdata_db')
@pytest.mark.django_db(transaction=True) @pytest.mark.django_db(transaction=True)
@@ -555,11 +632,6 @@ class WorkoutViewTest(TestCase):
'file':screenshot_file, 'file':screenshot_file,
} }
#form = ImageForm(form_data, file_data)
#if not form.is_valid():
# print form.errors
#self.assertTrue(form.is_valid())
response = self.c.post(url,form_data,format='multipart',follow=True) response = self.c.post(url,form_data,format='multipart',follow=True)
expected_url = reverse(self.r.defaultlandingpage, expected_url = reverse(self.r.defaultlandingpage,
+12
View File
@@ -61,3 +61,15 @@ class SimpleViewTest(TestCase):
self.assertRedirects(response, self.assertRedirects(response,
expected_url='/rowers/list-workouts/', expected_url='/rowers/list-workouts/',
status_code=302,target_status_code=200) status_code=302,target_status_code=200)
def test_start_coachtrial(self):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
url = '/rowers/startcoachtrial/'
response = self.c.get(url,follow=True)
self.assertRedirects(response,
expected_url='/rowers/list-workouts/',
status_code=302,target_status_code=200)
+4 -26
View File
@@ -28,15 +28,6 @@ class ViewTest(TestCase):
self.nu = datetime.datetime.now() self.nu = datetime.datetime.now()
def test_upload_view_notloggedin(self):
response = self.c.post('/rowers/workout/upload/',follow=True)
self.assertRedirects(response, expected_url='/login/?next=/rowers/workout/upload/',
status_code=302,target_status_code=200)
self.assertEqual(response.status_code, 200)
@patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db)
def test_upload_view_sled(self, mocked_sqlalchemy,mocked_getsmallrowdata_db): def test_upload_view_sled(self, mocked_sqlalchemy,mocked_getsmallrowdata_db):
@@ -129,7 +120,7 @@ class ViewTest(TestCase):
f_to_be_deleted = w.csvfilename f_to_be_deleted = w.csvfilename
try: try:
os.remove(f_to_be_deleted+'.gz') os.remove(f_to_be_deleted+'.gz')
except (FileNotFoundError,OSError): except (FileNotFoundError,OSError): # pragma: no cover
pass pass
@patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.create_engine')
@@ -230,7 +221,7 @@ class ViewTest(TestCase):
f_to_be_deleted = w.csvfilename f_to_be_deleted = w.csvfilename
try: try:
os.remove(f_to_be_deleted+'.gz') os.remove(f_to_be_deleted+'.gz')
except (FileNotFoundError,OSError): except (FileNotFoundError,OSError): # pragma: no cover
pass pass
@@ -271,7 +262,7 @@ class ViewTest(TestCase):
f_to_be_deleted = w.csvfilename f_to_be_deleted = w.csvfilename
try: try:
os.remove(f_to_be_deleted+'.gz') os.remove(f_to_be_deleted+'.gz')
except (FileNotFoundError,OSError): except (FileNotFoundError,OSError): # pragma: no cover
pass pass
@patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.create_engine')
@@ -341,19 +332,6 @@ class ViewTest(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
filename = 'rowers/tests/testdata/crewnerddata.CSV'
f = open(filename,'rb')
file_data = {'file': f}
form_data = {}
form = CNsummaryForm(form_data,file_data)
response = self.c.post('rowers/workout/'+encoded1+'/crewnerdsummary',
form_data,
follow=True)
f.close()
self.assertEqual(response.status_code, 200)
w = Workout.objects.get(id=1) w = Workout.objects.get(id=1)
f_to_be_deleted = w.csvfilename f_to_be_deleted = w.csvfilename
try: try:
@@ -660,7 +638,7 @@ class ViewTest(TestCase):
@patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.TCXParser') @patch('rowers.dataprep.TCXParser')
def test_upload_view_TCX_CN(self, mocked_sqlalchemy, def test_upload_view_TCX_Rim(self, mocked_sqlalchemy,
mocked_tcx_parser): mocked_tcx_parser):
self.c.login(username='john',password='koeinsloot') self.c.login(username='john',password='koeinsloot')
+22
View File
@@ -7,6 +7,7 @@ from __future__ import unicode_literals
from .statements import * from .statements import *
nu = datetime.datetime.now() nu = datetime.datetime.now()
import rowers.courses as courses
tested = [ tested = [
@@ -27,6 +28,14 @@ class URLTests(TestCase):
r = Rower.objects.create(user=u,rowerplan='coach',gdproptin=True, ftpset=True, r = Rower.objects.create(user=u,rowerplan='coach',gdproptin=True, ftpset=True,
gdproptindate=timezone.now()) gdproptindate=timezone.now())
self.c = Client() 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() self.nu = datetime.datetime.now()
filename = 'rowers/tests/testdata/testdata.csv' filename = 'rowers/tests/testdata/testdata.csv'
@@ -168,6 +177,19 @@ class URLTests(TestCase):
'/rowers/workout/upload/team/', '/rowers/workout/upload/team/',
'/rowers/workouts-join/', '/rowers/workouts-join/',
'/rowers/workouts-join-select/', '/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'), name='strokedatajson_v3'),
re_path(r'^api/TCX/workouts/$', views.strokedata_tcx, re_path(r'^api/TCX/workouts/$', views.strokedata_tcx,
name='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'^500v/$', views.error500_view, name='error500_view'),
re_path(r'^500q/$', views.servererror_view, name='servererror_view'), re_path(r'^500q/$', views.servererror_view, name='servererror_view'),
path('502/', TemplateView.as_view(template_name='502.html'), name='502'), path('502/', TemplateView.as_view(template_name='502.html'), name='502'),
+28 -3
View File
@@ -312,6 +312,31 @@ def geo_distance(lat1, lon1, lat2, lon2):
return [distance, bearing] 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): def isbreakthrough(delta, cpvalues, p0, p1, p2, p3, ratio):
pwr = abs(p0)/(1+(delta/abs(p2))) pwr = abs(p0)/(1+(delta/abs(p2)))
@@ -322,9 +347,9 @@ def isbreakthrough(delta, cpvalues, p0, p1, p2, p3, ratio):
pwr *= ratio pwr *= ratio
delta = delta.values.astype(int) delta = delta.astype(int, errors='ignore').values
cpvalues = cpvalues.values.astype(int) cpvalues = cpvalues.astype(int, errors='ignore').values
pwr = pwr.astype(int) pwr = pwr.astype(int, errors='ignore').values
res = np.sum(cpvalues > pwr+1) res = np.sum(cpvalues > pwr+1)
res2 = np.sum(cpvalues > pwr2+1) res2 = np.sum(cpvalues > pwr2+1)
+26 -26
View File
@@ -56,7 +56,7 @@ def analysis_new(request,
try: try:
thesession = PlannedSession.objects.get(id=session) thesession = PlannedSession.objects.get(id=session)
if not can_view_session(user, thesession): if not can_view_session(user, thesession): # pragma: no cover
raise PermissionDenied("you cannot view this session") raise PermissionDenied("you cannot view this session")
except PlannedSession.DoesNotExist: except PlannedSession.DoesNotExist:
thesession = None thesession = None
@@ -102,7 +102,7 @@ def analysis_new(request,
try: try:
waterboattype = options['waterboattype'] waterboattype = options['waterboattype']
except KeyError: except KeyError: # pragma: no cover
pass pass
if request.method == 'POST': if request.method == 'POST':
@@ -188,7 +188,7 @@ def analysis_new(request,
pass pass
rankingtypes = [False,True] rankingtypes = [False,True]
if ranking: if ranking: # pragma: no cover
rankingtypes = [True] rankingtypes = [True]
@@ -272,7 +272,7 @@ def analysis_new(request,
request.session['options'] = options request.session['options'] = options
savedata = options.get('savedata',False) savedata = options.get('savedata',False)
if savedata: if savedata: # pragma: no cover
ids = options.get('ids',[]) ids = options.get('ids',[])
tw = [] tw = []
for id in ids: for id in ids:
@@ -526,7 +526,7 @@ def trendflexdata(workouts, options, userid=0):
extratitle = ' '+u.first_name+' '+u.last_name extratitle = ' '+u.first_name+' '+u.last_name
savedata = options.get('savedata',False) savedata = options.get('savedata',False)
if savedata: if savedata: # pragma: no cover
return df return df
script, div = interactive_multiflex(df, xparam, yparam, script, div = interactive_multiflex(df, xparam, yparam,
@@ -561,7 +561,7 @@ def flexalldata(workouts, options):
extratitle = ' '+u.first_name+' '+u.last_name extratitle = ' '+u.first_name+' '+u.last_name
savedata = options.get('savedata',False) savedata = options.get('savedata',False)
if savedata: if savedata: # pragma: no cover
workstrokesonly = not includereststrokes workstrokesonly = not includereststrokes
columns = [xparam, yparam1, yparam2, 'spm', 'driveenergy', 'distance'] columns = [xparam, yparam1, yparam2, 'spm', 'driveenergy', 'distance']
ids = [int(w.id) for w in workouts] ids = [int(w.id) for w in workouts]
@@ -603,7 +603,7 @@ def histodata(workouts, options):
extratitle = ' '+u.first_name+' '+u.last_name extratitle = ' '+u.first_name+' '+u.last_name
savedata = options.get('savedata',False) savedata = options.get('savedata',False)
if savedata: if savedata: # pragma: no cover
workstrokesonly = not includereststrokes workstrokesonly = not includereststrokes
ids = [int(w.id) for w in workouts] ids = [int(w.id) for w in workouts]
df = dataprep.getsmallrowdata_db([plotfield], ids=ids, df = dataprep.getsmallrowdata_db([plotfield], ids=ids,
@@ -640,7 +640,7 @@ def cpdata(workouts, options):
}) })
savedata = options.get('savedata',False) savedata = options.get('savedata',False)
if savedata: if savedata: # pragma: no cover
return powerdf return powerdf
if powerdf.empty: # pragma: no cover if powerdf.empty: # pragma: no cover
@@ -869,7 +869,7 @@ def comparisondata(workouts, options):
} }
savedata = options.get('savedata',False) savedata = options.get('savedata',False)
if savedata: if savedata: # pragma: no cover
workstrokesonly = not includereststrokes workstrokesonly = not includereststrokes
columns = [xparam, yparam1, columns = [xparam, yparam1,
'ftime', 'distance', 'fpace', 'ftime', 'distance', 'fpace',
@@ -950,7 +950,7 @@ def boxplotdata(workouts, options):
extratitle = ' '+u.first_name+' '+u.last_name extratitle = ' '+u.first_name+' '+u.last_name
savedata = options.get('savedata',False) savedata = options.get('savedata',False)
if savedata: if savedata: # pragma: no cover
return datadf return datadf
script, div = interactive_boxchart(datadf, plotfield, script, div = interactive_boxchart(datadf, plotfield,
@@ -1425,7 +1425,7 @@ def rankings_view2(request, userid=0,
wcdurations = options['wcdurations'] wcdurations = options['wcdurations']
wcpower = options['wcpower'] wcpower = options['wcpower']
lastupdated = options['lastupdated'] lastupdated = options['lastupdated']
except KeyError: except KeyError: # pragma: no cover
pass pass
try: try:
userid = options['userid'] userid = options['userid']
@@ -2017,7 +2017,7 @@ class ForceCurveAnalysisDelete(DeleteView):
def get_object(self, *args, **kwargs): def get_object(self, *args, **kwargs):
obj = super(ForceCurveAnalysisDelete, self).get_object(*args, **kwargs) obj = super(ForceCurveAnalysisDelete, self).get_object(*args, **kwargs)
if obj.rower != self.request.user.rower: if obj.rower != self.request.user.rower: # pragma: no cover
raise PermissionDenied("You are not allowed to delete this Analysis") raise PermissionDenied("You are not allowed to delete this Analysis")
return obj return obj
@@ -2039,8 +2039,7 @@ class SavedAnalysisView(UserPassesTestMixin, View):
analyses = None analyses = None
chart = instroke_multi_interactive_chart chart = instroke_multi_interactive_chart
def handle_no_permission(self): # pragma: no cover
def handle_no_permission(self):
if self.request.user.is_anonymous: if self.request.user.is_anonymous:
url = reverse('login')+'?next='+self.request.path url = reverse('login')+'?next='+self.request.path
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@@ -2079,7 +2078,7 @@ class SavedAnalysisView(UserPassesTestMixin, View):
super(SavedAnalysisView, self).setup(request, *args, **kwargs) super(SavedAnalysisView, self).setup(request, *args, **kwargs)
self.r = getrequestrower(request, userid=self.userid) self.r = getrequestrower(request, userid=self.userid)
if self.r is not None and not is_coach_user(self.request.user, self.r.user): if self.r is not None and not is_coach_user(self.request.user, self.r.user): # pragma: no cover
raise PermissionDenied("You are not allowed to see analyses for this user") raise PermissionDenied("You are not allowed to see analyses for this user")
self.analyses = self.analysis_class.objects.filter(rower=self.r).order_by("-date","-id") self.analyses = self.analysis_class.objects.filter(rower=self.r).order_by("-date","-id")
@@ -2087,7 +2086,7 @@ class SavedAnalysisView(UserPassesTestMixin, View):
startdate = request.GET.get('startdate') startdate = request.GET.get('startdate')
enddate = request.GET.get('enddate') enddate = request.GET.get('enddate')
self.searchform = SearchForm() self.searchform = SearchForm()
if query: if query: # pragma: no cover
query_list = query.split() query_list = query.split()
if self.analysis_class == InStrokeAnalysis: if self.analysis_class == InStrokeAnalysis:
self.analyses = self.analyses.filter( self.analyses = self.analyses.filter(
@@ -2111,13 +2110,13 @@ class SavedAnalysisView(UserPassesTestMixin, View):
) )
self.searchform = SearchForm(initial={'q': query}) self.searchform = SearchForm(initial={'q': query})
date_initial = {} date_initial = {}
if startdate: if startdate: # pragma: no cover
try: try:
self.analyses = self.analyses.filter(date__gte=startdate) self.analyses = self.analyses.filter(date__gte=startdate)
date_initial['startdate'] = startdate date_initial['startdate'] = startdate
except ValidationError: except ValidationError:
messages.error(request,"{startdate} is not a valid start date".format(startdate=startdate)) messages.error(request,"{startdate} is not a valid start date".format(startdate=startdate))
if enddate: if enddate: # pragma: no cover
self.analyses = self.analyses.filter(date__lte=enddate) self.analyses = self.analyses.filter(date__lte=enddate)
date_initial['enddate'] = enddate date_initial['enddate'] = enddate
self.dateform = DateRangeForm(initial=date_initial) self.dateform = DateRangeForm(initial=date_initial)
@@ -2136,7 +2135,7 @@ class SavedAnalysisView(UserPassesTestMixin, View):
'dateform': self.dateform, 'dateform': self.dateform,
}) })
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs): # pragma: no cover
form = self.form_class(request.POST) form = self.form_class(request.POST)
if form.is_valid(): if form.is_valid():
@@ -2211,7 +2210,7 @@ class InStrokeAnalysisDelete(DeleteView):
def get_object(self, *args, **kwargs): def get_object(self, *args, **kwargs):
obj = super(InStrokeAnalysisDelete, self).get_object(*args, **kwargs) obj = super(InStrokeAnalysisDelete, self).get_object(*args, **kwargs)
if obj.rower != self.request.user.rower: if obj.rower != self.request.user.rower: # pragma: no cover
raise PermissionDenied("You are not allowed to delete this Analysis") raise PermissionDenied("You are not allowed to delete this Analysis")
return obj return obj
@@ -2356,7 +2355,7 @@ def alert_report_view(request, id=0, userid=0, nperiod=0):
stats = alert_get_stats(alert, nperiod=nperiod) stats = alert_get_stats(alert, nperiod=nperiod)
is_ajax = request_is_ajax(request) is_ajax = request_is_ajax(request)
if is_ajax: if is_ajax: # pragma: no cover
return JSONResponse({ return JSONResponse({
"stats": stats, "stats": stats,
}) })
@@ -2680,9 +2679,10 @@ def history_view(request, userid=0):
last28 = timezone.now() - datetime.timedelta(days=28) last28 = timezone.now() - datetime.timedelta(days=28)
today = timezone.now() today = timezone.now()
lastyear = today - datetime.timedelta(days=365)
lastyear = datetime.datetime( #lastyear = datetime.datetime(
year=today.year - 1, month=today.month, day=today.day) # year=today.year - 1, month=today.month, day=today.day)
firstmay = datetime.datetime( firstmay = datetime.datetime(
year=today.year, month=5, day=1).astimezone(usertimezone) year=today.year, month=5, day=1).astimezone(usertimezone)
@@ -2771,7 +2771,7 @@ def history_view_data(request, userid=0):
df = getsmallrowdata_db(columns, ids=ids) df = getsmallrowdata_db(columns, ids=ids)
try: try:
df['deltat'] = df['time'].diff().clip(lower=0) df['deltat'] = df['time'].diff().clip(lower=0)
except KeyError: except KeyError: # pragma: no cover
pass pass
df = dataprep.clean_df_stats(df, workstrokesonly=True, df = dataprep.clean_df_stats(df, workstrokesonly=True,
ignoreadvanced=True, ignorehr=False) ignoreadvanced=True, ignorehr=False)
@@ -2824,7 +2824,7 @@ def history_view_data(request, userid=0):
ddict['powermean'] = int(wavg(ddf, 'power', 'deltat')) ddict['powermean'] = int(wavg(ddf, 'power', 'deltat'))
try: try:
ddict['powermax'] = ddf['power'].max().astype(int) ddict['powermax'] = ddf['power'].max().astype(int)
except KeyError: except KeyError: # pragma: no cover
ddict['powermax'] = 0 ddict['powermax'] = 0
ddict['nrworkouts'] = a_workouts.count() ddict['nrworkouts'] = a_workouts.count()
listofdicts.append(ddict) listofdicts.append(ddict)
@@ -2839,7 +2839,7 @@ def history_view_data(request, userid=0):
try: try:
totalsdict['powermean'] = int(wavg(df, 'power', 'deltat')) totalsdict['powermean'] = int(wavg(df, 'power', 'deltat'))
totalsdict['powermax'] = df['power'].max().astype(int) totalsdict['powermax'] = df['power'].max().astype(int)
except KeyError: except KeyError: # pragma: no cover
totalsdict['powermean'] = 0 totalsdict['powermean'] = 0
totalsdict['powermax'] = 0 totalsdict['powermax'] = 0
try: try:
+129 -16
View File
@@ -1,6 +1,7 @@
from rowers.views.statements import * from rowers.views.statements import *
from rowers.tasks import handle_calctrimp from rowers.tasks import handle_calctrimp
from rowers.opaque import encoder from rowers.opaque import encoder
from rowers.courses import coursetokml, coursestokml
from xml.etree import ElementTree as ET from xml.etree import ElementTree as ET
import arrow 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.decorators import parser_classes
from rest_framework.parsers import BaseParser from rest_framework.parsers import BaseParser
from rowers.utils import geo_distance
from datetime import datetime as dt from datetime import datetime as dt
import rowingdata.tcxtools as tcxtools import rowingdata.tcxtools as tcxtools
@@ -24,7 +27,7 @@ class XMLParser(BaseParser):
dologging("apilog.log", "XML Parser") dologging("apilog.log", "XML Parser")
try: try:
s = ET.parse(stream).getroot() s = ET.parse(stream).getroot()
except Exception as e: except Exception as e: # pragma: no cover
dologging("apilog.log",e) dologging("apilog.log",e)
return HttpResponse(status=500) return HttpResponse(status=500)
return s return s
@@ -228,13 +231,123 @@ def strokedataform_v2(request, id=0):
def part_of_day(hour): def part_of_day(hour):
if 5 <= hour < 12: if 5 <= hour < 12:
return "Morning" return "Morning"
elif 12 <= hour < 18: elif 12 <= hour < 18: # pragma: no cover
return "Afternoon" return "Afternoon"
elif 18 <= hour < 24: elif 18 <= hour < 24: # pragma: no cover
return "Evening" return "Evening"
else: else: # pragma: no cover
return "Night" 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 @csrf_exempt
@login_required() @login_required()
@api_view(["POST"]) @api_view(["POST"])
@@ -244,11 +357,11 @@ def strokedata_tcx(request):
""" """
Upload a TCX file through API 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') 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','POST data not application/xml, request to TCX endpoint')
dologging('apilog.log', request.content_type.lower()) dologging('apilog.log', request.content_type.lower())
return HttpResponseNotAllowed("Need application/xml") return HttpResponseNotAllowed("Need application/xml")
@@ -275,7 +388,7 @@ def strokedata_tcx(request):
lap_duration_seconds = float(lap_duration_node.text) lap_duration_seconds = float(lap_duration_node.text)
total_duration += lap_duration_seconds total_duration += lap_duration_seconds
except Exception as e: except Exception as e: # pragma: no cover
dologging('apilog.log','TCX') dologging('apilog.log','TCX')
dologging('apilog.log',e) dologging('apilog.log',e)
return HttpResponseNotAllowed("Could not parse TCX data") return HttpResponseNotAllowed("Could not parse TCX data")
@@ -329,7 +442,7 @@ def strokedata_tcx(request):
"workout id": workoutid, "workout id": workoutid,
"status": "success", "status": "success",
}) })
except Exception as e: except Exception as e: # pragma: no cover
dologging('apilog.log','TCX API endpoint') dologging('apilog.log','TCX API endpoint')
dologging('apilog.log',e) dologging('apilog.log',e)
return HttpResponse(status=500) return HttpResponse(status=500)
@@ -382,7 +495,7 @@ def strokedatajson_v3(request):
title = request.data.get('name','') title = request.data.get('name','')
try: try:
elapsedTime = request.data['elapsedTime'] elapsedTime = request.data['elapsedTime']
except KeyError: except KeyError: # pragma: no cover
try: try:
duration = request.data['duration'] duration = request.data['duration']
try: try:
@@ -394,7 +507,7 @@ def strokedatajson_v3(request):
return HttpResponse("Missing Elapsed Time", status=400) return HttpResponse("Missing Elapsed Time", status=400)
try: try:
totalDistance = request.data['distance'] totalDistance = request.data['distance']
except KeyError: except KeyError: # pragma: no cover
return HttpResponse("Missing Total Distance", status=400) return HttpResponse("Missing Total Distance", status=400)
timeZone = request.data.get('timezone','UTC') timeZone = request.data.get('timezone','UTC')
workouttype = request.data.get('workouttype','rower') workouttype = request.data.get('workouttype','rower')
@@ -415,12 +528,12 @@ def strokedatajson_v3(request):
df = pd.DataFrame() df = pd.DataFrame()
try: try:
strokes = request.data['strokes'] strokes = request.data['strokes']
except KeyError: except KeyError: # pragma: no cover
return HttpResponse("No Stroke Data in JSON", status=400) return HttpResponse("No Stroke Data in JSON", status=400)
try: try:
df = pd.DataFrame(strokes['data']) df = pd.DataFrame(strokes['data'])
except KeyError: except KeyError: # pragma: no cover
try: try:
df = pd.DataFrame(request.data['strokedata']) df = pd.DataFrame(request.data['strokedata'])
except: except:
@@ -431,7 +544,7 @@ def strokedatajson_v3(request):
status, comment, data = api_get_dataframe(startdatetime, df) status, comment, data = api_get_dataframe(startdatetime, df)
if status != 200: if status != 200: # pragma: no cover
return HttpResponse(comment, status=status) return HttpResponse(comment, status=status)
@@ -541,7 +654,7 @@ def strokedatajson_v2(request, id):
try: try:
for d in request.data['data']: for d in request.data['data']:
dologging('apilog.log',json.dumps(d)) dologging('apilog.log',json.dumps(d))
except KeyError: except KeyError: # pragma: no cover
try: try:
for d in request.data['strokedata']: for d in request.data['strokedata']:
dologging('apilog.log',json.dumps(d)) dologging('apilog.log',json.dumps(d))
@@ -565,7 +678,7 @@ def strokedatajson_v2(request, id):
df.sort_index(inplace=True) df.sort_index(inplace=True)
status, comment, data = api_get_dataframe(row.startdatetime, df) status, comment, data = api_get_dataframe(row.startdatetime, df)
if status != 200: if status != 200: # pragma: no cover
return HttpResponse(comment, status=status) return HttpResponse(comment, status=status)
r = getrower(request.user) r = getrower(request.user)
+16 -16
View File
@@ -41,7 +41,7 @@ def workout_export_view(request, id=0, source='c2'):
integration = importsources[source](request.user) integration = importsources[source](request.user)
try: try:
id = integration.workout_export(w) id = integration.workout_export(w)
except NoTokenError: except NoTokenError: # pragma: no cover
return HttpResponseRedirect(integration.make_authorization_url()) return HttpResponseRedirect(integration.make_authorization_url())
messages.info( messages.info(
@@ -73,7 +73,7 @@ def rower_rojabo_authorize(request): # pragma: no cover
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@login_required() @login_required()
def rower_integration_authorize(request, source='c2'): def rower_integration_authorize(request, source='c2'): # pragma: no cover
try: try:
integration = importsources[source](request.user) integration = importsources[source](request.user)
except KeyError: except KeyError:
@@ -107,7 +107,7 @@ def rower_polar_authorize(request, source='polar'): # pragma: no cover
def rower_integration_token_refresh(request, source='c2'): def rower_integration_token_refresh(request, source='c2'):
try: try:
integration = importsources[source](request.user) integration = importsources[source](request.user)
except KeyError: except KeyError: # pragma: no cover
url = reverse('workouts_view') url = reverse('workouts_view')
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
try: try:
@@ -217,7 +217,7 @@ def rower_process_polarcallback(request):
try: try:
error = polar_user_data['error'] error = polar_user_data['error']
if error['error_type'] == 'user_already_registered': if error['error_type'] == 'user_already_registered': # pragma: no cover
s = error['message'] s = error['message']
tester = re.compile(r'.*userid:(?P<id>\d+)') tester = re.compile(r'.*userid:(?P<id>\d+)')
testresult = tester.match(s) testresult = tester.match(s)
@@ -371,7 +371,7 @@ def workout_import_view(request, source='c2'):
r = getrequestrower(request) r = getrequestrower(request)
try: try:
integration = importsources[source](request.user) integration = importsources[source](request.user)
except KeyError: except KeyError: # pragma: no cover
messages.error(request,"This integration is not supported") messages.error(request,"This integration is not supported")
url = reverse('workouts_view') url = reverse('workouts_view')
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@@ -428,7 +428,7 @@ def workout_import_view(request, source='c2'):
startdate=startdate, startdate=startdate,
enddate=enddate enddate=enddate
) )
except NoTokenError: except NoTokenError: # pragma: no cover
messages.error(request,"You must first make the connection to {source}".format( messages.error(request,"You must first make the connection to {source}".format(
source=source source=source
)) ))
@@ -504,7 +504,7 @@ def rower_process_stravacallback(request):
try: try:
res = strava_integration.get_token(code,logfile='strava_log.log') res = strava_integration.get_token(code,logfile='strava_log.log')
except NoTokenError: except NoTokenError: # pragma: no cover
message = "Something went wrong with the Strava authorization" message = "Something went wrong with the Strava authorization"
messages.error(request, message) messages.error(request, message)
url = reverse('rower_exportsettings_view') url = reverse('rower_exportsettings_view')
@@ -526,7 +526,7 @@ def rower_process_stravacallback(request):
r.save() r.save()
try: try:
_ = strava_integration.set_strava_athlete_id() _ = strava_integration.set_strava_athlete_id()
except NoTokenError: except NoTokenError: # pragma: no cover
messages.error(request,'Something went wrong storing your Strava ID. Please authorize again') messages.error(request,'Something went wrong storing your Strava ID. Please authorize again')
url = reverse('rower_exportsettings_view') url = reverse('rower_exportsettings_view')
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@@ -540,7 +540,7 @@ def rower_process_stravacallback(request):
messages.error(request, message) messages.error(request, message)
url = reverse('rower_exportsettings_view') url = reverse('rower_exportsettings_view')
return HttpResponseRedirect(url) return HttpResponseRedirect(url) # pragma: no cover
# Process SportTracks callback # Process SportTracks callback
@@ -653,7 +653,7 @@ def rower_process_testcallback(request): # pragma: no cover
message="This functionality requires a Pro plan or higher", message="This functionality requires a Pro plan or higher",
redirect_field_name=None) redirect_field_name=None)
@permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True) @permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True)
def workout_rojaboimport_view(request, message="", userid=0): def workout_rojaboimport_view(request, message="", userid=0): # pragma: no cover
r = getrequestrower(request, userid=userid) r = getrequestrower(request, userid=userid)
if r.user != request.user: if r.user != request.user:
messages.error( messages.error(
@@ -728,21 +728,21 @@ def workout_rojaboimport_view(request, message="", userid=0):
item['warm_up']['steps'], item['warm_up']['steps'],
warmup=True warmup=True
) )
except KeyError: except KeyError: # pragma: no cover
pass pass
try: try:
steps = steps + rojabo_stuff.stepsconvert( steps = steps + rojabo_stuff.stepsconvert(
item['primary']['steps'], item['primary']['steps'],
startid=len(steps) startid=len(steps)
) )
except KeyError: except KeyError: # pragma: no cover
pass pass
try: try:
steps = steps + rojabo_stuff.stepsconvert( steps = steps + rojabo_stuff.stepsconvert(
item['cool_down']['steps'], item['cool_down']['steps'],
cooldown=True, cooldown=True,
startid=len(steps)) startid=len(steps))
except KeyError: except KeyError: # pragma: no cover
pass pass
if steps: if steps:
@@ -755,7 +755,7 @@ def workout_rojaboimport_view(request, message="", userid=0):
ps.save() ps.save()
messages.info(request,'Saved planned session {id}'.format(id=ps.id)) messages.info(request,'Saved planned session {id}'.format(id=ps.id))
except KeyError: except KeyError: # pragma: no cover
pass pass
except KeyError: except KeyError:
pass pass
@@ -1019,7 +1019,7 @@ def garmin_details_view(request):
def workout_getimportview(request, externalid, source='c2', do_async=True): def workout_getimportview(request, externalid, source='c2', do_async=True):
try: try:
integration = importsources[source](request.user) integration = importsources[source](request.user)
except (TypeError, NotImplementedError, KeyError): except (TypeError, NotImplementedError, KeyError): # pragma: no cover
return reverse("workouts_view") return reverse("workouts_view")
if 'startdate' in request.session and source == 'nk': # pragma: no cover if 'startdate' in request.session and source == 'nk': # pragma: no cover
startdate = request.session.get('startdate') startdate = request.session.get('startdate')
@@ -1051,7 +1051,7 @@ def workout_getimportview(request, externalid, source='c2', do_async=True):
# Imports all new workouts from SportTracks # Imports all new workouts from SportTracks
@login_required() @login_required()
def workout_getsporttracksworkout_all(request): def workout_getsporttracksworkout_all(request): # pragma: no cover
st_integration = importsources['sporttracks'](request.user) st_integration = importsources['sporttracks'](request.user)
try: try:
_ = st_integration.get_workouts() _ = st_integration.get_workouts()
+20 -1
View File
@@ -7,7 +7,7 @@ from django.contrib.gis.geoip2 import GeoIP2
from django import forms from django import forms
from rowers.plannedsessions import timefield_to_seconds_duration from rowers.plannedsessions import timefield_to_seconds_duration
from rowers.courses import getnearestraces, getnearestcourses from rowers.courses import getnearestraces, getnearestcourses,coursetokml, coursestokml
# List Courses # List Courses
@@ -16,6 +16,25 @@ def courses_view(request):
r = getrower(request.user) r = getrower(request.user)
g = GeoIP2() 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') ip = request.META.get('HTTP_X_REAL_IP', '1.1.1.1')
try: try:
lat_lon = g.lat_lon(ip) lat_lon = g.lat_lon(ip)
+1 -1
View File
@@ -22,7 +22,7 @@ from scipy.special import lambertw
from io import BytesIO from io import BytesIO
import rowers.plots as plots import rowers.plots as plots
from rowers.permissions import IsOwnerOrNot, IsCompetitorOrNot 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.decorators import api_view, renderer_classes, permission_classes
from rest_framework.response import Response from rest_framework.response import Response
from rq.job import Job from rq.job import Job
+7 -7
View File
@@ -258,7 +258,7 @@ def user_messages(request,userid=0):
'rower':r}) 'rower':r})
@login_required() @login_required()
def user_message_delete(request,id=0): def user_message_delete(request,id=0): # pragma: no cover
try: try:
msg = UserMessage.objects.get(id=id) msg = UserMessage.objects.get(id=id)
except UserMessage.DoesNotExist: except UserMessage.DoesNotExist:
@@ -278,7 +278,7 @@ def user_message_delete(request,id=0):
@login_required() @login_required()
def user_message_markread(request,id=0): def user_message_markread(request,id=0): # pragma: no cover
try: try:
msg = UserMessage.objects.get(id=id) msg = UserMessage.objects.get(id=id)
except UserMessage.DoesNotExist: except UserMessage.DoesNotExist:
@@ -448,13 +448,13 @@ def rower_exportsettings_view(request, userid=0):
if not user_is_not_basic(r.user): # pragma: no cover if not user_is_not_basic(r.user): # pragma: no cover
if not user_is_coachee(r.user): if not user_is_coachee(r.user):
doset = False doset = False
if not doset: if not doset: # pragma: no cover
before = getattr(r, attr) before = getattr(r, attr)
if before == value: if before == value:
doset = True doset = True
if doset: if doset:
setattr(r, attr, value) setattr(r, attr, value)
else: else: # pragma: no cover
if user_is_not_basic(r.user): if user_is_not_basic(r.user):
messages.error( messages.error(
request, 'Could not set '+attr+'. You need to create the connection first.') request, 'Could not set '+attr+'. You need to create the connection first.')
@@ -605,7 +605,7 @@ def rower_simpleprefs_view(request, userid=0):
r = getrequestrowercoachee(request, userid=userid, notpermanent=True) r = getrequestrowercoachee(request, userid=userid, notpermanent=True)
firsttime = False firsttime = False
if not r.ftpset: if not r.ftpset: # pragma: no cover
r.ftpset = True r.ftpset = True
r.save() r.save()
firsttime = True firsttime = True
@@ -623,7 +623,7 @@ def rower_simpleprefs_view(request, userid=0):
form = SimpleRowerPowerForm(instance=r) form = SimpleRowerPowerForm(instance=r)
if request.method == 'POST': if request.method == 'POST': # pragma: no cover
form = SimpleRowerPowerForm(request.POST, instance=r) form = SimpleRowerPowerForm(request.POST, instance=r)
if form.is_valid(): if form.is_valid():
form.save(commit=True) form.save(commit=True)
@@ -714,7 +714,7 @@ def rower_prefs_view(request, userid=0, message=""):
r.pw_ut1, r.pw_ut1,
r.pw_at, r.pw_at,
r.pw_tr, r.pw_an])/r.ftp r.pw_tr, r.pw_an])/r.ftp
if cogganzones: if cogganzones: # pragma: no cover
powerfrac = np.array([55.,75.,90.,105.,120.]) powerfrac = np.array([55.,75.,90.,105.,120.])
powerzones = ['Rest', powerzones = ['Rest',
'Active Recovery', 'Active Recovery',
+8 -8
View File
@@ -1664,7 +1664,7 @@ def course_compare_view(request, id=0):
labeldict = { labeldict = {
int(w.id): w.__str__() for w in workouts int(w.id): w.__str__() for w in workouts
} }
except: except: # pragma: no cover
labeldict = {} labeldict = {}
res = interactive_multiple_compare_chart(workoutids, xparam, yparam, res = interactive_multiple_compare_chart(workoutids, xparam, yparam,
@@ -1846,7 +1846,7 @@ def virtualevent_compare_view(request, id=0):
labeldict = { labeldict = {
int(w.id): w.__str__() for w in workouts int(w.id): w.__str__() for w in workouts
} }
except: except: # pragma: no cover
labeldict = {} labeldict = {}
res = interactive_multiple_compare_chart(workoutids, xparam, yparam, res = interactive_multiple_compare_chart(workoutids, xparam, yparam,
@@ -2048,9 +2048,9 @@ def workouts_bulk_actions(request):
w = get_workout_by_opaqueid(request, encid) w = get_workout_by_opaqueid(request, encid)
if w.user == r: if w.user == r:
workouts.append(w) workouts.append(w)
else: else: # pragma: no cover
messages.error(request,'Bulk actions are not accessible to coaches') messages.error(request,'Bulk actions are not accessible to coaches')
except KeyError: except KeyError: # pragma: no cover
pass pass
if request.method == 'POST': if request.method == 'POST':
@@ -2058,7 +2058,7 @@ def workouts_bulk_actions(request):
form = WorkoutMultipleCompareForm(request.POST) form = WorkoutMultipleCompareForm(request.POST)
if form.is_valid() and actionform.is_valid(): if form.is_valid() and actionform.is_valid():
workouts = form.cleaned_data['workouts'] workouts = form.cleaned_data['workouts']
if len(workouts) == 0: if len(workouts) == 0: # pragma: no cover
url = reverse('workouts_view') url = reverse('workouts_view')
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
action = actionform.cleaned_data['action'] action = actionform.cleaned_data['action']
@@ -2078,14 +2078,14 @@ def workouts_bulk_actions(request):
'Workout {id} exported to {destination}'.format( 'Workout {id} exported to {destination}'.format(
id=encoder.encode_hex(w.id), id=encoder.encode_hex(w.id),
destination=destination)) destination=destination))
except NoTokenError: except NoTokenError: # pragma: no cover
messages.error(request, messages.error(request,
'Export to {destination} of workout {id} failed'.format( 'Export to {destination} of workout {id} failed'.format(
id=encoder.encode_hex(w.id), id=encoder.encode_hex(w.id),
destination=destination)) destination=destination))
url = reverse('workouts_view') url = reverse('workouts_view')
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
else: else: # pragma: no cover
if len(workouts) == 0: if len(workouts) == 0:
url = reverse(workouts_view) url = reverse(workouts_view)
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@@ -3299,7 +3299,6 @@ def instroke_chart_interactive(request, id=0, analysis=0, userid=0):
rowdata = rrdata(csvfile=w.csvfilename) rowdata = rrdata(csvfile=w.csvfilename)
instrokemetrics = rowdata.get_instroke_columns() instrokemetrics = rowdata.get_instroke_columns()
form = InstrokeForm(choices=instrokemetrics) form = InstrokeForm(choices=instrokemetrics)
breadcrumbs = [ breadcrumbs = [
@@ -3513,6 +3512,7 @@ def instroke_chart_interactive(request, id=0, analysis=0, userid=0):
name=name,notes=notes, name=name,notes=notes,
) )
# change to range spm_min to spm_max # change to range spm_min to spm_max
vals, units, typ = rowdata.updateinterval_range( vals, units, typ = rowdata.updateinterval_range(
' Cadence (stokes/min)', spm_min, spm_max, ' Cadence (stokes/min)', spm_min, spm_max,
+1 -1
View File
@@ -62,7 +62,7 @@ DEBUG = True
TEMPLATES[0]['OPTIONS']['debug'] = DEBUG TEMPLATES[0]['OPTIONS']['debug'] = DEBUG
ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'dunav.ngrok.io'] ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'dunav.ngrok.io','rowsandall-2']
# INSTALLED_APPS += ['debug_toolbar',] # INSTALLED_APPS += ['debug_toolbar',]
# INSTALLED_APPS += ["sslserver"] # INSTALLED_APPS += ["sslserver"]