diff --git a/fittrackee_api/fittrackee_api/activities/models.py b/fittrackee_api/fittrackee_api/activities/models.py index 669ef699..a9e3820d 100644 --- a/fittrackee_api/fittrackee_api/activities/models.py +++ b/fittrackee_api/fittrackee_api/activities/models.py @@ -123,6 +123,7 @@ class Activity(db.Model): map_id = db.Column(db.String(50), nullable=True) weather_start = db.Column(JSON, nullable=True) weather_end = db.Column(JSON, nullable=True) + notes = db.Column(db.String(500), nullable=True) segments = db.relationship('ActivitySegment', lazy=True, cascade='all, delete', @@ -238,7 +239,8 @@ class Activity(db.Model): "records": [record.serialize() for record in self.records], "map": self.map_id if self.map else None, "weather_start": self.weather_start, - "weather_end": self.weather_end + "weather_end": self.weather_end, + "notes": self.notes } @classmethod diff --git a/fittrackee_api/fittrackee_api/activities/utils.py b/fittrackee_api/fittrackee_api/activities/utils.py index 2806dbad..e2ffac40 100644 --- a/fittrackee_api/fittrackee_api/activities/utils.py +++ b/fittrackee_api/fittrackee_api/activities/utils.py @@ -73,6 +73,7 @@ def create_activity( distance=distance, duration=duration ) + new_activity.notes = activity_data.get('notes') if title is not None and title != '': new_activity.title = title @@ -116,6 +117,8 @@ def edit_activity(activity, activity_data, auth_user_id): activity.sport_id = activity_data.get('sport_id') if activity_data.get('title'): activity.title = activity_data.get('title') + if activity_data.get('notes'): + activity.notes = activity_data.get('notes') if not activity.gpx: if activity_data.get('activity_date'): activity_date = datetime.strptime( diff --git a/fittrackee_api/fittrackee_api/tests/test_activities_api_1_post.py b/fittrackee_api/fittrackee_api/tests/test_activities_api_1_post.py index c67b8bf5..112382df 100644 --- a/fittrackee_api/fittrackee_api/tests/test_activities_api_1_post.py +++ b/fittrackee_api/fittrackee_api/tests/test_activities_api_1_post.py @@ -23,6 +23,9 @@ def assert_activity_data_with_gpx(data): assert data['data']['activities'][0]['pauses'] is None assert data['data']['activities'][0]['with_gpx'] is True assert data['data']['activities'][0]['map'] is not None + assert data['data']['activities'][0]['weather_start'] is None + assert data['data']['activities'][0]['weather_end'] is None + assert data['data']['activities'][0]['notes'] is None assert len(data['data']['activities'][0]['segments']) == 1 segment = data['data']['activities'][0]['segments'][0] @@ -81,6 +84,9 @@ def assert_activity_data_wo_gpx(data): assert data['data']['activities'][0]['pauses'] is None assert data['data']['activities'][0]['with_gpx'] is False assert data['data']['activities'][0]['map'] is None + assert data['data']['activities'][0]['weather_start'] is None + assert data['data']['activities'][0]['weather_end'] is None + assert data['data']['activities'][0]['notes'] is None assert len(data['data']['activities'][0]['segments']) == 0 @@ -305,6 +311,48 @@ def test_add_an_activity_with_gpx_without_name( assert_activity_data_with_gpx(data) +def test_get_an_activity_with_gpx_notes( + app, user_1, sport_1_cycling, gpx_file +): + client = app.test_client() + resp_login = client.post( + '/api/auth/login', + data=json.dumps(dict( + email='test@test.com', + password='12345678' + )), + content_type='application/json' + ) + client.post( + '/api/activities', + data=dict( + file=(BytesIO(str.encode(gpx_file)), 'example.gpx'), + data='{"sport_id": 1, "notes": "test activity"}' + ), + headers=dict( + content_type='multipart/form-data', + Authorization='Bearer ' + json.loads( + resp_login.data.decode() + )['auth_token'] + ) + ) + response = client.get( + '/api/activities/1', + headers=dict( + Authorization='Bearer ' + json.loads( + resp_login.data.decode() + )['auth_token'] + ) + ) + data = json.loads(response.data.decode()) + + assert response.status_code == 200 + assert 'success' in data['status'] + assert len(data['data']['activities']) == 1 + assert 'just an activity' == data['data']['activities'][0]['title'] + assert 'test activity' == data['data']['activities'][0]['notes'] + + def test_add_an_activity_with_gpx_invalid_file( app, user_1, sport_1_cycling, gpx_file_wo_track ): @@ -570,6 +618,48 @@ def test_get_an_activity_wo_gpx(app, user_1, sport_1_cycling): assert_activity_data_wo_gpx(data) +def test_get_an_activity_wo_gpx_notes(app, user_1, sport_1_cycling): + client = app.test_client() + resp_login = client.post( + '/api/auth/login', + data=json.dumps(dict( + email='test@test.com', + password='12345678' + )), + content_type='application/json' + ) + client.post( + '/api/activities/no_gpx', + content_type='application/json', + data=json.dumps(dict( + sport_id=1, + duration=3600, + activity_date='2018-05-15 14:05', + distance=10, + notes="new test with notes" + )), + headers=dict( + Authorization='Bearer ' + json.loads( + resp_login.data.decode() + )['auth_token'] + ) + ) + response = client.get( + '/api/activities/1', + headers=dict( + Authorization='Bearer ' + json.loads( + resp_login.data.decode() + )['auth_token'] + ) + ) + data = json.loads(response.data.decode()) + + assert response.status_code == 200 + assert 'success' in data['status'] + assert len(data['data']['activities']) == 1 + assert 'new test with notes' == data['data']['activities'][0]['notes'] + + def test_get_an_activity_wo_gpx_with_timezone(app, user_1, sport_1_cycling): user_1.timezone = 'Europe/Paris' client = app.test_client() diff --git a/fittrackee_api/fittrackee_api/tests/test_activities_api_2_patch.py b/fittrackee_api/fittrackee_api/tests/test_activities_api_2_patch.py index 2258054d..32d00007 100644 --- a/fittrackee_api/fittrackee_api/tests/test_activities_api_2_patch.py +++ b/fittrackee_api/fittrackee_api/tests/test_activities_api_2_patch.py @@ -89,6 +89,26 @@ def test_edit_an_activity_with_gpx( assert data['data']['activities'][0]['title'] == 'Activity test' assert_activity_data_with_gpx(data) + response = client.patch( + '/api/activities/1', + content_type='application/json', + data=json.dumps(dict( + notes="test notes" + )), + headers=dict( + Authorization='Bearer ' + json.loads( + resp_login.data.decode() + )['auth_token'] + ) + ) + data = json.loads(response.data.decode()) + + assert response.status_code == 200 + assert 'success' in data['status'] + assert len(data['data']['activities']) == 1 + assert data['data']['activities'][0]['title'] == 'Activity test' + assert data['data']['activities'][0]['notes'] == 'test notes' + def test_edit_an_activity_with_gpx_partial( app, user_1, sport_1_cycling, sport_2_running, gpx_file @@ -276,6 +296,10 @@ def test_edit_an_activity_wo_gpx( assert data['data']['activities'][0]['moving'] == '1:00:00' assert data['data']['activities'][0]['pauses'] is None assert data['data']['activities'][0]['with_gpx'] is False + assert data['data']['activities'][0]['map'] is None + assert data['data']['activities'][0]['weather_start'] is None + assert data['data']['activities'][0]['weather_end'] is None + assert data['data']['activities'][0]['notes'] is None records = data['data']['activities'][0]['records'] assert len(records) == 4 @@ -337,6 +361,71 @@ def test_edit_an_activity_wo_gpx( assert data['data']['activities'][0]['moving'] == '1:00:00' assert data['data']['activities'][0]['pauses'] is None assert data['data']['activities'][0]['with_gpx'] is False + assert data['data']['activities'][0]['map'] is None + assert data['data']['activities'][0]['weather_start'] is None + assert data['data']['activities'][0]['weather_end'] is None + assert data['data']['activities'][0]['notes'] is None + + records = data['data']['activities'][0]['records'] + assert len(records) == 4 + assert records[0]['sport_id'] == 2 + assert records[0]['activity_id'] == 1 + assert records[0]['record_type'] == 'MS' + assert records[0]['activity_date'] == 'Tue, 15 May 2018 15:05:00 GMT' + assert records[0]['value'] == 8.0 + assert records[1]['sport_id'] == 2 + assert records[1]['activity_id'] == 1 + assert records[1]['record_type'] == 'LD' + assert records[1]['activity_date'] == 'Tue, 15 May 2018 15:05:00 GMT' + assert records[1]['value'] == '1:00:00' + assert records[2]['sport_id'] == 2 + assert records[2]['activity_id'] == 1 + assert records[2]['record_type'] == 'FD' + assert records[2]['activity_date'] == 'Tue, 15 May 2018 15:05:00 GMT' + assert records[2]['value'] == 8.0 + assert records[3]['sport_id'] == 2 + assert records[3]['activity_id'] == 1 + assert records[3]['record_type'] == 'AS' + assert records[3]['activity_date'] == 'Tue, 15 May 2018 15:05:00 GMT' + assert records[3]['value'] == 8.0 + + response = client.patch( + '/api/activities/1', + content_type='application/json', + data=json.dumps(dict( + notes='test notes' + )), + headers=dict( + Authorization='Bearer ' + json.loads( + resp_login.data.decode() + )['auth_token'] + ) + ) + data = json.loads(response.data.decode()) + + assert response.status_code == 200 + assert 'success' in data['status'] + assert len(data['data']['activities']) == 1 + assert 'creation_date' in data['data']['activities'][0] + assert data['data']['activities'][0]['activity_date'] == 'Tue, 15 May 2018 15:05:00 GMT' # noqa + assert data['data']['activities'][0]['user_id'] == 1 + assert data['data']['activities'][0]['sport_id'] == 2 + assert data['data']['activities'][0]['duration'] == '1:00:00' + assert data['data']['activities'][0]['title'] == 'Activity test' + assert data['data']['activities'][0]['ascent'] is None + assert data['data']['activities'][0]['ave_speed'] == 8.0 + assert data['data']['activities'][0]['descent'] is None + assert data['data']['activities'][0]['distance'] == 8.0 + assert data['data']['activities'][0]['max_alt'] is None + assert data['data']['activities'][0]['max_speed'] == 8.0 + assert data['data']['activities'][0]['min_alt'] is None + assert data['data']['activities'][0]['moving'] == '1:00:00' + assert data['data']['activities'][0]['pauses'] is None + assert data['data']['activities'][0]['with_gpx'] is False + assert data['data']['activities'][0]['map'] is None + assert data['data']['activities'][0]['weather_start'] is None + assert data['data']['activities'][0]['weather_end'] is None + assert data['data']['activities'][0]['notes'] == 'test notes' records = data['data']['activities'][0]['records'] assert len(records) == 4 diff --git a/fittrackee_api/fittrackee_api/tests/test_activities_model.py b/fittrackee_api/fittrackee_api/tests/test_activities_model.py index 4cd60799..6b5524c8 100644 --- a/fittrackee_api/fittrackee_api/tests/test_activities_model.py +++ b/fittrackee_api/fittrackee_api/tests/test_activities_model.py @@ -40,6 +40,7 @@ def test_add_activity( assert serialized_activity['map'] is None assert serialized_activity['weather_start'] is None assert serialized_activity['weather_end'] is None + assert serialized_activity['notes'] is None def test_add_segment( diff --git a/fittrackee_api/migrations/versions/096dd0b43beb_.py b/fittrackee_api/migrations/versions/096dd0b43beb_.py new file mode 100644 index 00000000..3932f03c --- /dev/null +++ b/fittrackee_api/migrations/versions/096dd0b43beb_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: 096dd0b43beb +Revises: 71093ac9ca44 +Create Date: 2018-06-13 17:56:20.359884 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '096dd0b43beb' +down_revision = '71093ac9ca44' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('activities', sa.Column('notes', sa.String(length=500), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('activities', 'notes') + # ### end Alembic commands ###