diff --git a/Makefile b/Makefile index 5494f81a..d34b12ef 100644 --- a/Makefile +++ b/Makefile @@ -134,6 +134,9 @@ migrate-db: recalculate: $(FLASK) recalculate +revision: + $(FLASK) db revision --directory $(MIGRATIONS) --message $(MIGRATION_MESSAGE) + run: $(MAKE) P="run-server run-workers" make-p diff --git a/fittrackee/database_utils.py b/fittrackee/database_utils.py index d84c3d77..060c426c 100644 --- a/fittrackee/database_utils.py +++ b/fittrackee/database_utils.py @@ -6,7 +6,6 @@ from fittrackee.application.utils import ( update_app_config_from_database, ) from fittrackee.users.models import User -from fittrackee.workouts.models import Sport def init_database(app: Flask) -> None: @@ -17,50 +16,6 @@ def init_database(app: Flask) -> None: admin.admin = True admin.timezone = 'Europe/Paris' db.session.add(admin) - sport = Sport(label='Cycling (Sport)') - sport.img = '/img/sports/cycling-sport.png' - sport.is_default = True - db.session.add(sport) - sport = Sport(label='Cycling (Transport)') - sport.img = '/img/sports/cycling-transport.png' - sport.is_default = True - db.session.add(sport) - sport = Sport(label='Hiking') - sport.img = '/img/sports/hiking.png' - sport.is_default = True - db.session.add(sport) - sport = Sport(label='Mountain Biking') - sport.img = '/img/sports/mountain-biking.png' - sport.is_default = True - db.session.add(sport) - sport = Sport(label='Mountain Biking (Electric)') - sport.img = '/img/sports/electric-mountain-biking.png' - sport.is_default = True - db.session.add(sport) - sport = Sport(label='Running') - sport.img = '/img/sports/running.png' - sport.is_default = True - db.session.add(sport) - sport = Sport(label='Trail') - sport.img = '/img/sports/trail.png' - sport.is_default = True - db.session.add(sport) - sport = Sport(label='Walking') - sport.img = '/img/sports/walking.png' - sport.is_default = True - db.session.add(sport) - sport = Sport(label='Skiing (Alpine)') - sport.img = '/img/sports/alpine-skiing.png' - sport.is_default = True - db.session.add(sport) - sport = Sport(label='Skiing (Cross Country)') - sport.img = '/img/sports/cross-country-skiing.png' - sport.is_default = True - db.session.add(sport) - sport = Sport(label='Rowing') - sport.img = '/img/sports/rowing.png' - sport.is_default = True - db.session.add(sport) db.session.commit() _, db_app_config = init_config() update_app_config_from_database(app, db_app_config) diff --git a/fittrackee/migrations/versions/02_b7cfe0c17708_create_activity_sport_tables.py b/fittrackee/migrations/versions/02_b7cfe0c17708_create_activity_sport_tables.py index 0b630ed8..78a72bc2 100644 --- a/fittrackee/migrations/versions/02_b7cfe0c17708_create_activity_sport_tables.py +++ b/fittrackee/migrations/versions/02_b7cfe0c17708_create_activity_sport_tables.py @@ -51,6 +51,19 @@ def upgrade(): ) # ### end Alembic commands ### + op.execute( + """ + INSERT INTO sports (label, img, is_default) + VALUES + ('Cycling (Sport)', '/img/sports/cycling-sport.png', True), + ('Cycling (Transport)', '/img/sports/cycling-transport.png', True), + ('Hiking', '/img/sports/hiking.png', True), + ('Mountain Biking', '/img/sports/mountain-biking.png', True), + ('Running', '/img/sports/running.png', True), + ('Walking', '/img/sports/walking.png', True) + """ + ) + def downgrade(): # ### commands auto generated by Alembic - please adjust! ### diff --git a/fittrackee/migrations/versions/17_cee0830497f8_add_new_sports.py b/fittrackee/migrations/versions/17_cee0830497f8_add_new_sports.py new file mode 100644 index 00000000..4100d486 --- /dev/null +++ b/fittrackee/migrations/versions/17_cee0830497f8_add_new_sports.py @@ -0,0 +1,39 @@ +""" Add new sports + +Revision ID: cee0830497f8 +Revises: 4e8597c50064 +Create Date: 2021-08-25 13:58:52.333603 + +""" +from alembic import op + + +# revision identifiers, used by Alembic. +revision = 'cee0830497f8' +down_revision = '4e8597c50064' +branch_labels = None +depends_on = None + + +def upgrade(): + + op.execute( + """ + INSERT INTO sports (label, img, is_active) + VALUES + ('Mountain Biking (Electric)', '/img/sports/electric-mountain-biking.png', True), + ('Trail', '/img/sports/trail.png', True), + ('Skiing (Alpine)', '/img/sports/alpine-skiing.png', True), + ('Skiing (Cross Country)', '/img/sports/cross-country-skiing.png', True), + ('Rowing', '/img/sports/rowing.png', True) + """ + ) + + +def downgrade(): + op.execute( + """ + DELETE FROM sports + WHERE label = 'Mountain Biking (Electric)'; + """ + ) \ No newline at end of file diff --git a/fittrackee/tests/fixtures/fixtures_workouts.py b/fittrackee/tests/fixtures/fixtures_workouts.py index 094d6f7d..7ef7e047 100644 --- a/fittrackee/tests/fixtures/fixtures_workouts.py +++ b/fittrackee/tests/fixtures/fixtures_workouts.py @@ -108,8 +108,11 @@ def seven_workouts_user_1() -> Workout: ) workout.ave_speed = float(workout.distance) / (1024 / 3600) workout.moving = workout.duration + workout.ascent = 120 + workout.descent = 200 db.session.add(workout) db.session.flush() + workout = Workout( user_id=1, sport_id=1, @@ -119,8 +122,11 @@ def seven_workouts_user_1() -> Workout: ) workout.ave_speed = float(workout.distance) / (3456 / 3600) workout.moving = workout.duration + workout.ascent = 100 + workout.descent = 80 db.session.add(workout) db.session.flush() + workout = Workout( user_id=1, sport_id=1, @@ -130,8 +136,11 @@ def seven_workouts_user_1() -> Workout: ) workout.ave_speed = float(workout.distance) / (1024 / 3600) workout.moving = workout.duration + workout.ascent = 80 + workout.descent = 100 db.session.add(workout) db.session.flush() + workout = Workout( user_id=1, sport_id=1, @@ -141,8 +150,11 @@ def seven_workouts_user_1() -> Workout: ) workout.ave_speed = float(workout.distance) / (600 / 3600) workout.moving = workout.duration + workout.ascent = 120 + workout.descent = 180 db.session.add(workout) db.session.flush() + workout = Workout( user_id=1, sport_id=1, @@ -152,8 +164,11 @@ def seven_workouts_user_1() -> Workout: ) workout.ave_speed = float(workout.distance) / (1000 / 3600) workout.moving = workout.duration + workout.ascent = 100 + workout.descent = 200 db.session.add(workout) db.session.flush() + workout = Workout( user_id=1, sport_id=1, @@ -163,8 +178,11 @@ def seven_workouts_user_1() -> Workout: ) workout.ave_speed = float(workout.distance) / (6000 / 3600) workout.moving = workout.duration + workout.ascent = 40 + workout.descent = 20 db.session.add(workout) db.session.flush() + workout = Workout( user_id=1, sport_id=1, diff --git a/fittrackee/tests/workouts/test_stats_api.py b/fittrackee/tests/workouts/test_stats_api.py index 0c053a7c..a5d70680 100644 --- a/fittrackee/tests/workouts/test_stats_api.py +++ b/fittrackee/tests/workouts/test_stats_api.py @@ -110,6 +110,8 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2017': { '1': { 'nb_workouts': 2, + 'total_ascent': 220.0, + 'total_descent': 280.0, 'total_distance': 15.0, 'total_duration': 4480, } @@ -117,11 +119,15 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2018': { '1': { 'nb_workouts': 5, + 'total_ascent': 340.0, + 'total_descent': 500.0, 'total_distance': 39.0, 'total_duration': 11624, }, '2': { 'nb_workouts': 1, + 'total_ascent': 0.0, + 'total_descent': 0.0, 'total_distance': 12.0, 'total_duration': 6000, }, @@ -151,11 +157,15 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2018': { '1': { 'nb_workouts': 1, + 'total_ascent': 40.0, + 'total_descent': 20.0, 'total_distance': 8.0, 'total_duration': 6000, }, '2': { 'nb_workouts': 1, + 'total_ascent': 0.0, + 'total_descent': 0.0, 'total_distance': 12.0, 'total_duration': 6000, }, @@ -186,11 +196,15 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2018': { '1': { 'nb_workouts': 1, + 'total_ascent': 40.0, + 'total_descent': 20.0, 'total_distance': 8.0, 'total_duration': 6000, }, '2': { 'nb_workouts': 1, + 'total_ascent': 0.0, + 'total_descent': 0.0, 'total_distance': 12.0, 'total_duration': 6000, }, @@ -220,6 +234,8 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2017': { '1': { 'nb_workouts': 2, + 'total_ascent': 220.0, + 'total_descent': 280.0, 'total_distance': 15.0, 'total_duration': 4480, } @@ -227,11 +243,15 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2018': { '1': { 'nb_workouts': 5, + 'total_ascent': 340.0, + 'total_descent': 500.0, 'total_distance': 39.0, 'total_duration': 11624, }, '2': { 'nb_workouts': 1, + 'total_ascent': 0.0, + 'total_descent': 0.0, 'total_distance': 12.0, 'total_duration': 6000, }, @@ -261,11 +281,15 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2018': { '1': { 'nb_workouts': 1, + 'total_ascent': 40.0, + 'total_descent': 20.0, 'total_distance': 8.0, 'total_duration': 6000, }, '2': { 'nb_workouts': 1, + 'total_ascent': 0.0, + 'total_descent': 0.0, 'total_distance': 12.0, 'total_duration': 6000, }, @@ -296,11 +320,15 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2018': { '1': { 'nb_workouts': 1, + 'total_ascent': 40.0, + 'total_descent': 20.0, 'total_distance': 8.0, 'total_duration': 6000, }, '2': { 'nb_workouts': 1, + 'total_ascent': 0.0, + 'total_descent': 0.0, 'total_distance': 12.0, 'total_duration': 6000, }, @@ -330,6 +358,8 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2017-03': { '1': { 'nb_workouts': 1, + 'total_ascent': 120.0, + 'total_descent': 200.0, 'total_distance': 5.0, 'total_duration': 1024, } @@ -337,6 +367,8 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2017-06': { '1': { 'nb_workouts': 1, + 'total_ascent': 100.0, + 'total_descent': 80.0, 'total_distance': 10.0, 'total_duration': 3456, } @@ -344,6 +376,8 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2018-01': { '1': { 'nb_workouts': 1, + 'total_ascent': 80.0, + 'total_descent': 100.0, 'total_distance': 10.0, 'total_duration': 1024, } @@ -351,6 +385,8 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2018-02': { '1': { 'nb_workouts': 2, + 'total_ascent': 220.0, + 'total_descent': 380.0, 'total_distance': 11.0, 'total_duration': 1600, } @@ -358,11 +394,15 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2018-04': { '1': { 'nb_workouts': 1, + 'total_ascent': 40.0, + 'total_descent': 20.0, 'total_distance': 8.0, 'total_duration': 6000, }, '2': { 'nb_workouts': 1, + 'total_ascent': 0.0, + 'total_descent': 0.0, 'total_distance': 12.0, 'total_duration': 6000, }, @@ -370,6 +410,8 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2018-05': { '1': { 'nb_workouts': 1, + 'total_ascent': 0.0, + 'total_descent': 0.0, 'total_distance': 10.0, 'total_duration': 3000, } @@ -399,6 +441,8 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2017-03': { '1': { 'nb_workouts': 1, + 'total_ascent': 120.0, + 'total_descent': 200.0, 'total_distance': 5.0, 'total_duration': 1024, } @@ -406,6 +450,8 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2017-06': { '1': { 'nb_workouts': 1, + 'total_ascent': 100.0, + 'total_descent': 80.0, 'total_distance': 10.0, 'total_duration': 3456, } @@ -413,6 +459,8 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2018-01': { '1': { 'nb_workouts': 1, + 'total_ascent': 80.0, + 'total_descent': 100.0, 'total_distance': 10.0, 'total_duration': 1024, } @@ -420,6 +468,8 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2018-02': { '1': { 'nb_workouts': 2, + 'total_ascent': 220.0, + 'total_descent': 380.0, 'total_distance': 11.0, 'total_duration': 1600, } @@ -427,11 +477,15 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2018-04': { '1': { 'nb_workouts': 1, + 'total_ascent': 40.0, + 'total_descent': 20.0, 'total_distance': 8.0, 'total_duration': 6000, }, '2': { 'nb_workouts': 1, + 'total_ascent': 0.0, + 'total_descent': 0.0, 'total_distance': 12.0, 'total_duration': 6000, }, @@ -439,6 +493,8 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2018-05': { '1': { 'nb_workouts': 1, + 'total_ascent': 0.0, + 'total_descent': 0.0, 'total_distance': 10.0, 'total_duration': 3000, } @@ -468,11 +524,15 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2018-04': { '1': { 'nb_workouts': 1, + 'total_ascent': 40.0, + 'total_descent': 20.0, 'total_distance': 8.0, 'total_duration': 6000, }, '2': { 'nb_workouts': 1, + 'total_ascent': 0.0, + 'total_descent': 0.0, 'total_distance': 12.0, 'total_duration': 6000, }, @@ -502,6 +562,8 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2017-03-19': { '1': { 'nb_workouts': 1, + 'total_ascent': 120.0, + 'total_descent': 200.0, 'total_distance': 5.0, 'total_duration': 1024, } @@ -509,6 +571,8 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2017-05-28': { '1': { 'nb_workouts': 1, + 'total_ascent': 100.0, + 'total_descent': 80.0, 'total_distance': 10.0, 'total_duration': 3456, } @@ -516,6 +580,8 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2017-12-31': { '1': { 'nb_workouts': 1, + 'total_ascent': 80.0, + 'total_descent': 100.0, 'total_distance': 10.0, 'total_duration': 1024, } @@ -523,6 +589,8 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2018-02-18': { '1': { 'nb_workouts': 2, + 'total_ascent': 220.0, + 'total_descent': 380.0, 'total_distance': 11.0, 'total_duration': 1600, } @@ -530,11 +598,15 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2018-04-01': { '1': { 'nb_workouts': 1, + 'total_ascent': 40.0, + 'total_descent': 20.0, 'total_distance': 8.0, 'total_duration': 6000, }, '2': { 'nb_workouts': 1, + 'total_ascent': 0.0, + 'total_descent': 0.0, 'total_distance': 12.0, 'total_duration': 6000, }, @@ -542,6 +614,8 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2018-05-06': { '1': { 'nb_workouts': 1, + 'total_ascent': 0.0, + 'total_descent': 0.0, 'total_distance': 10.0, 'total_duration': 3000, } @@ -571,11 +645,15 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2018-04-01': { '1': { 'nb_workouts': 1, + 'total_ascent': 40.0, + 'total_descent': 20.0, 'total_distance': 8.0, 'total_duration': 6000, }, '2': { 'nb_workouts': 1, + 'total_ascent': 0.0, + 'total_descent': 0.0, 'total_distance': 12.0, 'total_duration': 6000, }, @@ -605,6 +683,8 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2017-03-20': { '1': { 'nb_workouts': 1, + 'total_ascent': 120.0, + 'total_descent': 200.0, 'total_distance': 5.0, 'total_duration': 1024, } @@ -612,6 +692,8 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2017-05-29': { '1': { 'nb_workouts': 1, + 'total_ascent': 100.0, + 'total_descent': 80.0, 'total_distance': 10.0, 'total_duration': 3456, } @@ -619,6 +701,8 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2018-01-01': { '1': { 'nb_workouts': 1, + 'total_ascent': 80.0, + 'total_descent': 100.0, 'total_distance': 10.0, 'total_duration': 1024, } @@ -626,6 +710,8 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2018-02-19': { '1': { 'nb_workouts': 2, + 'total_ascent': 220.0, + 'total_descent': 380.0, 'total_distance': 11.0, 'total_duration': 1600, } @@ -633,11 +719,15 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2018-03-26': { '1': { 'nb_workouts': 1, + 'total_ascent': 40.0, + 'total_descent': 20.0, 'total_distance': 8.0, 'total_duration': 6000, }, '2': { 'nb_workouts': 1, + 'total_ascent': 0.0, + 'total_descent': 0.0, 'total_distance': 12.0, 'total_duration': 6000, }, @@ -645,6 +735,8 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2018-05-07': { '1': { 'nb_workouts': 1, + 'total_ascent': 0.0, + 'total_descent': 0.0, 'total_distance': 10.0, 'total_duration': 3000, } @@ -674,11 +766,15 @@ class TestGetStatsByTime(ApiTestCaseMixin): '2018-03-26': { '1': { 'nb_workouts': 1, + 'total_ascent': 40.0, + 'total_descent': 20.0, 'total_distance': 8.0, 'total_duration': 6000, }, '2': { 'nb_workouts': 1, + 'total_ascent': 0.0, + 'total_descent': 0.0, 'total_distance': 12.0, 'total_duration': 6000, }, @@ -709,11 +805,15 @@ class TestGetStatsBySport(ApiTestCaseMixin): assert data['data']['statistics'] == { '1': { 'nb_workouts': 7, + 'total_ascent': 560.0, + 'total_descent': 780.0, 'total_distance': 54.0, 'total_duration': 16104, }, '2': { 'nb_workouts': 1, + 'total_ascent': 0.0, + 'total_descent': 0.0, 'total_distance': 12.0, 'total_duration': 6000, }, @@ -741,6 +841,8 @@ class TestGetStatsBySport(ApiTestCaseMixin): assert data['data']['statistics'] == { '1': { 'nb_workouts': 7, + 'total_ascent': 560.0, + 'total_descent': 780.0, 'total_distance': 54.0, 'total_duration': 16104, } diff --git a/fittrackee/workouts/stats.py b/fittrackee/workouts/stats.py index 5ca9a23e..cc1713e3 100644 --- a/fittrackee/workouts/stats.py +++ b/fittrackee/workouts/stats.py @@ -77,12 +77,14 @@ def get_workouts( workouts_list_by_sport[sport_id][ 'total_duration' ] += convert_timedelta_to_integer(workout.moving) - workouts_list_by_sport[sport_id]['total_ascent'] += float( - workout.ascent - ) - workouts_list_by_sport[sport_id]['total_descent'] += float( - workout.descent - ) + if workout.ascent: + workouts_list_by_sport[sport_id]['total_ascent'] += float( + workout.ascent + ) + if workout.descent: + workouts_list_by_sport[sport_id]['total_descent'] += float( + workout.descent + ) # filter_type == 'by_time' else: @@ -133,11 +135,11 @@ def get_workouts( if workout.ascent: workouts_list_by_time[time_period][sport_id][ 'total_ascent' - ] += float(workout.ascent / 1000) + ] += float(workout.ascent) if workout.descent: workouts_list_by_time[time_period][sport_id][ 'total_descent' - ] += float(workout.descent / 1000) + ] += float(workout.descent) return { 'status': 'success', 'data': { @@ -188,6 +190,8 @@ def get_workouts_by_time( "2017": { "3": { "nb_workouts": 2, + "total_ascent": 203.0, + "total_ascent": 156.0, "total_distance": 15.282, "total_duration": 12341 } @@ -195,11 +199,15 @@ def get_workouts_by_time( "2019": { "1": { "nb_workouts": 3, + "total_ascent": 150.0, + "total_ascent": 178.0, "total_distance": 47, "total_duration": 9960 }, "2": { "nb_workouts": 1, + "total_ascent": 46.0, + "total_ascent": 78.0, "total_distance": 5.613, "total_duration": 1267 } @@ -285,16 +293,22 @@ def get_workouts_by_sport( "statistics": { "1": { "nb_workouts": 3, + "total_ascent": 150.0, + "total_ascent": 178.0, "total_distance": 47, "total_duration": 9960 }, "2": { "nb_workouts": 1, + "total_ascent": 46.0, + "total_ascent": 78.0, "total_distance": 5.613, "total_duration": 1267 }, "3": { "nb_workouts": 2, + "total_ascent": 203.0, + "total_ascent": 156.0, "total_distance": 15.282, "total_duration": 12341 }