From eb126856a76a5344975ae19572eee30a22c7a31d Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 28 Feb 2024 09:34:14 +0100 Subject: [PATCH 1/7] 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 2/7] 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 3/7] 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 4/7] 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 5/7] 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 6/7] 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 7/7] 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),