From eb126856a76a5344975ae19572eee30a22c7a31d Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 28 Feb 2024 09:34:14 +0100 Subject: [PATCH 01/13] list of courses api --- rowers/courses.py | 48 ++++++++++++++++++++++++++ rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4001 bytes rowers/urls.py | 1 + rowers/views/apiviews.py | 40 +++++++++++++++++++++ 4 files changed, 89 insertions(+) diff --git a/rowers/courses.py b/rowers/courses.py index 7fa3d8f9..3b21f110 100644 --- a/rowers/courses.py +++ b/rowers/courses.py @@ -22,6 +22,7 @@ import time from django.db import IntegrityError import uuid from django.conf import settings +import math import geocoder @@ -120,6 +121,50 @@ xmlns_uris_dict = {'gx': 'http://www.google.com/kml/ext/2.2', '': "http://www.opengis.net/kml/2.2"} +def get_polar_angle(point, reference_point): + """ + Calculate the polar angle of a point with respect to a reference point. + """ + try: + delta_x = point.longitude - reference_point.longitude + delta_y = point.latitude - reference_point.latitude + except AttributeError: + delta_x = point['longitude'] - reference_point.longitude + delta_y = point['latitude'] - reference_point.latitude + + + return math.atan2(delta_y, delta_x) + +class Coordinate: + def __init__(self, latitude, longitude): + self.latitude = latitude + self.longitude = longitude + +def get_coordinates_centerpoint(coordinates): + try: + centroid_latitude = sum(coord.latitude for coord in coordinates) / len(coordinates) + centroid_longitude = sum(coord.longitude for coord in coordinates) / len(coordinates) + centroid = Coordinate(centroid_latitude, centroid_longitude) + except AttributeError: + centroid_latitude = sum(coord['latitude'] for coord in coordinates) / len(coordinates) + centroid_longitude = sum(coord['longitude'] for coord in coordinates) / len(coordinates) + centroid = Coordinate(centroid_latitude, centroid_longitude) + + return centroid + +def sort_coordinates_ccw(coordinates): + """ + Sort coordinates in counterclockwise order. + """ + # Find the reference point (centroid) + centroid = get_coordinates_centerpoint(coordinates) + + # Sort coordinates based on polar angle with respect to the centroid + sorted_coords = sorted(coordinates, key=lambda coord: get_polar_angle(coord, centroid)) + + return sorted_coords + + def crewnerdcourse(doc): courses = [] for course in doc: @@ -162,6 +207,8 @@ def get_polygons(polygonpms): 'latitude': float(coordinates[1]), }) + points = sort_coordinates_ccw(points) + polygons.append({ 'name': name, 'points': points @@ -206,6 +253,7 @@ def coursetokml(course): coordinates.text = '' points = GeoPoint.objects.filter( polygon=polygon).order_by("order_in_poly") + points = sort_coordinates_ccw(points) for point in points: coordinates.text += '{lon},{lat},0 '.format( lat=point.latitude, diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 4d5ff82492bdb363963f86cbb5e817ff6e628836..064a2033aef291a5803f3378c3e205781e6aa2c7 100644 GIT binary patch literal 4001 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 GfB^t!@ Date: Wed, 28 Feb 2024 14:05:10 +0100 Subject: [PATCH 02/13] better kml export --- rowers/courses.py | 63 +++++++++++++++++++++--- rowers/interactiveplots.py | 4 +- rowers/models.py | 78 +++++++++++++++++++++++++++++- rowers/templates/list_courses.html | 1 + rowers/urls.py | 1 + rowers/utils.py | 25 ++++++++++ rowers/views/apiviews.py | 53 ++++++++++++++++++-- 7 files changed, 211 insertions(+), 14 deletions(-) diff --git a/rowers/courses.py b/rowers/courses.py index 3b21f110..2104ea8f 100644 --- a/rowers/courses.py +++ b/rowers/courses.py @@ -4,6 +4,7 @@ from rowers.models import ( Rower, Workout, GeoPoint, GeoPolygon, GeoCourse, course_length, course_coord_center, course_coord_maxmin, + course_coord_crewnerd_navigation, polygon_coord_center, PlannedSession, polygon_to_path, coordinate_in_path ) @@ -216,8 +217,17 @@ def get_polygons(polygonpms): return polygons +def crewnerdify(polygons): + polygons[0].name = "Start" + polygons[len(polygons)-1].name = "Finish" -def coursetokml(course): + if len(polygons) > 2: + for i in range(1,len(polygons)-1): + polygons[i].name = 'WP{n}'.format(n=polygons[i].order_in_course) + + return polygons + +def coursetokml(course,cn=False): top = Element('kml') for prefix, uri in xmlns_uris_dict.items(): if prefix != '': @@ -227,23 +237,60 @@ def coursetokml(course): document = SubElement(top, 'Document') name = SubElement(document, 'name') - name.text = 'Courses.kml' - folder = SubElement(document, 'Folder') - foldername = SubElement(folder, 'name') - foldername.text = 'Courses' - folder2 = SubElement(folder, 'Folder') + name.text = 'courses' + opendoc = SubElement(document,'open') + opendoc.text = '1' + style = SubElement(document,'Style', id="default") + linestyle = SubElement(style, 'LineStyle') + linestylecolor = SubElement(linestyle, 'color') + linestylecolor.text = "ff00ffff" + polystyle = SubElement(style, 'PolyStyle') + polystylecolor = SubElement(polystyle, 'color') + polystylecolor.text = "ff7fffff" + + stylemap = SubElement(document, 'StyleMap', id="default0") + 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" + + stylehl = SubElement(document, 'Style', id="hl") + iconstyle = SubElement(stylehl, 'IconStyle') + scale = SubElement(iconstyle, 'scale') + scale.text = "1.2" + linestyle = SubElement(stylehl, 'LineStyle') + linestylecolor = SubElement(linestyle, 'color') + linestylecolor.text = "ff00ffff" + polystyle = SubElement(stylehl, 'PolyStyle') + polystylecolor = SubElement(polystyle, 'color') + polystylecolor.text = "ff7fffff" + + folder2 = SubElement(document, 'Folder') coursename = SubElement(folder2, 'name') coursename.text = course.name - open = SubElement(folder2, 'open') - open.text = '1' + openst = SubElement(folder2, 'open') + openst.text = '1' + coursedescription = SubElement(folder2, 'description') + coursedescription.text = "Rowsandall.com\n" + course.notes polygons = GeoPolygon.objects.filter( course=course).order_by("order_in_course") + if cn: + polygons = crewnerdify(polygons) + for polygon in polygons: placemark = SubElement(folder2, 'Placemark') polygonname = SubElement(placemark, 'name') polygonname.text = polygon.name + polygonstyle = SubElement(placemark, 'styleUrl') + polygonstyle.text = "#default0" p = SubElement(placemark, 'Polygon') tessellate = SubElement(p, 'tessellate') tessellate.text = '1' diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 1264b7af..5bc84a34 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -33,7 +33,7 @@ import datetime from rowers import mytypes from rowers.courses import ( course_coord_center, course_coord_maxmin, - polygon_coord_center + polygon_coord_center, course_coord_crewnerd_navigation, ) from django.conf import settings from collections import OrderedDict @@ -2165,6 +2165,8 @@ def interactive_histoall(theworkouts, histoparam, includereststrokes, def course_map(course): latmean, lonmean, coordinates = course_coord_center(course) + if course.with_cn_nav_waypoints: + latmean, lonmean, coordinates = course_coord_crewnerd_navigation(course) lat_min, lat_max, long_min, long_max = course_coord_maxmin(course) coordinates = course_spline(coordinates) diff --git a/rowers/models.py b/rowers/models.py index 6d0adf97..c1760c78 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -8,7 +8,7 @@ from rowers.utils import ( steps_read_fit, steps_write_fit, ps_dict_order, uniqify ) from rowers.metrics import axlabels -from rowers.utils import geo_distance +from rowers.utils import geo_distance, move_one_meter from rowers.formfields import * from rowers.database import * import uuid @@ -594,7 +594,65 @@ def course_spline(coordinates): return newcoordinates +def polygon_nearest_point(polygon, latitude, longitude,debug=False): + points = GeoPoint.objects.filter(polygon=polygon) + points = sorted(points, key = lambda p: geo_distance(p.latitude, p.longitude, latitude, longitude)) + if debug: + for p in points: + print(p,p.latitude, p.longitude, latitude, longitude, geo_distance(p.latitude, p.longitude, latitude, longitude)) + + return points[0].latitude, points[0].longitude + +def polygon_exit_point(polygon, lat1, lon1, lat2, lon2): + dist, bearing = geo_distance(lat1, lon1, lat2, lon2) + dirveclat = (lat2-lat1)/dist + dirveclon = (lon2-lon1)/dist + + newlat, newlon = move_one_meter(lat2, lon2, bearing) + path = polygon_to_path(polygon) + while path.contains_points([(newlat, newlon)])[0]: + newlat, newlon = move_one_meter(newlat, newlon, bearing) + + return newlat, newlon + +def course_coord_crewnerd_navigation(course): + polygons = GeoPolygon.objects.filter( + course=course).order_by("order_in_course") + + latitudes = [] + longitudes = [] + + latitude, longitude = polygon_coord_center(polygons[0]) + + latitudes.append(latitude) + longitudes.append(longitude) + + debug = True + + for p in polygons[1:]: + oldlat = latitude + oldlon = longitude + latitude, longitude = polygon_nearest_point(p,latitude,longitude, debug=debug) + debug = False + latitudes.append(latitude) + longitudes.append(longitude) + latitude, longitude = polygon_exit_point(p, oldlat, oldlon, latitude, longitude) + latitudes.append(latitude) + longitudes.append(longitude) + + + latitude = pd.Series(latitudes).median() + longitude = pd.Series(longitudes).median() + + coordinates = pd.DataFrame({ + 'latitude': latitudes, + 'longitude': longitudes, + }) + + return latitude, longitude, coordinates + + def course_coord_center(course): polygons = GeoPolygon.objects.filter( @@ -1590,6 +1648,24 @@ class GeoCourse(models.Model): def coord(self): return course_coord_center(self) + @property + def with_cn_nav_waypoints(self): + polygons = GeoPolygon.objects.filter(course=self).order_by("order_in_course") + + if polygons[0].name != "Start": + return False + if polygons[len(polygons)-1].name != "Finish": + return False + for i in range(1,len(polygons)-1): + if polygons[i].name[0:2].lower() != 'wp': + return False + try: + getal = float(polygons[i].name[2:]) + except ValueError: + return False + + return True + class GeoCourseEditForm(ModelForm): class Meta: diff --git a/rowers/templates/list_courses.html b/rowers/templates/list_courses.html index bc66697a..3f37a654 100644 --- a/rowers/templates/list_courses.html +++ b/rowers/templates/list_courses.html @@ -106,6 +106,7 @@

CrewNerd has published a nice video tutorial of the process. Click here to see the video. The part we're interested in starts at 2:05. + There is also a written tutorial by CrewNerd.

diff --git a/rowers/urls.py b/rowers/urls.py index e179b99b..0ef84538 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -252,6 +252,7 @@ urlpatterns = [ re_path(r'^api/TCX/workouts/$', views.strokedata_tcx, name='strokedata_tcx'), re_path(r'^api/courses/$', views.course_list, name='course_list'), + re_path(r'^api/courses/(?P\d+)/$', views.get_crewnerd_kml, name='get_crewnerd_kml'), re_path(r'^500v/$', views.error500_view, name='error500_view'), re_path(r'^500q/$', views.servererror_view, name='servererror_view'), path('502/', TemplateView.as_view(template_name='502.html'), name='502'), diff --git a/rowers/utils.py b/rowers/utils.py index 92357775..bf71456b 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -312,6 +312,31 @@ def geo_distance(lat1, lon1, lat2, lon2): return [distance, bearing] +def move_one_meter(latitude, longitude, bearing): + # Earth radius in meters + earth_radius = 6371000 + + # Convert latitude and longitude from degrees to radians + lat_rad = math.radians(latitude) + lon_rad = math.radians(longitude) + + # Convert bearing from degrees to radians + bearing_rad = math.radians(bearing) + + # Calculate the new latitude + new_lat = math.asin(math.sin(lat_rad) * math.cos(1/earth_radius) + + math.cos(lat_rad) * math.sin(1/earth_radius) * math.cos(bearing_rad)) + + # Calculate the new longitude + new_lon = lon_rad + math.atan2(math.sin(bearing_rad) * math.sin(1/earth_radius) * math.cos(lat_rad), + math.cos(1/earth_radius) - math.sin(lat_rad) * math.sin(new_lat)) + + # Convert new latitude and longitude from radians to degrees + new_lat = math.degrees(new_lat) + new_lon = math.degrees(new_lon) + + return new_lat, new_lon + def isbreakthrough(delta, cpvalues, p0, p1, p2, p3, ratio): pwr = abs(p0)/(1+(delta/abs(p2))) diff --git a/rowers/views/apiviews.py b/rowers/views/apiviews.py index bdbb2782..8827de99 100644 --- a/rowers/views/apiviews.py +++ b/rowers/views/apiviews.py @@ -1,6 +1,7 @@ from rowers.views.statements import * from rowers.tasks import handle_calctrimp from rowers.opaque import encoder +from rowers.courses import coursetokml from xml.etree import ElementTree as ET import arrow @@ -11,6 +12,8 @@ from rowers.dataroutines import get_workouttype_from_tcx, get_startdate_time_zon from rest_framework.decorators import parser_classes from rest_framework.parsers import BaseParser +from rowers.utils import geo_distance + from datetime import datetime as dt import rowingdata.tcxtools as tcxtools @@ -252,9 +255,38 @@ Optional, not for CN def course_list(request): if request.method != 'GET': dologging('apilog.log','{m} request to KML endpoint'.format(m=request.method)) - return HttpResponseNotAllower("Method not supported") + 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): + newlist.append(c) + courses = newlist + except ValueError: + pass + - courses = GeoCourse.objects.all() courselist = [] for c in courses: d = { @@ -269,10 +301,23 @@ def course_list(request): response_dict = {'courses': courselist} - print(response_dict) - return JsonResponse(response_dict, content_type='application/json; charset=utf8') +@api_view(["GET"]) +def get_crewnerd_kml(request,id=0): + if request.method != 'GET': + 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: + raise Http404("This course does not exist") + + kml = coursetokml(c, cn=True) + + return HttpResponse(kml) + # Stroke data views @csrf_exempt From 25bfcb17fddbaf41165a22dd09aba49369a7ef7f Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 28 Feb 2024 15:36:12 +0100 Subject: [PATCH 03/13] exporting courses.kml --- rowers/courses.py | 134 +++++++++++++++++++---------- rowers/templates/list_courses.html | 54 ++++++------ rowers/urls.py | 1 + rowers/views/apiviews.py | 24 +++++- rowers/views/racesviews.py | 21 ++++- rowers/views/statements.py | 2 +- 6 files changed, 165 insertions(+), 71 deletions(-) diff --git a/rowers/courses.py b/rowers/courses.py index 2104ea8f..faf675d2 100644 --- a/rowers/courses.py +++ b/rowers/courses.py @@ -227,57 +227,22 @@ def crewnerdify(polygons): return polygons -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 +def removewhitespace(s): + regels = s.split('\n') + regels = [regel for regel in regels if regel.strip()] + return "\n".join(regels) - document = SubElement(top, 'Document') - name = SubElement(document, 'name') - name.text = 'courses' - opendoc = SubElement(document,'open') - opendoc.text = '1' - style = SubElement(document,'Style', id="default") - linestyle = SubElement(style, 'LineStyle') - linestylecolor = SubElement(linestyle, 'color') - linestylecolor.text = "ff00ffff" - polystyle = SubElement(style, 'PolyStyle') - polystylecolor = SubElement(polystyle, 'color') - polystylecolor.text = "ff7fffff" - - stylemap = SubElement(document, 'StyleMap', id="default0") - 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" - - stylehl = SubElement(document, 'Style', id="hl") - iconstyle = SubElement(stylehl, 'IconStyle') - scale = SubElement(iconstyle, 'scale') - scale.text = "1.2" - linestyle = SubElement(stylehl, 'LineStyle') - linestylecolor = SubElement(linestyle, 'color') - linestylecolor.text = "ff00ffff" - polystyle = SubElement(stylehl, 'PolyStyle') - polystylecolor = SubElement(polystyle, 'color') - polystylecolor.text = "ff7fffff" - +def getcoursefolder(course, document, cn=False): folder2 = SubElement(document, 'Folder') coursename = SubElement(folder2, 'name') coursename.text = course.name openst = SubElement(folder2, 'open') openst.text = '1' coursedescription = SubElement(folder2, 'description') - coursedescription.text = "Rowsandall.com\n" + course.notes + if len(removewhitespace(course.notes)): + coursedescription.text = "Rowsandall.com\n" + removewhitespace(course.notes) + else: + coursedescription.text = "Rowsandall.com " + course.name polygons = GeoPolygon.objects.filter( course=course).order_by("order_in_course") @@ -310,6 +275,87 @@ def coursetokml(course,cn=False): lat=points[0].latitude, lon=points[0].longitude, ) + return folder2 + +def getstyle(document, id): + style = SubElement(document,'Style', id=id) + iconstyle = SubElement(style, 'IconStyle') + scale = SubElement(iconstyle, 'scale') + scale.text = "1.2" + linestyle = SubElement(style, 'LineStyle') + linestylecolor = SubElement(linestyle, 'color') + linestylecolor.text = "ff00ffff" + polystyle = SubElement(style, 'PolyStyle') + polystylecolor = SubElement(polystyle, 'color') + polystylecolor.text = "ff7fffff" + + return style + +def getstylemap(document, id): + stylemap = SubElement(document, 'StyleMap', id=id) + pair1 = SubElement(stylemap, 'Pair') + pair1key = SubElement(pair1, 'key') + pair1key.text = "normal" + pair1styleurl = SubElement(pair1, 'styleUrl') + pair1styleurl.text = "#default" + pair2 = SubElement(stylemap, 'Pair') + pair2key = SubElement(pair2, 'key') + pair2key.text = "highlight" + pair2styleurl = SubElement(pair2, 'styleUrl') + pair2styleurl.text = "#hl" + + return stylemap + +def coursestokml(courseids, cn=False): + courses = GeoCourse.objects.filter(id__in=courseids) + top = Element('kml') + for prefix, uri in xmlns_uris_dict.items(): + if prefix != '': + top.attrib['xmlns:' + prefix] = uri + else: + top.attrib['xmlns'] = uri + + document = SubElement(top, 'Document') + name = SubElement(document, 'name') + name.text = 'courses' + + opendoc = SubElement(document,'open') + opendoc.text = '1' + + style = getstyle(document, "default") + + stylemap = getstylemap(document, 'default0') + + stylehl = getstyle(document, "hl") + + for course in courses: + coursefolder = getcoursefolder(course, document, cn=cn) + + return prettify(top) + +def coursetokml(course,cn=False): + top = Element('kml') + for prefix, uri in xmlns_uris_dict.items(): + if prefix != '': + top.attrib['xmlns:' + prefix] = uri + else: + top.attrib['xmlns'] = uri + + document = SubElement(top, 'Document') + name = SubElement(document, 'name') + name.text = 'courses' + + opendoc = SubElement(document,'open') + opendoc.text = '1' + + style = getstyle(document, "default") + + stylemap = getstylemap(document, 'default0') + + stylehl = getstyle(document, "hl") + + coursefolder = getcoursefolder(course, document, cn=cn) + return prettify(top) diff --git a/rowers/templates/list_courses.html b/rowers/templates/list_courses.html index 3f37a654..a70edd17 100644 --- a/rowers/templates/list_courses.html +++ b/rowers/templates/list_courses.html @@ -23,30 +23,36 @@

  • {% if courses %}

    - - - - - - - - - - {% for course in courses %} - - - - - - - - {% endfor %} - -
    Country Name Distance
    {{ course.country }} - {{ course.name }} - - {{ course.distance }} m -
    +

    + {% csrf_token %} + + + + + + + + + + + + {% for course in courses %} + + + + + + + + + {% endfor %} + +
    Download Country Name Distance
    {{ course.country }} + {{ course.name }} + + {{ course.distance }} m +
    +

    {% else %}

    No courses found

    diff --git a/rowers/urls.py b/rowers/urls.py index 0ef84538..30326a73 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -253,6 +253,7 @@ urlpatterns = [ name='strokedata_tcx'), re_path(r'^api/courses/$', views.course_list, name='course_list'), re_path(r'^api/courses/(?P\d+)/$', views.get_crewnerd_kml, name='get_crewnerd_kml'), + re_path(r'^api/courses/kml/$', views.get_crewnerd_multiple, name='get_crewnerd_multiple'), re_path(r'^500v/$', views.error500_view, name='error500_view'), re_path(r'^500q/$', views.servererror_view, name='servererror_view'), path('502/', TemplateView.as_view(template_name='502.html'), name='502'), diff --git a/rowers/views/apiviews.py b/rowers/views/apiviews.py index 8827de99..1604c497 100644 --- a/rowers/views/apiviews.py +++ b/rowers/views/apiviews.py @@ -1,7 +1,7 @@ from rowers.views.statements import * from rowers.tasks import handle_calctrimp from rowers.opaque import encoder -from rowers.courses import coursetokml +from rowers.courses import coursetokml, coursestokml from xml.etree import ElementTree as ET import arrow @@ -252,6 +252,7 @@ Optional, not for CN - Update one or more new courses from KML """ @api_view(["GET"]) +@permission_classes([AllowAny]) def course_list(request): if request.method != 'GET': dologging('apilog.log','{m} request to KML endpoint'.format(m=request.method)) @@ -304,6 +305,7 @@ def course_list(request): 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': dologging('apilog.log','{m} request to CrewNerd KML endpoint'.format(m=request.method)) @@ -318,6 +320,26 @@ def get_crewnerd_kml(request,id=0): return HttpResponse(kml) +#@csrf_exempt +@api_view(["GET"]) +@permission_classes([AllowAny]) +def get_crewnerd_multiple(request): + if request.method != 'GET': + 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()) + ids = [int(id) for id in tdict['id']] + 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 diff --git a/rowers/views/racesviews.py b/rowers/views/racesviews.py index e3526de1..ac7b6852 100644 --- a/rowers/views/racesviews.py +++ b/rowers/views/racesviews.py @@ -7,7 +7,7 @@ from django.contrib.gis.geoip2 import GeoIP2 from django import forms from rowers.plannedsessions import timefield_to_seconds_duration -from rowers.courses import getnearestraces, getnearestcourses +from rowers.courses import getnearestraces, getnearestcourses,coursetokml, coursestokml # List Courses @@ -16,6 +16,25 @@ def courses_view(request): r = getrower(request.user) g = GeoIP2() + if request.method == 'POST': + try: + tdict = dict(request.POST.lists()) + ids = tdict['courseid'] + courseids = [int(id) for id in ids] + + kmlstring = coursestokml(courseids) + kmlfilename = 'courses.kml' + response = HttpResponse(kmlstring) + response['Content-Disposition'] = 'attachment; filename="{filename}"'.format( + filename=kmlfilename) + response['Content-Type'] = 'application/octet-stream' + + return response + + except KeyError: + pass + + ip = request.META.get('HTTP_X_REAL_IP', '1.1.1.1') try: lat_lon = g.lat_lon(ip) diff --git a/rowers/views/statements.py b/rowers/views/statements.py index 4c7706e3..80006ded 100644 --- a/rowers/views/statements.py +++ b/rowers/views/statements.py @@ -22,7 +22,7 @@ from scipy.special import lambertw from io import BytesIO import rowers.plots as plots from rowers.permissions import IsOwnerOrNot, IsCompetitorOrNot -from rest_framework.permissions import IsAuthenticated +from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework.decorators import api_view, renderer_classes, permission_classes from rest_framework.response import Response from rq.job import Job From d07b20eb29d88925310af79acc30efcc41ae8a90 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 28 Feb 2024 15:57:44 +0100 Subject: [PATCH 04/13] small impro --- rowers/courses.py | 2 +- rowers/tests/testdata/testdata.tcx.gz | Bin 4001 -> 3999 bytes rowers/views/apiviews.py | 8 ++++---- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rowers/courses.py b/rowers/courses.py index faf675d2..1c2afa85 100644 --- a/rowers/courses.py +++ b/rowers/courses.py @@ -233,7 +233,7 @@ def removewhitespace(s): return "\n".join(regels) def getcoursefolder(course, document, cn=False): - folder2 = SubElement(document, 'Folder') + folder2 = SubElement(document, 'Folder', id="{id}".format(id=course.id)) coursename = SubElement(folder2, 'name') coursename.text = course.name openst = SubElement(folder2, 'open') diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 064a2033aef291a5803f3378c3e205781e6aa2c7..6ffeb4f2a9000733342e64f1d0ef10d54c7072da 100644 GIT binary patch literal 3999 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 F0RRN_JzW3* literal 4001 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 Date: Thu, 29 Feb 2024 10:43:56 +0100 Subject: [PATCH 05/13] fix --- rowers/models.py | 6 +++--- rowers/tests/testdata/testdata.tcx.gz | Bin 3999 -> 3999 bytes rowers/views/analysisviews.py | 5 +++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/rowers/models.py b/rowers/models.py index c1760c78..a05e0be3 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -598,9 +598,9 @@ 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)) + #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 diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 6ffeb4f2a9000733342e64f1d0ef10d54c7072da..23bd61cc089d063670e638aa199f6f6e33b8fc30 100644 GIT binary patch delta 3913 zcmV-P54P~1ADNE{<_TAge_JHVP=4w^fljBY&UXou2Hy zSzc^bXX|@+(ZJokpAPRFJiq8x>(%=B!E)U%FCKP%_oCY@_tMVm&Aq#?`u==>v3UFT ztvT*4PFHJlbauK(Uv7?GEl<16;`+lEZ|Lss6#Lz~Z#c{Sqki>f)xZDcUB6uC8@zIY zcbnB`XL!rz?BaMq0J!+$$ra;g+f)3oT5tMpeY8wRZC3lc^FBV2@D~BllL!GZe>*?B z=ad-D+`ufq!LjrU^fc!x49C_d3J~;oJu(oP@uf83q#J?p!R z{`u;3dGGFD9ea79bm;T5zB|c>JzE}~tzT{qh5fB#^LuyldU=R8U1ay?rz{@{Pd6a$nj5(05?8){ zwBEj|{rki1Td_=JFSlHMkS~|%0Ur|P$GH3Z3dM`n@o~DfW&i5z<>8ZVfBm{UNfbZZ zdUfya=)a%c{dUXw*=pTi-JHKj@4z>_i`(5kKk|!LLW~`LO|}QzlE768%q55%&ZU!370(N_ta4?&0R#sV;f9QW1A&h*34l zry+t1a*wm&?rcTeW0H3(Uc)_>ly`>NaQ78)x3nGSor1d!h6-}WTX4s!xCf|+yCm*O zs5IKVH*O-_>yr1t6>;|_cyJnh-no)y9MgQb`+B$w7$4AT`1DXP!UN8S-CQtTueW}TZBp)7g61$nBAJXJHr(>^3q zPIRUmqLlRAu$_<0vs6UhnwXS&mCTcvP>{ULhFG?U zO1^?vGD>G@Mx#AeL_V5;;H@g{u92Ssc_!l`Miu#-oLi9me~kS0HI4Qxh8=j#JWsYP zc{?NW6-8_~7y$61AumueI&W{|x94Wn4UtdsYaOc6ut;J_AKt9USLDO9Y#7mMqO|0T zX4p6KBawFotde|y$;fzdzw6!Kz#A*f_tsA$BdkspV=Hz7$pYREfoGJiwzTn~8!BLRY{ zNrva5GVhyiKMHv^Y*AG6eBh!Qo^LWg3VCNRqNrp(f7+tn+a~klkl*Iss#>7gvK5uL zB9G07H$*6-6-U}p@>f7y|wI$=G%uh z+K)k=VcQZ*quX2Sit6^E#pg#MPsTAsRTDfZOMXrjm98T4VyrN$=6P4NmUdR;r6Tgd zB<`vVe@t7pqEevA{21gdnxt_$QnfrS+Ur(kR7EYeBJym!(?w}*j3sk&=0v`p8XL~J zx6UJKMteb+jLbKCewchT6d(o_`Pg*(=|^mYa<>#!lvWs;Za;mQ7i&V&?Nu^w%Vu52 zR!chyd2fVu5gU=RsnkRe}7Z*wj%Omg4b17iO0#r=bOwAlNVzN zRn=7z%GTAj9+=~h4~7s`of9OMt+DY$f=dpjrc ze-+vDWDPlF)jTgWncQ1$_49+|y+Mnl8tokwwdZF<-cd#5bLRwNO@Djmx#&DUC-N2P z_DNggh^ke~T;LX^odJ0<0HW%0?jpA+?es@`_C`e43DH9%e^c@m>Gl>3dr^6EJlc{P z-mUWRnua$_o^$t8mG{;sOE9(!%*m4%f8(t~O{~r*-JoEsPH;x{d`0nj8;nIpO-_za zZ;PD>`PzJVp&s&rxt3P7riGWH_49L*`IAKYYg`{unJATRrX#w&ezJ{Dbtd{#0q6_K~bf~aIZe?ZZ- zsAnbft|Ic@AP{J*x`1IaGT+D#laI!w!Co`k2MR^)`8kdDp&s%`##)S;tn0wB$@~q; z$BM{{fuJ1iZPAqqXXVMMXtYOfyojn>N^nJ2C!Le`)>A#?336{N)_8J4*)=MK8u?Mk zvq94Jzzm`2s%3K`U(-BKVuE1Qe`H{Wh($9qXh!qAuZVmwl4M>}X%T^%%uhX5C;N=X zGO02%B61@?^@vR(Zw;k?D)Llx9h+Q#+A=dnAn$V?o~j>Sd(a~klmBJ`Z*dIHs4j}RtdJ1Avg5x={p8V$ zabAm_%C<_Ilhi{+f9Qh|Ayorxq+}8LhJM;E9iKa6S|_41?kFh;GLiN<*p{UqL~jjy zUHhE8Fves$CpEi%7`->v8j1upvNS@%7QzjfRpKni!Cj45Iq}ox~^GB zvFO63vl{TJBJ|FHC0#}?15aj+n>N=EqYoyEvigS67Io>gf4P1X^n@l7fhs|ZX*z3~ zH6Q*kdTRnJt8bXyWSXY6ES+KW-gqml((u`qUFU{c&D|L2bB3j$uD+h7Y&#WdYThX5 z*$80NNxch`8St?sXbqxw#-eU5Lu4O|Hcq2v*N=ie8j5Ij8h$7nzTxK08w0(i+&@d_ z&EpuGnm7Fcf8UA$Q5IfVgvrF~YfHwiC}?Yw#)cH53O!Mq>!*)iz(nQ3#b}v|M(&yq ze;o8|qC?W5w>+8U&6*>382z?oqF1STR%~bm6`}WrtTJdT3`M9P}Yez1I!+o+l&qZNMKy&uD_M zE?zIee-_p7ThMkK^wu~;RCU#mXqz{0`i@-o#xq$}(AI&QOsYmx^9Io;JIy-OW$8F{ zZNQ&;ZeBvqMkqUJH0WtMB^a%Z(lB~wEaXH>4SEi3z@NS&*T#gNNL6^{Y@AG`ZOhUb zL@#7m>%(^r+FU;a^kRs#YMzIdkvsLmtB_kJe`4CELmzN54ON<&H-J9omWhBEb#{HE zmXF$$)Ll9;w@d`>nzr8i$;|cCg0_R`bCETJDtzOEZ+86*&_^T5y3Y0> z>?gCjWt-~<(dU({8A;{Mi`EydzM{hac^Eizg@{%8M%uQTW9PUptmN?^r{6=2%z=Iy(xM>7Wxlc zKW;c!JnqgfKRi9_mz%@yPWm5ze>qv6F4z6u_uEb1?{)niyz_th7Zk9=CBS#b{f`HW z-2r!YXZrXX^XJP?tKIhp4=xhNvx^V=KJC)mb8+@|)2&~2Cnwvdl|HW4$1l6SOXux& zZ>9~XYulrYL7gt`B`{(B;tD~+@KmGE>xIFh)CrjV`{Cair^77~Yf1cjQbm*0b z^I^-A?my|0*;#P)^Vhqr)@iesPy1~*Tt3<_&VTSc-Spva%adidStbto(}zcQ<-QIc zckAOv4-bENn%Qjs+-?6gy~R&opT1b8I|KLuVE6yQ;>s7hC%=76hj+H9SL^f~9wd~@ Xi-+lHyhxWmyz@V2ZSMla1AqYli-_O+ delta 3913 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 Date: Thu, 29 Feb 2024 18:08:46 +0100 Subject: [PATCH 06/13] better tests --- rowers/tasks.py | 44 +- rowers/tests/test_aworkouts.py | 1607 ++++++++++++++++---------------- rowers/tests/test_urls.py | 22 + rowers/views/apiviews.py | 50 +- rowers/views/workoutviews.py | 6 +- 5 files changed, 894 insertions(+), 835 deletions(-) diff --git a/rowers/tasks.py b/rowers/tasks.py index dac27df8..388af488 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -94,7 +94,7 @@ SITE_URL_DEV = CFG['site_url'] PROGRESS_CACHE_SECRET = CFG['progress_cache_secret'] try: SETTINGS_NAME = CFG['settings_name'] -except KeyError: +except KeyError: # pragma: no cover SETTINGS_NAME = 'rowsandall_ap.settings' try: @@ -321,7 +321,7 @@ def summaryfromsplitdata(splitdata, data, filename, sep='|', workouttype='rower' return sums, sa, results @app.task -def handle_post_workout_api(uploadoptions, debug=False, **kwargs): +def handle_post_workout_api(uploadoptions, debug=False, **kwargs): # pragma: no cover session = requests.session() newHeaders = {'Content-type': 'application/json', 'Accept': 'text/plain'} session.headers.update(newHeaders) @@ -335,14 +335,14 @@ def handle_post_workout_api(uploadoptions, debug=False, **kwargs): @app.task -def handle_remove_workouts_team(ws, t, debug=False, **kwargs): +def handle_remove_workouts_team(ws, t, debug=False, **kwargs): # pragma: no cover for w in ws: w.team.remove(t) return 1 @app.task -def handle_add_workouts_team(ws, t, debug=False, **kwargs): +def handle_add_workouts_team(ws, t, debug=False, **kwargs): # pragma: no cover for w in ws: w.team.add(t) @@ -350,7 +350,7 @@ def handle_add_workouts_team(ws, t, debug=False, **kwargs): return 1 def uploadactivity(access_token, filename, description='', - name='Rowsandall.com workout'): + name='Rowsandall.com workout'): # pragma: no cover data_gz = BytesIO() try: @@ -393,7 +393,7 @@ def uploadactivity(access_token, filename, description='', @app.task -def check_tp_workout_id(workout, location, attempts=5, debug=False, **kwargs): +def check_tp_workout_id(workout, location, attempts=5, debug=False, **kwargs): # pragma: no cover authorizationstring = str('Bearer ' + workout.user.tptoken) headers = {'Authorization': authorizationstring, 'user-agent': 'sanderroosendaal', @@ -411,7 +411,7 @@ def check_tp_workout_id(workout, location, attempts=5, debug=False, **kwargs): return 1 @app.task -def handle_workout_tp_upload(w, thetoken, tcxfilename, debug=False, **kwargs): +def handle_workout_tp_upload(w, thetoken, tcxfilename, debug=False, **kwargs): # pragma: no cover tpid = 0 r = w.user if not tcxfilename: @@ -443,7 +443,7 @@ def handle_workout_tp_upload(w, thetoken, tcxfilename, debug=False, **kwargs): return tpid @app.task -def instroke_static(w, metric, debug=False, **kwargs): +def instroke_static(w, metric, debug=False, **kwargs): # pragma: no cover f1 = w.csvfilename[6:-4] rowdata = rdata(csvfile=w.csvfilename) @@ -512,7 +512,7 @@ def handle_c2_sync(workoutid, url, headers, data, debug=False, **kwargs): try: workout = Workout.objects.get(id=workoutid) - except Workout.DoesNotExist: + except Workout.DoesNotExist: # pragma: no cover dologging('c2_log.log','failed for c2id {c2id}'.format(c2id=c2id)) return 0 @@ -531,7 +531,7 @@ def handle_c2_sync(workoutid, url, headers, data, debug=False, **kwargs): return 1 -def splitstdata(lijst): +def splitstdata(lijst): # pragma: no cover t = [] latlong = [] while len(lijst) >= 2: @@ -543,7 +543,7 @@ def splitstdata(lijst): @app.task def handle_sporttracks_workout_from_data(user, importid, source, - workoutsource, debug=False, **kwargs): + workoutsource, debug=False, **kwargs): # pragma: no cover r = user.rower authorizationstring = str('Bearer ' + r.sporttrackstoken) @@ -812,7 +812,7 @@ def handle_strava_sync(stravatoken, if not failed: try: workout = Workout.objects.get(id=workoutid) - except Workout.DoesNotExist: + except Workout.DoesNotExist: # pragma: no cover return 0 workout.uploadedtostrava = res.id @@ -1562,7 +1562,7 @@ def handle_calctrimp(id, try: workout = Workout.objects.get(id=id) - except Workout.DoesNotExist: + except Workout.DoesNotExist: # pragma: no cover dologging('metrics.log','Could not find workout {id}'.format(id=id)) return 0 @@ -2037,7 +2037,7 @@ def handle_sendemail_expired(useremail, userfirstname, userlastname, expireddate return 1 @app.task -def handle_sendemail_newftp(rower,power,mode, **kwargs): +def handle_sendemail_newftp(rower,power,mode, **kwargs): # pragma: no cover subject = "You may want to update your FTP on rowsandall.com" from_email = 'Rowsandall ' @@ -2069,7 +2069,7 @@ def handle_sendemail_breakthrough(workoutid, useremail, lastname = '' - if surname: + if surname: # pragma: no cover lastname = userlastname tablevalues = [ @@ -2122,7 +2122,7 @@ def handle_sendemail_hard(workoutid, useremail, ] lastname = '' - if surname: + if surname: # pragma: no cover lastname = userlastname # send email with attachment @@ -3414,7 +3414,7 @@ def handle_nk_async_workout(alldata, userid, nktoken, nkid, delaysec, defaulttim jsonData = response.json() try: strokeData = jsonData[str(nkid)] - except KeyError: + except KeyError: # pragma: no cover dologging('nklog.log','Could not find strokeData') return 0 @@ -3424,7 +3424,7 @@ def handle_nk_async_workout(alldata, userid, nktoken, nkid, delaysec, defaulttim if oarlockSessions: oarlocksession = oarlockSessions[0] seatNumber = oarlocksession['seatNumber'] - except KeyError: + except KeyError: # pragma: no cover pass df = strokeDataToDf(strokeData, seatIndex=seatNumber) @@ -3439,14 +3439,14 @@ def handle_nk_async_workout(alldata, userid, nktoken, nkid, delaysec, defaulttim workoutid, error = add_workout_from_data(userid, nkid, data, df) # dologging('nklog.log','NK Workout ID {id}'.format(id=workoutid)) - if workoutid == 0: + if workoutid == 0: # pragma: no cover return 0 try: workout = Workout.objects.get(id=workoutid) newnkid = workout.uploadedtonk sr = create_or_update_syncrecord(workout.user, workout, nkid=newnkid) - except Workout.DoesNotExist: + except Workout.DoesNotExist: # pragma: no cover pass return workoutid @@ -3775,7 +3775,7 @@ def handle_c2_async_workout(alldata, userid, c2token, c2id, delaysec, return workoutid @app.task -def fetch_rojabo_session(id,alldata,userid,rowerid,debug=False, **kwargs): +def fetch_rojabo_session(id,alldata,userid,rowerid,debug=False, **kwargs): # pragma: no cover try: item = alldata[id] except KeyError: @@ -3880,7 +3880,7 @@ def fetch_strava_workout(stravatoken, oauth_data, stravaid, csvfilename, userid, wsize = round(5./dt) velo2 = ewmovingaverage(velo, wsize) - except ValueError: + except ValueError: # pragma: no cover velo2 = velo if coords is not None: diff --git a/rowers/tests/test_aworkouts.py b/rowers/tests/test_aworkouts.py index 3f5f8f24..06da2171 100644 --- a/rowers/tests/test_aworkouts.py +++ b/rowers/tests/test_aworkouts.py @@ -32,858 +32,889 @@ def create_image(storage, filename, size=(100, 100), image_mode='RGB', image_for return storage.save(filename, image_file) class ListWorkoutTest(TestCase): - def setUp(self): - self.u = UserFactory() + def setUp(self): + self.u = UserFactory() + + self.r = Rower.objects.create(user=self.u, + birthdate=faker.profile()['birthdate'], + gdproptin=True, ftpset=True,surveydone=True, + gdproptindate=timezone.now(), + rowerplan='coach') - self.r = Rower.objects.create(user=self.u, - birthdate=faker.profile()['birthdate'], - gdproptin=True, ftpset=True,surveydone=True, - gdproptindate=timezone.now(), - rowerplan='coach') + self.c = Client() + self.user_workouts = WorkoutFactory.create_batch(len(workouttypes), user=self.r) + i = 0 + for workouttype in workouttypes: + self.user_workouts[i].workouttype = workouttype[0] + self.user_workouts[i].save() + i = i+1 - self.c = Client() - self.user_workouts = WorkoutFactory.create_batch(len(workouttypes), user=self.r) - i = 0 - for workouttype in workouttypes: - self.user_workouts[i].workouttype = workouttype[0] - self.user_workouts[i].save() - i = i+1 + self.factory = RequestFactory() + self.password = faker.word() + self.u.set_password(self.password) + self.u.save() - self.factory = RequestFactory() - self.password = faker.word() - self.u.set_password(self.password) - self.u.save() + def tearDown(self): + for workout in self.user_workouts: + try: + os.remove(workout.csvfilename) + except (IOError, FileNotFoundError,OSError): + pass - def tearDown(self): - for workout in self.user_workouts: - try: - os.remove(workout.csvfilename) - except (IOError, FileNotFoundError,OSError): - pass + @patch('rowers.dataprep.create_engine') + @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.myqueue') + def test_list_workouts(self, mocked_sqlalchemy, + mocked_getsmallrowdata_db, + mocked_myqueue): - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - @patch('rowers.dataprep.myqueue') - def test_list_workouts(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db, - mocked_myqueue): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) + url = reverse('workouts_view') + + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + url2 = url+'?page=2' + + response = self.c.get(url2) + self.assertEqual(response.status_code,200) + + url3 = url+'?page=3' + + response = self.c.get(url3) + 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) - url = reverse('workouts_view') - 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)], + } - url2 = url+'?page=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) - response = self.c.get(url2) - self.assertEqual(response.status_code,200) - - url3 = url+'?page=3' - - response = self.c.get(url3) - self.assertEqual(response.status_code,200) + form_data = { + 'workouts': ['1','2'], + 'action': ['remove'], + } + response = self.c.post(expected_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) class WorkoutViewTest(TestCase): - def setUp(self): - self.u = UserFactory() + def setUp(self): + self.u = UserFactory() - self.r = Rower.objects.create(user=self.u, - birthdate=faker.profile()['birthdate'], - gdproptin=True, ftpset=True,surveydone=True, - gdproptindate=timezone.now(), - rowerplan='coach') + self.r = Rower.objects.create(user=self.u, + birthdate=faker.profile()['birthdate'], + gdproptin=True, ftpset=True,surveydone=True, + gdproptindate=timezone.now(), + rowerplan='coach') - self.c = Client() - self.c2 = Client() # for anonymous - self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) - self.factory = RequestFactory() - self.password = faker.word() - self.u.set_password(self.password) - self.u.save() + self.c = Client() + self.c2 = Client() # for anonymous + self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) + self.factory = RequestFactory() + self.password = faker.word() + self.u.set_password(self.password) + self.u.save() - result = get_random_file(filename='rowers/tests/testdata/onwater2.csv') + result = get_random_file(filename='rowers/tests/testdata/onwater2.csv') - self.wwater = WorkoutFactory(user=self.r, - csvfilename=result['filename'], - starttime=result['starttime'], - startdatetime=result['startdatetime'], - duration=result['duration'], - distance=result['totaldist'], - workouttype = 'water', - ) + self.wwater = WorkoutFactory(user=self.r, + csvfilename=result['filename'], + starttime=result['starttime'], + startdatetime=result['startdatetime'], + duration=result['duration'], + distance=result['totaldist'], + 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') + + self.winstroke = WorkoutFactory(user=self.r, + csvfilename=result['filename'], + starttime=result['starttime'], + startdatetime=result['startdatetime'], + duration=result['duration'], + distance=result['totaldist'], + workouttype = 'water', + ) - result = get_random_file(filename='rowers/tests/testdata/quiske_in_stroke.csv') + result = get_random_file(filename='rowers/tests/testdata/erg1.csv') + + self.werg1 = WorkoutFactory(user=self.r, + csvfilename=result['filename'], + starttime=result['starttime'], + startdatetime=result['startdatetime'], + duration=result['duration'], + distance=result['totaldist'], + workouttype = 'rower', + ) - self.winstroke = WorkoutFactory(user=self.r, - csvfilename=result['filename'], - starttime=result['starttime'], - startdatetime=result['startdatetime'], - duration=result['duration'], - distance=result['totaldist'], - workouttype = 'water', - ) + result = get_random_file(filename='rowers/tests/testdata/erg2.csv') - result = get_random_file(filename='rowers/tests/testdata/erg1.csv') + self.werg2 = WorkoutFactory(user=self.r, + csvfilename=result['filename'], + starttime=result['starttime'], + startdatetime=result['startdatetime'], + duration=result['duration'], + distance=result['totaldist'], + workouttype = 'rower', + ) - self.werg1 = WorkoutFactory(user=self.r, - csvfilename=result['filename'], - starttime=result['starttime'], - startdatetime=result['startdatetime'], - duration=result['duration'], - distance=result['totaldist'], - workouttype = 'rower', - ) + result = get_random_file(filename='rowers/tests/testdata/erg2.csv') - result = get_random_file(filename='rowers/tests/testdata/erg2.csv') - - self.werg2 = WorkoutFactory(user=self.r, - csvfilename=result['filename'], - starttime=result['starttime'], - startdatetime=result['startdatetime'], - duration=result['duration'], - distance=result['totaldist'], - workouttype = 'rower', - ) - - result = get_random_file(filename='rowers/tests/testdata/erg2.csv') - - self.werg2copy = WorkoutFactory(user=self.r, - csvfilename=result['filename'], - starttime=result['starttime'], - startdatetime=result['startdatetime'], - duration=result['duration'], - distance=result['totaldist'], - workouttype = 'rower', - ) + self.werg2copy = WorkoutFactory(user=self.r, + csvfilename=result['filename'], + starttime=result['starttime'], + startdatetime=result['startdatetime'], + duration=result['duration'], + distance=result['totaldist'], + workouttype = 'rower', + ) - result = get_random_file(filename='rowers/tests/testdata/erg3.csv') + result = get_random_file(filename='rowers/tests/testdata/erg3.csv') - self.werg3 = WorkoutFactory(user=self.r, - csvfilename=result['filename'], - starttime=result['starttime'], - startdatetime=result['startdatetime'], - duration=result['duration'], - distance=result['totaldist'], - workouttype = 'rower', - ) - - def tearDown(self): - vs = VideoAnalysis.objects.all() - for v in vs: + self.werg3 = WorkoutFactory(user=self.r, + csvfilename=result['filename'], + starttime=result['starttime'], + startdatetime=result['startdatetime'], + duration=result['duration'], + distance=result['totaldist'], + workouttype = 'rower', + ) + + def tearDown(self): + vs = VideoAnalysis.objects.all() + for v in vs: v.delete() - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - @patch('rowers.middleware.myqueue') - def test_forcecurve(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_forcecurve_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) + @patch('rowers.dataprep.create_engine') + @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.middleware.myqueue') + def test_forcecurve(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, + mocked_myqueue): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) - response = self.c.get(url) - self.assertEqual(response.status_code,200) + url = reverse('workout_forcecurve_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) + + response = self.c.get(url) + self.assertEqual(response.status_code,200) - form_data = { - 'workstrokesonly': True, - 'plottype': 'scatter' - } + form_data = { + 'workstrokesonly': True, + 'plottype': 'scatter' + } - response = self.c.post(url,form_data) - self.assertEqual(response.status_code,200) + response = self.c.post(url,form_data) + self.assertEqual(response.status_code,200) - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - @pytest.mark.django_db(transaction=True) - def test_joins(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): - with transaction.atomic(): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) + @patch('rowers.dataprep.create_engine') + @patch('rowers.dataprep.getsmallrowdata_db') + @pytest.mark.django_db(transaction=True) + def test_joins(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): + with transaction.atomic(): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + url = reverse('workouts_join_select') + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + d1 = self.werg1.date-datetime.timedelta(days=2) + d2 = self.werg2.date+datetime.timedelta(days=2) + + form_data = { + 'startdate': d1.strftime('%Y-%m-%d'), + 'enddate': d2.strftime('%Y-%m-%d'), + 'modality':'water', + 'waterboattype':['1x'], + } - url = reverse('workouts_join_select') - response = self.c.get(url) - self.assertEqual(response.status_code,200) + form = DateRangeForm(form_data) + self.assertTrue(form.is_valid()) - d1 = self.werg1.date-datetime.timedelta(days=2) - d2 = self.werg2.date+datetime.timedelta(days=2) + form = TrendFlexModalForm(form_data) + self.assertTrue(form.is_valid()) - form_data = { - 'startdate': d1.strftime('%Y-%m-%d'), - 'enddate': d2.strftime('%Y-%m-%d'), - 'modality':'water', - 'waterboattype':['1x'], - } + response = self.c.post(url,form_data) + self.assertEqual(response.status_code,200) - form = DateRangeForm(form_data) - self.assertTrue(form.is_valid()) + url = reverse('workouts_join_view') + + response = self.c.get(url,follow=True) + self.assertEqual(response.status_code,200) - form = TrendFlexModalForm(form_data) - self.assertTrue(form.is_valid()) + expected_url = reverse('workouts_join_select') + self.assertRedirects(response, + expected_url=expected_url, + status_code=302, + target_status_code=200) - response = self.c.post(url,form_data) - self.assertEqual(response.status_code,200) + form_data = { + 'workout_name': 'joined', + 'set_private': False, + 'workouts': [str(self.werg1.id),str(self.werg2.id),str(self.werg3.id)], + } - url = reverse('workouts_join_view') + form = WorkoutMultipleCompareForm(form_data) + self.assertTrue(form.is_valid()) - response = self.c.get(url,follow=True) - self.assertEqual(response.status_code,200) + form = WorkoutJoinParamForm(form_data) + self.assertTrue(form.is_valid()) - expected_url = reverse('workouts_join_select') - self.assertRedirects(response, - expected_url=expected_url, - status_code=302, - target_status_code=200) + response = self.c.post(url,form_data,follow=True) + self.assertEqual(response.status_code,200) - form_data = { - 'workout_name': 'joined', - 'set_private': False, - 'workouts': [str(self.werg1.id),str(self.werg2.id),str(self.werg3.id)], - } - - form = WorkoutMultipleCompareForm(form_data) - self.assertTrue(form.is_valid()) - - form = WorkoutJoinParamForm(form_data) - self.assertTrue(form.is_valid()) - - response = self.c.post(url,form_data,follow=True) - self.assertEqual(response.status_code,200) - - expected_url = reverse(self.r.defaultlandingpage, - kwargs = { + expected_url = reverse(self.r.defaultlandingpage, + kwargs = { 'id':encoder.encode_hex(12) - }) - - self.assertRedirects(response, - expected_url=expected_url, - status_code=302,target_status_code=200) - - - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - @patch('rowers.weather.requests.get', side_effect=mocked_requests) - def test_waterworkout_view(self, - mocked_sqlalchemy, mocked_getsmallrowdata_db, mocked_requests): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) - - url = reverse('workout_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) - - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - url = reverse('workout_downloadmetar_view',kwargs={ - 'id': encoder.encode_hex(self.wwater.id), - 'airportcode': 'LKHO' - } - ) - - response = self.c.get(url,follow=True) - self.assertEqual(response.status_code,200) - - url = reverse('workout_downloadwind_view',kwargs={ - 'id': encoder.encode_hex(self.wwater.id), - } - ) - - response = self.c.get(url,follow=True) - self.assertEqual(response.status_code,200) - - # Stacked Flex Chart - url = reverse('workout_flexchart_stacked_view',kwargs={ - 'id': encoder.encode_hex(self.wwater.id), - }) - - response = self.c.get(url,follow=True) - self.assertEqual(response.status_code,200) - - form_data = { - 'xaxis':'time', - 'yaxis1':'power', - 'yaxis2':'pace', - 'yaxis3':'hr', - 'yaxis4':'spm' - } - - - - response = self.c.post(url,form_data) - self.assertEqual(response.status_code,200) - - url = reverse('workout_erase_column_view',kwargs={'id':encoder.encode_hex(self.wwater.id),'column':'hr'}) - expected_url = reverse('workout_data_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) - - response = self.c.get(url,follow=True) - self.assertEqual(response.status_code,200) - - form_data = {} - response = self.c.post(url,form_data,follow=True) - self.assertRedirects(response,expected_url=expected_url,status_code=302,target_status_code=200) - - url = expected_url - form_data = { - 'cols':['hr','spm'] - } - form = DataFrameColumnsForm(form_data) - - self.assertTrue(form.is_valid()) - - response = self.c.post(url,form_data) - self.assertEqual(response.status_code,200) - - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_smoothen(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) - - - url = reverse('workout_smoothenpace_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) - - response = self.c.get(url,follow=True) - self.assertEqual(response.status_code,200) - - expected_url = reverse(self.r.defaultlandingpage, - kwargs = { - 'id':encoder.encode_hex(self.wwater.id) - } - ) - - self.assertRedirects(response, - expected_url=expected_url, - status_code=302,target_status_code=200) - - url = reverse('workout_undo_smoothenpace_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) - - response = self.c.get(url,follow=True) - self.assertEqual(response.status_code,200) - - expected_url = reverse(self.r.defaultlandingpage, - kwargs = { - 'id':encoder.encode_hex(self.wwater.id) - } - ) - - self.assertRedirects(response, - expected_url=expected_url, - status_code=302,target_status_code=200) - - - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_windform(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) - - - url = reverse('workout_wind_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) - - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - form_data = { - 'dist1':1000, - 'dist2':2000, - 'vwind1':2.0, - 'vwind2':1.4, - 'windunit':'m', - 'winddirection1': 0, - 'winddirection2': 90, - } - - form = UpdateWindForm(form_data) - self.assertTrue(form.is_valid()) - - response = self.c.post(url,form_data) - self.assertEqual(response.status_code,200) - - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_streamform(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) - - - url = reverse('workout_stream_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) - - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - form_data = { - 'dist1':1000, - 'dist2':2000, - 'stream1':2.0, - 'stream2':1.4, - 'streamunit':'m', - } - - form = UpdateStreamForm(form_data) - self.assertTrue(form.is_valid()) - - response = self.c.post(url,form_data) - self.assertEqual(response.status_code,200) - - - - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - @patch('rowers.middleware.myqueue') - def test_setpowerform(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, - mocked_myqueue): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) - - url = reverse('instroke_view',kwargs={'id':encoder.encode_hex(self.winstroke.id)}) - - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - rowdata = rowingdata.rowingdata(csvfile=self.winstroke.csvfilename) - instrokemetrics = rowdata.get_instroke_columns() - - self.assertTrue(len(instrokemetrics)>0) - - url = reverse('instroke_chart', - kwargs={ - 'id':encoder.encode_hex(self.winstroke.id), - 'metric':instrokemetrics[0], - }) - url2 = reverse(self.r.defaultlandingpage,kwargs={'id':encoder.encode_hex(self.winstroke.id)}) - response = self.c.get(url) - self.assertRedirects(response, - expected_url=url2, - status_code=302,target_status_code=200) - - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_setpowerform(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) - - - url = reverse('workout_otwsetpower_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) - - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - form_data = { - 'quick_calc':True, - 'boattype': '1x', - 'weightvalue': 75.0, - 'boatbrand':'maas', - } - - form = AdvancedWorkoutForm(form_data) - self.assertTrue(form.is_valid()) - - response = self.c.post(url,form_data,follow=True) - self.assertEqual(response.status_code,200) - - expected_url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) - - self.assertRedirects(response, - expected_url=expected_url, - status_code=302,target_status_code=200) - - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_commentview(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) - - - url = reverse('workout_comment_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) - - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - form_data = { - 'comment': faker.text() - } - - form = WorkoutCommentForm(form_data) - self.assertTrue(form.is_valid()) - - response = self.c.post(url,form_data,follow=True) - self.assertEqual(response.status_code,200) - - url = reverse('workout_unsubscribe_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_mapview(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) - - - url = reverse('workout_map_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) - - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def notworking_test_workout_image(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) - - url = reverse('workout_uploadimage_view', - kwargs={'id':encoder.encode_hex(self.werg1.id)}) - - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - with open('rowers/tests/testdata/lofoten.jpg','rb') as screenshot_file: - file_data = {'file':screenshot_file} - form_data = { - '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) - - expected_url = reverse(self.r.defaultlandingpage, + }) + + self.assertRedirects(response, + expected_url=expected_url, + status_code=302,target_status_code=200) + + + @patch('rowers.dataprep.create_engine') + @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.weather.requests.get', side_effect=mocked_requests) + def test_waterworkout_view(self, + mocked_sqlalchemy, mocked_getsmallrowdata_db, mocked_requests): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + url = reverse('workout_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) + + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + url = reverse('workout_downloadmetar_view',kwargs={ + 'id': encoder.encode_hex(self.wwater.id), + 'airportcode': 'LKHO' + } + ) + + response = self.c.get(url,follow=True) + self.assertEqual(response.status_code,200) + + url = reverse('workout_downloadwind_view',kwargs={ + 'id': encoder.encode_hex(self.wwater.id), + } + ) + + response = self.c.get(url,follow=True) + self.assertEqual(response.status_code,200) + + # Stacked Flex Chart + url = reverse('workout_flexchart_stacked_view',kwargs={ + 'id': encoder.encode_hex(self.wwater.id), + }) + + response = self.c.get(url,follow=True) + self.assertEqual(response.status_code,200) + + form_data = { + 'xaxis':'time', + 'yaxis1':'power', + 'yaxis2':'pace', + 'yaxis3':'hr', + 'yaxis4':'spm' + } + + + + response = self.c.post(url,form_data) + self.assertEqual(response.status_code,200) + + url = reverse('workout_erase_column_view',kwargs={'id':encoder.encode_hex(self.wwater.id),'column':'hr'}) + expected_url = reverse('workout_data_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) + + response = self.c.get(url,follow=True) + self.assertEqual(response.status_code,200) + + form_data = {} + response = self.c.post(url,form_data,follow=True) + self.assertRedirects(response,expected_url=expected_url,status_code=302,target_status_code=200) + + url = expected_url + form_data = { + 'cols':['hr','spm'] + } + form = DataFrameColumnsForm(form_data) + + self.assertTrue(form.is_valid()) + + response = self.c.post(url,form_data) + self.assertEqual(response.status_code,200) + + @patch('rowers.dataprep.create_engine') + @patch('rowers.dataprep.getsmallrowdata_db') + def test_smoothen(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + + url = reverse('workout_smoothenpace_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) + + response = self.c.get(url,follow=True) + self.assertEqual(response.status_code,200) + + expected_url = reverse(self.r.defaultlandingpage, + kwargs = { + 'id':encoder.encode_hex(self.wwater.id) + } + ) + + self.assertRedirects(response, + expected_url=expected_url, + status_code=302,target_status_code=200) + + url = reverse('workout_undo_smoothenpace_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) + + response = self.c.get(url,follow=True) + self.assertEqual(response.status_code,200) + + expected_url = reverse(self.r.defaultlandingpage, + kwargs = { + 'id':encoder.encode_hex(self.wwater.id) + } + ) + + self.assertRedirects(response, + expected_url=expected_url, + status_code=302,target_status_code=200) + + + @patch('rowers.dataprep.create_engine') + @patch('rowers.dataprep.getsmallrowdata_db') + def test_windform(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + + url = reverse('workout_wind_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) + + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + form_data = { + 'dist1':1000, + 'dist2':2000, + 'vwind1':2.0, + 'vwind2':1.4, + 'windunit':'m', + 'winddirection1': 0, + 'winddirection2': 90, + } + + form = UpdateWindForm(form_data) + self.assertTrue(form.is_valid()) + + response = self.c.post(url,form_data) + self.assertEqual(response.status_code,200) + + @patch('rowers.dataprep.create_engine') + @patch('rowers.dataprep.getsmallrowdata_db') + def test_streamform(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + + url = reverse('workout_stream_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) + + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + form_data = { + 'dist1':1000, + 'dist2':2000, + 'stream1':2.0, + 'stream2':1.4, + 'streamunit':'m', + } + + form = UpdateStreamForm(form_data) + self.assertTrue(form.is_valid()) + + response = self.c.post(url,form_data) + self.assertEqual(response.status_code,200) + + + + @patch('rowers.dataprep.create_engine') + @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.middleware.myqueue') + def test_setpowerform(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, + mocked_myqueue): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + url = reverse('instroke_view',kwargs={'id':encoder.encode_hex(self.winstroke.id)}) + + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + rowdata = rowingdata.rowingdata(csvfile=self.winstroke.csvfilename) + instrokemetrics = rowdata.get_instroke_columns() + + self.assertTrue(len(instrokemetrics)>0) + + url = reverse('instroke_chart', + kwargs={ + 'id':encoder.encode_hex(self.winstroke.id), + 'metric':instrokemetrics[0], + }) + url2 = reverse(self.r.defaultlandingpage,kwargs={'id':encoder.encode_hex(self.winstroke.id)}) + response = self.c.get(url) + self.assertRedirects(response, + expected_url=url2, + status_code=302,target_status_code=200) + + @patch('rowers.dataprep.create_engine') + @patch('rowers.dataprep.getsmallrowdata_db') + def test_setpowerform(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + + url = reverse('workout_otwsetpower_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) + + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + form_data = { + 'quick_calc':True, + 'boattype': '1x', + 'weightvalue': 75.0, + 'boatbrand':'maas', + } + + form = AdvancedWorkoutForm(form_data) + self.assertTrue(form.is_valid()) + + response = self.c.post(url,form_data,follow=True) + self.assertEqual(response.status_code,200) + + expected_url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) + + self.assertRedirects(response, + expected_url=expected_url, + status_code=302,target_status_code=200) + + @patch('rowers.dataprep.create_engine') + @patch('rowers.dataprep.getsmallrowdata_db') + def test_commentview(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + + url = reverse('workout_comment_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) + + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + form_data = { + 'comment': faker.text() + } + + form = WorkoutCommentForm(form_data) + self.assertTrue(form.is_valid()) + + response = self.c.post(url,form_data,follow=True) + self.assertEqual(response.status_code,200) + + url = reverse('workout_unsubscribe_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + @patch('rowers.dataprep.create_engine') + @patch('rowers.dataprep.getsmallrowdata_db') + def test_mapview(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + + url = reverse('workout_map_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) + + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + @patch('rowers.dataprep.create_engine') + @patch('rowers.dataprep.getsmallrowdata_db') + def notworking_test_workout_image(self, mocked_sqlalchemy, + mocked_getsmallrowdata_db): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + url = reverse('workout_uploadimage_view', + kwargs={'id':encoder.encode_hex(self.werg1.id)}) + + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + with open('rowers/tests/testdata/lofoten.jpg','rb') as screenshot_file: + file_data = {'file':screenshot_file} + form_data = { + 'file':screenshot_file, + } + + response = self.c.post(url,form_data,format='multipart',follow=True) + + expected_url = reverse(self.r.defaultlandingpage, kwargs={'id':encoder.encode_hex(self.werg1.id)}) - self.assertRedirects(response, - expected_url=expected_url, - status_code=302,target_status_code=200) + self.assertRedirects(response, + expected_url=expected_url, + status_code=302,target_status_code=200) + + @patch('rowers.dataprep.create_engine') + @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.getrowdata_db',side_effect=mocked_getrowdata_db) + @pytest.mark.django_db(transaction=True) + def test_workout_split(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, + mocked_getrowdata_db): + with transaction.atomic(): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + url = reverse('workout_split_view',kwargs={'id':encoder.encode_hex(self.werg1.id)}) + + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + form_data = { + 'splittime': '2:00', + 'splitmode': ['keep original','keep first','keep second'] + } + + form = WorkoutSplitForm(form_data) + + self.assertTrue(form.is_valid()) + + response = self.c.post(url, form_data,follow=True) + self.assertEqual(response.status_code,200) + + @patch('rowers.dataprep.create_engine') + @patch('rowers.dataprep.getrowdata_db',side_effect=mocked_getrowdata_db) + @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_forfusion) + def test_workout_fusion(self, mocked_sqlalchemy, mocked_getrowdata_db, + mocked_getsmallrowdata_db): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + url = reverse('workout_fusion_view', + kwargs={'id1':encoder.encode_hex(self.werg2.id), + 'id2':encoder.encode_hex(self.werg2copy.id)}) + + + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + form_data = { + 'posneg':'pos', + 'offset': datetime.timedelta(seconds=0), + 'columns': ['power','hr' + ] + } + + form = FusionMetricChoiceForm(form_data,instance=self.werg2copy) + + self.assertTrue(form.is_valid()) + + response = self.c.post(url,form_data,follow=True) + self.assertEqual(response.status_code,200) + + + def test_remove_power_view(self): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + url = reverse('remove_power_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) + url2 = reverse(self.r.defaultlandingpage, + kwargs={ + 'id':encoder.encode_hex(self.wwater.id) + } + ) + response = self.c.get(url) + self.assertRedirects(response, + expected_url=url2, + status_code=302, + target_status_code=200 + ) - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - @patch('rowers.dataprep.getrowdata_db',side_effect=mocked_getrowdata_db) - @pytest.mark.django_db(transaction=True) - def test_workout_split(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, - mocked_getrowdata_db): - with transaction.atomic(): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) - - url = reverse('workout_split_view',kwargs={'id':encoder.encode_hex(self.werg1.id)}) - - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - form_data = { - 'splittime': '2:00', - 'splitmode': ['keep original','keep first','keep second'] - } - - form = WorkoutSplitForm(form_data) - - self.assertTrue(form.is_valid()) - - response = self.c.post(url, form_data,follow=True) - self.assertEqual(response.status_code,200) - - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getrowdata_db',side_effect=mocked_getrowdata_db) - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_forfusion) - def test_workout_fusion(self, mocked_sqlalchemy, mocked_getrowdata_db, - mocked_getsmallrowdata_db): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) - - url = reverse('workout_fusion_view', - kwargs={'id1':encoder.encode_hex(self.werg2.id), - 'id2':encoder.encode_hex(self.werg2copy.id)}) + @patch('rowers.dataprep.create_engine') + @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.get_video_data',side_effect=mocked_videodata) + def test_workout_video_view(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, + mocked_videodata): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + url = reverse('workout_video_create_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) + url2 = reverse('workout_video_view',kwargs={'id':encoder.encode_hex(1)}) - response = self.c.get(url) - self.assertEqual(response.status_code,200) + yturl = 'https://youtu.be/6UzaWm1Ybrw' + + response = self.c.get(url) + + self.assertEqual(response.status_code,200) + + form_data = { + 'url': yturl, + 'delay': -119, + 'groups': ['basic'], + 'name': 'Video A', + 'save_button':'Save', + } - form_data = { - 'posneg':'pos', - 'offset': datetime.timedelta(seconds=0), - 'columns': ['power','hr' - ] - } + response = self.c.post(url,form_data) + self.assertRedirects(response, + expected_url=url2, + status_code=302, + target_status_code=200) + + response = self.c.get(url2) + self.assertEqual(response.status_code,200) - form = FusionMetricChoiceForm(form_data,instance=self.werg2copy) + form_data = { + 'url': yturl, + 'delay': -119, + 'groups': ['basic'], + 'name': 'Video A', + 'save_button': 'Save', + } - self.assertTrue(form.is_valid()) + response = self.c.post(url2,form_data) + self.assertEqual(response.status_code,200) - response = self.c.post(url,form_data,follow=True) - self.assertEqual(response.status_code,200) + url = reverse('workout_video_view_mini',kwargs={'id':encoder.encode_hex(1)}) + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + response = self.c.post(url,form_data) + self.assertEqual(response.status_code,200) + + # delete video + url = reverse('video_delete',kwargs={'pk':1}) + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + response = self.c.post(url,{},follow=True) + expected_url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) + self.assertRedirects(response,expected_url=expected_url,status_code=302,target_status_code=200) + + @patch('rowers.dataprep.create_engine') + @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.get_video_data',side_effect=mocked_videodata) + def test_workout_video_view_erg(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, + mocked_videodata): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + url = reverse('workout_video_create_view',kwargs={'id':encoder.encode_hex(self.werg1.id)}) + url2 = reverse('workout_video_view',kwargs={'id':encoder.encode_hex(1)}) + + + yturl = 'https://youtu.be/6UzaWm1Ybrw' + + response = self.c.get(url) + + self.assertEqual(response.status_code,200) + + form_data = { + 'url': yturl, + 'delay': -119, + 'groups': ['basic'], + 'name': 'Video A', + 'save_button':'Save', + } + + response = self.c.post(url,form_data) + self.assertRedirects(response, + expected_url=url2, + status_code=302, + target_status_code=200) + + response = self.c.get(url2) + self.assertEqual(response.status_code,200) + + form_data = { + 'url': yturl, + 'delay': -119, + 'groups': ['basic'], + 'name': 'Video A', + 'save_button': 'Save', + } + + response = self.c.post(url2,form_data) + self.assertEqual(response.status_code,200) + + url = reverse('workout_video_view_mini',kwargs={'id':encoder.encode_hex(1)}) + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + response = self.c.post(url,form_data) + self.assertEqual(response.status_code,200) + + url = reverse('workout_video_view_mini',kwargs={'id':encoder.encode_hex(1)}) + response = self.c2.get(url) + self.assertEqual(response.status_code,200) + + url = reverse('workout_video_view',kwargs={'id':encoder.encode_hex(1)}) + response = self.c2.get(url) + self.assertEqual(response.status_code,200) - def test_remove_power_view(self): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) - - url = reverse('remove_power_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) - url2 = reverse(self.r.defaultlandingpage, - kwargs={ - 'id':encoder.encode_hex(self.wwater.id) - } - ) - response = self.c.get(url) - self.assertRedirects(response, - expected_url=url2, - status_code=302, - target_status_code=200 - ) - - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - @patch('rowers.dataprep.get_video_data',side_effect=mocked_videodata) - def test_workout_video_view(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, + @patch('rowers.dataprep.create_engine') + @patch('rowers.dataprep.getrowdata_db',side_effect=mocked_getrowdata_db) + @patch('rowers.dataprep.get_video_data',side_effect=mocked_videodata) + def test_video_selectworkout(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, mocked_videodata): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) - url = reverse('workout_video_create_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) - url2 = reverse('workout_video_view',kwargs={'id':encoder.encode_hex(1)}) + url = reverse('video_selectworkout') + + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + d1 = self.wwater.date-datetime.timedelta(days=24) + d2 = self.wwater.date+datetime.timedelta(days=24) + + form_data = { + 'workout':'1', + 'startdate': d1.strftime("%Y-%m-%d"), + 'enddate': d2.strftime("%Y-%m-%d") + } - yturl = 'https://youtu.be/6UzaWm1Ybrw' + url2 = reverse('workout_video_create_view',kwargs={'id':encoder.encode_hex(1)}) - response = self.c.get(url) - - self.assertEqual(response.status_code,200) - - form_data = { - 'url': yturl, - 'delay': -119, - 'groups': ['basic'], - 'name': 'Video A', - 'save_button':'Save', - } - - response = self.c.post(url,form_data) - self.assertRedirects(response, - expected_url=url2, - status_code=302, - target_status_code=200) - - response = self.c.get(url2) - self.assertEqual(response.status_code,200) - - form_data = { - 'url': yturl, - 'delay': -119, - 'groups': ['basic'], - 'name': 'Video A', - 'save_button': 'Save', - } - - response = self.c.post(url2,form_data) - self.assertEqual(response.status_code,200) - - url = reverse('workout_video_view_mini',kwargs={'id':encoder.encode_hex(1)}) - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - response = self.c.post(url,form_data) - self.assertEqual(response.status_code,200) - - # delete video - url = reverse('video_delete',kwargs={'pk':1}) - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - response = self.c.post(url,{},follow=True) - expected_url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) - self.assertRedirects(response,expected_url=expected_url,status_code=302,target_status_code=200) - - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - @patch('rowers.dataprep.get_video_data',side_effect=mocked_videodata) - def test_workout_video_view_erg(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, - mocked_videodata): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) - - url = reverse('workout_video_create_view',kwargs={'id':encoder.encode_hex(self.werg1.id)}) - url2 = reverse('workout_video_view',kwargs={'id':encoder.encode_hex(1)}) - - - yturl = 'https://youtu.be/6UzaWm1Ybrw' - - response = self.c.get(url) - - self.assertEqual(response.status_code,200) - - form_data = { - 'url': yturl, - 'delay': -119, - 'groups': ['basic'], - 'name': 'Video A', - 'save_button':'Save', - } - - response = self.c.post(url,form_data) - self.assertRedirects(response, - expected_url=url2, - status_code=302, - target_status_code=200) - - response = self.c.get(url2) - self.assertEqual(response.status_code,200) - - form_data = { - 'url': yturl, - 'delay': -119, - 'groups': ['basic'], - 'name': 'Video A', - 'save_button': 'Save', - } - - response = self.c.post(url2,form_data) - self.assertEqual(response.status_code,200) - - url = reverse('workout_video_view_mini',kwargs={'id':encoder.encode_hex(1)}) - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - response = self.c.post(url,form_data) - self.assertEqual(response.status_code,200) - - url = reverse('workout_video_view_mini',kwargs={'id':encoder.encode_hex(1)}) - response = self.c2.get(url) - self.assertEqual(response.status_code,200) - - url = reverse('workout_video_view',kwargs={'id':encoder.encode_hex(1)}) - response = self.c2.get(url) - self.assertEqual(response.status_code,200) - - - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getrowdata_db',side_effect=mocked_getrowdata_db) - @patch('rowers.dataprep.get_video_data',side_effect=mocked_videodata) - def test_video_selectworkout(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, - mocked_videodata): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) - - url = reverse('video_selectworkout') - - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - d1 = self.wwater.date-datetime.timedelta(days=24) - d2 = self.wwater.date+datetime.timedelta(days=24) - - - form_data = { - 'workout':'1', - 'startdate': d1.strftime("%Y-%m-%d"), - 'enddate': d2.strftime("%Y-%m-%d") - } - - url2 = reverse('workout_video_create_view',kwargs={'id':encoder.encode_hex(1)}) - - response = self.c.post(url,form_data) - self.assertRedirects(response, - expected_url=url2, - status_code=302, - target_status_code=200) + response = self.c.post(url,form_data) + self.assertRedirects(response, + expected_url=url2, + status_code=302, + target_status_code=200) - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_editsummaryview(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) + @patch('rowers.dataprep.create_engine') + @patch('rowers.dataprep.getsmallrowdata_db') + def test_editsummaryview(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + + url = reverse('workout_summary_edit_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) + + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + form_data = { + 'ruptures':'ruptures', + } + + response = self.c.post(url,form_data) + self.assertEqual(response.status_code,200) + + form_data = { + 'intervalstring':'4x2min/1min', + } + + form = SummaryStringForm(form_data) + self.assertTrue(form.is_valid()) + + response = self.c.post(url,form_data) + self.assertEqual(response.status_code,200) + + form_data = { + 'powerorpace':'power', + 'value_pace':'2:23', + 'value_power':'200', + 'value_work':'400', + 'value_spm':'20', + 'activeminutesmin':'0', + 'activeminutesmax':'60', + 'savepowerpaceform':True, + } + + response = self.c.post(url,form_data) + self.assertEqual(response.status_code,200) + + form_data = { + 'savestringform':'4x2min/1min', + } + + response = self.c.post(url,form_data) + self.assertEqual(response.status_code,200) + + form_data = { + 'selector':'power', + 'power': 200, + 'pace': '2:30', + 'work': 400, + 'activeminutesmin':'0', + 'activeminutesmax': '60', + } + + form = PowerIntervalUpdateForm(form_data) + self.assertTrue(form.is_valid()) + + response = self.c.post(url,form_data) + self.assertEqual(response.status_code,200) - url = reverse('workout_summary_edit_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) - - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - form_data = { - 'ruptures':'ruptures', - } - - response = self.c.post(url,form_data) - self.assertEqual(response.status_code,200) - - form_data = { - 'intervalstring':'4x2min/1min', - } - - form = SummaryStringForm(form_data) - self.assertTrue(form.is_valid()) - - response = self.c.post(url,form_data) - self.assertEqual(response.status_code,200) - - form_data = { - 'powerorpace':'power', - 'value_pace':'2:23', - 'value_power':'200', - 'value_work':'400', - 'value_spm':'20', - 'activeminutesmin':'0', - 'activeminutesmax':'60', - 'savepowerpaceform':True, - } - - response = self.c.post(url,form_data) - self.assertEqual(response.status_code,200) - - form_data = { - 'savestringform':'4x2min/1min', - } - - response = self.c.post(url,form_data) - self.assertEqual(response.status_code,200) - - form_data = { - 'selector':'power', - 'power': 200, - 'pace': '2:30', - 'work': 400, - 'activeminutesmin':'0', - 'activeminutesmax': '60', - } - - form = PowerIntervalUpdateForm(form_data) - self.assertTrue(form.is_valid()) - - response = self.c.post(url,form_data) - self.assertEqual(response.status_code,200) - - - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_workout_delete(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) - - url = reverse('workout_delete',kwargs={'pk':self.wwater.id}) - - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - post_data = {} - - response = self.c.post(url,follow=True) - - expected_url = reverse('workouts_view') - - self.assertRedirects(response, - expected_url=expected_url, - status_code=302,target_status_code=200) + @patch('rowers.dataprep.create_engine') + @patch('rowers.dataprep.getsmallrowdata_db') + def test_workout_delete(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + url = reverse('workout_delete',kwargs={'pk':self.wwater.id}) + + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + post_data = {} + + response = self.c.post(url,follow=True) + + expected_url = reverse('workouts_view') + + self.assertRedirects(response, + expected_url=expected_url, + status_code=302,target_status_code=200) diff --git a/rowers/tests/test_urls.py b/rowers/tests/test_urls.py index 87da9b7d..c798445f 100644 --- a/rowers/tests/test_urls.py +++ b/rowers/tests/test_urls.py @@ -7,6 +7,7 @@ from __future__ import unicode_literals from .statements import * nu = datetime.datetime.now() +import rowers.courses as courses tested = [ @@ -27,6 +28,14 @@ class URLTests(TestCase): r = Rower.objects.create(user=u,rowerplan='coach',gdproptin=True, ftpset=True, gdproptindate=timezone.now()) self.c = Client() + cs = courses.kmltocourse('rowers/tests/testdata/thyro.kml') + course = cs[0] + cname = course['name'] + cnotes = course['description'] + polygons = course['polygons'] + self.ThyroBaantje = courses.createcourse(r,cname,polygons,notes=cnotes) + self.ThyroBaantje.save() + self.nu = datetime.datetime.now() filename = 'rowers/tests/testdata/testdata.csv' @@ -168,6 +177,19 @@ class URLTests(TestCase): '/rowers/workout/upload/team/', '/rowers/workouts-join/', '/rowers/workouts-join-select/', + '/rowers/api/courses/', + '/rowers/api/courses/?name=Brno', + '/rowers/api/courses/?course_distance=2000', + '/rowers/api/courses/?course_distance=aap', + '/rowers/api/courses/?distance_from=52&latitude=42&longitude=7', + '/rowers/api/courses/?distance_from=50&latitude=42&longitude=-aap', + '/rowers/api/courses/', + '/rowers/api/courses/1/', + '/rowers/list-workouts/?selectworkouts=true', + '/rowers/api/courses/kml/', + '/rowers/api/courses/kml/?id=1', + '/rowers/api/courses/kml?id=1&id=4/', + '/rowers/api/courses/kml/?id=aap', ] diff --git a/rowers/views/apiviews.py b/rowers/views/apiviews.py index d7173ea3..c6f60c98 100644 --- a/rowers/views/apiviews.py +++ b/rowers/views/apiviews.py @@ -27,7 +27,7 @@ class XMLParser(BaseParser): dologging("apilog.log", "XML Parser") try: s = ET.parse(stream).getroot() - except Exception as e: + except Exception as e: # pragma: no cover dologging("apilog.log",e) return HttpResponse(status=500) return s @@ -231,11 +231,11 @@ def strokedataform_v2(request, id=0): def part_of_day(hour): if 5 <= hour < 12: return "Morning" - elif 12 <= hour < 18: + elif 12 <= hour < 18: # pragma: no cover return "Afternoon" - elif 18 <= hour < 24: + elif 18 <= hour < 24: # pragma: no cover return "Evening" - else: + else: # pragma: no cover return "Night" # KML API views @@ -254,7 +254,7 @@ Optional, not for CN @api_view(["GET"]) @permission_classes([AllowAny]) def course_list(request): - if request.method != 'GET': + if request.method != 'GET': # pragma: no cover dologging('apilog.log','{m} request to KML endpoint'.format(m=request.method)) return HttpResponseNotAllowed("Method not supported") @@ -281,7 +281,7 @@ def course_list(request): newlist = [] for c in courses: distance = geo_distance(float(latitude), float(longitude), c.coord[0], c.coord[1])[0] - if distance < float(distance_from): + if distance < float(distance_from): # pragma: no cover newlist.append(c) courses = newlist except ValueError: @@ -307,13 +307,13 @@ def course_list(request): @api_view(["GET"]) @permission_classes([AllowAny]) def get_crewnerd_kml(request,id=0): - if request.method != 'GET': + 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: + except GeoCourse.DoesNotExist: # pragma: no cover raise Http404("This course does not exist") kml = coursetokml(c, cn=True) @@ -324,14 +324,20 @@ def get_crewnerd_kml(request,id=0): @api_view(["GET"]) @permission_classes([AllowAny]) def get_crewnerd_multiple(request): - if request.method != 'GET': + 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()) - ids = [int(id) for id in tdict['id']] + 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] @@ -351,11 +357,11 @@ def strokedata_tcx(request): """ Upload a TCX file through API """ - if request.method != 'POST': + if request.method != 'POST': # pragma: no cover dologging('apilog.log','GET request to TCX endpoint') - return HttpResponseNotAllowed("Method not supported") # pragma: no cover + return HttpResponseNotAllowed("Method not supported") - if 'application/xml' not in request.content_type.lower(): + if 'application/xml' not in request.content_type.lower(): # pragma: no cover dologging('apilog.log','POST data not application/xml, request to TCX endpoint') dologging('apilog.log', request.content_type.lower()) return HttpResponseNotAllowed("Need application/xml") @@ -382,7 +388,7 @@ def strokedata_tcx(request): lap_duration_seconds = float(lap_duration_node.text) total_duration += lap_duration_seconds - except Exception as e: + except Exception as e: # pragma: no cover dologging('apilog.log','TCX') dologging('apilog.log',e) return HttpResponseNotAllowed("Could not parse TCX data") @@ -436,7 +442,7 @@ def strokedata_tcx(request): "workout id": workoutid, "status": "success", }) - except Exception as e: + except Exception as e: # pragma: no cover dologging('apilog.log','TCX API endpoint') dologging('apilog.log',e) return HttpResponse(status=500) @@ -489,7 +495,7 @@ def strokedatajson_v3(request): title = request.data.get('name','') try: elapsedTime = request.data['elapsedTime'] - except KeyError: + except KeyError: # pragma: no cover try: duration = request.data['duration'] try: @@ -501,7 +507,7 @@ def strokedatajson_v3(request): return HttpResponse("Missing Elapsed Time", status=400) try: totalDistance = request.data['distance'] - except KeyError: + except KeyError: # pragma: no cover return HttpResponse("Missing Total Distance", status=400) timeZone = request.data.get('timezone','UTC') workouttype = request.data.get('workouttype','rower') @@ -522,12 +528,12 @@ def strokedatajson_v3(request): df = pd.DataFrame() try: strokes = request.data['strokes'] - except KeyError: + except KeyError: # pragma: no cover return HttpResponse("No Stroke Data in JSON", status=400) try: df = pd.DataFrame(strokes['data']) - except KeyError: + except KeyError: # pragma: no cover try: df = pd.DataFrame(request.data['strokedata']) except: @@ -538,7 +544,7 @@ def strokedatajson_v3(request): status, comment, data = api_get_dataframe(startdatetime, df) - if status != 200: + if status != 200: # pragma: no cover return HttpResponse(comment, status=status) @@ -648,7 +654,7 @@ def strokedatajson_v2(request, id): try: for d in request.data['data']: dologging('apilog.log',json.dumps(d)) - except KeyError: + except KeyError: # pragma: no cover try: for d in request.data['strokedata']: dologging('apilog.log',json.dumps(d)) @@ -672,7 +678,7 @@ def strokedatajson_v2(request, id): df.sort_index(inplace=True) status, comment, data = api_get_dataframe(row.startdatetime, df) - if status != 200: + if status != 200: # pragma: no cover return HttpResponse(comment, status=status) r = getrower(request.user) diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 7aa5b6f5..32bf82cf 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -1664,7 +1664,7 @@ def course_compare_view(request, id=0): labeldict = { int(w.id): w.__str__() for w in workouts } - except: + except: # pragma: no cover labeldict = {} res = interactive_multiple_compare_chart(workoutids, xparam, yparam, @@ -1846,7 +1846,7 @@ def virtualevent_compare_view(request, id=0): labeldict = { int(w.id): w.__str__() for w in workouts } - except: + except: # pragma: no cover labeldict = {} res = interactive_multiple_compare_chart(workoutids, xparam, yparam, @@ -2085,7 +2085,7 @@ def workouts_bulk_actions(request): destination=destination)) url = reverse('workouts_view') return HttpResponseRedirect(url) - else: + else: # pragma: no cover if len(workouts) == 0: url = reverse(workouts_view) return HttpResponseRedirect(url) From e328bc38fd8bf3304d1aeb181a5b6fa7690d477c Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Thu, 29 Feb 2024 19:17:27 +0100 Subject: [PATCH 07/13] expanding tests a bit --- rowers/tests/test_aworkouts.py | 15 ++++++++++++++- rowers/tests/testdata/testdata.tcx.gz | Bin 3999 -> 4000 bytes rowers/views/workoutviews.py | 8 ++++---- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/rowers/tests/test_aworkouts.py b/rowers/tests/test_aworkouts.py index 06da2171..595f0148 100644 --- a/rowers/tests/test_aworkouts.py +++ b/rowers/tests/test_aworkouts.py @@ -117,10 +117,23 @@ class ListWorkoutTest(TestCase): form_data = { 'workouts': ['1','2'], - 'action': ['remove'], + '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, diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 23bd61cc089d063670e638aa199f6f6e33b8fc30..f67f2da08debcff3873da3041d8086f2cfd88f25 100644 GIT binary patch delta 257 zcmV+c0sj7O}Bp8ot$i+R{FSFAHVGSE}ggAyRK|D;Q1XTfpR&tLDh zTBprkKJB;NaQSGzIRC-(bkm2wEl-x+W|=tTPahuLmHRq)+^vrvJv{v7X=bzibGQB1 z^cFvTefnaV?hN1ufZhKGiz{F3p8WPP9p2fZUaiw}c#u#oFCM0+@giON@Xr4LkP83& H#DD<+XIYPr delta 256 zcmV+b0ssD>AD9~XYulrYL7gt`B z`{(B;tD~+@KmGE>xIFh)CrjV`{Cair^77~Yp5DiF=#_`_Vat>5Kk1U$S#WUm^Vhqr z)@iesPy1~*Tt3<_&VTSc-Spva%adidStbto(}zcQ<-QIcckAOv4-bENn%Qjs+-?6g zy~R&opT1b8I|KLuVE6yQ;>s7hC%=76hj+H9SL^f~9wd~@i-+lHyhxWmyz@V2ZSMla GfB^uzdXM4& diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 32bf82cf..83fbcadd 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -2048,9 +2048,9 @@ def workouts_bulk_actions(request): w = get_workout_by_opaqueid(request, encid) if w.user == r: workouts.append(w) - else: + else: # pragma: no cover messages.error(request,'Bulk actions are not accessible to coaches') - except KeyError: + except KeyError: # pragma: no cover pass if request.method == 'POST': @@ -2058,7 +2058,7 @@ def workouts_bulk_actions(request): form = WorkoutMultipleCompareForm(request.POST) if form.is_valid() and actionform.is_valid(): workouts = form.cleaned_data['workouts'] - if len(workouts) == 0: + if len(workouts) == 0: # pragma: no cover url = reverse('workouts_view') return HttpResponseRedirect(url) action = actionform.cleaned_data['action'] @@ -2078,7 +2078,7 @@ def workouts_bulk_actions(request): 'Workout {id} exported to {destination}'.format( id=encoder.encode_hex(w.id), destination=destination)) - except NoTokenError: + except NoTokenError: # pragma: no cover messages.error(request, 'Export to {destination} of workout {id} failed'.format( id=encoder.encode_hex(w.id), From c4426d8a0b927412ba5945bd7635e11478338fc1 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 1 Mar 2024 12:26:36 +0100 Subject: [PATCH 08/13] Increasing coverage --- rowers/dataprep.py | 14 +-- rowers/interactiveplots.py | 7 +- rowers/tests/statements.py | 2 +- rowers/tests/test_analysis.py | 123 ++++++++++++++++++++++++++ rowers/tests/test_aworkouts.py | 28 ++++++ rowers/tests/test_basicrower.py | 12 +++ rowers/tests/test_uploads.py | 30 +------ rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4000 bytes rowers/views/analysisviews.py | 47 +++++----- rowers/views/importviews.py | 32 +++---- rowers/views/userviews.py | 14 +-- rowers/views/workoutviews.py | 2 +- 12 files changed, 226 insertions(+), 85 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 35a7ec9f..f206a457 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -120,10 +120,10 @@ from rowers.datautils import rpetotss def rscore_approx(row): if row['rscore'] > 0: return row['rscore'] - if row['rscore'] == 0: + if row['rscore'] == 0: # pragma: no cover if row['hrtss'] > 0: return row['hrtss'] - else: + else: # pragma: no cover dd = 3600*row['duration'].hour+60*row['duration'].minute+row['duration'].second dd = dd/3600 try: @@ -135,7 +135,7 @@ def rscore_approx(row): w = Workout.objects.get(id=row['id']) trimp, hrtss = workout_trimp(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 = dd/3600 return int(rpetotss[row['rpe']]*dd) @@ -143,7 +143,7 @@ def rscore_approx(row): minutes = row['duration'].hour*60+row['duration'].minute return minutes - return 0 + return 0 # pragma: no cover thetimezone = get_current_timezone() @@ -177,7 +177,7 @@ def workout_goldmedalstandard(workout, reset=False): if workout.workouttype in otwtypes: try: factor = 100./(100.-workout.user.otwslack) - except ZeroDivisionError: + except ZeroDivisionError: # pragma: no cover factor = 1. workout.user.otwslack = 0 workout.user.save() @@ -186,8 +186,8 @@ def workout_goldmedalstandard(workout, reset=False): workout.goldmedalseconds = goldmedalseconds workout.save() return goldmedalstandard, goldmedalseconds - else: - return 0, 0 + + return 0, 0 # pragma: no cover def check_marker(workout): diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 5bc84a34..4c03a9bb 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -4426,6 +4426,7 @@ def instroke_interactive_chart(df,metric, workout, spm_min, spm_max, df_plot ) + TIPS = OrderedDict([ ('x','@x'), ('median','@median'), @@ -4515,7 +4516,6 @@ def instroke_interactive_chart(df,metric, workout, spm_min, spm_max, script = "" div = "Something went wrong with the chart" - return (script, div) @@ -4531,6 +4531,7 @@ def interactive_chart(id=0, promember=0, intervaldata={}): columns = ['time', 'pace', 'hr', 'fpace', 'ftime', 'spm'] datadf = dataprep.getsmallrowdata_db(columns, ids=[id]) + datadf.dropna(axis=0, how='any', inplace=True) row = Workout.objects.get(id=id) @@ -4578,6 +4579,7 @@ def interactive_chart(id=0, promember=0, intervaldata={}): ) plot.line('time', 'pace', source=source, legend_label="Pace", name="pace") + try: plot.title.text = row.name except ValueError: # pragma: no cover @@ -4647,7 +4649,6 @@ def interactive_chart(id=0, promember=0, intervaldata={}): hover.mode = 'mouse' # hover.name = ["spm", "pace"] - script, div = components(plot) return [script, div] @@ -5915,7 +5916,7 @@ def interactive_flex_chart2(id, r, promember=0, popt, pcov = optimize.curve_fit(func, x1, y1) ytrend = func(x1, popt[0], popt[1]) rowdata['ytrend'] = ytrend - except TypeError: + except TypeError: # pragma: no cover rowdata['ytrend'] = y1 # prepare data diff --git a/rowers/tests/statements.py b/rowers/tests/statements.py index 7fa4e1ed..3b88c2e2 100644 --- a/rowers/tests/statements.py +++ b/rowers/tests/statements.py @@ -198,7 +198,7 @@ class WorkoutFactory(factory.DjangoModelFactory): startdatetime = FuzzyDateTime(start_dt=one_month_ago, force_year=datetime.date.today().year) starttime = get_random_file(name=faker.word())['starttime'] workouttype='water' - date=timezone.now().date() + date=startdatetime.evaluate(2,None, False).date() duration=get_random_file(name=faker.word())['duration'] distance=get_random_file(name=faker.word())['totaldist'] csvfilename=get_random_file(name=faker.word())['filename'] diff --git a/rowers/tests/test_analysis.py b/rowers/tests/test_analysis.py index fe0997e4..8c7bc3e0 100644 --- a/rowers/tests/test_analysis.py +++ b/rowers/tests/test_analysis.py @@ -25,6 +25,11 @@ class ListWorkoutTest(TestCase): self.c = Client() 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.password = faker.word() self.u.set_password(self.password) @@ -79,6 +84,10 @@ class PlannedSessionTests(TestCase): self.c = Client() 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.password = faker.word() self.u.set_password(self.password) @@ -130,6 +139,22 @@ class ForcecurveTest(TestCase): self.c = Client() 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.password = faker.word() @@ -143,6 +168,7 @@ class ForcecurveTest(TestCase): except (IOError, FileNotFoundError,OSError): pass + @patch('rowers.dataprep.getsmallrowdata_db',side_effect = mocked_getempowerdata_db) def test_forcecurve_plot(self, mocked_getsmallrowdata_db): login = self.c.login(username=self.u.username, password = self.password) @@ -182,7 +208,62 @@ class ForcecurveTest(TestCase): response = self.c.get(url) 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.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.password = faker.word() self.u.set_password(self.password) @@ -296,6 +381,10 @@ class WorkoutBoxPlotTestNew(TestCase): self.c = Client() 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.password = faker.word() self.u.set_password(self.password) @@ -393,6 +482,10 @@ class WorkoutHistoTestNew(TestCase): self.c = Client() 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.password = faker.word() self.u.set_password(self.password) @@ -490,6 +583,10 @@ class History(TestCase): self.c = Client() 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.password = faker.word() self.u.set_password(self.password) @@ -577,6 +674,10 @@ class GoldMedalScores(TestCase): self.c = Client() 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') self.w1 = WorkoutFactory(user=self.r, csvfilename=result['filename'], @@ -677,6 +778,10 @@ class WorkoutFlexallTestNew(TestCase): self.c = Client() 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.password = faker.word() self.u.set_password(self.password) @@ -774,6 +879,10 @@ class WorkoutStatsTestNew(TestCase): self.c = Client() 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() i = 0 @@ -1082,6 +1191,10 @@ class MarkerPerformanceTest(TestCase): self.c = Client() 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.password = faker.word() self.u.set_password(self.password) @@ -1095,6 +1208,8 @@ class MarkerPerformanceTest(TestCase): workouttype='rower', rankingpiece=True, ) + self.werg2k.date = self.werg2k.startdatetime.date() + self.werg2k.save() # need a 2k, 5k, 1h row self.werg5k = WorkoutFactory( @@ -1104,6 +1219,8 @@ class MarkerPerformanceTest(TestCase): workouttype='rower', rankingpiece=True, ) + self.werg5k.date = self.werg5k.startdatetime.date() + self.werg5k.save() # need a 2k, 5k, 1h row self.werg1h = WorkoutFactory( @@ -1113,6 +1230,8 @@ class MarkerPerformanceTest(TestCase): workouttype='rower', rankingpiece=True, ) + self.werg1h.date = self.werg1h.startdatetime.date() + self.werg1h.save() def tearDown(self): for workout in self.user_workouts: @@ -1280,6 +1399,10 @@ class AlertTest(TestCase): self.c = Client() 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.password = faker.word() self.u.set_password(self.password) diff --git a/rowers/tests/test_aworkouts.py b/rowers/tests/test_aworkouts.py index 595f0148..fdbb7e75 100644 --- a/rowers/tests/test_aworkouts.py +++ b/rowers/tests/test_aworkouts.py @@ -254,6 +254,34 @@ class WorkoutViewTest(TestCase): response = self.c.post(url,form_data) 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.getsmallrowdata_db') @pytest.mark.django_db(transaction=True) diff --git a/rowers/tests/test_basicrower.py b/rowers/tests/test_basicrower.py index 39a9b1f6..934c7737 100644 --- a/rowers/tests/test_basicrower.py +++ b/rowers/tests/test_basicrower.py @@ -61,3 +61,15 @@ class SimpleViewTest(TestCase): self.assertRedirects(response, expected_url='/rowers/list-workouts/', 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) diff --git a/rowers/tests/test_uploads.py b/rowers/tests/test_uploads.py index 9c6ba94b..68a61119 100644 --- a/rowers/tests/test_uploads.py +++ b/rowers/tests/test_uploads.py @@ -28,15 +28,6 @@ class ViewTest(TestCase): 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.getsmallrowdata_db',side_effect=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 try: os.remove(f_to_be_deleted+'.gz') - except (FileNotFoundError,OSError): + except (FileNotFoundError,OSError): # pragma: no cover pass @patch('rowers.dataprep.create_engine') @@ -230,7 +221,7 @@ class ViewTest(TestCase): f_to_be_deleted = w.csvfilename try: os.remove(f_to_be_deleted+'.gz') - except (FileNotFoundError,OSError): + except (FileNotFoundError,OSError): # pragma: no cover pass @@ -271,7 +262,7 @@ class ViewTest(TestCase): f_to_be_deleted = w.csvfilename try: os.remove(f_to_be_deleted+'.gz') - except (FileNotFoundError,OSError): + except (FileNotFoundError,OSError): # pragma: no cover pass @patch('rowers.dataprep.create_engine') @@ -341,19 +332,6 @@ class ViewTest(TestCase): 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) f_to_be_deleted = w.csvfilename try: @@ -660,7 +638,7 @@ class ViewTest(TestCase): @patch('rowers.dataprep.create_engine') @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): self.c.login(username='john',password='koeinsloot') diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index f67f2da08debcff3873da3041d8086f2cfd88f25..e380513e867d867d4c7b9150dbb1e0a971ee706e 100644 GIT binary patch literal 4000 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\d+)') testresult = tester.match(s) @@ -371,7 +371,7 @@ def workout_import_view(request, source='c2'): r = getrequestrower(request) try: integration = importsources[source](request.user) - except KeyError: + except KeyError: # pragma: no cover messages.error(request,"This integration is not supported") url = reverse('workouts_view') return HttpResponseRedirect(url) @@ -428,7 +428,7 @@ def workout_import_view(request, source='c2'): startdate=startdate, enddate=enddate ) - except NoTokenError: + except NoTokenError: # pragma: no cover messages.error(request,"You must first make the connection to {source}".format( source=source )) @@ -504,7 +504,7 @@ def rower_process_stravacallback(request): try: 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" messages.error(request, message) url = reverse('rower_exportsettings_view') @@ -526,7 +526,7 @@ def rower_process_stravacallback(request): r.save() try: _ = 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') url = reverse('rower_exportsettings_view') return HttpResponseRedirect(url) @@ -540,7 +540,7 @@ def rower_process_stravacallback(request): messages.error(request, message) url = reverse('rower_exportsettings_view') - return HttpResponseRedirect(url) + return HttpResponseRedirect(url) # pragma: no cover # Process SportTracks callback @@ -653,7 +653,7 @@ def rower_process_testcallback(request): # pragma: no cover message="This functionality requires a Pro plan or higher", redirect_field_name=None) @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) if r.user != request.user: messages.error( @@ -728,21 +728,21 @@ def workout_rojaboimport_view(request, message="", userid=0): item['warm_up']['steps'], warmup=True ) - except KeyError: + except KeyError: # pragma: no cover pass try: steps = steps + rojabo_stuff.stepsconvert( item['primary']['steps'], startid=len(steps) ) - except KeyError: + except KeyError: # pragma: no cover pass try: steps = steps + rojabo_stuff.stepsconvert( item['cool_down']['steps'], cooldown=True, startid=len(steps)) - except KeyError: + except KeyError: # pragma: no cover pass if steps: @@ -755,7 +755,7 @@ def workout_rojaboimport_view(request, message="", userid=0): ps.save() messages.info(request,'Saved planned session {id}'.format(id=ps.id)) - except KeyError: + except KeyError: # pragma: no cover pass except KeyError: pass @@ -1019,7 +1019,7 @@ def garmin_details_view(request): def workout_getimportview(request, externalid, source='c2', do_async=True): try: integration = importsources[source](request.user) - except (TypeError, NotImplementedError, KeyError): + except (TypeError, NotImplementedError, KeyError): # pragma: no cover return reverse("workouts_view") if 'startdate' in request.session and source == 'nk': # pragma: no cover 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 @login_required() -def workout_getsporttracksworkout_all(request): +def workout_getsporttracksworkout_all(request): # pragma: no cover st_integration = importsources['sporttracks'](request.user) try: _ = st_integration.get_workouts() diff --git a/rowers/views/userviews.py b/rowers/views/userviews.py index dbf08d5e..b03e5eb7 100644 --- a/rowers/views/userviews.py +++ b/rowers/views/userviews.py @@ -258,7 +258,7 @@ def user_messages(request,userid=0): 'rower':r}) @login_required() -def user_message_delete(request,id=0): +def user_message_delete(request,id=0): # pragma: no cover try: msg = UserMessage.objects.get(id=id) except UserMessage.DoesNotExist: @@ -278,7 +278,7 @@ def user_message_delete(request,id=0): @login_required() -def user_message_markread(request,id=0): +def user_message_markread(request,id=0): # pragma: no cover try: msg = UserMessage.objects.get(id=id) 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_coachee(r.user): doset = False - if not doset: + if not doset: # pragma: no cover before = getattr(r, attr) if before == value: doset = True if doset: setattr(r, attr, value) - else: + else: # pragma: no cover if user_is_not_basic(r.user): messages.error( 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) firsttime = False - if not r.ftpset: + if not r.ftpset: # pragma: no cover r.ftpset = True r.save() firsttime = True @@ -623,7 +623,7 @@ def rower_simpleprefs_view(request, userid=0): form = SimpleRowerPowerForm(instance=r) - if request.method == 'POST': + if request.method == 'POST': # pragma: no cover form = SimpleRowerPowerForm(request.POST, instance=r) if form.is_valid(): form.save(commit=True) @@ -714,7 +714,7 @@ def rower_prefs_view(request, userid=0, message=""): r.pw_ut1, r.pw_at, r.pw_tr, r.pw_an])/r.ftp - if cogganzones: + if cogganzones: # pragma: no cover powerfrac = np.array([55.,75.,90.,105.,120.]) powerzones = ['Rest', 'Active Recovery', diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 83fbcadd..b22a7da4 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -3299,7 +3299,6 @@ def instroke_chart_interactive(request, id=0, analysis=0, userid=0): rowdata = rrdata(csvfile=w.csvfilename) instrokemetrics = rowdata.get_instroke_columns() - form = InstrokeForm(choices=instrokemetrics) breadcrumbs = [ @@ -3513,6 +3512,7 @@ def instroke_chart_interactive(request, id=0, analysis=0, userid=0): name=name,notes=notes, ) + # change to range spm_min to spm_max vals, units, typ = rowdata.updateinterval_range( ' Cadence (stokes/min)', spm_min, spm_max, From 122ca24d7bdf0bddc860bac0ef83ca62991c3a5a Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 1 Mar 2024 17:37:19 +0100 Subject: [PATCH 09/13] d --- rowsandall_app/settings_dev.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rowsandall_app/settings_dev.py b/rowsandall_app/settings_dev.py index 5bcfef4a..1adb217b 100644 --- a/rowsandall_app/settings_dev.py +++ b/rowsandall_app/settings_dev.py @@ -62,7 +62,7 @@ DEBUG = True 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 += ["sslserver"] From aa4e713c8e788e7ac037b01bffd308546efb812d Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 12 Mar 2024 22:09:23 +0100 Subject: [PATCH 10/13] not working --- rowers/rowing-workout-metrics.proto | 49 +++++++++++++++++++++++++++++ rowers/rowing_workout_metrics.proto | 49 +++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 rowers/rowing-workout-metrics.proto create mode 100644 rowers/rowing_workout_metrics.proto diff --git a/rowers/rowing-workout-metrics.proto b/rowers/rowing-workout-metrics.proto new file mode 100644 index 00000000..2992b397 --- /dev/null +++ b/rowers/rowing-workout-metrics.proto @@ -0,0 +1,49 @@ +syntax = "proto3"; + +package rowing_workout_metrics; +option go_package = "./rowing-workout-metrics"; + +// OTW-metrics service definition +service Metrics { + rpc CalcMetrics (WorkoutMetricsRequest) returns (WorkoutMetricsResponse); +}; + +// GetCP service definition +service CP { + rpc GetCP (CPRequest) returns (CPResponse); +} + +// WorkoutMetricsRequest message +message WorkoutMetricsRequest { + string filename = 1; + string sex = 2; + double ftp = 3; + double hrftp = 4; + double hrmax = 5; + double hrmin = 6; + +}; + +// WorkoutMetricsReponse message +message WorkoutMetricsResponse { + double tss = 1; + double normp = 2; + double trimp = 3; + double hrtss = 4; + double normv = 5; + double normw = 6; +}; + +// CPRequest message +message CPRequest { + string filename = 1; + string filetype = 2; // CSV or Parquet + repeated double tarr = 3; +} + +message CPResponse { + repeated double delta = 1; + repeated double power = 2; + double avgpower = 3; + +} \ No newline at end of file diff --git a/rowers/rowing_workout_metrics.proto b/rowers/rowing_workout_metrics.proto new file mode 100644 index 00000000..2992b397 --- /dev/null +++ b/rowers/rowing_workout_metrics.proto @@ -0,0 +1,49 @@ +syntax = "proto3"; + +package rowing_workout_metrics; +option go_package = "./rowing-workout-metrics"; + +// OTW-metrics service definition +service Metrics { + rpc CalcMetrics (WorkoutMetricsRequest) returns (WorkoutMetricsResponse); +}; + +// GetCP service definition +service CP { + rpc GetCP (CPRequest) returns (CPResponse); +} + +// WorkoutMetricsRequest message +message WorkoutMetricsRequest { + string filename = 1; + string sex = 2; + double ftp = 3; + double hrftp = 4; + double hrmax = 5; + double hrmin = 6; + +}; + +// WorkoutMetricsReponse message +message WorkoutMetricsResponse { + double tss = 1; + double normp = 2; + double trimp = 3; + double hrtss = 4; + double normv = 5; + double normw = 6; +}; + +// CPRequest message +message CPRequest { + string filename = 1; + string filetype = 2; // CSV or Parquet + repeated double tarr = 3; +} + +message CPResponse { + repeated double delta = 1; + repeated double power = 2; + double avgpower = 3; + +} \ No newline at end of file From 4b83a251db8de8f31cc0ae351b651d719f9b4a05 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 12 Mar 2024 22:09:33 +0100 Subject: [PATCH 11/13] not working 2 --- rowers/dataprep.py | 45 +++++ rowers/datautils.py | 1 - rowers/otw_power_calculator_pb2.py | 190 ++----------------- rowers/otw_power_calculator_pb2_grpc.py | 62 ++++-- rowers/rowing_workout_metrics_pb2.py | 221 +++------------------- rowers/rowing_workout_metrics_pb2_grpc.py | 122 ++++++++++-- rowers/tasks.py | 1 - rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4000 bytes 8 files changed, 233 insertions(+), 409 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index f206a457..d05ec634 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -31,6 +31,11 @@ from zipfile import BadZipFile import zipfile import os 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 ( KinoMapParser, @@ -499,9 +504,48 @@ def calculate_goldmedalstandard(rower, workout, recurrance=True): def setcp(workout, background=False, recurrance=True): filename = 'media/cpdata_{id}.parquet.gz'.format(id=workout.id) + 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') + strokesdf = getsmallrowdata_db( ['power', 'workoutid', 'time'], ids=[workout.id]) + protos = grpc.protos("rowing_workout_metrics.proto") + services = grpc.services("rowing_workout_metrics.proto") + req = protos.CPRequest( + filename = csvfile, + filetype = "CSV", + tarr = datautils.getlogarr(1.05*strokesdf['time'].max()) + ) + response = services.GetCP(req, "localhost:50052", insecure=True) + delta = response.delta + cpvalues = response.power + powermean = response.avgpower + + df = pd.DataFrame({ + 'delta': delta, + 'cp': cpvalues, + 'id': workout.id, + }) + df.to_parquet(filename, engine='fastparquet', compression='GZIP') + if recurrance: + goldmedalstandard, goldmedalduration = calculate_goldmedalstandard( + workout.user, workout) + workout.goldmedalstandard = goldmedalstandard + workout.goldmedalduration = goldmedalduration + workout.save() + return df, delta, cpvalues + + + try: if strokesdf['power'].std() == 0: return pd.DataFrame(), pd.Series(dtype='float'), pd.Series(dtype='float') @@ -682,6 +726,7 @@ def fetchcp_new(rower, workouts): for workout in workouts: cpfile = 'media/cpdata_{id}.parquet.gz'.format(id=workout.id) try: + df, delta, cpvalues = setcp(workout) df = pd.read_parquet(cpfile) df['workout'] = str(workout) df['url'] = workout.url() diff --git a/rowers/datautils.py b/rowers/datautils.py index 029de2f3..fcebd334 100644 --- a/rowers/datautils.py +++ b/rowers/datautils.py @@ -1,4 +1,3 @@ - import pandas as pd import numpy as np from scipy.interpolate import griddata diff --git a/rowers/otw_power_calculator_pb2.py b/rowers/otw_power_calculator_pb2.py index f0c2091c..d099031d 100644 --- a/rowers/otw_power_calculator_pb2.py +++ b/rowers/otw_power_calculator_pb2.py @@ -1,185 +1,31 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: otw-power-calculator.proto - -from google.protobuf import symbol_database as _symbol_database -from google.protobuf import reflection as _reflection -from google.protobuf import message as _message +# Protobuf Python Version: 4.25.1 +"""Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor -import sys -_b = sys.version_info[0] < 3 and ( - lambda x: x) or (lambda x: x.encode('latin1')) +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder # @@protoc_insertion_point(imports) _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( - 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 +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') +_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) diff --git a/rowers/otw_power_calculator_pb2_grpc.py b/rowers/otw_power_calculator_pb2_grpc.py index f6d10b5a..c3a58814 100644 --- a/rowers/otw_power_calculator_pb2_grpc.py +++ b/rowers/otw_power_calculator_pb2_grpc.py @@ -1,4 +1,5 @@ # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" import grpc import rowers.otw_power_calculator_pb2 as otw__power__calculator__pb2 @@ -12,36 +13,57 @@ class PowerStub(object): """Constructor. Args: - channel: A grpc.Channel. + channel: A grpc.Channel. """ self.CalcPower = channel.unary_unary( - '/otw_power_calculator.Power/CalcPower', - request_serializer=otw__power__calculator__pb2.WorkoutPowerRequest.SerializeToString, - response_deserializer=otw__power__calculator__pb2.CalculationResult.FromString, - ) + '/otw_power_calculator.Power/CalcPower', + request_serializer=otw__power__calculator__pb2.WorkoutPowerRequest.SerializeToString, + response_deserializer=otw__power__calculator__pb2.CalculationResult.FromString, + ) class PowerServicer(object): """Power service definition """ - def CalcPower(self, request, context): # pragma: no cover - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) # pragma: no cover - context.set_details('Method not implemented!') # pragma: no cover - raise NotImplementedError( - 'Method not implemented!') # pragma: no cover + def CalcPower(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_PowerServicer_to_server(servicer, server): # pragma: no cover +def add_PowerServicer_to_server(servicer, server): rpc_method_handlers = { - 'CalcPower': grpc.unary_unary_rpc_method_handler( - servicer.CalcPower, - request_deserializer=otw__power__calculator__pb2.WorkoutPowerRequest.FromString, - response_serializer=otw__power__calculator__pb2.CalculationResult.SerializeToString, - ), - } # pragma: no cover + 'CalcPower': grpc.unary_unary_rpc_method_handler( + servicer.CalcPower, + request_deserializer=otw__power__calculator__pb2.WorkoutPowerRequest.FromString, + response_serializer=otw__power__calculator__pb2.CalculationResult.SerializeToString, + ), + } 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,)) + + + # 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) diff --git a/rowers/rowing_workout_metrics_pb2.py b/rowers/rowing_workout_metrics_pb2.py index d6422d38..6a4554b2 100644 --- a/rowers/rowing_workout_metrics_pb2.py +++ b/rowers/rowing_workout_metrics_pb2.py @@ -1,210 +1,37 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: rowing-workout-metrics.proto - -from google.protobuf import symbol_database as _symbol_database -from google.protobuf import reflection as _reflection -from google.protobuf import message as _message +# Protobuf Python Version: 4.25.1 +"""Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor -import sys -_b = sys.version_info[0] < 3 and ( - lambda x: x) or (lambda x: x.encode('latin1')) +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder # @@protoc_insertion_point(imports) _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( - 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 +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\x32w\n\x07Metrics\x12l\n\x0b\x43\x61lcMetrics\x12-.rowing_workout_metrics.WorkoutMetricsRequest\x1a..rowing_workout_metrics.WorkoutMetricsResponse2T\n\x02\x43P\x12N\n\x05GetCP\x12!.rowing_workout_metrics.CPRequest\x1a\".rowing_workout_metrics.CPResponseB\x1aZ\x18./rowing-workout-metricsb\x06proto3') +_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=409 + _globals['_METRICS']._serialized_end=528 + _globals['_CP']._serialized_start=530 + _globals['_CP']._serialized_end=614 # @@protoc_insertion_point(module_scope) diff --git a/rowers/rowing_workout_metrics_pb2_grpc.py b/rowers/rowing_workout_metrics_pb2_grpc.py index 30aac7b5..2f5a91d7 100644 --- a/rowers/rowing_workout_metrics_pb2_grpc.py +++ b/rowers/rowing_workout_metrics_pb2_grpc.py @@ -1,4 +1,5 @@ # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" import grpc import rowers.rowing_workout_metrics_pb2 as rowing__workout__metrics__pb2 @@ -12,13 +13,13 @@ class MetricsStub(object): """Constructor. Args: - channel: A grpc.Channel. + channel: A grpc.Channel. """ self.CalcMetrics = channel.unary_unary( - '/rowing_workout_metrics.Metrics/CalcMetrics', - request_serializer=rowing__workout__metrics__pb2.WorkoutMetricsRequest.SerializeToString, - response_deserializer=rowing__workout__metrics__pb2.WorkoutMetricsResponse.FromString, - ) + '/rowing_workout_metrics.Metrics/CalcMetrics', + request_serializer=rowing__workout__metrics__pb2.WorkoutMetricsRequest.SerializeToString, + response_deserializer=rowing__workout__metrics__pb2.WorkoutMetricsResponse.FromString, + ) class MetricsServicer(object): @@ -26,22 +27,107 @@ class MetricsServicer(object): """ def CalcMetrics(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) # pragma: no cover - context.set_details('Method not implemented!') # pragma: no cover - raise NotImplementedError( - 'Method not implemented!') # pragma: no cover + """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 = { - 'CalcMetrics': grpc.unary_unary_rpc_method_handler( - servicer.CalcMetrics, - request_deserializer=rowing__workout__metrics__pb2.WorkoutMetricsRequest.FromString, - response_serializer=rowing__workout__metrics__pb2.WorkoutMetricsResponse.SerializeToString, - ), + 'CalcMetrics': grpc.unary_unary_rpc_method_handler( + servicer.CalcMetrics, + request_deserializer=rowing__workout__metrics__pb2.WorkoutMetricsRequest.FromString, + response_serializer=rowing__workout__metrics__pb2.WorkoutMetricsResponse.SerializeToString, + ), } 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,)) + + + # 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) + + +class CPStub(object): + """GetCP service definition + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.GetCP = channel.unary_unary( + '/rowing_workout_metrics.CP/GetCP', + request_serializer=rowing__workout__metrics__pb2.CPRequest.SerializeToString, + response_deserializer=rowing__workout__metrics__pb2.CPResponse.FromString, + ) + + +class CPServicer(object): + """GetCP service definition + """ + + 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_CPServicer_to_server(servicer, server): + rpc_method_handlers = { + '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( + 'rowing_workout_metrics.CP', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class CP(object): + """GetCP service definition + """ + + @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.CP/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) diff --git a/rowers/tasks.py b/rowers/tasks.py index 388af488..6572a1dd 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -1477,7 +1477,6 @@ def handle_calctrimp(id, csvfile = csvfilename+'.gz' else: # pragma: no cover return 0 - csvfile = os.path.abspath(csvfile) with grpc.insecure_channel( diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index e380513e867d867d4c7b9150dbb1e0a971ee706e..41219d8855b45ea65b83040560a6a760f9deb700 100644 GIT binary patch literal 4000 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 GfB^s+y%|XW literal 4000 zcmV;R4`1*fiwFpRxZ!02|8!+@bYx+4VJ>uIcmVC4NpBoC7J%>m6@m}RVG#CP__!#F zf^35^U}u8F$mBMtNENwfq?VvAFZuUFc000ctB^ctieMG!gQ2djt?R2tzMV&3zPUI* zc-3ET*O!|w9;1QB2VWjPI(mNHtvBn<**E=W=&!%+hVIvH+aIKzH`^~BzZizA!`15b z>(}P2yS`X&%*o}&Dt);&o2B;fu%r9A5O-kN01uub-SA6QKJH^9wJzLyU+zT5i`e8-64Xm#`H-40(RQR~k7fAs0@f9!_->#K|1t2z9Bm43<1 z<|mu4`)=6&&|g1WZ(a`lzlU2tBAw>v?)+tcj5b|l|L40atnRqPt*@VTZ`KztFaA$A zAnuMExZ@JHzJ9XVy{g0S$Gf+pPh@Yl+dVO}5Zml0)T%I03 z>ozaD^F;Bzt+)5?PX7De-S4(sU9LC7?ald%^bY)mcX7Ad`$vB8N{F%JugUI!2NJkV zf&51l_9X5`@ZJN4JD%@uhnvxB`_W~e|M1ISo(b+8!#_M@I_JN-VMt;Vk5>D?KD_2C zUG&4BclRyk7vV0vTIviYNxN7Db!3C{z@8s;Mcm0%#63Yoa6!SHlHOF1d$>P$s!QIj zRK(pGVpNUtX^7y0+~Z=nJ6jR=nB?7x*Km&|<(*+M+cdn!v$Fv;oz8>xZ#s{<-dG}&uCfq9< z=PR<~giQn+HQa-gwBMH`@1Y{@&Nv{@=yg$w>hsHycZ7-*JBfx_=cYv{OP(Jc7Z*gHDkAR;I-L^-oi91hTWF*G zB;qG%S)>(ucPw@)h~;EE`6& znkX&#q8av${6yrP0jne*U^X&e+dN-Ul-360)2pbm=OY;3G`zXb^MWRcyvm+ufT9tb z1&#JK$-G#To-L4uJmPG0`xd1gC-00G44Nn{6Bf1SmqflIdp=ueY*q6-GnzOP^4RA2 zyUBCTy=6escyiKy5_f^guEDF2r8KuDjKnAPePszTNKqiAGoN7=bOw=Lf#pSC@Ptcwy5{E$^10rce%H!7HGC? zMdgFYWAovSk-(}8}D>cS{q}@oSY?*ucyX_ zbMCG4h?>z}5N0Fu&7L189}NYFK}9|`-G2TN8=>4SMHQtLhNj!kU*^S{kaT;M%-gbA z*Rj>oPD0)rVO_*Vq%8U7!<&G-O|T7mtIE0-*T~*s~=H4!e zd`0#=Swjw4HO~vpCij+G{ro6-Z_pyCMtes^?fC_fcT^Gi+&O_*)8F2CE;`RIiF`%6 zebUxAqH5JL7q~@f7eHPNfT+5hyT~m{JO9z1y%EuMLiEtc-W~~T#!9qQGDJ8W06slljGCd zVrN3WHXmN7hrD2}rB$tI;iYK({E}q8CJj&6cp=pI@Vs*+{q~ER=PR=3ll*cdRgI1J zu2odtlYB){T8RdjKsDM2Hyho)d2dI^%ORlgN*|t&MOPtTl*~&-&d z$-JwGyf+908mlf~n2pRg^5f*AacQvEjP`*-QG0$#qkX7{Jd&{%qbBP*aBMPvPx7%M z@?sz;M|)dzrNTvdaw;0_(Hk$K>Xs5*(bY+p(Dz})V7m0*H41p8Y`&s;UaixQh!hMR1tcgTV#1u zp$~30QePQ*cJ-i7cA5YZUgQqA>< zWy@`rgq|xxAG3xZby8n;)xw5;8uYmns$*4V7N!}iY4{63FL}UMg=`Upibm^~q~=v* z+;K4EF{m_rbj_Q0U*j&+gFcPj;BtRF4SHX+5AK69GNBoFqv)*xAl6BJu(Lsr&A1yy z?-P1m6OEkBRTMop^pl{ECR(LShrksz?x=b5ra+&R^@zH%8>H;`Z&E*d^kSUXqNlR0 z(v~FkP!alIL`c;D8!1_YzM-GDOULKVnAVA?j5|sSg3P3S4z^|KN6}luUe`V+FN`sp z&PmO#A4l&E1qr$V-$T*DdTeUm6zDMtu(IF|Dd226;$ln797WFtovv#ZQY^Y~>7oXF zstCO^U`dye%fPc)KkS^o2F?kOJ^LtH{J@XG<>#Y*SVoqb2kP0oM9=btFLD%+fId=nl}l0HUb!RQt!fS z27GJ@TBGQlv8Y?i5ZT9~jnk;v^^>5Fh9X*>h9Am?Z@78$ra*5g_s`OK^Ek$)=FNY= zw_-q)g;y3~Hu3t}lCdia+S;VCA;qXdPt@l6`J)#wQMqt2TBf3ryXM2620feTkaXxR z&t`eE=ExmKziXN3Rcf9U+gv*HcjWF`CZgBP^-gA!pw-ecN6?2|%S6yEd4twVGw$YY z*N5CP(TbqTk!wS%zPT^@P!al&J80>;YS>sde8X+Pp8`FiVHT&7dV#Xb)$@YXJg5l0 z-L+Owsd-Ykjnb0p>up8oy&75mj9^B--Z9o4+HMz41&|6|{BWW|OMX)Vxvj$xgElby+$NT^sP{o|~7@vk}Tp z8V!1yPYFhAqco1*84EelQiGmD8}R4v$h9${CsGw&IU8qFY1^`NM$roy*81?BgErSM z0KFI@t(xbdW#rDi@G9h%iI}$O&YQ=mu64ONs<&t*$6papFw zK`*(X3hAWYO3}z2G&OG$^ue%T&`G_xqNy59&6@;0CF72itrfg;&5=8Imrl$r6G6MC zt@nO5b3L`7?I`+OWX+%o-}vC0UB3YI(MYoHbW$JvZ0ve!YTg)nCL`AB!Z!gzv+Eat zUUK0T=+I+P^m(bEHsDWyJ{Vxs^}`Q2d4DG8sd@9p(C3w`0fP=b$JwB7>(`H?XA^{U zk-PMwl+^HZCyIuC0`$&!wo&EM2@XrTbQXnPDncLgnzo>mdXGg1eBVkira&)boW-EZ z(g~qxrR{^zx0<_g^pdM@f+{07#G;k94;t`WFP(ApA+Kqxi`+#hy4cyGH2hk!bOd%Q zSp#~NBR8VV#-#(TZ{8^S-AdNFb?zdS4cbDR>nB0)bM=i9J^R_LZrSGgQS^BwYerIe z^P=@dt8b|F&6@;0?-nsv8Fx`)TitTz-n@+7<-#i^dKZgIeQT6D4f>c1uXJ{O4A7dV z&D@)p(C=2VRz~h(#MT*m0qCtsGrej76ar{Ha_@`YPlf*N);~5Jt)6yQHy^&g9Qy6? zC+EZG-<|gt{bo4$bhqi#gKjv0H@^R^KNWCY_!T}m8$Lf;?GJdgKhqz-F@L`KwBCPz z^v!kRczOMH-|xHh_FP}S-gcYQ?)-fBw9?1*=Ipc^x^&)d|8^xR?~XhA>gDj_^7__m z^3~PV`TC?A(oes6F>cQN(aF+xU%yAI}2`q{&=_b zCT;feZol1zn@9V>`H!Bbn?C-rKkvJ3pE%@CZy(*Q`#O5sZO)#2d;Hz^na%Fc{q`Ty zTm1dYi(mV6X8@l8?EgPn-TGqx Date: Wed, 13 Mar 2024 18:57:18 +0100 Subject: [PATCH 12/13] working --- rowers/dataprep.py | 111 +++++++++++----------- rowers/datautils.py | 16 ++-- rowers/models.py | 21 ++-- rowers/rowing-workout-metrics.proto | 49 ---------- rowers/rowing_workout_metrics.proto | 49 ---------- rowers/rowing_workout_metrics_pb2.py | 8 +- rowers/rowing_workout_metrics_pb2_grpc.py | 65 ++++--------- rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4002 bytes rowers/utils.py | 6 +- 9 files changed, 97 insertions(+), 228 deletions(-) delete mode 100644 rowers/rowing-workout-metrics.proto delete mode 100644 rowers/rowing_workout_metrics.proto diff --git a/rowers/dataprep.py b/rowers/dataprep.py index d05ec634..26684810 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -502,8 +502,28 @@ def calculate_goldmedalstandard(rower, workout, recurrance=True): def setcp(workout, background=False, recurrance=True): - filename = 'media/cpdata_{id}.parquet.gz'.format(id=workout.id) + try: + 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( + ['power', 'workoutid', 'time'], ids=[workout.id]) + + if strokesdf.empty: + return pd.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float') + + totaltime = strokesdf['time'].max() + maxt = totaltime/1000. + logarr = datautils.getlogarr(maxt) + csvfilename = workout.csvfilename # check what the real file name is if os.path.exists(csvfilename): @@ -514,80 +534,53 @@ def setcp(workout, background=False, recurrance=True): 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) - strokesdf = getsmallrowdata_db( - ['power', 'workoutid', 'time'], ids=[workout.id]) - protos = grpc.protos("rowing_workout_metrics.proto") - services = grpc.services("rowing_workout_metrics.proto") - req = protos.CPRequest( - filename = csvfile, - filetype = "CSV", - tarr = datautils.getlogarr(1.05*strokesdf['time'].max()) - ) - response = services.GetCP(req, "localhost:50052", insecure=True) - delta = response.delta - cpvalues = response.power + 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({ 'delta': delta, 'cp': cpvalues, 'id': workout.id, }) + df.to_parquet(filename, engine='fastparquet', compression='GZIP') + if recurrance: goldmedalstandard, goldmedalduration = calculate_goldmedalstandard( workout.user, workout) workout.goldmedalstandard = goldmedalstandard workout.goldmedalduration = goldmedalduration workout.save() + return df, delta, cpvalues - - - try: - 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') - - if not strokesdf.empty: - totaltime = strokesdf['time'].max() - try: - 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) - dfgrouped = strokesdf.groupby(['workoutid']) - delta, cpvalues, avgpower = datautils.getcp(dfgrouped, logarr) - - df = pd.DataFrame({ - 'delta': delta, - 'cp': cpvalues, - 'id': workout.id, - }) - df.to_parquet(filename, engine='fastparquet', - compression='GZIP') - if recurrance: - goldmedalstandard, goldmedalduration = calculate_goldmedalstandard( - workout.user, workout) - workout.goldmedalstandard = goldmedalstandard - workout.goldmedalduration = goldmedalduration - workout.save() - 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): @@ -723,6 +716,7 @@ def join_workouts(r, ids, title='Joined Workout', def fetchcp_new(rower, workouts): data = [] + for workout in workouts: cpfile = 'media/cpdata_{id}.parquet.gz'.format(id=workout.id) try: @@ -743,12 +737,15 @@ def fetchcp_new(rower, workouts): if len(data) > 1: df = pd.concat(data, axis=0) + try: df = df[df['cp'] == df.groupby(['delta'])['cp'].transform('max')] except KeyError: # pragma: no cover 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[df['cp']>20] + return df['delta'], df['cp'], 0, df['workout'], df['url'] diff --git a/rowers/datautils.py b/rowers/datautils.py index fcebd334..cb17a357 100644 --- a/rowers/datautils.py +++ b/rowers/datautils.py @@ -123,18 +123,18 @@ def cpfit(powerdf, fraclimit=0.0001, nmax=1000): def getlogarr(maxt): - maxlog10 = np.log10(maxt-5) + dtmin = 10 + maxlog10 = np.log10(maxt) # print(maxlog10,round(maxlog10)) - aantal = 10*round(maxlog10) + aantal = 40 logarr = np.arange(aantal+1)/10. + vs = 10.**logarr + res = [] - for la in logarr: - try: - v = 5+int(10.**(la)) - except ValueError: # pragma: no cover - v = 0 - res.append(v) + for v in vs: + if v > dtmin and vuIcmVC4TW=dT7J%RLD~vp}4~tUg!o!Q} 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 Iio}2c00)vf`2YX_ literal 4000 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 GfB^s+y%|XW diff --git a/rowers/utils.py b/rowers/utils.py index bf71456b..34f808ea 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -347,9 +347,9 @@ def isbreakthrough(delta, cpvalues, p0, p1, p2, p3, ratio): pwr *= ratio - delta = delta.values.astype(int) - cpvalues = cpvalues.values.astype(int) - pwr = pwr.astype(int) + delta = delta.astype(int, errors='ignore').values + cpvalues = cpvalues.astype(int, errors='ignore').values + pwr = pwr.astype(int, errors='ignore').values res = np.sum(cpvalues > pwr+1) res2 = np.sum(cpvalues > pwr2+1) From 289b1a7b036e67916615a43a89e809f1cc78e51e Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 26 Mar 2024 08:31:43 +0100 Subject: [PATCH 13/13] hotfix --- rowers/dataprep.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 26684810..57c9d176 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -1053,14 +1053,22 @@ def checkbreakthrough(w, r): cpdf, delta, cpvalues = setcp(w) if not cpdf.empty: if workouttype in otwtypes: - res, btvalues, res2 = utils.isbreakthrough( - delta, cpvalues, r.p0, r.p1, r.p2, r.p3, r.cpratio) - _ = update_rolling_cp(r, otwtypes, 'water') + try: + res, btvalues, res2 = utils.isbreakthrough( + delta, cpvalues, r.p0, r.p1, r.p2, r.p3, r.cpratio) + _ = update_rolling_cp(r, otwtypes, 'water') + except ValueError: + res = 0 + res2 = 0 elif workouttype in otetypes: - res, btvalues, res2 = utils.isbreakthrough( - delta, cpvalues, r.ep0, r.ep1, r.ep2, r.ep3, r.ecpratio) - _ = update_rolling_cp(r, otetypes, 'erg') + try: + res, btvalues, res2 = utils.isbreakthrough( + delta, cpvalues, r.ep0, r.ep1, r.ep2, r.ep3, r.ecpratio) + _ = update_rolling_cp(r, otetypes, 'erg') + except ValueError: + res = 0 + res2 = 0 else: # pragma: no cover res = 0 res2 = 0