diff --git a/rowers/courses.py b/rowers/courses.py index 7fa3d8f9..1c2afa85 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 ) @@ -22,6 +23,7 @@ import time from django.db import IntegrityError import uuid from django.conf import settings +import math import geocoder @@ -120,6 +122,50 @@ xmlns_uris_dict = {'gx': 'http://www.google.com/kml/ext/2.2', '': "http://www.opengis.net/kml/2.2"} +def get_polar_angle(point, reference_point): + """ + Calculate the polar angle of a point with respect to a reference point. + """ + try: + delta_x = point.longitude - reference_point.longitude + delta_y = point.latitude - reference_point.latitude + except AttributeError: + delta_x = point['longitude'] - reference_point.longitude + delta_y = point['latitude'] - reference_point.latitude + + + return math.atan2(delta_y, delta_x) + +class Coordinate: + def __init__(self, latitude, longitude): + self.latitude = latitude + self.longitude = longitude + +def get_coordinates_centerpoint(coordinates): + try: + centroid_latitude = sum(coord.latitude for coord in coordinates) / len(coordinates) + centroid_longitude = sum(coord.longitude for coord in coordinates) / len(coordinates) + centroid = Coordinate(centroid_latitude, centroid_longitude) + except AttributeError: + centroid_latitude = sum(coord['latitude'] for coord in coordinates) / len(coordinates) + centroid_longitude = sum(coord['longitude'] for coord in coordinates) / len(coordinates) + centroid = Coordinate(centroid_latitude, centroid_longitude) + + return centroid + +def sort_coordinates_ccw(coordinates): + """ + Sort coordinates in counterclockwise order. + """ + # Find the reference point (centroid) + centroid = get_coordinates_centerpoint(coordinates) + + # Sort coordinates based on polar angle with respect to the centroid + sorted_coords = sorted(coordinates, key=lambda coord: get_polar_angle(coord, centroid)) + + return sorted_coords + + def crewnerdcourse(doc): courses = [] for course in doc: @@ -162,6 +208,8 @@ def get_polygons(polygonpms): 'latitude': float(coordinates[1]), }) + points = sort_coordinates_ccw(points) + polygons.append({ 'name': name, 'points': points @@ -169,34 +217,45 @@ def get_polygons(polygonpms): return polygons +def crewnerdify(polygons): + polygons[0].name = "Start" + polygons[len(polygons)-1].name = "Finish" -def coursetokml(course): - top = Element('kml') - for prefix, uri in xmlns_uris_dict.items(): - if prefix != '': - top.attrib['xmlns:' + prefix] = uri - else: - top.attrib['xmlns'] = uri + if len(polygons) > 2: + for i in range(1,len(polygons)-1): + polygons[i].name = 'WP{n}'.format(n=polygons[i].order_in_course) - document = SubElement(top, 'Document') - name = SubElement(document, 'name') - name.text = 'Courses.kml' - folder = SubElement(document, 'Folder') - foldername = SubElement(folder, 'name') - foldername.text = 'Courses' - folder2 = SubElement(folder, 'Folder') + return polygons + +def removewhitespace(s): + regels = s.split('\n') + regels = [regel for regel in regels if regel.strip()] + return "\n".join(regels) + +def getcoursefolder(course, document, cn=False): + folder2 = SubElement(document, 'Folder', id="{id}".format(id=course.id)) coursename = SubElement(folder2, 'name') coursename.text = course.name - open = SubElement(folder2, 'open') - open.text = '1' + openst = SubElement(folder2, 'open') + openst.text = '1' + coursedescription = SubElement(folder2, 'description') + if len(removewhitespace(course.notes)): + coursedescription.text = "Rowsandall.com\n" + removewhitespace(course.notes) + else: + coursedescription.text = "Rowsandall.com " + course.name polygons = GeoPolygon.objects.filter( course=course).order_by("order_in_course") + if cn: + polygons = crewnerdify(polygons) + for polygon in polygons: placemark = SubElement(folder2, 'Placemark') polygonname = SubElement(placemark, 'name') polygonname.text = polygon.name + polygonstyle = SubElement(placemark, 'styleUrl') + polygonstyle.text = "#default0" p = SubElement(placemark, 'Polygon') tessellate = SubElement(p, 'tessellate') tessellate.text = '1' @@ -206,6 +265,7 @@ def coursetokml(course): coordinates.text = '' points = GeoPoint.objects.filter( polygon=polygon).order_by("order_in_poly") + points = sort_coordinates_ccw(points) for point in points: coordinates.text += '{lon},{lat},0 '.format( lat=point.latitude, @@ -215,6 +275,87 @@ def coursetokml(course): lat=points[0].latitude, lon=points[0].longitude, ) + return folder2 + +def getstyle(document, id): + style = SubElement(document,'Style', id=id) + iconstyle = SubElement(style, 'IconStyle') + scale = SubElement(iconstyle, 'scale') + scale.text = "1.2" + linestyle = SubElement(style, 'LineStyle') + linestylecolor = SubElement(linestyle, 'color') + linestylecolor.text = "ff00ffff" + polystyle = SubElement(style, 'PolyStyle') + polystylecolor = SubElement(polystyle, 'color') + polystylecolor.text = "ff7fffff" + + return style + +def getstylemap(document, id): + stylemap = SubElement(document, 'StyleMap', id=id) + pair1 = SubElement(stylemap, 'Pair') + pair1key = SubElement(pair1, 'key') + pair1key.text = "normal" + pair1styleurl = SubElement(pair1, 'styleUrl') + pair1styleurl.text = "#default" + pair2 = SubElement(stylemap, 'Pair') + pair2key = SubElement(pair2, 'key') + pair2key.text = "highlight" + pair2styleurl = SubElement(pair2, 'styleUrl') + pair2styleurl.text = "#hl" + + return stylemap + +def coursestokml(courseids, cn=False): + courses = GeoCourse.objects.filter(id__in=courseids) + top = Element('kml') + for prefix, uri in xmlns_uris_dict.items(): + if prefix != '': + top.attrib['xmlns:' + prefix] = uri + else: + top.attrib['xmlns'] = uri + + document = SubElement(top, 'Document') + name = SubElement(document, 'name') + name.text = 'courses' + + opendoc = SubElement(document,'open') + opendoc.text = '1' + + style = getstyle(document, "default") + + stylemap = getstylemap(document, 'default0') + + stylehl = getstyle(document, "hl") + + for course in courses: + coursefolder = getcoursefolder(course, document, cn=cn) + + return prettify(top) + +def coursetokml(course,cn=False): + top = Element('kml') + for prefix, uri in xmlns_uris_dict.items(): + if prefix != '': + top.attrib['xmlns:' + prefix] = uri + else: + top.attrib['xmlns'] = uri + + document = SubElement(top, 'Document') + name = SubElement(document, 'name') + name.text = 'courses' + + opendoc = SubElement(document,'open') + opendoc.text = '1' + + style = getstyle(document, "default") + + stylemap = getstylemap(document, 'default0') + + stylehl = getstyle(document, "hl") + + coursefolder = getcoursefolder(course, document, cn=cn) + return prettify(top) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 35a7ec9f..57c9d176 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -31,6 +31,11 @@ from zipfile import BadZipFile import zipfile import os from rowers.models import strokedatafields +import grpc +import grpc.experimental +import rowers.rowing_workout_metrics_pb2 as metrics_pb2 +import rowers.rowing_workout_metrics_pb2_grpc as metrics_pb2_grpc +import traceback from rowingdata import ( KinoMapParser, @@ -120,10 +125,10 @@ from rowers.datautils import rpetotss def rscore_approx(row): if row['rscore'] > 0: return row['rscore'] - if row['rscore'] == 0: + if row['rscore'] == 0: # pragma: no cover if row['hrtss'] > 0: return row['hrtss'] - else: + else: # pragma: no cover dd = 3600*row['duration'].hour+60*row['duration'].minute+row['duration'].second dd = dd/3600 try: @@ -135,7 +140,7 @@ def rscore_approx(row): w = Workout.objects.get(id=row['id']) trimp, hrtss = workout_trimp(w) rscore, normp = workout_rscore(w) - if row['rpe'] and row['rpe'] > 0: + if row['rpe'] and row['rpe'] > 0: # pragma: no cover dd = 3600*row['duration'].hour+60*row['duration'].minute+row['duration'].second dd = dd/3600 return int(rpetotss[row['rpe']]*dd) @@ -143,7 +148,7 @@ def rscore_approx(row): minutes = row['duration'].hour*60+row['duration'].minute return minutes - return 0 + return 0 # pragma: no cover thetimezone = get_current_timezone() @@ -177,7 +182,7 @@ def workout_goldmedalstandard(workout, reset=False): if workout.workouttype in otwtypes: try: factor = 100./(100.-workout.user.otwslack) - except ZeroDivisionError: + except ZeroDivisionError: # pragma: no cover factor = 1. workout.user.otwslack = 0 workout.user.save() @@ -186,8 +191,8 @@ def workout_goldmedalstandard(workout, reset=False): workout.goldmedalseconds = goldmedalseconds workout.save() return goldmedalstandard, goldmedalseconds - else: - return 0, 0 + + return 0, 0 # pragma: no cover def check_marker(workout): @@ -497,53 +502,85 @@ def calculate_goldmedalstandard(rower, workout, recurrance=True): def setcp(workout, background=False, recurrance=True): - filename = 'media/cpdata_{id}.parquet.gz'.format(id=workout.id) + try: + filename = 'media/cpdata_{id}.parquet.gz'.format(id=workout.id) + df = pd.read_parquet(filename) + + if not df.empty: + # check dts + tarr = datautils.getlogarr(4000) + if df['delta'][0] in tarr: + return(df, df['delta'], df['cp']) + except: + pass strokesdf = getsmallrowdata_db( ['power', 'workoutid', 'time'], ids=[workout.id]) - try: - if strokesdf['power'].std() == 0: - return pd.DataFrame(), pd.Series(dtype='float'), pd.Series(dtype='float') - except (KeyError, TypeError): - return pd.DataFrame(), pd.Series(dtype='float'), pd.Series(dtype='float') - - if background: # pragma: no cover - _ = myqueue(queuelow, handle_setcp, strokesdf, filename, workout.id) + if strokesdf.empty: return pd.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float') + + totaltime = strokesdf['time'].max() + maxt = totaltime/1000. + logarr = datautils.getlogarr(maxt) + + csvfilename = workout.csvfilename + # check what the real file name is + if os.path.exists(csvfilename): + csvfile = csvfilename + elif os.path.exists(csvfilename+'.csv'): # pragma: no cover + csvfile = csvfilename+'.csv' + elif os.path.exists(csvfilename+'.gz'): # pragma: no cover + csvfile = csvfilename+'.gz' + else: # pragma: no cover + return pd.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float') + csvfile = os.path.abspath(csvfile) - if not strokesdf.empty: - totaltime = strokesdf['time'].max() + + with grpc.insecure_channel( + target='localhost:50052', + options=[('grpc.lb_policy_name', 'pick_first'), + ('grpc.enable_retries', 0), ('grpc.keepalive_timeout_ms', + 10000)] + ) as channel: try: - powermean = strokesdf['power'].mean() - except KeyError: # pragma: no cover - powermean = 0 + grpc.channel_ready_future(channel).result(timeout=10) + except grpc.FutureTimeoutError: # pragma: no cover + dologging('metrics.log','grpc channel time out in setcp') + return pd.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float') - if powermean != 0: - thesecs = totaltime - maxt = 1.05 * thesecs + stub = metrics_pb2_grpc.MetricsStub(channel) + req = metrics_pb2.CPRequest(filename = csvfile, filetype = "CSV", tarr = logarr) - if maxt > 0: - logarr = datautils.getlogarr(maxt) - dfgrouped = strokesdf.groupby(['workoutid']) - delta, cpvalues, avgpower = datautils.getcp(dfgrouped, logarr) + try: + response = stub.GetCP(req, timeout=60) + except Exception as e: + dologging('metrics.log', traceback.format_exc()) + return pd.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float') - df = pd.DataFrame({ - 'delta': delta, - 'cp': cpvalues, - 'id': workout.id, - }) - df.to_parquet(filename, engine='fastparquet', - compression='GZIP') - if recurrance: - goldmedalstandard, goldmedalduration = calculate_goldmedalstandard( - workout.user, workout) - workout.goldmedalstandard = goldmedalstandard - workout.goldmedalduration = goldmedalduration - workout.save() - return df, delta, cpvalues + delta = pd.Series(np.array(response.delta)) + cpvalues = pd.Series(np.array(response.power)) + powermean = response.avgpower - return pd.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float') + + + df = pd.DataFrame({ + 'delta': delta, + 'cp': cpvalues, + 'id': workout.id, + }) + + df.to_parquet(filename, engine='fastparquet', compression='GZIP') + + if recurrance: + goldmedalstandard, goldmedalduration = calculate_goldmedalstandard( + workout.user, workout) + workout.goldmedalstandard = goldmedalstandard + workout.goldmedalduration = goldmedalduration + workout.save() + + return df, delta, cpvalues + def update_wps(r, types, mode='water', asynchron=True): @@ -679,9 +716,11 @@ def join_workouts(r, ids, title='Joined Workout', def fetchcp_new(rower, workouts): data = [] + for workout in workouts: cpfile = 'media/cpdata_{id}.parquet.gz'.format(id=workout.id) try: + df, delta, cpvalues = setcp(workout) df = pd.read_parquet(cpfile) df['workout'] = str(workout) df['url'] = workout.url() @@ -698,12 +737,15 @@ def fetchcp_new(rower, workouts): if len(data) > 1: df = pd.concat(data, axis=0) + try: df = df[df['cp'] == df.groupby(['delta'])['cp'].transform('max')] except KeyError: # pragma: no cover return pd.Series(dtype='float'), pd.Series(dtype='float'), 0, pd.Series(dtype='float'), pd.Series(dtype='float') df = df.sort_values(['delta']).reset_index() + df = df[df['cp']>20] + return df['delta'], df['cp'], 0, df['workout'], df['url'] @@ -1011,14 +1053,22 @@ def checkbreakthrough(w, r): cpdf, delta, cpvalues = setcp(w) if not cpdf.empty: if workouttype in otwtypes: - res, btvalues, res2 = utils.isbreakthrough( - delta, cpvalues, r.p0, r.p1, r.p2, r.p3, r.cpratio) - _ = update_rolling_cp(r, otwtypes, 'water') + try: + res, btvalues, res2 = utils.isbreakthrough( + delta, cpvalues, r.p0, r.p1, r.p2, r.p3, r.cpratio) + _ = update_rolling_cp(r, otwtypes, 'water') + except ValueError: + res = 0 + res2 = 0 elif workouttype in otetypes: - res, btvalues, res2 = utils.isbreakthrough( - delta, cpvalues, r.ep0, r.ep1, r.ep2, r.ep3, r.ecpratio) - _ = update_rolling_cp(r, otetypes, 'erg') + try: + res, btvalues, res2 = utils.isbreakthrough( + delta, cpvalues, r.ep0, r.ep1, r.ep2, r.ep3, r.ecpratio) + _ = update_rolling_cp(r, otetypes, 'erg') + except ValueError: + res = 0 + res2 = 0 else: # pragma: no cover res = 0 res2 = 0 diff --git a/rowers/datautils.py b/rowers/datautils.py index 029de2f3..cb17a357 100644 --- a/rowers/datautils.py +++ b/rowers/datautils.py @@ -1,4 +1,3 @@ - import pandas as pd import numpy as np from scipy.interpolate import griddata @@ -124,18 +123,18 @@ def cpfit(powerdf, fraclimit=0.0001, nmax=1000): def getlogarr(maxt): - maxlog10 = np.log10(maxt-5) + dtmin = 10 + maxlog10 = np.log10(maxt) # print(maxlog10,round(maxlog10)) - aantal = 10*round(maxlog10) + aantal = 40 logarr = np.arange(aantal+1)/10. + vs = 10.**logarr + res = [] - for la in logarr: - try: - v = 5+int(10.**(la)) - except ValueError: # pragma: no cover - v = 0 - res.append(v) + for v in vs: + if v > dtmin and v= 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 @@ -1477,7 +1477,6 @@ def handle_calctrimp(id, csvfile = csvfilename+'.gz' else: # pragma: no cover return 0 - csvfile = os.path.abspath(csvfile) with grpc.insecure_channel( @@ -1562,7 +1561,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 +2036,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 +2068,7 @@ def handle_sendemail_breakthrough(workoutid, useremail, lastname = '' - if surname: + if surname: # pragma: no cover lastname = userlastname tablevalues = [ @@ -2122,7 +2121,7 @@ def handle_sendemail_hard(workoutid, useremail, ] lastname = '' - if surname: + if surname: # pragma: no cover lastname = userlastname # send email with attachment @@ -3414,7 +3413,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 +3423,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 +3438,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 +3774,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 +3879,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/templates/list_courses.html b/rowers/templates/list_courses.html index bc66697a..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

    @@ -106,6 +112,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/tests/statements.py b/rowers/tests/statements.py index 7fa4e1ed..3b88c2e2 100644 --- a/rowers/tests/statements.py +++ b/rowers/tests/statements.py @@ -198,7 +198,7 @@ class WorkoutFactory(factory.DjangoModelFactory): startdatetime = FuzzyDateTime(start_dt=one_month_ago, force_year=datetime.date.today().year) starttime = get_random_file(name=faker.word())['starttime'] workouttype='water' - date=timezone.now().date() + date=startdatetime.evaluate(2,None, False).date() duration=get_random_file(name=faker.word())['duration'] distance=get_random_file(name=faker.word())['totaldist'] csvfilename=get_random_file(name=faker.word())['filename'] diff --git a/rowers/tests/test_analysis.py b/rowers/tests/test_analysis.py index fe0997e4..8c7bc3e0 100644 --- a/rowers/tests/test_analysis.py +++ b/rowers/tests/test_analysis.py @@ -25,6 +25,11 @@ class ListWorkoutTest(TestCase): self.c = Client() self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) + for w in self.user_workouts: + if w.date != w.startdatetime.date(): + w.date = w.startdatetime.date() + w.save() + self.factory = RequestFactory() self.password = faker.word() self.u.set_password(self.password) @@ -79,6 +84,10 @@ class PlannedSessionTests(TestCase): self.c = Client() self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) + for w in self.user_workouts: + if w.date != w.startdatetime.date(): + w.date = w.startdatetime.date() + w.save() self.factory = RequestFactory() self.password = faker.word() self.u.set_password(self.password) @@ -130,6 +139,22 @@ class ForcecurveTest(TestCase): self.c = Client() self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) + for w in self.user_workouts: + if w.date != w.startdatetime.date(): + w.date = w.startdatetime.date() + w.save() + + result = get_random_file(filename='rowers/tests/testdata/quiske_in_stroke.csv') + + self.winstroke = WorkoutFactory(user=self.r, + csvfilename=result['filename'], + starttime=result['starttime'], + startdatetime=result['startdatetime'], + duration=result['duration'], + distance=result['totaldist'], + workouttype = 'water', + ) + self.factory = RequestFactory() self.password = faker.word() @@ -143,6 +168,7 @@ class ForcecurveTest(TestCase): except (IOError, FileNotFoundError,OSError): pass + @patch('rowers.dataprep.getsmallrowdata_db',side_effect = mocked_getempowerdata_db) def test_forcecurve_plot(self, mocked_getsmallrowdata_db): login = self.c.login(username=self.u.username, password = self.password) @@ -182,7 +208,62 @@ class ForcecurveTest(TestCase): response = self.c.get(url) self.assertEqual(response.status_code,200) + url = '/rowers/analysis/forcecurveanalysis/1/delete/' + response = self.c.get(url) + self.assertEqual(response.status_code, 200) + + form_data = {} + response = self.c.post(url, form_data, follow=True) + + expected_url = '/rowers/analysis/forcecurveanalysis/' + self.assertRedirects(response, expected_url=expected_url, status_code=302,target_status_code=200) + + def test_instroke_analysis(self): + login = self.c.login(username=self.u.username, password = self.password) + self.assertTrue(login) + + encoded1 = str(encoder.encode_hex(self.winstroke.id)) + url = '/rowers/workout/'+encoded1+'/instroke/interactive/' + + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + # saving + form_data = { + 'spm_min': 19, + 'spm_max': 25, + 'activeminutesmin': 0, + 'activeminutesmax': 10, + 'individual_curves': False, + 'notes': 'aap', + 'name': 'sdsd', + 'metric': 'boat accelerator curve', + '_save_as_new':'Save' + } + + form = InstrokeForm(form_data,choices=['boat accelerator curve', 'oar angle velocity curve']) + result = form.is_valid() + if not result: + print(form.errors) + self.assertTrue(form.is_valid()) + + response = self.c.post(url, form_data) + self.assertEqual(response.status_code,200) + + url = '/rowers/analysis/instrokeanalysis/' + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + url = '/rowers/analysis/instrokeanalysis/1/delete/' + response = self.c.get(url) + self.assertEqual(response.status_code, 200) + + form_data = {} + response = self.c.post(url, form_data, follow=True) + + expected_url = '/rowers/analysis/instrokeanalysis/' + self.assertRedirects(response, expected_url=expected_url, status_code=302,target_status_code=200) @@ -198,6 +279,10 @@ class WorkoutCompareTestNew(TestCase): self.c = Client() self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) + for w in self.user_workouts: + if w.date != w.startdatetime.date(): + w.date = w.startdatetime.date() + w.save() self.factory = RequestFactory() self.password = faker.word() self.u.set_password(self.password) @@ -296,6 +381,10 @@ class WorkoutBoxPlotTestNew(TestCase): self.c = Client() self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) + for w in self.user_workouts: + if w.date != w.startdatetime.date(): + w.date = w.startdatetime.date() + w.save() self.factory = RequestFactory() self.password = faker.word() self.u.set_password(self.password) @@ -393,6 +482,10 @@ class WorkoutHistoTestNew(TestCase): self.c = Client() self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) + for w in self.user_workouts: + if w.date != w.startdatetime.date(): + w.date = w.startdatetime.date() + w.save() self.factory = RequestFactory() self.password = faker.word() self.u.set_password(self.password) @@ -490,6 +583,10 @@ class History(TestCase): self.c = Client() self.user_workouts = WorkoutFactory.create_batch(20, user=self.r) + for w in self.user_workouts: + if w.date != w.startdatetime.date(): + w.date = w.startdatetime.date() + w.save() self.factory = RequestFactory() self.password = faker.word() self.u.set_password(self.password) @@ -577,6 +674,10 @@ class GoldMedalScores(TestCase): self.c = Client() self.user_workouts = WorkoutFactory.create_batch(20, user=self.r) + for w in self.user_workouts: + if w.date != w.startdatetime.date(): + w.date = w.startdatetime.date() + w.save() result = get_random_file(filename='rowers/tests/testdata/onwater2.csv') self.w1 = WorkoutFactory(user=self.r, csvfilename=result['filename'], @@ -677,6 +778,10 @@ class WorkoutFlexallTestNew(TestCase): self.c = Client() self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) + for w in self.user_workouts: + if w.date != w.startdatetime.date(): + w.date = w.startdatetime.date() + w.save() self.factory = RequestFactory() self.password = faker.word() self.u.set_password(self.password) @@ -774,6 +879,10 @@ class WorkoutStatsTestNew(TestCase): self.c = Client() self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) + for w in self.user_workouts: + if w.date != w.startdatetime.date(): + w.date = w.startdatetime.date() + w.save() today = datetime.date.today() i = 0 @@ -1082,6 +1191,10 @@ class MarkerPerformanceTest(TestCase): self.c = Client() self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) + for w in self.user_workouts: + if w.date != w.startdatetime.date(): + w.date = w.startdatetime.date() + w.save() self.factory = RequestFactory() self.password = faker.word() self.u.set_password(self.password) @@ -1095,6 +1208,8 @@ class MarkerPerformanceTest(TestCase): workouttype='rower', rankingpiece=True, ) + self.werg2k.date = self.werg2k.startdatetime.date() + self.werg2k.save() # need a 2k, 5k, 1h row self.werg5k = WorkoutFactory( @@ -1104,6 +1219,8 @@ class MarkerPerformanceTest(TestCase): workouttype='rower', rankingpiece=True, ) + self.werg5k.date = self.werg5k.startdatetime.date() + self.werg5k.save() # need a 2k, 5k, 1h row self.werg1h = WorkoutFactory( @@ -1113,6 +1230,8 @@ class MarkerPerformanceTest(TestCase): workouttype='rower', rankingpiece=True, ) + self.werg1h.date = self.werg1h.startdatetime.date() + self.werg1h.save() def tearDown(self): for workout in self.user_workouts: @@ -1280,6 +1399,10 @@ class AlertTest(TestCase): self.c = Client() self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) + for w in self.user_workouts: + if w.date != w.startdatetime.date(): + w.date = w.startdatetime.date() + w.save() self.factory = RequestFactory() self.password = faker.word() self.u.set_password(self.password) diff --git a/rowers/tests/test_aworkouts.py b/rowers/tests/test_aworkouts.py index 3f5f8f24..fdbb7e75 100644 --- a/rowers/tests/test_aworkouts.py +++ b/rowers/tests/test_aworkouts.py @@ -32,858 +32,930 @@ 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) + form_data = { + 'workouts': ['1','2'], + 'action': ['export'], + 'destination': 'strava', + } - url3 = url+'?page=3' - - response = self.c.get(url3) - self.assertEqual(response.status_code,200) + response = self.c.post(expected_url, form_data, follow=True) + url = expected_url + expected_url = reverse('workouts_view') + self.assertRedirects(response, + expected_url=expected_url, + status_code=302, + target_status_code=200) + form_data = { + 'workouts': ['1','2'], + 'action': ['remove'], + } + response = self.c.post(url, form_data, follow=True) + expected_url = reverse('workouts_view') + self.assertRedirects(response, + expected_url=expected_url, + status_code=302, + target_status_code=200) + @override_settings(TESTING=True) 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') + @patch('rowers.middleware.myqueue') + def test_resample(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, + mocked_myqueue): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) - url = reverse('workouts_join_select') - response = self.c.get(url) - self.assertEqual(response.status_code,200) + url = reverse('workout_resample_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) + + 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 = { + 'resamplechoice': 'copy', + } - 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()) + form_data = { + 'resamplechoice': 'overwrite', + } - form = TrendFlexModalForm(form_data) - self.assertTrue(form.is_valid()) + 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) + + 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_view') + form = DateRangeForm(form_data) + self.assertTrue(form.is_valid()) - 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') + + response = self.c.get(url,follow=True) + self.assertEqual(response.status_code,200) - form = WorkoutMultipleCompareForm(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) - form = WorkoutJoinParamForm(form_data) - self.assertTrue(form.is_valid()) + form_data = { + 'workout_name': 'joined', + 'set_private': False, + 'workouts': [str(self.werg1.id),str(self.werg2.id),str(self.werg3.id)], + } - response = self.c.post(url,form_data,follow=True) - self.assertEqual(response.status_code,200) + form = WorkoutMultipleCompareForm(form_data) + self.assertTrue(form.is_valid()) - expected_url = reverse(self.r.defaultlandingpage, - kwargs = { + 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 = { '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_basicrower.py b/rowers/tests/test_basicrower.py index 39a9b1f6..934c7737 100644 --- a/rowers/tests/test_basicrower.py +++ b/rowers/tests/test_basicrower.py @@ -61,3 +61,15 @@ class SimpleViewTest(TestCase): self.assertRedirects(response, expected_url='/rowers/list-workouts/', status_code=302,target_status_code=200) + + def test_start_coachtrial(self): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + url = '/rowers/startcoachtrial/' + + response = self.c.get(url,follow=True) + + self.assertRedirects(response, + expected_url='/rowers/list-workouts/', + status_code=302,target_status_code=200) diff --git a/rowers/tests/test_uploads.py b/rowers/tests/test_uploads.py index 9c6ba94b..68a61119 100644 --- a/rowers/tests/test_uploads.py +++ b/rowers/tests/test_uploads.py @@ -28,15 +28,6 @@ class ViewTest(TestCase): self.nu = datetime.datetime.now() - def test_upload_view_notloggedin(self): - response = self.c.post('/rowers/workout/upload/',follow=True) - - - self.assertRedirects(response, expected_url='/login/?next=/rowers/workout/upload/', - status_code=302,target_status_code=200) - - self.assertEqual(response.status_code, 200) - @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) def test_upload_view_sled(self, mocked_sqlalchemy,mocked_getsmallrowdata_db): @@ -129,7 +120,7 @@ class ViewTest(TestCase): f_to_be_deleted = w.csvfilename try: os.remove(f_to_be_deleted+'.gz') - except (FileNotFoundError,OSError): + except (FileNotFoundError,OSError): # pragma: no cover pass @patch('rowers.dataprep.create_engine') @@ -230,7 +221,7 @@ class ViewTest(TestCase): f_to_be_deleted = w.csvfilename try: os.remove(f_to_be_deleted+'.gz') - except (FileNotFoundError,OSError): + except (FileNotFoundError,OSError): # pragma: no cover pass @@ -271,7 +262,7 @@ class ViewTest(TestCase): f_to_be_deleted = w.csvfilename try: os.remove(f_to_be_deleted+'.gz') - except (FileNotFoundError,OSError): + except (FileNotFoundError,OSError): # pragma: no cover pass @patch('rowers.dataprep.create_engine') @@ -341,19 +332,6 @@ class ViewTest(TestCase): self.assertEqual(response.status_code, 200) - filename = 'rowers/tests/testdata/crewnerddata.CSV' - f = open(filename,'rb') - file_data = {'file': f} - form_data = {} - form = CNsummaryForm(form_data,file_data) - - response = self.c.post('rowers/workout/'+encoded1+'/crewnerdsummary', - form_data, - follow=True) - - f.close() - - self.assertEqual(response.status_code, 200) w = Workout.objects.get(id=1) f_to_be_deleted = w.csvfilename try: @@ -660,7 +638,7 @@ class ViewTest(TestCase): @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.TCXParser') - def test_upload_view_TCX_CN(self, mocked_sqlalchemy, + def test_upload_view_TCX_Rim(self, mocked_sqlalchemy, mocked_tcx_parser): self.c.login(username='john',password='koeinsloot') diff --git a/rowers/tests/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/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 4d5ff824..535be5e4 100644 Binary files a/rowers/tests/testdata/testdata.tcx.gz and b/rowers/tests/testdata/testdata.tcx.gz differ diff --git a/rowers/urls.py b/rowers/urls.py index 6824aa6f..30326a73 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -251,6 +251,9 @@ urlpatterns = [ name='strokedatajson_v3'), re_path(r'^api/TCX/workouts/$', views.strokedata_tcx, name='strokedata_tcx'), + re_path(r'^api/courses/$', views.course_list, name='course_list'), + re_path(r'^api/courses/(?P\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/utils.py b/rowers/utils.py index 92357775..34f808ea 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))) @@ -322,9 +347,9 @@ def isbreakthrough(delta, cpvalues, p0, p1, p2, p3, ratio): pwr *= ratio - delta = delta.values.astype(int) - cpvalues = cpvalues.values.astype(int) - pwr = pwr.astype(int) + delta = delta.astype(int, errors='ignore').values + cpvalues = cpvalues.astype(int, errors='ignore').values + pwr = pwr.astype(int, errors='ignore').values res = np.sum(cpvalues > pwr+1) res2 = np.sum(cpvalues > pwr2+1) diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index 38b7ec34..af0f2272 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -56,7 +56,7 @@ def analysis_new(request, try: thesession = PlannedSession.objects.get(id=session) - if not can_view_session(user, thesession): + if not can_view_session(user, thesession): # pragma: no cover raise PermissionDenied("you cannot view this session") except PlannedSession.DoesNotExist: thesession = None @@ -102,7 +102,7 @@ def analysis_new(request, try: waterboattype = options['waterboattype'] - except KeyError: + except KeyError: # pragma: no cover pass if request.method == 'POST': @@ -188,7 +188,7 @@ def analysis_new(request, pass rankingtypes = [False,True] - if ranking: + if ranking: # pragma: no cover rankingtypes = [True] @@ -272,7 +272,7 @@ def analysis_new(request, request.session['options'] = options savedata = options.get('savedata',False) - if savedata: + if savedata: # pragma: no cover ids = options.get('ids',[]) tw = [] for id in ids: @@ -526,7 +526,7 @@ def trendflexdata(workouts, options, userid=0): extratitle = ' '+u.first_name+' '+u.last_name savedata = options.get('savedata',False) - if savedata: + if savedata: # pragma: no cover return df script, div = interactive_multiflex(df, xparam, yparam, @@ -561,7 +561,7 @@ def flexalldata(workouts, options): extratitle = ' '+u.first_name+' '+u.last_name savedata = options.get('savedata',False) - if savedata: + if savedata: # pragma: no cover workstrokesonly = not includereststrokes columns = [xparam, yparam1, yparam2, 'spm', 'driveenergy', 'distance'] ids = [int(w.id) for w in workouts] @@ -603,7 +603,7 @@ def histodata(workouts, options): extratitle = ' '+u.first_name+' '+u.last_name savedata = options.get('savedata',False) - if savedata: + if savedata: # pragma: no cover workstrokesonly = not includereststrokes ids = [int(w.id) for w in workouts] df = dataprep.getsmallrowdata_db([plotfield], ids=ids, @@ -640,7 +640,7 @@ def cpdata(workouts, options): }) savedata = options.get('savedata',False) - if savedata: + if savedata: # pragma: no cover return powerdf if powerdf.empty: # pragma: no cover @@ -869,7 +869,7 @@ def comparisondata(workouts, options): } savedata = options.get('savedata',False) - if savedata: + if savedata: # pragma: no cover workstrokesonly = not includereststrokes columns = [xparam, yparam1, 'ftime', 'distance', 'fpace', @@ -950,7 +950,7 @@ def boxplotdata(workouts, options): extratitle = ' '+u.first_name+' '+u.last_name savedata = options.get('savedata',False) - if savedata: + if savedata: # pragma: no cover return datadf script, div = interactive_boxchart(datadf, plotfield, @@ -1425,7 +1425,7 @@ def rankings_view2(request, userid=0, wcdurations = options['wcdurations'] wcpower = options['wcpower'] lastupdated = options['lastupdated'] - except KeyError: + except KeyError: # pragma: no cover pass try: userid = options['userid'] @@ -2017,7 +2017,7 @@ class ForceCurveAnalysisDelete(DeleteView): def get_object(self, *args, **kwargs): obj = super(ForceCurveAnalysisDelete, self).get_object(*args, **kwargs) - if obj.rower != self.request.user.rower: + if obj.rower != self.request.user.rower: # pragma: no cover raise PermissionDenied("You are not allowed to delete this Analysis") return obj @@ -2039,8 +2039,7 @@ class SavedAnalysisView(UserPassesTestMixin, View): analyses = None chart = instroke_multi_interactive_chart - - def handle_no_permission(self): + def handle_no_permission(self): # pragma: no cover if self.request.user.is_anonymous: url = reverse('login')+'?next='+self.request.path return HttpResponseRedirect(url) @@ -2079,7 +2078,7 @@ class SavedAnalysisView(UserPassesTestMixin, View): super(SavedAnalysisView, self).setup(request, *args, **kwargs) self.r = getrequestrower(request, userid=self.userid) - if self.r is not None and not is_coach_user(self.request.user, self.r.user): + if self.r is not None and not is_coach_user(self.request.user, self.r.user): # pragma: no cover raise PermissionDenied("You are not allowed to see analyses for this user") self.analyses = self.analysis_class.objects.filter(rower=self.r).order_by("-date","-id") @@ -2087,7 +2086,7 @@ class SavedAnalysisView(UserPassesTestMixin, View): startdate = request.GET.get('startdate') enddate = request.GET.get('enddate') self.searchform = SearchForm() - if query: + if query: # pragma: no cover query_list = query.split() if self.analysis_class == InStrokeAnalysis: self.analyses = self.analyses.filter( @@ -2111,13 +2110,13 @@ class SavedAnalysisView(UserPassesTestMixin, View): ) self.searchform = SearchForm(initial={'q': query}) date_initial = {} - if startdate: + if startdate: # pragma: no cover try: self.analyses = self.analyses.filter(date__gte=startdate) date_initial['startdate'] = startdate except ValidationError: messages.error(request,"{startdate} is not a valid start date".format(startdate=startdate)) - if enddate: + if enddate: # pragma: no cover self.analyses = self.analyses.filter(date__lte=enddate) date_initial['enddate'] = enddate self.dateform = DateRangeForm(initial=date_initial) @@ -2136,7 +2135,7 @@ class SavedAnalysisView(UserPassesTestMixin, View): 'dateform': self.dateform, }) - def post(self, request, *args, **kwargs): + def post(self, request, *args, **kwargs): # pragma: no cover form = self.form_class(request.POST) if form.is_valid(): @@ -2211,7 +2210,7 @@ class InStrokeAnalysisDelete(DeleteView): def get_object(self, *args, **kwargs): obj = super(InStrokeAnalysisDelete, self).get_object(*args, **kwargs) - if obj.rower != self.request.user.rower: + if obj.rower != self.request.user.rower: # pragma: no cover raise PermissionDenied("You are not allowed to delete this Analysis") return obj @@ -2356,7 +2355,7 @@ def alert_report_view(request, id=0, userid=0, nperiod=0): stats = alert_get_stats(alert, nperiod=nperiod) is_ajax = request_is_ajax(request) - if is_ajax: + if is_ajax: # pragma: no cover return JSONResponse({ "stats": stats, }) @@ -2680,9 +2679,10 @@ def history_view(request, userid=0): last28 = timezone.now() - datetime.timedelta(days=28) today = timezone.now() + lastyear = today - datetime.timedelta(days=365) - lastyear = datetime.datetime( - year=today.year - 1, month=today.month, day=today.day) + #lastyear = datetime.datetime( + # year=today.year - 1, month=today.month, day=today.day) firstmay = datetime.datetime( year=today.year, month=5, day=1).astimezone(usertimezone) @@ -2771,7 +2771,7 @@ def history_view_data(request, userid=0): df = getsmallrowdata_db(columns, ids=ids) try: df['deltat'] = df['time'].diff().clip(lower=0) - except KeyError: + except KeyError: # pragma: no cover pass df = dataprep.clean_df_stats(df, workstrokesonly=True, ignoreadvanced=True, ignorehr=False) @@ -2824,7 +2824,7 @@ def history_view_data(request, userid=0): ddict['powermean'] = int(wavg(ddf, 'power', 'deltat')) try: ddict['powermax'] = ddf['power'].max().astype(int) - except KeyError: + except KeyError: # pragma: no cover ddict['powermax'] = 0 ddict['nrworkouts'] = a_workouts.count() listofdicts.append(ddict) @@ -2839,7 +2839,7 @@ def history_view_data(request, userid=0): try: totalsdict['powermean'] = int(wavg(df, 'power', 'deltat')) totalsdict['powermax'] = df['power'].max().astype(int) - except KeyError: + except KeyError: # pragma: no cover totalsdict['powermean'] = 0 totalsdict['powermax'] = 0 try: diff --git a/rowers/views/apiviews.py b/rowers/views/apiviews.py index 4b07edbc..c6f60c98 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, coursestokml from xml.etree import ElementTree as ET import arrow @@ -11,6 +12,8 @@ from rowers.dataroutines import get_workouttype_from_tcx, get_startdate_time_zon from rest_framework.decorators import parser_classes from rest_framework.parsers import BaseParser +from rowers.utils import geo_distance + from datetime import datetime as dt import rowingdata.tcxtools as tcxtools @@ -24,7 +27,7 @@ class XMLParser(BaseParser): dologging("apilog.log", "XML Parser") try: s = ET.parse(stream).getroot() - except Exception as e: + except Exception as e: # pragma: no cover dologging("apilog.log",e) return HttpResponse(status=500) return s @@ -228,13 +231,123 @@ def strokedataform_v2(request, id=0): def part_of_day(hour): if 5 <= hour < 12: return "Morning" - elif 12 <= hour < 18: + elif 12 <= hour < 18: # pragma: no cover return "Afternoon" - elif 18 <= hour < 24: + elif 18 <= hour < 24: # pragma: no cover return "Evening" - else: + else: # pragma: no cover return "Night" +# KML API views +""" +- Get a list of courses OK + - Nearby a certain coordinate + - Filtered by +- Get a (KML) course (in response.content rather than as attachment) OK +- Get multiple courses as one KML in response.content OK + - GET with parameters? +Optional, not for CN +- Create one or more new courses from KML + - Should check for duplicates (Placemark ID) +- Update one or more new courses from KML +""" +@api_view(["GET"]) +@permission_classes([AllowAny]) +def course_list(request): + if request.method != 'GET': # pragma: no cover + dologging('apilog.log','{m} request to KML endpoint'.format(m=request.method)) + return HttpResponseNotAllowed("Method not supported") + + query_data = {} + name = request.GET.get('name') + distance = request.GET.get('course_distance') + latitude = request.GET.get('latitude') + longitude = request.GET.get('longitude') + distance_from = request.GET.get('distance_from') + if name is not None: + query_data['name__contains'] = name + if distance is not None: + try: + query_data['distance__lte'] = int(distance)+50 + query_data['distance__gte'] = int(distance)-50 + except: + pass + + + + courses = GeoCourse.objects.filter(**query_data) + if latitude is not None and longitude is not None and distance_from is not None: + try: + newlist = [] + for c in courses: + distance = geo_distance(float(latitude), float(longitude), c.coord[0], c.coord[1])[0] + if distance < float(distance_from): # pragma: no cover + newlist.append(c) + courses = newlist + except ValueError: + pass + + + courselist = [] + for c in courses: + d = { + 'id': c.id, + 'name':c.name, + 'country':c.country, + 'distance':c.distance, + 'notes':c.notes, + 'location':(c.coord[0], c.coord[1]), + } + courselist.append(d) + + response_dict = {'courses': courselist} + + return JsonResponse(response_dict, content_type='application/json; charset=utf8') + +@api_view(["GET"]) +@permission_classes([AllowAny]) +def get_crewnerd_kml(request,id=0): + if request.method != 'GET': # pragma: no cover + dologging('apilog.log','{m} request to CrewNerd KML endpoint'.format(m=request.method)) + return HttpResponseNotAllowed("Method not supported") + + try: + c = GeoCourse.objects.get(id=id) + except GeoCourse.DoesNotExist: # pragma: no cover + raise Http404("This course does not exist") + + kml = coursetokml(c, cn=True) + + return HttpResponse(kml) + +#@csrf_exempt +@api_view(["GET"]) +@permission_classes([AllowAny]) +def get_crewnerd_multiple(request): + if request.method != 'GET': # pragma: no cover + dologging('apilog.log','{m} request to CrewNerd KML endpoint'.format(m=request.method)) + return HttpResponseNotAllowed("Method not supported") + + ids = request.GET.get('id') + if ids is not None: + tdict = dict(request.GET.lists()) + idsnew = [] + for id in tdict['id']: + try: + idsnew.append(int(id)) + except ValueError: + pass + ids = idsnew + else: + gcs = GeoCourse.objects.all() + ids = [c.id for c in gcs] + + kml = coursestokml(ids, cn=True) + + return HttpResponse(kml) + +# Stroke data views + @csrf_exempt @login_required() @api_view(["POST"]) @@ -244,11 +357,11 @@ def strokedata_tcx(request): """ Upload a TCX file through API """ - if request.method != 'POST': + if request.method != 'POST': # pragma: no cover dologging('apilog.log','GET request to TCX endpoint') - return HttpResponseNotAllowed("Method not supported") # pragma: no cover + return HttpResponseNotAllowed("Method not supported") - if 'application/xml' not in request.content_type.lower(): + if 'application/xml' not in request.content_type.lower(): # pragma: no cover dologging('apilog.log','POST data not application/xml, request to TCX endpoint') dologging('apilog.log', request.content_type.lower()) return HttpResponseNotAllowed("Need application/xml") @@ -275,7 +388,7 @@ def strokedata_tcx(request): lap_duration_seconds = float(lap_duration_node.text) total_duration += lap_duration_seconds - except Exception as e: + except Exception as e: # pragma: no cover dologging('apilog.log','TCX') dologging('apilog.log',e) return HttpResponseNotAllowed("Could not parse TCX data") @@ -329,7 +442,7 @@ def strokedata_tcx(request): "workout id": workoutid, "status": "success", }) - except Exception as e: + except Exception as e: # pragma: no cover dologging('apilog.log','TCX API endpoint') dologging('apilog.log',e) return HttpResponse(status=500) @@ -382,7 +495,7 @@ def strokedatajson_v3(request): title = request.data.get('name','') try: elapsedTime = request.data['elapsedTime'] - except KeyError: + except KeyError: # pragma: no cover try: duration = request.data['duration'] try: @@ -394,7 +507,7 @@ def strokedatajson_v3(request): return HttpResponse("Missing Elapsed Time", status=400) try: totalDistance = request.data['distance'] - except KeyError: + except KeyError: # pragma: no cover return HttpResponse("Missing Total Distance", status=400) timeZone = request.data.get('timezone','UTC') workouttype = request.data.get('workouttype','rower') @@ -415,12 +528,12 @@ def strokedatajson_v3(request): df = pd.DataFrame() try: strokes = request.data['strokes'] - except KeyError: + except KeyError: # pragma: no cover return HttpResponse("No Stroke Data in JSON", status=400) try: df = pd.DataFrame(strokes['data']) - except KeyError: + except KeyError: # pragma: no cover try: df = pd.DataFrame(request.data['strokedata']) except: @@ -431,7 +544,7 @@ def strokedatajson_v3(request): status, comment, data = api_get_dataframe(startdatetime, df) - if status != 200: + if status != 200: # pragma: no cover return HttpResponse(comment, status=status) @@ -541,7 +654,7 @@ def strokedatajson_v2(request, id): try: for d in request.data['data']: dologging('apilog.log',json.dumps(d)) - except KeyError: + except KeyError: # pragma: no cover try: for d in request.data['strokedata']: dologging('apilog.log',json.dumps(d)) @@ -565,7 +678,7 @@ def strokedatajson_v2(request, id): df.sort_index(inplace=True) status, comment, data = api_get_dataframe(row.startdatetime, df) - if status != 200: + if status != 200: # pragma: no cover return HttpResponse(comment, status=status) r = getrower(request.user) diff --git a/rowers/views/importviews.py b/rowers/views/importviews.py index cc07d85b..93ba604e 100644 --- a/rowers/views/importviews.py +++ b/rowers/views/importviews.py @@ -41,7 +41,7 @@ def workout_export_view(request, id=0, source='c2'): integration = importsources[source](request.user) try: id = integration.workout_export(w) - except NoTokenError: + except NoTokenError: # pragma: no cover return HttpResponseRedirect(integration.make_authorization_url()) messages.info( @@ -73,7 +73,7 @@ def rower_rojabo_authorize(request): # pragma: no cover return HttpResponseRedirect(url) @login_required() -def rower_integration_authorize(request, source='c2'): +def rower_integration_authorize(request, source='c2'): # pragma: no cover try: integration = importsources[source](request.user) except KeyError: @@ -107,7 +107,7 @@ def rower_polar_authorize(request, source='polar'): # pragma: no cover def rower_integration_token_refresh(request, source='c2'): try: integration = importsources[source](request.user) - except KeyError: + except KeyError: # pragma: no cover url = reverse('workouts_view') return HttpResponseRedirect(url) try: @@ -217,7 +217,7 @@ def rower_process_polarcallback(request): try: error = polar_user_data['error'] - if error['error_type'] == 'user_already_registered': + if error['error_type'] == 'user_already_registered': # pragma: no cover s = error['message'] tester = re.compile(r'.*userid:(?P\d+)') testresult = tester.match(s) @@ -371,7 +371,7 @@ def workout_import_view(request, source='c2'): r = getrequestrower(request) try: integration = importsources[source](request.user) - except KeyError: + except KeyError: # pragma: no cover messages.error(request,"This integration is not supported") url = reverse('workouts_view') return HttpResponseRedirect(url) @@ -428,7 +428,7 @@ def workout_import_view(request, source='c2'): startdate=startdate, enddate=enddate ) - except NoTokenError: + except NoTokenError: # pragma: no cover messages.error(request,"You must first make the connection to {source}".format( source=source )) @@ -504,7 +504,7 @@ def rower_process_stravacallback(request): try: res = strava_integration.get_token(code,logfile='strava_log.log') - except NoTokenError: + except NoTokenError: # pragma: no cover message = "Something went wrong with the Strava authorization" messages.error(request, message) url = reverse('rower_exportsettings_view') @@ -526,7 +526,7 @@ def rower_process_stravacallback(request): r.save() try: _ = strava_integration.set_strava_athlete_id() - except NoTokenError: + except NoTokenError: # pragma: no cover messages.error(request,'Something went wrong storing your Strava ID. Please authorize again') url = reverse('rower_exportsettings_view') return HttpResponseRedirect(url) @@ -540,7 +540,7 @@ def rower_process_stravacallback(request): messages.error(request, message) url = reverse('rower_exportsettings_view') - return HttpResponseRedirect(url) + return HttpResponseRedirect(url) # pragma: no cover # Process SportTracks callback @@ -653,7 +653,7 @@ def rower_process_testcallback(request): # pragma: no cover message="This functionality requires a Pro plan or higher", redirect_field_name=None) @permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True) -def workout_rojaboimport_view(request, message="", userid=0): +def workout_rojaboimport_view(request, message="", userid=0): # pragma: no cover r = getrequestrower(request, userid=userid) if r.user != request.user: messages.error( @@ -728,21 +728,21 @@ def workout_rojaboimport_view(request, message="", userid=0): item['warm_up']['steps'], warmup=True ) - except KeyError: + except KeyError: # pragma: no cover pass try: steps = steps + rojabo_stuff.stepsconvert( item['primary']['steps'], startid=len(steps) ) - except KeyError: + except KeyError: # pragma: no cover pass try: steps = steps + rojabo_stuff.stepsconvert( item['cool_down']['steps'], cooldown=True, startid=len(steps)) - except KeyError: + except KeyError: # pragma: no cover pass if steps: @@ -755,7 +755,7 @@ def workout_rojaboimport_view(request, message="", userid=0): ps.save() messages.info(request,'Saved planned session {id}'.format(id=ps.id)) - except KeyError: + except KeyError: # pragma: no cover pass except KeyError: pass @@ -1019,7 +1019,7 @@ def garmin_details_view(request): def workout_getimportview(request, externalid, source='c2', do_async=True): try: integration = importsources[source](request.user) - except (TypeError, NotImplementedError, KeyError): + except (TypeError, NotImplementedError, KeyError): # pragma: no cover return reverse("workouts_view") if 'startdate' in request.session and source == 'nk': # pragma: no cover startdate = request.session.get('startdate') @@ -1051,7 +1051,7 @@ def workout_getimportview(request, externalid, source='c2', do_async=True): # Imports all new workouts from SportTracks @login_required() -def workout_getsporttracksworkout_all(request): +def workout_getsporttracksworkout_all(request): # pragma: no cover st_integration = importsources['sporttracks'](request.user) try: _ = st_integration.get_workouts() diff --git a/rowers/views/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 diff --git a/rowers/views/userviews.py b/rowers/views/userviews.py index dbf08d5e..b03e5eb7 100644 --- a/rowers/views/userviews.py +++ b/rowers/views/userviews.py @@ -258,7 +258,7 @@ def user_messages(request,userid=0): 'rower':r}) @login_required() -def user_message_delete(request,id=0): +def user_message_delete(request,id=0): # pragma: no cover try: msg = UserMessage.objects.get(id=id) except UserMessage.DoesNotExist: @@ -278,7 +278,7 @@ def user_message_delete(request,id=0): @login_required() -def user_message_markread(request,id=0): +def user_message_markread(request,id=0): # pragma: no cover try: msg = UserMessage.objects.get(id=id) except UserMessage.DoesNotExist: @@ -448,13 +448,13 @@ def rower_exportsettings_view(request, userid=0): if not user_is_not_basic(r.user): # pragma: no cover if not user_is_coachee(r.user): doset = False - if not doset: + if not doset: # pragma: no cover before = getattr(r, attr) if before == value: doset = True if doset: setattr(r, attr, value) - else: + else: # pragma: no cover if user_is_not_basic(r.user): messages.error( request, 'Could not set '+attr+'. You need to create the connection first.') @@ -605,7 +605,7 @@ def rower_simpleprefs_view(request, userid=0): r = getrequestrowercoachee(request, userid=userid, notpermanent=True) firsttime = False - if not r.ftpset: + if not r.ftpset: # pragma: no cover r.ftpset = True r.save() firsttime = True @@ -623,7 +623,7 @@ def rower_simpleprefs_view(request, userid=0): form = SimpleRowerPowerForm(instance=r) - if request.method == 'POST': + if request.method == 'POST': # pragma: no cover form = SimpleRowerPowerForm(request.POST, instance=r) if form.is_valid(): form.save(commit=True) @@ -714,7 +714,7 @@ def rower_prefs_view(request, userid=0, message=""): r.pw_ut1, r.pw_at, r.pw_tr, r.pw_an])/r.ftp - if cogganzones: + if cogganzones: # pragma: no cover powerfrac = np.array([55.,75.,90.,105.,120.]) powerzones = ['Rest', 'Active Recovery', diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 7aa5b6f5..b22a7da4 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, @@ -2048,9 +2048,9 @@ def workouts_bulk_actions(request): w = get_workout_by_opaqueid(request, encid) if w.user == r: workouts.append(w) - else: + else: # pragma: no cover messages.error(request,'Bulk actions are not accessible to coaches') - except KeyError: + except KeyError: # pragma: no cover pass if request.method == 'POST': @@ -2058,7 +2058,7 @@ def workouts_bulk_actions(request): form = WorkoutMultipleCompareForm(request.POST) if form.is_valid() and actionform.is_valid(): workouts = form.cleaned_data['workouts'] - if len(workouts) == 0: + if len(workouts) == 0: # pragma: no cover url = reverse('workouts_view') return HttpResponseRedirect(url) action = actionform.cleaned_data['action'] @@ -2078,14 +2078,14 @@ def workouts_bulk_actions(request): 'Workout {id} exported to {destination}'.format( id=encoder.encode_hex(w.id), destination=destination)) - except NoTokenError: + except NoTokenError: # pragma: no cover messages.error(request, 'Export to {destination} of workout {id} failed'.format( id=encoder.encode_hex(w.id), destination=destination)) url = reverse('workouts_view') return HttpResponseRedirect(url) - else: + else: # pragma: no cover if len(workouts) == 0: url = reverse(workouts_view) return HttpResponseRedirect(url) @@ -3299,7 +3299,6 @@ def instroke_chart_interactive(request, id=0, analysis=0, userid=0): rowdata = rrdata(csvfile=w.csvfilename) instrokemetrics = rowdata.get_instroke_columns() - form = InstrokeForm(choices=instrokemetrics) breadcrumbs = [ @@ -3513,6 +3512,7 @@ def instroke_chart_interactive(request, id=0, analysis=0, userid=0): name=name,notes=notes, ) + # change to range spm_min to spm_max vals, units, typ = rowdata.updateinterval_range( ' Cadence (stokes/min)', spm_min, spm_max, diff --git a/rowsandall_app/settings_dev.py b/rowsandall_app/settings_dev.py index 5bcfef4a..1adb217b 100644 --- a/rowsandall_app/settings_dev.py +++ b/rowsandall_app/settings_dev.py @@ -62,7 +62,7 @@ DEBUG = True TEMPLATES[0]['OPTIONS']['debug'] = DEBUG -ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'dunav.ngrok.io'] +ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'dunav.ngrok.io','rowsandall-2'] # INSTALLED_APPS += ['debug_toolbar',] # INSTALLED_APPS += ["sslserver"]