From 5e1169fd2c5ea1ceff76928681b5449b6681e042 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Thu, 13 Jun 2024 11:07:29 +0200 Subject: [PATCH 1/2] change osm to arcgis --- rowers/courses.py | 7 +++- rowers/tests/mocks.py | 51 +++++++++++++++++++++++++- rowers/tests/test_aavirtualevents.py | 40 +++++++++++++------- rowers/tests/test_permissions2.py | 20 +++++----- rowers/tests/test_races.py | 33 +++++++++++------ rowers/tests/test_urls.py | 4 +- rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4000 bytes rowers/views/apiviews.py | 1 - 8 files changed, 118 insertions(+), 38 deletions(-) diff --git a/rowers/courses.py b/rowers/courses.py index fb2f60c4..c685450c 100644 --- a/rowers/courses.py +++ b/rowers/courses.py @@ -39,7 +39,10 @@ from rowers.models import VirtualRace def howfaris(lat_lon, course): coords = course.coord - distance = geo_distance(lat_lon[0], lat_lon[1], coords[0], coords[1])[0] + try: + distance = geo_distance(lat_lon[0], lat_lon[1], coords[0], coords[1])[0] + except TypeError: + return 10000000 return distance @@ -398,7 +401,7 @@ def createcourse( if i == 0 and j == 0: latitude = point['latitude'] longitude = point['longitude'] - g = geocoder.osm([latitude, longitude], method='reverse') + g = geocoder.arcgis([latitude, longitude], method='reverse') if g.ok: country = g.json['country'] else: # pragma: no cover diff --git a/rowers/tests/mocks.py b/rowers/tests/mocks.py index cd73dd7a..0942905b 100644 --- a/rowers/tests/mocks.py +++ b/rowers/tests/mocks.py @@ -99,6 +99,14 @@ def mocked_myqueue(*args, **kwargs): # pragma: no cover def mock_c2open(*args, **kwargs): # pragma: no cover return('aap') +def mock_osm(*args, **kwargs): + return { + 'ok': True, + json: { + 'country': 'Atlantis', + } + } + def mocked_session(*args, **kwargs): class MockEngine: @@ -853,6 +861,41 @@ def mocked_requests(*args, **kwargs): } } + osm_json = [ + { + "place_id": 165922574, + "licence": "Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright", + "osm_type": "node", + "osm_id": 2756013867, + "lat": "52.2229364", + "lon": "6.846701", + "category": "place", + "type": "house", + "place_rank": 30, + "importance": 0.000009999999999954, + "addresstype": "place", + "name": "", + "display_name": "254, Ir. Schiffstraat, Havengebied, Enschede, Overijssel, Netherlands, 7547 RD, Netherlands", + "address": { + "house_number": "254", + "road": "Ir. Schiffstraat", + "industrial": "Havengebied", + "city": "Enschede", + "state": "Overijssel", + "ISO3166-2-lvl4": "NL-OV", + "country": "Netherlands", + "postcode": "7547 RD", + "country_code": "nl" + }, + "boundingbox": [ + "52.2228864", + "52.2229864", + "6.8466510", + "6.8467510" + ] + } + ] + with open('rowers/tests/testdata/stravaworkoutlist.txt') as f: stravaworkoutlist = json.load(f) @@ -1061,7 +1104,7 @@ def mocked_requests(*args, **kwargs): if 'rojabo' in args[0]: return MockResponse(rojabodata, 200) - + osmtester = re.compile(r'.*?nominatim.openstreetmap.org') polartester = re.compile(r'.*?polaraccesslink\.com') polarremotetester = re.compile(r'.*?polarremote\.com') c2tester = re.compile(r'.*?log\.concept2\.com') @@ -1167,6 +1210,12 @@ def mocked_requests(*args, **kwargs): garmintrainingscheduleregex = r'.*?garmin\.com\/training-api\/schedule' garmintrainingscheduletester = re.compile(garmintrainingscheduleregex) + if osmtester.match(args[0]): + return MockResponse(osm_json, 200) + + if args[0] == '1.1.1.1': + return MockResponse(osm_json, 200) + if garmintester.match(args[0]): if garmindownloadtester.match(args[0]): # pragma: no cover return MockStreamResponse('rowers/tests/testdata/3x250m.fit',200) diff --git a/rowers/tests/test_aavirtualevents.py b/rowers/tests/test_aavirtualevents.py index e58e5861..92e978fd 100644 --- a/rowers/tests/test_aavirtualevents.py +++ b/rowers/tests/test_aavirtualevents.py @@ -342,7 +342,9 @@ class VirtualEventViewTest(MyTestCase): #Scenarios # races page - def test_races_view(self): + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) + @patch('rowers.courses.geocoder.osm', side_effect=mock_osm) + def test_races_view(self, mock_get, mock_osm): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -384,7 +386,8 @@ class VirtualEventViewTest(MyTestCase): # set up new OTE race - def test_new_indoorrace(self): + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) + def test_new_indoorrace(self, mock_get): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -467,7 +470,8 @@ class VirtualEventViewTest(MyTestCase): # set up new OTE race - def test_edit_indoorrace(self): + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) + def test_edit_indoorrace(self, mock_get): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -514,7 +518,8 @@ class VirtualEventViewTest(MyTestCase): # set up new otw race # set up new OTE race - def test_new_race(self): + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) + def test_new_race(self, mock_get): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -551,7 +556,8 @@ class VirtualEventViewTest(MyTestCase): expected_url = reverse('virtualevents_view'), status_code=302,target_status_code=200) - def test_edit_race(self): + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) + def test_edit_race(self, mock_get): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -590,7 +596,8 @@ class VirtualEventViewTest(MyTestCase): status_code=302,target_status_code=200) # view - def test_race_view(self): + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) + def test_race_view(self, mock_get): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -614,7 +621,8 @@ class VirtualEventViewTest(MyTestCase): # register # withdraw - def test_register_race_view(self): + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) + def test_register_race_view(self, mock_get): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -691,7 +699,8 @@ class VirtualEventViewTest(MyTestCase): # add boat (OTW) - def test_register_otwrace_view(self): + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) + def test_register_otwrace_view(self, mock_get): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -760,7 +769,8 @@ class VirtualEventViewTest(MyTestCase): # submit result (OTE) - def test_ote_submit(self): + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) + def test_ote_submit(self, mock_get): login = self.c.login(username=self.upiet.username, password=self.passwordpiet) self.assertTrue(login) @@ -793,7 +803,8 @@ class VirtualEventViewTest(MyTestCase): self.assertEqual(response.status_code,200) # submit result (OTE) - def test_ote_submitfalse(self): + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) + def test_ote_submitfalse(self, mock_get): login = self.c.login(username=self.upiet.username, password=self.passwordpiet) self.assertTrue(login) @@ -820,7 +831,8 @@ class VirtualEventViewTest(MyTestCase): self.assertTrue(not therecord.coursecompleted) # submit result (OTW) - def test_otw_submit(self): + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) + def test_otw_submit(self, mock_get): login = self.c.login(username=self.upiet.username, password=self.passwordpiet) self.assertTrue(login) @@ -842,7 +854,8 @@ class VirtualEventViewTest(MyTestCase): # course view # submit result (OTW) - def test_otw_courses(self): + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) + def test_otw_courses(self, mock_get): login = self.c.login(username=self.upiet.username, password=self.passwordpiet) self.assertTrue(login) @@ -861,7 +874,8 @@ class VirtualEventViewTest(MyTestCase): response = self.c.get(url) self.assertEqual(response.status_code,200) - def test_otw_courses_edit(self): + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) + def test_otw_courses_edit(self, mock_get): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) diff --git a/rowers/tests/test_permissions2.py b/rowers/tests/test_permissions2.py index 074f4e7f..1675c7df 100644 --- a/rowers/tests/test_permissions2.py +++ b/rowers/tests/test_permissions2.py @@ -222,16 +222,18 @@ class PermissionsViewTests(TestCase): @patch('requests.get',side_effect=mocked_requests) @patch('requests.post',side_effect=mocked_requests) @patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data) + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) def test_permissions_anonymous( - self,view,permissions, - mock_Session, - mock_c2open, - mocked_sqlalchemy, - mocked_read_df_sql, - mocked_read_data, - mock_get, - mock_post, - mocked_get_video_data, + self,view,permissions, + mock_Session, + mock_c2open, + mocked_sqlalchemy, + mocked_read_df_sql, + mocked_read_data, + mock_get, + mock_post, + mocked_get_video_data, + mock_get2, ): if permissions['anonymous'] and not permissions['is_staff'] and permissions['dotest']: diff --git a/rowers/tests/test_races.py b/rowers/tests/test_races.py index a8d3ecbb..8e7ac627 100644 --- a/rowers/tests/test_races.py +++ b/rowers/tests/test_races.py @@ -258,7 +258,8 @@ class ChallengesTest(TestCase): tcourses = getnearestcourses(lat_lon,courses) self.assertEqual(len(tcourses),1) - def test_courses_view(self): + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) + def test_courses_view(self, mock_get): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -274,7 +275,8 @@ class ChallengesTest(TestCase): response = self.c.get(url) self.assertEqual(response.status_code,200) - def test_coursemap_view(self): + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) + def test_coursemap_view(self, mock_get): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -282,7 +284,8 @@ class ChallengesTest(TestCase): response = self.c.get(url) self.assertEqual(response.status_code,200) - def test_coursemapcompare_view(self): + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) + def test_coursemapcompare_view(self, mock_get): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -290,7 +293,8 @@ class ChallengesTest(TestCase): response = self.c.get(url) self.assertEqual(response.status_code,200) - def test_coursecompare_view(self): + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) + def test_coursecompare_view(self, mock_get): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -300,7 +304,8 @@ class ChallengesTest(TestCase): - def test_course_create_edit_delete(self): + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) + def test_course_create_edit_delete(self, mock_get): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -464,7 +469,8 @@ class ChallengesTest(TestCase): self.assertEqual(response.status_code, 200) - def test_fastestrace_view(self): + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) + def test_fastestrace_view(self, mock_get): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -530,7 +536,8 @@ class ChallengesTest(TestCase): self.assertEqual(response.status_code, 200) - def test_virtualevents_view(self): + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) + def test_virtualevents_view(self, mock_get): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -584,8 +591,9 @@ class ChallengesTest(TestCase): response = self.c.post(url) self.assertEqual(response.status_code,200) + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) @patch('rowers.views.racesviews.myqueue') - def test_course_view(self, mocked_myqueue): + def test_course_view(self, mock_get, mocked_myqueue): login = self.c.login(username=self.u, password=self.password) self.assertTrue(login) @@ -607,8 +615,9 @@ class ChallengesTest(TestCase): self.assertEqual(response.status_code, 200) + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) @patch('rowers.views.racesviews.myqueue') - def test_virtualevent_view(self,mocked_myqueue): + def test_virtualevent_view(self,mock_get, mocked_myqueue): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -1007,7 +1016,8 @@ class IndoorChallengesTest(TestCase): pass - def test_virtualevents_view(self): + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) + def test_virtualevents_view(self, mock_get): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -1171,8 +1181,9 @@ class IndoorChallengesTest(TestCase): + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) @patch('rowers.views.racesviews.myqueue') - def test_virtualevent_view(self,mocked_myqueue): + def test_virtualevent_view(self,mock_get, mocked_myqueue): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) diff --git a/rowers/tests/test_urls.py b/rowers/tests/test_urls.py index 4f2d6d9c..829f7864 100644 --- a/rowers/tests/test_urls.py +++ b/rowers/tests/test_urls.py @@ -198,6 +198,7 @@ class URLTests(TestCase): @patch('requests.post',side_effect=mocked_requests) @patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data) @patch('rowers.views.statements.sendmail',side_effect=mocked_sendmail) + @patch('django.contrib.gis.geoip2.GeoIP2.city', side_effect=mocked_requests) def test_url_generator(self,url,expected, mocked_sqlalchemy, mocked_read_df_sql, @@ -205,7 +206,8 @@ class URLTests(TestCase): mock_get, mock_post, mocked_get_video_data, - mocked_sendmail): + mocked_sendmail, + mock_get2): if url not in tested: login = self.c.login(username='john',password='koeinsloot') diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 3c0a65ca9ac545281fb7f0d3324d570c2c271e85..8058cd3557d950a0012ee8831c8091f80bed9d17 100644 GIT binary patch literal 4000 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 GfB^t@(m}2O diff --git a/rowers/views/apiviews.py b/rowers/views/apiviews.py index 1937d9fd..bba37957 100644 --- a/rowers/views/apiviews.py +++ b/rowers/views/apiviews.py @@ -517,7 +517,6 @@ def strokedatajson_v3(request): } """ - print("V3") if request.method != 'POST': return HttpResponseNotAllowed("Method not supported") # pragma: no cover From bafbd98c3df94acfcd1f571752871c6eadf346e6 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Thu, 13 Jun 2024 15:27:11 +0200 Subject: [PATCH 2/2] fixing filtering --- rowers/dataroutines.py | 18 ++++++++++++++---- rowers/interactiveplots.py | 15 ++++++++++----- rowers/metrics.py | 1 + rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4001 bytes 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index 92c83cb7..154d75f8 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -1512,7 +1512,7 @@ def getrowdata_pl(id=0, doclean=False, convertnewtons=True, def read_data(columns, ids=[], doclean=True, workstrokesonly=True, debug=False, for_chart=False, compute=True, - startenddict={}): + startenddict={}, driveenergy=True): if ids: csvfilenames = [ 'media/strokedata_{id}.parquet.gz'.format(id=id) for id in ids] @@ -1520,7 +1520,9 @@ def read_data(columns, ids=[], doclean=True, workstrokesonly=True, debug=False, return pl.DataFrame() data = [] - columns = [c for c in columns if c != 'None'] + ['distance', 'spm', 'workoutid','workoutstate', 'driveenergy'] + columns = [c for c in columns if c != 'None'] + ['distance', 'spm', 'workoutid','workoutstate'] + if driveenergy: + columns = columns + ['driveenergy'] columns = list(set(columns)) for id, f in zip(ids, csvfilenames): @@ -1583,9 +1585,10 @@ def read_data(columns, ids=[], doclean=True, workstrokesonly=True, debug=False, if len(data)==0: return pl.DataFrame() + try: datadf = pl.concat(data).select(columns) - except (SchemaError, ShapeError): + except (ShapeError, SchemaError): data = [ df.select(columns) for df in data] @@ -1593,6 +1596,7 @@ def read_data(columns, ids=[], doclean=True, workstrokesonly=True, debug=False, # float columns floatcolumns = [] intcolumns = [] + stringcolumns = [] for c in columns: try: if metricsdicts[c]['numtype'] == 'float': @@ -1600,7 +1604,11 @@ def read_data(columns, ids=[], doclean=True, workstrokesonly=True, debug=False, if metricsdicts[c]['numtype'] == 'integer': intcolumns.append(c) except KeyError: - pass + if c[0] == 'f': + stringcolumns.append(c) + else: + intcolumns.append(c) + data = [ df.with_columns( cs.float().cast(pl.Float64) @@ -1610,6 +1618,8 @@ def read_data(columns, ids=[], doclean=True, workstrokesonly=True, debug=False, cs.by_name(intcolumns).cast(pl.Int64) ).with_columns( cs.by_name(floatcolumns).cast(pl.Float64) + ).with_columns( + cs.by_name(stringcolumns).cast(pl.String) ) for df in data ] diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index af49c155..2a3769ce 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1802,7 +1802,7 @@ def interactive_flexchart_stacked(id, r, xparam='time', 'metrics': metrics_list, } - script, div = get_chart("/stacked", chart_data, debug=False) + script, div = get_chart("/stacked", chart_data, debug=True) return script, div @@ -2129,16 +2129,20 @@ def interactive_multiple_compare_chart(ids, xparam, yparam, plottype='line', compute = True doclean = True + driveenergy = False + if 'driveenergy' in columns: + driveenergy = True + datadf = pl.DataFrame() if promember: datadf = dataprep.read_data(columns, ids=ids, doclean=doclean, - compute=compute, + compute=compute, driveenergy=driveenergy, workstrokesonly=workstrokesonly, for_chart=True, startenddict=startenddict) else: datadf = dataprep.read_data(columns_basic, ids=ids, doclean=doclean, - compute=compute, + compute=compute, driveenergy = driveenergy, workstrokesonly=workstrokesonly, for_chart=True, startenddict=startenddict) @@ -2165,10 +2169,10 @@ def interactive_multiple_compare_chart(ids, xparam, yparam, plottype='line', if (xparam == 'time'): datadf = datadf.with_columns((pl.col(xparam)-datadf[0,xparam]).alias(xparam)) - + data_dict = datadf.to_dicts() - metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics] + metrics_list = [{'name': name, 'rowingmetrics':{k: v for k, v in d.items() if k != 'numtype_pl'} } for name, d in metrics.rowingmetrics] try: workoutsdict = [{'id': id, 'label': labeldict[id]} for id in ids] @@ -2185,6 +2189,7 @@ def interactive_multiple_compare_chart(ids, xparam, yparam, plottype='line', 'workouts': workoutsdict, } + script, div = get_chart("/compare", chart_data, debug=False) return script, div diff --git a/rowers/metrics.py b/rowers/metrics.py index 3766addb..83299c9c 100644 --- a/rowers/metrics.py +++ b/rowers/metrics.py @@ -5,6 +5,7 @@ import numpy as np import pandas as pd from scipy import optimize from django.utils import timezone +from polars import String, Int32, Float64 from math import log10, log2 from rowers.mytypes import otwtypes, otetypes diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 8058cd3557d950a0012ee8831c8091f80bed9d17..cca9807a60c3a074c80a095ef2d65c35e1781136 100644 GIT binary patch delta 3914 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 Y^5S878ZXkN5AXaB9RSb;oy33v0KH!FGXMYp delta 3913 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