From 014be0042bf5905e802899382c5e3e6e42ccfeab Mon Sep 17 00:00:00 2001 From: Sam Date: Sun, 6 Oct 2024 13:15:13 +0200 Subject: [PATCH 1/4] API & Client - get description from gpx file if present + minor fixes --- .../tests/fixtures/fixtures_workouts.py | 562 +++++------------- .../workouts/test_workouts_api_1_post.py | 114 +++- fittrackee/workouts/utils/gpx.py | 6 +- fittrackee/workouts/utils/workouts.py | 11 +- .../src/components/Workout/WorkoutEdition.vue | 14 +- .../src/locales/en/workouts.json | 1 + .../src/locales/fr/workouts.json | 1 + 7 files changed, 289 insertions(+), 420 deletions(-) diff --git a/fittrackee/tests/fixtures/fixtures_workouts.py b/fittrackee/tests/fixtures/fixtures_workouts.py index 1bf6f9996..b9e4081db 100644 --- a/fittrackee/tests/fixtures/fixtures_workouts.py +++ b/fittrackee/tests/fixtures/fixtures_workouts.py @@ -277,6 +277,112 @@ def workout_cycling_user_2() -> Workout: return workout +track_points_part_1 = ( + ' ' + ' 998' + ' ' + ' ' + ' ' + ' 998' + ' ' + ' ' + ' ' + ' 994' + ' ' + ' ' + ' ' + ' 994' + ' ' + ' ' + ' ' + ' 994' + ' ' + ' ' + ' ' + ' 993' + ' ' + ' ' + ' ' + ' 992' + ' ' + ' ' + ' ' + ' 992' + ' ' + ' ' + ' ' + ' 987' + ' ' + ' ' +) +track_points_part_2 = ( + ' ' + ' 987' + ' ' + ' ' + ' ' + ' 987' + ' ' + ' ' + ' ' + ' 987' + ' ' + ' ' + ' ' + ' 986' + ' ' + ' ' + ' ' + ' 986' + ' ' + ' ' + ' ' + ' 986' + ' ' + ' ' + ' ' + ' 985' + ' ' + ' ' + ' ' + ' 980' + ' ' + ' ' + ' ' + ' 980' + ' ' + ' ' + ' ' + ' 980' + ' ' + ' ' + ' ' + ' 979' + ' ' + ' ' + ' ' + ' 981' + ' ' + ' ' + ' ' + ' 980' + ' ' + ' ' + ' ' + ' 979' + ' ' + ' ' + ' ' + ' 979' + ' ' + ' ' + ' ' + ' 975' + ' ' + ' ' +) + + @pytest.fixture() def gpx_file() -> str: return ( @@ -286,107 +392,9 @@ def gpx_file() -> str: ' ' ' just a workout' ' ' - ' ' - ' 998' - ' ' - ' ' - ' ' - ' 998' - ' ' - ' ' - ' ' - ' 994' - ' ' - ' ' - ' ' - ' 994' - ' ' - ' ' - ' ' - ' 994' - ' ' - ' ' - ' ' - ' 993' - ' ' - ' ' - ' ' - ' 992' - ' ' - ' ' - ' ' - ' 992' - ' ' - ' ' - ' ' - ' 987' - ' ' - ' ' - ' ' - ' 987' - ' ' - ' ' - ' ' - ' 987' - ' ' - ' ' - ' ' - ' 987' - ' ' - ' ' - ' ' - ' 986' - ' ' - ' ' - ' ' - ' 986' - ' ' - ' ' - ' ' - ' 986' - ' ' - ' ' - ' ' - ' 985' - ' ' - ' ' - ' ' - ' 980' - ' ' - ' ' - ' ' - ' 980' - ' ' - ' ' - ' ' - ' 980' - ' ' - ' ' - ' ' - ' 979' - ' ' - ' ' - ' ' - ' 981' - ' ' - ' ' - ' ' - ' 980' - ' ' - ' ' - ' ' - ' 979' - ' ' - ' ' - ' ' - ' 979' - ' ' - ' ' - ' ' - ' 975' - ' ' - ' ' - ' ' + + track_points_part_1 + + track_points_part_2 + + ' ' ' ' '' ) @@ -400,107 +408,45 @@ def gpx_file_wo_name() -> str: ' ' ' ' ' ' - ' ' - ' 998' - ' ' - ' ' - ' ' - ' 998' - ' ' - ' ' - ' ' - ' 994' - ' ' - ' ' - ' ' - ' 994' - ' ' - ' ' - ' ' - ' 994' - ' ' - ' ' - ' ' - ' 993' - ' ' - ' ' - ' ' - ' 992' - ' ' - ' ' - ' ' - ' 992' - ' ' - ' ' - ' ' - ' 987' - ' ' - ' ' - ' ' - ' 987' - ' ' - ' ' - ' ' - ' 987' - ' ' - ' ' - ' ' - ' 987' - ' ' - ' ' - ' ' - ' 986' - ' ' - ' ' - ' ' - ' 986' - ' ' - ' ' - ' ' - ' 986' - ' ' - ' ' - ' ' - ' 985' - ' ' - ' ' - ' ' - ' 980' - ' ' - ' ' - ' ' - ' 980' - ' ' - ' ' - ' ' - ' 980' - ' ' - ' ' - ' ' - ' 979' - ' ' - ' ' - ' ' - ' 981' - ' ' - ' ' - ' ' - ' 980' - ' ' - ' ' - ' ' - ' 979' - ' ' - ' ' - ' ' - ' 979' - ' ' - ' ' - ' ' - ' 975' - ' ' - ' ' - ' ' + + track_points_part_1 + + track_points_part_2 + + ' ' + ' ' + '' + ) + + +@pytest.fixture() +def gpx_file_with_description() -> str: + return ( + '' + '' # noqa + ' ' + ' ' + ' just a workout' + ' this is workout description' + ' ' + + track_points_part_1 + + track_points_part_2 + + ' ' + ' ' + '' + ) + + +@pytest.fixture() +def gpx_file_with_empty_description() -> str: + return ( + '' + '' # noqa + ' ' + ' ' + ' just a workout' + ' ' + ' ' + + track_points_part_1 + + track_points_part_2 + + ' ' ' ' '' ) @@ -719,108 +665,10 @@ def gpx_file_w_title_exceeding_limit() -> str: ' ' f' {random_string(TITLE_MAX_CHARACTERS + 1)}' ' ' - ' ' - ' 998' - ' ' - ' ' - ' ' - ' 998' - ' ' - ' ' - ' ' - ' 994' - ' ' - ' ' - ' ' - ' 994' - ' ' - ' ' - ' ' - ' 994' - ' ' - ' ' - ' ' - ' 993' - ' ' - ' ' - ' ' - ' 992' - ' ' - ' ' - ' ' - ' 992' - ' ' - ' ' - ' ' - ' 987' - ' ' - ' ' - ' ' - ' 987' - ' ' - ' ' - ' ' - ' 987' - ' ' - ' ' - ' ' - ' 987' - ' ' - ' ' - ' ' - ' 986' - ' ' - ' ' - ' ' - ' 986' - ' ' - ' ' - ' ' - ' 986' - ' ' - ' ' - ' ' - ' 985' - ' ' - ' ' - ' ' - ' 980' - ' ' - ' ' - ' ' - ' 980' - ' ' - ' ' - ' ' - ' 980' - ' ' - ' ' - ' ' - ' 979' - ' ' - ' ' - ' ' - ' 981' - ' ' - ' ' - ' ' - ' 980' - ' ' - ' ' - ' ' - ' 979' - ' ' - ' ' - ' ' - ' 979' - ' ' - ' ' - ' ' - ' 975' - ' ' - ' ' - ' ' - ' ' + + track_points_part_1 + + track_points_part_2 + + ' ' + + ' ' '' ) @@ -902,110 +750,8 @@ def gpx_file_with_segments() -> str: ' ' ' ' ' just a workout' - ' ' - ' ' - ' 998' - ' ' - ' ' - ' ' - ' 998' - ' ' - ' ' - ' ' - ' 994' - ' ' - ' ' - ' ' - ' 994' - ' ' - ' ' - ' ' - ' 994' - ' ' - ' ' - ' ' - ' 993' - ' ' - ' ' - ' ' - ' 992' - ' ' - ' ' - ' ' - ' 992' - ' ' - ' ' - ' ' - ' 987' - ' ' - ' ' - ' ' - ' ' - ' ' - ' 987' - ' ' - ' ' - ' ' - ' 987' - ' ' - ' ' - ' ' - ' 987' - ' ' - ' ' - ' ' - ' 986' - ' ' - ' ' - ' ' - ' 986' - ' ' - ' ' - ' ' - ' 986' - ' ' - ' ' - ' ' - ' 985' - ' ' - ' ' - ' ' - ' 980' - ' ' - ' ' - ' ' - ' 980' - ' ' - ' ' - ' ' - ' 980' - ' ' - ' ' - ' ' - ' 979' - ' ' - ' ' - ' ' - ' 981' - ' ' - ' ' - ' ' - ' 980' - ' ' - ' ' - ' ' - ' 979' - ' ' - ' ' - ' ' - ' 979' - ' ' - ' ' - ' ' - ' 975' - ' ' - ' ' - ' ' + ' ' + track_points_part_1 + ' ' + ' ' + track_points_part_2 + ' ' ' ' '' ) diff --git a/fittrackee/tests/workouts/test_workouts_api_1_post.py b/fittrackee/tests/workouts/test_workouts_api_1_post.py index 4e7f9bc68..8d68bdd23 100644 --- a/fittrackee/tests/workouts/test_workouts_api_1_post.py +++ b/fittrackee/tests/workouts/test_workouts_api_1_post.py @@ -739,17 +739,22 @@ def test_it_returns_400_when_quotes_are_not_escaped_in_description( self.assert_400(response) @pytest.mark.parametrize( - 'input_test_description,input_description', + 'input_test_description,input_description,expected_description', [ - ('empty description', ''), - ('short description', 'test workout'), - ('description with special characters', "test \n'workout'©"), + ('empty description', '', None), + ('short description', 'test workout', 'test workout'), + ( + 'description with special characters', + "test \n'workout'©", + "test \n'workout'©", + ), ], ) def test_it_adds_a_workout_with_gpx_and_description( self, input_test_description: str, input_description: str, + expected_description: str, app: Flask, user_1: User, sport_1_cycling: Sport, @@ -777,7 +782,9 @@ def test_it_adds_a_workout_with_gpx_and_description( assert response.status_code == 201 assert 'created' in data['status'] assert len(data['data']['workouts']) == 1 - assert data['data']['workouts'][0]['description'] == input_description + assert ( + data['data']['workouts'][0]['description'] == expected_description + ) def test_it_adds_a_workout_with_gpx_and_description_exceeding_limit( self, @@ -809,6 +816,103 @@ def test_it_adds_a_workout_with_gpx_and_description_exceeding_limit( assert len(data['data']['workouts']) == 1 assert data['data']['workouts'][0]['description'] == description[:-1] + def test_it_adds_a_workout_with_description_from_gpx( + self, + app: Flask, + user_1: User, + sport_1_cycling: Sport, + gpx_file_with_description: str, + ) -> None: + client, auth_token = self.get_test_client_and_auth_token( + app, user_1.email + ) + + response = client.post( + '/api/workouts', + data=dict( + file=( + BytesIO(str.encode(gpx_file_with_description)), + 'example.gpx', + ), + data='{"sport_id": 1}', + ), + headers=dict( + content_type='multipart/form-data', + Authorization=f'Bearer {auth_token}', + ), + ) + data = json.loads(response.data.decode()) + + assert response.status_code == 201 + assert 'created' in data['status'] + assert len(data['data']['workouts']) == 1 + assert ( + data['data']['workouts'][0]['description'] + == "this is workout description" + ) + + def test_it_adds_a_workout_with_empty_gpx_description( + self, + app: Flask, + user_1: User, + sport_1_cycling: Sport, + gpx_file_with_empty_description: str, + ) -> None: + client, auth_token = self.get_test_client_and_auth_token( + app, user_1.email + ) + + response = client.post( + '/api/workouts', + data=dict( + file=( + BytesIO(str.encode(gpx_file_with_empty_description)), + 'example.gpx', + ), + data='{"sport_id": 1}', + ), + headers=dict( + content_type='multipart/form-data', + Authorization=f'Bearer {auth_token}', + ), + ) + data = json.loads(response.data.decode()) + + assert response.status_code == 201 + assert 'created' in data['status'] + assert len(data['data']['workouts']) == 1 + assert data['data']['workouts'][0]['description'] is None + + def test_it_overrides_gpx_description_when_description_is_provided( + self, + app: Flask, + user_1: User, + sport_1_cycling: Sport, + gpx_file: str, + ) -> None: + description = self.random_string() + client, auth_token = self.get_test_client_and_auth_token( + app, user_1.email + ) + + response = client.post( + '/api/workouts', + data=dict( + file=(BytesIO(str.encode(gpx_file)), 'example.gpx'), + data=f'{{"sport_id": 1, "description": "{description}"}}', + ), + headers=dict( + content_type='multipart/form-data', + Authorization=f'Bearer {auth_token}', + ), + ) + data = json.loads(response.data.decode()) + + assert response.status_code == 201 + assert 'created' in data['status'] + assert len(data['data']['workouts']) == 1 + assert data['data']['workouts'][0]['description'] == description + def test_it_adds_a_workout_with_equipments( self, app: Flask, diff --git a/fittrackee/workouts/utils/gpx.py b/fittrackee/workouts/utils/gpx.py index 37cf9c6ef..5f6a1c1f6 100644 --- a/fittrackee/workouts/utils/gpx.py +++ b/fittrackee/workouts/utils/gpx.py @@ -90,7 +90,11 @@ def get_gpx_info( if gpx is None: raise InvalidGPXException('error', 'no tracks in gpx file') - gpx_data: Dict = {'name': gpx.tracks[0].name, 'segments': []} + gpx_data: Dict = { + 'name': gpx.tracks[0].name, + 'description': gpx.tracks[0].description, + 'segments': [], + } max_speed = 0.0 start: Optional[datetime] = None map_data = [] diff --git a/fittrackee/workouts/utils/workouts.py b/fittrackee/workouts/utils/workouts.py index 458098a57..33a8ad214 100644 --- a/fittrackee/workouts/utils/workouts.py +++ b/fittrackee/workouts/utils/workouts.py @@ -138,6 +138,13 @@ def create_workout( if gpx_data else '' ) + description = ( + workout_data.get('description', '') + if workout_data.get('description', '') + else gpx_data['description'] + if gpx_data + else '' + ) new_workout = Workout( user_id=user.id, @@ -152,9 +159,7 @@ def create_workout( else workout_data['notes'][:NOTES_MAX_CHARACTERS] ) new_workout.description = ( - None - if workout_data.get('description') is None - else workout_data['description'][:DESCRIPTION_MAX_CHARACTERS] + description[:DESCRIPTION_MAX_CHARACTERS] if description else None ) if title is not None and title != '': diff --git a/fittrackee_client/src/components/Workout/WorkoutEdition.vue b/fittrackee_client/src/components/Workout/WorkoutEdition.vue index d2bcaccb4..b48470cf7 100644 --- a/fittrackee_client/src/components/Workout/WorkoutEdition.vue +++ b/fittrackee_client/src/components/Workout/WorkoutEdition.vue @@ -94,7 +94,9 @@
- + -
+