From 66365b7b8413d9f38aae0fb5726ed5c5db5322c6 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 3 Dec 2024 20:39:59 +0100 Subject: [PATCH 01/21] first batch of tests --- rowers/integrations/strava.py | 2 +- rowers/rower_rules.py | 4 +- rowers/tests/test_api.py | 233 ++++++++++++++++++++++++++++++++-- rowers/uploads.py | 2 + 4 files changed, 228 insertions(+), 13 deletions(-) diff --git a/rowers/integrations/strava.py b/rowers/integrations/strava.py index b78281a0..f6e878c1 100644 --- a/rowers/integrations/strava.py +++ b/rowers/integrations/strava.py @@ -214,7 +214,7 @@ class StravaIntegration(SyncIntegration): def get_workout(self, id, *args, **kwargs) -> int: try: _ = self.open() - except NoTokenError("Strava error"): + except NoTokenError: return 0 record = create_or_update_syncrecord(self.rower, None, stravaid=id) diff --git a/rowers/rower_rules.py b/rowers/rower_rules.py index c8a14c14..05a16994 100644 --- a/rowers/rower_rules.py +++ b/rowers/rower_rules.py @@ -479,7 +479,9 @@ def is_workout_team(user, workout): @rules.predicate def can_view_workout(user, workout): - if workout.privacy != 'private': + if workout.workoutsource == 'strava': + return user == workout.user.user + if workout.privacy not in ('hidden', 'private'): return True if user.is_anonymous: # pragma: no cover return False diff --git a/rowers/tests/test_api.py b/rowers/tests/test_api.py index 1187265e..d120c8b2 100644 --- a/rowers/tests/test_api.py +++ b/rowers/tests/test_api.py @@ -24,8 +24,229 @@ from rest_framework.test import APIRequestFactory, force_authenticate import json +# import BeautifulSoup +from bs4 import BeautifulSoup + from rowers.ownapistuff import * from rowers.views.apiviews import * +from rowers.teams import add_member, add_coach + +class TeamFactory(factory.DjangoModelFactory): + class Meta: + model = Team + + name = factory.LazyAttribute(lambda _: faker.word()) + notes = faker.text() + private = 'open' + viewing = 'allmembers' + +class StravaPrivacy(TestCase): + def setUp(self): + self.u = UserFactory() + self.u2 = UserFactory() + self.u3 = UserFactory() + + self.r = Rower.objects.create(user=self.u, + birthdate=faker.profile()['birthdate'], + gdproptin=True, ftpset=True,surveydone=True, + gdproptindate=timezone.now(), + rowerplan='coach',subscription_id=1) + + self.r.stravatoken = '12' + self.r.stravarefreshtoken = '123' + self.r.stravatokenexpirydate = arrow.get(datetime.datetime.now()-datetime.timedelta(days=1)).datetime + self.r.strava_owner_id = 4 + + self.r.save() + + self.c = Client() + self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) + for w in self.user_workouts: + w.workoutsource = 'strava' + w.privacy = 'hidden' + w.save() + + self.factory = RequestFactory() + self.password = faker.word() + self.u.set_password(self.password) + self.u.save() + self.factory = APIRequestFactory() + + self.r2 = Rower.objects.create(user=self.u2, + birthdate=faker.profile()['birthdate'], + gdproptin=True, ftpset=True,surveydone=True, + gdproptindate=timezone.now(), + rowerplan='coach',clubsize=3) + + self.r3 = Rower.objects.create(user=self.u3, + birthdate=faker.profile()['birthdate'], + gdproptin=True, ftpset=True,surveydone=True, + gdproptindate=timezone.now(), + rowerplan='basic') + + self.c = Client() + + self.password2 = faker.word() + self.u2.set_password(self.password2) + self.u2.save() + + self.password3 = faker.word() + self.u3.set_password(self.password3) + self.u3.save() + + self.team = TeamFactory(manager=self.u2) + + # all are team members + add_member(self.team.id, self.r) + add_member(self.team.id, self.r2) + add_member(self.team.id, self.r3) + + # r2 coaches r + add_coach(self.r2, self.r) + + self.factory = APIRequestFactory() + + # Test if workout with workoutsource strava and privacy hidden can be seen by coach + def test_privacy_coach(self): + login = self.c.login(username=self.u2.username, password=self.password2) + self.assertTrue(login) + + w = self.user_workouts[0] + url = reverse('workout_view',kwargs={'id':encoder.encode_hex(w.id)}) + response = self.c.get(url) + self.assertEqual(response.status_code,403) + + # Test if workout with workoutsource strava and privacy hidden can be seen by team member + def test_privacy_member(self): + login = self.c.login(username=self.u3.username, password=self.password3) + self.assertTrue(login) + + w = self.user_workouts[0] + url = reverse('workout_view',kwargs={'id':encoder.encode_hex(w.id)}) + response = self.c.get(url) + self.assertEqual(response.status_code,403) + + # same test as above but with user r and the response code should be 200 + def test_privacy_owner(self): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + w = self.user_workouts[0] + url = reverse('workout_view',kwargs={'id':encoder.encode_hex(w.id)}) + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + + + # test if list_workouts returns all workouts for user r + def test_list_workouts(self): + 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) + + # the response.content is html, so we need to parse it + soup = BeautifulSoup(response.content, 'html.parser') + # the workouts look like ... and there should be 5 unique ids + # the id is a hex string + workouts = set([a['href'].split('/')[3] for a in soup.find_all('a') if a['href'].startswith('/rowers/workout/')]) + + # throw out "c2import", "nkimport", "stravaimport", "concept2import", "sporttracksimport" from the set + workouts = set([w for w in workouts if w not in [ + 'upload', 'addmanual', 'c2import', 'polarimport', 'rp3import', 'nkimport', 'stravaimport', 'concept2import', 'sporttracksimport']]) + + self.assertEqual(len(workouts),5) + + # same test as above but list_workouts with team id = self.team.id + def test_list_workouts_team(self): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + url = reverse('workouts_view',kwargs={'teamid':self.team.id}) + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + # the response.content is html, so we need to parse it + soup = BeautifulSoup(response.content, 'html.parser') + # the workouts look like ... and there should be 5 unique ids + # the id is a hex string + workouts = set([a['href'].split('/')[3] for a in soup.find_all('a') if a['href'].startswith('/rowers/workout/')]) + + # throw out "c2import", "nkimport", "stravaimport", "concept2import", "sporttracksimport" from the set + workouts = set([w for w in workouts if w not in [ + 'upload', 'addmanual', 'c2import', 'polarimport', 'rp3import', 'nkimport', 'stravaimport', 'concept2import', 'sporttracksimport']]) + + self.assertEqual(len(workouts),0) + + # same test as the previous one but with self.r2 and the number of workouts found should 0 + def test_list_workouts_team_coach(self): + login = self.c.login(username=self.u2.username, password=self.password2) + self.assertTrue(login) + + url = reverse('workouts_view',kwargs={'teamid':self.team.id}) + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + # the response.content is html, so we need to parse it + soup = BeautifulSoup(response.content, 'html.parser') + # the workouts look like ... and there should be 5 unique ids + # the id is a hex string + workouts = set([a['href'].split('/')[3] for a in soup.find_all('a') if a['href'].startswith('/rowers/workout/')]) + + # throw out "c2import", "nkimport", "stravaimport", "concept2import", "sporttracksimport" from the set + workouts = set([w for w in workouts if w not in [ + 'upload', 'addmanual', 'c2import', 'polarimport', 'rp3import', 'nkimport', 'stravaimport', 'concept2import', 'sporttracksimport']]) + + self.assertEqual(len(workouts),0) + + # same test as the previous one but with self.r3 and the number of workouts found should 0 + def test_list_workouts_team_member(self): + login = self.c.login(username=self.u3.username, password=self.password3) + self.assertTrue(login) + + url = reverse('workouts_view',kwargs={'teamid':self.team.id}) + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + # the response.content is html, so we need to parse it + soup = BeautifulSoup(response.content, 'html.parser') + # the workouts look like ... and there should be 5 unique ids + # the id is a hex string + workouts = set([a['href'].split('/')[3] for a in soup.find_all('a') if a['href'].startswith('/rowers/workout/')]) + + # throw out "c2import", "nkimport", "stravaimport", "concept2import", "sporttracksimport" from the set + workouts = set([w for w in workouts if w not in [ + 'upload', 'addmanual', 'c2import', 'polarimport', 'rp3import', 'nkimport', 'stravaimport', 'concept2import', 'sporttracksimport']]) + + self.assertEqual(len(workouts),0) + + # now test strava import and test if the created workout has workoutsource strava and privacy hidden + @patch('rowers.utils.requests.get', side_effect=mocked_requests) + @patch('rowers.integrations.strava.requests.post', side_effect=mocked_requests) + @patch('rowers.dataprep.read_data') + def test_stravaimport(self, mock_get, mock_post, mocked_read_data): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + # remove all self.workouts + Workout.objects.filter(user=self.r).delete() + + # get the strava data like in test_strava_import in test_imports.py + response = self.c.get('/rowers/workout/stravaimport/12', follow=True) + expected_url = reverse('workout_import_view', kwargs={'source':'strava'}) + self.assertRedirects(response, expected_url, status_code=301, target_status_code=200) + + # check if the workout was created + ws = Workout.objects.filter(user=self.r) + self.assertEqual(len(ws),1) + w = ws[0] + self.assertEqual(w.workoutsource,'strava') + self.assertEqual(w.privacy,'hidden') + + + class OwnApi(TestCase): def setUp(self): @@ -35,17 +256,7 @@ class OwnApi(TestCase): birthdate=faker.profile()['birthdate'], gdproptin=True, ftpset=True,surveydone=True, gdproptindate=timezone.now(), - rowerplan='coach',subscription_id=1) - - - self.c = Client() - 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.factory = APIRequestFactory() + rowerplan='pro',subscription_id=1) def test_strokedataform(self): diff --git a/rowers/uploads.py b/rowers/uploads.py index 781271f7..83846898 100644 --- a/rowers/uploads.py +++ b/rowers/uploads.py @@ -141,6 +141,8 @@ def do_sync(w, options, quick=False): w.uploadedtostrava = options['stravaid'] # upload_to_strava = False do_strava_export = False + w.workoutsource = 'strava' + w.privacy = 'hidden' w.save() record = create_or_update_syncrecord(w.user, w, stravaid=options['stravaid']) except KeyError: From 8d888fcd4deff3c5121d2268bda2cc8bebb6c8d9 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 3 Dec 2024 23:21:12 +0100 Subject: [PATCH 02/21] passing basic tests developed --- rowers/tests/testdata/thyro.csv.gz | Bin 0 -> 95992 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rowers/tests/testdata/thyro.csv.gz diff --git a/rowers/tests/testdata/thyro.csv.gz b/rowers/tests/testdata/thyro.csv.gz new file mode 100644 index 0000000000000000000000000000000000000000..df06716c52699d2ae73b9b8539d9f9aa337c99fc GIT binary patch literal 95992 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 Date: Tue, 3 Dec 2024 23:21:26 +0100 Subject: [PATCH 03/21] passing basic new tests --- rowers/dataprep.py | 5 ++- rowers/tests/test_api.py | 83 ++++++++++++++++++++++++++++++------ rowers/views/workoutviews.py | 2 +- 3 files changed, 76 insertions(+), 14 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 8d6ed877..bfd4921a 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -1304,8 +1304,11 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', if makeprivate: # pragma: no cover privacy = 'hidden' - else: + elif workoutsource != 'strava': privacy = 'visible' + else: + privacy = 'hidden' + # checking for inf values diff --git a/rowers/tests/test_api.py b/rowers/tests/test_api.py index d120c8b2..aaccbaa2 100644 --- a/rowers/tests/test_api.py +++ b/rowers/tests/test_api.py @@ -22,6 +22,9 @@ from rowers.opaque import encoder from rest_framework.test import APIRequestFactory, force_authenticate +UPLOAD_SERVICE_URL = '/rowers/workout/api/upload/' +UPLOAD_SERVICE_SECRET = "FoYezZWLSyfAVimumpHEeYsJjsNCerxV" + import json # import BeautifulSoup @@ -60,11 +63,6 @@ class StravaPrivacy(TestCase): self.r.save() self.c = Client() - self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) - for w in self.user_workouts: - w.workoutsource = 'strava' - w.privacy = 'hidden' - w.save() self.factory = RequestFactory() self.password = faker.word() @@ -101,6 +99,17 @@ class StravaPrivacy(TestCase): add_member(self.team.id, self.r2) add_member(self.team.id, self.r3) + self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) + for w in self.user_workouts: + if w.id <= 3: + w.workoutsource = 'strava' + w.privacy = 'hidden' + else: + w.workoutsource = 'concept2' + w.privacy = 'visible' + w.team.add(self.team) + w.save() + # r2 coaches r add_coach(self.r2, self.r) @@ -178,7 +187,7 @@ class StravaPrivacy(TestCase): workouts = set([w for w in workouts if w not in [ 'upload', 'addmanual', 'c2import', 'polarimport', 'rp3import', 'nkimport', 'stravaimport', 'concept2import', 'sporttracksimport']]) - self.assertEqual(len(workouts),0) + self.assertEqual(len(workouts),2) # same test as the previous one but with self.r2 and the number of workouts found should 0 def test_list_workouts_team_coach(self): @@ -199,7 +208,7 @@ class StravaPrivacy(TestCase): workouts = set([w for w in workouts if w not in [ 'upload', 'addmanual', 'c2import', 'polarimport', 'rp3import', 'nkimport', 'stravaimport', 'concept2import', 'sporttracksimport']]) - self.assertEqual(len(workouts),0) + self.assertEqual(len(workouts),2) # same test as the previous one but with self.r3 and the number of workouts found should 0 def test_list_workouts_team_member(self): @@ -220,7 +229,7 @@ class StravaPrivacy(TestCase): workouts = set([w for w in workouts if w not in [ 'upload', 'addmanual', 'c2import', 'polarimport', 'rp3import', 'nkimport', 'stravaimport', 'concept2import', 'sporttracksimport']]) - self.assertEqual(len(workouts),0) + self.assertEqual(len(workouts),2) # now test strava import and test if the created workout has workoutsource strava and privacy hidden @patch('rowers.utils.requests.get', side_effect=mocked_requests) @@ -233,10 +242,10 @@ class StravaPrivacy(TestCase): # remove all self.workouts Workout.objects.filter(user=self.r).delete() - # get the strava data like in test_strava_import in test_imports.py - response = self.c.get('/rowers/workout/stravaimport/12', follow=True) - expected_url = reverse('workout_import_view', kwargs={'source':'strava'}) - self.assertRedirects(response, expected_url, status_code=301, target_status_code=200) + # create a workout using dataprep.new_workout_from_file with workoutsource = strava + result = get_random_file(filename='rowers/tests/testdata/thyro.csv') + workout_id, message, filename = dataprep.new_workout_from_file(self.r, result['filename'], + workoutsource='strava', makeprivate=True) # check if the workout was created ws = Workout.objects.filter(user=self.r) @@ -245,6 +254,56 @@ class StravaPrivacy(TestCase): self.assertEqual(w.workoutsource,'strava') self.assertEqual(w.privacy,'hidden') + # same as test above but makeprivate = False + @patch('rowers.utils.requests.get', side_effect=mocked_requests) + @patch('rowers.integrations.strava.requests.post', side_effect=mocked_requests) + @patch('rowers.dataprep.read_data') + def test_stravaimport_public(self, mock_get, mock_post, mocked_read_data): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + # remove all self.workouts + Workout.objects.filter(user=self.r).delete() + + # create a workout using dataprep.new_workout_from_file with workoutsource = strava + result = get_random_file(filename='rowers/tests/testdata/thyro.csv') + workout_id, message, filename = dataprep.new_workout_from_file(self.r, result['filename'], + workoutsource='strava', makeprivate=False) + + # check if the workout was created + ws = Workout.objects.filter(user=self.r) + self.assertEqual(len(ws),1) + w = ws[0] + self.assertEqual(w.workoutsource,'strava') + self.assertEqual(w.privacy,'hidden') + + + # test ownapi with stravaid = '122' + def test_ownapi(self): + # remove all self.workouts + Workout.objects.filter(user=self.r).delete() + + result = get_random_file(filename='rowers/tests/testdata/thyro.csv') + uploadoptions = { + 'workouttype': 'water', + 'boattype': '1x', + 'notes': 'A test file upload', + 'stravaid': '122', + 'secret': UPLOAD_SERVICE_SECRET, + 'user': self.u.id, + 'file': result['filename'], + } + url = reverse('workout_upload_api') + response = self.c.post(url, uploadoptions) + self.assertEqual(response.status_code,200) + + # check if the workout was created + ws = Workout.objects.filter(user=self.r) + self.assertEqual(len(ws),1) + w = ws[0] + self.assertEqual(w.workoutsource,'strava') + self.assertEqual(w.privacy,'hidden') + diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index eb0aed30..063e4751 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -4934,7 +4934,7 @@ def workout_upload_api(request): # only allow local host hostt = request.get_host().split(':') - if hostt[0] not in ['localhost', '127.0.0.1', 'dev.rowsandall.com', 'rowsandall.com']: + if hostt[0] not in ['localhost', '127.0.0.1', 'dev.rowsandall.com', 'rowsandall.com','testserver']: message = {'status': 'false', 'message': 'permission denied for host '+hostt[0]} return JSONResponse(status=403, data=message) From fd7e7e35e5c3577ebd81a247b2d1b84c24f999d4 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 4 Dec 2024 00:34:27 +0100 Subject: [PATCH 04/21] added a simple analysis test but should be altered --- rowers/tests/test_api.py | 93 ++++++++++++++++++++++++++++++++++- rowers/views/analysisviews.py | 4 ++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/rowers/tests/test_api.py b/rowers/tests/test_api.py index aaccbaa2..8563eb11 100644 --- a/rowers/tests/test_api.py +++ b/rowers/tests/test_api.py @@ -33,6 +33,7 @@ from bs4 import BeautifulSoup from rowers.ownapistuff import * from rowers.views.apiviews import * from rowers.teams import add_member, add_coach +from rowers.views.analysisviews import histodata class TeamFactory(factory.DjangoModelFactory): class Meta: @@ -303,7 +304,97 @@ class StravaPrivacy(TestCase): w = ws[0] self.assertEqual(w.workoutsource,'strava') self.assertEqual(w.privacy,'hidden') - + + + # test some analysis, should only use the workouts with workoutsource != strava + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) + def test_workouts_analysis(self, mocked_read_data): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + url = '/rowers/history/' + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + url = '/rowers/history/data/' + response = self.c.get(url) + self.assertEqual(response.status_code,200) + # response.json() has a key "script" with a javascript script + # check if this is correct + self.assertTrue('script' in response.json()) + + # now check histogram + startdate = (self.user_workouts[0].startdatetime-datetime.timedelta(days=3)).date() + enddate = (self.user_workouts[0].startdatetime+datetime.timedelta(days=3)).date() + + # make sure the dates are not naive + try: + startdate = pytz.utc.localize(startdate) + except (ValueError, AttributeError): + pass + try: + enddate = pytz.utc.localize(enddate) + except (ValueError, AttributeError): + pass + + form_data = { + 'function':'histo', + 'xparam':'hr', + 'plotfield':'spm', + 'yparam':'pace', + 'groupby':'spm', + 'palette':'monochrome_blue', + 'xaxis':'time', + 'yaxis1':'power', + 'yaxis2':'hr', + 'startdate':startdate, + 'enddate':enddate, + 'plottype':'scatter', + 'spmmin':15, + 'spmmax':55, + 'workmin':0, + 'workmax':1500, + 'includereststrokes':False, + 'modality':'all', + 'waterboattype':['1x','2x','4x'], + 'userid':self.u.id, + 'workouts':[w.id for w in Workout.objects.filter(user=self.r)], + } + + form = AnalysisChoiceForm(form_data) + optionsform = AnalysisOptionsForm(form_data) + dateform = DateRangeForm(form_data) + + result = form.is_valid() + if not result: + print(form.errors) + + self.assertTrue(form.is_valid()) + self.assertTrue(optionsform.is_valid()) + self.assertTrue(dateform.is_valid()) + + response = self.c.post('/rowers/user-analysis-select/',form_data) + + self.assertEqual(response.status_code,200) + + # get data from histodata function + ws = Workout.objects.filter(user=self.r) + + script, div = histodata(ws,form_data) + # script has a line starting with 'data = [ ... ]' + # we need to get that line + data = [line for line in script.split('\n') if line.startswith('data = [')][0] + # the line should be a list of float values + self.assertTrue(data.startswith('data = [')) + self.assertTrue(data.endswith(']')) + # count the number of commas between the brackets + self.assertEqual(data.count(','),1377) + + + + + + # try and fail to submit a workout with workoutsource = strava to a challenge diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index 1938df26..b3368972 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -2276,6 +2276,8 @@ def history_view_data(request, userid=0): ddf = ddf.with_columns(pl.col("time").diff().clip(lower_bound=0).alias("deltat")) except KeyError: # pragma: no cover pass + except ColumnNotFoundError: + pass ddf = dataprep.clean_df_stats_pl(ddf, workstrokesonly=False, ignoreadvanced=True) @@ -2288,6 +2290,8 @@ def history_view_data(request, userid=0): ddict['hrmax'] = int(ddf['hr'].max()) except (KeyError, ValueError, AttributeError): # pragma: no cover ddict['hrmax'] = 0 + except ColumnNotFoundError: + ddict['hrmax'] = 0 ddict['powermean'] = int(wavg(ddf, 'power', 'deltat')) try: From 1daed901040b752592370bfde2fb236fbbed4f64 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 4 Dec 2024 21:02:58 +0100 Subject: [PATCH 05/21] excluding strava from analysis --- rowers/dataprep.py | 3 ++ rowers/interactiveplots.py | 2 +- rowers/models.py | 24 +++++++++++- rowers/rower_rules.py | 10 +++++ rowers/tests/test_api.py | 72 +++++++++++++++++++++++++++++++++-- rowers/views/analysisviews.py | 34 ++++++++++++----- rowers/views/statements.py | 1 + rowers/views/workoutviews.py | 17 +++++---- 8 files changed, 138 insertions(+), 25 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index bfd4921a..2e1ea17e 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -217,6 +217,9 @@ def workout_goldmedalstandard(workout, reset=False): def check_marker(workout): r = workout.user + if workout.workoutsource == 'strava': + return None + gmstandard, gmseconds = workout_goldmedalstandard(workout) if gmseconds < 60: return None diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 4e4d4c74..2f0d2a5d 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -550,7 +550,7 @@ def goldmedalscorechart(user, startdate=None, enddate=None): workouts = Workout.objects.filter(user=user.rower, date__gte=startdate, date__lte=enddate, workouttype__in=mytypes.rowtypes, - duplicate=False).order_by('date') + duplicate=False).order_by('date').exclude(workoutsource='strava') markerworkouts = workouts.filter(rankingpiece=True) outids = [w.id for w in markerworkouts] diff --git a/rowers/models.py b/rowers/models.py index 5ddc51dc..7c196450 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -1423,9 +1423,26 @@ parchoicesy1 = list(sorted(favchartlabelsy1.items(), key=lambda x: x[1])) parchoicesy2 = list(sorted(favchartlabelsy2.items(), key=lambda x: x[1])) parchoicesx = list(sorted(favchartlabelsx.items(), key=lambda x: x[1])) +# special filter for workouts to exclude strava workouts by default +class WorkoutQuerySet(models.QuerySet): + def filter(self, *args, exclude_strava=True, **kwargs): + queryset = super().filter(*args, **kwargs) + if exclude_strava: + queryset = queryset.exclude(workoutsource='strava') + + return queryset + + def get(self, *args, **kwargs): + queryset = self + + return super().get(*args, **kwargs) + + +class WorkoutManager(models.Manager): + def get_queryset(self): + return WorkoutQuerySet(self.model, using=self._db) + # Saving a chart as a favorite chart - - class FavoriteChart(models.Model): workouttypechoices = [ ('ote', 'Erg/SkiErg'), @@ -3704,6 +3721,9 @@ class Workout(models.Model): default=False, verbose_name='Duplicate Workout') impeller = models.BooleanField(default=False, verbose_name='Impeller') + # attach the WorkoutManager + #objects = WorkoutManager() + def url(self): str = '/rowers/workout/{id}/'.format( id=encoder.encode_hex(self.id) diff --git a/rowers/rower_rules.py b/rowers/rower_rules.py index 05a16994..ed5cd2dd 100644 --- a/rowers/rower_rules.py +++ b/rowers/rower_rules.py @@ -451,6 +451,11 @@ def is_workout_user(user, workout): except AttributeError: # pragma: no cover return False + if workout.privacy == 'hidden': + return user == workout.user.user + if workout.workoutsource == 'strava': + return user == workout.user.user + if workout.user == r: return True @@ -469,6 +474,11 @@ def is_workout_team(user, workout): except AttributeError: # pragma: no cover return False + if workout.privacy == 'hidden': + return user == workout.user.user + if workout.workoutsource == 'strava': + return user == workout.user.user + if workout.user == r: return True diff --git a/rowers/tests/test_api.py b/rowers/tests/test_api.py index 8563eb11..8bd91a55 100644 --- a/rowers/tests/test_api.py +++ b/rowers/tests/test_api.py @@ -102,13 +102,17 @@ class StravaPrivacy(TestCase): self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) for w in self.user_workouts: - if w.id <= 3: + if w.id <= 2: w.workoutsource = 'strava' w.privacy = 'hidden' + elif w.id == 3: # user can change privacy but cannot change workoutsource + w.workoutsource = 'strava' + w.privacy = 'visible' else: w.workoutsource = 'concept2' w.privacy = 'visible' w.team.add(self.team) + w.csvfilename = get_random_file(filename='rowers/tests/testdata/thyro.csv')['filename'] w.save() # r2 coaches r @@ -116,6 +120,13 @@ class StravaPrivacy(TestCase): self.factory = APIRequestFactory() + def tearDown(self): + for workout in self.user_workouts: + try: + os.remove(workout.csvfilename) + except (OSError, FileNotFoundError, IOError): + pass + # Test if workout with workoutsource strava and privacy hidden can be seen by coach def test_privacy_coach(self): login = self.c.login(username=self.u2.username, password=self.password2) @@ -126,6 +137,16 @@ class StravaPrivacy(TestCase): response = self.c.get(url) self.assertEqual(response.status_code,403) + # Same test as above but for 'workout_edit_view' + def test_privacy_coach_edit(self): + login = self.c.login(username=self.u2.username, password=self.password2) + self.assertTrue(login) + + w = self.user_workouts[0] + url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)}) + response = self.c.get(url) + self.assertEqual(response.status_code,403) + # Test if workout with workoutsource strava and privacy hidden can be seen by team member def test_privacy_member(self): login = self.c.login(username=self.u3.username, password=self.password3) @@ -136,6 +157,16 @@ class StravaPrivacy(TestCase): response = self.c.get(url) self.assertEqual(response.status_code,403) + # Same test as above but for 'workout_edit_view' + def test_privacy_member_edit(self): + login = self.c.login(username=self.u3.username, password=self.password3) + self.assertTrue(login) + + w = self.user_workouts[0] + url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)}) + response = self.c.get(url) + self.assertEqual(response.status_code,403) + # same test as above but with user r and the response code should be 200 def test_privacy_owner(self): login = self.c.login(username=self.u.username, password=self.password) @@ -146,6 +177,16 @@ class StravaPrivacy(TestCase): response = self.c.get(url) self.assertEqual(response.status_code,200) + # same test as above but for 'workout_edit_view' + def test_privacy_owner_edit(self): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + w = self.user_workouts[0] + url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)}) + response = self.c.get(url) + self.assertEqual(response.status_code,200) + # test if list_workouts returns all workouts for user r @@ -168,6 +209,7 @@ class StravaPrivacy(TestCase): 'upload', 'addmanual', 'c2import', 'polarimport', 'rp3import', 'nkimport', 'stravaimport', 'concept2import', 'sporttracksimport']]) self.assertEqual(len(workouts),5) + # same test as above but list_workouts with team id = self.team.id def test_list_workouts_team(self): @@ -211,6 +253,27 @@ class StravaPrivacy(TestCase): self.assertEqual(len(workouts),2) + # same test as above but with without the teamid kwarg but with a rowerid=self.r.id + def test_list_workouts_team_coach2(self): + login = self.c.login(username=self.u2.username, password=self.password2) + self.assertTrue(login) + + url = reverse('workouts_view',kwargs={'rowerid':self.r.id}) + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + # the response.content is html, so we need to parse it + soup = BeautifulSoup(response.content, 'html.parser') + # the workouts look like ... and there should be 5 unique ids + # the id is a hex string + workouts = set([a['href'].split('/')[3] for a in soup.find_all('a') if a['href'].startswith('/rowers/workout/')]) + + # throw out "c2import", "nkimport", "stravaimport", "concept2import", "sporttracksimport" from the set + workouts = set([w for w in workouts if w not in [ + 'upload', 'addmanual', 'c2import', 'polarimport', 'rp3import', 'nkimport', 'stravaimport', 'concept2import', 'sporttracksimport']]) + + self.assertEqual(len(workouts),2) + # same test as the previous one but with self.r3 and the number of workouts found should 0 def test_list_workouts_team_member(self): login = self.c.login(username=self.u3.username, password=self.password3) @@ -307,8 +370,9 @@ class StravaPrivacy(TestCase): # test some analysis, should only use the workouts with workoutsource != strava - @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) - def test_workouts_analysis(self, mocked_read_data): + #@patch('rowers.dataprep.read_data', side_effect=mocked_read_data) + #def test_workouts_analysis(self, mocked_read_data): + def test_workouts_analysis(self): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -388,7 +452,7 @@ class StravaPrivacy(TestCase): self.assertTrue(data.startswith('data = [')) self.assertTrue(data.endswith(']')) # count the number of commas between the brackets - self.assertEqual(data.count(','),1377) + self.assertEqual(data.count(','),2062) diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index b3368972..c11ef6c0 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -199,14 +199,14 @@ def analysis_new(request, startdatetime__lte=enddate, workouttype__in=modalities, rankingpiece__in=rankingtypes, - ) + ).exclude(workoutsource='strava') elif theteam is not None and theteam.viewing == 'coachonly': # pragma: no cover workouts = Workout.objects.filter(team=theteam, user=r, startdatetime__gte=startdate, startdatetime__lte=enddate, workouttype__in=modalities, rankingpiece__in=rankingtypes, - ) + ).exclude(workoutsource='strava') elif thesession is not None: workouts = get_workouts_session(r, thesession) else: @@ -363,6 +363,7 @@ def trendflexdata(workouts, options, userid=0): savedata = options.get('savedata',False) + workouts = workouts.exclude(workoutsource='strava') fieldlist, fielddict = dataprep.getstatsfields() fieldlist = [xparam, yparam, groupby, @@ -566,6 +567,8 @@ def flexalldata(workouts, options): trendline = options['trendline'] promember = True + workouts = workouts.exclude(workoutsource='strava') + workstrokesonly = not includereststrokes userid = options['userid'] @@ -612,6 +615,9 @@ def histodata(workouts, options): workmax = options['workmax'] userid = options['userid'] + workouts = workouts.exclude(workoutsource='strava') + + if userid == 0: # pragma: no cover extratitle = '' else: @@ -645,7 +651,8 @@ def cpdata(workouts, options): u = User.objects.get(id=userid) r = u.rower - + + delta, cpvalue, avgpower, workoutnames, urls = dataprep.fetchcp_new( r, workouts) @@ -798,6 +805,8 @@ def cpdata(workouts, options): def statsdata(workouts, options): + workouts = workouts.exclude(workoutsource='strava') + includereststrokes = options['includereststrokes'] ids = options['ids'] @@ -872,12 +881,13 @@ def statsdata(workouts, options): def comparisondata(workouts, options): + workouts = workouts.exclude(workoutsource='strava') includereststrokes = options['includereststrokes'] xparam = options['xaxis'] yparam1 = options['yaxis1'] plottype = options['plottype'] promember = True - + workstrokesonly = not includereststrokes ids = [w.id for w in workouts] @@ -915,6 +925,7 @@ def comparisondata(workouts, options): def boxplotdata(workouts, options): + workouts = workouts.exclude(workoutsource='strava') includereststrokes = options['includereststrokes'] spmmin = options['spmmin'] @@ -926,7 +937,7 @@ def boxplotdata(workouts, options): plotfield = options['plotfield'] workstrokesonly = not includereststrokes - + datemapping = { w.id: w.date for w in workouts } @@ -1020,11 +1031,14 @@ def analysis_view_data(request, userid=0): for id in ids: try: - workouts.append(Workout.objects.get(id=id)) + w = Workout.objects.get(id=id) + if w.workoutsource != 'strava': + workouts.append(w) except Workout.DoesNotExist: # pragma: no cover pass + if function == 'boxplot': script, div = boxplotdata(workouts, options) elif function == 'trendflex': # pragma: no cover @@ -1069,7 +1083,7 @@ def create_marker_workouts_view(request, userid=0, workouts = Workout.objects.filter(user=theuser.rower, date__gte=startdate, date__lte=enddate, workouttype__in=mytypes.rowtypes, - duplicate=False).order_by('date') + duplicate=False).order_by('date').exclude(workoutsource='strava') for workout in workouts: _ = dataprep.check_marker(workout) @@ -1113,7 +1127,7 @@ def goldmedalscores_view(request, userid=0, theuser, startdate=startdate, enddate=enddate, ) - bestworkouts = Workout.objects.filter(id__in=ids).order_by('-date') + bestworkouts = Workout.objects.filter(id__in=ids).order_by('-date').exclude(workoutsource='strava') breadcrumbs = [ { @@ -1311,7 +1325,7 @@ def performancemanager_view(request, userid=0, mode='rower', user = therower, date__gte=startdate-datetime.timedelta(days=90), date__lte=enddate, duplicate=False, - rankingpiece=True, workouttype__in=mytypes.rowtypes).order_by('date') + rankingpiece=True, workouttype__in=mytypes.rowtypes).order_by('date').exclude(workoutsource='strava') ids = [w.id for w in markerworkouts] form = PerformanceManagerForm(initial={ @@ -1323,7 +1337,7 @@ def performancemanager_view(request, userid=0, mode='rower', ids = pd.Series(ids, dtype='int').dropna().values - bestworkouts = Workout.objects.filter(id__in=ids).order_by('-date') + bestworkouts = Workout.objects.filter(id__in=ids).order_by('-date').exclude(workoutsource='strava') breadcrumbs = [ { diff --git a/rowers/views/statements.py b/rowers/views/statements.py index d134013d..eeb04039 100644 --- a/rowers/views/statements.py +++ b/rowers/views/statements.py @@ -28,6 +28,7 @@ from rest_framework.response import Response from rq.job import Job from rules.contrib.views import permission_required, objectgetter from django.core.cache import cache +from django.db import models from django.utils.crypto import get_random_string from rq.registry import StartedJobRegistry from rq.exceptions import NoSuchJobError diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 063e4751..67a655af 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -2204,25 +2204,25 @@ def workouts_view(request, message='', successmessage='', team=theteam, startdatetime__gte=startdate, startdatetime__lte=enddate, - privacy='visible').order_by("-date", "-starttime") + privacy='visible').order_by("-date", "-starttime").exclude(workoutsource='strava') g_workouts = Workout.objects.filter( team=theteam, startdatetime__gte=activity_startdate, startdatetime__lte=activity_enddate, duplicate=False, - privacy='visible').order_by("-date", "-starttime") + privacy='visible').order_by("-date", "-starttime").exclude(workoutsource='strava') elif theteam.viewing == 'coachonly': # pragma: no cover workouts = Workout.objects.filter( team=theteam, user=r, startdatetime__gte=startdate, startdatetime__lte=enddate, - privacy='visible').order_by("-startdatetime") + privacy='visible').order_by("-startdatetime").exclude(workoutsource='strava') g_workouts = Workout.objects.filter( team=theteam, user=r, startdatetime__gte=activity_startdate, startdatetime__lte=activity_enddate, duplicate=False, - privacy='visible').order_by("-startdatetime") + privacy='visible').order_by("-startdatetime").exclude(workoutsource='strava') elif request.user != r.user: theteam = None @@ -2230,13 +2230,13 @@ def workouts_view(request, message='', successmessage='', user=r, startdatetime__gte=startdate, startdatetime__lte=enddate, - privacy='visible').order_by("-date", "-starttime") + privacy='visible').order_by("-date", "-starttime").exclude(workoutsource='strava') g_workouts = Workout.objects.filter( user=r, startdatetime__gte=activity_startdate, startdatetime__lte=activity_enddate, duplicate=False, - privacy='visible').order_by("-startdatetime") + privacy='visible').order_by("-startdatetime").exclude(workoutsource='strava') else: theteam = None workouts = Workout.objects.filter( @@ -2252,7 +2252,7 @@ def workouts_view(request, message='', successmessage='', if g_workouts.count() == 0: g_workouts = Workout.objects.filter( user=r, - startdatetime__gte=timezone.now()-timedelta(days=15)).order_by("-startdatetime") + startdatetime__gte=timezone.now()-timedelta(days=15)).order_by("-startdatetime").exclude(workoutsource='strava') g_enddate = timezone.now() g_startdate = (timezone.now()-timedelta(days=15)) @@ -2266,7 +2266,8 @@ def workouts_view(request, message='', successmessage='', reduce(operator.and_, (Q(name__icontains=q) for q in query_list)) | reduce(operator.and_, - (Q(notes__icontains=q) for q in query_list)) + (Q(notes__icontains=q) for q in query_list)), + exclude_strava=False, ) searchform = SearchForm(initial={'q': query}) else: From 13effe6cce58a9d87bea0d31c6181922edc5e708 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 4 Dec 2024 21:19:38 +0100 Subject: [PATCH 06/21] getting all tests to pass, improved developers page --- rowers/templates/developers.html | 19 ++++++++-------- rowers/tests/test_api.py | 8 +++++++ rowers/tests/testdata/testdata.tcx.gz | Bin 3998 -> 4000 bytes rowers/views/analysisviews.py | 31 +++++++++++++++++++++----- 4 files changed, 42 insertions(+), 16 deletions(-) diff --git a/rowers/templates/developers.html b/rowers/templates/developers.html index ff0f01d0..3d966f82 100644 --- a/rowers/templates/developers.html +++ b/rowers/templates/developers.html @@ -8,7 +8,7 @@
  • -

    On this page, a work in progress, I will collect useful information +

    On this page, I will collect useful information for developers of rowing data apps and hardware.

    I presume you have an app (smartphone app, dedicated hardware, web site) @@ -61,11 +61,11 @@

Using the REST API

-

We are building a REST API which will allow you to post and +

We have a REST API which will allow you to post and receive stroke data from the site directly.

-

The REST API is a work in progress. We are open to improvement +

We are open to improvement suggestions (provided they don't break existing apps). Please send email to info@rowsandall.com with questions and/or suggestions. We @@ -84,7 +84,6 @@

  • Disadvantages

      -
    • The API is not stable and not fully tested yet.
    • You need to register your app with us. We can revoke your permissions if you misuse them.
    • The user user must grant permissions to your app.
    • @@ -114,7 +113,7 @@

      We have disabled the self service app link for security reasons. - We will replace it with a secure self service app link soon. If you + If you need to register an app, please send email to info@rowsandall.com

      Authentication

      @@ -728,11 +727,11 @@
    • peakdriveforce: Peak handle force (lbs)
    • lapidx: Lap identifier
    • hr: Heart rate (beats per minute)
    • -
    • wash: Wash as defined per Empower oarlock (degrees)
    • -
    • catch: Catch angle per Empower oarlock (degrees)
    • -
    • finish: Finish angle per Empower oarlock (degrees)
    • -
    • peakforceangle: Peak Force Angle per Empower oarlock (degrees)
    • -
    • slip: Slip as defined per Empower oarlock (degrees)
    • +
    • wash: Wash as defined for your smart power measuring oarlock (degrees)
    • +
    • catch: Catch angle for your smart power measuring oarlock (degrees)
    • +
    • finish: Finish angle for your smart power measuring oarlock (degrees)
    • +
    • peakforceangle: Peak Force Angle for your smart power measuring oarlock (degrees)
    • +
    • slip: Slip as defined for your smart power measuring oarlock (degrees)

    diff --git a/rowers/tests/test_api.py b/rowers/tests/test_api.py index 8bd91a55..649bca2f 100644 --- a/rowers/tests/test_api.py +++ b/rowers/tests/test_api.py @@ -471,6 +471,14 @@ class OwnApi(TestCase): gdproptin=True, ftpset=True,surveydone=True, gdproptindate=timezone.now(), rowerplan='pro',subscription_id=1) + self.c = Client() + 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.factory = APIRequestFactory() def test_strokedataform(self): diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index a12f9e93dea8c9787d530429a5d32f8dea26f3f4..c460bd6ffea839a3141ab5d40fe6491413adb205 100644 GIT binary patch literal 4000 zcmV;R4`1*fiwFoRxKL*T|8!+@bYx+4VJ>uIcmVC4NpBoC7J%>m6@m`QVPN)K__!#F z0>{o^4A`C^F*3OgDpE)88L1_x%iF&nvfGhmTZQCFQv|C(9}IPMZCzhI^6fnQ?%ny> z-kassW_7Xtb{`Gw?|pal;PB;Dw_2~(r;nHGetGqz>$~5&&2lg8yxx4f|EljV4;G8J zZ{M2J?&^HCHpds|i}dB@_|@{f+bnKBeDQ|%cc(b$-u;EMJUZ@IZ&v;LAKvxLb-uwH zCwRA6eR77kY%Z=&7X*NdU!Onwd3%b7tM#Vu*2l|q)Mj5_~8Mh zpOHU)-0*(y=gW(${@eY3_OF&#`@1jG*H2H52+%zQc}VaQ`M}}|L$-6h;*7?y0h2IBedxvyFWi(VR6ePZhZZ`d$&4&eg1#C z0dcq7z%7@!@%7X7_EqiQA8p@?Wg>gM<>rGtS*8bkM3^7r?(Z8EuU4n0>DHG0tBaGP z=iT~sca|uAvi0WP-SK}vx%=&w%Zt^zzqvVok=}v7@Gfq5`}D}qUI{UF_%+!ca8Cj^ zDUkny!j8o42tIkhaLe=E?r=SNZN9kd%O8LJ%QL~PWBBK1Oy~Sp*Y`Fz3^KQSz{36_jS4*A2Bxx6`ppI;C8rbuLu82Fiinu3;2rekNQ_`CXau0XsPIbw< zm5R7KLyW3XJ`E9Ekb9gBcV{c&9+SLV@fz;2q`WiChP$tbyQS?o?-bl^FjSB`-h(?< z#XUep+$C{OLZ#8>y>S!aUYEQFu86xg!GqK2^Ujqt)f;mWy$kB$WvY9 zshTOC4j`FwqBG?XrKInM?R;dOr6Tgy#H7@#WS+!?g5+gJkIWc-)(-1+&;^8yu-XJZ+KHRKEX?&6HdQ$^&RL8o)#pz|f?c?)f{ zABB7{k;H1kbLc}!|J}XFx6yu(JfU#}UNzc>vgGFzp+l+2sv~Oz5LFspj3w>)Igz)a zBJxR3XF^r-6~vNJI!iMe?Xe>A(F6o_d0x;YkyqLC3{W&; zGo#VICYcv&(z6B9kVl-1Zr`G`!{nXuf{jjd{)XGRkzLLS>Z ze>-{3xwi~R8c$AoQA%cATi5*dqmUN^3_&IHLPaAsjr=&|y$MO;QA6HwlleQ6=X%H^ z7zq$mO)@+em3iNE`%%cVVT+=g=K~kj@O+c`QOG-k5k)2Q(H8aIHkluX{5JPi)dJ0y zt*E>gd2BwsA@Vu*R;)_5w`D6TZD=w-3VCbNP#-nAy(L#v=0mgUMj`JFMRXePtz}m< z-#)a_ehl&q+m=`w-QHSPRJRW;K0gY1GL9jtn&3%U@^h-FbQO^oV})5Y&%2_vw6h{F z6_F1naaUzv+Oidu0!`+}AaBtmjnk2;C3!W6OwMPl6hM; z>pHet+EK`RBdm+qh?FJYe0U>}w+XgEZ&g{>;u`t8lD8F+ClkD`x=K7wCO+R}ewe%% zOQ@=@l2Ep;uJynihkP)EsOp>`v22ZvZ@T>$B~H_kwB!0(mI?D zd2XJZLGmtVU8_1FI>Ms5{hY|xG}=c4h(QydcT7dAsC|pik3l}?-UbYszPHX&(cIfP zk*~;}Cu_(dtLAy3$>iR0tDhev?+scc)oAais69U;@{TGZpF1ZIYx>(e&qe3?IgzhO zw@=y{M^vp^<^s1U?F`6^0T5M}a~HWqX{SHhvo|8TPKX{F`MZ*@NVm6O*o(@OT5>LE{%dt25_xMV{Zo;rqU+e?`qP$~F#>s?^YB#t@FEvo$42aox}VlWKAPY#s${-! zHkNIDZ^s~S$vDwfSH;5FSaVvJwxTF)l3!t^Ztq-Ct8Px@EAr&nU?>u3^6jJdMMrzr z)}kJR{4V!aH_wMq)T)~k`HDO_k}NbPP?3+JXiko6^ZY2}oe7p$6QxCfvSHdep|8l5 zBQYo3Lr|e7Xz1_Q|FoL%-sR4>F6zd61jeE%_xC{$m7$l4&`WNZ0=fa;(qzzM>vuhf zJ{UnlQlU?(Ay34pZ&E*qKDRR^WF2~Ei`sV5=K4|4TVn-PK3oJZP3rH6o+?7`bBipG zD)hlkM(Qg=&#oTy$xgF@z^c$kUsTPT(}0f^q31N$6R75TgfJQOwGH?+Nxd@+NUFIW zv23}`oX~Sc=wsIKqfY9}u3Fg8kApsULUpXl%)&HdH4T3T=p_&Ms*o+BP|;}poYcIE zj5`j7JO-79kFI(1?rPkHdeEn_8(i*>r$O(F_QAbZMkX}lZVu^D%R z=zT)3Yod{}xr(CahJFbZx^~31Bp&&sw;Cm=qSdUH38v{Ki0ag~=AqAXFM_g=anS^e8pYVO8BpEE24b@lZuW!tGxQ}aea&qe^FPU>Bl z%z%$AL2D4bGZuAg86x{wv~e0WyM7e((NILI)9^#t@C`R_-WcdD<^EYZZyv|k)V%2r z_*M*vvhd0xOeS7mTQYV)*QLR=(jBsy-LlqVw+26`i|Uf%S80Lx!%cS60}-c<^cMzZJ7wVC2!DrX~x~u z?fQ^gCR!0xIdW}i)i-xVA1Xp0atAG4R}CA>hHtnH_+y|)G|b{uQZG<;xq6dmulz#juWfk9$q6?zMkS=+FA^M=uHi>yf% zuTK_1E3%$})VthOBkIsQDr(%dt{UT@4_WHHZov0E8L4jr{vdir6NGi~dI`3uhTnp= z0QhD6)EdDC~~vNxW|s)Du-++CN*{ zb~$5cy>v!F?=t$RvgbYzQ2DG5< zDCi|OR3V+zTPYg3gQn(imBi>w(`;Ts=(v+HMoJ{n2ZolfebpNw5kP0brZ&t$|}UHB$IXmBK{m(HxvOGW5oUegwIQtz?ofbUxg#u(^@jI$V2 zSvnyUt+c%t`c`u{j9zl}O;BazhFG-H_Fe;i>!mY{KIAoRb&6TjMzG3&j7tOX{J{#fIPUOk6!n$F0O98 zCXX&J&sN7>pMLuFi*bGKFHV-e`~LOn?Bx3A{*vCubm)zT^I^;L?my|0*;#P&^Ow7= z)@ieskNa&mTtC{+&VTqa-Sp8<%d=&-Stbto(}zcQH)dC%=76M-R5BSL^f~9w(H`t0(Dc{FW|#^x%KrhJs$i GfB^tb+&muu literal 3998 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 Date: Thu, 5 Dec 2024 21:05:42 +0100 Subject: [PATCH 07/21] more or less done --- rowers/plannedsessions.py | 41 ++++++++++++ rowers/rower_rules.py | 3 + rowers/tests/test_api.py | 13 ++-- rowers/tests/test_races.py | 88 ++++++++++++++++++++++++++ rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4000 bytes rowers/views/analysisviews.py | 6 +- rowers/views/racesviews.py | 12 +++- rowers/views/statements.py | 3 +- 8 files changed, 154 insertions(+), 12 deletions(-) diff --git a/rowers/plannedsessions.py b/rowers/plannedsessions.py index d9e6add5..1918d750 100644 --- a/rowers/plannedsessions.py +++ b/rowers/plannedsessions.py @@ -1597,13 +1597,28 @@ def add_workout_fastestrace(ws, race, r, recordid=0, doregister=False): enddatetime ) + # from ws, remove any w where w.workoutsource = 'strava'. For each removal add an error "strava workout not permitted" to the errors list and if there are no workouts left, return 0, comments, errors, 0 + ws2 = [] + for w in ws: + if w.workoutsource != 'strava': + ws2.append(w) + else: + errors.append('Strava workouts are not permitted') + + ws = ws2 + + if len(ws) == 0: + return result, comments, errors, 0 + ids = [w.id for w in ws] ids = list(set(ids)) + if len(ids) > 1 and race.sessiontype in ['test', 'coursetest', 'race', 'indoorrace', 'fastest_time', 'fastest_distance']: # pragma: no cover errors.append('For tests, you can only attach one workout') return result, comments, errors, 0 + if r.birthdate: age = calculate_age(r.birthdate) else: # pragma: no cover @@ -1759,6 +1774,19 @@ def add_workout_indoorrace(ws, race, r, recordid=0, doregister=False): enddatetime ) + # from ws, remove any w where w.workoutsource = 'strava'. For each removal add an error "strava workout not permitted" to the errors list and if there are no workouts left, return 0, comments, errors, 0 + ws2 = [] + for w in ws: + if w.workoutsource != 'strava': + ws2.append(w) + else: + errors.append('Strava workouts are not permitted') + + ws = ws2 + + if len(ws) == 0: + return result, comments, errors, 0 + # check if all sessions have same date dates = [w.date for w in ws] if (not all(d == dates[0] for d in dates)) and race.sessiontype not in ['challenge', 'cycletarget']: # pragma: no cover @@ -1906,6 +1934,19 @@ def add_workout_race(ws, race, r, splitsecond=0, recordid=0, doregister=False): enddatetime ) + # from ws, remove any w where w.workoutsource = 'strava'. For each removal add an error "strava workout not permitted" to the errors list and if there are no workouts left, return 0, comments, errors, 0 + ws2 = [] + for w in ws: + if w.workoutsource != 'strava': + ws2.append(w) + else: + errors.append('Strava workouts are not permitted') + + ws = ws2 + + if len(ws) == 0: + return result, comments, errors, 0 + # check if all sessions have same date dates = [w.date for w in ws] if (not all(d == dates[0] for d in dates)) and race.sessiontype not in ['challenge', 'cycletarget']: # pragma: no cover diff --git a/rowers/rower_rules.py b/rowers/rower_rules.py index ed5cd2dd..68529023 100644 --- a/rowers/rower_rules.py +++ b/rowers/rower_rules.py @@ -463,6 +463,9 @@ def is_workout_user(user, workout): # check if user is in same team as owner of workout +@rules.predicate +def workout_is_strava(workout): + return workout.workoutsource == 'strava' @rules.predicate def is_workout_team(user, workout): diff --git a/rowers/tests/test_api.py b/rowers/tests/test_api.py index 649bca2f..83b2ba82 100644 --- a/rowers/tests/test_api.py +++ b/rowers/tests/test_api.py @@ -441,6 +441,12 @@ class StravaPrivacy(TestCase): self.assertEqual(response.status_code,200) + # count number of workouts by counting the number of occurences of '
  • {% endif %}
  • -

    Polar

    +

    Intervals.icu

    - {{ forms.polar.as_table }} + {{ forms.intervals.as_table }}
    -

    connect with Polar

    +

    connect with intervals.icu

  • Concept2

    @@ -103,20 +103,20 @@
  • +
  • +

    Polar

    + + {{ forms.polar.as_table }} + +
    +

    connect with Polar

    +
  • Rojabo

    connect with Rojabo

  • -
  • -

    Intervals.icu

    - - {{ forms.intervals.as_table }} - -
    -

    connect with intervals.icu

    -
  • Garmin Connect

    diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index f15cf38957d0b2a3b4cba860a3eabe8396f81f14..3a4eca337f44e3c892acabb79b59b31fb7ee2684 100644 GIT binary patch literal 4000 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 F0RYzQKaKzZ From dfad4865c908abb4b8b87c6a25d8d698b307c21f Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 14 Dec 2024 13:58:23 +0100 Subject: [PATCH 13/21] ready for merge back --- rowers/dataprep.py | 4 +- rowers/forms.py | 20 +++++--- rowers/integrations/intervals.py | 1 + rowers/models.py | 2 +- rowers/templates/rower_exportsettings.html | 53 +++++++++------------ rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 3999 bytes rowers/uploads.py | 36 +++++++------- rowers/views/userviews.py | 1 + rowers/views/workoutviews.py | 11 ----- static/img/intervals_logo_with_name.png | Bin 0 -> 18456 bytes 10 files changed, 61 insertions(+), 67 deletions(-) create mode 100644 static/img/intervals_logo_with_name.png diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 827a9717..28bf84be 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -369,7 +369,7 @@ def workout_summary_to_df( return df -def resample(id, r, parent, overwrite='copy'): +def resample(id, r, parent, overwrite=False): data, row = getrowdata_db(id=id) messages = [] @@ -393,7 +393,7 @@ def resample(id, r, parent, overwrite='copy'): data['pace'] = data['pace'] / 1000. data['time'] = data['time'] / 1000. - if overwrite == 'overwrite': + if overwrite == True: # remove CP data try: cpfile = 'media/cpdata_{id}.parquet.gz'.format(id=parent.id) diff --git a/rowers/forms.py b/rowers/forms.py index 0ebc5fb3..506f900f 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -67,13 +67,12 @@ class FlexibleDecimalField(forms.DecimalField): class ResampleForm(forms.Form): - resamplechoices = ( - ('overwrite', 'Overwrite Workout'), - ('copy', 'Create a Duplicate Workout') - ) - + # add resamplechoice field, the result is a True or False boolean, labels are "overwrite" and "create copy" resamplechoice = forms.ChoiceField( - initial='copy', choices=resamplechoices, label='Copy behavior') + required=True, + choices=((True, 'overwrite'), (False, 'create copy')), + label='Resample choice', + widget=forms.RadioSelect) class TrainingZonesForm(forms.Form): @@ -582,6 +581,11 @@ class UploadOptionsForm(forms.Form): races = VirtualRace.objects.filter( registration_closure__gt=timezone.now()) + # set upload_to_X based on r.X_auto_export + for field in ['C2', 'Strava', 'SportTracks', 'TrainingPeaks', 'Intervals']: + if getattr(r, field.lower()+'_auto_export') and r.rowerplan in ['pro', 'plan','coach']: + self.fields['upload_to_'+field].initial = True + registrations = IndoorVirtualRaceResult.objects.filter( race__in=races, userid=r.id) @@ -665,6 +669,10 @@ class TeamUploadOptionsForm(forms.Form): upload_to_TrainingPeaks = forms.BooleanField(initial=False, required=False, label='Export to TrainingPeaks') + + upload_to_Intervals = forms.BooleanField(initial=False, + required=False, + label='Export to TrainingPeaks') # do_physics = forms.BooleanField(initial=False,required=False,label='Power Estimate (OTW)') makeprivate = forms.BooleanField(initial=False, required=False, label='Make Workout Private') diff --git a/rowers/integrations/intervals.py b/rowers/integrations/intervals.py index 1cdcbfb0..7db883ae 100644 --- a/rowers/integrations/intervals.py +++ b/rowers/integrations/intervals.py @@ -17,6 +17,7 @@ import os from uuid import uuid4 from django.utils import timezone from datetime import timedelta +import rowers.dataprep as dataprep from rowsandall_app.settings import ( INTERVALS_CLIENT_ID, INTERVALS_REDIRECT_URI, INTERVALS_CLIENT_SECRET, SITE_URL diff --git a/rowers/models.py b/rowers/models.py index cd2500f7..047d8d0a 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -4561,6 +4561,7 @@ class RowerExportForm(ModelForm): 'rp3_auto_import', 'intervals_auto_import', 'intervals_auto_export', + 'intervals_resample_to_1s' ] class RowerExportFormStrava(ModelForm): @@ -4616,7 +4617,6 @@ class RowerExportFormTrainingPeaks(ModelForm): model = Rower fields = [ 'trainingpeaks_auto_export', - 'rp3_auto_import', ] class RowerExportFormRP3(ModelForm): diff --git a/rowers/templates/rower_exportsettings.html b/rowers/templates/rower_exportsettings.html index 0d7d3858..13418d15 100644 --- a/rowers/templates/rower_exportsettings.html +++ b/rowers/templates/rower_exportsettings.html @@ -64,13 +64,12 @@ {% endif %}
  • -

    Intervals.icu

    +

    NK

  • - {{ forms.intervals.as_table }} + {{ forms.nk.as_table }}
    -

    connect with intervals.icu

    +

    connect with NK Logbook

  • Concept2

    @@ -81,12 +80,27 @@

    connect with Concept2

  • -

    NK

    +

    RP3

    - {{ forms.nk.as_table }} + {{ forms.rp3.as_table }}
    -

    connect with NK Logbook

    +

    connect with RP3

    +
  • +
  • +

    Rojabo

    +

    connect with Rojabo

    +
  • +
  • +

    Intervals.icu

    + + {{ forms.intervals.as_table }} + +
    +

    connect with intervals.icu

  • SportTracks

    @@ -102,6 +116,8 @@ {{ forms.trainingpeaks.as_table }} +

    connect with Polar

  • Polar

    @@ -112,11 +128,6 @@

    connect with Polar

  • -
  • -

    Rojabo

    -

    connect with Rojabo

    -
  • Garmin Connect

    @@ -153,24 +164,6 @@

    -
  • -

    TrainingPeaks

    -
  • - {{ forms.trainingpeaks.as_table }} - -
    -

    connect with Polar

    -
  • -
  • -

    RP3

    - - {{ forms.rp3.as_table }} - -
    -

    connect with RP3

    -
  • {% if grants %}
  • Applications

    diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 3a4eca337f44e3c892acabb79b59b31fb7ee2684..dd7306f84921784d4337eb7cf60575abe39ca49f 100644 GIT binary patch delta 257 zcmV+c0sj7=AD<+lIJJZMCm_J{BTJ63+ zczBUGo?U#{_gR^Kd%F^qPsbfRc-_A`ySVb2 zJUBl;SsitK`stT1#^t%cI$8Ser`M~KmzO{HxAZ=yL$5rX4_lsg|4En3&Vs9OpTFL1 zwN9J8eA;ij;quXbasGoB>81~VU7jqv%`$PwpFTXgEBAHqq+1_9esuWrv&?4u=WhEi z=`DWt`t*-wx-)=#0K5MW7FWL5J^AfpI=r(*y;`T|@GzlVUOY-q2op#5N H#DD<+W`c@3 delta 258 zcmV+d0sa1;AD|xxABzYG+aFx92dNH!vHLS{=(;6*cijJQu-F}NXLqKLzcGKl{IuGA zfAH`kaXh>Du$`N`ZufR2DxZ!!c<{P^b#`&( zHFmSi+Z(A&*5Q0xx9Fkp2i>P(ua5c2dhPx I_QZez0LCki$p8QV diff --git a/rowers/uploads.py b/rowers/uploads.py index 71f07553..fae902d5 100644 --- a/rowers/uploads.py +++ b/rowers/uploads.py @@ -245,23 +245,6 @@ def do_sync(w, options, quick=False): dologging('c2_log.log','Error C2') pass - if do_strava_export: # pragma: no cover - strava_integration = StravaIntegration(w.user.user) - try: - id = strava_integration.workout_export(w) - dologging( - 'strava_export_log.log', - 'exporting workout {id} as {type}'.format( - id=w.id, - type=w.workouttype, - ) - ) - except NoTokenError: # pragma: no cover - id = 0 - message = "Please connect to Strava first" - except Exception as e: - dologging('stravalog.log', e) - if do_icu_export: intervals_integration = IntervalsIntegration(w.user.user) try: @@ -334,4 +317,23 @@ def do_sync(w, options, quick=False): dologging('tp_export.log','No Token Error') return 0 + # we do Strava last. + if do_strava_export: # pragma: no cover + strava_integration = StravaIntegration(w.user.user) + try: + id = strava_integration.workout_export(w) + dologging( + 'strava_export_log.log', + 'exporting workout {id} as {type}'.format( + id=w.id, + type=w.workouttype, + ) + ) + except NoTokenError: # pragma: no cover + id = 0 + message = "Please connect to Strava first" + except Exception as e: + dologging('stravalog.log', e) + + return 1 diff --git a/rowers/views/userviews.py b/rowers/views/userviews.py index d2d22e25..cf004032 100644 --- a/rowers/views/userviews.py +++ b/rowers/views/userviews.py @@ -465,6 +465,7 @@ def rower_exportsettings_view(request, userid=0): 'rp3_auto_import': 'rp3token', 'nk_auto_import': 'nktoken', 'intervals_auto_export': 'intervals_token', + 'intervals_resample_to_1s': 'intervals_token', } r = getrequestrowercoachee(request, userid=userid) diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 14112726..0412c7ff 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -5609,17 +5609,6 @@ def workout_upload_view(request, return response else: if not is_ajax: - if r.c2_auto_export and ispromember(r.user): # pragma: no cover - uploadoptions['upload_to_C2'] = True - - if r.strava_auto_export and ispromember(r.user): # pragma: no cover - uploadoptions['upload_to_Strava'] = True - - if r.sporttracks_auto_export and ispromember(r.user): # pragma: no cover - uploadoptions['upload_to_SportTracks'] = True - - if r.trainingpeaks_auto_export and ispromember(r.user): # pragma: no cover - uploadoptions['upload_to_TrainingPeaks'] = True form = DocumentsForm(initial=docformoptions) optionsform = UploadOptionsForm(initial=uploadoptions, diff --git a/static/img/intervals_logo_with_name.png b/static/img/intervals_logo_with_name.png new file mode 100644 index 0000000000000000000000000000000000000000..b448dd4e6a12f8d5d9b8888035ce6ea20d3f3f26 GIT binary patch literal 18456 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 literal 0 HcmV?d00001 From 0cf8704bd6bddb87b7644d810fe09198cb1cb6e5 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 14 Dec 2024 14:14:40 +0100 Subject: [PATCH 14/21] tested --- rowers/tests/testdata/testdata.tcx.gz | Bin 3999 -> 4001 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index dd7306f84921784d4337eb7cf60575abe39ca49f..f05208b7635b1a0be253904dc3acd3f4a6c0d0bb 100644 GIT binary patch literal 4001 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 H#DD<+r2IT+ literal 3999 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 F0RaAiDCqzI From 915e562a76e520aaa8a2933f94f36611a37af92d Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 14 Dec 2024 15:53:35 +0100 Subject: [PATCH 15/21] opening analysis for rowers themselves --- rowers/tests/test_api.py | 21 +++++--- rowers/tests/testdata/testdata.tcx.gz | Bin 4001 -> 4001 bytes rowers/uploads.py | 8 +++ rowers/views/analysisviews.py | 73 +++++++++++++------------- 4 files changed, 59 insertions(+), 43 deletions(-) diff --git a/rowers/tests/test_api.py b/rowers/tests/test_api.py index c8608415..14f0429a 100644 --- a/rowers/tests/test_api.py +++ b/rowers/tests/test_api.py @@ -208,7 +208,8 @@ class StravaPrivacy(TestCase): # throw out "c2import", "nkimport", "stravaimport", "concept2import", "sporttracksimport" from the set workouts = set([w for w in workouts if w not in [ - 'upload', 'addmanual', 'c2import', 'polarimport', 'rp3import', 'nkimport', 'stravaimport', 'concept2import', 'sporttracksimport']]) + 'upload', 'addmanual', 'c2import', 'polarimport', 'rp3import', 'nkimport', 'stravaimport', 'concept2import', 'sporttracksimport', + 'intervalsimport']]) self.assertEqual(len(workouts),5) @@ -230,7 +231,8 @@ class StravaPrivacy(TestCase): # throw out "c2import", "nkimport", "stravaimport", "concept2import", "sporttracksimport" from the set workouts = set([w for w in workouts if w not in [ - 'upload', 'addmanual', 'c2import', 'polarimport', 'rp3import', 'nkimport', 'stravaimport', 'concept2import', 'sporttracksimport']]) + 'upload', 'addmanual', 'c2import', 'polarimport', 'rp3import', 'nkimport', 'stravaimport', 'concept2import', 'sporttracksimport', + 'intervalsimport']]) self.assertEqual(len(workouts),2) @@ -251,7 +253,8 @@ class StravaPrivacy(TestCase): # throw out "c2import", "nkimport", "stravaimport", "concept2import", "sporttracksimport" from the set workouts = set([w for w in workouts if w not in [ - 'upload', 'addmanual', 'c2import', 'polarimport', 'rp3import', 'nkimport', 'stravaimport', 'concept2import', 'sporttracksimport']]) + 'upload', 'addmanual', 'c2import', 'polarimport', 'rp3import', 'nkimport', 'stravaimport', 'concept2import', 'sporttracksimport', + 'intervalsimport']]) self.assertEqual(len(workouts),2) @@ -272,7 +275,8 @@ class StravaPrivacy(TestCase): # throw out "c2import", "nkimport", "stravaimport", "concept2import", "sporttracksimport" from the set workouts = set([w for w in workouts if w not in [ - 'upload', 'addmanual', 'c2import', 'polarimport', 'rp3import', 'nkimport', 'stravaimport', 'concept2import', 'sporttracksimport']]) + 'upload', 'addmanual', 'c2import', 'polarimport', 'rp3import', 'nkimport', 'stravaimport', 'concept2import', 'sporttracksimport', + 'intervalsimport']]) self.assertEqual(len(workouts),2) @@ -293,7 +297,8 @@ class StravaPrivacy(TestCase): # throw out "c2import", "nkimport", "stravaimport", "concept2import", "sporttracksimport" from the set workouts = set([w for w in workouts if w not in [ - 'upload', 'addmanual', 'c2import', 'polarimport', 'rp3import', 'nkimport', 'stravaimport', 'concept2import', 'sporttracksimport']]) + 'upload', 'addmanual', 'c2import', 'polarimport', 'rp3import', 'nkimport', 'stravaimport', 'concept2import', 'sporttracksimport', + 'intervalsimport']]) self.assertEqual(len(workouts),2) @@ -447,7 +452,8 @@ class StravaPrivacy(TestCase): # print all lines of response.content that contain '
  • Strava

    -

    - {{ forms.strava.as_p }} +

    Warning: API restrictions!

    +

    + {{ forms.strava.as_p }}

    connect with strava

    Strava Auto Import also imports activity changes on Strava to Rowsandall, except when you delete From bcb1d439caaba3cf66feddd802f67109143775a3 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 15 Dec 2024 14:15:05 +0100 Subject: [PATCH 20/21] tested --- rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4000 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 8a3a272d623fb3a4896a09bda71dd11dba428ede..45ab3cec298b0eddcb0d98c86b97664587ef734c 100644 GIT binary patch literal 4000 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 GfB^uCm@x7H literal 4000 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 GfB^s Date: Sun, 15 Dec 2024 15:57:42 +0100 Subject: [PATCH 21/21] fixing resample issue --- rowers/dataprep.py | 12 ++++++++++++ rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 3999 bytes rowers/views/workoutviews.py | 1 + 3 files changed, 13 insertions(+) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 95fe1fc1..a824f571 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -374,6 +374,18 @@ def workout_summary_to_df( def resample(id, r, parent, overwrite=False): data, row = getrowdata_db(id=id) + rowdata = rrdata(csvfile=parent.csvfilename).df + # drop all columns except ' latitude' and ' longitude' and 'TimeStamp (sec)' from rowdata + allowedcolumns = [' latitude', ' longitude', 'TimeStamp (sec)'] + rowdata = rowdata.filter(allowedcolumns) + rowdata.rename(columns={'TimeStamp (sec)': 'time'}, inplace=True) + rowdata['time'] = (rowdata['time']-rowdata.loc[0,'time'])*1000. + rowdata.set_index('time', inplace=True) + data.set_index('time', inplace=True) + rowdata_interpolated = rowdata.reindex(data.index.union(rowdata.index)).interpolate('index') + data = data.merge(rowdata_interpolated, left_index=True, right_index=True, how='left') + data = data.reset_index() + messages = [] # resample diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 45ab3cec298b0eddcb0d98c86b97664587ef734c..eca15abb16d9035b8d6b2a4d682b2567cff2466b 100644 GIT binary patch literal 3999 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 F0RWJwKh6LE literal 4000 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 GfB^uCm@x7H diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 40bd1f56..b5e57156 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -4700,6 +4700,7 @@ def workout_map_view(request, id=0): u = w.user.user r = getrower(u) rowdata = rdata(csvfile=f1) + hascoordinates = 1 if rowdata != 0: try: