API - replace 'Activity' with 'Workout' - #58
This commit is contained in:
parent
24ee5bbcfa
commit
3a80e01cc2
3
Makefile
3
Makefile
@ -19,6 +19,9 @@ clean-install:
|
||||
rm -rf .pytest_cache
|
||||
rm -rf dist/
|
||||
|
||||
downgrade-db:
|
||||
$(FLASK) db downgrade --directory $(MIGRATIONS)
|
||||
|
||||
html:
|
||||
rm -rf docsrc/build
|
||||
rm -rf docs/*
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
---
|
||||
|
||||
This web application allows you to track your outdoor activities from gpx files and keep your data on your own server.
|
||||
This web application allows you to track your outdoor activities (workouts) from gpx files and keep your data on your own server.
|
||||
No mobile app is developed yet, but several existing mobile apps can store workouts data locally and export them into a gpx file.
|
||||
Examples (for Android):
|
||||
* [Runner Up](https://github.com/jonasoreland/runnerup) (GPL v3)
|
||||
|
@ -1,17 +0,0 @@
|
||||
Activities
|
||||
##########
|
||||
|
||||
.. autoflask:: fittrackee:create_app()
|
||||
:endpoints:
|
||||
activities.get_activities,
|
||||
activities.get_activity,
|
||||
activities.get_activity_gpx,
|
||||
activities.get_activity_chart_data,
|
||||
activities.get_segment_chart_data,
|
||||
activities.get_segment_gpx,
|
||||
activities.get_map,
|
||||
activities.get_map_tile,
|
||||
activities.post_activity,
|
||||
activities.post_activity_no_gpx,
|
||||
activities.update_activity,
|
||||
activities.delete_activity
|
@ -5,10 +5,10 @@ API documentation
|
||||
:maxdepth: 2
|
||||
:caption: Endpoints:
|
||||
|
||||
activities
|
||||
auth
|
||||
configuration
|
||||
records
|
||||
sports
|
||||
stats
|
||||
users
|
||||
workouts
|
||||
|
@ -3,6 +3,6 @@ Statistics
|
||||
|
||||
.. autoflask:: fittrackee:create_app()
|
||||
:endpoints:
|
||||
stats.get_activities_by_time,
|
||||
stats.get_activities_by_sport,
|
||||
stats.get_workouts_by_time,
|
||||
stats.get_workouts_by_sport,
|
||||
stats.get_application_stats
|
||||
|
17
docs/_sources/api/workouts.rst.txt
Normal file
17
docs/_sources/api/workouts.rst.txt
Normal file
@ -0,0 +1,17 @@
|
||||
Workouts
|
||||
##########
|
||||
|
||||
.. autoflask:: fittrackee:create_app()
|
||||
:endpoints:
|
||||
workouts.get_workouts,
|
||||
workouts.get_workout,
|
||||
workouts.get_workout_gpx,
|
||||
workouts.get_workout_chart_data,
|
||||
workouts.get_segment_chart_data,
|
||||
workouts.get_segment_gpx,
|
||||
workouts.get_map,
|
||||
workouts.get_map_tile,
|
||||
workouts.post_workout,
|
||||
workouts.post_workout_no_gpx,
|
||||
workouts.update_workout,
|
||||
workouts.delete_workout
|
@ -25,7 +25,7 @@ Administration
|
||||
|
||||
- **Sports**
|
||||
|
||||
- enable or disable a sport (a sport can be disabled even if activity with this sport exists)
|
||||
- enable or disable a sport (a sport can be disabled even if workout with this sport exists)
|
||||
|
||||
Account
|
||||
^^^^^^^
|
||||
@ -33,29 +33,29 @@ Account
|
||||
- A user can reset his password (*new in 0.3.0*)
|
||||
|
||||
|
||||
Activities/Workouts
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
- 6 sports supported:
|
||||
Workouts
|
||||
^^^^^^^^
|
||||
- 6 sports are supported:
|
||||
- Cycling (Sport)
|
||||
- Cycling (Transport)
|
||||
- Hiking
|
||||
- Montain Biking
|
||||
- Running
|
||||
- Walking
|
||||
- Dashboard with month calendar displaying activities and record. The week can start on Sunday or Monday (which can be changed in the user settings)
|
||||
- Activity creation by uploading a gpx file. An activity can even be created without gpx (the user must enter date, time, duration and distance)
|
||||
- An activity with a gpx file can be displayed with map, weather (if the DarkSky API key is provided) and charts (speed and elevation). Segments can be displayed
|
||||
- Activity edition and deletion. User can add a note
|
||||
- Dashboard with month calendar displaying workouts and record. The week can start on Sunday or Monday (which can be changed in the user settings)
|
||||
- Workout creation by uploading a gpx file. A workout can even be created without gpx (the user must enter date, time, duration and distance)
|
||||
- A workout with a gpx file can be displayed with map, weather (if the DarkSky API key is provided) and charts (speed and elevation). Segments can be displayed
|
||||
- Workout edition and deletion. User can add a note
|
||||
- User statistics
|
||||
- User records by sports:
|
||||
- average speed
|
||||
- farest distance
|
||||
- longest duration
|
||||
- maximum speed
|
||||
- Activities list and filter
|
||||
- Workours list and filter
|
||||
|
||||
.. note::
|
||||
for now, only the owner of the activity can see the activity.
|
||||
for now, only the owner of the workout can see it.
|
||||
|
||||
Translations
|
||||
^^^^^^^^^^^^
|
||||
@ -69,16 +69,16 @@ Dashboard
|
||||
:alt: FitTrackee Dashboard
|
||||
|
||||
|
||||
Activity/workout detail
|
||||
Workout detail
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.. figure:: _images/fittrackee_screenshot-02.png
|
||||
:alt: FitTrackee Activity
|
||||
:alt: FitTrackee Workout
|
||||
|
||||
|
||||
Activities/workouts list
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Workouts list
|
||||
~~~~~~~~~~~~~
|
||||
.. figure:: _images/fittrackee_screenshot-03.png
|
||||
:alt: FitTrackee Activities
|
||||
:alt: FitTrackee Workouts
|
||||
|
||||
|
||||
Statistics
|
||||
|
@ -5,8 +5,8 @@
|
||||
FitTrackee
|
||||
==========
|
||||
|
||||
| This web application allows you to track your outdoor activities from
|
||||
gpx files and keep your data on your own server.
|
||||
| This web application allows you to track your outdoor activities (workouts)
|
||||
from gpx files and keep your data on your own server.
|
||||
| No mobile app is developed yet, but several existing mobile apps can
|
||||
store workouts data locally and export them into a gpx file.
|
||||
| Examples (for Android):
|
||||
|
@ -16,7 +16,7 @@
|
||||
<link rel="index" title="Index" href="../genindex.html" />
|
||||
<link rel="search" title="Search" href="../search.html" />
|
||||
<link rel="next" title="Configuration" href="configuration.html" />
|
||||
<link rel="prev" title="Activities" href="activities.html" />
|
||||
<link rel="prev" title="API documentation" href="index.html" />
|
||||
<meta charset='utf-8'>
|
||||
<meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1'>
|
||||
@ -86,7 +86,7 @@
|
||||
|
||||
|
||||
<li>
|
||||
<a href="activities.html" title="Previous Chapter: Activities"><span class="glyphicon glyphicon-chevron-left visible-sm"></span><span class="hidden-sm hidden-tablet">« Activities</span>
|
||||
<a href="index.html" title="Previous Chapter: API documentation"><span class="glyphicon glyphicon-chevron-left visible-sm"></span><span class="hidden-sm hidden-tablet">« API documentation</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
@ -321,8 +321,8 @@
|
||||
<span class="nt">"language"</span><span class="p">:</span> <span class="s2">"en"</span><span class="p">,</span>
|
||||
<span class="nt">"last_name"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"location"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"nb_activities"</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
|
||||
<span class="nt">"nb_sports"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"nb_workouts"</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
|
||||
<span class="nt">"picture"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
<span class="nt">"sports_list"</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="mi">1</span><span class="p">,</span>
|
||||
@ -383,8 +383,8 @@
|
||||
<span class="nt">"language"</span><span class="p">:</span> <span class="s2">"en"</span><span class="p">,</span>
|
||||
<span class="nt">"last_name"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"location"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"nb_activities"</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
|
||||
<span class="nt">"nb_sports"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"nb_workouts"</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
|
||||
<span class="nt">"picture"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
<span class="nt">"sports_list"</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="mi">1</span><span class="p">,</span>
|
||||
|
@ -15,7 +15,7 @@
|
||||
<script src="../_static/doctools.js"></script>
|
||||
<link rel="index" title="Index" href="../genindex.html" />
|
||||
<link rel="search" title="Search" href="../search.html" />
|
||||
<link rel="next" title="Activities" href="activities.html" />
|
||||
<link rel="next" title="Authentication" href="auth.html" />
|
||||
<link rel="prev" title="Features" href="../features.html" />
|
||||
<meta charset='utf-8'>
|
||||
<meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'>
|
||||
@ -90,7 +90,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="activities.html" title="Next Chapter: Activities"><span class="glyphicon glyphicon-chevron-right visible-sm"></span><span class="hidden-sm hidden-tablet">Activities »</span>
|
||||
<a href="auth.html" title="Next Chapter: Authentication"><span class="glyphicon glyphicon-chevron-right visible-sm"></span><span class="hidden-sm hidden-tablet">Authentication »</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@ -129,13 +129,13 @@
|
||||
<div class="toctree-wrapper compound">
|
||||
<p class="caption"><span class="caption-text">Endpoints:</span></p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="activities.html">Activities</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="auth.html">Authentication</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="configuration.html">Configuration</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="records.html">Records</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="sports.html">Sports</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="stats.html">Statistics</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="users.html">Users</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="workouts.html">Workouts</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -155,40 +155,40 @@
|
||||
<span class="nt">"data"</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="nt">"records"</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"activity_date"</span><span class="p">:</span> <span class="s2">"Sun, 07 Jul 2019 08:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"activity_id"</span><span class="p">:</span> <span class="s2">"hvYBqYBRa7wwXpaStWR4V2"</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">9</span><span class="p">,</span>
|
||||
<span class="nt">"record_type"</span><span class="p">:</span> <span class="s2">"AS"</span><span class="p">,</span>
|
||||
<span class="nt">"sport_id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"user"</span><span class="p">:</span> <span class="s2">"admin"</span><span class="p">,</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mi">18</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mi">18</span><span class="p">,</span>
|
||||
<span class="nt">"workout_date"</span><span class="p">:</span> <span class="s2">"Sun, 07 Jul 2019 08:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"workout_id"</span><span class="p">:</span> <span class="s2">"hvYBqYBRa7wwXpaStWR4V2"</span>
|
||||
<span class="p">},</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"activity_date"</span><span class="p">:</span> <span class="s2">"Sun, 07 Jul 2019 08:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"activity_id"</span><span class="p">:</span> <span class="s2">"hvYBqYBRa7wwXpaStWR4V2"</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span>
|
||||
<span class="nt">"record_type"</span><span class="p">:</span> <span class="s2">"FD"</span><span class="p">,</span>
|
||||
<span class="nt">"sport_id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"user"</span><span class="p">:</span> <span class="s2">"admin"</span><span class="p">,</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mi">18</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mi">18</span><span class="p">,</span>
|
||||
<span class="nt">"workout_date"</span><span class="p">:</span> <span class="s2">"Sun, 07 Jul 2019 08:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"workout_id"</span><span class="p">:</span> <span class="s2">"hvYBqYBRa7wwXpaStWR4V2"</span>
|
||||
<span class="p">},</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"activity_date"</span><span class="p">:</span> <span class="s2">"Sun, 07 Jul 2019 08:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"activity_id"</span><span class="p">:</span> <span class="s2">"hvYBqYBRa7wwXpaStWR4V2"</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">11</span><span class="p">,</span>
|
||||
<span class="nt">"record_type"</span><span class="p">:</span> <span class="s2">"LD"</span><span class="p">,</span>
|
||||
<span class="nt">"sport_id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"user"</span><span class="p">:</span> <span class="s2">"admin"</span><span class="p">,</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="s2">"1:01:00"</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="s2">"1:01:00"</span><span class="p">,</span>
|
||||
<span class="nt">"workout_date"</span><span class="p">:</span> <span class="s2">"Sun, 07 Jul 2019 08:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"workout_id"</span><span class="p">:</span> <span class="s2">"hvYBqYBRa7wwXpaStWR4V2"</span>
|
||||
<span class="p">},</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"activity_date"</span><span class="p">:</span> <span class="s2">"Sun, 07 Jul 2019 08:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"activity_id"</span><span class="p">:</span> <span class="s2">"hvYBqYBRa7wwXpaStWR4V2"</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">12</span><span class="p">,</span>
|
||||
<span class="nt">"record_type"</span><span class="p">:</span> <span class="s2">"MS"</span><span class="p">,</span>
|
||||
<span class="nt">"sport_id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"user"</span><span class="p">:</span> <span class="s2">"admin"</span><span class="p">,</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mi">18</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mi">18</span><span class="p">,</span>
|
||||
<span class="nt">"workout_date"</span><span class="p">:</span> <span class="s2">"Sun, 07 Jul 2019 08:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"workout_id"</span><span class="p">:</span> <span class="s2">"hvYBqYBRa7wwXpaStWR4V2"</span>
|
||||
<span class="p">}</span>
|
||||
<span class="p">]</span>
|
||||
<span class="p">},</span>
|
||||
|
@ -197,42 +197,42 @@
|
||||
<span class="nt">"data"</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="nt">"sports"</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"has_activities"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
|
||||
<span class="nt">"has_workouts"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"img"</span><span class="p">:</span> <span class="s2">"/img/sports/cycling-sport.png"</span><span class="p">,</span>
|
||||
<span class="nt">"is_active"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
|
||||
<span class="nt">"label"</span><span class="p">:</span> <span class="s2">"Cycling (Sport)"</span>
|
||||
<span class="p">},</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"has_activities"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
<span class="nt">"has_workouts"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
|
||||
<span class="nt">"img"</span><span class="p">:</span> <span class="s2">"/img/sports/cycling-transport.png"</span><span class="p">,</span>
|
||||
<span class="nt">"is_active"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
|
||||
<span class="nt">"label"</span><span class="p">:</span> <span class="s2">"Cycling (Transport)"</span>
|
||||
<span class="p">},</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"has_activities"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
<span class="nt">"has_workouts"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"img"</span><span class="p">:</span> <span class="s2">"/img/sports/hiking.png"</span><span class="p">,</span>
|
||||
<span class="nt">"is_active"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
|
||||
<span class="nt">"label"</span><span class="p">:</span> <span class="s2">"Hiking"</span>
|
||||
<span class="p">},</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"has_activities"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
<span class="nt">"has_workouts"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
|
||||
<span class="nt">"img"</span><span class="p">:</span> <span class="s2">"/img/sports/mountain-biking.png"</span><span class="p">,</span>
|
||||
<span class="nt">"is_active"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
|
||||
<span class="nt">"label"</span><span class="p">:</span> <span class="s2">"Mountain Biking"</span>
|
||||
<span class="p">},</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"has_activities"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
<span class="nt">"has_workouts"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span>
|
||||
<span class="nt">"img"</span><span class="p">:</span> <span class="s2">"/img/sports/running.png"</span><span class="p">,</span>
|
||||
<span class="nt">"is_active"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
|
||||
<span class="nt">"label"</span><span class="p">:</span> <span class="s2">"Running"</span>
|
||||
<span class="p">},</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"has_activities"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
<span class="nt">"has_workouts"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
|
||||
<span class="nt">"img"</span><span class="p">:</span> <span class="s2">"/img/sports/walking.png"</span><span class="p">,</span>
|
||||
<span class="nt">"is_active"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
|
||||
@ -310,7 +310,7 @@
|
||||
<span class="nt">"data"</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="nt">"sports"</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"has_activities"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
<span class="nt">"has_workouts"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"img"</span><span class="p">:</span> <span class="s2">"/img/sports/cycling-sport.png"</span><span class="p">,</span>
|
||||
<span class="nt">"is_active"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
|
||||
@ -384,7 +384,7 @@ Authenticated user must be an admin</p>
|
||||
<span class="nt">"data"</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="nt">"sports"</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"has_activities"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
<span class="nt">"has_workouts"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"img"</span><span class="p">:</span> <span class="s2">"/img/sports/cycling-sport.png"</span><span class="p">,</span>
|
||||
<span class="nt">"is_active"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
|
@ -129,7 +129,7 @@
|
||||
<dl class="http get">
|
||||
<dt id="get--api-stats-(user_name)-by_time">
|
||||
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/stats/</code><span class="sig-paren">(</span><em class="sig-param">user_name</em><span class="sig-paren">)</span><code class="sig-name descname">/by_time</code><a class="headerlink" href="#get--api-stats-(user_name)-by_time" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Get activities statistics for a user by time</p>
|
||||
<dd><p>Get workouts statistics for a user by time</p>
|
||||
<p><strong>Example requests</strong>:</p>
|
||||
<ul class="simple">
|
||||
<li><p>without parameters</p></li>
|
||||
@ -140,7 +140,8 @@
|
||||
<ul class="simple">
|
||||
<li><p>with parameters</p></li>
|
||||
</ul>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/stats/admin/by_time?from=2018-01-01&to=2018-06-30&time=week</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/stats/admin/by_time?from=2018-01-01&to=2018-06-30&time=week</span>
|
||||
<span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Example responses</strong>:</p>
|
||||
@ -155,19 +156,19 @@
|
||||
<span class="nt">"statistics"</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="nt">"2017"</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="nt">"3"</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="nt">"nb_activities"</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
|
||||
<span class="nt">"nb_workouts"</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
|
||||
<span class="nt">"total_distance"</span><span class="p">:</span> <span class="mf">15.282</span><span class="p">,</span>
|
||||
<span class="nt">"total_duration"</span><span class="p">:</span> <span class="mi">12341</span>
|
||||
<span class="p">}</span>
|
||||
<span class="p">},</span>
|
||||
<span class="nt">"2019"</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="nt">"1"</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="nt">"nb_activities"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"nb_workouts"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"total_distance"</span><span class="p">:</span> <span class="mi">47</span><span class="p">,</span>
|
||||
<span class="nt">"total_duration"</span><span class="p">:</span> <span class="mi">9960</span>
|
||||
<span class="p">},</span>
|
||||
<span class="nt">"2"</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="nt">"nb_activities"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"nb_workouts"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"total_distance"</span><span class="p">:</span> <span class="mf">5.613</span><span class="p">,</span>
|
||||
<span class="nt">"total_duration"</span><span class="p">:</span> <span class="mi">1267</span>
|
||||
<span class="p">}</span>
|
||||
@ -179,7 +180,7 @@
|
||||
</pre></div>
|
||||
</div>
|
||||
<ul class="simple">
|
||||
<li><p>no activities</p></li>
|
||||
<li><p>no workouts</p></li>
|
||||
</ul>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
|
||||
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
|
||||
@ -239,10 +240,10 @@
|
||||
<dl class="http get">
|
||||
<dt id="get--api-stats-(user_name)-by_sport">
|
||||
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/stats/</code><span class="sig-paren">(</span><em class="sig-param">user_name</em><span class="sig-paren">)</span><code class="sig-name descname">/by_sport</code><a class="headerlink" href="#get--api-stats-(user_name)-by_sport" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Get activities statistics for a user by sport</p>
|
||||
<dd><p>Get workouts statistics for a user by sport</p>
|
||||
<p><strong>Example requests</strong>:</p>
|
||||
<ul class="simple">
|
||||
<li><p>without parameters (get stats for all sports with activities)</p></li>
|
||||
<li><p>without parameters (get stats for all sports with workouts)</p></li>
|
||||
</ul>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/stats/admin/by_sport</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
</pre></div>
|
||||
@ -264,17 +265,17 @@
|
||||
<span class="nt">"data"</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="nt">"statistics"</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="nt">"1"</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="nt">"nb_activities"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"nb_workouts"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"total_distance"</span><span class="p">:</span> <span class="mi">47</span><span class="p">,</span>
|
||||
<span class="nt">"total_duration"</span><span class="p">:</span> <span class="mi">9960</span>
|
||||
<span class="p">},</span>
|
||||
<span class="nt">"2"</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="nt">"nb_activities"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"nb_workouts"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"total_distance"</span><span class="p">:</span> <span class="mf">5.613</span><span class="p">,</span>
|
||||
<span class="nt">"total_duration"</span><span class="p">:</span> <span class="mi">1267</span>
|
||||
<span class="p">},</span>
|
||||
<span class="nt">"3"</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="nt">"nb_activities"</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
|
||||
<span class="nt">"nb_workouts"</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
|
||||
<span class="nt">"total_distance"</span><span class="p">:</span> <span class="mf">15.282</span><span class="p">,</span>
|
||||
<span class="nt">"total_duration"</span><span class="p">:</span> <span class="mi">12341</span>
|
||||
<span class="p">}</span>
|
||||
@ -285,7 +286,7 @@
|
||||
</pre></div>
|
||||
</div>
|
||||
<ul class="simple">
|
||||
<li><p>no activities</p></li>
|
||||
<li><p>no workouts</p></li>
|
||||
</ul>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
|
||||
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
|
||||
@ -348,10 +349,10 @@
|
||||
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"data"</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="nt">"activities"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"sports"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"uploads_dir_size"</span><span class="p">:</span> <span class="mi">1000</span><span class="p">,</span>
|
||||
<span class="nt">"users"</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
|
||||
<span class="nt">"uploads_dir_size"</span><span class="p">:</span> <span class="mi">1000</span>
|
||||
<span class="nt">"workouts"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="p">},</span>
|
||||
<span class="nt">"status"</span><span class="p">:</span> <span class="s2">"success"</span>
|
||||
<span class="p">}</span>
|
||||
|
@ -15,7 +15,7 @@
|
||||
<script src="../_static/doctools.js"></script>
|
||||
<link rel="index" title="Index" href="../genindex.html" />
|
||||
<link rel="search" title="Search" href="../search.html" />
|
||||
<link rel="next" title="Troubleshooting" href="../troubleshooting/index.html" />
|
||||
<link rel="next" title="Workouts" href="workouts.html" />
|
||||
<link rel="prev" title="Statistics" href="stats.html" />
|
||||
<meta charset='utf-8'>
|
||||
<meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'>
|
||||
@ -90,7 +90,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../troubleshooting/index.html" title="Next Chapter: Troubleshooting"><span class="glyphicon glyphicon-chevron-right visible-sm"></span><span class="hidden-sm hidden-tablet">Troubleshooting »</span>
|
||||
<a href="workouts.html" title="Next Chapter: Workouts"><span class="glyphicon glyphicon-chevron-right visible-sm"></span><span class="hidden-sm hidden-tablet">Workouts »</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@ -141,7 +141,7 @@
|
||||
<ul class="simple">
|
||||
<li><p>with some query parameters</p></li>
|
||||
</ul>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/users?order_by=activities_count&par_page=5</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/users?order_by=workouts_count&par_page=5</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
@ -162,8 +162,8 @@
|
||||
<span class="nt">"language"</span><span class="p">:</span> <span class="s2">"en"</span><span class="p">,</span>
|
||||
<span class="nt">"last_name"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"location"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"nb_activities"</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
|
||||
<span class="nt">"nb_sports"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"nb_workouts"</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
|
||||
<span class="nt">"picture"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
<span class="nt">"sports_list"</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="mi">1</span><span class="p">,</span>
|
||||
@ -185,8 +185,8 @@
|
||||
<span class="nt">"language"</span><span class="p">:</span> <span class="s2">"fr"</span><span class="p">,</span>
|
||||
<span class="nt">"last_name"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"location"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"nb_activities"</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
|
||||
<span class="nt">"nb_sports"</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
|
||||
<span class="nt">"nb_workouts"</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
|
||||
<span class="nt">"picture"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
<span class="nt">"sports_list"</span><span class="p">:</span> <span class="p">[],</span>
|
||||
<span class="nt">"timezone"</span><span class="p">:</span> <span class="s2">"Europe/Paris"</span><span class="p">,</span>
|
||||
@ -212,7 +212,7 @@
|
||||
<li><p><strong>per_page</strong> (<em>integer</em>) – number of users per page (default: 10, max: 50)</p></li>
|
||||
<li><p><strong>q</strong> (<em>string</em>) – query on user name</p></li>
|
||||
<li><p><strong>order_by</strong> (<em>string</em>) – sorting criteria (<code class="docutils literal notranslate"><span class="pre">username</span></code>, <code class="docutils literal notranslate"><span class="pre">created_at</span></code>,
|
||||
<code class="docutils literal notranslate"><span class="pre">activities_count</span></code>, <code class="docutils literal notranslate"><span class="pre">admin</span></code>)</p></li>
|
||||
<code class="docutils literal notranslate"><span class="pre">workouts_count</span></code>, <code class="docutils literal notranslate"><span class="pre">admin</span></code>)</p></li>
|
||||
<li><p><strong>order</strong> (<em>string</em>) – sorting order (default: <code class="docutils literal notranslate"><span class="pre">asc</span></code>)</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
@ -260,8 +260,8 @@
|
||||
<span class="nt">"language"</span><span class="p">:</span> <span class="s2">"en"</span><span class="p">,</span>
|
||||
<span class="nt">"last_name"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"location"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"nb_activities"</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
|
||||
<span class="nt">"nb_sports"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"nb_workouts"</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
|
||||
<span class="nt">"picture"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
<span class="nt">"sports_list"</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="mi">1</span><span class="p">,</span>
|
||||
@ -367,7 +367,7 @@
|
||||
<span class="nt">"language"</span><span class="p">:</span> <span class="s2">"en"</span><span class="p">,</span>
|
||||
<span class="nt">"last_name"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"location"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"nb_activities"</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
|
||||
<span class="nt">"nb_workouts"</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
|
||||
<span class="nt">"nb_sports"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"picture"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
<span class="nt">"sports_list"</span><span class="p">:</span> <span class="p">[</span>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Activities — FitTrackee 0.4.2
|
||||
<title>Workouts — FitTrackee 0.4.2
|
||||
documentation</title>
|
||||
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
|
||||
<link rel="stylesheet" href="../_static/bootstrap-sphinx.css" type="text/css" />
|
||||
@ -15,8 +15,8 @@
|
||||
<script src="../_static/doctools.js"></script>
|
||||
<link rel="index" title="Index" href="../genindex.html" />
|
||||
<link rel="search" title="Search" href="../search.html" />
|
||||
<link rel="next" title="Authentication" href="auth.html" />
|
||||
<link rel="prev" title="API documentation" href="index.html" />
|
||||
<link rel="next" title="Troubleshooting" href="../troubleshooting/index.html" />
|
||||
<link rel="prev" title="Users" href="users.html" />
|
||||
<meta charset='utf-8'>
|
||||
<meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1'>
|
||||
@ -76,7 +76,7 @@
|
||||
<ul class="dropdown-menu localtoc"
|
||||
role="menu"
|
||||
aria-labelledby="dLabelLocalToc"><ul>
|
||||
<li><a class="reference internal" href="#">Activities</a></li>
|
||||
<li><a class="reference internal" href="#">Workouts</a></li>
|
||||
</ul>
|
||||
</ul>
|
||||
</li>
|
||||
@ -86,11 +86,11 @@
|
||||
|
||||
|
||||
<li>
|
||||
<a href="index.html" title="Previous Chapter: API documentation"><span class="glyphicon glyphicon-chevron-left visible-sm"></span><span class="hidden-sm hidden-tablet">« API documentation</span>
|
||||
<a href="users.html" title="Previous Chapter: Users"><span class="glyphicon glyphicon-chevron-left visible-sm"></span><span class="hidden-sm hidden-tablet">« Users</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="auth.html" title="Next Chapter: Authentication"><span class="glyphicon glyphicon-chevron-right visible-sm"></span><span class="hidden-sm hidden-tablet">Authentication »</span>
|
||||
<a href="../troubleshooting/index.html" title="Next Chapter: Troubleshooting"><span class="glyphicon glyphicon-chevron-right visible-sm"></span><span class="hidden-sm hidden-tablet">Troubleshooting »</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@ -100,7 +100,7 @@
|
||||
|
||||
<li class="hidden-sm">
|
||||
<div id="sourcelink">
|
||||
<a href="../_sources/api/activities.rst.txt"
|
||||
<a href="../_sources/api/workouts.rst.txt"
|
||||
rel="nofollow">Source</a>
|
||||
</div></li>
|
||||
|
||||
@ -124,37 +124,36 @@
|
||||
<div class="row">
|
||||
<div class="body col-md-12 content" role="main">
|
||||
|
||||
<div class="section" id="activities">
|
||||
<h1>Activities<a class="headerlink" href="#activities" title="Permalink to this headline">¶</a></h1>
|
||||
<div class="section" id="workouts">
|
||||
<h1>Workouts<a class="headerlink" href="#workouts" title="Permalink to this headline">¶</a></h1>
|
||||
<dl class="http get">
|
||||
<dt id="get--api-activities">
|
||||
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/activities</code><a class="headerlink" href="#get--api-activities" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Get activities for the authenticated user.</p>
|
||||
<dt id="get--api-workouts">
|
||||
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/workouts</code><a class="headerlink" href="#get--api-workouts" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Get workouts for the authenticated user.</p>
|
||||
<p><strong>Example requests</strong>:</p>
|
||||
<ul class="simple">
|
||||
<li><p>without parameters</p></li>
|
||||
</ul>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/activities/</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/workouts/</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<ul class="simple">
|
||||
<li><p>with some query parameters</p></li>
|
||||
</ul>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/activities?from=2019-07-02&to=2019-07-31&sport_id=1</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/workouts?from=2019-07-02&to=2019-07-31&sport_id=1</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Example responses</strong>:</p>
|
||||
<ul class="simple">
|
||||
<li><p>returning at least one activity</p></li>
|
||||
<li><p>returning at least one workout</p></li>
|
||||
</ul>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
|
||||
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
|
||||
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"data"</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="nt">"activities"</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="nt">"workouts"</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"activity_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"ascent"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"ave_speed"</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
|
||||
<span class="nt">"bounds"</span><span class="p">:</span> <span class="p">[],</span>
|
||||
@ -169,46 +168,46 @@
|
||||
<span class="nt">"min_alt"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"modification_date"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"moving"</span><span class="p">:</span> <span class="s2">"0:17:04"</span><span class="p">,</span>
|
||||
<span class="nt">"next_activity"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"next_workout"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"notes"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"pauses"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"previous_activity"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"previous_workout"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"records"</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"activity_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"activity_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
|
||||
<span class="nt">"record_type"</span><span class="p">:</span> <span class="s2">"MS"</span><span class="p">,</span>
|
||||
<span class="nt">"sport_id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"user"</span><span class="p">:</span> <span class="s2">"admin"</span><span class="p">,</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mf">10.0</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
|
||||
<span class="nt">"workout_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"workout_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span>
|
||||
<span class="p">},</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"activity_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"activity_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"record_type"</span><span class="p">:</span> <span class="s2">"LD"</span><span class="p">,</span>
|
||||
<span class="nt">"sport_id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"user"</span><span class="p">:</span> <span class="s2">"admin"</span><span class="p">,</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="s2">"0:17:04"</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="s2">"0:17:04"</span><span class="p">,</span>
|
||||
<span class="nt">"workout_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"workout_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span>
|
||||
<span class="p">},</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"activity_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"activity_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
|
||||
<span class="nt">"record_type"</span><span class="p">:</span> <span class="s2">"FD"</span><span class="p">,</span>
|
||||
<span class="nt">"sport_id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"user"</span><span class="p">:</span> <span class="s2">"admin"</span><span class="p">,</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mf">10.0</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
|
||||
<span class="nt">"workout_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"workout_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span>
|
||||
<span class="p">},</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"activity_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"activity_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"record_type"</span><span class="p">:</span> <span class="s2">"AS"</span><span class="p">,</span>
|
||||
<span class="nt">"sport_id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"user"</span><span class="p">:</span> <span class="s2">"admin"</span><span class="p">,</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mf">10.0</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
|
||||
<span class="nt">"workout_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"workout_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span>
|
||||
<span class="p">}</span>
|
||||
<span class="p">],</span>
|
||||
<span class="nt">"segments"</span><span class="p">:</span> <span class="p">[],</span>
|
||||
@ -217,7 +216,8 @@
|
||||
<span class="nt">"user"</span><span class="p">:</span> <span class="s2">"admin"</span><span class="p">,</span>
|
||||
<span class="nt">"weather_end"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"weather_start"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"with_gpx"</span><span class="p">:</span> <span class="kc">false</span>
|
||||
<span class="nt">"with_gpx"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
<span class="nt">"workout_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span>
|
||||
<span class="p">}</span>
|
||||
<span class="p">]</span>
|
||||
<span class="p">},</span>
|
||||
@ -226,14 +226,14 @@
|
||||
</pre></div>
|
||||
</div>
|
||||
<ul class="simple">
|
||||
<li><p>returning no activities</p></li>
|
||||
<li><p>returning no workouts</p></li>
|
||||
</ul>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
|
||||
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
|
||||
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"data"</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="nt">"activities"</span><span class="p">:</span> <span class="p">[]</span>
|
||||
<span class="nt">"workouts"</span><span class="p">:</span> <span class="p">[]</span>
|
||||
<span class="p">},</span>
|
||||
<span class="nt">"status"</span><span class="p">:</span> <span class="s2">"success"</span>
|
||||
<span class="p">}</span>
|
||||
@ -248,7 +248,7 @@
|
||||
<dt class="field-even">Query Parameters</dt>
|
||||
<dd class="field-even"><ul class="simple">
|
||||
<li><p><strong>page</strong> (<em>integer</em>) – page if using pagination (default: 1)</p></li>
|
||||
<li><p><strong>per_page</strong> (<em>integer</em>) – number of activities per page
|
||||
<li><p><strong>per_page</strong> (<em>integer</em>) – number of workouts per page
|
||||
(default: 5, max: 50)</p></li>
|
||||
<li><p><strong>sport_id</strong> (<em>integer</em>) – sport id</p></li>
|
||||
<li><p><strong>from</strong> (<em>string</em>) – start date (format: <code class="docutils literal notranslate"><span class="pre">%Y-%m-%d</span></code>)</p></li>
|
||||
@ -285,11 +285,11 @@
|
||||
</dd></dl>
|
||||
|
||||
<dl class="http get">
|
||||
<dt id="get--api-activities-(string-activity_short_id)">
|
||||
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/activities/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">activity_short_id</em><span class="sig-paren">)</span><a class="headerlink" href="#get--api-activities-(string-activity_short_id)" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Get an activity</p>
|
||||
<dt id="get--api-workouts-(string-workout_short_id)">
|
||||
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/workouts/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">workout_short_id</em><span class="sig-paren">)</span><a class="headerlink" href="#get--api-workouts-(string-workout_short_id)" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Get an workout</p>
|
||||
<p><strong>Example request</strong>:</p>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/activities/kjxavSTUrJvoAh2wvCeGEF</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/workouts/kjxavSTUrJvoAh2wvCeGEF</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Example responses</strong>:</p>
|
||||
@ -301,9 +301,8 @@
|
||||
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"data"</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="nt">"activities"</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="nt">"workouts"</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"activity_date"</span><span class="p">:</span> <span class="s2">"Sun, 07 Jul 2019 07:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"ascent"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"ave_speed"</span><span class="p">:</span> <span class="mi">16</span><span class="p">,</span>
|
||||
<span class="nt">"bounds"</span><span class="p">:</span> <span class="p">[],</span>
|
||||
@ -318,10 +317,10 @@
|
||||
<span class="nt">"min_alt"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"modification_date"</span><span class="p">:</span> <span class="s2">"Sun, 14 Jul 2019 18:57:22 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"moving"</span><span class="p">:</span> <span class="s2">"0:45:00"</span><span class="p">,</span>
|
||||
<span class="nt">"next_activity"</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
|
||||
<span class="nt">"notes"</span><span class="p">:</span> <span class="s2">"activity without gpx"</span><span class="p">,</span>
|
||||
<span class="nt">"next_workout"</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
|
||||
<span class="nt">"notes"</span><span class="p">:</span> <span class="s2">"workout without gpx"</span><span class="p">,</span>
|
||||
<span class="nt">"pauses"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"previous_activity"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"previous_workout"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"records"</span><span class="p">:</span> <span class="p">[],</span>
|
||||
<span class="nt">"segments"</span><span class="p">:</span> <span class="p">[],</span>
|
||||
<span class="nt">"sport_id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
@ -329,7 +328,8 @@
|
||||
<span class="nt">"user"</span><span class="p">:</span> <span class="s2">"admin"</span><span class="p">,</span>
|
||||
<span class="nt">"weather_end"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"weather_start"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"with_gpx"</span><span class="p">:</span> <span class="kc">false</span>
|
||||
<span class="nt">"with_gpx"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
<span class="nt">"workout_date"</span><span class="p">:</span> <span class="s2">"Sun, 07 Jul 2019 07:00:00 GMT"</span>
|
||||
<span class="p">}</span>
|
||||
<span class="p">]</span>
|
||||
<span class="p">},</span>
|
||||
@ -345,7 +345,7 @@
|
||||
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"data"</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="nt">"activities"</span><span class="p">:</span> <span class="p">[]</span>
|
||||
<span class="nt">"workouts"</span><span class="p">:</span> <span class="p">[]</span>
|
||||
<span class="p">},</span>
|
||||
<span class="nt">"status"</span><span class="p">:</span> <span class="s2">"not found"</span>
|
||||
<span class="p">}</span>
|
||||
@ -355,7 +355,7 @@
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><strong>auth_user_id</strong> (<em>integer</em>) – authenticate user id (from JSON Web Token)</p></li>
|
||||
<li><p><strong>activity_short_id</strong> (<em>string</em>) – activity short id</p></li>
|
||||
<li><p><strong>workout_short_id</strong> (<em>string</em>) – workout short id</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt class="field-even">Request Headers</dt>
|
||||
@ -373,18 +373,18 @@
|
||||
</ul>
|
||||
</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4">403 Forbidden</a> – You do not have permissions.</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5">404 Not Found</a> – activity not found</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5">404 Not Found</a> – workout not found</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="http get">
|
||||
<dt id="get--api-activities-(string-activity_short_id)-gpx">
|
||||
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/activities/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">activity_short_id</em><span class="sig-paren">)</span><code class="sig-name descname">/gpx</code><a class="headerlink" href="#get--api-activities-(string-activity_short_id)-gpx" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Get gpx file for an activity displayed on map with Leaflet</p>
|
||||
<dt id="get--api-workouts-(string-workout_short_id)-gpx">
|
||||
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/workouts/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">workout_short_id</em><span class="sig-paren">)</span><code class="sig-name descname">/gpx</code><a class="headerlink" href="#get--api-workouts-(string-workout_short_id)-gpx" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Get gpx file for an workout displayed on map with Leaflet</p>
|
||||
<p><strong>Example request</strong>:</p>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/activities/kjxavSTUrJvoAh2wvCeGEF/gpx</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/workouts/kjxavSTUrJvoAh2wvCeGEF/gpx</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
@ -405,7 +405,7 @@
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><strong>auth_user_id</strong> (<em>integer</em>) – authenticate user id (from JSON Web Token)</p></li>
|
||||
<li><p><strong>activity_short_id</strong> (<em>string</em>) – activity short id</p></li>
|
||||
<li><p><strong>workout_short_id</strong> (<em>string</em>) – workout short id</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt class="field-even">Request Headers</dt>
|
||||
@ -423,8 +423,8 @@
|
||||
</ul>
|
||||
</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5">404 Not Found</a> – <ul>
|
||||
<li><p>activity not found</p></li>
|
||||
<li><p>no gpx file for this activity</p></li>
|
||||
<li><p>workout not found</p></li>
|
||||
<li><p>no gpx file for this workout</p></li>
|
||||
</ul>
|
||||
</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1">500 Internal Server Error</a> – </p></li>
|
||||
@ -434,11 +434,11 @@
|
||||
</dd></dl>
|
||||
|
||||
<dl class="http get">
|
||||
<dt id="get--api-activities-(string-activity_short_id)-chart_data">
|
||||
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/activities/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">activity_short_id</em><span class="sig-paren">)</span><code class="sig-name descname">/chart_data</code><a class="headerlink" href="#get--api-activities-(string-activity_short_id)-chart_data" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Get chart data from an activity gpx file, to display it with Recharts</p>
|
||||
<dt id="get--api-workouts-(string-workout_short_id)-chart_data">
|
||||
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/workouts/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">workout_short_id</em><span class="sig-paren">)</span><code class="sig-name descname">/chart_data</code><a class="headerlink" href="#get--api-workouts-(string-workout_short_id)-chart_data" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Get chart data from an workout gpx file, to display it with Recharts</p>
|
||||
<p><strong>Example request</strong>:</p>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/activities/kjxavSTUrJvoAh2wvCeGEF/chart</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/workouts/kjxavSTUrJvoAh2wvCeGEF/chart</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
@ -478,7 +478,7 @@
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><strong>auth_user_id</strong> (<em>integer</em>) – authenticate user id (from JSON Web Token)</p></li>
|
||||
<li><p><strong>activity_short_id</strong> (<em>string</em>) – activity short id</p></li>
|
||||
<li><p><strong>workout_short_id</strong> (<em>string</em>) – workout short id</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt class="field-even">Request Headers</dt>
|
||||
@ -496,8 +496,8 @@
|
||||
</ul>
|
||||
</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5">404 Not Found</a> – <ul>
|
||||
<li><p>activity not found</p></li>
|
||||
<li><p>no gpx file for this activity</p></li>
|
||||
<li><p>workout not found</p></li>
|
||||
<li><p>no gpx file for this workout</p></li>
|
||||
</ul>
|
||||
</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1">500 Internal Server Error</a> – </p></li>
|
||||
@ -507,11 +507,11 @@
|
||||
</dd></dl>
|
||||
|
||||
<dl class="http get">
|
||||
<dt id="get--api-activities-(string-activity_short_id)-chart_data-segment-(int-segment_id)">
|
||||
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/activities/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">activity_short_id</em><span class="sig-paren">)</span><code class="sig-name descname">/chart_data/segment/</code><span class="sig-paren">(</span><em class="property">int: </em><em class="sig-param">segment_id</em><span class="sig-paren">)</span><a class="headerlink" href="#get--api-activities-(string-activity_short_id)-chart_data-segment-(int-segment_id)" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Get chart data from an activity gpx file, to display it with Recharts</p>
|
||||
<dt id="get--api-workouts-(string-workout_short_id)-chart_data-segment-(int-segment_id)">
|
||||
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/workouts/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">workout_short_id</em><span class="sig-paren">)</span><code class="sig-name descname">/chart_data/segment/</code><span class="sig-paren">(</span><em class="property">int: </em><em class="sig-param">segment_id</em><span class="sig-paren">)</span><a class="headerlink" href="#get--api-workouts-(string-workout_short_id)-chart_data-segment-(int-segment_id)" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Get chart data from an workout gpx file, to display it with Recharts</p>
|
||||
<p><strong>Example request</strong>:</p>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/activities/kjxavSTUrJvoAh2wvCeGEF/chart/segment/0</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/workouts/kjxavSTUrJvoAh2wvCeGEF/chart/segment/0</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
@ -551,7 +551,7 @@
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><strong>auth_user_id</strong> (<em>integer</em>) – authenticate user id (from JSON Web Token)</p></li>
|
||||
<li><p><strong>activity_short_id</strong> (<em>string</em>) – activity short id</p></li>
|
||||
<li><p><strong>workout_short_id</strong> (<em>string</em>) – workout short id</p></li>
|
||||
<li><p><strong>segment_id</strong> (<em>integer</em>) – segment id</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
@ -563,14 +563,14 @@
|
||||
<dt class="field-odd">Status Codes</dt>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a> – success</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a> – no gpx file for this activity</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a> – no gpx file for this workout</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a> – <ul>
|
||||
<li><p>Provide a valid auth token.</p></li>
|
||||
<li><p>Signature expired. Please log in again.</p></li>
|
||||
<li><p>Invalid token. Please log in again.</p></li>
|
||||
</ul>
|
||||
</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5">404 Not Found</a> – activity not found</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5">404 Not Found</a> – workout not found</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1">500 Internal Server Error</a> – </p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
@ -578,11 +578,11 @@
|
||||
</dd></dl>
|
||||
|
||||
<dl class="http get">
|
||||
<dt id="get--api-activities-(string-activity_short_id)-gpx-segment-(int-segment_id)">
|
||||
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/activities/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">activity_short_id</em><span class="sig-paren">)</span><code class="sig-name descname">/gpx/segment/</code><span class="sig-paren">(</span><em class="property">int: </em><em class="sig-param">segment_id</em><span class="sig-paren">)</span><a class="headerlink" href="#get--api-activities-(string-activity_short_id)-gpx-segment-(int-segment_id)" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Get gpx file for an activity segment displayed on map with Leaflet</p>
|
||||
<dt id="get--api-workouts-(string-workout_short_id)-gpx-segment-(int-segment_id)">
|
||||
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/workouts/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">workout_short_id</em><span class="sig-paren">)</span><code class="sig-name descname">/gpx/segment/</code><span class="sig-paren">(</span><em class="property">int: </em><em class="sig-param">segment_id</em><span class="sig-paren">)</span><a class="headerlink" href="#get--api-workouts-(string-workout_short_id)-gpx-segment-(int-segment_id)" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Get gpx file for an workout segment displayed on map with Leaflet</p>
|
||||
<p><strong>Example request</strong>:</p>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/activities/kjxavSTUrJvoAh2wvCeGEF/gpx/segment/0</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/workouts/kjxavSTUrJvoAh2wvCeGEF/gpx/segment/0</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
@ -603,7 +603,7 @@
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><strong>auth_user_id</strong> (<em>integer</em>) – authenticate user id (from JSON Web Token)</p></li>
|
||||
<li><p><strong>activity_short_id</strong> (<em>string</em>) – activity short id</p></li>
|
||||
<li><p><strong>workout_short_id</strong> (<em>string</em>) – workout short id</p></li>
|
||||
<li><p><strong>segment_id</strong> (<em>integer</em>) – segment id</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
@ -615,14 +615,14 @@
|
||||
<dt class="field-odd">Status Codes</dt>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a> – success</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a> – no gpx file for this activity</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a> – no gpx file for this workout</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a> – <ul>
|
||||
<li><p>Provide a valid auth token.</p></li>
|
||||
<li><p>Signature expired. Please log in again.</p></li>
|
||||
<li><p>Invalid token. Please log in again.</p></li>
|
||||
</ul>
|
||||
</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5">404 Not Found</a> – activity not found</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5">404 Not Found</a> – workout not found</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1">500 Internal Server Error</a> – </p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
@ -630,11 +630,11 @@
|
||||
</dd></dl>
|
||||
|
||||
<dl class="http get">
|
||||
<dt id="get--api-activities-map-(map_id)">
|
||||
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/activities/map/</code><span class="sig-paren">(</span><em class="sig-param">map_id</em><span class="sig-paren">)</span><a class="headerlink" href="#get--api-activities-map-(map_id)" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Get map image for activities with gpx</p>
|
||||
<dt id="get--api-workouts-map-(map_id)">
|
||||
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/workouts/map/</code><span class="sig-paren">(</span><em class="sig-param">map_id</em><span class="sig-paren">)</span><a class="headerlink" href="#get--api-workouts-map-(map_id)" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Get map image for workouts with gpx</p>
|
||||
<p><strong>Example request</strong>:</p>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/activities/map/fa33f4d996844a5c73ecd1ae24456ab8?1563529507772</span>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/workouts/map/fa33f4d996844a5c73ecd1ae24456ab8?1563529507772</span>
|
||||
<span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
@ -646,7 +646,7 @@
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><strong>map_id</strong> (<em>string</em>) – activity map id</p></li>
|
||||
<li><p><strong>map_id</strong> (<em>string</em>) – workout map id</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt class="field-even">Status Codes</dt>
|
||||
@ -666,11 +666,11 @@
|
||||
</dd></dl>
|
||||
|
||||
<dl class="http get">
|
||||
<dt id="get--api-activities-map_tile-(s)-(z)-(x)-(y).png">
|
||||
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/activities/map_tile/</code><span class="sig-paren">(</span><em class="sig-param">s</em><span class="sig-paren">)</span><code class="sig-name descname">/</code><span class="sig-paren">(</span><em class="sig-param">z</em><span class="sig-paren">)</span><code class="sig-name descname">/</code><span class="sig-paren">(</span><em class="sig-param">x</em><span class="sig-paren">)</span><code class="sig-name descname">/</code><span class="sig-paren">(</span><em class="sig-param">y</em><span class="sig-paren">)</span><code class="sig-name descname">.png</code><a class="headerlink" href="#get--api-activities-map_tile-(s)-(z)-(x)-(y).png" title="Permalink to this definition">¶</a></dt>
|
||||
<dt id="get--api-workouts-map_tile-(s)-(z)-(x)-(y).png">
|
||||
<code class="sig-name descname">GET </code><code class="sig-name descname">/api/workouts/map_tile/</code><span class="sig-paren">(</span><em class="sig-param">s</em><span class="sig-paren">)</span><code class="sig-name descname">/</code><span class="sig-paren">(</span><em class="sig-param">z</em><span class="sig-paren">)</span><code class="sig-name descname">/</code><span class="sig-paren">(</span><em class="sig-param">x</em><span class="sig-paren">)</span><code class="sig-name descname">/</code><span class="sig-paren">(</span><em class="sig-param">y</em><span class="sig-paren">)</span><code class="sig-name descname">.png</code><a class="headerlink" href="#get--api-workouts-map_tile-(s)-(z)-(x)-(y).png" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Get map tile from tile server.</p>
|
||||
<p><strong>Example request</strong>:</p>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/activities/map_tile/c/13/4109/2930.png</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">GET</span> <span class="nn">/api/workouts/map_tile/c/13/4109/2930.png</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Example response</strong>:</p>
|
||||
@ -692,11 +692,11 @@
|
||||
</dd></dl>
|
||||
|
||||
<dl class="http post">
|
||||
<dt id="post--api-activities">
|
||||
<code class="sig-name descname">POST </code><code class="sig-name descname">/api/activities</code><a class="headerlink" href="#post--api-activities" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Post an activity with a gpx file</p>
|
||||
<dt id="post--api-workouts">
|
||||
<code class="sig-name descname">POST </code><code class="sig-name descname">/api/workouts</code><a class="headerlink" href="#post--api-workouts" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Post an workout with a gpx file</p>
|
||||
<p><strong>Example request</strong>:</p>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">POST</span> <span class="nn">/api/activities/</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">POST</span> <span class="nn">/api/workouts/</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">multipart/form-data</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
@ -706,9 +706,8 @@
|
||||
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"data"</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="nt">"activities"</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="nt">"workouts"</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"activity_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"ascent"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"ave_speed"</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
|
||||
<span class="nt">"bounds"</span><span class="p">:</span> <span class="p">[],</span>
|
||||
@ -723,46 +722,46 @@
|
||||
<span class="nt">"min_alt"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"modification_date"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"moving"</span><span class="p">:</span> <span class="s2">"0:17:04"</span><span class="p">,</span>
|
||||
<span class="nt">"next_activity"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"next_workout"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"notes"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"pauses"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"previous_activity"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"previous_workout"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"records"</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"activity_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"activity_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
|
||||
<span class="nt">"record_type"</span><span class="p">:</span> <span class="s2">"MS"</span><span class="p">,</span>
|
||||
<span class="nt">"sport_id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"user"</span><span class="p">:</span> <span class="s2">"admin"</span><span class="p">,</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mf">10.0</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mf">10.</span><span class="p">,</span>
|
||||
<span class="nt">"workout_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"workout_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span>
|
||||
<span class="p">},</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"activity_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"activity_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"record_type"</span><span class="p">:</span> <span class="s2">"LD"</span><span class="p">,</span>
|
||||
<span class="nt">"sport_id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"user"</span><span class="p">:</span> <span class="s2">"admin"</span><span class="p">,</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="s2">"0:17:04"</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="s2">"0:17:04"</span><span class="p">,</span>
|
||||
<span class="nt">"workout_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"workout_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span><span class="p">,</span>
|
||||
<span class="p">},</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"activity_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"activity_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
|
||||
<span class="nt">"record_type"</span><span class="p">:</span> <span class="s2">"FD"</span><span class="p">,</span>
|
||||
<span class="nt">"sport_id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"user"</span><span class="p">:</span> <span class="s2">"admin"</span><span class="p">,</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mf">10.0</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
|
||||
<span class="nt">"workout_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"workout_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span>
|
||||
<span class="p">},</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"activity_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"activity_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"record_type"</span><span class="p">:</span> <span class="s2">"AS"</span><span class="p">,</span>
|
||||
<span class="nt">"sport_id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"user"</span><span class="p">:</span> <span class="s2">"admin"</span><span class="p">,</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mf">10.0</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
|
||||
<span class="nt">"workout_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"workout_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span>
|
||||
<span class="p">}</span>
|
||||
<span class="p">],</span>
|
||||
<span class="nt">"segments"</span><span class="p">:</span> <span class="p">[],</span>
|
||||
@ -771,7 +770,8 @@
|
||||
<span class="nt">"user"</span><span class="p">:</span> <span class="s2">"admin"</span><span class="p">,</span>
|
||||
<span class="nt">"weather_end"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"weather_start"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"with_gpx"</span><span class="p">:</span> <span class="kc">false</span>
|
||||
<span class="nt">"with_gpx"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
<span class="nt">"workout_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span>
|
||||
<span class="p">}</span>
|
||||
<span class="p">]</span>
|
||||
<span class="p">},</span>
|
||||
@ -798,7 +798,7 @@
|
||||
</dd>
|
||||
<dt class="field-even">Status Codes</dt>
|
||||
<dd class="field-even"><ul class="simple">
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.2">201 Created</a> – activity created</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.2">201 Created</a> – workout created</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a> – <ul>
|
||||
<li><p>Invalid payload.</p></li>
|
||||
<li><p>No file part.</p></li>
|
||||
@ -820,11 +820,11 @@
|
||||
</dd></dl>
|
||||
|
||||
<dl class="http post">
|
||||
<dt id="post--api-activities-no_gpx">
|
||||
<code class="sig-name descname">POST </code><code class="sig-name descname">/api/activities/no_gpx</code><a class="headerlink" href="#post--api-activities-no_gpx" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Post an activity without gpx file</p>
|
||||
<dt id="post--api-workouts-no_gpx">
|
||||
<code class="sig-name descname">POST </code><code class="sig-name descname">/api/workouts/no_gpx</code><a class="headerlink" href="#post--api-workouts-no_gpx" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Post an workout without gpx file</p>
|
||||
<p><strong>Example request</strong>:</p>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">POST</span> <span class="nn">/api/activities/no_gpx</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">POST</span> <span class="nn">/api/workouts/no_gpx</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
@ -834,9 +834,8 @@
|
||||
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"data"</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="nt">"activities"</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="nt">"workouts"</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"activity_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"ascent"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"ave_speed"</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
|
||||
<span class="nt">"bounds"</span><span class="p">:</span> <span class="p">[],</span>
|
||||
@ -850,46 +849,46 @@
|
||||
<span class="nt">"min_alt"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"modification_date"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"moving"</span><span class="p">:</span> <span class="s2">"0:17:04"</span><span class="p">,</span>
|
||||
<span class="nt">"next_activity"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"next_workout"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"notes"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"pauses"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"previous_activity"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"previous_workout"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"records"</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"activity_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"activity_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
|
||||
<span class="nt">"record_type"</span><span class="p">:</span> <span class="s2">"MS"</span><span class="p">,</span>
|
||||
<span class="nt">"sport_id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"user"</span><span class="p">:</span> <span class="s2">"admin"</span><span class="p">,</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mf">10.0</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mf">10.</span><span class="p">,</span>
|
||||
<span class="nt">"workout_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"workout_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span>
|
||||
<span class="p">},</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"activity_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"activity_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"record_type"</span><span class="p">:</span> <span class="s2">"LD"</span><span class="p">,</span>
|
||||
<span class="nt">"sport_id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"user"</span><span class="p">:</span> <span class="s2">"admin"</span><span class="p">,</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="s2">"0:17:04"</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="s2">"0:17:04"</span><span class="p">,</span>
|
||||
<span class="nt">"workout_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"workout_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span>
|
||||
<span class="p">},</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"activity_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"activity_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
|
||||
<span class="nt">"record_type"</span><span class="p">:</span> <span class="s2">"FD"</span><span class="p">,</span>
|
||||
<span class="nt">"sport_id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"user"</span><span class="p">:</span> <span class="s2">"admin"</span><span class="p">,</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mf">10.0</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
|
||||
<span class="nt">"workout_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"workout_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span>
|
||||
<span class="p">},</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"activity_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"activity_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"record_type"</span><span class="p">:</span> <span class="s2">"AS"</span><span class="p">,</span>
|
||||
<span class="nt">"sport_id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"user"</span><span class="p">:</span> <span class="s2">"admin"</span><span class="p">,</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mf">10.0</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
|
||||
<span class="nt">"workout_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"workout_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span>
|
||||
<span class="p">}</span>
|
||||
<span class="p">],</span>
|
||||
<span class="nt">"segments"</span><span class="p">:</span> <span class="p">[],</span>
|
||||
@ -899,7 +898,8 @@
|
||||
<span class="nt">"uuid"</span><span class="p">:</span> <span class="nt">"kjxavSTUrJvoAh2wvCeGEF"</span>
|
||||
<span class="nt">"weather_end"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"weather_start"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"with_gpx"</span><span class="p">:</span> <span class="kc">false</span>
|
||||
<span class="nt">"with_gpx"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
<span class="nt">"workout_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span>
|
||||
<span class="p">}</span>
|
||||
<span class="p">]</span>
|
||||
<span class="p">},</span>
|
||||
@ -915,12 +915,12 @@
|
||||
</dd>
|
||||
<dt class="field-even">Request JSON Object</dt>
|
||||
<dd class="field-even"><ul class="simple">
|
||||
<li><p><strong>activity_date</strong> (<em>string</em>) – activity date (format: <code class="docutils literal notranslate"><span class="pre">%Y-%m-%d</span> <span class="pre">%H:%M</span></code>)</p></li>
|
||||
<li><p><strong>distance</strong> (<em>float</em>) – activity distance in km</p></li>
|
||||
<li><p><strong>duration</strong> (<em>integer</em>) – activity duration in seconds</p></li>
|
||||
<li><p><strong>workout_date</strong> (<em>string</em>) – workout date (format: <code class="docutils literal notranslate"><span class="pre">%Y-%m-%d</span> <span class="pre">%H:%M</span></code>)</p></li>
|
||||
<li><p><strong>distance</strong> (<em>float</em>) – workout distance in km</p></li>
|
||||
<li><p><strong>duration</strong> (<em>integer</em>) – workout duration in seconds</p></li>
|
||||
<li><p><strong>notes</strong> (<em>string</em>) – notes (not mandatory)</p></li>
|
||||
<li><p><strong>sport_id</strong> (<em>integer</em>) – activity sport id</p></li>
|
||||
<li><p><strong>title</strong> (<em>string</em>) – activity title</p></li>
|
||||
<li><p><strong>sport_id</strong> (<em>integer</em>) – workout sport id</p></li>
|
||||
<li><p><strong>title</strong> (<em>string</em>) – workout title</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt class="field-odd">Request Headers</dt>
|
||||
@ -930,7 +930,7 @@
|
||||
</dd>
|
||||
<dt class="field-even">Status Codes</dt>
|
||||
<dd class="field-even"><ul class="simple">
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.2">201 Created</a> – activity created</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.2">201 Created</a> – workout created</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a> – invalid payload</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a> – <ul>
|
||||
<li><p>Provide a valid auth token.</p></li>
|
||||
@ -945,11 +945,11 @@
|
||||
</dd></dl>
|
||||
|
||||
<dl class="http patch">
|
||||
<dt id="patch--api-activities-(string-activity_short_id)">
|
||||
<code class="sig-name descname">PATCH </code><code class="sig-name descname">/api/activities/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">activity_short_id</em><span class="sig-paren">)</span><a class="headerlink" href="#patch--api-activities-(string-activity_short_id)" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Update an activity</p>
|
||||
<dt id="patch--api-workouts-(string-workout_short_id)">
|
||||
<code class="sig-name descname">PATCH </code><code class="sig-name descname">/api/workouts/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">workout_short_id</em><span class="sig-paren">)</span><a class="headerlink" href="#patch--api-workouts-(string-workout_short_id)" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Update an workout</p>
|
||||
<p><strong>Example request</strong>:</p>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">PATCH</span> <span class="nn">/api/activities/1</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">PATCH</span> <span class="nn">/api/workouts/1</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
@ -959,9 +959,8 @@
|
||||
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"data"</span><span class="p">:</span> <span class="p">{</span>
|
||||
<span class="nt">"activities"</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="nt">"workouts"</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"activity_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"ascent"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"ave_speed"</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
|
||||
<span class="nt">"bounds"</span><span class="p">:</span> <span class="p">[],</span>
|
||||
@ -975,46 +974,46 @@
|
||||
<span class="nt">"min_alt"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"modification_date"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"moving"</span><span class="p">:</span> <span class="s2">"0:17:04"</span><span class="p">,</span>
|
||||
<span class="nt">"next_activity"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"next_workout"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"notes"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"pauses"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"previous_activity"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"previous_workout"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"records"</span><span class="p">:</span> <span class="p">[</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"activity_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"activity_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
|
||||
<span class="nt">"record_type"</span><span class="p">:</span> <span class="s2">"MS"</span><span class="p">,</span>
|
||||
<span class="nt">"sport_id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"user"</span><span class="p">:</span> <span class="s2">"admin"</span><span class="p">,</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mf">10.0</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
|
||||
<span class="nt">"workout_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"workout_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span>
|
||||
<span class="p">},</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"activity_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"activity_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
|
||||
<span class="nt">"record_type"</span><span class="p">:</span> <span class="s2">"LD"</span><span class="p">,</span>
|
||||
<span class="nt">"sport_id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"user"</span><span class="p">:</span> <span class="s2">"admin"</span><span class="p">,</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="s2">"0:17:04"</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="s2">"0:17:04"</span><span class="p">,</span>
|
||||
<span class="nt">"workout_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"workout_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span>
|
||||
<span class="p">},</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"activity_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"activity_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
|
||||
<span class="nt">"record_type"</span><span class="p">:</span> <span class="s2">"FD"</span><span class="p">,</span>
|
||||
<span class="nt">"sport_id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"user"</span><span class="p">:</span> <span class="s2">"admin"</span><span class="p">,</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mf">10.0</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
|
||||
<span class="nt">"workout_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"workout_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span><span class="p">,</span>
|
||||
<span class="p">},</span>
|
||||
<span class="p">{</span>
|
||||
<span class="nt">"activity_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"activity_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span><span class="p">,</span>
|
||||
<span class="nt">"id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"record_type"</span><span class="p">:</span> <span class="s2">"AS"</span><span class="p">,</span>
|
||||
<span class="nt">"sport_id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
|
||||
<span class="nt">"user"</span><span class="p">:</span> <span class="s2">"admin"</span><span class="p">,</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mf">10.0</span>
|
||||
<span class="nt">"value"</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
|
||||
<span class="nt">"workout_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span><span class="p">,</span>
|
||||
<span class="nt">"workout_id"</span><span class="p">:</span> <span class="s2">"kjxavSTUrJvoAh2wvCeGEF"</span><span class="p">,</span>
|
||||
<span class="p">}</span>
|
||||
<span class="p">],</span>
|
||||
<span class="nt">"segments"</span><span class="p">:</span> <span class="p">[],</span>
|
||||
@ -1024,7 +1023,8 @@
|
||||
<span class="nt">"uuid"</span><span class="p">:</span> <span class="nt">"kjxavSTUrJvoAh2wvCeGEF"</span>
|
||||
<span class="nt">"weather_end"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"weather_start"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
|
||||
<span class="nt">"with_gpx"</span><span class="p">:</span> <span class="kc">false</span>
|
||||
<span class="nt">"with_gpx"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
|
||||
<span class="nt">"workout_date"</span><span class="p">:</span> <span class="s2">"Mon, 01 Jan 2018 00:00:00 GMT"</span>
|
||||
<span class="p">}</span>
|
||||
<span class="p">]</span>
|
||||
<span class="p">},</span>
|
||||
@ -1036,20 +1036,20 @@
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><strong>auth_user_id</strong> (<em>integer</em>) – authenticate user id (from JSON Web Token)</p></li>
|
||||
<li><p><strong>activity_short_id</strong> (<em>string</em>) – activity short id</p></li>
|
||||
<li><p><strong>workout_short_id</strong> (<em>string</em>) – workout short id</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt class="field-even">Request JSON Object</dt>
|
||||
<dd class="field-even"><ul class="simple">
|
||||
<li><p><strong>activity_date</strong> (<em>string</em>) – activity date (format: <code class="docutils literal notranslate"><span class="pre">%Y-%m-%d</span> <span class="pre">%H:%M</span></code>)
|
||||
(only for activity without gpx)</p></li>
|
||||
<li><p><strong>distance</strong> (<em>float</em>) – activity distance in km
|
||||
(only for activity without gpx)</p></li>
|
||||
<li><p><strong>duration</strong> (<em>integer</em>) – activity duration in seconds
|
||||
(only for activity without gpx)</p></li>
|
||||
<li><p><strong>workout_date</strong> (<em>string</em>) – workout date (format: <code class="docutils literal notranslate"><span class="pre">%Y-%m-%d</span> <span class="pre">%H:%M</span></code>)
|
||||
(only for workout without gpx)</p></li>
|
||||
<li><p><strong>distance</strong> (<em>float</em>) – workout distance in km
|
||||
(only for workout without gpx)</p></li>
|
||||
<li><p><strong>duration</strong> (<em>integer</em>) – workout duration in seconds
|
||||
(only for workout without gpx)</p></li>
|
||||
<li><p><strong>notes</strong> (<em>string</em>) – notes</p></li>
|
||||
<li><p><strong>sport_id</strong> (<em>integer</em>) – activity sport id</p></li>
|
||||
<li><p><strong>title</strong> (<em>string</em>) – activity title</p></li>
|
||||
<li><p><strong>sport_id</strong> (<em>integer</em>) – workout sport id</p></li>
|
||||
<li><p><strong>title</strong> (<em>string</em>) – workout title</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt class="field-odd">Request Headers</dt>
|
||||
@ -1059,7 +1059,7 @@
|
||||
</dd>
|
||||
<dt class="field-even">Status Codes</dt>
|
||||
<dd class="field-even"><ul class="simple">
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a> – activity updated</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1">200 OK</a> – workout updated</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">400 Bad Request</a> – invalid payload</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a> – <ul>
|
||||
<li><p>Provide a valid auth token.</p></li>
|
||||
@ -1067,7 +1067,7 @@
|
||||
<li><p>Invalid token. Please log in again.</p></li>
|
||||
</ul>
|
||||
</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5">404 Not Found</a> – activity not found</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5">404 Not Found</a> – workout not found</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1">500 Internal Server Error</a> – </p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
@ -1075,11 +1075,11 @@
|
||||
</dd></dl>
|
||||
|
||||
<dl class="http delete">
|
||||
<dt id="delete--api-activities-(string-activity_short_id)">
|
||||
<code class="sig-name descname">DELETE </code><code class="sig-name descname">/api/activities/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">activity_short_id</em><span class="sig-paren">)</span><a class="headerlink" href="#delete--api-activities-(string-activity_short_id)" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Delete an activity</p>
|
||||
<dt id="delete--api-workouts-(string-workout_short_id)">
|
||||
<code class="sig-name descname">DELETE </code><code class="sig-name descname">/api/workouts/</code><span class="sig-paren">(</span><em class="property">string: </em><em class="sig-param">workout_short_id</em><span class="sig-paren">)</span><a class="headerlink" href="#delete--api-workouts-(string-workout_short_id)" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Delete an workout</p>
|
||||
<p><strong>Example request</strong>:</p>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">DELETE</span> <span class="nn">/api/activities/kjxavSTUrJvoAh2wvCeGEF</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
<div class="highlight-http notranslate"><div class="highlight"><pre><span></span><span class="nf">DELETE</span> <span class="nn">/api/workouts/kjxavSTUrJvoAh2wvCeGEF</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
|
||||
<span class="na">Content-Type</span><span class="o">:</span> <span class="l">application/json</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
@ -1092,7 +1092,7 @@
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><strong>auth_user_id</strong> (<em>integer</em>) – authenticate user id (from JSON Web Token)</p></li>
|
||||
<li><p><strong>activity_short_id</strong> (<em>string</em>) – activity short id</p></li>
|
||||
<li><p><strong>workout_short_id</strong> (<em>string</em>) – workout short id</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt class="field-even">Request Headers</dt>
|
||||
@ -1102,14 +1102,14 @@
|
||||
</dd>
|
||||
<dt class="field-odd">Status Codes</dt>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.5">204 No Content</a> – activity deleted</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.5">204 No Content</a> – workout deleted</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2">401 Unauthorized</a> – <ul>
|
||||
<li><p>Provide a valid auth token.</p></li>
|
||||
<li><p>Signature expired. Please log in again.</p></li>
|
||||
<li><p>Invalid token. Please log in again.</p></li>
|
||||
</ul>
|
||||
</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5">404 Not Found</a> – activity not found</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5">404 Not Found</a> – workout not found</p></li>
|
||||
<li><p><a class="reference external" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1">500 Internal Server Error</a> – Error. Please try again or contact the administrator.</p></li>
|
||||
</ul>
|
||||
</dd>
|
@ -80,13 +80,13 @@
|
||||
<li><a class="reference internal" href="#list">List</a><ul>
|
||||
<li><a class="reference internal" href="#administration">Administration</a></li>
|
||||
<li><a class="reference internal" href="#account">Account</a></li>
|
||||
<li><a class="reference internal" href="#activities-workouts">Activities/Workouts</a></li>
|
||||
<li><a class="reference internal" href="#workouts">Workouts</a></li>
|
||||
<li><a class="reference internal" href="#translations">Translations</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#dashboard">Dashboard</a></li>
|
||||
<li><a class="reference internal" href="#activity-workout-detail">Activity/workout detail</a></li>
|
||||
<li><a class="reference internal" href="#activities-workouts-list">Activities/workouts list</a></li>
|
||||
<li><a class="reference internal" href="#workout-detail">Workout detail</a></li>
|
||||
<li><a class="reference internal" href="#workouts-list">Workouts list</a></li>
|
||||
<li><a class="reference internal" href="#statistics">Statistics</a></li>
|
||||
<li><a class="reference internal" href="#id1">Administration</a></li>
|
||||
</ul>
|
||||
@ -164,7 +164,7 @@
|
||||
</li>
|
||||
<li><p><strong>Sports</strong></p>
|
||||
<ul class="simple">
|
||||
<li><p>enable or disable a sport (a sport can be disabled even if activity with this sport exists)</p></li>
|
||||
<li><p>enable or disable a sport (a sport can be disabled even if workout with this sport exists)</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
@ -176,11 +176,11 @@
|
||||
<li><p>A user can reset his password (<em>new in 0.3.0</em>)</p></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="activities-workouts">
|
||||
<h3>Activities/Workouts<a class="headerlink" href="#activities-workouts" title="Permalink to this headline">¶</a></h3>
|
||||
<div class="section" id="workouts">
|
||||
<h3>Workouts<a class="headerlink" href="#workouts" title="Permalink to this headline">¶</a></h3>
|
||||
<ul class="simple">
|
||||
<li><dl class="simple">
|
||||
<dt>6 sports supported:</dt><dd><ul>
|
||||
<dt>6 sports are supported:</dt><dd><ul>
|
||||
<li><p>Cycling (Sport)</p></li>
|
||||
<li><p>Cycling (Transport)</p></li>
|
||||
<li><p>Hiking</p></li>
|
||||
@ -191,10 +191,10 @@
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li><p>Dashboard with month calendar displaying activities and record. The week can start on Sunday or Monday (which can be changed in the user settings)</p></li>
|
||||
<li><p>Activity creation by uploading a gpx file. An activity can even be created without gpx (the user must enter date, time, duration and distance)</p></li>
|
||||
<li><p>An activity with a gpx file can be displayed with map, weather (if the DarkSky API key is provided) and charts (speed and elevation). Segments can be displayed</p></li>
|
||||
<li><p>Activity edition and deletion. User can add a note</p></li>
|
||||
<li><p>Dashboard with month calendar displaying workouts and record. The week can start on Sunday or Monday (which can be changed in the user settings)</p></li>
|
||||
<li><p>Workout creation by uploading a gpx file. A workout can even be created without gpx (the user must enter date, time, duration and distance)</p></li>
|
||||
<li><p>A workout with a gpx file can be displayed with map, weather (if the DarkSky API key is provided) and charts (speed and elevation). Segments can be displayed</p></li>
|
||||
<li><p>Workout edition and deletion. User can add a note</p></li>
|
||||
<li><p>User statistics</p></li>
|
||||
<li><dl class="simple">
|
||||
<dt>User records by sports:</dt><dd><ul>
|
||||
@ -206,11 +206,11 @@
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li><p>Activities list and filter</p></li>
|
||||
<li><p>Workours list and filter</p></li>
|
||||
</ul>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>for now, only the owner of the activity can see the activity.</p>
|
||||
<p>for now, only the owner of the workout can see it.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="translations">
|
||||
@ -224,16 +224,16 @@
|
||||
<img alt="FitTrackee Dashboard" src="_images/fittrackee_screenshot-01.png" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="activity-workout-detail">
|
||||
<h2>Activity/workout detail<a class="headerlink" href="#activity-workout-detail" title="Permalink to this headline">¶</a></h2>
|
||||
<div class="section" id="workout-detail">
|
||||
<h2>Workout detail<a class="headerlink" href="#workout-detail" title="Permalink to this headline">¶</a></h2>
|
||||
<div class="figure align-default">
|
||||
<img alt="FitTrackee Activity" src="_images/fittrackee_screenshot-02.png" />
|
||||
<img alt="FitTrackee Workout" src="_images/fittrackee_screenshot-02.png" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="activities-workouts-list">
|
||||
<h2>Activities/workouts list<a class="headerlink" href="#activities-workouts-list" title="Permalink to this headline">¶</a></h2>
|
||||
<div class="section" id="workouts-list">
|
||||
<h2>Workouts list<a class="headerlink" href="#workouts-list" title="Permalink to this headline">¶</a></h2>
|
||||
<div class="figure align-default">
|
||||
<img alt="FitTrackee Activities" src="_images/fittrackee_screenshot-03.png" />
|
||||
<img alt="FitTrackee Workouts" src="_images/fittrackee_screenshot-03.png" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="statistics">
|
||||
|
@ -125,46 +125,6 @@
|
||||
<tr class="pcap"><td></td><td> </td><td></td></tr>
|
||||
<tr class="cap" id="cap-/api"><td></td><td>
|
||||
<strong>/api</strong></td><td></td></tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a href="api/activities.html#get--api-activities"><code class="xref">GET /api/activities</code></a></td><td>
|
||||
<em></em></td></tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a href="api/activities.html#get--api-activities-(string-activity_short_id)"><code class="xref">GET /api/activities/(string:activity_short_id)</code></a></td><td>
|
||||
<em></em></td></tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a href="api/activities.html#get--api-activities-(string-activity_short_id)-chart_data"><code class="xref">GET /api/activities/(string:activity_short_id)/chart_data</code></a></td><td>
|
||||
<em></em></td></tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a href="api/activities.html#get--api-activities-(string-activity_short_id)-chart_data-segment-(int-segment_id)"><code class="xref">GET /api/activities/(string:activity_short_id)/chart_data/segment/(int:segment_id)</code></a></td><td>
|
||||
<em></em></td></tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a href="api/activities.html#get--api-activities-(string-activity_short_id)-gpx"><code class="xref">GET /api/activities/(string:activity_short_id)/gpx</code></a></td><td>
|
||||
<em></em></td></tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a href="api/activities.html#get--api-activities-(string-activity_short_id)-gpx-segment-(int-segment_id)"><code class="xref">GET /api/activities/(string:activity_short_id)/gpx/segment/(int:segment_id)</code></a></td><td>
|
||||
<em></em></td></tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a href="api/activities.html#get--api-activities-map-(map_id)"><code class="xref">GET /api/activities/map/(map_id)</code></a></td><td>
|
||||
<em></em></td></tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a href="api/activities.html#get--api-activities-map_tile-(s)-(z)-(x)-(y).png"><code class="xref">GET /api/activities/map_tile/(s)/(z)/(x)/(y).png</code></a></td><td>
|
||||
<em></em></td></tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
@ -233,12 +193,42 @@
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a href="api/activities.html#post--api-activities"><code class="xref">POST /api/activities</code></a></td><td>
|
||||
<a href="api/workouts.html#get--api-workouts"><code class="xref">GET /api/workouts</code></a></td><td>
|
||||
<em></em></td></tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a href="api/activities.html#post--api-activities-no_gpx"><code class="xref">POST /api/activities/no_gpx</code></a></td><td>
|
||||
<a href="api/workouts.html#get--api-workouts-(string-workout_short_id)"><code class="xref">GET /api/workouts/(string:workout_short_id)</code></a></td><td>
|
||||
<em></em></td></tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a href="api/workouts.html#get--api-workouts-(string-workout_short_id)-chart_data"><code class="xref">GET /api/workouts/(string:workout_short_id)/chart_data</code></a></td><td>
|
||||
<em></em></td></tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a href="api/workouts.html#get--api-workouts-(string-workout_short_id)-chart_data-segment-(int-segment_id)"><code class="xref">GET /api/workouts/(string:workout_short_id)/chart_data/segment/(int:segment_id)</code></a></td><td>
|
||||
<em></em></td></tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a href="api/workouts.html#get--api-workouts-(string-workout_short_id)-gpx"><code class="xref">GET /api/workouts/(string:workout_short_id)/gpx</code></a></td><td>
|
||||
<em></em></td></tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a href="api/workouts.html#get--api-workouts-(string-workout_short_id)-gpx-segment-(int-segment_id)"><code class="xref">GET /api/workouts/(string:workout_short_id)/gpx/segment/(int:segment_id)</code></a></td><td>
|
||||
<em></em></td></tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a href="api/workouts.html#get--api-workouts-map-(map_id)"><code class="xref">GET /api/workouts/map/(map_id)</code></a></td><td>
|
||||
<em></em></td></tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a href="api/workouts.html#get--api-workouts-map_tile-(s)-(z)-(x)-(y).png"><code class="xref">GET /api/workouts/map_tile/(s)/(z)/(x)/(y).png</code></a></td><td>
|
||||
<em></em></td></tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
@ -273,7 +263,12 @@
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a href="api/activities.html#delete--api-activities-(string-activity_short_id)"><code class="xref">DELETE /api/activities/(string:activity_short_id)</code></a></td><td>
|
||||
<a href="api/workouts.html#post--api-workouts"><code class="xref">POST /api/workouts</code></a></td><td>
|
||||
<em></em></td></tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a href="api/workouts.html#post--api-workouts-no_gpx"><code class="xref">POST /api/workouts/no_gpx</code></a></td><td>
|
||||
<em></em></td></tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
@ -288,7 +283,7 @@
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a href="api/activities.html#patch--api-activities-(string-activity_short_id)"><code class="xref">PATCH /api/activities/(string:activity_short_id)</code></a></td><td>
|
||||
<a href="api/workouts.html#delete--api-workouts-(string-workout_short_id)"><code class="xref">DELETE /api/workouts/(string:workout_short_id)</code></a></td><td>
|
||||
<em></em></td></tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
@ -305,6 +300,11 @@
|
||||
<td>
|
||||
<a href="api/users.html#patch--api-users-(user_name)"><code class="xref">PATCH /api/users/(user_name)</code></a></td><td>
|
||||
<em></em></td></tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a href="api/workouts.html#patch--api-workouts-(string-workout_short_id)"><code class="xref">PATCH /api/workouts/(string:workout_short_id)</code></a></td><td>
|
||||
<em></em></td></tr>
|
||||
</table>
|
||||
|
||||
|
||||
|
@ -123,8 +123,8 @@
|
||||
<div class="section" id="fittrackee">
|
||||
<h1>FitTrackee<a class="headerlink" href="#fittrackee" title="Permalink to this headline">¶</a></h1>
|
||||
<div class="line-block">
|
||||
<div class="line">This web application allows you to track your outdoor activities from
|
||||
gpx files and keep your data on your own server.</div>
|
||||
<div class="line">This web application allows you to track your outdoor activities (workouts)
|
||||
from gpx files and keep your data on your own server.</div>
|
||||
<div class="line">No mobile app is developed yet, but several existing mobile apps can
|
||||
store workouts data locally and export them into a gpx file.</div>
|
||||
<div class="line">Examples (for Android):</div>
|
||||
@ -164,20 +164,20 @@ Map</a>.</div>
|
||||
<li class="toctree-l1"><a class="reference internal" href="features.html">Features</a><ul>
|
||||
<li class="toctree-l2"><a class="reference internal" href="features.html#list">List</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="features.html#dashboard">Dashboard</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="features.html#activity-workout-detail">Activity/workout detail</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="features.html#activities-workouts-list">Activities/workouts list</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="features.html#workout-detail">Workout detail</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="features.html#workouts-list">Workouts list</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="features.html#statistics">Statistics</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="features.html#id1">Administration</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="api/index.html">API documentation</a><ul>
|
||||
<li class="toctree-l2"><a class="reference internal" href="api/activities.html">Activities</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="api/auth.html">Authentication</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="api/configuration.html">Configuration</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="api/records.html">Records</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="api/sports.html">Sports</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="api/stats.html">Statistics</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="api/users.html">Users</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="api/workouts.html">Workouts</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="troubleshooting/index.html">Troubleshooting</a><ul>
|
||||
|
BIN
docs/objects.inv
BIN
docs/objects.inv
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -16,7 +16,7 @@
|
||||
<link rel="index" title="Index" href="../genindex.html" />
|
||||
<link rel="search" title="Search" href="../search.html" />
|
||||
<link rel="next" title="Administrator" href="administrator.html" />
|
||||
<link rel="prev" title="Users" href="../api/users.html" />
|
||||
<link rel="prev" title="Workouts" href="../api/workouts.html" />
|
||||
<meta charset='utf-8'>
|
||||
<meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1'>
|
||||
@ -86,7 +86,7 @@
|
||||
|
||||
|
||||
<li>
|
||||
<a href="../api/users.html" title="Previous Chapter: Users"><span class="glyphicon glyphicon-chevron-left visible-sm"></span><span class="hidden-sm hidden-tablet">« Users</span>
|
||||
<a href="../api/workouts.html" title="Previous Chapter: Workouts"><span class="glyphicon glyphicon-chevron-left visible-sm"></span><span class="hidden-sm hidden-tablet">« Workouts</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
|
@ -1,17 +0,0 @@
|
||||
Activities
|
||||
##########
|
||||
|
||||
.. autoflask:: fittrackee:create_app()
|
||||
:endpoints:
|
||||
activities.get_activities,
|
||||
activities.get_activity,
|
||||
activities.get_activity_gpx,
|
||||
activities.get_activity_chart_data,
|
||||
activities.get_segment_chart_data,
|
||||
activities.get_segment_gpx,
|
||||
activities.get_map,
|
||||
activities.get_map_tile,
|
||||
activities.post_activity,
|
||||
activities.post_activity_no_gpx,
|
||||
activities.update_activity,
|
||||
activities.delete_activity
|
@ -5,10 +5,10 @@ API documentation
|
||||
:maxdepth: 2
|
||||
:caption: Endpoints:
|
||||
|
||||
activities
|
||||
auth
|
||||
configuration
|
||||
records
|
||||
sports
|
||||
stats
|
||||
users
|
||||
workouts
|
||||
|
@ -3,6 +3,6 @@ Statistics
|
||||
|
||||
.. autoflask:: fittrackee:create_app()
|
||||
:endpoints:
|
||||
stats.get_activities_by_time,
|
||||
stats.get_activities_by_sport,
|
||||
stats.get_workouts_by_time,
|
||||
stats.get_workouts_by_sport,
|
||||
stats.get_application_stats
|
||||
|
17
docsrc/source/api/workouts.rst
Normal file
17
docsrc/source/api/workouts.rst
Normal file
@ -0,0 +1,17 @@
|
||||
Workouts
|
||||
##########
|
||||
|
||||
.. autoflask:: fittrackee:create_app()
|
||||
:endpoints:
|
||||
workouts.get_workouts,
|
||||
workouts.get_workout,
|
||||
workouts.get_workout_gpx,
|
||||
workouts.get_workout_chart_data,
|
||||
workouts.get_segment_chart_data,
|
||||
workouts.get_segment_gpx,
|
||||
workouts.get_map,
|
||||
workouts.get_map_tile,
|
||||
workouts.post_workout,
|
||||
workouts.post_workout_no_gpx,
|
||||
workouts.update_workout,
|
||||
workouts.delete_workout
|
@ -25,7 +25,7 @@ Administration
|
||||
|
||||
- **Sports**
|
||||
|
||||
- enable or disable a sport (a sport can be disabled even if activity with this sport exists)
|
||||
- enable or disable a sport (a sport can be disabled even if workout with this sport exists)
|
||||
|
||||
Account
|
||||
^^^^^^^
|
||||
@ -33,29 +33,29 @@ Account
|
||||
- A user can reset his password (*new in 0.3.0*)
|
||||
|
||||
|
||||
Activities/Workouts
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
- 6 sports supported:
|
||||
Workouts
|
||||
^^^^^^^^
|
||||
- 6 sports are supported:
|
||||
- Cycling (Sport)
|
||||
- Cycling (Transport)
|
||||
- Hiking
|
||||
- Montain Biking
|
||||
- Running
|
||||
- Walking
|
||||
- Dashboard with month calendar displaying activities and record. The week can start on Sunday or Monday (which can be changed in the user settings)
|
||||
- Activity creation by uploading a gpx file. An activity can even be created without gpx (the user must enter date, time, duration and distance)
|
||||
- An activity with a gpx file can be displayed with map, weather (if the DarkSky API key is provided) and charts (speed and elevation). Segments can be displayed
|
||||
- Activity edition and deletion. User can add a note
|
||||
- Dashboard with month calendar displaying workouts and record. The week can start on Sunday or Monday (which can be changed in the user settings)
|
||||
- Workout creation by uploading a gpx file. A workout can even be created without gpx (the user must enter date, time, duration and distance)
|
||||
- A workout with a gpx file can be displayed with map, weather (if the DarkSky API key is provided) and charts (speed and elevation). Segments can be displayed
|
||||
- Workout edition and deletion. User can add a note
|
||||
- User statistics
|
||||
- User records by sports:
|
||||
- average speed
|
||||
- farest distance
|
||||
- longest duration
|
||||
- maximum speed
|
||||
- Activities list and filter
|
||||
- Workours list and filter
|
||||
|
||||
.. note::
|
||||
for now, only the owner of the activity can see the activity.
|
||||
for now, only the owner of the workout can see it.
|
||||
|
||||
Translations
|
||||
^^^^^^^^^^^^
|
||||
@ -69,16 +69,16 @@ Dashboard
|
||||
:alt: FitTrackee Dashboard
|
||||
|
||||
|
||||
Activity/workout detail
|
||||
Workout detail
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.. figure:: _images/fittrackee_screenshot-02.png
|
||||
:alt: FitTrackee Activity
|
||||
:alt: FitTrackee Workout
|
||||
|
||||
|
||||
Activities/workouts list
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Workouts list
|
||||
~~~~~~~~~~~~~
|
||||
.. figure:: _images/fittrackee_screenshot-03.png
|
||||
:alt: FitTrackee Activities
|
||||
:alt: FitTrackee Workouts
|
||||
|
||||
|
||||
Statistics
|
||||
|
@ -5,8 +5,8 @@
|
||||
FitTrackee
|
||||
==========
|
||||
|
||||
| This web application allows you to track your outdoor activities from
|
||||
gpx files and keep your data on your own server.
|
||||
| This web application allows you to track your outdoor activities (workouts)
|
||||
from gpx files and keep your data on your own server.
|
||||
| No mobile app is developed yet, but several existing mobile apps can
|
||||
store workouts data locally and export them into a gpx file.
|
||||
| Examples (for Android):
|
||||
|
@ -62,21 +62,21 @@ def create_app() -> Flask:
|
||||
_, db_app_config = init_config()
|
||||
update_app_config_from_database(app, db_app_config)
|
||||
|
||||
from .activities.activities import activities_blueprint # noqa
|
||||
from .activities.records import records_blueprint # noqa
|
||||
from .activities.sports import sports_blueprint # noqa
|
||||
from .activities.stats import stats_blueprint # noqa
|
||||
from .application.app_config import config_blueprint # noqa
|
||||
from .users.auth import auth_blueprint # noqa
|
||||
from .users.users import users_blueprint # noqa
|
||||
from .workouts.records import records_blueprint # noqa
|
||||
from .workouts.sports import sports_blueprint # noqa
|
||||
from .workouts.stats import stats_blueprint # noqa
|
||||
from .workouts.workouts import workouts_blueprint # noqa
|
||||
|
||||
app.register_blueprint(users_blueprint, url_prefix='/api')
|
||||
app.register_blueprint(auth_blueprint, url_prefix='/api')
|
||||
app.register_blueprint(activities_blueprint, url_prefix='/api')
|
||||
app.register_blueprint(config_blueprint, url_prefix='/api')
|
||||
app.register_blueprint(records_blueprint, url_prefix='/api')
|
||||
app.register_blueprint(sports_blueprint, url_prefix='/api')
|
||||
app.register_blueprint(stats_blueprint, url_prefix='/api')
|
||||
app.register_blueprint(config_blueprint, url_prefix='/api')
|
||||
app.register_blueprint(users_blueprint, url_prefix='/api')
|
||||
app.register_blueprint(workouts_blueprint, url_prefix='/api')
|
||||
|
||||
if app.debug:
|
||||
logging.getLogger('sqlalchemy').setLevel(logging.WARNING)
|
||||
|
@ -6,10 +6,10 @@ from typing import Dict, Optional
|
||||
|
||||
import gunicorn.app.base
|
||||
from fittrackee import create_app, db
|
||||
from fittrackee.activities.models import Activity
|
||||
from fittrackee.activities.utils import update_activity
|
||||
from fittrackee.application.utils import init_config
|
||||
from fittrackee.database_utils import init_database
|
||||
from fittrackee.workouts.models import Workout
|
||||
from fittrackee.workouts.utils import update_workout
|
||||
from flask import Flask
|
||||
from flask_dramatiq import worker
|
||||
from flask_migrate import upgrade
|
||||
@ -68,19 +68,19 @@ def init_data() -> None:
|
||||
|
||||
@app.cli.command()
|
||||
def recalculate() -> None:
|
||||
print("Starting activities data refresh")
|
||||
activities = (
|
||||
Activity.query.filter(Activity.gpx != None) # noqa
|
||||
.order_by(Activity.activity_date.asc()) # noqa
|
||||
print("Starting workouts data refresh")
|
||||
workouts = (
|
||||
Workout.query.filter(Workout.gpx != None) # noqa
|
||||
.order_by(Workout.workout_date.asc()) # noqa
|
||||
.all()
|
||||
)
|
||||
if len(activities) == 0:
|
||||
print('➡️ no activities to upgrade.')
|
||||
if len(workouts) == 0:
|
||||
print('➡️ no workouts to upgrade.')
|
||||
return None
|
||||
pbar = tqdm(activities)
|
||||
for activity in pbar:
|
||||
update_activity(activity)
|
||||
pbar.set_postfix(activitiy_id=activity.id)
|
||||
pbar = tqdm(workouts)
|
||||
for workout in pbar:
|
||||
update_workout(workout)
|
||||
pbar.set_postfix(activitiy_id=workout.id)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
|
@ -1,419 +0,0 @@
|
||||
import hashlib
|
||||
import os
|
||||
import tempfile
|
||||
import zipfile
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Optional, Tuple, Union
|
||||
from uuid import UUID
|
||||
|
||||
import gpxpy.gpx
|
||||
import pytz
|
||||
from fittrackee import appLog, db
|
||||
from flask import current_app
|
||||
from sqlalchemy import exc
|
||||
from staticmap import Line, StaticMap
|
||||
from werkzeug.datastructures import FileStorage
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from ..users.models import User
|
||||
from .models import Activity, ActivitySegment, Sport
|
||||
from .utils_files import get_absolute_file_path
|
||||
from .utils_gpx import get_gpx_info
|
||||
|
||||
|
||||
class ActivityException(Exception):
|
||||
def __init__(
|
||||
self, status: str, message: str, e: Optional[Exception] = None
|
||||
) -> None:
|
||||
self.status = status
|
||||
self.message = message
|
||||
self.e = e
|
||||
|
||||
|
||||
def get_datetime_with_tz(
|
||||
timezone: str, activity_date: datetime, gpx_data: Optional[Dict] = None
|
||||
) -> Tuple[Optional[datetime], datetime]:
|
||||
"""
|
||||
Return naive datetime and datetime with user timezone
|
||||
"""
|
||||
activity_date_tz = None
|
||||
if timezone:
|
||||
user_tz = pytz.timezone(timezone)
|
||||
utc_tz = pytz.utc
|
||||
if gpx_data:
|
||||
# activity date in gpx is in UTC, but in naive datetime
|
||||
fmt = '%Y-%m-%d %H:%M:%S'
|
||||
activity_date_string = activity_date.strftime(fmt)
|
||||
activity_date_tmp = utc_tz.localize(
|
||||
datetime.strptime(activity_date_string, fmt)
|
||||
)
|
||||
activity_date_tz = activity_date_tmp.astimezone(user_tz)
|
||||
else:
|
||||
activity_date_tz = user_tz.localize(activity_date)
|
||||
activity_date = activity_date_tz.astimezone(utc_tz)
|
||||
# make datetime 'naive' like in gpx file
|
||||
activity_date = activity_date.replace(tzinfo=None)
|
||||
|
||||
return activity_date_tz, activity_date
|
||||
|
||||
|
||||
def update_activity_data(
|
||||
activity: Union[Activity, ActivitySegment], gpx_data: Dict
|
||||
) -> Union[Activity, ActivitySegment]:
|
||||
"""
|
||||
Update activity or activity segment with data from gpx file
|
||||
"""
|
||||
activity.pauses = gpx_data['stop_time']
|
||||
activity.moving = gpx_data['moving_time']
|
||||
activity.min_alt = gpx_data['elevation_min']
|
||||
activity.max_alt = gpx_data['elevation_max']
|
||||
activity.descent = gpx_data['downhill']
|
||||
activity.ascent = gpx_data['uphill']
|
||||
activity.max_speed = gpx_data['max_speed']
|
||||
activity.ave_speed = gpx_data['average_speed']
|
||||
return activity
|
||||
|
||||
|
||||
def create_activity(
|
||||
user: User, activity_data: Dict, gpx_data: Optional[Dict] = None
|
||||
) -> Activity:
|
||||
"""
|
||||
Create Activity from data entered by user and from gpx if a gpx file is
|
||||
provided
|
||||
"""
|
||||
activity_date = (
|
||||
gpx_data['start']
|
||||
if gpx_data
|
||||
else datetime.strptime(
|
||||
activity_data['activity_date'], '%Y-%m-%d %H:%M'
|
||||
)
|
||||
)
|
||||
activity_date_tz, activity_date = get_datetime_with_tz(
|
||||
user.timezone, activity_date, gpx_data
|
||||
)
|
||||
|
||||
duration = (
|
||||
gpx_data['duration']
|
||||
if gpx_data
|
||||
else timedelta(seconds=activity_data['duration'])
|
||||
)
|
||||
distance = gpx_data['distance'] if gpx_data else activity_data['distance']
|
||||
title = gpx_data['name'] if gpx_data else activity_data.get('title', '')
|
||||
|
||||
new_activity = Activity(
|
||||
user_id=user.id,
|
||||
sport_id=activity_data['sport_id'],
|
||||
activity_date=activity_date,
|
||||
distance=distance,
|
||||
duration=duration,
|
||||
)
|
||||
new_activity.notes = activity_data.get('notes')
|
||||
|
||||
if title is not None and title != '':
|
||||
new_activity.title = title
|
||||
else:
|
||||
sport = Sport.query.filter_by(id=new_activity.sport_id).first()
|
||||
fmt = "%Y-%m-%d %H:%M:%S"
|
||||
activity_datetime = (
|
||||
activity_date_tz.strftime(fmt)
|
||||
if activity_date_tz
|
||||
else new_activity.activity_date.strftime(fmt)
|
||||
)
|
||||
new_activity.title = f'{sport.label} - {activity_datetime}'
|
||||
|
||||
if gpx_data:
|
||||
new_activity.gpx = gpx_data['filename']
|
||||
new_activity.bounds = gpx_data['bounds']
|
||||
update_activity_data(new_activity, gpx_data)
|
||||
else:
|
||||
new_activity.moving = duration
|
||||
new_activity.ave_speed = (
|
||||
None
|
||||
if duration.seconds == 0
|
||||
else float(new_activity.distance) / (duration.seconds / 3600)
|
||||
)
|
||||
new_activity.max_speed = new_activity.ave_speed
|
||||
return new_activity
|
||||
|
||||
|
||||
def create_segment(
|
||||
activity_id: int, activity_uuid: UUID, segment_data: Dict
|
||||
) -> ActivitySegment:
|
||||
"""
|
||||
Create Activity Segment from gpx data
|
||||
"""
|
||||
new_segment = ActivitySegment(
|
||||
activity_id=activity_id,
|
||||
activity_uuid=activity_uuid,
|
||||
segment_id=segment_data['idx'],
|
||||
)
|
||||
new_segment.duration = segment_data['duration']
|
||||
new_segment.distance = segment_data['distance']
|
||||
update_activity_data(new_segment, segment_data)
|
||||
return new_segment
|
||||
|
||||
|
||||
def update_activity(activity: Activity) -> Activity:
|
||||
"""
|
||||
Update activity data from gpx file
|
||||
"""
|
||||
gpx_data, _, _ = get_gpx_info(
|
||||
get_absolute_file_path(activity.gpx), False, False
|
||||
)
|
||||
updated_activity = update_activity_data(activity, gpx_data)
|
||||
updated_activity.duration = gpx_data['duration']
|
||||
updated_activity.distance = gpx_data['distance']
|
||||
db.session.flush()
|
||||
|
||||
for segment_idx, segment in enumerate(updated_activity.segments):
|
||||
segment_data = gpx_data['segments'][segment_idx]
|
||||
updated_segment = update_activity_data(segment, segment_data)
|
||||
updated_segment.duration = segment_data['duration']
|
||||
updated_segment.distance = segment_data['distance']
|
||||
db.session.flush()
|
||||
|
||||
return updated_activity
|
||||
|
||||
|
||||
def edit_activity(
|
||||
activity: Activity, activity_data: Dict, auth_user_id: int
|
||||
) -> Activity:
|
||||
"""
|
||||
Edit an activity
|
||||
Note: the gpx file is NOT modified
|
||||
|
||||
In a next version, map_data and weather_data will be updated
|
||||
(case of a modified gpx file, see issue #7)
|
||||
"""
|
||||
user = User.query.filter_by(id=auth_user_id).first()
|
||||
if activity_data.get('refresh'):
|
||||
activity = update_activity(activity)
|
||||
if activity_data.get('sport_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(
|
||||
activity_data['activity_date'], '%Y-%m-%d %H:%M'
|
||||
)
|
||||
_, activity.activity_date = get_datetime_with_tz(
|
||||
user.timezone, activity_date
|
||||
)
|
||||
|
||||
if activity_data.get('duration'):
|
||||
activity.duration = timedelta(seconds=activity_data['duration'])
|
||||
activity.moving = activity.duration
|
||||
|
||||
if activity_data.get('distance'):
|
||||
activity.distance = activity_data['distance']
|
||||
|
||||
activity.ave_speed = (
|
||||
None
|
||||
if activity.duration.seconds == 0
|
||||
else float(activity.distance) / (activity.duration.seconds / 3600)
|
||||
)
|
||||
activity.max_speed = activity.ave_speed
|
||||
return activity
|
||||
|
||||
|
||||
def get_file_path(dir_path: str, filename: str) -> str:
|
||||
"""
|
||||
Get full path for a file
|
||||
"""
|
||||
if not os.path.exists(dir_path):
|
||||
os.makedirs(dir_path)
|
||||
file_path = os.path.join(dir_path, filename)
|
||||
return file_path
|
||||
|
||||
|
||||
def get_new_file_path(
|
||||
auth_user_id: int,
|
||||
activity_date: str,
|
||||
sport: str,
|
||||
old_filename: Optional[str] = None,
|
||||
extension: Optional[str] = None,
|
||||
) -> str:
|
||||
"""
|
||||
Generate a file path from user and activity data
|
||||
"""
|
||||
if not extension and old_filename:
|
||||
extension = f".{old_filename.rsplit('.', 1)[1].lower()}"
|
||||
_, new_filename = tempfile.mkstemp(
|
||||
prefix=f'{activity_date}_{sport}_', suffix=extension
|
||||
)
|
||||
dir_path = os.path.join('activities', str(auth_user_id))
|
||||
if not os.path.exists(dir_path):
|
||||
os.makedirs(dir_path)
|
||||
file_path = os.path.join(dir_path, new_filename.split('/')[-1])
|
||||
return file_path
|
||||
|
||||
|
||||
def generate_map(map_filepath: str, map_data: List) -> None:
|
||||
"""
|
||||
Generate and save map image from map data
|
||||
"""
|
||||
m = StaticMap(400, 225, 10)
|
||||
line = Line(map_data, '#3388FF', 4)
|
||||
m.add_line(line)
|
||||
image = m.render()
|
||||
image.save(map_filepath)
|
||||
|
||||
|
||||
def get_map_hash(map_filepath: str) -> str:
|
||||
"""
|
||||
Generate a md5 hash used as id instead of activity id, to retrieve map
|
||||
image (maps are sensitive data)
|
||||
"""
|
||||
md5 = hashlib.md5()
|
||||
absolute_map_filepath = get_absolute_file_path(map_filepath)
|
||||
with open(absolute_map_filepath, 'rb') as f:
|
||||
for chunk in iter(lambda: f.read(128 * md5.block_size), b''):
|
||||
md5.update(chunk)
|
||||
return md5.hexdigest()
|
||||
|
||||
|
||||
def process_one_gpx_file(params: Dict, filename: str) -> Activity:
|
||||
"""
|
||||
Get all data from a gpx file to create an activity with map image
|
||||
"""
|
||||
try:
|
||||
gpx_data, map_data, weather_data = get_gpx_info(params['file_path'])
|
||||
auth_user_id = params['user'].id
|
||||
new_filepath = get_new_file_path(
|
||||
auth_user_id=auth_user_id,
|
||||
activity_date=gpx_data['start'],
|
||||
old_filename=filename,
|
||||
sport=params['sport_label'],
|
||||
)
|
||||
absolute_gpx_filepath = get_absolute_file_path(new_filepath)
|
||||
os.rename(params['file_path'], absolute_gpx_filepath)
|
||||
gpx_data['filename'] = new_filepath
|
||||
|
||||
map_filepath = get_new_file_path(
|
||||
auth_user_id=auth_user_id,
|
||||
activity_date=gpx_data['start'],
|
||||
extension='.png',
|
||||
sport=params['sport_label'],
|
||||
)
|
||||
absolute_map_filepath = get_absolute_file_path(map_filepath)
|
||||
generate_map(absolute_map_filepath, map_data)
|
||||
except (gpxpy.gpx.GPXXMLSyntaxException, TypeError) as e:
|
||||
raise ActivityException('error', 'Error during gpx file parsing.', e)
|
||||
except Exception as e:
|
||||
raise ActivityException('error', 'Error during gpx processing.', e)
|
||||
|
||||
try:
|
||||
new_activity = create_activity(
|
||||
params['user'], params['activity_data'], gpx_data
|
||||
)
|
||||
new_activity.map = map_filepath
|
||||
new_activity.map_id = get_map_hash(map_filepath)
|
||||
new_activity.weather_start = weather_data[0]
|
||||
new_activity.weather_end = weather_data[1]
|
||||
db.session.add(new_activity)
|
||||
db.session.flush()
|
||||
|
||||
for segment_data in gpx_data['segments']:
|
||||
new_segment = create_segment(
|
||||
new_activity.id, new_activity.uuid, segment_data
|
||||
)
|
||||
db.session.add(new_segment)
|
||||
db.session.commit()
|
||||
return new_activity
|
||||
except (exc.IntegrityError, ValueError) as e:
|
||||
raise ActivityException('fail', 'Error during activity save.', e)
|
||||
|
||||
|
||||
def process_zip_archive(common_params: Dict, extract_dir: str) -> List:
|
||||
"""
|
||||
Get files from a zip archive and create activities, if number of files
|
||||
does not exceed defined limit.
|
||||
"""
|
||||
with zipfile.ZipFile(common_params['file_path'], "r") as zip_ref:
|
||||
zip_ref.extractall(extract_dir)
|
||||
|
||||
new_activities = []
|
||||
gpx_files_limit = os.getenv('REACT_APP_GPX_LIMIT_IMPORT', 10)
|
||||
if (
|
||||
gpx_files_limit
|
||||
and isinstance(gpx_files_limit, str)
|
||||
and gpx_files_limit.isdigit()
|
||||
):
|
||||
gpx_files_limit = int(gpx_files_limit)
|
||||
else:
|
||||
gpx_files_limit = 10
|
||||
appLog.warning('GPX limit not configured, set to 10.')
|
||||
gpx_files_ok = 0
|
||||
|
||||
for gpx_file in os.listdir(extract_dir):
|
||||
if (
|
||||
'.' in gpx_file
|
||||
and gpx_file.rsplit('.', 1)[1].lower()
|
||||
in current_app.config['ACTIVITY_ALLOWED_EXTENSIONS']
|
||||
):
|
||||
gpx_files_ok += 1
|
||||
if gpx_files_ok > gpx_files_limit:
|
||||
break
|
||||
file_path = os.path.join(extract_dir, gpx_file)
|
||||
params = common_params
|
||||
params['file_path'] = file_path
|
||||
new_activity = process_one_gpx_file(params, gpx_file)
|
||||
new_activities.append(new_activity)
|
||||
|
||||
return new_activities
|
||||
|
||||
|
||||
def process_files(
|
||||
auth_user_id: int,
|
||||
activity_data: Dict,
|
||||
activity_file: FileStorage,
|
||||
folders: Dict,
|
||||
) -> List:
|
||||
"""
|
||||
Store gpx file or zip archive and create activities
|
||||
"""
|
||||
if activity_file.filename is None:
|
||||
raise ActivityException('error', 'File has no filename.')
|
||||
filename = secure_filename(activity_file.filename)
|
||||
extension = f".{filename.rsplit('.', 1)[1].lower()}"
|
||||
file_path = get_file_path(folders['tmp_dir'], filename)
|
||||
sport = Sport.query.filter_by(id=activity_data.get('sport_id')).first()
|
||||
if not sport:
|
||||
raise ActivityException(
|
||||
'error',
|
||||
f"Sport id: {activity_data.get('sport_id')} does not exist",
|
||||
)
|
||||
user = User.query.filter_by(id=auth_user_id).first()
|
||||
|
||||
common_params = {
|
||||
'user': user,
|
||||
'activity_data': activity_data,
|
||||
'file_path': file_path,
|
||||
'sport_label': sport.label,
|
||||
}
|
||||
|
||||
try:
|
||||
activity_file.save(file_path)
|
||||
except Exception as e:
|
||||
raise ActivityException('error', 'Error during activity file save.', e)
|
||||
|
||||
if extension == ".gpx":
|
||||
return [process_one_gpx_file(common_params, filename)]
|
||||
else:
|
||||
return process_zip_archive(common_params, folders['extract_dir'])
|
||||
|
||||
|
||||
def get_upload_dir_size() -> int:
|
||||
"""
|
||||
Return upload directory size
|
||||
"""
|
||||
upload_path = get_absolute_file_path('')
|
||||
total_size = 0
|
||||
for dir_path, _, filenames in os.walk(upload_path):
|
||||
for f in filenames:
|
||||
fp = os.path.join(dir_path, f)
|
||||
total_size += os.path.getsize(fp)
|
||||
return total_size
|
@ -25,7 +25,7 @@ class BaseConfig:
|
||||
os.getenv('UPLOAD_FOLDER', current_app.root_path), 'uploads'
|
||||
)
|
||||
PICTURE_ALLOWED_EXTENSIONS = {'jpg', 'png', 'gif'}
|
||||
ACTIVITY_ALLOWED_EXTENSIONS = {'gpx', 'zip'}
|
||||
WORKOUT_ALLOWED_EXTENSIONS = {'gpx', 'zip'}
|
||||
TEMPLATES_FOLDER = os.path.join(current_app.root_path, 'email/templates')
|
||||
UI_URL = os.environ.get('UI_URL')
|
||||
EMAIL_URL = os.environ.get('EMAIL_URL')
|
||||
|
@ -1,10 +1,10 @@
|
||||
from fittrackee import db
|
||||
from fittrackee.activities.models import Sport
|
||||
from fittrackee.application.utils import (
|
||||
init_config,
|
||||
update_app_config_from_database,
|
||||
)
|
||||
from fittrackee.users.models import User
|
||||
from fittrackee.workouts.models import Sport
|
||||
from flask import Flask
|
||||
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,244 @@
|
||||
"""rename 'activity' with 'workout'
|
||||
|
||||
Revision ID: 4e8597c50064
|
||||
Revises: 3243cd25eca7
|
||||
Create Date: 2021-01-09 19:41:26.589237
|
||||
|
||||
"""
|
||||
import os
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from flask import current_app
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '4e8597c50064'
|
||||
down_revision = '3243cd25eca7'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def rename_upload_folder(src, dst):
|
||||
upload_folder = current_app.config['UPLOAD_FOLDER']
|
||||
src_directory = f'{upload_folder}/{src}'
|
||||
if os.path.exists(src_directory):
|
||||
try:
|
||||
os.rename(src_directory, f'{upload_folder}/{dst}')
|
||||
except Exception as e:
|
||||
print(
|
||||
f'ERROR: can not rename upload folder \'{src}\' to \'{dst}\':'
|
||||
f'\n {e}.'
|
||||
f'\n Please rename it manually.')
|
||||
|
||||
|
||||
def upgrade():
|
||||
|
||||
op.create_table('workouts',
|
||||
sa.Column('id', sa.Integer(), server_default=sa.text("nextval('workouts_id_seq'::regclass)"), autoincrement=True, nullable=False),
|
||||
sa.Column('uuid', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||
sa.Column('sport_id', sa.Integer(), nullable=False),
|
||||
sa.Column('title', sa.String(length=255), nullable=True),
|
||||
sa.Column('gpx', sa.String(length=255), nullable=True),
|
||||
sa.Column('creation_date', sa.DateTime(), nullable=True),
|
||||
sa.Column('modification_date', sa.DateTime(), nullable=True),
|
||||
sa.Column('workout_date', sa.DateTime(), nullable=False),
|
||||
sa.Column('duration', sa.Interval(), nullable=False),
|
||||
sa.Column('pauses', sa.Interval(), nullable=True),
|
||||
sa.Column('moving', sa.Interval(), nullable=True),
|
||||
sa.Column('distance', sa.Numeric(precision=6, scale=3), nullable=True),
|
||||
sa.Column('min_alt', sa.Numeric(precision=6, scale=2), nullable=True),
|
||||
sa.Column('max_alt', sa.Numeric(precision=6, scale=2), nullable=True),
|
||||
sa.Column('descent', sa.Numeric(precision=7, scale=2), nullable=True),
|
||||
sa.Column('ascent', sa.Numeric(precision=7, scale=2), nullable=True),
|
||||
sa.Column('max_speed', sa.Numeric(precision=6, scale=2), nullable=True),
|
||||
sa.Column('ave_speed', sa.Numeric(precision=6, scale=2), nullable=True),
|
||||
sa.Column('bounds', postgresql.ARRAY(sa.Float()), nullable=True),
|
||||
sa.Column('map', sa.String(length=255), nullable=True),
|
||||
sa.Column('map_id', sa.String(length=50), nullable=True),
|
||||
sa.Column('weather_start', sa.JSON(), nullable=True),
|
||||
sa.Column('weather_end', sa.JSON(), nullable=True),
|
||||
sa.Column('notes', sa.String(length=500), nullable=True),
|
||||
sa.ForeignKeyConstraint(['sport_id'], ['sports.id'], ),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('uuid')
|
||||
)
|
||||
op.execute(
|
||||
"SELECT setval('workouts_id_seq', (SELECT max(id) FROM activities));"
|
||||
)
|
||||
|
||||
op.create_table('workout_segments',
|
||||
sa.Column('workout_id', sa.Integer(), nullable=False),
|
||||
sa.Column('workout_uuid', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('segment_id', sa.Integer(), nullable=False),
|
||||
sa.Column('duration', sa.Interval(), nullable=False),
|
||||
sa.Column('pauses', sa.Interval(), nullable=True),
|
||||
sa.Column('moving', sa.Interval(), nullable=True),
|
||||
sa.Column('distance', sa.Numeric(precision=6, scale=3), nullable=True),
|
||||
sa.Column('min_alt', sa.Numeric(precision=6, scale=2), nullable=True),
|
||||
sa.Column('max_alt', sa.Numeric(precision=6, scale=2), nullable=True),
|
||||
sa.Column('descent', sa.Numeric(precision=7, scale=2), nullable=True),
|
||||
sa.Column('ascent', sa.Numeric(precision=7, scale=2), nullable=True),
|
||||
sa.Column('max_speed', sa.Numeric(precision=6, scale=2), nullable=True),
|
||||
sa.Column('ave_speed', sa.Numeric(precision=6, scale=2), nullable=True),
|
||||
sa.ForeignKeyConstraint(['workout_id'], ['workouts.id'], ),
|
||||
sa.PrimaryKeyConstraint('workout_id', 'segment_id')
|
||||
)
|
||||
|
||||
op.add_column('records', sa.Column('workout_date', sa.DateTime(), nullable=True))
|
||||
op.add_column('records', sa.Column('workout_id', sa.Integer(), nullable=True))
|
||||
op.add_column('records', sa.Column('workout_uuid', postgresql.UUID(as_uuid=True), nullable=True))
|
||||
op.create_foreign_key('records_workout_id_fkey', 'records', 'workouts', ['workout_id'], ['id'])
|
||||
|
||||
op.execute(
|
||||
"""
|
||||
INSERT INTO workouts (id, user_id, sport_id, title, gpx,
|
||||
creation_date, modification_date, workout_date, duration, pauses,
|
||||
moving, distance, min_alt, max_alt, descent, ascent, max_speed,
|
||||
ave_speed, bounds, map, map_id, weather_start, weather_end, notes, uuid)
|
||||
SELECT id, user_id, sport_id, title, REPLACE(gpx, 'activities/', 'workouts/'),
|
||||
creation_date, modification_date, activity_date, duration, pauses,
|
||||
moving, distance, min_alt, max_alt, descent, ascent, max_speed,
|
||||
ave_speed, bounds, REPLACE(map, 'activities/', 'workouts/'), map_id,
|
||||
weather_start, weather_end, notes, uuid
|
||||
FROM activities;
|
||||
"""
|
||||
)
|
||||
|
||||
op.execute(
|
||||
"""
|
||||
INSERT INTO workout_segments (workout_id, workout_uuid, segment_id,
|
||||
duration, pauses, moving, distance, min_alt, max_alt, descent,
|
||||
ascent, max_speed, ave_speed)
|
||||
SELECT activity_id, activity_uuid, segment_id,
|
||||
duration, pauses, moving, distance, min_alt, max_alt, descent,
|
||||
ascent, max_speed, ave_speed FROM activity_segments;
|
||||
"""
|
||||
)
|
||||
|
||||
op.execute(
|
||||
'UPDATE records SET workout_id = activity_id, '
|
||||
' workout_uuid = activity_uuid, '
|
||||
' workout_date = activity_date;'
|
||||
)
|
||||
|
||||
op.alter_column('records', 'workout_date', nullable=False)
|
||||
op.alter_column('records', 'workout_id', nullable=False)
|
||||
op.alter_column('records', 'workout_uuid', nullable=False)
|
||||
op.drop_column('records', 'activity_date')
|
||||
op.drop_column('records', 'activity_id')
|
||||
op.drop_column('records', 'activity_uuid')
|
||||
|
||||
op.drop_table('activity_segments')
|
||||
|
||||
op.drop_table('activities')
|
||||
|
||||
rename_upload_folder('activities', 'workouts')
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.create_table('activities',
|
||||
sa.Column('id', sa.INTEGER(), server_default=sa.text("nextval('activities_id_seq'::regclass)"), autoincrement=True, nullable=False),
|
||||
sa.Column('user_id', sa.INTEGER(), autoincrement=False, nullable=False),
|
||||
sa.Column('sport_id', sa.INTEGER(), autoincrement=False, nullable=False),
|
||||
sa.Column('title', sa.VARCHAR(length=255), autoincrement=False, nullable=True),
|
||||
sa.Column('gpx', sa.VARCHAR(length=255), autoincrement=False, nullable=True),
|
||||
sa.Column('creation_date', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
|
||||
sa.Column('modification_date', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
|
||||
sa.Column('activity_date', postgresql.TIMESTAMP(), autoincrement=False, nullable=False),
|
||||
sa.Column('duration', postgresql.INTERVAL(), autoincrement=False, nullable=False),
|
||||
sa.Column('pauses', postgresql.INTERVAL(), autoincrement=False, nullable=True),
|
||||
sa.Column('moving', postgresql.INTERVAL(), autoincrement=False, nullable=True),
|
||||
sa.Column('distance', sa.NUMERIC(precision=6, scale=3), autoincrement=False, nullable=True),
|
||||
sa.Column('min_alt', sa.NUMERIC(precision=6, scale=2), autoincrement=False, nullable=True),
|
||||
sa.Column('max_alt', sa.NUMERIC(precision=6, scale=2), autoincrement=False, nullable=True),
|
||||
sa.Column('descent', sa.NUMERIC(precision=7, scale=2), autoincrement=False, nullable=True),
|
||||
sa.Column('ascent', sa.NUMERIC(precision=7, scale=2), autoincrement=False, nullable=True),
|
||||
sa.Column('max_speed', sa.NUMERIC(precision=6, scale=2), autoincrement=False, nullable=True),
|
||||
sa.Column('ave_speed', sa.NUMERIC(precision=6, scale=2), autoincrement=False, nullable=True),
|
||||
sa.Column('bounds', postgresql.ARRAY(postgresql.DOUBLE_PRECISION(precision=53)), autoincrement=False, nullable=True),
|
||||
sa.Column('map', sa.VARCHAR(length=255), autoincrement=False, nullable=True),
|
||||
sa.Column('map_id', sa.VARCHAR(length=50), autoincrement=False, nullable=True),
|
||||
sa.Column('weather_end', postgresql.JSON(astext_type=sa.Text()), autoincrement=False, nullable=True),
|
||||
sa.Column('weather_start', postgresql.JSON(astext_type=sa.Text()), autoincrement=False, nullable=True),
|
||||
sa.Column('notes', sa.VARCHAR(length=500), autoincrement=False, nullable=True),
|
||||
sa.Column('uuid', postgresql.UUID(), autoincrement=False, nullable=False),
|
||||
sa.ForeignKeyConstraint(['sport_id'], ['sports.id'], name='activities_sport_id_fkey'),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], name='activities_user_id_fkey'),
|
||||
sa.PrimaryKeyConstraint('id', name='activities_pkey'),
|
||||
sa.UniqueConstraint('uuid', name='activities_uuid_key'),
|
||||
postgresql_ignore_search_path=False
|
||||
)
|
||||
op.execute(
|
||||
"SELECT setval('activities_id_seq', (SELECT max(id) FROM workouts));"
|
||||
)
|
||||
|
||||
op.create_table('activity_segments',
|
||||
sa.Column('activity_id', sa.INTEGER(), autoincrement=False, nullable=False),
|
||||
sa.Column('segment_id', sa.INTEGER(), autoincrement=False, nullable=False),
|
||||
sa.Column('duration', postgresql.INTERVAL(), autoincrement=False, nullable=False),
|
||||
sa.Column('pauses', postgresql.INTERVAL(), autoincrement=False, nullable=True),
|
||||
sa.Column('moving', postgresql.INTERVAL(), autoincrement=False, nullable=True),
|
||||
sa.Column('distance', sa.NUMERIC(precision=6, scale=3), autoincrement=False, nullable=True),
|
||||
sa.Column('min_alt', sa.NUMERIC(precision=6, scale=2), autoincrement=False, nullable=True),
|
||||
sa.Column('max_alt', sa.NUMERIC(precision=6, scale=2), autoincrement=False, nullable=True),
|
||||
sa.Column('descent', sa.NUMERIC(precision=7, scale=2), autoincrement=False, nullable=True),
|
||||
sa.Column('ascent', sa.NUMERIC(precision=7, scale=2), autoincrement=False, nullable=True),
|
||||
sa.Column('max_speed', sa.NUMERIC(precision=6, scale=2), autoincrement=False, nullable=True),
|
||||
sa.Column('ave_speed', sa.NUMERIC(precision=6, scale=2), autoincrement=False, nullable=True),
|
||||
sa.Column('activity_uuid', postgresql.UUID(), autoincrement=False, nullable=False),
|
||||
sa.ForeignKeyConstraint(['activity_id'], ['activities.id'], name='activity_segments_activity_id_fkey'),
|
||||
sa.PrimaryKeyConstraint('activity_id', 'segment_id', name='activity_segments_pkey')
|
||||
)
|
||||
|
||||
op.execute(
|
||||
"""
|
||||
INSERT INTO activities (id, user_id, sport_id, title, gpx,
|
||||
creation_date, modification_date, activity_date, duration, pauses,
|
||||
moving, distance, min_alt, max_alt, descent, ascent, max_speed,
|
||||
ave_speed, bounds, map, map_id, weather_start, weather_end, notes, uuid)
|
||||
SELECT id, user_id, sport_id, title, REPLACE(gpx, 'workouts/', 'activities/'),
|
||||
creation_date, modification_date, workout_date, duration, pauses,
|
||||
moving, distance, min_alt, max_alt, descent, ascent, max_speed,
|
||||
ave_speed, bounds, REPLACE(map, 'workouts/', 'activities/'), map_id,
|
||||
weather_start, weather_end, notes, uuid
|
||||
FROM workouts;
|
||||
"""
|
||||
)
|
||||
|
||||
op.execute(
|
||||
"""
|
||||
INSERT INTO activity_segments (activity_id, activity_uuid, segment_id,
|
||||
duration, pauses, moving, distance, min_alt, max_alt, descent,
|
||||
ascent, max_speed, ave_speed)
|
||||
SELECT workout_id, workout_uuid, segment_id,
|
||||
duration, pauses, moving, distance, min_alt, max_alt, descent,
|
||||
ascent, max_speed, ave_speed FROM workout_segments;
|
||||
"""
|
||||
)
|
||||
|
||||
op.add_column('records', sa.Column('activity_uuid', postgresql.UUID(), autoincrement=False, nullable=True))
|
||||
op.add_column('records', sa.Column('activity_id', sa.INTEGER(), autoincrement=False, nullable=True))
|
||||
op.add_column('records', sa.Column('activity_date', postgresql.TIMESTAMP(), autoincrement=False, nullable=True))
|
||||
op.create_foreign_key('records_activity_id_fkey', 'records', 'activities', ['activity_id'], ['id'])
|
||||
|
||||
op.execute(
|
||||
'UPDATE records SET activity_id = workout_id, '
|
||||
' activity_uuid = workout_uuid, '
|
||||
' activity_date = workout_date;'
|
||||
)
|
||||
|
||||
op.alter_column('records', 'activity_date', nullable=False)
|
||||
op.alter_column('records', 'activity_id', nullable=False)
|
||||
op.alter_column('records', 'activity_uuid', nullable=False)
|
||||
op.drop_column('records', 'workout_uuid')
|
||||
op.drop_column('records', 'workout_id')
|
||||
op.drop_column('records', 'workout_date')
|
||||
|
||||
op.drop_table('workout_segments')
|
||||
|
||||
op.drop_table('workouts')
|
||||
|
||||
rename_upload_folder('workouts', 'activities')
|
@ -1,75 +0,0 @@
|
||||
from uuid import UUID
|
||||
|
||||
from fittrackee.activities.models import Activity, Sport
|
||||
from fittrackee.activities.utils_id import decode_short_id
|
||||
from fittrackee.users.models import User
|
||||
from flask import Flask
|
||||
|
||||
|
||||
class TestActivityModel:
|
||||
def test_activity_model(
|
||||
self,
|
||||
app: Flask,
|
||||
sport_1_cycling: Sport,
|
||||
user_1: User,
|
||||
activity_cycling_user_1: Activity,
|
||||
) -> None:
|
||||
activity_cycling_user_1.title = 'Test'
|
||||
|
||||
assert 1 == activity_cycling_user_1.id
|
||||
assert activity_cycling_user_1.uuid is not None
|
||||
assert 1 == activity_cycling_user_1.user_id
|
||||
assert 1 == activity_cycling_user_1.sport_id
|
||||
assert '2018-01-01 00:00:00' == str(
|
||||
activity_cycling_user_1.activity_date
|
||||
)
|
||||
assert 10.0 == float(activity_cycling_user_1.distance)
|
||||
assert '1:00:00' == str(activity_cycling_user_1.duration)
|
||||
assert 'Test' == activity_cycling_user_1.title
|
||||
assert '<Activity \'Cycling\' - 2018-01-01 00:00:00>' == str(
|
||||
activity_cycling_user_1
|
||||
)
|
||||
|
||||
serialized_activity = activity_cycling_user_1.serialize()
|
||||
assert isinstance(decode_short_id(serialized_activity['id']), UUID)
|
||||
assert 'test' == serialized_activity['user']
|
||||
assert 1 == serialized_activity['sport_id']
|
||||
assert serialized_activity['title'] == 'Test'
|
||||
assert 'creation_date' in serialized_activity
|
||||
assert serialized_activity['modification_date'] is not None
|
||||
assert (
|
||||
str(serialized_activity['activity_date']) == '2018-01-01 00:00:00'
|
||||
)
|
||||
assert serialized_activity['duration'] == '1:00:00'
|
||||
assert serialized_activity['pauses'] is None
|
||||
assert serialized_activity['moving'] == '1:00:00'
|
||||
assert serialized_activity['distance'] == 10.0
|
||||
assert serialized_activity['max_alt'] is None
|
||||
assert serialized_activity['descent'] is None
|
||||
assert serialized_activity['ascent'] is None
|
||||
assert serialized_activity['max_speed'] == 10.0
|
||||
assert serialized_activity['ave_speed'] == 10.0
|
||||
assert serialized_activity['with_gpx'] is False
|
||||
assert serialized_activity['bounds'] == []
|
||||
assert serialized_activity['previous_activity'] is None
|
||||
assert serialized_activity['next_activity'] is None
|
||||
assert serialized_activity['segments'] == []
|
||||
assert serialized_activity['records'] != []
|
||||
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_activity_segment_model(
|
||||
self,
|
||||
app: Flask,
|
||||
sport_1_cycling: Sport,
|
||||
user_1: User,
|
||||
activity_cycling_user_1: Activity,
|
||||
activity_cycling_user_1_segment: Activity,
|
||||
) -> None:
|
||||
assert (
|
||||
f'<Segment \'{activity_cycling_user_1_segment.segment_id}\' '
|
||||
f'for activity \'{activity_cycling_user_1.short_id}\'>'
|
||||
== str(activity_cycling_user_1_segment)
|
||||
)
|
@ -4,10 +4,10 @@ from typing import Generator, Optional
|
||||
|
||||
import pytest
|
||||
from fittrackee import create_app, db
|
||||
from fittrackee.activities.models import Activity, ActivitySegment, Sport
|
||||
from fittrackee.application.models import AppConfig
|
||||
from fittrackee.application.utils import update_app_config_from_database
|
||||
from fittrackee.users.models import User
|
||||
from fittrackee.workouts.models import Sport, Workout, WorkoutSegment
|
||||
|
||||
os.environ['FLASK_ENV'] = 'testing'
|
||||
os.environ['APP_SETTINGS'] = 'fittrackee.config.TestingConfig'
|
||||
@ -180,149 +180,149 @@ def sport_2_running() -> Sport:
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def activity_cycling_user_1() -> Activity:
|
||||
activity = Activity(
|
||||
def workout_cycling_user_1() -> Workout:
|
||||
workout = Workout(
|
||||
user_id=1,
|
||||
sport_id=1,
|
||||
activity_date=datetime.datetime.strptime('01/01/2018', '%d/%m/%Y'),
|
||||
workout_date=datetime.datetime.strptime('01/01/2018', '%d/%m/%Y'),
|
||||
distance=10,
|
||||
duration=datetime.timedelta(seconds=3600),
|
||||
)
|
||||
activity.max_speed = 10
|
||||
activity.ave_speed = 10
|
||||
activity.moving = activity.duration
|
||||
db.session.add(activity)
|
||||
workout.max_speed = 10
|
||||
workout.ave_speed = 10
|
||||
workout.moving = workout.duration
|
||||
db.session.add(workout)
|
||||
db.session.commit()
|
||||
return activity
|
||||
return workout
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def activity_cycling_user_1_segment(
|
||||
activity_cycling_user_1: Activity,
|
||||
) -> ActivitySegment:
|
||||
activity_segment = ActivitySegment(
|
||||
activity_id=activity_cycling_user_1.id,
|
||||
activity_uuid=activity_cycling_user_1.uuid,
|
||||
def workout_cycling_user_1_segment(
|
||||
workout_cycling_user_1: Workout,
|
||||
) -> WorkoutSegment:
|
||||
workout_segment = WorkoutSegment(
|
||||
workout_id=workout_cycling_user_1.id,
|
||||
workout_uuid=workout_cycling_user_1.uuid,
|
||||
segment_id=0,
|
||||
)
|
||||
activity_segment.duration = datetime.timedelta(seconds=6000)
|
||||
activity_segment.moving = activity_segment.duration
|
||||
activity_segment.distance = 5
|
||||
db.session.add(activity_segment)
|
||||
workout_segment.duration = datetime.timedelta(seconds=6000)
|
||||
workout_segment.moving = workout_segment.duration
|
||||
workout_segment.distance = 5
|
||||
db.session.add(workout_segment)
|
||||
db.session.commit()
|
||||
return activity_segment
|
||||
return workout_segment
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def activity_running_user_1() -> Activity:
|
||||
activity = Activity(
|
||||
def workout_running_user_1() -> Workout:
|
||||
workout = Workout(
|
||||
user_id=1,
|
||||
sport_id=2,
|
||||
activity_date=datetime.datetime.strptime('01/04/2018', '%d/%m/%Y'),
|
||||
workout_date=datetime.datetime.strptime('01/04/2018', '%d/%m/%Y'),
|
||||
distance=12,
|
||||
duration=datetime.timedelta(seconds=6000),
|
||||
)
|
||||
activity.moving = activity.duration
|
||||
db.session.add(activity)
|
||||
workout.moving = workout.duration
|
||||
db.session.add(workout)
|
||||
db.session.commit()
|
||||
return activity
|
||||
return workout
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def seven_activities_user_1() -> Activity:
|
||||
activity = Activity(
|
||||
def seven_workouts_user_1() -> Workout:
|
||||
workout = Workout(
|
||||
user_id=1,
|
||||
sport_id=1,
|
||||
activity_date=datetime.datetime.strptime('20/03/2017', '%d/%m/%Y'),
|
||||
workout_date=datetime.datetime.strptime('20/03/2017', '%d/%m/%Y'),
|
||||
distance=5,
|
||||
duration=datetime.timedelta(seconds=1024),
|
||||
)
|
||||
activity.ave_speed = float(activity.distance) / (1024 / 3600)
|
||||
activity.moving = activity.duration
|
||||
db.session.add(activity)
|
||||
workout.ave_speed = float(workout.distance) / (1024 / 3600)
|
||||
workout.moving = workout.duration
|
||||
db.session.add(workout)
|
||||
db.session.flush()
|
||||
activity = Activity(
|
||||
workout = Workout(
|
||||
user_id=1,
|
||||
sport_id=1,
|
||||
activity_date=datetime.datetime.strptime('01/06/2017', '%d/%m/%Y'),
|
||||
workout_date=datetime.datetime.strptime('01/06/2017', '%d/%m/%Y'),
|
||||
distance=10,
|
||||
duration=datetime.timedelta(seconds=3456),
|
||||
)
|
||||
activity.ave_speed = float(activity.distance) / (3456 / 3600)
|
||||
activity.moving = activity.duration
|
||||
db.session.add(activity)
|
||||
workout.ave_speed = float(workout.distance) / (3456 / 3600)
|
||||
workout.moving = workout.duration
|
||||
db.session.add(workout)
|
||||
db.session.flush()
|
||||
activity = Activity(
|
||||
workout = Workout(
|
||||
user_id=1,
|
||||
sport_id=1,
|
||||
activity_date=datetime.datetime.strptime('01/01/2018', '%d/%m/%Y'),
|
||||
workout_date=datetime.datetime.strptime('01/01/2018', '%d/%m/%Y'),
|
||||
distance=10,
|
||||
duration=datetime.timedelta(seconds=1024),
|
||||
)
|
||||
activity.ave_speed = float(activity.distance) / (1024 / 3600)
|
||||
activity.moving = activity.duration
|
||||
db.session.add(activity)
|
||||
workout.ave_speed = float(workout.distance) / (1024 / 3600)
|
||||
workout.moving = workout.duration
|
||||
db.session.add(workout)
|
||||
db.session.flush()
|
||||
activity = Activity(
|
||||
workout = Workout(
|
||||
user_id=1,
|
||||
sport_id=1,
|
||||
activity_date=datetime.datetime.strptime('23/02/2018', '%d/%m/%Y'),
|
||||
workout_date=datetime.datetime.strptime('23/02/2018', '%d/%m/%Y'),
|
||||
distance=1,
|
||||
duration=datetime.timedelta(seconds=600),
|
||||
)
|
||||
activity.ave_speed = float(activity.distance) / (600 / 3600)
|
||||
activity.moving = activity.duration
|
||||
db.session.add(activity)
|
||||
workout.ave_speed = float(workout.distance) / (600 / 3600)
|
||||
workout.moving = workout.duration
|
||||
db.session.add(workout)
|
||||
db.session.flush()
|
||||
activity = Activity(
|
||||
workout = Workout(
|
||||
user_id=1,
|
||||
sport_id=1,
|
||||
activity_date=datetime.datetime.strptime('23/02/2018', '%d/%m/%Y'),
|
||||
workout_date=datetime.datetime.strptime('23/02/2018', '%d/%m/%Y'),
|
||||
distance=10,
|
||||
duration=datetime.timedelta(seconds=1000),
|
||||
)
|
||||
activity.ave_speed = float(activity.distance) / (1000 / 3600)
|
||||
activity.moving = activity.duration
|
||||
db.session.add(activity)
|
||||
workout.ave_speed = float(workout.distance) / (1000 / 3600)
|
||||
workout.moving = workout.duration
|
||||
db.session.add(workout)
|
||||
db.session.flush()
|
||||
activity = Activity(
|
||||
workout = Workout(
|
||||
user_id=1,
|
||||
sport_id=1,
|
||||
activity_date=datetime.datetime.strptime('01/04/2018', '%d/%m/%Y'),
|
||||
workout_date=datetime.datetime.strptime('01/04/2018', '%d/%m/%Y'),
|
||||
distance=8,
|
||||
duration=datetime.timedelta(seconds=6000),
|
||||
)
|
||||
activity.ave_speed = float(activity.distance) / (6000 / 3600)
|
||||
activity.moving = activity.duration
|
||||
db.session.add(activity)
|
||||
workout.ave_speed = float(workout.distance) / (6000 / 3600)
|
||||
workout.moving = workout.duration
|
||||
db.session.add(workout)
|
||||
db.session.flush()
|
||||
activity = Activity(
|
||||
workout = Workout(
|
||||
user_id=1,
|
||||
sport_id=1,
|
||||
activity_date=datetime.datetime.strptime('09/05/2018', '%d/%m/%Y'),
|
||||
workout_date=datetime.datetime.strptime('09/05/2018', '%d/%m/%Y'),
|
||||
distance=10,
|
||||
duration=datetime.timedelta(seconds=3000),
|
||||
)
|
||||
activity.ave_speed = float(activity.distance) / (3000 / 3600)
|
||||
activity.moving = activity.duration
|
||||
db.session.add(activity)
|
||||
workout.ave_speed = float(workout.distance) / (3000 / 3600)
|
||||
workout.moving = workout.duration
|
||||
db.session.add(workout)
|
||||
db.session.commit()
|
||||
return activity
|
||||
return workout
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def activity_cycling_user_2() -> Activity:
|
||||
activity = Activity(
|
||||
def workout_cycling_user_2() -> Workout:
|
||||
workout = Workout(
|
||||
user_id=2,
|
||||
sport_id=1,
|
||||
activity_date=datetime.datetime.strptime('23/01/2018', '%d/%m/%Y'),
|
||||
workout_date=datetime.datetime.strptime('23/01/2018', '%d/%m/%Y'),
|
||||
distance=15,
|
||||
duration=datetime.timedelta(seconds=3600),
|
||||
)
|
||||
activity.moving = activity.duration
|
||||
db.session.add(activity)
|
||||
workout.moving = workout.duration
|
||||
db.session.add(workout)
|
||||
db.session.commit()
|
||||
return activity
|
||||
return workout
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@ -332,7 +332,7 @@ def gpx_file() -> str:
|
||||
'<gpx xmlns:gpxdata="http://www.cluetrust.com/XML/GPXDATA/1/0" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxext="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns="http://www.topografix.com/GPX/1/1">' # noqa
|
||||
' <metadata/>'
|
||||
' <trk>'
|
||||
' <name>just an activity</name>'
|
||||
' <name>just a workout</name>'
|
||||
' <trkseg>'
|
||||
' <trkpt lat="44.68095" lon="6.07367">'
|
||||
' <ele>998</ele>'
|
||||
@ -580,7 +580,7 @@ def gpx_file_with_segments() -> str:
|
||||
'<gpx xmlns:gpxdata="http://www.cluetrust.com/XML/GPXDATA/1/0" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxext="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns="http://www.topografix.com/GPX/1/1">' # noqa
|
||||
' <metadata/>'
|
||||
' <trk>'
|
||||
' <name>just an activity</name>'
|
||||
' <name>just a workout</name>'
|
||||
' <trkseg>'
|
||||
' <trkpt lat="44.68095" lon="6.07367">'
|
||||
' <ele>998</ele>'
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -3,9 +3,9 @@ from datetime import datetime, timedelta
|
||||
from io import BytesIO
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from fittrackee.activities.models import Activity, Sport
|
||||
from fittrackee.users.models import User
|
||||
from fittrackee.users.utils_token import get_user_token
|
||||
from fittrackee.workouts.models import Sport, Workout
|
||||
from flask import Flask
|
||||
from freezegun import freeze_time
|
||||
|
||||
@ -446,8 +446,8 @@ class TestUserProfile:
|
||||
assert data['data']['timezone'] is None
|
||||
assert data['data']['weekm'] is False
|
||||
assert data['data']['language'] is None
|
||||
assert data['data']['nb_activities'] == 0
|
||||
assert data['data']['nb_sports'] == 0
|
||||
assert data['data']['nb_workouts'] == 0
|
||||
assert data['data']['sports_list'] == []
|
||||
assert data['data']['total_distance'] == 0
|
||||
assert data['data']['total_duration'] == '0:00:00'
|
||||
@ -484,21 +484,21 @@ class TestUserProfile:
|
||||
assert data['data']['timezone'] == 'America/New_York'
|
||||
assert data['data']['weekm'] is False
|
||||
assert data['data']['language'] == 'en'
|
||||
assert data['data']['nb_activities'] == 0
|
||||
assert data['data']['nb_sports'] == 0
|
||||
assert data['data']['nb_workouts'] == 0
|
||||
assert data['data']['sports_list'] == []
|
||||
assert data['data']['total_distance'] == 0
|
||||
assert data['data']['total_duration'] == '0:00:00'
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_it_returns_user_profile_with_activities(
|
||||
def test_it_returns_user_profile_with_workouts(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
activity_cycling_user_1: Activity,
|
||||
activity_running_user_1: Activity,
|
||||
workout_cycling_user_1: Workout,
|
||||
workout_running_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -521,8 +521,8 @@ class TestUserProfile:
|
||||
assert data['data']['created_at']
|
||||
assert not data['data']['admin']
|
||||
assert data['data']['timezone'] is None
|
||||
assert data['data']['nb_activities'] == 2
|
||||
assert data['data']['nb_sports'] == 2
|
||||
assert data['data']['nb_workouts'] == 2
|
||||
assert data['data']['sports_list'] == [1, 2]
|
||||
assert data['data']['total_distance'] == 22
|
||||
assert data['data']['total_duration'] == '2:40:00'
|
||||
@ -585,8 +585,8 @@ class TestUserProfileUpdate:
|
||||
assert data['data']['timezone'] == 'America/New_York'
|
||||
assert data['data']['weekm'] is True
|
||||
assert data['data']['language'] == 'fr'
|
||||
assert data['data']['nb_activities'] == 0
|
||||
assert data['data']['nb_sports'] == 0
|
||||
assert data['data']['nb_workouts'] == 0
|
||||
assert data['data']['sports_list'] == []
|
||||
assert data['data']['total_distance'] == 0
|
||||
assert data['data']['total_duration'] == '0:00:00'
|
||||
@ -636,8 +636,8 @@ class TestUserProfileUpdate:
|
||||
assert data['data']['timezone'] == 'America/New_York'
|
||||
assert data['data']['weekm'] is True
|
||||
assert data['data']['language'] == 'fr'
|
||||
assert data['data']['nb_activities'] == 0
|
||||
assert data['data']['nb_sports'] == 0
|
||||
assert data['data']['nb_workouts'] == 0
|
||||
assert data['data']['sports_list'] == []
|
||||
assert data['data']['total_distance'] == 0
|
||||
assert data['data']['total_duration'] == '0:00:00'
|
||||
|
@ -3,13 +3,13 @@ from datetime import datetime, timedelta
|
||||
from io import BytesIO
|
||||
from unittest.mock import patch
|
||||
|
||||
from fittrackee.activities.models import Activity, Sport
|
||||
from fittrackee.users.models import User
|
||||
from fittrackee.workouts.models import Sport, Workout
|
||||
from flask import Flask
|
||||
|
||||
|
||||
class TestGetUser:
|
||||
def test_it_gets_single_user_without_activities(
|
||||
def test_it_gets_single_user_without_workouts(
|
||||
self, app: Flask, user_1: User, user_2: User
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
@ -45,20 +45,20 @@ class TestGetUser:
|
||||
assert user['timezone'] is None
|
||||
assert user['weekm'] is False
|
||||
assert user['language'] is None
|
||||
assert user['nb_activities'] == 0
|
||||
assert user['nb_sports'] == 0
|
||||
assert user['nb_workouts'] == 0
|
||||
assert user['sports_list'] == []
|
||||
assert user['total_distance'] == 0
|
||||
assert user['total_duration'] == '0:00:00'
|
||||
|
||||
def test_it_gets_single_user_with_activities(
|
||||
def test_it_gets_single_user_with_workouts(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
activity_cycling_user_1: Activity,
|
||||
activity_running_user_1: Activity,
|
||||
workout_cycling_user_1: Workout,
|
||||
workout_running_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -93,8 +93,8 @@ class TestGetUser:
|
||||
assert user['timezone'] is None
|
||||
assert user['weekm'] is False
|
||||
assert user['language'] is None
|
||||
assert user['nb_activities'] == 2
|
||||
assert user['nb_sports'] == 2
|
||||
assert user['nb_workouts'] == 2
|
||||
assert user['sports_list'] == [1, 2]
|
||||
assert user['total_distance'] == 22
|
||||
assert user['total_duration'] == '2:40:00'
|
||||
@ -158,24 +158,24 @@ class TestGetUsers:
|
||||
assert data['data']['users'][0]['timezone'] is None
|
||||
assert data['data']['users'][0]['weekm'] is False
|
||||
assert data['data']['users'][0]['language'] is None
|
||||
assert data['data']['users'][0]['nb_activities'] == 0
|
||||
assert data['data']['users'][0]['nb_sports'] == 0
|
||||
assert data['data']['users'][0]['nb_workouts'] == 0
|
||||
assert data['data']['users'][0]['sports_list'] == []
|
||||
assert data['data']['users'][0]['total_distance'] == 0
|
||||
assert data['data']['users'][0]['total_duration'] == '0:00:00'
|
||||
assert data['data']['users'][1]['timezone'] is None
|
||||
assert data['data']['users'][1]['weekm'] is False
|
||||
assert data['data']['users'][1]['language'] is None
|
||||
assert data['data']['users'][1]['nb_activities'] == 0
|
||||
assert data['data']['users'][1]['nb_sports'] == 0
|
||||
assert data['data']['users'][1]['nb_workouts'] == 0
|
||||
assert data['data']['users'][1]['sports_list'] == []
|
||||
assert data['data']['users'][1]['total_distance'] == 0
|
||||
assert data['data']['users'][1]['total_duration'] == '0:00:00'
|
||||
assert data['data']['users'][2]['timezone'] is None
|
||||
assert data['data']['users'][2]['weekm'] is True
|
||||
assert data['data']['users'][2]['language'] is None
|
||||
assert data['data']['users'][2]['nb_activities'] == 0
|
||||
assert data['data']['users'][2]['nb_sports'] == 0
|
||||
assert data['data']['users'][2]['nb_workouts'] == 0
|
||||
assert data['data']['users'][2]['sports_list'] == []
|
||||
assert data['data']['users'][2]['total_distance'] == 0
|
||||
assert data['data']['users'][2]['total_duration'] == '0:00:00'
|
||||
@ -187,17 +187,17 @@ class TestGetUsers:
|
||||
'total': 3,
|
||||
}
|
||||
|
||||
def test_it_gets_users_list_with_activities(
|
||||
def test_it_gets_users_list_with_workouts(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
user_2: User,
|
||||
user_3: User,
|
||||
sport_1_cycling: Sport,
|
||||
activity_cycling_user_1: Activity,
|
||||
workout_cycling_user_1: Workout,
|
||||
sport_2_running: Sport,
|
||||
activity_running_user_1: Activity,
|
||||
activity_cycling_user_2: Activity,
|
||||
workout_running_user_1: Workout,
|
||||
workout_cycling_user_2: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -229,22 +229,22 @@ class TestGetUsers:
|
||||
assert 'sam@test.com' in data['data']['users'][2]['email']
|
||||
assert data['data']['users'][0]['timezone'] is None
|
||||
assert data['data']['users'][0]['weekm'] is False
|
||||
assert data['data']['users'][0]['nb_activities'] == 2
|
||||
assert data['data']['users'][0]['nb_sports'] == 2
|
||||
assert data['data']['users'][0]['nb_workouts'] == 2
|
||||
assert data['data']['users'][0]['sports_list'] == [1, 2]
|
||||
assert data['data']['users'][0]['total_distance'] == 22.0
|
||||
assert data['data']['users'][0]['total_duration'] == '2:40:00'
|
||||
assert data['data']['users'][1]['timezone'] is None
|
||||
assert data['data']['users'][1]['weekm'] is False
|
||||
assert data['data']['users'][1]['nb_activities'] == 1
|
||||
assert data['data']['users'][1]['nb_sports'] == 1
|
||||
assert data['data']['users'][1]['nb_workouts'] == 1
|
||||
assert data['data']['users'][1]['sports_list'] == [1]
|
||||
assert data['data']['users'][1]['total_distance'] == 15
|
||||
assert data['data']['users'][1]['total_duration'] == '1:00:00'
|
||||
assert data['data']['users'][2]['timezone'] is None
|
||||
assert data['data']['users'][2]['weekm'] is True
|
||||
assert data['data']['users'][2]['nb_activities'] == 0
|
||||
assert data['data']['users'][2]['nb_sports'] == 0
|
||||
assert data['data']['users'][2]['nb_workouts'] == 0
|
||||
assert data['data']['users'][2]['sports_list'] == []
|
||||
assert data['data']['users'][2]['total_distance'] == 0
|
||||
assert data['data']['users'][2]['total_duration'] == '0:00:00'
|
||||
@ -745,14 +745,14 @@ class TestGetUsers:
|
||||
'total': 3,
|
||||
}
|
||||
|
||||
def test_it_gets_users_list_ordered_by_activities_count(
|
||||
def test_it_gets_users_list_ordered_by_workouts_count(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
user_2: User,
|
||||
user_3: User,
|
||||
sport_1_cycling: Sport,
|
||||
activity_cycling_user_2: Activity,
|
||||
workout_cycling_user_2: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -762,7 +762,7 @@ class TestGetUsers:
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
'/api/users?order_by=activities_count',
|
||||
'/api/users?order_by=workouts_count',
|
||||
headers=dict(
|
||||
Authorization='Bearer '
|
||||
+ json.loads(resp_login.data.decode())['auth_token']
|
||||
@ -774,11 +774,11 @@ class TestGetUsers:
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['users']) == 3
|
||||
assert 'test' in data['data']['users'][0]['username']
|
||||
assert 0 == data['data']['users'][0]['nb_activities']
|
||||
assert 0 == data['data']['users'][0]['nb_workouts']
|
||||
assert 'sam' in data['data']['users'][1]['username']
|
||||
assert 0 == data['data']['users'][1]['nb_activities']
|
||||
assert 0 == data['data']['users'][1]['nb_workouts']
|
||||
assert 'toto' in data['data']['users'][2]['username']
|
||||
assert 1 == data['data']['users'][2]['nb_activities']
|
||||
assert 1 == data['data']['users'][2]['nb_workouts']
|
||||
assert data['pagination'] == {
|
||||
'has_next': False,
|
||||
'has_prev': False,
|
||||
@ -787,14 +787,14 @@ class TestGetUsers:
|
||||
'total': 3,
|
||||
}
|
||||
|
||||
def test_it_gets_users_list_ordered_by_activities_count_ascending(
|
||||
def test_it_gets_users_list_ordered_by_workouts_count_ascending(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
user_2: User,
|
||||
user_3: User,
|
||||
sport_1_cycling: Sport,
|
||||
activity_cycling_user_2: Activity,
|
||||
workout_cycling_user_2: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -804,7 +804,7 @@ class TestGetUsers:
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
'/api/users?order_by=activities_count&order=asc',
|
||||
'/api/users?order_by=workouts_count&order=asc',
|
||||
headers=dict(
|
||||
Authorization='Bearer '
|
||||
+ json.loads(resp_login.data.decode())['auth_token']
|
||||
@ -816,11 +816,11 @@ class TestGetUsers:
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['users']) == 3
|
||||
assert 'test' in data['data']['users'][0]['username']
|
||||
assert 0 == data['data']['users'][0]['nb_activities']
|
||||
assert 0 == data['data']['users'][0]['nb_workouts']
|
||||
assert 'sam' in data['data']['users'][1]['username']
|
||||
assert 0 == data['data']['users'][1]['nb_activities']
|
||||
assert 0 == data['data']['users'][1]['nb_workouts']
|
||||
assert 'toto' in data['data']['users'][2]['username']
|
||||
assert 1 == data['data']['users'][2]['nb_activities']
|
||||
assert 1 == data['data']['users'][2]['nb_workouts']
|
||||
assert data['pagination'] == {
|
||||
'has_next': False,
|
||||
'has_prev': False,
|
||||
@ -829,14 +829,14 @@ class TestGetUsers:
|
||||
'total': 3,
|
||||
}
|
||||
|
||||
def test_it_gets_users_list_ordered_by_activities_count_descending(
|
||||
def test_it_gets_users_list_ordered_by_workouts_count_descending(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
user_2: User,
|
||||
user_3: User,
|
||||
sport_1_cycling: Sport,
|
||||
activity_cycling_user_2: Activity,
|
||||
workout_cycling_user_2: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -846,7 +846,7 @@ class TestGetUsers:
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
'/api/users?order_by=activities_count&order=desc',
|
||||
'/api/users?order_by=workouts_count&order=desc',
|
||||
headers=dict(
|
||||
Authorization='Bearer '
|
||||
+ json.loads(resp_login.data.decode())['auth_token']
|
||||
@ -858,11 +858,11 @@ class TestGetUsers:
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['users']) == 3
|
||||
assert 'toto' in data['data']['users'][0]['username']
|
||||
assert 1 == data['data']['users'][0]['nb_activities']
|
||||
assert 1 == data['data']['users'][0]['nb_workouts']
|
||||
assert 'test' in data['data']['users'][1]['username']
|
||||
assert 0 == data['data']['users'][1]['nb_activities']
|
||||
assert 0 == data['data']['users'][1]['nb_workouts']
|
||||
assert 'sam' in data['data']['users'][2]['username']
|
||||
assert 0 == data['data']['users'][2]['nb_activities']
|
||||
assert 0 == data['data']['users'][2]['nb_workouts']
|
||||
assert data['pagination'] == {
|
||||
'has_next': False,
|
||||
'has_prev': False,
|
||||
@ -1156,7 +1156,7 @@ class TestDeleteUser:
|
||||
|
||||
assert response.status_code == 204
|
||||
|
||||
def test_user_with_activity_can_delete_its_own_account(
|
||||
def test_user_with_workout_can_delete_its_own_account(
|
||||
self, app: Flask, user_1: User, sport_1_cycling: Sport, gpx_file: str
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
@ -1166,7 +1166,7 @@ class TestDeleteUser:
|
||||
content_type='application/json',
|
||||
)
|
||||
client.post(
|
||||
'/api/activities',
|
||||
'/api/workouts',
|
||||
data=dict(
|
||||
file=(BytesIO(str.encode(gpx_file)), 'example.gpx'),
|
||||
data='{"sport_id": 1}',
|
||||
|
@ -19,8 +19,8 @@ class TestUserModel:
|
||||
assert serialized_user['timezone'] is None
|
||||
assert serialized_user['weekm'] is False
|
||||
assert serialized_user['language'] is None
|
||||
assert serialized_user['nb_activities'] == 0
|
||||
assert serialized_user['nb_sports'] == 0
|
||||
assert serialized_user['nb_workouts'] == 0
|
||||
assert serialized_user['total_distance'] == 0
|
||||
assert serialized_user['total_duration'] == '0:00:00'
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import json
|
||||
|
||||
from fittrackee.activities.models import Activity, Sport
|
||||
from fittrackee.users.models import User
|
||||
from fittrackee.workouts.models import Sport, Workout
|
||||
from flask import Flask
|
||||
|
||||
|
||||
@ -13,8 +13,8 @@ class TestGetRecords:
|
||||
user_2: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
activity_cycling_user_1: Activity,
|
||||
activity_cycling_user_2: Activity,
|
||||
workout_cycling_user_1: Workout,
|
||||
workout_cycling_user_2: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -37,64 +37,64 @@ class TestGetRecords:
|
||||
|
||||
assert (
|
||||
'Mon, 01 Jan 2018 00:00:00 GMT'
|
||||
== data['data']['records'][0]['activity_date']
|
||||
== data['data']['records'][0]['workout_date']
|
||||
)
|
||||
assert 'test' == data['data']['records'][0]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
|
||||
assert (
|
||||
activity_cycling_user_1.short_id
|
||||
== data['data']['records'][0]['activity_id']
|
||||
workout_cycling_user_1.short_id
|
||||
== data['data']['records'][0]['workout_id']
|
||||
)
|
||||
assert 'AS' == data['data']['records'][0]['record_type']
|
||||
assert 'value' in data['data']['records'][0]
|
||||
|
||||
assert (
|
||||
'Mon, 01 Jan 2018 00:00:00 GMT'
|
||||
== data['data']['records'][1]['activity_date']
|
||||
== data['data']['records'][1]['workout_date']
|
||||
)
|
||||
assert 'test' == data['data']['records'][1]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][1]['sport_id']
|
||||
assert (
|
||||
activity_cycling_user_1.short_id
|
||||
== data['data']['records'][1]['activity_id']
|
||||
workout_cycling_user_1.short_id
|
||||
== data['data']['records'][1]['workout_id']
|
||||
)
|
||||
assert 'FD' == data['data']['records'][1]['record_type']
|
||||
assert 'value' in data['data']['records'][1]
|
||||
|
||||
assert (
|
||||
'Mon, 01 Jan 2018 00:00:00 GMT'
|
||||
== data['data']['records'][2]['activity_date']
|
||||
== data['data']['records'][2]['workout_date']
|
||||
)
|
||||
assert 'test' == data['data']['records'][2]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][2]['sport_id']
|
||||
assert (
|
||||
activity_cycling_user_1.short_id
|
||||
== data['data']['records'][2]['activity_id']
|
||||
workout_cycling_user_1.short_id
|
||||
== data['data']['records'][2]['workout_id']
|
||||
)
|
||||
assert 'LD' == data['data']['records'][2]['record_type']
|
||||
assert 'value' in data['data']['records'][2]
|
||||
|
||||
assert (
|
||||
'Mon, 01 Jan 2018 00:00:00 GMT'
|
||||
== data['data']['records'][3]['activity_date']
|
||||
== data['data']['records'][3]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][3]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][3]['sport_id']
|
||||
assert (
|
||||
activity_cycling_user_1.short_id
|
||||
== data['data']['records'][3]['activity_id']
|
||||
workout_cycling_user_1.short_id
|
||||
== data['data']['records'][3]['workout_id']
|
||||
)
|
||||
assert 'MS' == data['data']['records'][3]['record_type']
|
||||
assert 'value' in data['data']['records'][3]
|
||||
|
||||
def test_it_gets_no_records_if_user_has_no_activity(
|
||||
def test_it_gets_no_records_if_user_has_no_workout(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
user_2: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
activity_cycling_user_2: Activity,
|
||||
workout_cycling_user_2: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -115,7 +115,7 @@ class TestGetRecords:
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['records']) == 0
|
||||
|
||||
def test_it_gets_no_records_if_activity_has_zero_value(
|
||||
def test_it_gets_no_records_if_workout_has_zero_value(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
@ -129,15 +129,15 @@ class TestGetRecords:
|
||||
content_type='application/json',
|
||||
)
|
||||
client.post(
|
||||
'/api/activities/no_gpx',
|
||||
'/api/workouts/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(
|
||||
dict(
|
||||
sport_id=1,
|
||||
duration=0,
|
||||
activity_date='2018-05-14 14:05',
|
||||
workout_date='2018-05-14 14:05',
|
||||
distance=0,
|
||||
title='Activity test',
|
||||
title='Workout test',
|
||||
)
|
||||
),
|
||||
headers=dict(
|
||||
@ -158,7 +158,7 @@ class TestGetRecords:
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['records']) == 0
|
||||
|
||||
def test_it_gets_updated_records_after_activities_post_and_patch(
|
||||
def test_it_gets_updated_records_after_workouts_post_and_patch(
|
||||
self, app: Flask, user_1: User, sport_1_cycling: Sport
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
@ -168,15 +168,15 @@ class TestGetRecords:
|
||||
content_type='application/json',
|
||||
)
|
||||
response = client.post(
|
||||
'/api/activities/no_gpx',
|
||||
'/api/workouts/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(
|
||||
dict(
|
||||
sport_id=1,
|
||||
duration=3600,
|
||||
activity_date='2018-05-14 14:05',
|
||||
workout_date='2018-05-14 14:05',
|
||||
distance=7,
|
||||
title='Activity test 1',
|
||||
title='Workout test 1',
|
||||
)
|
||||
),
|
||||
headers=dict(
|
||||
@ -185,7 +185,7 @@ class TestGetRecords:
|
||||
),
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
activity_1_short_id = data['data']['activities'][0]['id']
|
||||
workout_1_short_id = data['data']['workouts'][0]['id']
|
||||
response = client.get(
|
||||
'/api/records',
|
||||
headers=dict(
|
||||
@ -201,56 +201,56 @@ class TestGetRecords:
|
||||
|
||||
assert (
|
||||
'Mon, 14 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][0]['activity_date']
|
||||
== data['data']['records'][0]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][0]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
|
||||
assert activity_1_short_id == data['data']['records'][0]['activity_id']
|
||||
assert workout_1_short_id == data['data']['records'][0]['workout_id']
|
||||
assert 'AS' == data['data']['records'][0]['record_type']
|
||||
assert 7.0 == data['data']['records'][0]['value']
|
||||
|
||||
assert (
|
||||
'Mon, 14 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][1]['activity_date']
|
||||
== data['data']['records'][1]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][1]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][1]['sport_id']
|
||||
assert activity_1_short_id == data['data']['records'][1]['activity_id']
|
||||
assert workout_1_short_id == data['data']['records'][1]['workout_id']
|
||||
assert 'FD' == data['data']['records'][1]['record_type']
|
||||
assert 7.0 == data['data']['records'][1]['value']
|
||||
|
||||
assert (
|
||||
'Mon, 14 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][2]['activity_date']
|
||||
== data['data']['records'][2]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][2]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][2]['sport_id']
|
||||
assert activity_1_short_id == data['data']['records'][2]['activity_id']
|
||||
assert workout_1_short_id == data['data']['records'][2]['workout_id']
|
||||
assert 'LD' == data['data']['records'][2]['record_type']
|
||||
assert '1:00:00' == data['data']['records'][2]['value']
|
||||
|
||||
assert (
|
||||
'Mon, 14 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][3]['activity_date']
|
||||
== data['data']['records'][3]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][3]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][3]['sport_id']
|
||||
assert activity_1_short_id == data['data']['records'][3]['activity_id']
|
||||
assert workout_1_short_id == data['data']['records'][3]['workout_id']
|
||||
assert 'MS' == data['data']['records'][3]['record_type']
|
||||
assert 7.0 == data['data']['records'][3]['value']
|
||||
|
||||
# Post activity with lower duration (same sport)
|
||||
# Post workout with lower duration (same sport)
|
||||
# => 2 new records: Average speed and Max speed
|
||||
response = client.post(
|
||||
'/api/activities/no_gpx',
|
||||
'/api/workouts/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(
|
||||
dict(
|
||||
sport_id=1,
|
||||
duration=3000,
|
||||
activity_date='2018-05-15 14:05',
|
||||
workout_date='2018-05-15 14:05',
|
||||
distance=7,
|
||||
title='Activity test 2',
|
||||
title='Workout test 2',
|
||||
)
|
||||
),
|
||||
headers=dict(
|
||||
@ -259,7 +259,7 @@ class TestGetRecords:
|
||||
),
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
activity_2_short_id = data['data']['activities'][0]['id']
|
||||
workout_2_short_id = data['data']['workouts'][0]['id']
|
||||
response = client.get(
|
||||
'/api/records',
|
||||
headers=dict(
|
||||
@ -275,55 +275,55 @@ class TestGetRecords:
|
||||
|
||||
assert (
|
||||
'Tue, 15 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][0]['activity_date']
|
||||
== data['data']['records'][0]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][0]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
|
||||
assert activity_2_short_id == data['data']['records'][0]['activity_id']
|
||||
assert workout_2_short_id == data['data']['records'][0]['workout_id']
|
||||
assert 'AS' == data['data']['records'][0]['record_type']
|
||||
assert 8.4 == data['data']['records'][0]['value']
|
||||
|
||||
assert (
|
||||
'Mon, 14 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][1]['activity_date']
|
||||
== data['data']['records'][1]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][1]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][1]['sport_id']
|
||||
assert activity_1_short_id == data['data']['records'][1]['activity_id']
|
||||
assert workout_1_short_id == data['data']['records'][1]['workout_id']
|
||||
assert 'FD' == data['data']['records'][1]['record_type']
|
||||
assert 7.0 == data['data']['records'][1]['value']
|
||||
|
||||
assert (
|
||||
'Mon, 14 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][2]['activity_date']
|
||||
== data['data']['records'][2]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][2]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][2]['sport_id']
|
||||
assert activity_1_short_id == data['data']['records'][2]['activity_id']
|
||||
assert workout_1_short_id == data['data']['records'][2]['workout_id']
|
||||
assert 'LD' == data['data']['records'][2]['record_type']
|
||||
assert '1:00:00' == data['data']['records'][2]['value']
|
||||
|
||||
assert (
|
||||
'Tue, 15 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][0]['activity_date']
|
||||
== data['data']['records'][0]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][0]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
|
||||
assert activity_2_short_id == data['data']['records'][0]['activity_id']
|
||||
assert workout_2_short_id == data['data']['records'][0]['workout_id']
|
||||
assert 'MS' == data['data']['records'][3]['record_type']
|
||||
assert 8.4 == data['data']['records'][3]['value']
|
||||
|
||||
# Post activity with no new records
|
||||
# Post workout with no new records
|
||||
response = client.post(
|
||||
'/api/activities/no_gpx',
|
||||
'/api/workouts/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(
|
||||
dict(
|
||||
sport_id=1,
|
||||
duration=3500,
|
||||
activity_date='2018-05-16 14:05',
|
||||
workout_date='2018-05-16 14:05',
|
||||
distance=6.5,
|
||||
title='Activity test 3',
|
||||
title='Workout test 3',
|
||||
)
|
||||
),
|
||||
headers=dict(
|
||||
@ -332,7 +332,7 @@ class TestGetRecords:
|
||||
),
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
activity_3_short_id = data['data']['activities'][0]['id']
|
||||
workout_3_short_id = data['data']['workouts'][0]['id']
|
||||
response = client.get(
|
||||
'/api/records',
|
||||
headers=dict(
|
||||
@ -348,48 +348,48 @@ class TestGetRecords:
|
||||
|
||||
assert (
|
||||
'Tue, 15 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][0]['activity_date']
|
||||
== data['data']['records'][0]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][0]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
|
||||
assert activity_2_short_id == data['data']['records'][0]['activity_id']
|
||||
assert workout_2_short_id == data['data']['records'][0]['workout_id']
|
||||
assert 'AS' == data['data']['records'][0]['record_type']
|
||||
assert 8.4 == data['data']['records'][0]['value']
|
||||
|
||||
assert (
|
||||
'Mon, 14 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][1]['activity_date']
|
||||
== data['data']['records'][1]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][1]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][1]['sport_id']
|
||||
assert activity_1_short_id == data['data']['records'][1]['activity_id']
|
||||
assert workout_1_short_id == data['data']['records'][1]['workout_id']
|
||||
assert 'FD' == data['data']['records'][1]['record_type']
|
||||
assert 7.0 == data['data']['records'][1]['value']
|
||||
|
||||
assert (
|
||||
'Mon, 14 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][2]['activity_date']
|
||||
== data['data']['records'][2]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][2]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][2]['sport_id']
|
||||
assert activity_1_short_id == data['data']['records'][2]['activity_id']
|
||||
assert workout_1_short_id == data['data']['records'][2]['workout_id']
|
||||
assert 'LD' == data['data']['records'][2]['record_type']
|
||||
assert '1:00:00' == data['data']['records'][2]['value']
|
||||
|
||||
assert (
|
||||
'Tue, 15 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][0]['activity_date']
|
||||
== data['data']['records'][0]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][0]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
|
||||
assert activity_2_short_id == data['data']['records'][0]['activity_id']
|
||||
assert workout_2_short_id == data['data']['records'][0]['workout_id']
|
||||
assert 'MS' == data['data']['records'][3]['record_type']
|
||||
assert 8.4 == data['data']['records'][3]['value']
|
||||
|
||||
# Edit last activity
|
||||
# Edit last workout
|
||||
# 1 new record: Longest duration
|
||||
client.patch(
|
||||
f'/api/activities/{activity_3_short_id}',
|
||||
f'/api/workouts/{workout_3_short_id}',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(duration=4000)),
|
||||
headers=dict(
|
||||
@ -412,47 +412,47 @@ class TestGetRecords:
|
||||
|
||||
assert (
|
||||
'Tue, 15 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][0]['activity_date']
|
||||
== data['data']['records'][0]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][0]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
|
||||
assert activity_2_short_id == data['data']['records'][0]['activity_id']
|
||||
assert workout_2_short_id == data['data']['records'][0]['workout_id']
|
||||
assert 'AS' == data['data']['records'][0]['record_type']
|
||||
assert 8.4 == data['data']['records'][0]['value']
|
||||
|
||||
assert (
|
||||
'Mon, 14 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][1]['activity_date']
|
||||
== data['data']['records'][1]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][1]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][1]['sport_id']
|
||||
assert activity_1_short_id == data['data']['records'][1]['activity_id']
|
||||
assert workout_1_short_id == data['data']['records'][1]['workout_id']
|
||||
assert 'FD' == data['data']['records'][1]['record_type']
|
||||
assert 7.0 == data['data']['records'][1]['value']
|
||||
|
||||
assert (
|
||||
'Wed, 16 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][2]['activity_date']
|
||||
== data['data']['records'][2]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][2]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][2]['sport_id']
|
||||
assert activity_3_short_id == data['data']['records'][2]['activity_id']
|
||||
assert workout_3_short_id == data['data']['records'][2]['workout_id']
|
||||
assert 'LD' == data['data']['records'][2]['record_type']
|
||||
assert '1:06:40' == data['data']['records'][2]['value']
|
||||
|
||||
assert (
|
||||
'Tue, 15 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][0]['activity_date']
|
||||
== data['data']['records'][0]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][0]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
|
||||
assert activity_2_short_id == data['data']['records'][0]['activity_id']
|
||||
assert workout_2_short_id == data['data']['records'][0]['workout_id']
|
||||
assert 'MS' == data['data']['records'][3]['record_type']
|
||||
assert 8.4 == data['data']['records'][3]['value']
|
||||
|
||||
# delete activity 2 => AS and MS record update
|
||||
# delete workout 2 => AS and MS record update
|
||||
client.delete(
|
||||
f'/api/activities/{activity_2_short_id}',
|
||||
f'/api/workouts/{workout_2_short_id}',
|
||||
headers=dict(
|
||||
Authorization='Bearer '
|
||||
+ json.loads(resp_login.data.decode())['auth_token']
|
||||
@ -473,56 +473,56 @@ class TestGetRecords:
|
||||
|
||||
assert (
|
||||
'Mon, 14 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][0]['activity_date']
|
||||
== data['data']['records'][0]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][0]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
|
||||
assert activity_1_short_id == data['data']['records'][0]['activity_id']
|
||||
assert workout_1_short_id == data['data']['records'][0]['workout_id']
|
||||
assert 'AS' == data['data']['records'][0]['record_type']
|
||||
assert 7.0 == data['data']['records'][0]['value']
|
||||
|
||||
assert (
|
||||
'Mon, 14 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][1]['activity_date']
|
||||
== data['data']['records'][1]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][1]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][1]['sport_id']
|
||||
assert activity_1_short_id == data['data']['records'][1]['activity_id']
|
||||
assert workout_1_short_id == data['data']['records'][1]['workout_id']
|
||||
assert 'FD' == data['data']['records'][1]['record_type']
|
||||
assert 7.0 == data['data']['records'][1]['value']
|
||||
|
||||
assert (
|
||||
'Wed, 16 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][2]['activity_date']
|
||||
== data['data']['records'][2]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][2]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][2]['sport_id']
|
||||
assert activity_3_short_id == data['data']['records'][2]['activity_id']
|
||||
assert workout_3_short_id == data['data']['records'][2]['workout_id']
|
||||
assert 'LD' == data['data']['records'][2]['record_type']
|
||||
assert '1:06:40' == data['data']['records'][2]['value']
|
||||
|
||||
assert (
|
||||
'Mon, 14 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][3]['activity_date']
|
||||
== data['data']['records'][3]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][3]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][3]['sport_id']
|
||||
assert activity_1_short_id == data['data']['records'][3]['activity_id']
|
||||
assert workout_1_short_id == data['data']['records'][3]['workout_id']
|
||||
assert 'MS' == data['data']['records'][3]['record_type']
|
||||
assert 7.0 == data['data']['records'][3]['value']
|
||||
|
||||
# add an activity with the same data as activity 1 except with a
|
||||
# add a workout with the same data as workout 1 except with a
|
||||
# later date => no change in record
|
||||
response = client.post(
|
||||
'/api/activities/no_gpx',
|
||||
'/api/workouts/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(
|
||||
dict(
|
||||
sport_id=1,
|
||||
duration=3600,
|
||||
activity_date='2018-05-20 14:05',
|
||||
workout_date='2018-05-20 14:05',
|
||||
distance=7,
|
||||
title='Activity test 4',
|
||||
title='Workout test 4',
|
||||
)
|
||||
),
|
||||
headers=dict(
|
||||
@ -531,7 +531,7 @@ class TestGetRecords:
|
||||
),
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
activity_4_short_id = data['data']['activities'][0]['id']
|
||||
workout_4_short_id = data['data']['workouts'][0]['id']
|
||||
response = client.get(
|
||||
'/api/records',
|
||||
headers=dict(
|
||||
@ -547,58 +547,58 @@ class TestGetRecords:
|
||||
|
||||
assert (
|
||||
'Mon, 14 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][0]['activity_date']
|
||||
== data['data']['records'][0]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][0]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
|
||||
assert activity_1_short_id == data['data']['records'][0]['activity_id']
|
||||
assert workout_1_short_id == data['data']['records'][0]['workout_id']
|
||||
assert 'AS' == data['data']['records'][0]['record_type']
|
||||
assert 7.0 == data['data']['records'][0]['value']
|
||||
|
||||
assert (
|
||||
'Mon, 14 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][1]['activity_date']
|
||||
== data['data']['records'][1]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][1]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][1]['sport_id']
|
||||
assert activity_1_short_id == data['data']['records'][1]['activity_id']
|
||||
assert workout_1_short_id == data['data']['records'][1]['workout_id']
|
||||
assert 'FD' == data['data']['records'][1]['record_type']
|
||||
assert 7.0 == data['data']['records'][1]['value']
|
||||
|
||||
assert (
|
||||
'Wed, 16 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][2]['activity_date']
|
||||
== data['data']['records'][2]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][2]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][2]['sport_id']
|
||||
assert activity_3_short_id == data['data']['records'][2]['activity_id']
|
||||
assert workout_3_short_id == data['data']['records'][2]['workout_id']
|
||||
assert 'LD' == data['data']['records'][2]['record_type']
|
||||
assert '1:06:40' == data['data']['records'][2]['value']
|
||||
|
||||
assert (
|
||||
'Mon, 14 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][3]['activity_date']
|
||||
== data['data']['records'][3]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][3]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][3]['sport_id']
|
||||
assert activity_1_short_id == data['data']['records'][3]['activity_id']
|
||||
assert workout_1_short_id == data['data']['records'][3]['workout_id']
|
||||
assert 'MS' == data['data']['records'][3]['record_type']
|
||||
assert 7.0 == data['data']['records'][3]['value']
|
||||
|
||||
# add an activity with the same data as activity 1 except with
|
||||
# add a workout with the same data as workout 1 except with
|
||||
# an earlier date
|
||||
# => record update (activity 5 replace activity 1)
|
||||
# => record update (workout 5 replace workout 1)
|
||||
|
||||
response = client.post(
|
||||
'/api/activities/no_gpx',
|
||||
'/api/workouts/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(
|
||||
dict(
|
||||
sport_id=1,
|
||||
duration=3600,
|
||||
activity_date='2018-05-14 08:05',
|
||||
workout_date='2018-05-14 08:05',
|
||||
distance=7,
|
||||
title='Activity test 5',
|
||||
title='Workout test 5',
|
||||
)
|
||||
),
|
||||
headers=dict(
|
||||
@ -607,7 +607,7 @@ class TestGetRecords:
|
||||
),
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
activity_5_short_id = data['data']['activities'][0]['id']
|
||||
workout_5_short_id = data['data']['workouts'][0]['id']
|
||||
response = client.get(
|
||||
'/api/records',
|
||||
headers=dict(
|
||||
@ -623,68 +623,68 @@ class TestGetRecords:
|
||||
|
||||
assert (
|
||||
'Mon, 14 May 2018 08:05:00 GMT'
|
||||
== data['data']['records'][0]['activity_date']
|
||||
== data['data']['records'][0]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][0]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
|
||||
assert activity_5_short_id == data['data']['records'][0]['activity_id']
|
||||
assert workout_5_short_id == data['data']['records'][0]['workout_id']
|
||||
assert 'AS' == data['data']['records'][0]['record_type']
|
||||
assert 7.0 == data['data']['records'][0]['value']
|
||||
|
||||
assert (
|
||||
'Mon, 14 May 2018 08:05:00 GMT'
|
||||
== data['data']['records'][1]['activity_date']
|
||||
== data['data']['records'][1]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][1]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][1]['sport_id']
|
||||
assert activity_5_short_id == data['data']['records'][1]['activity_id']
|
||||
assert workout_5_short_id == data['data']['records'][1]['workout_id']
|
||||
assert 'FD' == data['data']['records'][1]['record_type']
|
||||
assert 7.0 == data['data']['records'][1]['value']
|
||||
|
||||
assert (
|
||||
'Wed, 16 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][2]['activity_date']
|
||||
== data['data']['records'][2]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][2]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][2]['sport_id']
|
||||
assert activity_3_short_id == data['data']['records'][2]['activity_id']
|
||||
assert workout_3_short_id == data['data']['records'][2]['workout_id']
|
||||
assert 'LD' == data['data']['records'][2]['record_type']
|
||||
assert '1:06:40' == data['data']['records'][2]['value']
|
||||
|
||||
assert (
|
||||
'Mon, 14 May 2018 08:05:00 GMT'
|
||||
== data['data']['records'][3]['activity_date']
|
||||
== data['data']['records'][3]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][3]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][3]['sport_id']
|
||||
assert activity_5_short_id == data['data']['records'][3]['activity_id']
|
||||
assert workout_5_short_id == data['data']['records'][3]['workout_id']
|
||||
assert 'MS' == data['data']['records'][3]['record_type']
|
||||
assert 7.0 == data['data']['records'][3]['value']
|
||||
|
||||
# delete all activities - no more records
|
||||
# delete all workouts - no more records
|
||||
client.delete(
|
||||
f'/api/activities/{activity_1_short_id}',
|
||||
f'/api/workouts/{workout_1_short_id}',
|
||||
headers=dict(
|
||||
Authorization='Bearer '
|
||||
+ json.loads(resp_login.data.decode())['auth_token']
|
||||
),
|
||||
)
|
||||
client.delete(
|
||||
f'/api/activities/{activity_3_short_id}',
|
||||
f'/api/workouts/{workout_3_short_id}',
|
||||
headers=dict(
|
||||
Authorization='Bearer '
|
||||
+ json.loads(resp_login.data.decode())['auth_token']
|
||||
),
|
||||
)
|
||||
client.delete(
|
||||
f'/api/activities/{activity_4_short_id}',
|
||||
f'/api/workouts/{workout_4_short_id}',
|
||||
headers=dict(
|
||||
Authorization='Bearer '
|
||||
+ json.loads(resp_login.data.decode())['auth_token']
|
||||
),
|
||||
)
|
||||
client.delete(
|
||||
f'/api/activities/{activity_5_short_id}',
|
||||
f'/api/workouts/{workout_5_short_id}',
|
||||
headers=dict(
|
||||
Authorization='Bearer '
|
||||
+ json.loads(resp_login.data.decode())['auth_token']
|
||||
@ -717,15 +717,15 @@ class TestGetRecords:
|
||||
content_type='application/json',
|
||||
)
|
||||
response = client.post(
|
||||
'/api/activities/no_gpx',
|
||||
'/api/workouts/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(
|
||||
dict(
|
||||
sport_id=1,
|
||||
duration=3600,
|
||||
activity_date='2018-05-14 14:05',
|
||||
workout_date='2018-05-14 14:05',
|
||||
distance=7,
|
||||
title='Activity test 1',
|
||||
title='Workout test 1',
|
||||
)
|
||||
),
|
||||
headers=dict(
|
||||
@ -734,17 +734,17 @@ class TestGetRecords:
|
||||
),
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
activity_1_short_id = data['data']['activities'][0]['id']
|
||||
workout_1_short_id = data['data']['workouts'][0]['id']
|
||||
response = client.post(
|
||||
'/api/activities/no_gpx',
|
||||
'/api/workouts/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(
|
||||
dict(
|
||||
sport_id=2,
|
||||
duration=3600,
|
||||
activity_date='2018-05-16 16:05',
|
||||
workout_date='2018-05-16 16:05',
|
||||
distance=20,
|
||||
title='Activity test 2',
|
||||
title='Workout test 2',
|
||||
)
|
||||
),
|
||||
headers=dict(
|
||||
@ -753,17 +753,17 @@ class TestGetRecords:
|
||||
),
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
activity_2_short_id = data['data']['activities'][0]['id']
|
||||
workout_2_short_id = data['data']['workouts'][0]['id']
|
||||
client.post(
|
||||
'/api/activities/no_gpx',
|
||||
'/api/workouts/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(
|
||||
dict(
|
||||
sport_id=1,
|
||||
duration=3000,
|
||||
activity_date='2018-05-17 17:05',
|
||||
workout_date='2018-05-17 17:05',
|
||||
distance=3,
|
||||
title='Activity test 3',
|
||||
title='Workout test 3',
|
||||
)
|
||||
),
|
||||
headers=dict(
|
||||
@ -772,15 +772,15 @@ class TestGetRecords:
|
||||
),
|
||||
)
|
||||
response = client.post(
|
||||
'/api/activities/no_gpx',
|
||||
'/api/workouts/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(
|
||||
dict(
|
||||
sport_id=2,
|
||||
duration=3000,
|
||||
activity_date='2018-05-18 18:05',
|
||||
workout_date='2018-05-18 18:05',
|
||||
distance=10,
|
||||
title='Activity test 4',
|
||||
title='Workout test 4',
|
||||
)
|
||||
),
|
||||
headers=dict(
|
||||
@ -789,7 +789,7 @@ class TestGetRecords:
|
||||
),
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
activity_4_short_id = data['data']['activities'][0]['id']
|
||||
workout_4_short_id = data['data']['workouts'][0]['id']
|
||||
response = client.get(
|
||||
'/api/records',
|
||||
headers=dict(
|
||||
@ -805,86 +805,86 @@ class TestGetRecords:
|
||||
|
||||
assert (
|
||||
'Mon, 14 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][0]['activity_date']
|
||||
== data['data']['records'][0]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][0]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
|
||||
assert activity_1_short_id == data['data']['records'][0]['activity_id']
|
||||
assert workout_1_short_id == data['data']['records'][0]['workout_id']
|
||||
assert 'AS' == data['data']['records'][0]['record_type']
|
||||
assert 7.0 == data['data']['records'][0]['value']
|
||||
|
||||
assert (
|
||||
'Mon, 14 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][1]['activity_date']
|
||||
== data['data']['records'][1]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][1]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][1]['sport_id']
|
||||
assert activity_1_short_id == data['data']['records'][1]['activity_id']
|
||||
assert workout_1_short_id == data['data']['records'][1]['workout_id']
|
||||
assert 'FD' == data['data']['records'][1]['record_type']
|
||||
assert 7.0 == data['data']['records'][1]['value']
|
||||
|
||||
assert (
|
||||
'Mon, 14 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][2]['activity_date']
|
||||
== data['data']['records'][2]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][2]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][2]['sport_id']
|
||||
assert activity_1_short_id == data['data']['records'][2]['activity_id']
|
||||
assert workout_1_short_id == data['data']['records'][2]['workout_id']
|
||||
assert 'LD' == data['data']['records'][2]['record_type']
|
||||
assert '1:00:00' == data['data']['records'][2]['value']
|
||||
|
||||
assert (
|
||||
'Mon, 14 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][3]['activity_date']
|
||||
== data['data']['records'][3]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][3]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][3]['sport_id']
|
||||
assert activity_1_short_id == data['data']['records'][3]['activity_id']
|
||||
assert workout_1_short_id == data['data']['records'][3]['workout_id']
|
||||
assert 'MS' == data['data']['records'][3]['record_type']
|
||||
assert 7.0 == data['data']['records'][3]['value']
|
||||
|
||||
assert (
|
||||
'Wed, 16 May 2018 16:05:00 GMT'
|
||||
== data['data']['records'][4]['activity_date']
|
||||
== data['data']['records'][4]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][4]['user']
|
||||
assert sport_2_running.id == data['data']['records'][4]['sport_id']
|
||||
assert activity_2_short_id == data['data']['records'][4]['activity_id']
|
||||
assert workout_2_short_id == data['data']['records'][4]['workout_id']
|
||||
assert 'AS' == data['data']['records'][4]['record_type']
|
||||
assert 20.0 == data['data']['records'][4]['value']
|
||||
|
||||
assert (
|
||||
'Wed, 16 May 2018 16:05:00 GMT'
|
||||
== data['data']['records'][5]['activity_date']
|
||||
== data['data']['records'][5]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][5]['user']
|
||||
assert sport_2_running.id == data['data']['records'][5]['sport_id']
|
||||
assert activity_2_short_id == data['data']['records'][5]['activity_id']
|
||||
assert workout_2_short_id == data['data']['records'][5]['workout_id']
|
||||
assert 'FD' == data['data']['records'][5]['record_type']
|
||||
assert 20.0 == data['data']['records'][5]['value']
|
||||
|
||||
assert (
|
||||
'Wed, 16 May 2018 16:05:00 GMT'
|
||||
== data['data']['records'][6]['activity_date']
|
||||
== data['data']['records'][6]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][6]['user']
|
||||
assert sport_2_running.id == data['data']['records'][6]['sport_id']
|
||||
assert activity_2_short_id == data['data']['records'][6]['activity_id']
|
||||
assert workout_2_short_id == data['data']['records'][6]['workout_id']
|
||||
assert 'LD' == data['data']['records'][6]['record_type']
|
||||
assert '1:00:00' == data['data']['records'][6]['value']
|
||||
|
||||
assert (
|
||||
'Wed, 16 May 2018 16:05:00 GMT'
|
||||
== data['data']['records'][7]['activity_date']
|
||||
== data['data']['records'][7]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][7]['user']
|
||||
assert sport_2_running.id == data['data']['records'][7]['sport_id']
|
||||
assert activity_2_short_id == data['data']['records'][7]['activity_id']
|
||||
assert workout_2_short_id == data['data']['records'][7]['workout_id']
|
||||
assert 'MS' == data['data']['records'][7]['record_type']
|
||||
assert 20.0 == data['data']['records'][7]['value']
|
||||
|
||||
client.patch(
|
||||
f'/api/activities/{activity_2_short_id}',
|
||||
f'/api/workouts/{workout_2_short_id}',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(sport_id=1)),
|
||||
headers=dict(
|
||||
@ -907,80 +907,80 @@ class TestGetRecords:
|
||||
|
||||
assert (
|
||||
'Wed, 16 May 2018 16:05:00 GMT'
|
||||
== data['data']['records'][0]['activity_date']
|
||||
== data['data']['records'][0]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][0]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][0]['sport_id']
|
||||
assert activity_2_short_id == data['data']['records'][0]['activity_id']
|
||||
assert workout_2_short_id == data['data']['records'][0]['workout_id']
|
||||
assert 'AS' == data['data']['records'][0]['record_type']
|
||||
assert 20.0 == data['data']['records'][0]['value']
|
||||
|
||||
assert (
|
||||
'Wed, 16 May 2018 16:05:00 GMT'
|
||||
== data['data']['records'][1]['activity_date']
|
||||
== data['data']['records'][1]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][1]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][1]['sport_id']
|
||||
assert activity_2_short_id == data['data']['records'][1]['activity_id']
|
||||
assert workout_2_short_id == data['data']['records'][1]['workout_id']
|
||||
assert 'FD' == data['data']['records'][1]['record_type']
|
||||
assert 20.0 == data['data']['records'][1]['value']
|
||||
|
||||
assert (
|
||||
'Mon, 14 May 2018 14:05:00 GMT'
|
||||
== data['data']['records'][2]['activity_date']
|
||||
== data['data']['records'][2]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][2]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][2]['sport_id']
|
||||
assert activity_1_short_id == data['data']['records'][2]['activity_id']
|
||||
assert workout_1_short_id == data['data']['records'][2]['workout_id']
|
||||
assert 'LD' == data['data']['records'][2]['record_type']
|
||||
assert '1:00:00' == data['data']['records'][2]['value']
|
||||
|
||||
assert (
|
||||
'Wed, 16 May 2018 16:05:00 GMT'
|
||||
== data['data']['records'][3]['activity_date']
|
||||
== data['data']['records'][3]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][3]['user']
|
||||
assert sport_1_cycling.id == data['data']['records'][3]['sport_id']
|
||||
assert activity_2_short_id == data['data']['records'][3]['activity_id']
|
||||
assert workout_2_short_id == data['data']['records'][3]['workout_id']
|
||||
assert 'MS' == data['data']['records'][3]['record_type']
|
||||
assert 20.0 == data['data']['records'][3]['value']
|
||||
|
||||
assert (
|
||||
'Fri, 18 May 2018 18:05:00 GMT'
|
||||
== data['data']['records'][4]['activity_date']
|
||||
== data['data']['records'][4]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][4]['user']
|
||||
assert sport_2_running.id == data['data']['records'][4]['sport_id']
|
||||
assert activity_4_short_id == data['data']['records'][4]['activity_id']
|
||||
assert workout_4_short_id == data['data']['records'][4]['workout_id']
|
||||
assert 'AS' == data['data']['records'][4]['record_type']
|
||||
assert 12.0 == data['data']['records'][4]['value']
|
||||
|
||||
assert (
|
||||
'Fri, 18 May 2018 18:05:00 GMT'
|
||||
== data['data']['records'][5]['activity_date']
|
||||
== data['data']['records'][5]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][5]['user']
|
||||
assert sport_2_running.id == data['data']['records'][5]['sport_id']
|
||||
assert activity_4_short_id == data['data']['records'][5]['activity_id']
|
||||
assert workout_4_short_id == data['data']['records'][5]['workout_id']
|
||||
assert 'FD' == data['data']['records'][5]['record_type']
|
||||
assert 10.0 == data['data']['records'][5]['value']
|
||||
|
||||
assert (
|
||||
'Fri, 18 May 2018 18:05:00 GMT'
|
||||
== data['data']['records'][6]['activity_date']
|
||||
== data['data']['records'][6]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][6]['user']
|
||||
assert sport_2_running.id == data['data']['records'][6]['sport_id']
|
||||
assert activity_4_short_id == data['data']['records'][6]['activity_id']
|
||||
assert workout_4_short_id == data['data']['records'][6]['workout_id']
|
||||
assert 'LD' == data['data']['records'][6]['record_type']
|
||||
assert '0:50:00' == data['data']['records'][6]['value']
|
||||
|
||||
assert (
|
||||
'Fri, 18 May 2018 18:05:00 GMT'
|
||||
== data['data']['records'][7]['activity_date']
|
||||
== data['data']['records'][7]['workout_date']
|
||||
) # noqa
|
||||
assert 'test' == data['data']['records'][7]['user']
|
||||
assert sport_2_running.id == data['data']['records'][7]['sport_id']
|
||||
assert activity_4_short_id == data['data']['records'][7]['activity_id']
|
||||
assert workout_4_short_id == data['data']['records'][7]['workout_id']
|
||||
assert 'MS' == data['data']['records'][7]['record_type']
|
||||
assert 12.0 == data['data']['records'][7]['value']
|
@ -1,7 +1,7 @@
|
||||
import datetime
|
||||
|
||||
from fittrackee.activities.models import Activity, Record, Sport
|
||||
from fittrackee.users.models import User
|
||||
from fittrackee.workouts.models import Record, Sport, Workout
|
||||
from flask import Flask
|
||||
|
||||
|
||||
@ -11,27 +11,27 @@ class TestRecordModel:
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
activity_cycling_user_1: Activity,
|
||||
workout_cycling_user_1: Workout,
|
||||
) -> None:
|
||||
record_ld = Record.query.filter_by(
|
||||
user_id=activity_cycling_user_1.user_id,
|
||||
sport_id=activity_cycling_user_1.sport_id,
|
||||
user_id=workout_cycling_user_1.user_id,
|
||||
sport_id=workout_cycling_user_1.sport_id,
|
||||
record_type='LD',
|
||||
).first()
|
||||
assert 'test' == record_ld.user.username
|
||||
assert 1 == record_ld.sport_id
|
||||
assert 1 == record_ld.activity_id
|
||||
assert 1 == record_ld.workout_id
|
||||
assert 'LD' == record_ld.record_type
|
||||
assert '2018-01-01 00:00:00' == str(record_ld.activity_date)
|
||||
assert '2018-01-01 00:00:00' == str(record_ld.workout_date)
|
||||
assert '<Record Cycling - LD - 2018-01-01>' == str(record_ld)
|
||||
|
||||
record_serialize = record_ld.serialize()
|
||||
assert 'id' in record_serialize
|
||||
assert 'user' in record_serialize
|
||||
assert 'sport_id' in record_serialize
|
||||
assert 'activity_id' in record_serialize
|
||||
assert 'workout_id' in record_serialize
|
||||
assert 'record_type' in record_serialize
|
||||
assert 'activity_date' in record_serialize
|
||||
assert 'workout_date' in record_serialize
|
||||
assert 'value' in record_serialize
|
||||
|
||||
def test_record_model_with_none_value(
|
||||
@ -39,19 +39,19 @@ class TestRecordModel:
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
activity_cycling_user_1: Activity,
|
||||
workout_cycling_user_1: Workout,
|
||||
) -> None:
|
||||
record_ld = Record.query.filter_by(
|
||||
user_id=activity_cycling_user_1.user_id,
|
||||
sport_id=activity_cycling_user_1.sport_id,
|
||||
user_id=workout_cycling_user_1.user_id,
|
||||
sport_id=workout_cycling_user_1.sport_id,
|
||||
record_type='LD',
|
||||
).first()
|
||||
record_ld.value = None
|
||||
assert 'test' == record_ld.user.username
|
||||
assert 1 == record_ld.sport_id
|
||||
assert 1 == record_ld.activity_id
|
||||
assert 1 == record_ld.workout_id
|
||||
assert 'LD' == record_ld.record_type
|
||||
assert '2018-01-01 00:00:00' == str(record_ld.activity_date)
|
||||
assert '2018-01-01 00:00:00' == str(record_ld.workout_date)
|
||||
assert '<Record Cycling - LD - 2018-01-01>' == str(record_ld)
|
||||
assert record_ld.value is None
|
||||
|
||||
@ -63,11 +63,11 @@ class TestRecordModel:
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
activity_cycling_user_1: Activity,
|
||||
workout_cycling_user_1: Workout,
|
||||
) -> None:
|
||||
record_as = Record.query.filter_by(
|
||||
user_id=activity_cycling_user_1.user_id,
|
||||
sport_id=activity_cycling_user_1.sport_id,
|
||||
user_id=workout_cycling_user_1.user_id,
|
||||
sport_id=workout_cycling_user_1.sport_id,
|
||||
record_type='AS',
|
||||
).first()
|
||||
|
||||
@ -84,11 +84,11 @@ class TestRecordModel:
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
activity_cycling_user_1: Activity,
|
||||
workout_cycling_user_1: Workout,
|
||||
) -> None:
|
||||
record_fd = Record.query.filter_by(
|
||||
user_id=activity_cycling_user_1.user_id,
|
||||
sport_id=activity_cycling_user_1.sport_id,
|
||||
user_id=workout_cycling_user_1.user_id,
|
||||
sport_id=workout_cycling_user_1.sport_id,
|
||||
record_type='FD',
|
||||
).first()
|
||||
|
||||
@ -105,11 +105,11 @@ class TestRecordModel:
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
activity_cycling_user_1: Activity,
|
||||
workout_cycling_user_1: Workout,
|
||||
) -> None:
|
||||
record_ld = Record.query.filter_by(
|
||||
user_id=activity_cycling_user_1.user_id,
|
||||
sport_id=activity_cycling_user_1.sport_id,
|
||||
user_id=workout_cycling_user_1.user_id,
|
||||
sport_id=workout_cycling_user_1.sport_id,
|
||||
record_type='LD',
|
||||
).first()
|
||||
|
||||
@ -126,11 +126,11 @@ class TestRecordModel:
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
activity_cycling_user_1: Activity,
|
||||
workout_cycling_user_1: Workout,
|
||||
) -> None:
|
||||
record_ld = Record.query.filter_by(
|
||||
user_id=activity_cycling_user_1.user_id,
|
||||
sport_id=activity_cycling_user_1.sport_id,
|
||||
user_id=workout_cycling_user_1.user_id,
|
||||
sport_id=workout_cycling_user_1.sport_id,
|
||||
record_type='LD',
|
||||
).first()
|
||||
record_ld.value = datetime.timedelta(seconds=0)
|
||||
@ -148,11 +148,11 @@ class TestRecordModel:
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
activity_cycling_user_1: Activity,
|
||||
workout_cycling_user_1: Workout,
|
||||
) -> None:
|
||||
record_ms = Record.query.filter_by(
|
||||
user_id=activity_cycling_user_1.user_id,
|
||||
sport_id=activity_cycling_user_1.sport_id,
|
||||
user_id=workout_cycling_user_1.user_id,
|
||||
sport_id=workout_cycling_user_1.sport_id,
|
||||
record_type='MS',
|
||||
).first()
|
||||
|
@ -1,7 +1,7 @@
|
||||
import json
|
||||
|
||||
from fittrackee.activities.models import Activity, Sport
|
||||
from fittrackee.users.models import User
|
||||
from fittrackee.workouts.models import Sport, Workout
|
||||
from flask import Flask
|
||||
|
||||
expected_sport_1_cycling_result = {
|
||||
@ -11,7 +11,7 @@ expected_sport_1_cycling_result = {
|
||||
'is_active': True,
|
||||
}
|
||||
expected_sport_1_cycling_admin_result = expected_sport_1_cycling_result.copy()
|
||||
expected_sport_1_cycling_admin_result['has_activities'] = False
|
||||
expected_sport_1_cycling_admin_result['has_workouts'] = False
|
||||
|
||||
expected_sport_2_running_result = {
|
||||
'id': 2,
|
||||
@ -20,7 +20,7 @@ expected_sport_2_running_result = {
|
||||
'is_active': True,
|
||||
}
|
||||
expected_sport_2_running_admin_result = expected_sport_2_running_result.copy()
|
||||
expected_sport_2_running_admin_result['has_activities'] = False
|
||||
expected_sport_2_running_admin_result['has_workouts'] = False
|
||||
|
||||
expected_sport_1_cycling_inactive_result = {
|
||||
'id': 1,
|
||||
@ -31,7 +31,7 @@ expected_sport_1_cycling_inactive_result = {
|
||||
expected_sport_1_cycling_inactive_admin_result = (
|
||||
expected_sport_1_cycling_inactive_result.copy()
|
||||
)
|
||||
expected_sport_1_cycling_inactive_admin_result['has_activities'] = False
|
||||
expected_sport_1_cycling_inactive_admin_result['has_workouts'] = False
|
||||
|
||||
|
||||
class TestGetSports:
|
||||
@ -266,7 +266,7 @@ class TestUpdateSport:
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['sports']) == 1
|
||||
assert data['data']['sports'][0]['is_active'] is False
|
||||
assert data['data']['sports'][0]['has_activities'] is False
|
||||
assert data['data']['sports'][0]['has_workouts'] is False
|
||||
|
||||
def test_it_enables_a_sport(
|
||||
self, app: Flask, user_1_admin: User, sport_1_cycling: Sport
|
||||
@ -296,14 +296,14 @@ class TestUpdateSport:
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['sports']) == 1
|
||||
assert data['data']['sports'][0]['is_active'] is True
|
||||
assert data['data']['sports'][0]['has_activities'] is False
|
||||
assert data['data']['sports'][0]['has_workouts'] is False
|
||||
|
||||
def test_it_disables_a_sport_with_activities(
|
||||
def test_it_disables_a_sport_with_workouts(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1_admin: User,
|
||||
sport_1_cycling: Sport,
|
||||
activity_cycling_user_1: Activity,
|
||||
workout_cycling_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -329,14 +329,14 @@ class TestUpdateSport:
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['sports']) == 1
|
||||
assert data['data']['sports'][0]['is_active'] is False
|
||||
assert data['data']['sports'][0]['has_activities'] is True
|
||||
assert data['data']['sports'][0]['has_workouts'] is True
|
||||
|
||||
def test_it_enables_a_sport_with_activities(
|
||||
def test_it_enables_a_sport_with_workouts(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1_admin: User,
|
||||
sport_1_cycling: Sport,
|
||||
activity_cycling_user_1: Activity,
|
||||
workout_cycling_user_1: Workout,
|
||||
) -> None:
|
||||
sport_1_cycling.is_active = False
|
||||
client = app.test_client()
|
||||
@ -363,7 +363,7 @@ class TestUpdateSport:
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['sports']) == 1
|
||||
assert data['data']['sports'][0]['is_active'] is True
|
||||
assert data['data']['sports'][0]['has_activities'] is True
|
||||
assert data['data']['sports'][0]['has_workouts'] is True
|
||||
|
||||
def test_returns_error_if_user_has_no_admin_rights(
|
||||
self, app: Flask, user_1: User, sport_1_cycling: Sport
|
@ -1,7 +1,7 @@
|
||||
from typing import Dict, Optional
|
||||
|
||||
from fittrackee.activities.models import Activity, Sport
|
||||
from fittrackee.users.models import User
|
||||
from fittrackee.workouts.models import Sport, Workout
|
||||
from flask import Flask
|
||||
|
||||
|
||||
@ -22,24 +22,24 @@ class TestSportModel:
|
||||
|
||||
def test_sport_model(self, app: Flask, sport_1_cycling: Sport) -> None:
|
||||
serialized_sport = self.assert_sport_model(sport_1_cycling)
|
||||
assert 'has_activities' not in serialized_sport
|
||||
assert 'has_workouts' not in serialized_sport
|
||||
|
||||
def test_sport_model_with_activity(
|
||||
def test_sport_model_with_workout(
|
||||
self,
|
||||
app: Flask,
|
||||
sport_1_cycling: Sport,
|
||||
user_1: User,
|
||||
activity_cycling_user_1: Activity,
|
||||
workout_cycling_user_1: Workout,
|
||||
) -> None:
|
||||
serialized_sport = self.assert_sport_model(sport_1_cycling)
|
||||
assert 'has_activities' not in serialized_sport
|
||||
assert 'has_workouts' not in serialized_sport
|
||||
|
||||
def test_sport_model_with_activity_as_admin(
|
||||
def test_sport_model_with_workout_as_admin(
|
||||
self,
|
||||
app: Flask,
|
||||
sport_1_cycling: Sport,
|
||||
user_1: User,
|
||||
activity_cycling_user_1: Activity,
|
||||
workout_cycling_user_1: Workout,
|
||||
) -> None:
|
||||
serialized_sport = self.assert_sport_model(sport_1_cycling, True)
|
||||
assert serialized_sport['has_activities'] is True
|
||||
assert serialized_sport['has_workouts'] is True
|
@ -1,12 +1,12 @@
|
||||
import json
|
||||
|
||||
from fittrackee.activities.models import Activity, Sport
|
||||
from fittrackee.users.models import User
|
||||
from fittrackee.workouts.models import Sport, Workout
|
||||
from flask import Flask
|
||||
|
||||
|
||||
class TestGetStatsByTime:
|
||||
def test_it_gets_no_stats_when_user_has_no_activities(
|
||||
def test_it_gets_no_stats_when_user_has_no_workouts(
|
||||
self, app: Flask, user_1: User
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
@ -58,8 +58,8 @@ class TestGetStatsByTime:
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
seven_activities_user_1: Activity,
|
||||
activity_running_user_1: Activity,
|
||||
seven_workouts_user_1: Workout,
|
||||
workout_running_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -90,8 +90,8 @@ class TestGetStatsByTime:
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
seven_activities_user_1: Activity,
|
||||
activity_running_user_1: Activity,
|
||||
seven_workouts_user_1: Workout,
|
||||
workout_running_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -113,14 +113,14 @@ class TestGetStatsByTime:
|
||||
assert 'fail' in data['status']
|
||||
assert 'Invalid time period.' in data['message']
|
||||
|
||||
def test_it_gets_stats_by_time_all_activities(
|
||||
def test_it_gets_stats_by_time_all_workouts(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
seven_activities_user_1: Activity,
|
||||
activity_running_user_1: Activity,
|
||||
seven_workouts_user_1: Workout,
|
||||
workout_running_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -143,19 +143,19 @@ class TestGetStatsByTime:
|
||||
assert data['data']['statistics'] == {
|
||||
'2017': {
|
||||
'1': {
|
||||
'nb_activities': 2,
|
||||
'nb_workouts': 2,
|
||||
'total_distance': 15.0,
|
||||
'total_duration': 4480,
|
||||
}
|
||||
},
|
||||
'2018': {
|
||||
'1': {
|
||||
'nb_activities': 5,
|
||||
'nb_workouts': 5,
|
||||
'total_distance': 39.0,
|
||||
'total_duration': 11624,
|
||||
},
|
||||
'2': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 12.0,
|
||||
'total_duration': 6000,
|
||||
},
|
||||
@ -168,8 +168,8 @@ class TestGetStatsByTime:
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
seven_activities_user_1: Activity,
|
||||
activity_running_user_1: Activity,
|
||||
seven_workouts_user_1: Workout,
|
||||
workout_running_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -192,12 +192,12 @@ class TestGetStatsByTime:
|
||||
assert data['data']['statistics'] == {
|
||||
'2018': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 8.0,
|
||||
'total_duration': 6000,
|
||||
},
|
||||
'2': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 12.0,
|
||||
'total_duration': 6000,
|
||||
},
|
||||
@ -210,8 +210,8 @@ class TestGetStatsByTime:
|
||||
user_1_paris: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
seven_activities_user_1: Activity,
|
||||
activity_running_user_1: Activity,
|
||||
seven_workouts_user_1: Workout,
|
||||
workout_running_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -235,12 +235,12 @@ class TestGetStatsByTime:
|
||||
assert data['data']['statistics'] == {
|
||||
'2018': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 8.0,
|
||||
'total_duration': 6000,
|
||||
},
|
||||
'2': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 12.0,
|
||||
'total_duration': 6000,
|
||||
},
|
||||
@ -253,8 +253,8 @@ class TestGetStatsByTime:
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
seven_activities_user_1: Activity,
|
||||
activity_running_user_1: Activity,
|
||||
seven_workouts_user_1: Workout,
|
||||
workout_running_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -277,19 +277,19 @@ class TestGetStatsByTime:
|
||||
assert data['data']['statistics'] == {
|
||||
'2017': {
|
||||
'1': {
|
||||
'nb_activities': 2,
|
||||
'nb_workouts': 2,
|
||||
'total_distance': 15.0,
|
||||
'total_duration': 4480,
|
||||
}
|
||||
},
|
||||
'2018': {
|
||||
'1': {
|
||||
'nb_activities': 5,
|
||||
'nb_workouts': 5,
|
||||
'total_distance': 39.0,
|
||||
'total_duration': 11624,
|
||||
},
|
||||
'2': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 12.0,
|
||||
'total_duration': 6000,
|
||||
},
|
||||
@ -302,8 +302,8 @@ class TestGetStatsByTime:
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
seven_activities_user_1: Activity,
|
||||
activity_running_user_1: Activity,
|
||||
seven_workouts_user_1: Workout,
|
||||
workout_running_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -326,12 +326,12 @@ class TestGetStatsByTime:
|
||||
assert data['data']['statistics'] == {
|
||||
'2018': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 8.0,
|
||||
'total_duration': 6000,
|
||||
},
|
||||
'2': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 12.0,
|
||||
'total_duration': 6000,
|
||||
},
|
||||
@ -344,8 +344,8 @@ class TestGetStatsByTime:
|
||||
user_1_paris: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
seven_activities_user_1: Activity,
|
||||
activity_running_user_1: Activity,
|
||||
seven_workouts_user_1: Workout,
|
||||
workout_running_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -368,12 +368,12 @@ class TestGetStatsByTime:
|
||||
assert data['data']['statistics'] == {
|
||||
'2018': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 8.0,
|
||||
'total_duration': 6000,
|
||||
},
|
||||
'2': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 12.0,
|
||||
'total_duration': 6000,
|
||||
},
|
||||
@ -386,8 +386,8 @@ class TestGetStatsByTime:
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
seven_activities_user_1: Activity,
|
||||
activity_running_user_1: Activity,
|
||||
seven_workouts_user_1: Workout,
|
||||
workout_running_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -410,47 +410,47 @@ class TestGetStatsByTime:
|
||||
assert data['data']['statistics'] == {
|
||||
'2017-03': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 5.0,
|
||||
'total_duration': 1024,
|
||||
}
|
||||
},
|
||||
'2017-06': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 10.0,
|
||||
'total_duration': 3456,
|
||||
}
|
||||
},
|
||||
'2018-01': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 10.0,
|
||||
'total_duration': 1024,
|
||||
}
|
||||
},
|
||||
'2018-02': {
|
||||
'1': {
|
||||
'nb_activities': 2,
|
||||
'nb_workouts': 2,
|
||||
'total_distance': 11.0,
|
||||
'total_duration': 1600,
|
||||
}
|
||||
},
|
||||
'2018-04': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 8.0,
|
||||
'total_duration': 6000,
|
||||
},
|
||||
'2': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 12.0,
|
||||
'total_duration': 6000,
|
||||
},
|
||||
},
|
||||
'2018-05': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 10.0,
|
||||
'total_duration': 3000,
|
||||
}
|
||||
@ -463,8 +463,8 @@ class TestGetStatsByTime:
|
||||
user_1_full: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
seven_activities_user_1: Activity,
|
||||
activity_running_user_1: Activity,
|
||||
seven_workouts_user_1: Workout,
|
||||
workout_running_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -487,47 +487,47 @@ class TestGetStatsByTime:
|
||||
assert data['data']['statistics'] == {
|
||||
'2017-03': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 5.0,
|
||||
'total_duration': 1024,
|
||||
}
|
||||
},
|
||||
'2017-06': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 10.0,
|
||||
'total_duration': 3456,
|
||||
}
|
||||
},
|
||||
'2018-01': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 10.0,
|
||||
'total_duration': 1024,
|
||||
}
|
||||
},
|
||||
'2018-02': {
|
||||
'1': {
|
||||
'nb_activities': 2,
|
||||
'nb_workouts': 2,
|
||||
'total_distance': 11.0,
|
||||
'total_duration': 1600,
|
||||
}
|
||||
},
|
||||
'2018-04': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 8.0,
|
||||
'total_duration': 6000,
|
||||
},
|
||||
'2': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 12.0,
|
||||
'total_duration': 6000,
|
||||
},
|
||||
},
|
||||
'2018-05': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 10.0,
|
||||
'total_duration': 3000,
|
||||
}
|
||||
@ -540,8 +540,8 @@ class TestGetStatsByTime:
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
seven_activities_user_1: Activity,
|
||||
activity_running_user_1: Activity,
|
||||
seven_workouts_user_1: Workout,
|
||||
workout_running_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -564,12 +564,12 @@ class TestGetStatsByTime:
|
||||
assert data['data']['statistics'] == {
|
||||
'2018-04': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 8.0,
|
||||
'total_duration': 6000,
|
||||
},
|
||||
'2': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 12.0,
|
||||
'total_duration': 6000,
|
||||
},
|
||||
@ -582,8 +582,8 @@ class TestGetStatsByTime:
|
||||
user_1_full: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
seven_activities_user_1: Activity,
|
||||
activity_running_user_1: Activity,
|
||||
seven_workouts_user_1: Workout,
|
||||
workout_running_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -606,47 +606,47 @@ class TestGetStatsByTime:
|
||||
assert data['data']['statistics'] == {
|
||||
'2017-03-19': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 5.0,
|
||||
'total_duration': 1024,
|
||||
}
|
||||
},
|
||||
'2017-05-28': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 10.0,
|
||||
'total_duration': 3456,
|
||||
}
|
||||
},
|
||||
'2017-12-31': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 10.0,
|
||||
'total_duration': 1024,
|
||||
}
|
||||
},
|
||||
'2018-02-18': {
|
||||
'1': {
|
||||
'nb_activities': 2,
|
||||
'nb_workouts': 2,
|
||||
'total_distance': 11.0,
|
||||
'total_duration': 1600,
|
||||
}
|
||||
},
|
||||
'2018-04-01': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 8.0,
|
||||
'total_duration': 6000,
|
||||
},
|
||||
'2': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 12.0,
|
||||
'total_duration': 6000,
|
||||
},
|
||||
},
|
||||
'2018-05-06': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 10.0,
|
||||
'total_duration': 3000,
|
||||
}
|
||||
@ -659,8 +659,8 @@ class TestGetStatsByTime:
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
seven_activities_user_1: Activity,
|
||||
activity_running_user_1: Activity,
|
||||
seven_workouts_user_1: Workout,
|
||||
workout_running_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -683,12 +683,12 @@ class TestGetStatsByTime:
|
||||
assert data['data']['statistics'] == {
|
||||
'2018-04-01': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 8.0,
|
||||
'total_duration': 6000,
|
||||
},
|
||||
'2': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 12.0,
|
||||
'total_duration': 6000,
|
||||
},
|
||||
@ -701,8 +701,8 @@ class TestGetStatsByTime:
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
seven_activities_user_1: Activity,
|
||||
activity_running_user_1: Activity,
|
||||
seven_workouts_user_1: Workout,
|
||||
workout_running_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -725,47 +725,47 @@ class TestGetStatsByTime:
|
||||
assert data['data']['statistics'] == {
|
||||
'2017-03-20': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 5.0,
|
||||
'total_duration': 1024,
|
||||
}
|
||||
},
|
||||
'2017-05-29': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 10.0,
|
||||
'total_duration': 3456,
|
||||
}
|
||||
},
|
||||
'2018-01-01': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 10.0,
|
||||
'total_duration': 1024,
|
||||
}
|
||||
},
|
||||
'2018-02-19': {
|
||||
'1': {
|
||||
'nb_activities': 2,
|
||||
'nb_workouts': 2,
|
||||
'total_distance': 11.0,
|
||||
'total_duration': 1600,
|
||||
}
|
||||
},
|
||||
'2018-03-26': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 8.0,
|
||||
'total_duration': 6000,
|
||||
},
|
||||
'2': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 12.0,
|
||||
'total_duration': 6000,
|
||||
},
|
||||
},
|
||||
'2018-05-07': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 10.0,
|
||||
'total_duration': 3000,
|
||||
}
|
||||
@ -778,8 +778,8 @@ class TestGetStatsByTime:
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
seven_activities_user_1: Activity,
|
||||
activity_running_user_1: Activity,
|
||||
seven_workouts_user_1: Workout,
|
||||
workout_running_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -802,12 +802,12 @@ class TestGetStatsByTime:
|
||||
assert data['data']['statistics'] == {
|
||||
'2018-03-26': {
|
||||
'1': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 8.0,
|
||||
'total_duration': 6000,
|
||||
},
|
||||
'2': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 12.0,
|
||||
'total_duration': 6000,
|
||||
},
|
||||
@ -822,8 +822,8 @@ class TestGetStatsBySport:
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
seven_activities_user_1: Activity,
|
||||
activity_running_user_1: Activity,
|
||||
seven_workouts_user_1: Workout,
|
||||
workout_running_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -845,12 +845,12 @@ class TestGetStatsBySport:
|
||||
assert 'success' in data['status']
|
||||
assert data['data']['statistics'] == {
|
||||
'1': {
|
||||
'nb_activities': 7,
|
||||
'nb_workouts': 7,
|
||||
'total_distance': 54.0,
|
||||
'total_duration': 16104,
|
||||
},
|
||||
'2': {
|
||||
'nb_activities': 1,
|
||||
'nb_workouts': 1,
|
||||
'total_distance': 12.0,
|
||||
'total_duration': 6000,
|
||||
},
|
||||
@ -862,8 +862,8 @@ class TestGetStatsBySport:
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
seven_activities_user_1: Activity,
|
||||
activity_running_user_1: Activity,
|
||||
seven_workouts_user_1: Workout,
|
||||
workout_running_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -885,7 +885,7 @@ class TestGetStatsBySport:
|
||||
assert 'success' in data['status']
|
||||
assert data['data']['statistics'] == {
|
||||
'1': {
|
||||
'nb_activities': 7,
|
||||
'nb_workouts': 7,
|
||||
'total_distance': 54.0,
|
||||
'total_duration': 16104,
|
||||
}
|
||||
@ -897,8 +897,8 @@ class TestGetStatsBySport:
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
seven_activities_user_1: Activity,
|
||||
activity_running_user_1: Activity,
|
||||
seven_workouts_user_1: Workout,
|
||||
workout_running_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -926,8 +926,8 @@ class TestGetStatsBySport:
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
seven_activities_user_1: Activity,
|
||||
activity_running_user_1: Activity,
|
||||
seven_workouts_user_1: Workout,
|
||||
workout_running_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -955,8 +955,8 @@ class TestGetStatsBySport:
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
seven_activities_user_1: Activity,
|
||||
activity_running_user_1: Activity,
|
||||
seven_workouts_user_1: Workout,
|
||||
workout_running_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -983,7 +983,7 @@ class TestGetStatsBySport:
|
||||
|
||||
|
||||
class TestGetAllStats:
|
||||
def test_it_returns_all_stats_when_users_have_no_activities(
|
||||
def test_it_returns_all_stats_when_users_have_no_workouts(
|
||||
self, app: Flask, user_1_admin: User, user_2: User
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
@ -1006,12 +1006,12 @@ class TestGetAllStats:
|
||||
data = json.loads(response.data.decode())
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert data['data']['activities'] == 0
|
||||
assert data['data']['workouts'] == 0
|
||||
assert data['data']['sports'] == 0
|
||||
assert data['data']['users'] == 2
|
||||
assert 'uploads_dir_size' in data['data']
|
||||
|
||||
def test_it_gets_app_all_stats_with_activities(
|
||||
def test_it_gets_app_all_stats_with_workouts(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1_admin: User,
|
||||
@ -1019,9 +1019,9 @@ class TestGetAllStats:
|
||||
user_3: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
activity_cycling_user_1: Activity,
|
||||
activity_cycling_user_2: Activity,
|
||||
activity_running_user_1: Activity,
|
||||
workout_cycling_user_1: Workout,
|
||||
workout_cycling_user_2: Workout,
|
||||
workout_running_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -1043,7 +1043,7 @@ class TestGetAllStats:
|
||||
data = json.loads(response.data.decode())
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert data['data']['activities'] == 3
|
||||
assert data['data']['workouts'] == 3
|
||||
assert data['data']['sports'] == 2
|
||||
assert data['data']['users'] == 3
|
||||
assert 'uploads_dir_size' in data['data']
|
||||
@ -1056,9 +1056,9 @@ class TestGetAllStats:
|
||||
user_3: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
activity_cycling_user_1: Activity,
|
||||
activity_cycling_user_2: Activity,
|
||||
activity_running_user_1: Activity,
|
||||
workout_cycling_user_1: Workout,
|
||||
workout_cycling_user_2: Workout,
|
||||
workout_running_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,59 +1,59 @@
|
||||
import json
|
||||
from typing import Dict
|
||||
|
||||
from fittrackee.activities.models import Activity, Sport
|
||||
from fittrackee.activities.utils_id import decode_short_id
|
||||
from fittrackee.users.models import User
|
||||
from fittrackee.workouts.models import Sport, Workout
|
||||
from fittrackee.workouts.utils_id import decode_short_id
|
||||
from flask import Flask
|
||||
|
||||
from .utils import get_random_short_id, post_an_activity
|
||||
from .utils import get_random_short_id, post_an_workout
|
||||
|
||||
|
||||
def assert_activity_data_with_gpx(data: Dict, sport_id: int) -> None:
|
||||
assert 'creation_date' in data['data']['activities'][0]
|
||||
def assert_workout_data_with_gpx(data: Dict, sport_id: int) -> None:
|
||||
assert 'creation_date' in data['data']['workouts'][0]
|
||||
assert (
|
||||
'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||
== data['data']['activities'][0]['activity_date']
|
||||
== data['data']['workouts'][0]['workout_date']
|
||||
)
|
||||
assert 'test' == data['data']['activities'][0]['user']
|
||||
assert '0:04:10' == data['data']['activities'][0]['duration']
|
||||
assert data['data']['activities'][0]['ascent'] == 0.4
|
||||
assert data['data']['activities'][0]['ave_speed'] == 4.61
|
||||
assert data['data']['activities'][0]['descent'] == 23.4
|
||||
assert data['data']['activities'][0]['distance'] == 0.32
|
||||
assert data['data']['activities'][0]['max_alt'] == 998.0
|
||||
assert data['data']['activities'][0]['max_speed'] == 5.12
|
||||
assert data['data']['activities'][0]['min_alt'] == 975.0
|
||||
assert data['data']['activities'][0]['moving'] == '0:04:10'
|
||||
assert data['data']['activities'][0]['pauses'] is None
|
||||
assert data['data']['activities'][0]['with_gpx'] is True
|
||||
assert 'test' == data['data']['workouts'][0]['user']
|
||||
assert '0:04:10' == data['data']['workouts'][0]['duration']
|
||||
assert data['data']['workouts'][0]['ascent'] == 0.4
|
||||
assert data['data']['workouts'][0]['ave_speed'] == 4.61
|
||||
assert data['data']['workouts'][0]['descent'] == 23.4
|
||||
assert data['data']['workouts'][0]['distance'] == 0.32
|
||||
assert data['data']['workouts'][0]['max_alt'] == 998.0
|
||||
assert data['data']['workouts'][0]['max_speed'] == 5.12
|
||||
assert data['data']['workouts'][0]['min_alt'] == 975.0
|
||||
assert data['data']['workouts'][0]['moving'] == '0:04:10'
|
||||
assert data['data']['workouts'][0]['pauses'] is None
|
||||
assert data['data']['workouts'][0]['with_gpx'] is True
|
||||
|
||||
records = data['data']['activities'][0]['records']
|
||||
records = data['data']['workouts'][0]['records']
|
||||
assert len(records) == 4
|
||||
assert records[0]['sport_id'] == sport_id
|
||||
assert records[0]['activity_id'] == data['data']['activities'][0]['id']
|
||||
assert records[0]['workout_id'] == data['data']['workouts'][0]['id']
|
||||
assert records[0]['record_type'] == 'MS'
|
||||
assert records[0]['activity_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||
assert records[0]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||
assert records[0]['value'] == 5.12
|
||||
assert records[1]['sport_id'] == sport_id
|
||||
assert records[1]['activity_id'] == data['data']['activities'][0]['id']
|
||||
assert records[1]['workout_id'] == data['data']['workouts'][0]['id']
|
||||
assert records[1]['record_type'] == 'LD'
|
||||
assert records[1]['activity_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||
assert records[1]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||
assert records[1]['value'] == '0:04:10'
|
||||
assert records[2]['sport_id'] == sport_id
|
||||
assert records[2]['activity_id'] == data['data']['activities'][0]['id']
|
||||
assert records[2]['workout_id'] == data['data']['workouts'][0]['id']
|
||||
assert records[2]['record_type'] == 'FD'
|
||||
assert records[2]['activity_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||
assert records[2]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||
assert records[2]['value'] == 0.32
|
||||
assert records[3]['sport_id'] == sport_id
|
||||
assert records[3]['activity_id'] == data['data']['activities'][0]['id']
|
||||
assert records[3]['workout_id'] == data['data']['workouts'][0]['id']
|
||||
assert records[3]['record_type'] == 'AS'
|
||||
assert records[3]['activity_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||
assert records[3]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||
assert records[3]['value'] == 4.61
|
||||
|
||||
|
||||
class TestEditActivityWithGpx:
|
||||
def test_it_updates_title_for_an_activity_with_gpx(
|
||||
class TestEditWorkoutWithGpx:
|
||||
def test_it_updates_title_for_an_workout_with_gpx(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
@ -61,25 +61,25 @@ class TestEditActivityWithGpx:
|
||||
sport_2_running: Sport,
|
||||
gpx_file: str,
|
||||
) -> None:
|
||||
token, activity_short_id = post_an_activity(app, gpx_file)
|
||||
token, workout_short_id = post_an_workout(app, gpx_file)
|
||||
client = app.test_client()
|
||||
|
||||
response = client.patch(
|
||||
f'/api/activities/{activity_short_id}',
|
||||
f'/api/workouts/{workout_short_id}',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(sport_id=2, title="Activity test")),
|
||||
data=json.dumps(dict(sport_id=2, title="Workout test")),
|
||||
headers=dict(Authorization=f'Bearer {token}'),
|
||||
)
|
||||
|
||||
data = json.loads(response.data.decode())
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['activities']) == 1
|
||||
assert sport_2_running.id == data['data']['activities'][0]['sport_id']
|
||||
assert data['data']['activities'][0]['title'] == 'Activity test'
|
||||
assert_activity_data_with_gpx(data, sport_2_running.id)
|
||||
assert len(data['data']['workouts']) == 1
|
||||
assert sport_2_running.id == data['data']['workouts'][0]['sport_id']
|
||||
assert data['data']['workouts'][0]['title'] == 'Workout test'
|
||||
assert_workout_data_with_gpx(data, sport_2_running.id)
|
||||
|
||||
def test_it_adds_notes_for_an_activity_with_gpx(
|
||||
def test_it_adds_notes_for_an_workout_with_gpx(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
@ -87,11 +87,11 @@ class TestEditActivityWithGpx:
|
||||
sport_2_running: Sport,
|
||||
gpx_file: str,
|
||||
) -> None:
|
||||
token, activity_short_id = post_an_activity(app, gpx_file)
|
||||
token, workout_short_id = post_an_workout(app, gpx_file)
|
||||
client = app.test_client()
|
||||
|
||||
response = client.patch(
|
||||
f'/api/activities/{activity_short_id}',
|
||||
f'/api/workouts/{workout_short_id}',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(notes="test notes")),
|
||||
headers=dict(Authorization=f'Bearer {token}'),
|
||||
@ -100,11 +100,11 @@ class TestEditActivityWithGpx:
|
||||
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'] == 'just an activity'
|
||||
assert data['data']['activities'][0]['notes'] == 'test notes'
|
||||
assert len(data['data']['workouts']) == 1
|
||||
assert data['data']['workouts'][0]['title'] == 'just a workout'
|
||||
assert data['data']['workouts'][0]['notes'] == 'test notes'
|
||||
|
||||
def test_it_raises_403_when_editing_an_activity_from_different_user(
|
||||
def test_it_raises_403_when_editing_an_workout_from_different_user(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
@ -113,7 +113,7 @@ class TestEditActivityWithGpx:
|
||||
sport_2_running: Sport,
|
||||
gpx_file: str,
|
||||
) -> None:
|
||||
_, activity_short_id = post_an_activity(app, gpx_file)
|
||||
_, workout_short_id = post_an_workout(app, gpx_file)
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
@ -122,9 +122,9 @@ class TestEditActivityWithGpx:
|
||||
)
|
||||
|
||||
response = client.patch(
|
||||
f'/api/activities/{activity_short_id}',
|
||||
f'/api/workouts/{workout_short_id}',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(sport_id=2, title="Activity test")),
|
||||
data=json.dumps(dict(sport_id=2, title="Workout test")),
|
||||
headers=dict(
|
||||
Authorization='Bearer '
|
||||
+ json.loads(resp_login.data.decode())['auth_token']
|
||||
@ -144,11 +144,11 @@ class TestEditActivityWithGpx:
|
||||
sport_2_running: Sport,
|
||||
gpx_file: str,
|
||||
) -> None:
|
||||
token, activity_short_id = post_an_activity(app, gpx_file)
|
||||
token, workout_short_id = post_an_workout(app, gpx_file)
|
||||
client = app.test_client()
|
||||
|
||||
response = client.patch(
|
||||
f'/api/activities/{activity_short_id}',
|
||||
f'/api/workouts/{workout_short_id}',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(sport_id=2)),
|
||||
headers=dict(Authorization=f'Bearer {token}'),
|
||||
@ -157,19 +157,19 @@ class TestEditActivityWithGpx:
|
||||
data = json.loads(response.data.decode())
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['activities']) == 1
|
||||
assert sport_2_running.id == data['data']['activities'][0]['sport_id']
|
||||
assert data['data']['activities'][0]['title'] == 'just an activity'
|
||||
assert_activity_data_with_gpx(data, sport_2_running.id)
|
||||
assert len(data['data']['workouts']) == 1
|
||||
assert sport_2_running.id == data['data']['workouts'][0]['sport_id']
|
||||
assert data['data']['workouts'][0]['title'] == 'just a workout'
|
||||
assert_workout_data_with_gpx(data, sport_2_running.id)
|
||||
|
||||
def test_it_returns_400_if_payload_is_empty(
|
||||
self, app: Flask, user_1: User, sport_1_cycling: Sport, gpx_file: str
|
||||
) -> None:
|
||||
token, activity_short_id = post_an_activity(app, gpx_file)
|
||||
token, workout_short_id = post_an_workout(app, gpx_file)
|
||||
client = app.test_client()
|
||||
|
||||
response = client.patch(
|
||||
f'/api/activities/{activity_short_id}',
|
||||
f'/api/workouts/{workout_short_id}',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict()),
|
||||
headers=dict(Authorization=f'Bearer {token}'),
|
||||
@ -183,11 +183,11 @@ class TestEditActivityWithGpx:
|
||||
def test_it_raises_500_if_sport_does_not_exists(
|
||||
self, app: Flask, user_1: User, sport_1_cycling: Sport, gpx_file: str
|
||||
) -> None:
|
||||
token, activity_short_id = post_an_activity(app, gpx_file)
|
||||
token, workout_short_id = post_an_workout(app, gpx_file)
|
||||
client = app.test_client()
|
||||
|
||||
response = client.patch(
|
||||
f'/api/activities/{activity_short_id}',
|
||||
f'/api/workouts/{workout_short_id}',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(sport_id=2)),
|
||||
headers=dict(Authorization=f'Bearer {token}'),
|
||||
@ -202,16 +202,16 @@ class TestEditActivityWithGpx:
|
||||
)
|
||||
|
||||
|
||||
class TestEditActivityWithoutGpx:
|
||||
def test_it_updates_an_activity_wo_gpx(
|
||||
class TestEditWorkoutWithoutGpx:
|
||||
def test_it_updates_an_workout_wo_gpx(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
activity_cycling_user_1: Activity,
|
||||
workout_cycling_user_1: Workout,
|
||||
) -> None:
|
||||
activity_short_id = activity_cycling_user_1.short_id
|
||||
workout_short_id = workout_cycling_user_1.short_id
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
@ -220,15 +220,15 @@ class TestEditActivityWithoutGpx:
|
||||
)
|
||||
|
||||
response = client.patch(
|
||||
f'/api/activities/{activity_short_id}',
|
||||
f'/api/workouts/{workout_short_id}',
|
||||
content_type='application/json',
|
||||
data=json.dumps(
|
||||
dict(
|
||||
sport_id=2,
|
||||
duration=3600,
|
||||
activity_date='2018-05-15 15:05',
|
||||
workout_date='2018-05-15 15:05',
|
||||
distance=8,
|
||||
title='Activity test',
|
||||
title='Workout test',
|
||||
)
|
||||
),
|
||||
headers=dict(
|
||||
@ -241,62 +241,62 @@ class TestEditActivityWithoutGpx:
|
||||
|
||||
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 len(data['data']['workouts']) == 1
|
||||
assert 'creation_date' in data['data']['workouts'][0]
|
||||
assert (
|
||||
data['data']['activities'][0]['activity_date']
|
||||
data['data']['workouts'][0]['workout_date']
|
||||
== 'Tue, 15 May 2018 15:05:00 GMT'
|
||||
)
|
||||
assert data['data']['activities'][0]['user'] == 'test'
|
||||
assert data['data']['activities'][0]['sport_id'] == sport_2_running.id
|
||||
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'] is None
|
||||
assert data['data']['workouts'][0]['user'] == 'test'
|
||||
assert data['data']['workouts'][0]['sport_id'] == sport_2_running.id
|
||||
assert data['data']['workouts'][0]['duration'] == '1:00:00'
|
||||
assert data['data']['workouts'][0]['title'] == 'Workout test'
|
||||
assert data['data']['workouts'][0]['ascent'] is None
|
||||
assert data['data']['workouts'][0]['ave_speed'] == 8.0
|
||||
assert data['data']['workouts'][0]['descent'] is None
|
||||
assert data['data']['workouts'][0]['distance'] == 8.0
|
||||
assert data['data']['workouts'][0]['max_alt'] is None
|
||||
assert data['data']['workouts'][0]['max_speed'] == 8.0
|
||||
assert data['data']['workouts'][0]['min_alt'] is None
|
||||
assert data['data']['workouts'][0]['moving'] == '1:00:00'
|
||||
assert data['data']['workouts'][0]['pauses'] is None
|
||||
assert data['data']['workouts'][0]['with_gpx'] is False
|
||||
assert data['data']['workouts'][0]['map'] is None
|
||||
assert data['data']['workouts'][0]['weather_start'] is None
|
||||
assert data['data']['workouts'][0]['weather_end'] is None
|
||||
assert data['data']['workouts'][0]['notes'] is None
|
||||
|
||||
records = data['data']['activities'][0]['records']
|
||||
records = data['data']['workouts'][0]['records']
|
||||
assert len(records) == 4
|
||||
assert records[0]['sport_id'] == sport_2_running.id
|
||||
assert records[0]['activity_id'] == activity_short_id
|
||||
assert records[0]['workout_id'] == workout_short_id
|
||||
assert records[0]['record_type'] == 'MS'
|
||||
assert records[0]['activity_date'] == 'Tue, 15 May 2018 15:05:00 GMT'
|
||||
assert records[0]['workout_date'] == 'Tue, 15 May 2018 15:05:00 GMT'
|
||||
assert records[0]['value'] == 8.0
|
||||
assert records[1]['sport_id'] == sport_2_running.id
|
||||
assert records[1]['activity_id'] == activity_short_id
|
||||
assert records[1]['workout_id'] == workout_short_id
|
||||
assert records[1]['record_type'] == 'LD'
|
||||
assert records[1]['activity_date'] == 'Tue, 15 May 2018 15:05:00 GMT'
|
||||
assert records[1]['workout_date'] == 'Tue, 15 May 2018 15:05:00 GMT'
|
||||
assert records[1]['value'] == '1:00:00'
|
||||
assert records[2]['sport_id'] == sport_2_running.id
|
||||
assert records[2]['activity_id'] == activity_short_id
|
||||
assert records[2]['workout_id'] == workout_short_id
|
||||
assert records[2]['record_type'] == 'FD'
|
||||
assert records[2]['activity_date'] == 'Tue, 15 May 2018 15:05:00 GMT'
|
||||
assert records[2]['workout_date'] == 'Tue, 15 May 2018 15:05:00 GMT'
|
||||
assert records[2]['value'] == 8.0
|
||||
assert records[3]['sport_id'] == sport_2_running.id
|
||||
assert records[3]['activity_id'] == activity_short_id
|
||||
assert records[3]['workout_id'] == workout_short_id
|
||||
assert records[3]['record_type'] == 'AS'
|
||||
assert records[3]['activity_date'] == 'Tue, 15 May 2018 15:05:00 GMT'
|
||||
assert records[3]['workout_date'] == 'Tue, 15 May 2018 15:05:00 GMT'
|
||||
assert records[3]['value'] == 8.0
|
||||
|
||||
def test_it_adds_notes_to_an_activity_wo_gpx(
|
||||
def test_it_adds_notes_to_an_workout_wo_gpx(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
activity_cycling_user_1: Activity,
|
||||
workout_cycling_user_1: Workout,
|
||||
) -> None:
|
||||
activity_short_id = activity_cycling_user_1.short_id
|
||||
workout_short_id = workout_cycling_user_1.short_id
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
@ -305,7 +305,7 @@ class TestEditActivityWithoutGpx:
|
||||
)
|
||||
|
||||
response = client.patch(
|
||||
f'/api/activities/{activity_short_id}',
|
||||
f'/api/workouts/{workout_short_id}',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(notes='test notes')),
|
||||
headers=dict(
|
||||
@ -317,61 +317,61 @@ class TestEditActivityWithoutGpx:
|
||||
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 len(data['data']['workouts']) == 1
|
||||
assert 'creation_date' in data['data']['workouts'][0]
|
||||
assert (
|
||||
data['data']['activities'][0]['activity_date']
|
||||
data['data']['workouts'][0]['workout_date']
|
||||
== 'Mon, 01 Jan 2018 00:00:00 GMT'
|
||||
)
|
||||
assert data['data']['activities'][0]['user'] == 'test'
|
||||
assert data['data']['activities'][0]['sport_id'] == sport_1_cycling.id
|
||||
assert data['data']['activities'][0]['duration'] == '1:00:00'
|
||||
assert data['data']['activities'][0]['title'] is None
|
||||
assert data['data']['activities'][0]['ascent'] is None
|
||||
assert data['data']['activities'][0]['ave_speed'] == 10.0
|
||||
assert data['data']['activities'][0]['descent'] is None
|
||||
assert data['data']['activities'][0]['distance'] == 10.0
|
||||
assert data['data']['activities'][0]['max_alt'] is None
|
||||
assert data['data']['activities'][0]['max_speed'] == 10.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'
|
||||
assert data['data']['workouts'][0]['user'] == 'test'
|
||||
assert data['data']['workouts'][0]['sport_id'] == sport_1_cycling.id
|
||||
assert data['data']['workouts'][0]['duration'] == '1:00:00'
|
||||
assert data['data']['workouts'][0]['title'] is None
|
||||
assert data['data']['workouts'][0]['ascent'] is None
|
||||
assert data['data']['workouts'][0]['ave_speed'] == 10.0
|
||||
assert data['data']['workouts'][0]['descent'] is None
|
||||
assert data['data']['workouts'][0]['distance'] == 10.0
|
||||
assert data['data']['workouts'][0]['max_alt'] is None
|
||||
assert data['data']['workouts'][0]['max_speed'] == 10.0
|
||||
assert data['data']['workouts'][0]['min_alt'] is None
|
||||
assert data['data']['workouts'][0]['moving'] == '1:00:00'
|
||||
assert data['data']['workouts'][0]['pauses'] is None
|
||||
assert data['data']['workouts'][0]['with_gpx'] is False
|
||||
assert data['data']['workouts'][0]['map'] is None
|
||||
assert data['data']['workouts'][0]['weather_start'] is None
|
||||
assert data['data']['workouts'][0]['weather_end'] is None
|
||||
assert data['data']['workouts'][0]['notes'] == 'test notes'
|
||||
|
||||
records = data['data']['activities'][0]['records']
|
||||
records = data['data']['workouts'][0]['records']
|
||||
assert len(records) == 4
|
||||
assert records[0]['sport_id'] == sport_1_cycling.id
|
||||
assert records[0]['activity_id'] == activity_short_id
|
||||
assert records[0]['workout_id'] == workout_short_id
|
||||
assert records[0]['record_type'] == 'MS'
|
||||
assert records[0]['activity_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT'
|
||||
assert records[0]['workout_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT'
|
||||
assert records[0]['value'] == 10.0
|
||||
assert records[1]['sport_id'] == sport_1_cycling.id
|
||||
assert records[1]['activity_id'] == activity_short_id
|
||||
assert records[1]['workout_id'] == workout_short_id
|
||||
assert records[1]['record_type'] == 'LD'
|
||||
assert records[1]['activity_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT'
|
||||
assert records[1]['workout_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT'
|
||||
assert records[1]['value'] == '1:00:00'
|
||||
assert records[2]['sport_id'] == sport_1_cycling.id
|
||||
assert records[2]['activity_id'] == activity_short_id
|
||||
assert records[2]['workout_id'] == workout_short_id
|
||||
assert records[2]['record_type'] == 'FD'
|
||||
assert records[2]['activity_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT'
|
||||
assert records[2]['workout_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT'
|
||||
assert records[2]['value'] == 10.0
|
||||
assert records[3]['sport_id'] == sport_1_cycling.id
|
||||
assert records[3]['activity_id'] == activity_short_id
|
||||
assert records[3]['workout_id'] == workout_short_id
|
||||
assert records[3]['record_type'] == 'AS'
|
||||
assert records[3]['activity_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT'
|
||||
assert records[3]['workout_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT'
|
||||
assert records[3]['value'] == 10.0
|
||||
|
||||
def test_returns_403_when_editing_an_activity_wo_gpx_from_different_user(
|
||||
def test_returns_403_when_editing_an_workout_wo_gpx_from_different_user(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
user_2: User,
|
||||
sport_1_cycling: Sport,
|
||||
activity_cycling_user_2: Activity,
|
||||
workout_cycling_user_2: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -381,15 +381,15 @@ class TestEditActivityWithoutGpx:
|
||||
)
|
||||
|
||||
response = client.patch(
|
||||
f'/api/activities/{activity_cycling_user_2.short_id}',
|
||||
f'/api/workouts/{workout_cycling_user_2.short_id}',
|
||||
content_type='application/json',
|
||||
data=json.dumps(
|
||||
dict(
|
||||
sport_id=2,
|
||||
duration=3600,
|
||||
activity_date='2018-05-15 15:05',
|
||||
workout_date='2018-05-15 15:05',
|
||||
distance=8,
|
||||
title='Activity test',
|
||||
title='Workout test',
|
||||
)
|
||||
),
|
||||
headers=dict(
|
||||
@ -403,15 +403,15 @@ class TestEditActivityWithoutGpx:
|
||||
assert 'error' in data['status']
|
||||
assert 'You do not have permissions.' in data['message']
|
||||
|
||||
def test_it_updates_an_activity_wo_gpx_with_timezone(
|
||||
def test_it_updates_an_workout_wo_gpx_with_timezone(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1_paris: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
activity_cycling_user_1: Activity,
|
||||
workout_cycling_user_1: Workout,
|
||||
) -> None:
|
||||
activity_short_id = activity_cycling_user_1.short_id
|
||||
workout_short_id = workout_cycling_user_1.short_id
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
@ -420,15 +420,15 @@ class TestEditActivityWithoutGpx:
|
||||
)
|
||||
|
||||
response = client.patch(
|
||||
f'/api/activities/{activity_short_id}',
|
||||
f'/api/workouts/{workout_short_id}',
|
||||
content_type='application/json',
|
||||
data=json.dumps(
|
||||
dict(
|
||||
sport_id=2,
|
||||
duration=3600,
|
||||
activity_date='2018-05-15 15:05',
|
||||
workout_date='2018-05-15 15:05',
|
||||
distance=8,
|
||||
title='Activity test',
|
||||
title='Workout test',
|
||||
)
|
||||
),
|
||||
headers=dict(
|
||||
@ -440,59 +440,59 @@ class TestEditActivityWithoutGpx:
|
||||
|
||||
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 len(data['data']['workouts']) == 1
|
||||
assert 'creation_date' in data['data']['workouts'][0]
|
||||
assert (
|
||||
data['data']['activities'][0]['activity_date']
|
||||
data['data']['workouts'][0]['workout_date']
|
||||
== 'Tue, 15 May 2018 13:05:00 GMT'
|
||||
)
|
||||
assert data['data']['activities'][0]['user'] == 'test'
|
||||
assert data['data']['activities'][0]['sport_id'] == sport_2_running.id
|
||||
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']['workouts'][0]['user'] == 'test'
|
||||
assert data['data']['workouts'][0]['sport_id'] == sport_2_running.id
|
||||
assert data['data']['workouts'][0]['duration'] == '1:00:00'
|
||||
assert data['data']['workouts'][0]['title'] == 'Workout test'
|
||||
assert data['data']['workouts'][0]['ascent'] is None
|
||||
assert data['data']['workouts'][0]['ave_speed'] == 8.0
|
||||
assert data['data']['workouts'][0]['descent'] is None
|
||||
assert data['data']['workouts'][0]['distance'] == 8.0
|
||||
assert data['data']['workouts'][0]['max_alt'] is None
|
||||
assert data['data']['workouts'][0]['max_speed'] == 8.0
|
||||
assert data['data']['workouts'][0]['min_alt'] is None
|
||||
assert data['data']['workouts'][0]['moving'] == '1:00:00'
|
||||
assert data['data']['workouts'][0]['pauses'] is None
|
||||
assert data['data']['workouts'][0]['with_gpx'] is False
|
||||
|
||||
records = data['data']['activities'][0]['records']
|
||||
records = data['data']['workouts'][0]['records']
|
||||
assert len(records) == 4
|
||||
assert records[0]['sport_id'] == sport_2_running.id
|
||||
assert records[0]['activity_id'] == activity_short_id
|
||||
assert records[0]['workout_id'] == workout_short_id
|
||||
assert records[0]['record_type'] == 'MS'
|
||||
assert records[0]['activity_date'] == 'Tue, 15 May 2018 13:05:00 GMT'
|
||||
assert records[0]['workout_date'] == 'Tue, 15 May 2018 13:05:00 GMT'
|
||||
assert records[0]['value'] == 8.0
|
||||
assert records[1]['sport_id'] == sport_2_running.id
|
||||
assert records[1]['activity_id'] == activity_short_id
|
||||
assert records[1]['workout_id'] == workout_short_id
|
||||
assert records[1]['record_type'] == 'LD'
|
||||
assert records[1]['activity_date'] == 'Tue, 15 May 2018 13:05:00 GMT'
|
||||
assert records[1]['workout_date'] == 'Tue, 15 May 2018 13:05:00 GMT'
|
||||
assert records[1]['value'] == '1:00:00'
|
||||
assert records[2]['sport_id'] == sport_2_running.id
|
||||
assert records[2]['activity_id'] == activity_short_id
|
||||
assert records[2]['workout_id'] == workout_short_id
|
||||
assert records[2]['record_type'] == 'FD'
|
||||
assert records[2]['activity_date'] == 'Tue, 15 May 2018 13:05:00 GMT'
|
||||
assert records[2]['workout_date'] == 'Tue, 15 May 2018 13:05:00 GMT'
|
||||
assert records[2]['value'] == 8.0
|
||||
assert records[3]['sport_id'] == sport_2_running.id
|
||||
assert records[3]['activity_id'] == activity_short_id
|
||||
assert records[3]['workout_id'] == workout_short_id
|
||||
assert records[3]['record_type'] == 'AS'
|
||||
assert records[3]['activity_date'] == 'Tue, 15 May 2018 13:05:00 GMT'
|
||||
assert records[3]['workout_date'] == 'Tue, 15 May 2018 13:05:00 GMT'
|
||||
assert records[3]['value'] == 8.0
|
||||
|
||||
def test_it_updates_only_sport_and_distance_an_activity_wo_gpx(
|
||||
def test_it_updates_only_sport_and_distance_an_workout_wo_gpx(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
activity_cycling_user_1: Activity,
|
||||
workout_cycling_user_1: Workout,
|
||||
) -> None:
|
||||
activity_short_id = activity_cycling_user_1.short_id
|
||||
workout_short_id = workout_cycling_user_1.short_id
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
@ -501,7 +501,7 @@ class TestEditActivityWithoutGpx:
|
||||
)
|
||||
|
||||
response = client.patch(
|
||||
f'/api/activities/{activity_short_id}',
|
||||
f'/api/workouts/{workout_short_id}',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(sport_id=2, distance=20)),
|
||||
headers=dict(
|
||||
@ -513,48 +513,48 @@ class TestEditActivityWithoutGpx:
|
||||
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 len(data['data']['workouts']) == 1
|
||||
assert 'creation_date' in data['data']['workouts'][0]
|
||||
assert (
|
||||
data['data']['activities'][0]['activity_date']
|
||||
data['data']['workouts'][0]['workout_date']
|
||||
== 'Mon, 01 Jan 2018 00:00:00 GMT'
|
||||
)
|
||||
assert data['data']['activities'][0]['user'] == 'test'
|
||||
assert data['data']['activities'][0]['sport_id'] == sport_2_running.id
|
||||
assert data['data']['activities'][0]['duration'] == '1:00:00'
|
||||
assert data['data']['activities'][0]['title'] is None
|
||||
assert data['data']['activities'][0]['ascent'] is None
|
||||
assert data['data']['activities'][0]['ave_speed'] == 20.0
|
||||
assert data['data']['activities'][0]['descent'] is None
|
||||
assert data['data']['activities'][0]['distance'] == 20.0
|
||||
assert data['data']['activities'][0]['max_alt'] is None
|
||||
assert data['data']['activities'][0]['max_speed'] == 20.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']['workouts'][0]['user'] == 'test'
|
||||
assert data['data']['workouts'][0]['sport_id'] == sport_2_running.id
|
||||
assert data['data']['workouts'][0]['duration'] == '1:00:00'
|
||||
assert data['data']['workouts'][0]['title'] is None
|
||||
assert data['data']['workouts'][0]['ascent'] is None
|
||||
assert data['data']['workouts'][0]['ave_speed'] == 20.0
|
||||
assert data['data']['workouts'][0]['descent'] is None
|
||||
assert data['data']['workouts'][0]['distance'] == 20.0
|
||||
assert data['data']['workouts'][0]['max_alt'] is None
|
||||
assert data['data']['workouts'][0]['max_speed'] == 20.0
|
||||
assert data['data']['workouts'][0]['min_alt'] is None
|
||||
assert data['data']['workouts'][0]['moving'] == '1:00:00'
|
||||
assert data['data']['workouts'][0]['pauses'] is None
|
||||
assert data['data']['workouts'][0]['with_gpx'] is False
|
||||
|
||||
records = data['data']['activities'][0]['records']
|
||||
records = data['data']['workouts'][0]['records']
|
||||
assert len(records) == 4
|
||||
assert records[0]['sport_id'] == sport_2_running.id
|
||||
assert records[0]['activity_id'] == activity_short_id
|
||||
assert records[0]['workout_id'] == workout_short_id
|
||||
assert records[0]['record_type'] == 'MS'
|
||||
assert records[0]['activity_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT'
|
||||
assert records[0]['workout_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT'
|
||||
assert records[0]['value'] == 20.0
|
||||
assert records[1]['sport_id'] == sport_2_running.id
|
||||
assert records[1]['activity_id'] == activity_short_id
|
||||
assert records[1]['workout_id'] == workout_short_id
|
||||
assert records[1]['record_type'] == 'LD'
|
||||
assert records[1]['activity_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT'
|
||||
assert records[1]['workout_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT'
|
||||
assert records[1]['value'] == '1:00:00'
|
||||
assert records[2]['sport_id'] == sport_2_running.id
|
||||
assert records[2]['activity_id'] == activity_short_id
|
||||
assert records[2]['workout_id'] == workout_short_id
|
||||
assert records[2]['record_type'] == 'FD'
|
||||
assert records[2]['activity_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT'
|
||||
assert records[2]['workout_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT'
|
||||
assert records[2]['value'] == 20.0
|
||||
assert records[3]['sport_id'] == sport_2_running.id
|
||||
assert records[3]['activity_id'] == activity_short_id
|
||||
assert records[3]['workout_id'] == workout_short_id
|
||||
assert records[3]['record_type'] == 'AS'
|
||||
assert records[3]['activity_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT'
|
||||
assert records[3]['workout_date'] == 'Mon, 01 Jan 2018 00:00:00 GMT'
|
||||
assert records[3]['value'] == 20.0
|
||||
|
||||
def test_it_returns_400_if_payload_is_empty(
|
||||
@ -562,7 +562,7 @@ class TestEditActivityWithoutGpx:
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
activity_cycling_user_1: Activity,
|
||||
workout_cycling_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -572,7 +572,7 @@ class TestEditActivityWithoutGpx:
|
||||
)
|
||||
|
||||
response = client.patch(
|
||||
f'/api/activities/{activity_cycling_user_1.short_id}',
|
||||
f'/api/workouts/{workout_cycling_user_1.short_id}',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict()),
|
||||
headers=dict(
|
||||
@ -591,7 +591,7 @@ class TestEditActivityWithoutGpx:
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
activity_cycling_user_1: Activity,
|
||||
workout_cycling_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -600,13 +600,13 @@ class TestEditActivityWithoutGpx:
|
||||
content_type='application/json',
|
||||
)
|
||||
response = client.patch(
|
||||
f'/api/activities/{activity_cycling_user_1.short_id}',
|
||||
f'/api/workouts/{workout_cycling_user_1.short_id}',
|
||||
content_type='application/json',
|
||||
data=json.dumps(
|
||||
dict(
|
||||
sport_id=1,
|
||||
duration=3600,
|
||||
activity_date='15/2018',
|
||||
workout_date='15/2018',
|
||||
distance=10,
|
||||
)
|
||||
),
|
||||
@ -625,7 +625,7 @@ class TestEditActivityWithoutGpx:
|
||||
in data['message']
|
||||
)
|
||||
|
||||
def test_it_returns_404_if_edited_activity_does_not_exists(
|
||||
def test_it_returns_404_if_edited_workout_does_not_exists(
|
||||
self, app: Flask, user_1: User, sport_1_cycling: Sport
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
@ -635,13 +635,13 @@ class TestEditActivityWithoutGpx:
|
||||
content_type='application/json',
|
||||
)
|
||||
response = client.patch(
|
||||
f'/api/activities/{get_random_short_id()}',
|
||||
f'/api/workouts/{get_random_short_id()}',
|
||||
content_type='application/json',
|
||||
data=json.dumps(
|
||||
dict(
|
||||
sport_id=1,
|
||||
duration=3600,
|
||||
activity_date='2018-05-15 14:05',
|
||||
workout_date='2018-05-15 14:05',
|
||||
distance=10,
|
||||
)
|
||||
),
|
||||
@ -654,11 +654,11 @@ class TestEditActivityWithoutGpx:
|
||||
data = json.loads(response.data.decode())
|
||||
assert response.status_code == 404
|
||||
assert 'not found' in data['status']
|
||||
assert len(data['data']['activities']) == 0
|
||||
assert len(data['data']['workouts']) == 0
|
||||
|
||||
|
||||
class TestRefreshActivityWithGpx:
|
||||
def test_refresh_an_activity_with_gpx(
|
||||
class TestRefreshWorkoutWithGpx:
|
||||
def test_refresh_an_workout_with_gpx(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
@ -666,17 +666,17 @@ class TestRefreshActivityWithGpx:
|
||||
sport_2_running: Sport,
|
||||
gpx_file: str,
|
||||
) -> None:
|
||||
token, activity_short_id = post_an_activity(app, gpx_file)
|
||||
activity_uuid = decode_short_id(activity_short_id)
|
||||
token, workout_short_id = post_an_workout(app, gpx_file)
|
||||
workout_uuid = decode_short_id(workout_short_id)
|
||||
client = app.test_client()
|
||||
|
||||
# Edit some activity data
|
||||
activity = Activity.query.filter_by(uuid=activity_uuid).first()
|
||||
activity.ascent = 1000
|
||||
activity.min_alt = -100
|
||||
# Edit some workout data
|
||||
workout = Workout.query.filter_by(uuid=workout_uuid).first()
|
||||
workout.ascent = 1000
|
||||
workout.min_alt = -100
|
||||
|
||||
response = client.patch(
|
||||
f'/api/activities/{activity_short_id}',
|
||||
f'/api/workouts/{workout_short_id}',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(refresh=True)),
|
||||
headers=dict(Authorization=f'Bearer {token}'),
|
||||
@ -685,7 +685,7 @@ class TestRefreshActivityWithGpx:
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['activities']) == 1
|
||||
assert 1 == data['data']['activities'][0]['sport_id']
|
||||
assert 0.4 == data['data']['activities'][0]['ascent']
|
||||
assert 975.0 == data['data']['activities'][0]['min_alt']
|
||||
assert len(data['data']['workouts']) == 1
|
||||
assert 1 == data['data']['workouts'][0]['sport_id']
|
||||
assert 0.4 == data['data']['workouts'][0]['ascent']
|
||||
assert 975.0 == data['data']['workouts'][0]['min_alt']
|
@ -1,34 +1,34 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
from fittrackee.activities.models import Activity, Sport
|
||||
from fittrackee.activities.utils import get_absolute_file_path
|
||||
from fittrackee.users.models import User
|
||||
from fittrackee.workouts.models import Sport, Workout
|
||||
from fittrackee.workouts.utils import get_absolute_file_path
|
||||
from flask import Flask
|
||||
|
||||
from .utils import get_random_short_id, post_an_activity
|
||||
from .utils import get_random_short_id, post_an_workout
|
||||
|
||||
|
||||
def get_gpx_filepath(activity_id: int) -> str:
|
||||
activity = Activity.query.filter_by(id=activity_id).first()
|
||||
return activity.gpx
|
||||
def get_gpx_filepath(workout_id: int) -> str:
|
||||
workout = Workout.query.filter_by(id=workout_id).first()
|
||||
return workout.gpx
|
||||
|
||||
|
||||
class TestDeleteActivityWithGpx:
|
||||
def test_it_deletes_an_activity_with_gpx(
|
||||
class TestDeleteWorkoutWithGpx:
|
||||
def test_it_deletes_an_workout_with_gpx(
|
||||
self, app: Flask, user_1: User, sport_1_cycling: Sport, gpx_file: str
|
||||
) -> None:
|
||||
token, activity_short_id = post_an_activity(app, gpx_file)
|
||||
token, workout_short_id = post_an_workout(app, gpx_file)
|
||||
client = app.test_client()
|
||||
|
||||
response = client.delete(
|
||||
f'/api/activities/{activity_short_id}',
|
||||
f'/api/workouts/{workout_short_id}',
|
||||
headers=dict(Authorization=f'Bearer {token}'),
|
||||
)
|
||||
|
||||
assert response.status_code == 204
|
||||
|
||||
def test_it_returns_403_when_deleting_an_activity_from_different_user(
|
||||
def test_it_returns_403_when_deleting_an_workout_from_different_user(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
@ -36,7 +36,7 @@ class TestDeleteActivityWithGpx:
|
||||
sport_1_cycling: Sport,
|
||||
gpx_file: str,
|
||||
) -> None:
|
||||
_, activity_short_id = post_an_activity(app, gpx_file)
|
||||
_, workout_short_id = post_an_workout(app, gpx_file)
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
@ -45,7 +45,7 @@ class TestDeleteActivityWithGpx:
|
||||
)
|
||||
|
||||
response = client.delete(
|
||||
f'/api/activities/{activity_short_id}',
|
||||
f'/api/workouts/{workout_short_id}',
|
||||
headers=dict(
|
||||
Authorization='Bearer '
|
||||
+ json.loads(resp_login.data.decode())['auth_token']
|
||||
@ -58,7 +58,7 @@ class TestDeleteActivityWithGpx:
|
||||
assert 'error' in data['status']
|
||||
assert 'You do not have permissions.' in data['message']
|
||||
|
||||
def test_it_returns_404_if_activity_does_not_exist(
|
||||
def test_it_returns_404_if_workout_does_not_exist(
|
||||
self, app: Flask, user_1: User
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
@ -68,7 +68,7 @@ class TestDeleteActivityWithGpx:
|
||||
content_type='application/json',
|
||||
)
|
||||
response = client.delete(
|
||||
f'/api/activities/{get_random_short_id()}',
|
||||
f'/api/workouts/{get_random_short_id()}',
|
||||
headers=dict(
|
||||
Authorization='Bearer '
|
||||
+ json.loads(resp_login.data.decode())['auth_token']
|
||||
@ -78,17 +78,17 @@ class TestDeleteActivityWithGpx:
|
||||
assert response.status_code == 404
|
||||
assert 'not found' in data['status']
|
||||
|
||||
def test_it_returns_500_when_deleting_an_activity_with_gpx_invalid_file(
|
||||
def test_it_returns_500_when_deleting_an_workout_with_gpx_invalid_file(
|
||||
self, app: Flask, user_1: User, sport_1_cycling: Sport, gpx_file: str
|
||||
) -> None:
|
||||
token, activity_short_id = post_an_activity(app, gpx_file)
|
||||
token, workout_short_id = post_an_workout(app, gpx_file)
|
||||
client = app.test_client()
|
||||
gpx_filepath = get_gpx_filepath(1)
|
||||
gpx_filepath = get_absolute_file_path(gpx_filepath)
|
||||
os.remove(gpx_filepath)
|
||||
|
||||
response = client.delete(
|
||||
f'/api/activities/{activity_short_id}',
|
||||
f'/api/workouts/{workout_short_id}',
|
||||
headers=dict(Authorization=f'Bearer {token}'),
|
||||
)
|
||||
|
||||
@ -102,13 +102,13 @@ class TestDeleteActivityWithGpx:
|
||||
)
|
||||
|
||||
|
||||
class TestDeleteActivityWithoutGpx:
|
||||
def test_it_deletes_an_activity_wo_gpx(
|
||||
class TestDeleteWorkoutWithoutGpx:
|
||||
def test_it_deletes_an_workout_wo_gpx(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
sport_1_cycling: Sport,
|
||||
activity_cycling_user_1: Activity,
|
||||
workout_cycling_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -117,7 +117,7 @@ class TestDeleteActivityWithoutGpx:
|
||||
content_type='application/json',
|
||||
)
|
||||
response = client.delete(
|
||||
f'/api/activities/{activity_cycling_user_1.short_id}',
|
||||
f'/api/workouts/{workout_cycling_user_1.short_id}',
|
||||
headers=dict(
|
||||
Authorization='Bearer '
|
||||
+ json.loads(resp_login.data.decode())['auth_token']
|
||||
@ -125,13 +125,13 @@ class TestDeleteActivityWithoutGpx:
|
||||
)
|
||||
assert response.status_code == 204
|
||||
|
||||
def test_it_returns_403_when_deleting_an_activity_from_different_user(
|
||||
def test_it_returns_403_when_deleting_an_workout_from_different_user(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
user_2: User,
|
||||
sport_1_cycling: Sport,
|
||||
activity_cycling_user_1: Activity,
|
||||
workout_cycling_user_1: Workout,
|
||||
) -> None:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -140,7 +140,7 @@ class TestDeleteActivityWithoutGpx:
|
||||
content_type='application/json',
|
||||
)
|
||||
response = client.delete(
|
||||
f'/api/activities/{activity_cycling_user_1.short_id}',
|
||||
f'/api/workouts/{workout_cycling_user_1.short_id}',
|
||||
headers=dict(
|
||||
Authorization='Bearer '
|
||||
+ json.loads(resp_login.data.decode())['auth_token']
|
73
fittrackee/tests/workouts/test_workouts_model.py
Normal file
73
fittrackee/tests/workouts/test_workouts_model.py
Normal file
@ -0,0 +1,73 @@
|
||||
from uuid import UUID
|
||||
|
||||
from fittrackee.users.models import User
|
||||
from fittrackee.workouts.models import Sport, Workout
|
||||
from fittrackee.workouts.utils_id import decode_short_id
|
||||
from flask import Flask
|
||||
|
||||
|
||||
class TestWorkoutModel:
|
||||
def test_workout_model(
|
||||
self,
|
||||
app: Flask,
|
||||
sport_1_cycling: Sport,
|
||||
user_1: User,
|
||||
workout_cycling_user_1: Workout,
|
||||
) -> None:
|
||||
workout_cycling_user_1.title = 'Test'
|
||||
|
||||
assert 1 == workout_cycling_user_1.id
|
||||
assert workout_cycling_user_1.uuid is not None
|
||||
assert 1 == workout_cycling_user_1.user_id
|
||||
assert 1 == workout_cycling_user_1.sport_id
|
||||
assert '2018-01-01 00:00:00' == str(
|
||||
workout_cycling_user_1.workout_date
|
||||
)
|
||||
assert 10.0 == float(workout_cycling_user_1.distance)
|
||||
assert '1:00:00' == str(workout_cycling_user_1.duration)
|
||||
assert 'Test' == workout_cycling_user_1.title
|
||||
assert '<Workout \'Cycling\' - 2018-01-01 00:00:00>' == str(
|
||||
workout_cycling_user_1
|
||||
)
|
||||
|
||||
serialized_workout = workout_cycling_user_1.serialize()
|
||||
assert isinstance(decode_short_id(serialized_workout['id']), UUID)
|
||||
assert 'test' == serialized_workout['user']
|
||||
assert 1 == serialized_workout['sport_id']
|
||||
assert serialized_workout['title'] == 'Test'
|
||||
assert 'creation_date' in serialized_workout
|
||||
assert serialized_workout['modification_date'] is not None
|
||||
assert str(serialized_workout['workout_date']) == '2018-01-01 00:00:00'
|
||||
assert serialized_workout['duration'] == '1:00:00'
|
||||
assert serialized_workout['pauses'] is None
|
||||
assert serialized_workout['moving'] == '1:00:00'
|
||||
assert serialized_workout['distance'] == 10.0
|
||||
assert serialized_workout['max_alt'] is None
|
||||
assert serialized_workout['descent'] is None
|
||||
assert serialized_workout['ascent'] is None
|
||||
assert serialized_workout['max_speed'] == 10.0
|
||||
assert serialized_workout['ave_speed'] == 10.0
|
||||
assert serialized_workout['with_gpx'] is False
|
||||
assert serialized_workout['bounds'] == []
|
||||
assert serialized_workout['previous_workout'] is None
|
||||
assert serialized_workout['next_workout'] is None
|
||||
assert serialized_workout['segments'] == []
|
||||
assert serialized_workout['records'] != []
|
||||
assert serialized_workout['map'] is None
|
||||
assert serialized_workout['weather_start'] is None
|
||||
assert serialized_workout['weather_end'] is None
|
||||
assert serialized_workout['notes'] is None
|
||||
|
||||
def test_workout_segment_model(
|
||||
self,
|
||||
app: Flask,
|
||||
sport_1_cycling: Sport,
|
||||
user_1: User,
|
||||
workout_cycling_user_1: Workout,
|
||||
workout_cycling_user_1_segment: Workout,
|
||||
) -> None:
|
||||
assert (
|
||||
f'<Segment \'{workout_cycling_user_1_segment.segment_id}\' '
|
||||
f'for workout \'{workout_cycling_user_1.short_id}\'>'
|
||||
== str(workout_cycling_user_1_segment)
|
||||
)
|
@ -3,7 +3,7 @@ from io import BytesIO
|
||||
from typing import Tuple
|
||||
from uuid import uuid4
|
||||
|
||||
from fittrackee.activities.utils_id import encode_uuid
|
||||
from fittrackee.workouts.utils_id import encode_uuid
|
||||
from flask import Flask
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ def get_random_short_id() -> str:
|
||||
return encode_uuid(uuid4())
|
||||
|
||||
|
||||
def post_an_activity(app: Flask, gpx_file: str) -> Tuple[str, str]:
|
||||
def post_an_workout(app: Flask, gpx_file: str) -> Tuple[str, str]:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
@ -20,7 +20,7 @@ def post_an_activity(app: Flask, gpx_file: str) -> Tuple[str, str]:
|
||||
)
|
||||
token = json.loads(resp_login.data.decode())['auth_token']
|
||||
response = client.post(
|
||||
'/api/activities',
|
||||
'/api/workouts',
|
||||
data=dict(
|
||||
file=(BytesIO(str.encode(gpx_file)), 'example.gpx'),
|
||||
data='{"sport_id": 1}',
|
||||
@ -30,4 +30,4 @@ def post_an_activity(app: Flask, gpx_file: str) -> Tuple[str, str]:
|
||||
),
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
return token, data['data']['activities'][0]['id']
|
||||
return token, data['data']['workouts'][0]['id']
|
@ -18,7 +18,7 @@ from sqlalchemy import exc, or_
|
||||
from werkzeug.exceptions import RequestEntityTooLarge
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from ..activities.utils_files import get_absolute_file_path
|
||||
from ..workouts.utils_files import get_absolute_file_path
|
||||
from .models import User
|
||||
from .utils import (
|
||||
authenticate,
|
||||
@ -310,8 +310,8 @@ def get_authenticated_user_profile(
|
||||
"language": "en",
|
||||
"last_name": null,
|
||||
"location": null,
|
||||
"nb_activities": 6,
|
||||
"nb_sports": 3,
|
||||
"nb_workouts": 6,
|
||||
"picture": false,
|
||||
"sports_list": [
|
||||
1,
|
||||
@ -371,8 +371,8 @@ def edit_user(auth_user_id: int) -> Union[Dict, HttpResponse]:
|
||||
"language": "en",
|
||||
"last_name": null,
|
||||
"location": null,
|
||||
"nb_activities": 6,
|
||||
"nb_sports": 3,
|
||||
"nb_workouts": 6,
|
||||
"picture": false,
|
||||
"sports_list": [
|
||||
1,
|
||||
|
@ -9,7 +9,7 @@ from sqlalchemy.ext.declarative import DeclarativeMeta
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
from sqlalchemy.sql.expression import select
|
||||
|
||||
from ..activities.models import Activity
|
||||
from ..workouts.models import Workout
|
||||
from .utils_token import decode_user_token, get_user_token
|
||||
|
||||
BaseModel: DeclarativeMeta = db.Model
|
||||
@ -32,8 +32,8 @@ class User(BaseModel):
|
||||
timezone = db.Column(db.String(50), nullable=True)
|
||||
# does the week start Monday?
|
||||
weekm = db.Column(db.Boolean(50), default=False, nullable=False)
|
||||
activities = db.relationship(
|
||||
'Activity', lazy=True, backref=db.backref('user', lazy='joined')
|
||||
workouts = db.relationship(
|
||||
'Workout', lazy=True, backref=db.backref('user', lazy='joined')
|
||||
)
|
||||
records = db.relationship(
|
||||
'Record', lazy=True, backref=db.backref('user', lazy='joined')
|
||||
@ -90,33 +90,33 @@ class User(BaseModel):
|
||||
return 'Invalid token. Please log in again.'
|
||||
|
||||
@hybrid_property
|
||||
def activities_count(self) -> int:
|
||||
return Activity.query.filter(Activity.user_id == self.id).count()
|
||||
def workouts_count(self) -> int:
|
||||
return Workout.query.filter(Workout.user_id == self.id).count()
|
||||
|
||||
@activities_count.expression # type: ignore
|
||||
def activities_count(self) -> int:
|
||||
@workouts_count.expression # type: ignore
|
||||
def workouts_count(self) -> int:
|
||||
return (
|
||||
select([func.count(Activity.id)])
|
||||
.where(Activity.user_id == self.id)
|
||||
.label('activities_count')
|
||||
select([func.count(Workout.id)])
|
||||
.where(Workout.user_id == self.id)
|
||||
.label('workouts_count')
|
||||
)
|
||||
|
||||
def serialize(self) -> Dict:
|
||||
sports = []
|
||||
total = (0, '0:00:00')
|
||||
if self.activities_count > 0: # type: ignore
|
||||
if self.workouts_count > 0: # type: ignore
|
||||
sports = (
|
||||
db.session.query(Activity.sport_id)
|
||||
.filter(Activity.user_id == self.id)
|
||||
.group_by(Activity.sport_id)
|
||||
.order_by(Activity.sport_id)
|
||||
db.session.query(Workout.sport_id)
|
||||
.filter(Workout.user_id == self.id)
|
||||
.group_by(Workout.sport_id)
|
||||
.order_by(Workout.sport_id)
|
||||
.all()
|
||||
)
|
||||
total = (
|
||||
db.session.query(
|
||||
func.sum(Activity.distance), func.sum(Activity.duration)
|
||||
func.sum(Workout.distance), func.sum(Workout.duration)
|
||||
)
|
||||
.filter(Activity.user_id == self.id)
|
||||
.filter(Workout.user_id == self.id)
|
||||
.first()
|
||||
)
|
||||
return {
|
||||
@ -133,8 +133,8 @@ class User(BaseModel):
|
||||
'timezone': self.timezone,
|
||||
'weekm': self.weekm,
|
||||
'language': self.language,
|
||||
'nb_activities': self.activities_count,
|
||||
'nb_sports': len(sports),
|
||||
'nb_workouts': self.workouts_count,
|
||||
'sports_list': [
|
||||
sport for sportslist in sports for sport in sportslist
|
||||
],
|
||||
|
@ -14,8 +14,8 @@ from fittrackee.responses import (
|
||||
from flask import Blueprint, request, send_file
|
||||
from sqlalchemy import exc
|
||||
|
||||
from ..activities.utils_files import get_absolute_file_path
|
||||
from .models import Activity, User
|
||||
from ..workouts.utils_files import get_absolute_file_path
|
||||
from .models import User, Workout
|
||||
from .utils import authenticate, authenticate_as_admin
|
||||
|
||||
users_blueprint = Blueprint('users', __name__)
|
||||
@ -42,7 +42,7 @@ def get_users(auth_user_id: int) -> Dict:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/users?order_by=activities_count&par_page=5 HTTP/1.1
|
||||
GET /api/users?order_by=workouts_count&par_page=5 HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
**Example response**:
|
||||
@ -65,8 +65,8 @@ def get_users(auth_user_id: int) -> Dict:
|
||||
"language": "en",
|
||||
"last_name": null,
|
||||
"location": null,
|
||||
"nb_activities": 6,
|
||||
"nb_sports": 3,
|
||||
"nb_workouts": 6,
|
||||
"picture": false,
|
||||
"sports_list": [
|
||||
1,
|
||||
@ -88,8 +88,8 @@ def get_users(auth_user_id: int) -> Dict:
|
||||
"language": "fr",
|
||||
"last_name": null,
|
||||
"location": null,
|
||||
"nb_activities": 0,
|
||||
"nb_sports": 0,
|
||||
"nb_workouts": 0,
|
||||
"picture": false,
|
||||
"sports_list": [],
|
||||
"timezone": "Europe/Paris",
|
||||
@ -108,7 +108,7 @@ def get_users(auth_user_id: int) -> Dict:
|
||||
:query integer per_page: number of users per page (default: 10, max: 50)
|
||||
:query string q: query on user name
|
||||
:query string order_by: sorting criteria (``username``, ``created_at``,
|
||||
``activities_count``, ``admin``)
|
||||
``workouts_count``, ``admin``)
|
||||
:query string order: sorting order (default: ``asc``)
|
||||
|
||||
:reqheader Authorization: OAuth 2.0 Bearer Token
|
||||
@ -137,11 +137,11 @@ def get_users(auth_user_id: int) -> Dict:
|
||||
User.username.like('%' + query + '%') if query else True,
|
||||
)
|
||||
.order_by(
|
||||
User.activities_count.asc() # type: ignore
|
||||
if order_by == 'activities_count' and order == 'asc'
|
||||
User.workouts_count.asc() # type: ignore
|
||||
if order_by == 'workouts_count' and order == 'asc'
|
||||
else True,
|
||||
User.activities_count.desc() # type: ignore
|
||||
if order_by == 'activities_count' and order == 'desc'
|
||||
User.workouts_count.desc() # type: ignore
|
||||
if order_by == 'workouts_count' and order == 'desc'
|
||||
else True,
|
||||
User.username.asc()
|
||||
if order_by == 'username' and order == 'asc'
|
||||
@ -212,8 +212,8 @@ def get_single_user(
|
||||
"language": "en",
|
||||
"last_name": null,
|
||||
"location": null,
|
||||
"nb_activities": 6,
|
||||
"nb_sports": 3,
|
||||
"nb_workouts": 6,
|
||||
"picture": false,
|
||||
"sports_list": [
|
||||
1,
|
||||
@ -328,7 +328,7 @@ def update_user(
|
||||
"language": "en",
|
||||
"last_name": null,
|
||||
"location": null,
|
||||
"nb_activities": 6,
|
||||
"nb_workouts": 6,
|
||||
"nb_sports": 3,
|
||||
"picture": false,
|
||||
"sports_list": [
|
||||
@ -443,8 +443,8 @@ def delete_user(
|
||||
'no other user has admin rights.'
|
||||
)
|
||||
|
||||
for activity in Activity.query.filter_by(user_id=user.id).all():
|
||||
db.session.delete(activity)
|
||||
for workout in Workout.query.filter_by(user_id=user.id).all():
|
||||
db.session.delete(workout)
|
||||
db.session.flush()
|
||||
user_picture = user.picture
|
||||
db.session.delete(user)
|
||||
@ -454,7 +454,7 @@ def delete_user(
|
||||
if os.path.isfile(picture_path):
|
||||
os.remove(picture_path)
|
||||
shutil.rmtree(
|
||||
get_absolute_file_path(f'activities/{user.id}'),
|
||||
get_absolute_file_path(f'workouts/{user.id}'),
|
||||
ignore_errors=True,
|
||||
)
|
||||
shutil.rmtree(
|
||||
|
@ -78,8 +78,8 @@ def verify_extension_and_size(
|
||||
return InvalidPayloadErrorResponse('No selected file.', 'fail')
|
||||
|
||||
allowed_extensions = (
|
||||
'ACTIVITY_ALLOWED_EXTENSIONS'
|
||||
if file_type == 'activity'
|
||||
'WORKOUT_ALLOWED_EXTENSIONS'
|
||||
if file_type == 'workout'
|
||||
else 'PICTURE_ALLOWED_EXTENSIONS'
|
||||
)
|
||||
|
||||
@ -158,13 +158,13 @@ def authenticate_as_admin(f: Callable) -> Callable:
|
||||
return decorated_function
|
||||
|
||||
|
||||
def can_view_activity(
|
||||
auth_user_id: int, activity_user_id: int
|
||||
def can_view_workout(
|
||||
auth_user_id: int, workout_user_id: int
|
||||
) -> Optional[HttpResponse]:
|
||||
"""
|
||||
Return error response if user has no right to view activity
|
||||
Return error response if user has no right to view workout
|
||||
"""
|
||||
if auth_user_id != activity_user_id:
|
||||
if auth_user_id != workout_user_id:
|
||||
return ForbiddenErrorResponse()
|
||||
return None
|
||||
|
||||
|
@ -30,7 +30,7 @@ def update_records(
|
||||
user_id: int, sport_id: int, connection: Connection, session: Session
|
||||
) -> None:
|
||||
record_table = Record.__table__
|
||||
new_records = Activity.get_user_activity_records(user_id, sport_id)
|
||||
new_records = Workout.get_user_workout_records(user_id, sport_id)
|
||||
for record_type, record_data in new_records.items():
|
||||
if record_data['record_value']:
|
||||
record = Record.query.filter_by(
|
||||
@ -45,14 +45,14 @@ def update_records(
|
||||
.where(record_table.c.id == record.id)
|
||||
.values(
|
||||
value=value,
|
||||
activity_id=record_data['activity'].id,
|
||||
activity_uuid=record_data['activity'].uuid,
|
||||
activity_date=record_data['activity'].activity_date,
|
||||
workout_id=record_data['workout'].id,
|
||||
workout_uuid=record_data['workout'].uuid,
|
||||
workout_date=record_data['workout'].workout_date,
|
||||
)
|
||||
)
|
||||
else:
|
||||
new_record = Record(
|
||||
activity=record_data['activity'], record_type=record_type
|
||||
workout=record_data['workout'], record_type=record_type
|
||||
)
|
||||
new_record.value = record_data['record_value'] # type: ignore
|
||||
session.add(new_record)
|
||||
@ -66,13 +66,13 @@ def update_records(
|
||||
|
||||
|
||||
class Sport(BaseModel):
|
||||
__tablename__ = "sports"
|
||||
__tablename__ = 'sports'
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
label = db.Column(db.String(50), unique=True, nullable=False)
|
||||
img = db.Column(db.String(255), unique=True, nullable=True)
|
||||
is_active = db.Column(db.Boolean, default=True, nullable=False)
|
||||
activities = db.relationship(
|
||||
'Activity', lazy=True, backref=db.backref('sports', lazy='joined')
|
||||
workouts = db.relationship(
|
||||
'Workout', lazy=True, backref=db.backref('sports', lazy='joined')
|
||||
)
|
||||
records = db.relationship(
|
||||
'Record', lazy=True, backref=db.backref('sports', lazy='joined')
|
||||
@ -92,12 +92,12 @@ class Sport(BaseModel):
|
||||
'is_active': self.is_active,
|
||||
}
|
||||
if is_admin:
|
||||
serialized_sport['has_activities'] = len(self.activities) > 0
|
||||
serialized_sport['has_workouts'] = len(self.workouts) > 0
|
||||
return serialized_sport
|
||||
|
||||
|
||||
class Activity(BaseModel):
|
||||
__tablename__ = "activities"
|
||||
class Workout(BaseModel):
|
||||
__tablename__ = 'workouts'
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
uuid = db.Column(
|
||||
postgresql.UUID(as_uuid=True),
|
||||
@ -115,7 +115,7 @@ class Activity(BaseModel):
|
||||
modification_date = db.Column(
|
||||
db.DateTime, onupdate=datetime.datetime.utcnow
|
||||
)
|
||||
activity_date = db.Column(db.DateTime, nullable=False)
|
||||
workout_date = db.Column(db.DateTime, nullable=False)
|
||||
duration = db.Column(db.Interval, nullable=False)
|
||||
pauses = db.Column(db.Interval, nullable=True)
|
||||
moving = db.Column(db.Interval, nullable=True)
|
||||
@ -133,32 +133,32 @@ class Activity(BaseModel):
|
||||
weather_end = db.Column(JSON, nullable=True)
|
||||
notes = db.Column(db.String(500), nullable=True)
|
||||
segments = db.relationship(
|
||||
'ActivitySegment',
|
||||
'WorkoutSegment',
|
||||
lazy=True,
|
||||
cascade='all, delete',
|
||||
backref=db.backref('activities', lazy='joined', single_parent=True),
|
||||
backref=db.backref('workouts', lazy='joined', single_parent=True),
|
||||
)
|
||||
records = db.relationship(
|
||||
'Record',
|
||||
lazy=True,
|
||||
cascade='all, delete',
|
||||
backref=db.backref('activities', lazy='joined', single_parent=True),
|
||||
backref=db.backref('workouts', lazy='joined', single_parent=True),
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'<Activity \'{self.sports.label}\' - {self.activity_date}>'
|
||||
return f'<Workout \'{self.sports.label}\' - {self.workout_date}>'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
user_id: int,
|
||||
sport_id: int,
|
||||
activity_date: datetime.datetime,
|
||||
workout_date: datetime.datetime,
|
||||
distance: float,
|
||||
duration: datetime.timedelta,
|
||||
) -> None:
|
||||
self.user_id = user_id
|
||||
self.sport_id = sport_id
|
||||
self.activity_date = activity_date
|
||||
self.workout_date = workout_date
|
||||
self.distance = distance
|
||||
self.duration = duration
|
||||
|
||||
@ -178,78 +178,78 @@ class Activity(BaseModel):
|
||||
max_speed_from = params.get('max_speed_from') if params else None
|
||||
max_speed_to = params.get('max_speed_to') if params else None
|
||||
sport_id = params.get('sport_id') if params else None
|
||||
previous_activity = (
|
||||
Activity.query.filter(
|
||||
Activity.id != self.id,
|
||||
Activity.user_id == self.user_id,
|
||||
Activity.sport_id == sport_id if sport_id else True,
|
||||
Activity.activity_date <= self.activity_date,
|
||||
Activity.activity_date
|
||||
previous_workout = (
|
||||
Workout.query.filter(
|
||||
Workout.id != self.id,
|
||||
Workout.user_id == self.user_id,
|
||||
Workout.sport_id == sport_id if sport_id else True,
|
||||
Workout.workout_date <= self.workout_date,
|
||||
Workout.workout_date
|
||||
>= datetime.datetime.strptime(date_from, '%Y-%m-%d')
|
||||
if date_from
|
||||
else True,
|
||||
Activity.activity_date
|
||||
Workout.workout_date
|
||||
<= datetime.datetime.strptime(date_to, '%Y-%m-%d')
|
||||
if date_to
|
||||
else True,
|
||||
Activity.distance >= int(distance_from)
|
||||
Workout.distance >= int(distance_from)
|
||||
if distance_from
|
||||
else True,
|
||||
Activity.distance <= int(distance_to) if distance_to else True,
|
||||
Activity.duration >= convert_in_duration(duration_from)
|
||||
Workout.distance <= int(distance_to) if distance_to else True,
|
||||
Workout.duration >= convert_in_duration(duration_from)
|
||||
if duration_from
|
||||
else True,
|
||||
Activity.duration <= convert_in_duration(duration_to)
|
||||
Workout.duration <= convert_in_duration(duration_to)
|
||||
if duration_to
|
||||
else True,
|
||||
Activity.ave_speed >= float(ave_speed_from)
|
||||
Workout.ave_speed >= float(ave_speed_from)
|
||||
if ave_speed_from
|
||||
else True,
|
||||
Activity.ave_speed <= float(ave_speed_to)
|
||||
Workout.ave_speed <= float(ave_speed_to)
|
||||
if ave_speed_to
|
||||
else True,
|
||||
Activity.max_speed >= float(max_speed_from)
|
||||
Workout.max_speed >= float(max_speed_from)
|
||||
if max_speed_from
|
||||
else True,
|
||||
Activity.max_speed <= float(max_speed_to)
|
||||
Workout.max_speed <= float(max_speed_to)
|
||||
if max_speed_to
|
||||
else True,
|
||||
)
|
||||
.order_by(Activity.activity_date.desc())
|
||||
.order_by(Workout.workout_date.desc())
|
||||
.first()
|
||||
)
|
||||
next_activity = (
|
||||
Activity.query.filter(
|
||||
Activity.id != self.id,
|
||||
Activity.user_id == self.user_id,
|
||||
Activity.sport_id == sport_id if sport_id else True,
|
||||
Activity.activity_date >= self.activity_date,
|
||||
Activity.activity_date
|
||||
next_workout = (
|
||||
Workout.query.filter(
|
||||
Workout.id != self.id,
|
||||
Workout.user_id == self.user_id,
|
||||
Workout.sport_id == sport_id if sport_id else True,
|
||||
Workout.workout_date >= self.workout_date,
|
||||
Workout.workout_date
|
||||
>= datetime.datetime.strptime(date_from, '%Y-%m-%d')
|
||||
if date_from
|
||||
else True,
|
||||
Activity.activity_date
|
||||
Workout.workout_date
|
||||
<= datetime.datetime.strptime(date_to, '%Y-%m-%d')
|
||||
if date_to
|
||||
else True,
|
||||
Activity.distance >= int(distance_from)
|
||||
Workout.distance >= int(distance_from)
|
||||
if distance_from
|
||||
else True,
|
||||
Activity.distance <= int(distance_to) if distance_to else True,
|
||||
Activity.duration >= convert_in_duration(duration_from)
|
||||
Workout.distance <= int(distance_to) if distance_to else True,
|
||||
Workout.duration >= convert_in_duration(duration_from)
|
||||
if duration_from
|
||||
else True,
|
||||
Activity.duration <= convert_in_duration(duration_to)
|
||||
Workout.duration <= convert_in_duration(duration_to)
|
||||
if duration_to
|
||||
else True,
|
||||
Activity.ave_speed >= float(ave_speed_from)
|
||||
Workout.ave_speed >= float(ave_speed_from)
|
||||
if ave_speed_from
|
||||
else True,
|
||||
Activity.ave_speed <= float(ave_speed_to)
|
||||
Workout.ave_speed <= float(ave_speed_to)
|
||||
if ave_speed_to
|
||||
else True,
|
||||
)
|
||||
.order_by(Activity.activity_date.asc())
|
||||
.order_by(Workout.workout_date.asc())
|
||||
.first()
|
||||
)
|
||||
return {
|
||||
@ -259,7 +259,7 @@ class Activity(BaseModel):
|
||||
'title': self.title,
|
||||
'creation_date': self.creation_date,
|
||||
'modification_date': self.modification_date,
|
||||
'activity_date': self.activity_date,
|
||||
'workout_date': self.workout_date,
|
||||
'duration': str(self.duration) if self.duration else None,
|
||||
'pauses': str(self.pauses) if self.pauses else None,
|
||||
'moving': str(self.moving) if self.moving else None,
|
||||
@ -274,10 +274,10 @@ class Activity(BaseModel):
|
||||
'bounds': [float(bound) for bound in self.bounds]
|
||||
if self.bounds
|
||||
else [], # noqa
|
||||
'previous_activity': previous_activity.short_id
|
||||
if previous_activity
|
||||
'previous_workout': previous_workout.short_id
|
||||
if previous_workout
|
||||
else None, # noqa
|
||||
'next_activity': next_activity.short_id if next_activity else None,
|
||||
'next_workout': next_workout.short_id if next_workout else None,
|
||||
'segments': [segment.serialize() for segment in self.segments],
|
||||
'records': [record.serialize() for record in self.records],
|
||||
'map': self.map_id if self.map else None,
|
||||
@ -287,7 +287,7 @@ class Activity(BaseModel):
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_user_activity_records(
|
||||
def get_user_workout_records(
|
||||
cls, user_id: int, sport_id: int, as_integer: Optional[bool] = False
|
||||
) -> Dict:
|
||||
record_types_columns = {
|
||||
@ -298,55 +298,53 @@ class Activity(BaseModel):
|
||||
}
|
||||
records = {}
|
||||
for record_type, column in record_types_columns.items():
|
||||
column_sorted = getattr(getattr(Activity, column), 'desc')()
|
||||
record_activity = (
|
||||
Activity.query.filter_by(user_id=user_id, sport_id=sport_id)
|
||||
.order_by(column_sorted, Activity.activity_date)
|
||||
column_sorted = getattr(getattr(Workout, column), 'desc')()
|
||||
record_workout = (
|
||||
Workout.query.filter_by(user_id=user_id, sport_id=sport_id)
|
||||
.order_by(column_sorted, Workout.workout_date)
|
||||
.first()
|
||||
)
|
||||
records[record_type] = dict(
|
||||
record_value=(
|
||||
getattr(record_activity, column)
|
||||
if record_activity
|
||||
else None
|
||||
getattr(record_workout, column) if record_workout else None
|
||||
),
|
||||
activity=record_activity,
|
||||
workout=record_workout,
|
||||
)
|
||||
return records
|
||||
|
||||
|
||||
@listens_for(Activity, 'after_insert')
|
||||
def on_activity_insert(
|
||||
mapper: Mapper, connection: Connection, activity: Activity
|
||||
@listens_for(Workout, 'after_insert')
|
||||
def on_workout_insert(
|
||||
mapper: Mapper, connection: Connection, workout: Workout
|
||||
) -> None:
|
||||
@listens_for(db.Session, 'after_flush', once=True)
|
||||
def receive_after_flush(session: Session, context: Any) -> None:
|
||||
update_records(
|
||||
activity.user_id, activity.sport_id, connection, session
|
||||
workout.user_id, workout.sport_id, connection, session
|
||||
) # noqa
|
||||
|
||||
|
||||
@listens_for(Activity, 'after_update')
|
||||
def on_activity_update(
|
||||
mapper: Mapper, connection: Connection, activity: Activity
|
||||
@listens_for(Workout, 'after_update')
|
||||
def on_workout_update(
|
||||
mapper: Mapper, connection: Connection, workout: Workout
|
||||
) -> None:
|
||||
if object_session(activity).is_modified(
|
||||
activity, include_collections=True
|
||||
if object_session(workout).is_modified(
|
||||
workout, include_collections=True
|
||||
): # noqa
|
||||
|
||||
@listens_for(db.Session, 'after_flush', once=True)
|
||||
def receive_after_flush(session: Session, context: Any) -> None:
|
||||
sports_list = [activity.sport_id]
|
||||
records = Record.query.filter_by(activity_id=activity.id).all()
|
||||
sports_list = [workout.sport_id]
|
||||
records = Record.query.filter_by(workout_id=workout.id).all()
|
||||
for rec in records:
|
||||
if rec.sport_id not in sports_list:
|
||||
sports_list.append(rec.sport_id)
|
||||
for sport_id in sports_list:
|
||||
update_records(activity.user_id, sport_id, connection, session)
|
||||
update_records(workout.user_id, sport_id, connection, session)
|
||||
|
||||
|
||||
@listens_for(Activity, 'after_delete')
|
||||
def on_activity_delete(
|
||||
@listens_for(Workout, 'after_delete')
|
||||
def on_workout_delete(
|
||||
mapper: Mapper, connection: Connection, old_record: 'Record'
|
||||
) -> None:
|
||||
@listens_for(db.Session, 'after_flush', once=True)
|
||||
@ -357,12 +355,12 @@ def on_activity_delete(
|
||||
os.remove(get_absolute_file_path(old_record.gpx))
|
||||
|
||||
|
||||
class ActivitySegment(BaseModel):
|
||||
__tablename__ = "activity_segments"
|
||||
activity_id = db.Column(
|
||||
db.Integer, db.ForeignKey('activities.id'), primary_key=True
|
||||
class WorkoutSegment(BaseModel):
|
||||
__tablename__ = 'workout_segments'
|
||||
workout_id = db.Column(
|
||||
db.Integer, db.ForeignKey('workouts.id'), primary_key=True
|
||||
)
|
||||
activity_uuid = db.Column(postgresql.UUID(as_uuid=True), nullable=False)
|
||||
workout_uuid = db.Column(postgresql.UUID(as_uuid=True), nullable=False)
|
||||
segment_id = db.Column(db.Integer, primary_key=True)
|
||||
duration = db.Column(db.Interval, nullable=False)
|
||||
pauses = db.Column(db.Interval, nullable=True)
|
||||
@ -378,19 +376,19 @@ class ActivitySegment(BaseModel):
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
f'<Segment \'{self.segment_id}\' '
|
||||
f'for activity \'{encode_uuid(self.activity_uuid)}\'>'
|
||||
f'for workout \'{encode_uuid(self.workout_uuid)}\'>'
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self, segment_id: int, activity_id: int, activity_uuid: UUID
|
||||
self, segment_id: int, workout_id: int, workout_uuid: UUID
|
||||
) -> None:
|
||||
self.segment_id = segment_id
|
||||
self.activity_id = activity_id
|
||||
self.activity_uuid = activity_uuid
|
||||
self.workout_id = workout_id
|
||||
self.workout_uuid = workout_uuid
|
||||
|
||||
def serialize(self) -> Dict:
|
||||
return {
|
||||
'activity_id': encode_uuid(self.activity_uuid),
|
||||
'workout_id': encode_uuid(self.workout_uuid),
|
||||
'segment_id': self.segment_id,
|
||||
'duration': str(self.duration) if self.duration else None,
|
||||
'pauses': str(self.pauses) if self.pauses else None,
|
||||
@ -417,28 +415,28 @@ class Record(BaseModel):
|
||||
sport_id = db.Column(
|
||||
db.Integer, db.ForeignKey('sports.id'), nullable=False
|
||||
)
|
||||
activity_id = db.Column(
|
||||
db.Integer, db.ForeignKey('activities.id'), nullable=False
|
||||
workout_id = db.Column(
|
||||
db.Integer, db.ForeignKey('workouts.id'), nullable=False
|
||||
)
|
||||
activity_uuid = db.Column(postgresql.UUID(as_uuid=True), nullable=False)
|
||||
workout_uuid = db.Column(postgresql.UUID(as_uuid=True), nullable=False)
|
||||
record_type = db.Column(Enum(*record_types, name="record_types"))
|
||||
activity_date = db.Column(db.DateTime, nullable=False)
|
||||
workout_date = db.Column(db.DateTime, nullable=False)
|
||||
_value = db.Column("value", db.Integer, nullable=True)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
f'<Record {self.sports.label} - '
|
||||
f'{self.record_type} - '
|
||||
f"{self.activity_date.strftime('%Y-%m-%d')}>"
|
||||
f"{self.workout_date.strftime('%Y-%m-%d')}>"
|
||||
)
|
||||
|
||||
def __init__(self, activity: Activity, record_type: str) -> None:
|
||||
self.user_id = activity.user_id
|
||||
self.sport_id = activity.sport_id
|
||||
self.activity_id = activity.id
|
||||
self.activity_uuid = activity.uuid
|
||||
def __init__(self, workout: Workout, record_type: str) -> None:
|
||||
self.user_id = workout.user_id
|
||||
self.sport_id = workout.sport_id
|
||||
self.workout_id = workout.id
|
||||
self.workout_uuid = workout.uuid
|
||||
self.record_type = record_type
|
||||
self.activity_date = activity.activity_date
|
||||
self.workout_date = workout.workout_date
|
||||
|
||||
@hybrid_property
|
||||
def value(self) -> Optional[Union[datetime.timedelta, float]]:
|
||||
@ -467,9 +465,9 @@ class Record(BaseModel):
|
||||
'id': self.id,
|
||||
'user': self.user.username,
|
||||
'sport_id': self.sport_id,
|
||||
'activity_id': encode_uuid(self.activity_uuid),
|
||||
'workout_id': encode_uuid(self.workout_uuid),
|
||||
'record_type': self.record_type,
|
||||
'activity_date': self.activity_date,
|
||||
'workout_date': self.workout_date,
|
||||
'value': value,
|
||||
}
|
||||
|
||||
@ -480,9 +478,9 @@ def on_record_delete(
|
||||
) -> None:
|
||||
@listens_for(db.Session, 'after_flush', once=True)
|
||||
def receive_after_flush(session: Session, context: Any) -> None:
|
||||
activity = old_record.activities
|
||||
new_records = Activity.get_user_activity_records(
|
||||
activity.user_id, activity.sport_id
|
||||
workout = old_record.workouts
|
||||
new_records = Workout.get_user_workout_records(
|
||||
workout.user_id, workout.sport_id
|
||||
)
|
||||
for record_type, record_data in new_records.items():
|
||||
if (
|
||||
@ -490,7 +488,7 @@ def on_record_delete(
|
||||
and record_type == old_record.record_type
|
||||
):
|
||||
new_record = Record(
|
||||
activity=record_data['activity'], record_type=record_type
|
||||
workout=record_data['workout'], record_type=record_type
|
||||
)
|
||||
new_record.value = record_data['record_value'] # type: ignore
|
||||
session.add(new_record)
|
@ -40,40 +40,40 @@ def get_records(auth_user_id: int) -> Dict:
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"activity_date": "Sun, 07 Jul 2019 08:00:00 GMT",
|
||||
"activity_id": "hvYBqYBRa7wwXpaStWR4V2",
|
||||
"id": 9,
|
||||
"record_type": "AS",
|
||||
"sport_id": 1,
|
||||
"user": "admin",
|
||||
"value": 18
|
||||
"value": 18,
|
||||
"workout_date": "Sun, 07 Jul 2019 08:00:00 GMT",
|
||||
"workout_id": "hvYBqYBRa7wwXpaStWR4V2"
|
||||
},
|
||||
{
|
||||
"activity_date": "Sun, 07 Jul 2019 08:00:00 GMT",
|
||||
"activity_id": "hvYBqYBRa7wwXpaStWR4V2",
|
||||
"id": 10,
|
||||
"record_type": "FD",
|
||||
"sport_id": 1,
|
||||
"user": "admin",
|
||||
"value": 18
|
||||
"value": 18,
|
||||
"workout_date": "Sun, 07 Jul 2019 08:00:00 GMT",
|
||||
"workout_id": "hvYBqYBRa7wwXpaStWR4V2"
|
||||
},
|
||||
{
|
||||
"activity_date": "Sun, 07 Jul 2019 08:00:00 GMT",
|
||||
"activity_id": "hvYBqYBRa7wwXpaStWR4V2",
|
||||
"id": 11,
|
||||
"record_type": "LD",
|
||||
"sport_id": 1,
|
||||
"user": "admin",
|
||||
"value": "1:01:00"
|
||||
"value": "1:01:00",
|
||||
"workout_date": "Sun, 07 Jul 2019 08:00:00 GMT",
|
||||
"workout_id": "hvYBqYBRa7wwXpaStWR4V2"
|
||||
},
|
||||
{
|
||||
"activity_date": "Sun, 07 Jul 2019 08:00:00 GMT",
|
||||
"activity_id": "hvYBqYBRa7wwXpaStWR4V2",
|
||||
"id": 12,
|
||||
"record_type": "MS",
|
||||
"sport_id": 1,
|
||||
"user": "admin",
|
||||
"value": 18
|
||||
"value": 18,
|
||||
"workout_date": "Sun, 07 Jul 2019 08:00:00 GMT",
|
||||
"workout_id": "hvYBqYBRa7wwXpaStWR4V2"
|
||||
}
|
||||
]
|
||||
},
|
@ -94,42 +94,42 @@ def get_sports(auth_user_id: int) -> Dict:
|
||||
"data": {
|
||||
"sports": [
|
||||
{
|
||||
"has_activities": true,
|
||||
"has_workouts": true,
|
||||
"id": 1,
|
||||
"img": "/img/sports/cycling-sport.png",
|
||||
"is_active": true,
|
||||
"label": "Cycling (Sport)"
|
||||
},
|
||||
{
|
||||
"has_activities": false,
|
||||
"has_workouts": false,
|
||||
"id": 2,
|
||||
"img": "/img/sports/cycling-transport.png",
|
||||
"is_active": true,
|
||||
"label": "Cycling (Transport)"
|
||||
},
|
||||
{
|
||||
"has_activities": false,
|
||||
"has_workouts": false,
|
||||
"id": 3,
|
||||
"img": "/img/sports/hiking.png",
|
||||
"is_active": true,
|
||||
"label": "Hiking"
|
||||
},
|
||||
{
|
||||
"has_activities": false,
|
||||
"has_workouts": false,
|
||||
"id": 4,
|
||||
"img": "/img/sports/mountain-biking.png",
|
||||
"is_active": true,
|
||||
"label": "Mountain Biking"
|
||||
},
|
||||
{
|
||||
"has_activities": false,
|
||||
"has_workouts": false,
|
||||
"id": 5,
|
||||
"img": "/img/sports/running.png",
|
||||
"is_active": true,
|
||||
"label": "Running"
|
||||
},
|
||||
{
|
||||
"has_activities": false,
|
||||
"has_workouts": false,
|
||||
"id": 6,
|
||||
"img": "/img/sports/walking.png",
|
||||
"is_active": true,
|
||||
@ -206,7 +206,7 @@ def get_sport(auth_user_id: int, sport_id: int) -> Union[Dict, HttpResponse]:
|
||||
"data": {
|
||||
"sports": [
|
||||
{
|
||||
"has_activities": false,
|
||||
"has_workouts": false,
|
||||
"id": 1,
|
||||
"img": "/img/sports/cycling-sport.png",
|
||||
"is_active": true,
|
||||
@ -283,7 +283,7 @@ def update_sport(
|
||||
"data": {
|
||||
"sports": [
|
||||
{
|
||||
"has_activities": false,
|
||||
"has_workouts": false,
|
||||
"id": 1,
|
||||
"img": "/img/sports/cycling-sport.png",
|
||||
"is_active": false,
|
@ -14,18 +14,18 @@ from sqlalchemy import func
|
||||
|
||||
from ..users.models import User
|
||||
from ..users.utils import authenticate, authenticate_as_admin
|
||||
from .models import Activity, Sport
|
||||
from .models import Sport, Workout
|
||||
from .utils import get_datetime_with_tz, get_upload_dir_size
|
||||
from .utils_format import convert_timedelta_to_integer
|
||||
|
||||
stats_blueprint = Blueprint('stats', __name__)
|
||||
|
||||
|
||||
def get_activities(
|
||||
def get_workouts(
|
||||
user_name: str, filter_type: str
|
||||
) -> Union[Dict, HttpResponse]:
|
||||
"""
|
||||
Return user activities by sport or by time
|
||||
Return user workouts by sport or by time
|
||||
"""
|
||||
try:
|
||||
user = User.query.filter_by(username=user_name).first()
|
||||
@ -52,91 +52,89 @@ def get_activities(
|
||||
if not sport:
|
||||
return NotFoundErrorResponse('Sport does not exist.')
|
||||
|
||||
activities = (
|
||||
Activity.query.filter(
|
||||
Activity.user_id == user.id,
|
||||
Activity.activity_date >= date_from if date_from else True,
|
||||
Activity.activity_date < date_to + timedelta(seconds=1)
|
||||
workouts = (
|
||||
Workout.query.filter(
|
||||
Workout.user_id == user.id,
|
||||
Workout.workout_date >= date_from if date_from else True,
|
||||
Workout.workout_date < date_to + timedelta(seconds=1)
|
||||
if date_to
|
||||
else True,
|
||||
Activity.sport_id == sport_id if sport_id else True,
|
||||
Workout.sport_id == sport_id if sport_id else True,
|
||||
)
|
||||
.order_by(Activity.activity_date.asc())
|
||||
.order_by(Workout.workout_date.asc())
|
||||
.all()
|
||||
)
|
||||
|
||||
activities_list_by_sport = {}
|
||||
activities_list_by_time = {} # type: ignore
|
||||
for activity in activities:
|
||||
workouts_list_by_sport = {}
|
||||
workouts_list_by_time = {} # type: ignore
|
||||
for workout in workouts:
|
||||
if filter_type == 'by_sport':
|
||||
sport_id = activity.sport_id
|
||||
if sport_id not in activities_list_by_sport:
|
||||
activities_list_by_sport[sport_id] = {
|
||||
'nb_activities': 0,
|
||||
sport_id = workout.sport_id
|
||||
if sport_id not in workouts_list_by_sport:
|
||||
workouts_list_by_sport[sport_id] = {
|
||||
'nb_workouts': 0,
|
||||
'total_distance': 0.0,
|
||||
'total_duration': 0,
|
||||
}
|
||||
activities_list_by_sport[sport_id]['nb_activities'] += 1
|
||||
activities_list_by_sport[sport_id]['total_distance'] += float(
|
||||
activity.distance
|
||||
workouts_list_by_sport[sport_id]['nb_workouts'] += 1
|
||||
workouts_list_by_sport[sport_id]['total_distance'] += float(
|
||||
workout.distance
|
||||
)
|
||||
activities_list_by_sport[sport_id][
|
||||
workouts_list_by_sport[sport_id][
|
||||
'total_duration'
|
||||
] += convert_timedelta_to_integer(activity.moving)
|
||||
] += convert_timedelta_to_integer(workout.moving)
|
||||
|
||||
# filter_type == 'by_time'
|
||||
else:
|
||||
if time == 'week':
|
||||
activity_date = activity.activity_date - timedelta(
|
||||
workout_date = workout.workout_date - timedelta(
|
||||
days=(
|
||||
activity.activity_date.isoweekday()
|
||||
if activity.activity_date.isoweekday() < 7
|
||||
workout.workout_date.isoweekday()
|
||||
if workout.workout_date.isoweekday() < 7
|
||||
else 0
|
||||
)
|
||||
)
|
||||
time_period = datetime.strftime(activity_date, "%Y-%m-%d")
|
||||
time_period = datetime.strftime(workout_date, "%Y-%m-%d")
|
||||
elif time == 'weekm': # week start Monday
|
||||
activity_date = activity.activity_date - timedelta(
|
||||
days=activity.activity_date.weekday()
|
||||
workout_date = workout.workout_date - timedelta(
|
||||
days=workout.workout_date.weekday()
|
||||
)
|
||||
time_period = datetime.strftime(activity_date, "%Y-%m-%d")
|
||||
time_period = datetime.strftime(workout_date, "%Y-%m-%d")
|
||||
elif time == 'month':
|
||||
time_period = datetime.strftime(
|
||||
activity.activity_date, "%Y-%m"
|
||||
workout.workout_date, "%Y-%m"
|
||||
)
|
||||
elif time == 'year' or not time:
|
||||
time_period = datetime.strftime(
|
||||
activity.activity_date, "%Y"
|
||||
)
|
||||
time_period = datetime.strftime(workout.workout_date, "%Y")
|
||||
else:
|
||||
return InvalidPayloadErrorResponse(
|
||||
'Invalid time period.', 'fail'
|
||||
)
|
||||
sport_id = activity.sport_id
|
||||
if time_period not in activities_list_by_time:
|
||||
activities_list_by_time[time_period] = {}
|
||||
if sport_id not in activities_list_by_time[time_period]:
|
||||
activities_list_by_time[time_period][sport_id] = {
|
||||
'nb_activities': 0,
|
||||
sport_id = workout.sport_id
|
||||
if time_period not in workouts_list_by_time:
|
||||
workouts_list_by_time[time_period] = {}
|
||||
if sport_id not in workouts_list_by_time[time_period]:
|
||||
workouts_list_by_time[time_period][sport_id] = {
|
||||
'nb_workouts': 0,
|
||||
'total_distance': 0.0,
|
||||
'total_duration': 0,
|
||||
}
|
||||
activities_list_by_time[time_period][sport_id][
|
||||
'nb_activities'
|
||||
workouts_list_by_time[time_period][sport_id][
|
||||
'nb_workouts'
|
||||
] += 1
|
||||
activities_list_by_time[time_period][sport_id][
|
||||
workouts_list_by_time[time_period][sport_id][
|
||||
'total_distance'
|
||||
] += float(activity.distance)
|
||||
activities_list_by_time[time_period][sport_id][
|
||||
] += float(workout.distance)
|
||||
workouts_list_by_time[time_period][sport_id][
|
||||
'total_duration'
|
||||
] += convert_timedelta_to_integer(activity.moving)
|
||||
] += convert_timedelta_to_integer(workout.moving)
|
||||
|
||||
return {
|
||||
'status': 'success',
|
||||
'data': {
|
||||
'statistics': activities_list_by_sport
|
||||
'statistics': workouts_list_by_sport
|
||||
if filter_type == 'by_sport'
|
||||
else activities_list_by_time
|
||||
else workouts_list_by_time
|
||||
},
|
||||
}
|
||||
except Exception as e:
|
||||
@ -145,11 +143,11 @@ def get_activities(
|
||||
|
||||
@stats_blueprint.route('/stats/<user_name>/by_time', methods=['GET'])
|
||||
@authenticate
|
||||
def get_activities_by_time(
|
||||
def get_workouts_by_time(
|
||||
auth_user_id: int, user_name: str
|
||||
) -> Union[Dict, HttpResponse]:
|
||||
"""
|
||||
Get activities statistics for a user by time
|
||||
Get workouts statistics for a user by time
|
||||
|
||||
**Example requests**:
|
||||
|
||||
@ -163,7 +161,8 @@ def get_activities_by_time(
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/stats/admin/by_time?from=2018-01-01&to=2018-06-30&time=week HTTP/1.1
|
||||
GET /api/stats/admin/by_time?from=2018-01-01&to=2018-06-30&time=week
|
||||
HTTP/1.1
|
||||
|
||||
**Example responses**:
|
||||
|
||||
@ -179,19 +178,19 @@ def get_activities_by_time(
|
||||
"statistics": {
|
||||
"2017": {
|
||||
"3": {
|
||||
"nb_activities": 2,
|
||||
"nb_workouts": 2,
|
||||
"total_distance": 15.282,
|
||||
"total_duration": 12341
|
||||
}
|
||||
},
|
||||
"2019": {
|
||||
"1": {
|
||||
"nb_activities": 3,
|
||||
"nb_workouts": 3,
|
||||
"total_distance": 47,
|
||||
"total_duration": 9960
|
||||
},
|
||||
"2": {
|
||||
"nb_activities": 1,
|
||||
"nb_workouts": 1,
|
||||
"total_distance": 5.613,
|
||||
"total_duration": 1267
|
||||
}
|
||||
@ -201,7 +200,7 @@ def get_activities_by_time(
|
||||
"status": "success"
|
||||
}
|
||||
|
||||
- no activities
|
||||
- no workouts
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
@ -238,20 +237,20 @@ def get_activities_by_time(
|
||||
- User does not exist.
|
||||
|
||||
"""
|
||||
return get_activities(user_name, 'by_time')
|
||||
return get_workouts(user_name, 'by_time')
|
||||
|
||||
|
||||
@stats_blueprint.route('/stats/<user_name>/by_sport', methods=['GET'])
|
||||
@authenticate
|
||||
def get_activities_by_sport(
|
||||
def get_workouts_by_sport(
|
||||
auth_user_id: int, user_name: str
|
||||
) -> Union[Dict, HttpResponse]:
|
||||
"""
|
||||
Get activities statistics for a user by sport
|
||||
Get workouts statistics for a user by sport
|
||||
|
||||
**Example requests**:
|
||||
|
||||
- without parameters (get stats for all sports with activities)
|
||||
- without parameters (get stats for all sports with workouts)
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
@ -276,17 +275,17 @@ def get_activities_by_sport(
|
||||
"data": {
|
||||
"statistics": {
|
||||
"1": {
|
||||
"nb_activities": 3,
|
||||
"nb_workouts": 3,
|
||||
"total_distance": 47,
|
||||
"total_duration": 9960
|
||||
},
|
||||
"2": {
|
||||
"nb_activities": 1,
|
||||
"nb_workouts": 1,
|
||||
"total_distance": 5.613,
|
||||
"total_duration": 1267
|
||||
},
|
||||
"3": {
|
||||
"nb_activities": 2,
|
||||
"nb_workouts": 2,
|
||||
"total_distance": 15.282,
|
||||
"total_duration": 12341
|
||||
}
|
||||
@ -295,7 +294,7 @@ def get_activities_by_sport(
|
||||
"status": "success"
|
||||
}
|
||||
|
||||
- no activities
|
||||
- no workouts
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
@ -326,7 +325,7 @@ def get_activities_by_sport(
|
||||
- Sport does not exist.
|
||||
|
||||
"""
|
||||
return get_activities(user_name, 'by_sport')
|
||||
return get_workouts(user_name, 'by_sport')
|
||||
|
||||
|
||||
@stats_blueprint.route('/stats/all', methods=['GET'])
|
||||
@ -351,10 +350,10 @@ def get_application_stats(auth_user_id: int) -> Dict:
|
||||
|
||||
{
|
||||
"data": {
|
||||
"activities": 3,
|
||||
"sports": 3,
|
||||
"uploads_dir_size": 1000,
|
||||
"users": 2,
|
||||
"uploads_dir_size": 1000
|
||||
"workouts": 3,
|
||||
},
|
||||
"status": "success"
|
||||
}
|
||||
@ -371,17 +370,17 @@ def get_application_stats(auth_user_id: int) -> Dict:
|
||||
:statuscode 403: You do not have permissions.
|
||||
"""
|
||||
|
||||
nb_activities = Activity.query.filter().count()
|
||||
nb_workouts = Workout.query.filter().count()
|
||||
nb_users = User.query.filter().count()
|
||||
nb_sports = (
|
||||
db.session.query(func.count(Activity.sport_id))
|
||||
.group_by(Activity.sport_id)
|
||||
db.session.query(func.count(Workout.sport_id))
|
||||
.group_by(Workout.sport_id)
|
||||
.count()
|
||||
)
|
||||
return {
|
||||
'status': 'success',
|
||||
'data': {
|
||||
'activities': nb_activities,
|
||||
'workouts': nb_workouts,
|
||||
'sports': nb_sports,
|
||||
'users': nb_users,
|
||||
'uploads_dir_size': get_upload_dir_size(),
|
417
fittrackee/workouts/utils.py
Normal file
417
fittrackee/workouts/utils.py
Normal file
@ -0,0 +1,417 @@
|
||||
import hashlib
|
||||
import os
|
||||
import tempfile
|
||||
import zipfile
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Optional, Tuple, Union
|
||||
from uuid import UUID
|
||||
|
||||
import gpxpy.gpx
|
||||
import pytz
|
||||
from fittrackee import appLog, db
|
||||
from flask import current_app
|
||||
from sqlalchemy import exc
|
||||
from staticmap import Line, StaticMap
|
||||
from werkzeug.datastructures import FileStorage
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from ..users.models import User
|
||||
from .models import Sport, Workout, WorkoutSegment
|
||||
from .utils_files import get_absolute_file_path
|
||||
from .utils_gpx import get_gpx_info
|
||||
|
||||
|
||||
class WorkoutException(Exception):
|
||||
def __init__(
|
||||
self, status: str, message: str, e: Optional[Exception] = None
|
||||
) -> None:
|
||||
self.status = status
|
||||
self.message = message
|
||||
self.e = e
|
||||
|
||||
|
||||
def get_datetime_with_tz(
|
||||
timezone: str, workout_date: datetime, gpx_data: Optional[Dict] = None
|
||||
) -> Tuple[Optional[datetime], datetime]:
|
||||
"""
|
||||
Return naive datetime and datetime with user timezone
|
||||
"""
|
||||
workout_date_tz = None
|
||||
if timezone:
|
||||
user_tz = pytz.timezone(timezone)
|
||||
utc_tz = pytz.utc
|
||||
if gpx_data:
|
||||
# workout date in gpx is in UTC, but in naive datetime
|
||||
fmt = '%Y-%m-%d %H:%M:%S'
|
||||
workout_date_string = workout_date.strftime(fmt)
|
||||
workout_date_tmp = utc_tz.localize(
|
||||
datetime.strptime(workout_date_string, fmt)
|
||||
)
|
||||
workout_date_tz = workout_date_tmp.astimezone(user_tz)
|
||||
else:
|
||||
workout_date_tz = user_tz.localize(workout_date)
|
||||
workout_date = workout_date_tz.astimezone(utc_tz)
|
||||
# make datetime 'naive' like in gpx file
|
||||
workout_date = workout_date.replace(tzinfo=None)
|
||||
|
||||
return workout_date_tz, workout_date
|
||||
|
||||
|
||||
def update_workout_data(
|
||||
workout: Union[Workout, WorkoutSegment], gpx_data: Dict
|
||||
) -> Union[Workout, WorkoutSegment]:
|
||||
"""
|
||||
Update workout or workout segment with data from gpx file
|
||||
"""
|
||||
workout.pauses = gpx_data['stop_time']
|
||||
workout.moving = gpx_data['moving_time']
|
||||
workout.min_alt = gpx_data['elevation_min']
|
||||
workout.max_alt = gpx_data['elevation_max']
|
||||
workout.descent = gpx_data['downhill']
|
||||
workout.ascent = gpx_data['uphill']
|
||||
workout.max_speed = gpx_data['max_speed']
|
||||
workout.ave_speed = gpx_data['average_speed']
|
||||
return workout
|
||||
|
||||
|
||||
def create_workout(
|
||||
user: User, workout_data: Dict, gpx_data: Optional[Dict] = None
|
||||
) -> Workout:
|
||||
"""
|
||||
Create Workout from data entered by user and from gpx if a gpx file is
|
||||
provided
|
||||
"""
|
||||
workout_date = (
|
||||
gpx_data['start']
|
||||
if gpx_data
|
||||
else datetime.strptime(workout_data['workout_date'], '%Y-%m-%d %H:%M')
|
||||
)
|
||||
workout_date_tz, workout_date = get_datetime_with_tz(
|
||||
user.timezone, workout_date, gpx_data
|
||||
)
|
||||
|
||||
duration = (
|
||||
gpx_data['duration']
|
||||
if gpx_data
|
||||
else timedelta(seconds=workout_data['duration'])
|
||||
)
|
||||
distance = gpx_data['distance'] if gpx_data else workout_data['distance']
|
||||
title = gpx_data['name'] if gpx_data else workout_data.get('title', '')
|
||||
|
||||
new_workout = Workout(
|
||||
user_id=user.id,
|
||||
sport_id=workout_data['sport_id'],
|
||||
workout_date=workout_date,
|
||||
distance=distance,
|
||||
duration=duration,
|
||||
)
|
||||
new_workout.notes = workout_data.get('notes')
|
||||
|
||||
if title is not None and title != '':
|
||||
new_workout.title = title
|
||||
else:
|
||||
sport = Sport.query.filter_by(id=new_workout.sport_id).first()
|
||||
fmt = "%Y-%m-%d %H:%M:%S"
|
||||
workout_datetime = (
|
||||
workout_date_tz.strftime(fmt)
|
||||
if workout_date_tz
|
||||
else new_workout.workout_date.strftime(fmt)
|
||||
)
|
||||
new_workout.title = f'{sport.label} - {workout_datetime}'
|
||||
|
||||
if gpx_data:
|
||||
new_workout.gpx = gpx_data['filename']
|
||||
new_workout.bounds = gpx_data['bounds']
|
||||
update_workout_data(new_workout, gpx_data)
|
||||
else:
|
||||
new_workout.moving = duration
|
||||
new_workout.ave_speed = (
|
||||
None
|
||||
if duration.seconds == 0
|
||||
else float(new_workout.distance) / (duration.seconds / 3600)
|
||||
)
|
||||
new_workout.max_speed = new_workout.ave_speed
|
||||
return new_workout
|
||||
|
||||
|
||||
def create_segment(
|
||||
workout_id: int, workout_uuid: UUID, segment_data: Dict
|
||||
) -> WorkoutSegment:
|
||||
"""
|
||||
Create Workout Segment from gpx data
|
||||
"""
|
||||
new_segment = WorkoutSegment(
|
||||
workout_id=workout_id,
|
||||
workout_uuid=workout_uuid,
|
||||
segment_id=segment_data['idx'],
|
||||
)
|
||||
new_segment.duration = segment_data['duration']
|
||||
new_segment.distance = segment_data['distance']
|
||||
update_workout_data(new_segment, segment_data)
|
||||
return new_segment
|
||||
|
||||
|
||||
def update_workout(workout: Workout) -> Workout:
|
||||
"""
|
||||
Update workout data from gpx file
|
||||
"""
|
||||
gpx_data, _, _ = get_gpx_info(
|
||||
get_absolute_file_path(workout.gpx), False, False
|
||||
)
|
||||
updated_workout = update_workout_data(workout, gpx_data)
|
||||
updated_workout.duration = gpx_data['duration']
|
||||
updated_workout.distance = gpx_data['distance']
|
||||
db.session.flush()
|
||||
|
||||
for segment_idx, segment in enumerate(updated_workout.segments):
|
||||
segment_data = gpx_data['segments'][segment_idx]
|
||||
updated_segment = update_workout_data(segment, segment_data)
|
||||
updated_segment.duration = segment_data['duration']
|
||||
updated_segment.distance = segment_data['distance']
|
||||
db.session.flush()
|
||||
|
||||
return updated_workout
|
||||
|
||||
|
||||
def edit_workout(
|
||||
workout: Workout, workout_data: Dict, auth_user_id: int
|
||||
) -> Workout:
|
||||
"""
|
||||
Edit an workout
|
||||
Note: the gpx file is NOT modified
|
||||
|
||||
In a next version, map_data and weather_data will be updated
|
||||
(case of a modified gpx file, see issue #7)
|
||||
"""
|
||||
user = User.query.filter_by(id=auth_user_id).first()
|
||||
if workout_data.get('refresh'):
|
||||
workout = update_workout(workout)
|
||||
if workout_data.get('sport_id'):
|
||||
workout.sport_id = workout_data.get('sport_id')
|
||||
if workout_data.get('title'):
|
||||
workout.title = workout_data.get('title')
|
||||
if workout_data.get('notes'):
|
||||
workout.notes = workout_data.get('notes')
|
||||
if not workout.gpx:
|
||||
if workout_data.get('workout_date'):
|
||||
workout_date = datetime.strptime(
|
||||
workout_data['workout_date'], '%Y-%m-%d %H:%M'
|
||||
)
|
||||
_, workout.workout_date = get_datetime_with_tz(
|
||||
user.timezone, workout_date
|
||||
)
|
||||
|
||||
if workout_data.get('duration'):
|
||||
workout.duration = timedelta(seconds=workout_data['duration'])
|
||||
workout.moving = workout.duration
|
||||
|
||||
if workout_data.get('distance'):
|
||||
workout.distance = workout_data['distance']
|
||||
|
||||
workout.ave_speed = (
|
||||
None
|
||||
if workout.duration.seconds == 0
|
||||
else float(workout.distance) / (workout.duration.seconds / 3600)
|
||||
)
|
||||
workout.max_speed = workout.ave_speed
|
||||
return workout
|
||||
|
||||
|
||||
def get_file_path(dir_path: str, filename: str) -> str:
|
||||
"""
|
||||
Get full path for a file
|
||||
"""
|
||||
if not os.path.exists(dir_path):
|
||||
os.makedirs(dir_path)
|
||||
file_path = os.path.join(dir_path, filename)
|
||||
return file_path
|
||||
|
||||
|
||||
def get_new_file_path(
|
||||
auth_user_id: int,
|
||||
workout_date: str,
|
||||
sport: str,
|
||||
old_filename: Optional[str] = None,
|
||||
extension: Optional[str] = None,
|
||||
) -> str:
|
||||
"""
|
||||
Generate a file path from user and workout data
|
||||
"""
|
||||
if not extension and old_filename:
|
||||
extension = f".{old_filename.rsplit('.', 1)[1].lower()}"
|
||||
_, new_filename = tempfile.mkstemp(
|
||||
prefix=f'{workout_date}_{sport}_', suffix=extension
|
||||
)
|
||||
dir_path = os.path.join('workouts', str(auth_user_id))
|
||||
if not os.path.exists(dir_path):
|
||||
os.makedirs(dir_path)
|
||||
file_path = os.path.join(dir_path, new_filename.split('/')[-1])
|
||||
return file_path
|
||||
|
||||
|
||||
def generate_map(map_filepath: str, map_data: List) -> None:
|
||||
"""
|
||||
Generate and save map image from map data
|
||||
"""
|
||||
m = StaticMap(400, 225, 10)
|
||||
line = Line(map_data, '#3388FF', 4)
|
||||
m.add_line(line)
|
||||
image = m.render()
|
||||
image.save(map_filepath)
|
||||
|
||||
|
||||
def get_map_hash(map_filepath: str) -> str:
|
||||
"""
|
||||
Generate a md5 hash used as id instead of workout id, to retrieve map
|
||||
image (maps are sensitive data)
|
||||
"""
|
||||
md5 = hashlib.md5()
|
||||
absolute_map_filepath = get_absolute_file_path(map_filepath)
|
||||
with open(absolute_map_filepath, 'rb') as f:
|
||||
for chunk in iter(lambda: f.read(128 * md5.block_size), b''):
|
||||
md5.update(chunk)
|
||||
return md5.hexdigest()
|
||||
|
||||
|
||||
def process_one_gpx_file(params: Dict, filename: str) -> Workout:
|
||||
"""
|
||||
Get all data from a gpx file to create an workout with map image
|
||||
"""
|
||||
try:
|
||||
gpx_data, map_data, weather_data = get_gpx_info(params['file_path'])
|
||||
auth_user_id = params['user'].id
|
||||
new_filepath = get_new_file_path(
|
||||
auth_user_id=auth_user_id,
|
||||
workout_date=gpx_data['start'],
|
||||
old_filename=filename,
|
||||
sport=params['sport_label'],
|
||||
)
|
||||
absolute_gpx_filepath = get_absolute_file_path(new_filepath)
|
||||
os.rename(params['file_path'], absolute_gpx_filepath)
|
||||
gpx_data['filename'] = new_filepath
|
||||
|
||||
map_filepath = get_new_file_path(
|
||||
auth_user_id=auth_user_id,
|
||||
workout_date=gpx_data['start'],
|
||||
extension='.png',
|
||||
sport=params['sport_label'],
|
||||
)
|
||||
absolute_map_filepath = get_absolute_file_path(map_filepath)
|
||||
generate_map(absolute_map_filepath, map_data)
|
||||
except (gpxpy.gpx.GPXXMLSyntaxException, TypeError) as e:
|
||||
raise WorkoutException('error', 'Error during gpx file parsing.', e)
|
||||
except Exception as e:
|
||||
raise WorkoutException('error', 'Error during gpx processing.', e)
|
||||
|
||||
try:
|
||||
new_workout = create_workout(
|
||||
params['user'], params['workout_data'], gpx_data
|
||||
)
|
||||
new_workout.map = map_filepath
|
||||
new_workout.map_id = get_map_hash(map_filepath)
|
||||
new_workout.weather_start = weather_data[0]
|
||||
new_workout.weather_end = weather_data[1]
|
||||
db.session.add(new_workout)
|
||||
db.session.flush()
|
||||
|
||||
for segment_data in gpx_data['segments']:
|
||||
new_segment = create_segment(
|
||||
new_workout.id, new_workout.uuid, segment_data
|
||||
)
|
||||
db.session.add(new_segment)
|
||||
db.session.commit()
|
||||
return new_workout
|
||||
except (exc.IntegrityError, ValueError) as e:
|
||||
raise WorkoutException('fail', 'Error during workout save.', e)
|
||||
|
||||
|
||||
def process_zip_archive(common_params: Dict, extract_dir: str) -> List:
|
||||
"""
|
||||
Get files from a zip archive and create workouts, if number of files
|
||||
does not exceed defined limit.
|
||||
"""
|
||||
with zipfile.ZipFile(common_params['file_path'], "r") as zip_ref:
|
||||
zip_ref.extractall(extract_dir)
|
||||
|
||||
new_workouts = []
|
||||
gpx_files_limit = os.getenv('REACT_APP_GPX_LIMIT_IMPORT', 10)
|
||||
if (
|
||||
gpx_files_limit
|
||||
and isinstance(gpx_files_limit, str)
|
||||
and gpx_files_limit.isdigit()
|
||||
):
|
||||
gpx_files_limit = int(gpx_files_limit)
|
||||
else:
|
||||
gpx_files_limit = 10
|
||||
appLog.warning('GPX limit not configured, set to 10.')
|
||||
gpx_files_ok = 0
|
||||
|
||||
for gpx_file in os.listdir(extract_dir):
|
||||
if (
|
||||
'.' in gpx_file
|
||||
and gpx_file.rsplit('.', 1)[1].lower()
|
||||
in current_app.config['WORKOUT_ALLOWED_EXTENSIONS']
|
||||
):
|
||||
gpx_files_ok += 1
|
||||
if gpx_files_ok > gpx_files_limit:
|
||||
break
|
||||
file_path = os.path.join(extract_dir, gpx_file)
|
||||
params = common_params
|
||||
params['file_path'] = file_path
|
||||
new_workout = process_one_gpx_file(params, gpx_file)
|
||||
new_workouts.append(new_workout)
|
||||
|
||||
return new_workouts
|
||||
|
||||
|
||||
def process_files(
|
||||
auth_user_id: int,
|
||||
workout_data: Dict,
|
||||
workout_file: FileStorage,
|
||||
folders: Dict,
|
||||
) -> List:
|
||||
"""
|
||||
Store gpx file or zip archive and create workouts
|
||||
"""
|
||||
if workout_file.filename is None:
|
||||
raise WorkoutException('error', 'File has no filename.')
|
||||
filename = secure_filename(workout_file.filename)
|
||||
extension = f".{filename.rsplit('.', 1)[1].lower()}"
|
||||
file_path = get_file_path(folders['tmp_dir'], filename)
|
||||
sport = Sport.query.filter_by(id=workout_data.get('sport_id')).first()
|
||||
if not sport:
|
||||
raise WorkoutException(
|
||||
'error',
|
||||
f"Sport id: {workout_data.get('sport_id')} does not exist",
|
||||
)
|
||||
user = User.query.filter_by(id=auth_user_id).first()
|
||||
|
||||
common_params = {
|
||||
'user': user,
|
||||
'workout_data': workout_data,
|
||||
'file_path': file_path,
|
||||
'sport_label': sport.label,
|
||||
}
|
||||
|
||||
try:
|
||||
workout_file.save(file_path)
|
||||
except Exception as e:
|
||||
raise WorkoutException('error', 'Error during workout file save.', e)
|
||||
|
||||
if extension == ".gpx":
|
||||
return [process_one_gpx_file(common_params, filename)]
|
||||
else:
|
||||
return process_zip_archive(common_params, folders['extract_dir'])
|
||||
|
||||
|
||||
def get_upload_dir_size() -> int:
|
||||
"""
|
||||
Return upload directory size
|
||||
"""
|
||||
upload_path = get_absolute_file_path('')
|
||||
total_size = 0
|
||||
for dir_path, _, filenames in os.walk(upload_path):
|
||||
for f in filenames:
|
||||
fp = os.path.join(dir_path, f)
|
||||
total_size += os.path.getsize(fp)
|
||||
return total_size
|
@ -6,7 +6,7 @@ import gpxpy.gpx
|
||||
from .utils_weather import get_weather
|
||||
|
||||
|
||||
class ActivityGPXException(Exception):
|
||||
class WorkoutGPXException(Exception):
|
||||
def __init__(
|
||||
self, status: str, message: str, e: Optional[Exception] = None
|
||||
) -> None:
|
||||
@ -74,7 +74,7 @@ def get_gpx_info(
|
||||
"""
|
||||
gpx = open_gpx_file(gpx_file)
|
||||
if gpx is None:
|
||||
raise ActivityGPXException('not found', 'No gpx file')
|
||||
raise WorkoutGPXException('not found', 'No gpx file')
|
||||
|
||||
gpx_data = {'name': gpx.tracks[0].name, 'segments': []}
|
||||
max_speed = 0
|
||||
@ -153,11 +153,11 @@ def get_gpx_segments(
|
||||
if segment_id is not None:
|
||||
segment_index = segment_id - 1
|
||||
if segment_index > (len(track_segments) - 1):
|
||||
raise ActivityGPXException(
|
||||
raise WorkoutGPXException(
|
||||
'not found', f'No segment with id \'{segment_id}\'', None
|
||||
)
|
||||
if segment_index < 0:
|
||||
raise ActivityGPXException('error', 'Incorrect segment id', None)
|
||||
raise WorkoutGPXException('error', 'Incorrect segment id', None)
|
||||
segments = [track_segments[segment_index]]
|
||||
else:
|
||||
segments = track_segments
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user