Merge branch 'release-v0.6.2'

This commit is contained in:
Sam 2022-04-03 19:47:55 +02:00
commit de9eac0a92
83 changed files with 1361 additions and 1125 deletions

View File

@ -1,5 +1,17 @@
# Change log
## Version 0.6.2 (2022/04/03)
### Issues Closed
#### Bugs Fixed
* [#175](https://github.com/SamR1/FitTrackee/issues/175) - Distance card on dashboard is not refreshed
* [#173](https://github.com/SamR1/FitTrackee/issues/173) - link to user profile in workout card is incorrect
In this release 2 issues were closed.
## Version 0.6.1 (2022/03/27)
### Issues Closed

View File

@ -3,14 +3,14 @@
[![PyPI version](https://img.shields.io/pypi/v/fittrackee.svg)](https://pypi.org/project/fittrackee/)
[![Python Version](https://img.shields.io/badge/python-3.7+-brightgreen.svg)](https://python.org)
[![Flask Version](https://img.shields.io/badge/flask-2.0-brightgreen.svg)](http://flask.pocoo.org/)
[![Flask Version](https://img.shields.io/badge/flask-2.1-brightgreen.svg)](http://flask.pocoo.org/)
[![code style: black](https://img.shields.io/badge/code%20style-black-black)](https://github.com/psf/black)
[![type check: mypy](https://img.shields.io/badge/type%20check-mypy-blue)](http://mypy-lang.org/)
[![Vue Version](https://img.shields.io/badge/vue-3.2-brightgreen.svg)](https://v3.vuejs.org/)
[![Typescript Version](https://img.shields.io/npm/types/typescript)](https://www.typescriptlang.org/)
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier)
![pipeline status](https://github.com/SamR1/FitTrackee/actions/workflows/.tests-python.yml/badge.svg?branch=master)
![pipeline status](https://github.com/SamR1/FitTrackee/actions/workflows/.tests-javascript.yml/badge.svg?branch=master)
[![pipeline status](https://github.com/SamR1/FitTrackee/actions/workflows/.tests-python.yml/badge.svg?branch=master)](https://github.com/SamR1/FitTrackee/actions/workflows/.tests-python.yml)
[![pipeline status](https://github.com/SamR1/FitTrackee/actions/workflows/.tests-javascript.yml/badge.svg?branch=master)](https://github.com/SamR1/FitTrackee/actions/workflows/.tests-javascript.yml)
---

View File

@ -1 +1 @@
0.6.1
0.6.2

View File

@ -1,4 +1,4 @@
# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: 61330c9d18ff48d9802f7a0c5ced8d16
config: 2ca26683378198d52362ecd6dd5b71fe
tags: 645f666f9bcd5a90fca523b33c5a78b7

Binary file not shown.

Before

Width:  |  Height:  |  Size: 538 KiB

After

Width:  |  Height:  |  Size: 539 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 368 KiB

After

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 103 KiB

View File

@ -1,5 +1,17 @@
# Change log
## Version 0.6.2 (2022/04/03)
### Issues Closed
#### Bugs Fixed
* [#175](https://github.com/SamR1/FitTrackee/issues/175) - Distance card on dashboard is not refreshed
* [#173](https://github.com/SamR1/FitTrackee/issues/173) - link to user profile in workout card is incorrect
In this release 2 issues were closed.
## Version 0.6.1 (2022/03/27)
### Issues Closed

View File

@ -369,13 +369,13 @@ Production environment
.. warning::
| Note that FitTrackee is under heavy development, some features may be unstable.
- Download the last release (for now, it is the release v0.6.1):
- Download the last release (for now, it is the release v0.6.2):
.. code:: bash
$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.1.tar.gz
$ tar -xzf v0.6.1.tar.gz
$ mv FitTrackee-0.6.1 FitTrackee
$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.2.tar.gz
$ tar -xzf v0.6.2.tar.gz
$ mv FitTrackee-0.6.2 FitTrackee
$ cd FitTrackee
- Create **.env** from example and update it
@ -493,13 +493,13 @@ Prod environment
- Change to the directory where FitTrackee directory is located
- Download the last release (for now, it is the release v0.6.1) and overwrite existing files:
- Download the last release (for now, it is the release v0.6.2) and overwrite existing files:
.. code:: bash
$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.1.tar.gz
$ tar -xzf v0.6.1.tar.gz
$ cp -R FitTrackee-0.6.1/* FitTrackee/
$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.2.tar.gz
$ tar -xzf v0.6.2.tar.gz
$ cp -R FitTrackee-0.6.2/* FitTrackee/
$ cd FitTrackee
- Update **.env** if needed (see `Environment variables <installation.html#environment-variables>`__).

View File

@ -154,9 +154,7 @@ var Documentation = {
this.fixFirefoxAnchorBug();
this.highlightSearchWords();
this.initIndexTable();
if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) {
this.initOnKeyListeners();
}
this.initOnKeyListeners();
},
/**
@ -269,6 +267,13 @@ var Documentation = {
window.history.replaceState({}, '', url);
},
/**
* helper function to focus on search bar
*/
focusSearchBar : function() {
$('input[name=q]').first().focus();
},
/**
* make the url absolute
*/
@ -291,27 +296,54 @@ var Documentation = {
},
initOnKeyListeners: function() {
// only install a listener if it is really needed
if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS &&
!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS)
return;
$(document).keydown(function(event) {
var activeElementType = document.activeElement.tagName;
// don't navigate when in search box, textarea, dropdown or button
if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT'
&& activeElementType !== 'BUTTON' && !event.altKey && !event.ctrlKey && !event.metaKey
&& !event.shiftKey) {
switch (event.keyCode) {
case 37: // left
var prevHref = $('link[rel="prev"]').prop('href');
if (prevHref) {
window.location.href = prevHref;
return false;
}
break;
case 39: // right
var nextHref = $('link[rel="next"]').prop('href');
if (nextHref) {
window.location.href = nextHref;
return false;
}
break;
&& activeElementType !== 'BUTTON') {
if (event.altKey || event.ctrlKey || event.metaKey)
return;
if (!event.shiftKey) {
switch (event.key) {
case 'ArrowLeft':
if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS)
break;
var prevHref = $('link[rel="prev"]').prop('href');
if (prevHref) {
window.location.href = prevHref;
return false;
}
break;
case 'ArrowRight':
if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS)
break;
var nextHref = $('link[rel="next"]').prop('href');
if (nextHref) {
window.location.href = nextHref;
return false;
}
break;
case 'Escape':
if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS)
break;
Documentation.hideSearchWords();
return false;
}
}
// some keyboard layouts may need Shift to get /
switch (event.key) {
case '/':
if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS)
break;
Documentation.focusSearchBar();
return false;
}
}
});

View File

@ -1,6 +1,6 @@
var DOCUMENTATION_OPTIONS = {
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
VERSION: '0.6.1',
VERSION: '0.6.2',
LANGUAGE: 'None',
COLLAPSE_INDEX: false,
BUILDER: 'html',
@ -8,5 +8,7 @@ var DOCUMENTATION_OPTIONS = {
LINK_SUFFIX: '.html',
HAS_SOURCE: true,
SOURCELINK_SUFFIX: '.txt',
NAVIGATION_WITH_KEYS: false
NAVIGATION_WITH_KEYS: false,
SHOW_SEARCH_SUMMARY: true,
ENABLE_SEARCH_SHORTCUTS: true,
};

View File

@ -172,10 +172,6 @@ var Search = {
}
// stem the word
var word = stemmer.stemWord(tmp[i].toLowerCase());
// prevent stemmer from cutting word smaller than two chars
if(word.length < 3 && tmp[i].length >= 3) {
word = tmp[i];
}
var toAppend;
// select the correct list
if (word[0] == '-') {
@ -276,7 +272,7 @@ var Search = {
setTimeout(function() {
displayNextItem();
}, 5);
} else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) {
} else if (DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY) {
$.ajax({url: requestUrl,
dataType: "text",
complete: function(jqxhr, textstatus) {
@ -293,7 +289,7 @@ var Search = {
}, 5);
}});
} else {
// no source available, just display title
// just display title
Search.output.append(listItem);
setTimeout(function() {
displayNextItem();

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>Authentication &#8212; FitTrackee 0.6.1
<title>Authentication &#8212; FitTrackee 0.6.2
documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="../index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.1
<span class="navbar-text navbar-version pull-left"><b>0.6.2
</b></span>
</div>
@ -628,8 +628,9 @@ character “_” allowed</p></li>
<dt class="field-odd">Request JSON Object</dt>
<dd class="field-odd"><ul class="simple">
<li><p><strong>timezone</strong> (<em>string</em>) user time zone</p></li>
<li><p><strong>weekm</strong> (<em>string</em>) does week start on Monday?</p></li>
<li><p><strong>weekm</strong> (<em>boolean</em>) does week start on Monday?</p></li>
<li><p><strong>language</strong> (<em>string</em>) language preferences</p></li>
<li><p><strong>imperial_units</strong> (<em>boolean</em>) display distance in imperial units</p></li>
</ul>
</dd>
<dt class="field-even">Request Headers</dt>
@ -1103,7 +1104,7 @@ character “_” allowed</p></li>
</p>
<p>
&copy; Copyright 2018 - 2022, SamR1.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
</p>
</div>
</footer>

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>Configuration &#8212; FitTrackee 0.6.1
<title>Configuration &#8212; FitTrackee 0.6.2
documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="../index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.1
<span class="navbar-text navbar-version pull-left"><b>0.6.2
</b></span>
</div>
@ -142,13 +142,14 @@
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;data&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;admin_contact&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;admin@example.com&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;gpx_limit_import&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;is_registration_enabled&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;max_single_file_size&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">1048576</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;max_zip_file_size&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">10485760</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;max_users&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;max_zip_file_size&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">10485760</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;map_attribution&quot;</span><span class="p">:</span><span class="w"> </span><span class="nt">&quot;&amp;copy; &lt;a href=http://www.openstreetmap.org/copyright&gt;OpenStreetMap&lt;/a&gt; contributors&quot;</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;version&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;0.6.1&quot;</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;version&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;0.6.2&quot;</span><span class="w"></span>
<span class="w"> </span><span class="p">},</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;success&quot;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
@ -182,10 +183,12 @@
<span class="w"> </span><span class="nt">&quot;data&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;admin_contact&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;admin@example.com&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;gpx_limit_import&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;is_registration_enabled&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;is_registration_enabled&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;max_single_file_size&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">1048576</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;max_users&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;max_zip_file_size&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">10485760</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;max_users&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;map_attribution&quot;</span><span class="p">:</span><span class="w"> </span><span class="nt">&quot;&amp;copy; &lt;a href=http://www.openstreetmap.org/copyright&gt;OpenStreetMap&lt;/a&gt; contributors&quot;</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;version&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;0.6.2&quot;</span><span class="w"></span>
<span class="w"> </span><span class="p">},</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;success&quot;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
@ -198,8 +201,8 @@
<li><p><strong>gpx_limit_import</strong> (<em>integer</em>) max number of files in zip archive</p></li>
<li><p><strong>is_registration_enabled</strong> (<em>boolean</em>) is registration enabled ?</p></li>
<li><p><strong>max_single_file_size</strong> (<em>integer</em>) max size of a single file</p></li>
<li><p><strong>max_zip_file_size</strong> (<em>integer</em>) max size of a zip archive</p></li>
<li><p><strong>max_users</strong> (<em>integer</em>) max users allowed to register on instance</p></li>
<li><p><strong>max_zip_file_size</strong> (<em>integer</em>) max size of a zip archive</p></li>
</ul>
</dd>
<dt class="field-even">Request Headers</dt>
@ -268,7 +271,7 @@
</p>
<p>
&copy; Copyright 2018 - 2022, SamR1.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
</p>
</div>
</footer>

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>API documentation &#8212; FitTrackee 0.6.1
<title>API documentation &#8212; FitTrackee 0.6.2
documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="../index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.1
<span class="navbar-text navbar-version pull-left"><b>0.6.2
</b></span>
</div>
@ -154,7 +154,7 @@
</p>
<p>
&copy; Copyright 2018 - 2022, SamR1.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
</p>
</div>
</footer>

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>Records &#8212; FitTrackee 0.6.1
<title>Records &#8212; FitTrackee 0.6.2
documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="../index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.1
<span class="navbar-text navbar-version pull-left"><b>0.6.2
</b></span>
</div>
@ -246,7 +246,7 @@
</p>
<p>
&copy; Copyright 2018 - 2022, SamR1.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
</p>
</div>
</footer>

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>Sports &#8212; FitTrackee 0.6.1
<title>Sports &#8212; FitTrackee 0.6.2
documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="../index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.1
<span class="navbar-text navbar-version pull-left"><b>0.6.2
</b></span>
</div>
@ -484,7 +484,7 @@ Authenticated user must be an admin</p>
</p>
<p>
&copy; Copyright 2018 - 2022, SamR1.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
</p>
</div>
</footer>

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>Statistics &#8212; FitTrackee 0.6.1
<title>Statistics &#8212; FitTrackee 0.6.2
documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="../index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.1
<span class="navbar-text navbar-version pull-left"><b>0.6.2
</b></span>
</div>
@ -411,7 +411,7 @@
</p>
<p>
&copy; Copyright 2018 - 2022, SamR1.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
</p>
</div>
</footer>

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>Users &#8212; FitTrackee 0.6.1
<title>Users &#8212; FitTrackee 0.6.2
documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="../index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.1
<span class="navbar-text navbar-version pull-left"><b>0.6.2
</b></span>
</div>
@ -279,7 +279,8 @@ has admin rights</p>
<dl class="http get">
<dt class="sig sig-object http" id="get--api-users-(user_name)">
<span class="sig-name descname"><span class="pre">GET</span> </span><span class="sig-name descname"><span class="pre">/api/users/</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="pre">user_name</span></em><span class="sig-paren">)</span><a class="headerlink" href="#get--api-users-(user_name)" title="Permalink to this definition"></a></dt>
<dd><p>Get single user details. Only user with admin rights can get user details.</p>
<dd><p>Get single user details. Only user with admin rights can get other users
details.</p>
<p>It returns user preferences only for authenticated user.</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/users/admin</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
@ -624,7 +625,7 @@ one admin</p>
</p>
<p>
&copy; Copyright 2018 - 2022, SamR1.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
</p>
</div>
</footer>

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>Workouts &#8212; FitTrackee 0.6.1
<title>Workouts &#8212; FitTrackee 0.6.2
documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="../index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.1
<span class="navbar-text navbar-version pull-left"><b>0.6.2
</b></span>
</div>
@ -1148,7 +1148,7 @@
</p>
<p>
&copy; Copyright 2018 - 2022, SamR1.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
</p>
</div>
</footer>

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>Change log &#8212; FitTrackee 0.6.1
<title>Change log &#8212; FitTrackee 0.6.2
documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/bootstrap-sphinx.css" />
@ -39,7 +39,7 @@
</button>
<a class="navbar-brand" href="index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.1
<span class="navbar-text navbar-version pull-left"><b>0.6.2
</b></span>
</div>
@ -77,223 +77,230 @@
role="menu"
aria-labelledby="dLabelLocalToc"><ul>
<li><a class="reference internal" href="#">Change log</a><ul>
<li><a class="reference internal" href="#version-0-6-1-2022-03-27">Version 0.6.1 (2022/03/27)</a><ul>
<li><a class="reference internal" href="#version-0-6-2-2022-04-03">Version 0.6.2 (2022/04/03)</a><ul>
<li><a class="reference internal" href="#issues-closed">Issues Closed</a><ul>
<li><a class="reference internal" href="#bugs-fixed">Bugs Fixed</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-6-0-2022-03-27">Version 0.6.0 (2022/03/27)</a><ul>
<li><a class="reference internal" href="#version-0-6-1-2022-03-27">Version 0.6.1 (2022/03/27)</a><ul>
<li><a class="reference internal" href="#id1">Issues Closed</a><ul>
<li><a class="reference internal" href="#features">Features</a></li>
<li><a class="reference internal" href="#id2">Bugs Fixed</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-6-0-2022-03-27">Version 0.6.0 (2022/03/27)</a><ul>
<li><a class="reference internal" href="#id3">Issues Closed</a><ul>
<li><a class="reference internal" href="#features">Features</a></li>
<li><a class="reference internal" href="#id4">Bugs Fixed</a></li>
</ul>
</li>
<li><a class="reference internal" href="#pull-requests">Pull Requests</a><ul>
<li><a class="reference internal" href="#id3">Bugs Fixed</a></li>
<li><a class="reference internal" href="#id5">Bugs Fixed</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-5-7-2022-02-13">Version 0.5.7 (2022/02/13)</a><ul>
<li><a class="reference internal" href="#id4">Issues Closed</a><ul>
<li><a class="reference internal" href="#id6">Issues Closed</a><ul>
<li><a class="reference internal" href="#misc">Misc</a></li>
</ul>
</li>
<li><a class="reference internal" href="#id5">Pull Requests</a><ul>
<li><a class="reference internal" href="#id7">Pull Requests</a><ul>
<li><a class="reference internal" href="#security">Security</a></li>
<li><a class="reference internal" href="#id6">Misc</a></li>
<li><a class="reference internal" href="#id8">Misc</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-5-6-2022-02-05">Version 0.5.6 (2022/02/05)</a><ul>
<li><a class="reference internal" href="#id7">Issues Closed</a><ul>
<li><a class="reference internal" href="#id8">Bugs Fixed</a></li>
<li><a class="reference internal" href="#id9">Issues Closed</a><ul>
<li><a class="reference internal" href="#id10">Bugs Fixed</a></li>
</ul>
</li>
<li><a class="reference internal" href="#id9">Pull Requests</a></li>
<li><a class="reference internal" href="#id11">Pull Requests</a></li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-5-5-2022-01-19">Version 0.5.5 (2022/01/19)</a><ul>
<li><a class="reference internal" href="#id10">Issues Closed</a><ul>
<li><a class="reference internal" href="#new-features">New Features</a></li>
<li><a class="reference internal" href="#id11">Bugs Fixed</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-5-4-2022-01-01">Version 0.5.4 (2022/01/01)</a><ul>
<li><a class="reference internal" href="#id12">Issues Closed</a><ul>
<li><a class="reference internal" href="#new-features">New Features</a></li>
<li><a class="reference internal" href="#id13">Bugs Fixed</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-5-3-2022-01-01">Version 0.5.3 (2022/01/01)</a><ul>
<li><a class="reference internal" href="#version-0-5-4-2022-01-01">Version 0.5.4 (2022/01/01)</a><ul>
<li><a class="reference internal" href="#id14">Issues Closed</a><ul>
<li><a class="reference internal" href="#id15">Bugs Fixed</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-5-2-2021-12-19">Version 0.5.2 (2021/12/19)</a><ul>
<li><a class="reference internal" href="#version-0-5-3-2022-01-01">Version 0.5.3 (2022/01/01)</a><ul>
<li><a class="reference internal" href="#id16">Issues Closed</a><ul>
<li><a class="reference internal" href="#id17">New Features</a></li>
<li><a class="reference internal" href="#id17">Bugs Fixed</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-5-1-2021-11-30">Version 0.5.1 (2021/11/30)</a><ul>
<li><a class="reference internal" href="#version-0-5-2-2021-12-19">Version 0.5.2 (2021/12/19)</a><ul>
<li><a class="reference internal" href="#id18">Issues Closed</a><ul>
<li><a class="reference internal" href="#id19">New Features</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-5-0-2021-11-14">Version 0.5.0 (2021/11/14)</a><ul>
<li><a class="reference internal" href="#version-0-5-1-2021-11-30">Version 0.5.1 (2021/11/30)</a><ul>
<li><a class="reference internal" href="#id20">Issues Closed</a><ul>
<li><a class="reference internal" href="#id21">New Features</a></li>
<li><a class="reference internal" href="#id22">Bugs Fixed</a></li>
<li><a class="reference internal" href="#id23">Misc</a></li>
</ul>
</li>
<li><a class="reference internal" href="#id24">Pull Requests</a></li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-5-0-2021-11-14">Version 0.5.0 (2021/11/14)</a><ul>
<li><a class="reference internal" href="#id22">Issues Closed</a><ul>
<li><a class="reference internal" href="#id23">New Features</a></li>
<li><a class="reference internal" href="#id24">Bugs Fixed</a></li>
<li><a class="reference internal" href="#id25">Misc</a></li>
</ul>
</li>
<li><a class="reference internal" href="#id26">Pull Requests</a></li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-4-9-2021-07-16">Version 0.4.9 (2021/07/16)</a><ul>
<li><a class="reference internal" href="#id25">Issues Closed</a><ul>
<li><a class="reference internal" href="#id26">New Features</a></li>
<li><a class="reference internal" href="#id27">Bugs Fixed</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-4-8-2021-07-03">Version 0.4.8 (2021/07/03)</a><ul>
<li><a class="reference internal" href="#id28">Issues Closed</a><ul>
<li><a class="reference internal" href="#id27">Issues Closed</a><ul>
<li><a class="reference internal" href="#id28">New Features</a></li>
<li><a class="reference internal" href="#id29">Bugs Fixed</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-4-7-2021-04-07">Version 0.4.7 (2021/04/07)</a><ul>
<li><a class="reference internal" href="#version-0-4-8-2021-07-03">Version 0.4.8 (2021/07/03)</a><ul>
<li><a class="reference internal" href="#id30">Issues Closed</a><ul>
<li><a class="reference internal" href="#id31">Bugs Fixed</a></li>
</ul>
</li>
<li><a class="reference internal" href="#id32">Misc</a></li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-4-7-2021-04-07">Version 0.4.7 (2021/04/07)</a><ul>
<li><a class="reference internal" href="#id32">Issues Closed</a><ul>
<li><a class="reference internal" href="#id33">Bugs Fixed</a></li>
</ul>
</li>
<li><a class="reference internal" href="#id34">Misc</a></li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-4-6-2021-02-21">Version 0.4.6 (2021/02/21)</a><ul>
<li><a class="reference internal" href="#id33">Issues Closed</a><ul>
<li><a class="reference internal" href="#id34">Bugs Fixed</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-4-5-2021-02-17">Version 0.4.5 (2021/02/17)</a><ul>
<li><a class="reference internal" href="#id35">Issues Closed</a><ul>
<li><a class="reference internal" href="#id36">Bugs Fixed</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-4-4-2021-01-31">Version 0.4.4 (2021/01/31)</a><ul>
<li><a class="reference internal" href="#version-0-4-5-2021-02-17">Version 0.4.5 (2021/02/17)</a><ul>
<li><a class="reference internal" href="#id37">Issues Closed</a><ul>
<li><a class="reference internal" href="#id38">Bugs Fixed</a></li>
</ul>
</li>
<li><a class="reference internal" href="#id39">Misc</a></li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-4-4-2021-01-31">Version 0.4.4 (2021/01/31)</a><ul>
<li><a class="reference internal" href="#id39">Issues Closed</a><ul>
<li><a class="reference internal" href="#id40">Bugs Fixed</a></li>
</ul>
</li>
<li><a class="reference internal" href="#id41">Misc</a></li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-4-3-2021-01-10">Version 0.4.3 (2021/01/10)</a><ul>
<li><a class="reference internal" href="#id40">Issues Closed</a><ul>
<li><a class="reference internal" href="#id41">New Features</a></li>
<li><a class="reference internal" href="#id42">Bugs Fixed</a></li>
<li><a class="reference internal" href="#id42">Issues Closed</a><ul>
<li><a class="reference internal" href="#id43">New Features</a></li>
<li><a class="reference internal" href="#id44">Bugs Fixed</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-4-2-2021-01-03">Version 0.4.2 (2021/01/03)</a><ul>
<li><a class="reference internal" href="#id43">Misc</a></li>
<li><a class="reference internal" href="#id45">Misc</a></li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-4-1-2020-12-31">Version 0.4.1 (2020/12/31)</a><ul>
<li><a class="reference internal" href="#id44">Issues Closed</a><ul>
<li><a class="reference internal" href="#id45">New Features</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-4-0-fittrackee-on-pypi-2020-09-19">Version 0.4.0 - FitTrackee on PyPI (2020/09/19)</a><ul>
<li><a class="reference internal" href="#id46">Issues Closed</a><ul>
<li><a class="reference internal" href="#id47">New Features</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-3-0-administration-2020-07-15">Version 0.3.0 - Administration (2020/07/15)</a><ul>
<li><a class="reference internal" href="#version-0-4-0-fittrackee-on-pypi-2020-09-19">Version 0.4.0 - FitTrackee on PyPI (2020/09/19)</a><ul>
<li><a class="reference internal" href="#id48">Issues Closed</a><ul>
<li><a class="reference internal" href="#id49">New Features</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-3-0-administration-2020-07-15">Version 0.3.0 - Administration (2020/07/15)</a><ul>
<li><a class="reference internal" href="#id50">Issues Closed</a><ul>
<li><a class="reference internal" href="#id51">New Features</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-2-5-fix-and-improvements-2020-01-31">Version 0.2.5 - Fix and improvements (2020/01/31)</a><ul>
<li><a class="reference internal" href="#id50">Misc</a></li>
<li><a class="reference internal" href="#id52">Misc</a></li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-2-4-minor-fix-2020-01-30">Version 0.2.4 - Minor fix (2020/01/30)</a><ul>
<li><a class="reference internal" href="#id51">Issues Closed</a><ul>
<li><a class="reference internal" href="#id52">Bugs Fixed</a></li>
<li><a class="reference internal" href="#id53">Issues Closed</a><ul>
<li><a class="reference internal" href="#id54">Bugs Fixed</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-2-3-fittrackee-available-in-french-2019-12-29">Version 0.2.3 - FitTrackee available in French (2019/12/29)</a><ul>
<li><a class="reference internal" href="#id53">Issues Closed</a><ul>
<li><a class="reference internal" href="#id54">New Features</a></li>
<li><a class="reference internal" href="#id55">Bugs Fixed</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-2-2-statistics-fix-2019-09-23">Version 0.2.2 - Statistics fix (2019/09/23)</a><ul>
<li><a class="reference internal" href="#id56">Issues Closed</a><ul>
<li><a class="reference internal" href="#id55">Issues Closed</a><ul>
<li><a class="reference internal" href="#id56">New Features</a></li>
<li><a class="reference internal" href="#id57">Bugs Fixed</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-2-1-fix-and-improvements-2019-09-01">Version 0.2.1 - Fix and improvements (2019/09/01)</a><ul>
<li><a class="reference internal" href="#version-0-2-2-statistics-fix-2019-09-23">Version 0.2.2 - Statistics fix (2019/09/23)</a><ul>
<li><a class="reference internal" href="#id58">Issues Closed</a><ul>
<li><a class="reference internal" href="#id59">New Features</a></li>
<li><a class="reference internal" href="#id60">Bugs Fixed</a></li>
<li><a class="reference internal" href="#id59">Bugs Fixed</a></li>
</ul>
</li>
<li><a class="reference internal" href="#id61">Misc</a></li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-2-1-fix-and-improvements-2019-09-01">Version 0.2.1 - Fix and improvements (2019/09/01)</a><ul>
<li><a class="reference internal" href="#id60">Issues Closed</a><ul>
<li><a class="reference internal" href="#id61">New Features</a></li>
<li><a class="reference internal" href="#id62">Bugs Fixed</a></li>
</ul>
</li>
<li><a class="reference internal" href="#id63">Misc</a></li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-2-0-statistics-2019-07-07">Version 0.2.0 - Statistics (2019/07/07)</a><ul>
<li><a class="reference internal" href="#id62">Issues Closed</a><ul>
<li><a class="reference internal" href="#id63">New Features</a></li>
<li><a class="reference internal" href="#id64">Issues Closed</a><ul>
<li><a class="reference internal" href="#id65">New Features</a></li>
</ul>
</li>
<li><a class="reference internal" href="#id64">Misc</a></li>
<li><a class="reference internal" href="#id66">Misc</a></li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-1-1-fix-and-improvements-2019-02-07">Version 0.1.1 - Fix and improvements (2019/02/07)</a><ul>
<li><a class="reference internal" href="#id65">Issues Closed</a><ul>
<li><a class="reference internal" href="#id66">New Features</a></li>
<li><a class="reference internal" href="#id67">Bugs Fixed</a></li>
<li><a class="reference internal" href="#id67">Issues Closed</a><ul>
<li><a class="reference internal" href="#id68">New Features</a></li>
<li><a class="reference internal" href="#id69">Bugs Fixed</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#version-0-1-0-first-release-2018-07-04">Version 0.1.0 - First release 🎉 (2018-07-04)</a><ul>
<li><a class="reference internal" href="#id68">Issues Closed</a><ul>
<li><a class="reference internal" href="#id69">New Features</a></li>
<li><a class="reference internal" href="#id70">Issues Closed</a><ul>
<li><a class="reference internal" href="#id71">New Features</a></li>
</ul>
</li>
</ul>
@ -345,13 +352,27 @@
<section id="change-log">
<h1>Change log<a class="headerlink" href="#change-log" title="Permalink to this headline"></a></h1>
<section id="version-0-6-1-2022-03-27">
<h2>Version 0.6.1 (2022/03/27)<a class="headerlink" href="#version-0-6-1-2022-03-27" title="Permalink to this headline"></a></h2>
<section id="version-0-6-2-2022-04-03">
<h2>Version 0.6.2 (2022/04/03)<a class="headerlink" href="#version-0-6-2-2022-04-03" title="Permalink to this headline"></a></h2>
<section id="issues-closed">
<h3>Issues Closed<a class="headerlink" href="#issues-closed" title="Permalink to this headline"></a></h3>
<section id="bugs-fixed">
<h4>Bugs Fixed<a class="headerlink" href="#bugs-fixed" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/175">#175</a> - Distance card on dashboard is not refreshed</p></li>
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/173">#173</a> - link to user profile in workout card is incorrect</p></li>
</ul>
<p>In this release 2 issues were closed.</p>
</section>
</section>
</section>
<section id="version-0-6-1-2022-03-27">
<h2>Version 0.6.1 (2022/03/27)<a class="headerlink" href="#version-0-6-1-2022-03-27" title="Permalink to this headline"></a></h2>
<section id="id1">
<h3>Issues Closed<a class="headerlink" href="#id1" title="Permalink to this headline"></a></h3>
<section id="id2">
<h4>Bugs Fixed<a class="headerlink" href="#id2" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/171">#171</a> - Stats chart is not updated correctly</p></li>
</ul>
<p>In this release 1 issue was closed.</p>
@ -361,8 +382,8 @@
<section id="version-0-6-0-2022-03-27">
<h2>Version 0.6.0 (2022/03/27)<a class="headerlink" href="#version-0-6-0-2022-03-27" title="Permalink to this headline"></a></h2>
<p>This version introduces some changes on <a class="reference external" href="https://samr1.github.io/FitTrackee/features.html#account-preferences">user registration</a>.<br />From now on, a user needs to confirm his account after registration (an email with confirmation instructions is sent after registration).</p>
<section id="id1">
<h3>Issues Closed<a class="headerlink" href="#id1" title="Permalink to this headline"></a></h3>
<section id="id3">
<h3>Issues Closed<a class="headerlink" href="#id3" title="Permalink to this headline"></a></h3>
<section id="features">
<h4>Features<a class="headerlink" href="#features" title="Permalink to this headline"></a></h4>
<ul class="simple">
@ -370,8 +391,8 @@
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/106">#106</a> - Allow user to update email</p></li>
</ul>
</section>
<section id="id2">
<h4>Bugs Fixed<a class="headerlink" href="#id2" title="Permalink to this headline"></a></h4>
<section id="id4">
<h4>Bugs Fixed<a class="headerlink" href="#id4" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/169">#169</a> - user picture is not refreshed after update</p></li>
</ul>
@ -379,8 +400,8 @@
</section>
<section id="pull-requests">
<h3>Pull Requests<a class="headerlink" href="#pull-requests" title="Permalink to this headline"></a></h3>
<section id="id3">
<h4>Bugs Fixed<a class="headerlink" href="#id3" title="Permalink to this headline"></a></h4>
<section id="id5">
<h4>Bugs Fixed<a class="headerlink" href="#id5" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/pull/161">#161</a> - Minor translation issue on Farthest</p></li>
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/pull/160">#160</a> - Minor translation issue on APP_ERROR</p></li>
@ -395,8 +416,8 @@
<p>This release contains several fixes including security fixes.<br />Thanks to &#64;DanielSiersleben for the report.</p>
<p>And from now on, admin account is not created on application initialization.<br />A new command is added to set administration rights on the account created after registration
(see <a class="reference external" href="https://samr1.github.io/FitTrackee/installation.html#upgrade">documentation</a>)</p>
<section id="id4">
<h3>Issues Closed<a class="headerlink" href="#id4" title="Permalink to this headline"></a></h3>
<section id="id6">
<h3>Issues Closed<a class="headerlink" href="#id6" title="Permalink to this headline"></a></h3>
<section id="misc">
<h4>Misc<a class="headerlink" href="#misc" title="Permalink to this headline"></a></h4>
<ul class="simple">
@ -404,8 +425,8 @@
</ul>
</section>
</section>
<section id="id5">
<h3>Pull Requests<a class="headerlink" href="#id5" title="Permalink to this headline"></a></h3>
<section id="id7">
<h3>Pull Requests<a class="headerlink" href="#id7" title="Permalink to this headline"></a></h3>
<section id="security">
<h4>Security<a class="headerlink" href="#security" title="Permalink to this headline"></a></h4>
<ul class="simple">
@ -423,8 +444,8 @@
</li>
</ul>
</section>
<section id="id6">
<h4>Misc<a class="headerlink" href="#id6" title="Permalink to this headline"></a></h4>
<section id="id8">
<h4>Misc<a class="headerlink" href="#id8" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/pull/152">#152</a> - Fixes and improvements:</p>
<ul>
@ -440,17 +461,17 @@
</section>
<section id="version-0-5-6-2022-02-05">
<h2>Version 0.5.6 (2022/02/05)<a class="headerlink" href="#version-0-5-6-2022-02-05" title="Permalink to this headline"></a></h2>
<section id="id7">
<h3>Issues Closed<a class="headerlink" href="#id7" title="Permalink to this headline"></a></h3>
<section id="id8">
<h4>Bugs Fixed<a class="headerlink" href="#id8" title="Permalink to this headline"></a></h4>
<section id="id9">
<h3>Issues Closed<a class="headerlink" href="#id9" title="Permalink to this headline"></a></h3>
<section id="id10">
<h4>Bugs Fixed<a class="headerlink" href="#id10" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/146">#146</a> - incorrect label on workouts filters</p></li>
</ul>
</section>
</section>
<section id="id9">
<h3>Pull Requests<a class="headerlink" href="#id9" title="Permalink to this headline"></a></h3>
<section id="id11">
<h3>Pull Requests<a class="headerlink" href="#id11" title="Permalink to this headline"></a></h3>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/pull/145">#145</a> - fix on database models</p></li>
</ul>
@ -459,8 +480,8 @@
</section>
<section id="version-0-5-5-2022-01-19">
<h2>Version 0.5.5 (2022/01/19)<a class="headerlink" href="#version-0-5-5-2022-01-19" title="Permalink to this headline"></a></h2>
<section id="id10">
<h3>Issues Closed<a class="headerlink" href="#id10" title="Permalink to this headline"></a></h3>
<section id="id12">
<h3>Issues Closed<a class="headerlink" href="#id12" title="Permalink to this headline"></a></h3>
<section id="new-features">
<h4>New Features<a class="headerlink" href="#new-features" title="Permalink to this headline"></a></h4>
<ul class="simple">
@ -470,8 +491,8 @@
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/134">#134</a> - Wind direction</p></li>
</ul>
</section>
<section id="id11">
<h4>Bugs Fixed<a class="headerlink" href="#id11" title="Permalink to this headline"></a></h4>
<section id="id13">
<h4>Bugs Fixed<a class="headerlink" href="#id13" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/commit/877fa0faaabc0130402638905fe04f84563eb278">877fa0f</a> - fix sport icon color (when changed) on calendar on small resolutions</p></li>
</ul>
@ -481,10 +502,10 @@
</section>
<section id="version-0-5-4-2022-01-01">
<h2>Version 0.5.4 (2022/01/01)<a class="headerlink" href="#version-0-5-4-2022-01-01" title="Permalink to this headline"></a></h2>
<section id="id12">
<h3>Issues Closed<a class="headerlink" href="#id12" title="Permalink to this headline"></a></h3>
<section id="id13">
<h4>Bugs Fixed<a class="headerlink" href="#id13" title="Permalink to this headline"></a></h4>
<section id="id14">
<h3>Issues Closed<a class="headerlink" href="#id14" title="Permalink to this headline"></a></h3>
<section id="id15">
<h4>Bugs Fixed<a class="headerlink" href="#id15" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/131">#131</a> - No workouts displayed on calendar</p></li>
</ul>
@ -494,10 +515,10 @@
</section>
<section id="version-0-5-3-2022-01-01">
<h2>Version 0.5.3 (2022/01/01)<a class="headerlink" href="#version-0-5-3-2022-01-01" title="Permalink to this headline"></a></h2>
<section id="id14">
<h3>Issues Closed<a class="headerlink" href="#id14" title="Permalink to this headline"></a></h3>
<section id="id15">
<h4>Bugs Fixed<a class="headerlink" href="#id15" title="Permalink to this headline"></a></h4>
<section id="id16">
<h3>Issues Closed<a class="headerlink" href="#id16" title="Permalink to this headline"></a></h3>
<section id="id17">
<h4>Bugs Fixed<a class="headerlink" href="#id17" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/129">#129</a> - Display only active sports when editing a workout</p></li>
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/127">#127</a> - parse_email_url() cant validate a legitimate EMAIL_URI such as “smtp://localhost:25”</p></li>
@ -508,10 +529,10 @@
</section>
<section id="version-0-5-2-2021-12-19">
<h2>Version 0.5.2 (2021/12/19)<a class="headerlink" href="#version-0-5-2-2021-12-19" title="Permalink to this headline"></a></h2>
<section id="id16">
<h3>Issues Closed<a class="headerlink" href="#id16" title="Permalink to this headline"></a></h3>
<section id="id17">
<h4>New Features<a class="headerlink" href="#id17" title="Permalink to this headline"></a></h4>
<section id="id18">
<h3>Issues Closed<a class="headerlink" href="#id18" title="Permalink to this headline"></a></h3>
<section id="id19">
<h4>New Features<a class="headerlink" href="#id19" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/123">#123</a> - Allow user to reset preferences for a sport</p></li>
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/121">#121</a> - Add activity : snowshoes</p></li>
@ -522,10 +543,10 @@
</section>
<section id="version-0-5-1-2021-11-30">
<h2>Version 0.5.1 (2021/11/30)<a class="headerlink" href="#version-0-5-1-2021-11-30" title="Permalink to this headline"></a></h2>
<section id="id18">
<h3>Issues Closed<a class="headerlink" href="#id18" title="Permalink to this headline"></a></h3>
<section id="id19">
<h4>New Features<a class="headerlink" href="#id19" title="Permalink to this headline"></a></h4>
<section id="id20">
<h3>Issues Closed<a class="headerlink" href="#id20" title="Permalink to this headline"></a></h3>
<section id="id21">
<h4>New Features<a class="headerlink" href="#id21" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/116">#116</a> - Better UI for Speed and Elevation buttons in the graph of the Workout screen</p></li>
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/115">#115</a> - Add option to download the GPX file of a Workout</p></li>
@ -537,10 +558,10 @@
</section>
<section id="version-0-5-0-2021-11-14">
<h2>Version 0.5.0 (2021/11/14)<a class="headerlink" href="#version-0-5-0-2021-11-14" title="Permalink to this headline"></a></h2>
<section id="id20">
<h3>Issues Closed<a class="headerlink" href="#id20" title="Permalink to this headline"></a></h3>
<section id="id21">
<h4>New Features<a class="headerlink" href="#id21" title="Permalink to this headline"></a></h4>
<section id="id22">
<h3>Issues Closed<a class="headerlink" href="#id22" title="Permalink to this headline"></a></h3>
<section id="id23">
<h4>New Features<a class="headerlink" href="#id23" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/99">#99</a> - Display workout with imperial units</p></li>
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/91">#91</a> - Display elevation chart with min and max altitude of workout</p></li>
@ -548,21 +569,21 @@
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/18">#18</a> - Better UI</p></li>
</ul>
</section>
<section id="id22">
<h4>Bugs Fixed<a class="headerlink" href="#id22" title="Permalink to this headline"></a></h4>
<section id="id24">
<h4>Bugs Fixed<a class="headerlink" href="#id24" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/95">#95</a> - Some workouts seem to be missing on statistics chart</p></li>
</ul>
</section>
<section id="id23">
<h4>Misc<a class="headerlink" href="#id23" title="Permalink to this headline"></a></h4>
<section id="id25">
<h4>Misc<a class="headerlink" href="#id25" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/104">#104</a> - Switch to AGPLv3 license</p></li>
</ul>
</section>
</section>
<section id="id24">
<h3>Pull Requests<a class="headerlink" href="#id24" title="Permalink to this headline"></a></h3>
<section id="id26">
<h3>Pull Requests<a class="headerlink" href="#id26" title="Permalink to this headline"></a></h3>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/pull/101">#101</a> - Docker updates for full files</p></li>
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/pull/100">#100</a> - Add client application in docker for development</p></li>
@ -579,17 +600,17 @@
</section>
<section id="version-0-4-9-2021-07-16">
<h2>Version 0.4.9 (2021/07/16)<a class="headerlink" href="#version-0-4-9-2021-07-16" title="Permalink to this headline"></a></h2>
<section id="id25">
<h3>Issues Closed<a class="headerlink" href="#id25" title="Permalink to this headline"></a></h3>
<section id="id26">
<h4>New Features<a class="headerlink" href="#id26" title="Permalink to this headline"></a></h4>
<section id="id27">
<h3>Issues Closed<a class="headerlink" href="#id27" title="Permalink to this headline"></a></h3>
<section id="id28">
<h4>New Features<a class="headerlink" href="#id28" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/83">#83</a> - allow using configured tile server to generate static maps<br /><strong>Note</strong>: to keep using the default tile server, set environment variable <code class="docutils literal notranslate"><span class="pre">DEFAULT_STATICMAP</span></code> to <code class="docutils literal notranslate"><span class="pre">True</span></code></p></li>
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/81">#81</a> - display remaining characters in textarea</p></li>
</ul>
</section>
<section id="id27">
<h4>Bugs Fixed<a class="headerlink" href="#id27" title="Permalink to this headline"></a></h4>
<section id="id29">
<h4>Bugs Fixed<a class="headerlink" href="#id29" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/82">#82</a> - a user can not modify his birth day</p></li>
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/80">#80</a> - can not save notes with control characters</p></li>
@ -600,10 +621,10 @@
</section>
<section id="version-0-4-8-2021-07-03">
<h2>Version 0.4.8 (2021/07/03)<a class="headerlink" href="#version-0-4-8-2021-07-03" title="Permalink to this headline"></a></h2>
<section id="id28">
<h3>Issues Closed<a class="headerlink" href="#id28" title="Permalink to this headline"></a></h3>
<section id="id29">
<h4>Bugs Fixed<a class="headerlink" href="#id29" title="Permalink to this headline"></a></h4>
<section id="id30">
<h3>Issues Closed<a class="headerlink" href="#id30" title="Permalink to this headline"></a></h3>
<section id="id31">
<h4>Bugs Fixed<a class="headerlink" href="#id31" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/79">#79</a> - Fails to start after make rebuild</p></li>
</ul>
@ -613,17 +634,17 @@
</section>
<section id="version-0-4-7-2021-04-07">
<h2>Version 0.4.7 (2021/04/07)<a class="headerlink" href="#version-0-4-7-2021-04-07" title="Permalink to this headline"></a></h2>
<section id="id30">
<h3>Issues Closed<a class="headerlink" href="#id30" title="Permalink to this headline"></a></h3>
<section id="id31">
<h4>Bugs Fixed<a class="headerlink" href="#id31" title="Permalink to this headline"></a></h4>
<section id="id32">
<h3>Issues Closed<a class="headerlink" href="#id32" title="Permalink to this headline"></a></h3>
<section id="id33">
<h4>Bugs Fixed<a class="headerlink" href="#id33" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/75">#75</a> - Workouts on the same day are not displayed in right order</p></li>
</ul>
</section>
</section>
<section id="id32">
<h3>Misc<a class="headerlink" href="#id32" title="Permalink to this headline"></a></h3>
<section id="id34">
<h3>Misc<a class="headerlink" href="#id34" title="Permalink to this headline"></a></h3>
<ul class="simple">
<li><p>Update Python and Javascript dependencies<br /><strong>IMPORTANT</strong>: Due to <a class="reference external" href="https://docs.sqlalchemy.org/en/14/changelog/changelog_14.html#change-3687655465c25a39b968b4f5f6e9170b">SQLAlchemy update (1.4+)</a>, engine URLs starting with <code class="docutils literal notranslate"><span class="pre">postgres://</span></code> are no longer supported. Please update <code class="docutils literal notranslate"><span class="pre">DATABASE_URL</span></code> with <code class="docutils literal notranslate"><span class="pre">postgresql://</span></code>.</p></li>
</ul>
@ -632,10 +653,10 @@
</section>
<section id="version-0-4-6-2021-02-21">
<h2>Version 0.4.6 (2021/02/21)<a class="headerlink" href="#version-0-4-6-2021-02-21" title="Permalink to this headline"></a></h2>
<section id="id33">
<h3>Issues Closed<a class="headerlink" href="#id33" title="Permalink to this headline"></a></h3>
<section id="id34">
<h4>Bugs Fixed<a class="headerlink" href="#id34" title="Permalink to this headline"></a></h4>
<section id="id35">
<h3>Issues Closed<a class="headerlink" href="#id35" title="Permalink to this headline"></a></h3>
<section id="id36">
<h4>Bugs Fixed<a class="headerlink" href="#id36" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/72">#72</a> - Error message when file exceeding size is incorrect</p></li>
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/71">#71</a> - max size or max number of files must be greater than 0</p></li>
@ -647,10 +668,10 @@
</section>
<section id="version-0-4-5-2021-02-17">
<h2>Version 0.4.5 (2021/02/17)<a class="headerlink" href="#version-0-4-5-2021-02-17" title="Permalink to this headline"></a></h2>
<section id="id35">
<h3>Issues Closed<a class="headerlink" href="#id35" title="Permalink to this headline"></a></h3>
<section id="id36">
<h4>Bugs Fixed<a class="headerlink" href="#id36" title="Permalink to this headline"></a></h4>
<section id="id37">
<h3>Issues Closed<a class="headerlink" href="#id37" title="Permalink to this headline"></a></h3>
<section id="id38">
<h4>Bugs Fixed<a class="headerlink" href="#id38" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/66">#66</a> - invalid gpx limit used when importing zip archive</p></li>
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/64">#64</a> - Only 50 workouts per month shown in calendar</p></li>
@ -661,17 +682,17 @@
</section>
<section id="version-0-4-4-2021-01-31">
<h2>Version 0.4.4 (2021/01/31)<a class="headerlink" href="#version-0-4-4-2021-01-31" title="Permalink to this headline"></a></h2>
<section id="id37">
<h3>Issues Closed<a class="headerlink" href="#id37" title="Permalink to this headline"></a></h3>
<section id="id38">
<h4>Bugs Fixed<a class="headerlink" href="#id38" title="Permalink to this headline"></a></h4>
<section id="id39">
<h3>Issues Closed<a class="headerlink" href="#id39" title="Permalink to this headline"></a></h3>
<section id="id40">
<h4>Bugs Fixed<a class="headerlink" href="#id40" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/62">#62</a> - Error when sending reset password email</p></li>
</ul>
</section>
</section>
<section id="id39">
<h3>Misc<a class="headerlink" href="#id39" title="Permalink to this headline"></a></h3>
<section id="id41">
<h3>Misc<a class="headerlink" href="#id41" title="Permalink to this headline"></a></h3>
<ul class="simple">
<li><p>Refactoring before introducing new features.</p></li>
<li><p>Add docker files for evaluation purposes.</p></li>
@ -681,16 +702,16 @@
</section>
<section id="version-0-4-3-2021-01-10">
<h2>Version 0.4.3 (2021/01/10)<a class="headerlink" href="#version-0-4-3-2021-01-10" title="Permalink to this headline"></a></h2>
<section id="id40">
<h3>Issues Closed<a class="headerlink" href="#id40" title="Permalink to this headline"></a></h3>
<section id="id41">
<h4>New Features<a class="headerlink" href="#id41" title="Permalink to this headline"></a></h4>
<section id="id42">
<h3>Issues Closed<a class="headerlink" href="#id42" title="Permalink to this headline"></a></h3>
<section id="id43">
<h4>New Features<a class="headerlink" href="#id43" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/58">#58</a> - Standardize terms used for workouts<br /><strong>Note:</strong> Database model, upload directory for workouts and API endpoints are also updated.</p></li>
</ul>
</section>
<section id="id42">
<h4>Bugs Fixed<a class="headerlink" href="#id42" title="Permalink to this headline"></a></h4>
<section id="id44">
<h4>Bugs Fixed<a class="headerlink" href="#id44" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/59">#59</a> - No message displayed on uploading image error</p></li>
</ul>
@ -700,18 +721,18 @@
</section>
<section id="version-0-4-2-2021-01-03">
<h2>Version 0.4.2 (2021/01/03)<a class="headerlink" href="#version-0-4-2-2021-01-03" title="Permalink to this headline"></a></h2>
<section id="id43">
<h3>Misc<a class="headerlink" href="#id43" title="Permalink to this headline"></a></h3>
<section id="id45">
<h3>Misc<a class="headerlink" href="#id45" title="Permalink to this headline"></a></h3>
<p>No new features in this release, only some refactorings before introducing
new features.</p>
</section>
</section>
<section id="version-0-4-1-2020-12-31">
<h2>Version 0.4.1 (2020/12/31)<a class="headerlink" href="#version-0-4-1-2020-12-31" title="Permalink to this headline"></a></h2>
<section id="id44">
<h3>Issues Closed<a class="headerlink" href="#id44" title="Permalink to this headline"></a></h3>
<section id="id45">
<h4>New Features<a class="headerlink" href="#id45" title="Permalink to this headline"></a></h4>
<section id="id46">
<h3>Issues Closed<a class="headerlink" href="#id46" title="Permalink to this headline"></a></h3>
<section id="id47">
<h4>New Features<a class="headerlink" href="#id47" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/57">#57</a> - Use uuid for activities</p></li>
</ul>
@ -727,10 +748,10 @@ new features.</p>
<li><p>Its now possible to change the tile provider for maps. The default tile server is now <strong>OpenStreetMap</strong>s standard tile layer (replacing <strong>ThunderForest Outdoors</strong>),
see <a class="reference external" href="https://samr1.github.io/FitTrackee/installation.html#map-tile-server">Map tile server in documentation</a>.</p></li>
</ul>
<section id="id46">
<h3>Issues Closed<a class="headerlink" href="#id46" title="Permalink to this headline"></a></h3>
<section id="id47">
<h4>New Features<a class="headerlink" href="#id47" title="Permalink to this headline"></a></h4>
<section id="id48">
<h3>Issues Closed<a class="headerlink" href="#id48" title="Permalink to this headline"></a></h3>
<section id="id49">
<h4>New Features<a class="headerlink" href="#id49" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/54">#54</a> - Tile server can be changed</p></li>
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/53">#53</a> - Simplify FitTrackee installation</p></li>
@ -746,10 +767,10 @@ see <a class="reference external" href="https://samr1.github.io/FitTrackee/insta
<li><p>FitTrackee administration is now available (see <a class="reference external" href="https://samr1.github.io/FitTrackee/features.html#administration">documentation</a>)<br />⚠️ Warning: some application parameters move from environment variables to database (see <a class="reference external" href="https://samr1.github.io/FitTrackee/installation.html#environment-variables">installation</a>).</p></li>
<li><p>in order to send emails, Redis is now a mandatory dependency</p></li>
</ul>
<section id="id48">
<h3>Issues Closed<a class="headerlink" href="#id48" title="Permalink to this headline"></a></h3>
<section id="id49">
<h4>New Features<a class="headerlink" href="#id49" title="Permalink to this headline"></a></h4>
<section id="id50">
<h3>Issues Closed<a class="headerlink" href="#id50" title="Permalink to this headline"></a></h3>
<section id="id51">
<h4>New Features<a class="headerlink" href="#id51" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/50">#50</a> - A user can reset his password</p></li>
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/17">#17</a> - A user can delete his account</p></li>
@ -761,8 +782,8 @@ see <a class="reference external" href="https://samr1.github.io/FitTrackee/insta
</section>
<section id="version-0-2-5-fix-and-improvements-2020-01-31">
<h2>Version 0.2.5 - Fix and improvements (2020/01/31)<a class="headerlink" href="#version-0-2-5-fix-and-improvements-2020-01-31" title="Permalink to this headline"></a></h2>
<section id="id50">
<h3>Misc<a class="headerlink" href="#id50" title="Permalink to this headline"></a></h3>
<section id="id52">
<h3>Misc<a class="headerlink" href="#id52" title="Permalink to this headline"></a></h3>
<p>This version contains minor fix and improvements on client side:</p>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/commit/4c3fc343d51b9c27d3ebab71df648bcf7d7bae59">4c3fc34</a> - empty user data on logout</p></li>
@ -775,10 +796,10 @@ add URL interceptors to simplify routes definition</p></li>
</section>
<section id="version-0-2-4-minor-fix-2020-01-30">
<h2>Version 0.2.4 - Minor fix (2020/01/30)<a class="headerlink" href="#version-0-2-4-minor-fix-2020-01-30" title="Permalink to this headline"></a></h2>
<section id="id51">
<h3>Issues Closed<a class="headerlink" href="#id51" title="Permalink to this headline"></a></h3>
<section id="id52">
<h4>Bugs Fixed<a class="headerlink" href="#id52" title="Permalink to this headline"></a></h4>
<section id="id53">
<h3>Issues Closed<a class="headerlink" href="#id53" title="Permalink to this headline"></a></h3>
<section id="id54">
<h4>Bugs Fixed<a class="headerlink" href="#id54" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/47">#47</a> - timezone drop-down is not displayed correctly</p></li>
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/46">#46</a> - calendar cannot display more than 5 or 6 activities on the same day</p></li>
@ -789,17 +810,17 @@ add URL interceptors to simplify routes definition</p></li>
</section>
<section id="version-0-2-3-fittrackee-available-in-french-2019-12-29">
<h2>Version 0.2.3 - FitTrackee available in French (2019/12/29)<a class="headerlink" href="#version-0-2-3-fittrackee-available-in-french-2019-12-29" title="Permalink to this headline"></a></h2>
<section id="id53">
<h3>Issues Closed<a class="headerlink" href="#id53" title="Permalink to this headline"></a></h3>
<section id="id54">
<h4>New Features<a class="headerlink" href="#id54" title="Permalink to this headline"></a></h4>
<section id="id55">
<h3>Issues Closed<a class="headerlink" href="#id55" title="Permalink to this headline"></a></h3>
<section id="id56">
<h4>New Features<a class="headerlink" href="#id56" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/43">#43</a> - Display weekend days with a different background color on calendar</p></li>
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/40">#40</a> - Localize FitTrackee (i18n)</p></li>
</ul>
</section>
<section id="id55">
<h4>Bugs Fixed<a class="headerlink" href="#id55" title="Permalink to this headline"></a></h4>
<section id="id57">
<h4>Bugs Fixed<a class="headerlink" href="#id57" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/44">#44</a> - Cannot edit an activity that does not have a gpx file</p></li>
</ul>
@ -809,10 +830,10 @@ add URL interceptors to simplify routes definition</p></li>
</section>
<section id="version-0-2-2-statistics-fix-2019-09-23">
<h2>Version 0.2.2 - Statistics fix (2019/09/23)<a class="headerlink" href="#version-0-2-2-statistics-fix-2019-09-23" title="Permalink to this headline"></a></h2>
<section id="id56">
<h3>Issues Closed<a class="headerlink" href="#id56" title="Permalink to this headline"></a></h3>
<section id="id57">
<h4>Bugs Fixed<a class="headerlink" href="#id57" title="Permalink to this headline"></a></h4>
<section id="id58">
<h3>Issues Closed<a class="headerlink" href="#id58" title="Permalink to this headline"></a></h3>
<section id="id59">
<h4>Bugs Fixed<a class="headerlink" href="#id59" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/41">#41</a> - User statistics are incorrect</p></li>
</ul>
@ -822,10 +843,10 @@ add URL interceptors to simplify routes definition</p></li>
</section>
<section id="version-0-2-1-fix-and-improvements-2019-09-01">
<h2>Version 0.2.1 - Fix and improvements (2019/09/01)<a class="headerlink" href="#version-0-2-1-fix-and-improvements-2019-09-01" title="Permalink to this headline"></a></h2>
<section id="id58">
<h3>Issues Closed<a class="headerlink" href="#id58" title="Permalink to this headline"></a></h3>
<section id="id59">
<h4>New Features<a class="headerlink" href="#id59" title="Permalink to this headline"></a></h4>
<section id="id60">
<h3>Issues Closed<a class="headerlink" href="#id60" title="Permalink to this headline"></a></h3>
<section id="id61">
<h4>New Features<a class="headerlink" href="#id61" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/4">#4</a> - Show points on the map when mouse over the chart</p></li>
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/14">#14</a> - Display segments informations</p></li>
@ -836,15 +857,15 @@ add URL interceptors to simplify routes definition</p></li>
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/37">#37</a> - Display map on activities list</p></li>
</ul>
</section>
<section id="id60">
<h4>Bugs Fixed<a class="headerlink" href="#id60" title="Permalink to this headline"></a></h4>
<section id="id62">
<h4>Bugs Fixed<a class="headerlink" href="#id62" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/34">#34</a> - Weather is not displayed anymore</p></li>
</ul>
</section>
</section>
<section id="id61">
<h3>Misc<a class="headerlink" href="#id61" title="Permalink to this headline"></a></h3>
<section id="id63">
<h3>Misc<a class="headerlink" href="#id63" title="Permalink to this headline"></a></h3>
<ul class="simple">
<li><p><strong><a class="reference external" href="https://poetry.eustace.io/">Poetry</a></strong> replaces <strong><a class="reference external" href="https://docs.pipenv.org">pipenv</a></strong> for Python packages management</p></li>
</ul>
@ -853,17 +874,17 @@ add URL interceptors to simplify routes definition</p></li>
</section>
<section id="version-0-2-0-statistics-2019-07-07">
<h2>Version 0.2.0 - Statistics (2019/07/07)<a class="headerlink" href="#version-0-2-0-statistics-2019-07-07" title="Permalink to this headline"></a></h2>
<section id="id62">
<h3>Issues Closed<a class="headerlink" href="#id62" title="Permalink to this headline"></a></h3>
<section id="id63">
<h4>New Features<a class="headerlink" href="#id63" title="Permalink to this headline"></a></h4>
<section id="id64">
<h3>Issues Closed<a class="headerlink" href="#id64" title="Permalink to this headline"></a></h3>
<section id="id65">
<h4>New Features<a class="headerlink" href="#id65" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/Fittrackee/issues/13">#13</a> - Detailed statistics</p></li>
</ul>
</section>
</section>
<section id="id64">
<h3>Misc<a class="headerlink" href="#id64" title="Permalink to this headline"></a></h3>
<section id="id66">
<h3>Misc<a class="headerlink" href="#id66" title="Permalink to this headline"></a></h3>
<ul class="simple">
<li><p>Update dependencies</p></li>
</ul>
@ -872,17 +893,17 @@ add URL interceptors to simplify routes definition</p></li>
</section>
<section id="version-0-1-1-fix-and-improvements-2019-02-07">
<h2>Version 0.1.1 - Fix and improvements (2019/02/07)<a class="headerlink" href="#version-0-1-1-fix-and-improvements-2019-02-07" title="Permalink to this headline"></a></h2>
<section id="id65">
<h3>Issues Closed<a class="headerlink" href="#id65" title="Permalink to this headline"></a></h3>
<section id="id66">
<h4>New Features<a class="headerlink" href="#id66" title="Permalink to this headline"></a></h4>
<section id="id67">
<h3>Issues Closed<a class="headerlink" href="#id67" title="Permalink to this headline"></a></h3>
<section id="id68">
<h4>New Features<a class="headerlink" href="#id68" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/25">#25</a> - Display records on calendar</p></li>
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/22">#22</a> - Add a total on current month statistics</p></li>
</ul>
</section>
<section id="id67">
<h4>Bugs Fixed<a class="headerlink" href="#id67" title="Permalink to this headline"></a></h4>
<section id="id69">
<h4>Bugs Fixed<a class="headerlink" href="#id69" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/31">#31</a> - Use moving duration for stats</p></li>
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/29">#29</a> - Pause duration calculation with segments</p></li>
@ -931,10 +952,10 @@ add URL interceptors to simplify routes definition</p></li>
<li><p>no administration for now</p></li>
</ul>
<p>➡️ more informations: see <a class="reference external" href="https://samr1.github.io/FitTrackee/">documentation</a> and <a class="reference external" href="https://github.com/SamR1/FitTrackee/issues">current issues</a></p>
<section id="id68">
<h3>Issues Closed<a class="headerlink" href="#id68" title="Permalink to this headline"></a></h3>
<section id="id69">
<h4>New Features<a class="headerlink" href="#id69" title="Permalink to this headline"></a></h4>
<section id="id70">
<h3>Issues Closed<a class="headerlink" href="#id70" title="Permalink to this headline"></a></h3>
<section id="id71">
<h4>New Features<a class="headerlink" href="#id71" title="Permalink to this headline"></a></h4>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/11">#11</a> - Timezone support</p></li>
<li><p><a class="reference external" href="https://github.com/SamR1/FitTrackee/issues/10">#10</a> - Add a note to an activity</p></li>
@ -962,7 +983,7 @@ add URL interceptors to simplify routes definition</p></li>
</p>
<p>
&copy; Copyright 2018 - 2022, SamR1.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
</p>
</div>
</footer>

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>Features &#8212; FitTrackee 0.6.1
<title>Features &#8212; FitTrackee 0.6.2
documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.1
<span class="navbar-text navbar-version pull-left"><b>0.6.2
</b></span>
</div>
@ -359,7 +359,7 @@
</p>
<p>
&copy; Copyright 2018 - 2022, SamR1.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
</p>
</div>
</footer>

View File

@ -4,7 +4,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Index &#8212; FitTrackee 0.6.1
<title>Index &#8212; FitTrackee 0.6.2
documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/bootstrap-sphinx.css" />
@ -37,7 +37,7 @@
</button>
<a class="navbar-brand" href="index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.1
<span class="navbar-text navbar-version pull-left"><b>0.6.2
</b></span>
</div>
@ -179,7 +179,7 @@
</p>
<p>
&copy; Copyright 2018 - 2022, SamR1.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
</p>
</div>
</footer>

View File

@ -4,7 +4,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>HTTP Routing Table &#8212; FitTrackee 0.6.1
<title>HTTP Routing Table &#8212; FitTrackee 0.6.2
documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/bootstrap-sphinx.css" />
@ -44,7 +44,7 @@
</button>
<a class="navbar-brand" href="index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.1
<span class="navbar-text navbar-version pull-left"><b>0.6.2
</b></span>
</div>
@ -355,7 +355,7 @@
</p>
<p>
&copy; Copyright 2018 - 2022, SamR1.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
</p>
</div>
</footer>

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>FitTrackee &#8212; FitTrackee 0.6.1
<title>FitTrackee &#8212; FitTrackee 0.6.2
documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/bootstrap-sphinx.css" />
@ -39,7 +39,7 @@
</button>
<a class="navbar-brand" href="#">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.1
<span class="navbar-text navbar-version pull-left"><b>0.6.2
</b></span>
</div>
@ -173,7 +173,7 @@ Map</a>.</div>
</p>
<p>
&copy; Copyright 2018 - 2022, SamR1.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
</p>
</div>
</footer>

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>Installation &#8212; FitTrackee 0.6.1
<title>Installation &#8212; FitTrackee 0.6.2
documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.1
<span class="navbar-text navbar-version pull-left"><b>0.6.2
</b></span>
</div>
@ -632,11 +632,11 @@ $ make install-db
</div>
</div>
<ul class="simple">
<li><p>Download the last release (for now, it is the release v0.6.1):</p></li>
<li><p>Download the last release (for now, it is the release v0.6.2):</p></li>
</ul>
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.1.tar.gz
$ tar -xzf v0.6.1.tar.gz
$ mv FitTrackee-0.6.1 FitTrackee
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.2.tar.gz
$ tar -xzf v0.6.2.tar.gz
$ mv FitTrackee-0.6.2 FitTrackee
$ <span class="nb">cd</span> FitTrackee
</pre></div>
</div>
@ -752,11 +752,11 @@ $ <span class="nb">source</span> .env
<ul class="simple">
<li><p>Stop the application</p></li>
<li><p>Change to the directory where FitTrackee directory is located</p></li>
<li><p>Download the last release (for now, it is the release v0.6.1) and overwrite existing files:</p></li>
<li><p>Download the last release (for now, it is the release v0.6.2) and overwrite existing files:</p></li>
</ul>
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.1.tar.gz
$ tar -xzf v0.6.1.tar.gz
$ cp -R FitTrackee-0.6.1/* FitTrackee/
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.2.tar.gz
$ tar -xzf v0.6.2.tar.gz
$ cp -R FitTrackee-0.6.2/* FitTrackee/
$ <span class="nb">cd</span> FitTrackee
</pre></div>
</div>
@ -984,7 +984,7 @@ $ make docker-build docker-run docker-init
</p>
<p>
&copy; Copyright 2018 - 2022, SamR1.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
</p>
</div>
</footer>

Binary file not shown.

View File

@ -4,7 +4,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Search &#8212; FitTrackee 0.6.1
<title>Search &#8212; FitTrackee 0.6.2
documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/bootstrap-sphinx.css" />
@ -44,7 +44,7 @@
</button>
<a class="navbar-brand" href="index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.1
<span class="navbar-text navbar-version pull-left"><b>0.6.2
</b></span>
</div>
@ -149,7 +149,7 @@
</p>
<p>
&copy; Copyright 2018 - 2022, SamR1.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
</p>
</div>
</footer>

File diff suppressed because one or more lines are too long

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>Administrator &#8212; FitTrackee 0.6.1
<title>Administrator &#8212; FitTrackee 0.6.2
documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="../index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.1
<span class="navbar-text navbar-version pull-left"><b>0.6.2
</b></span>
</div>
@ -155,7 +155,7 @@
</p>
<p>
&copy; Copyright 2018 - 2022, SamR1.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
</p>
</div>
</footer>

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>Troubleshooting &#8212; FitTrackee 0.6.1
<title>Troubleshooting &#8212; FitTrackee 0.6.2
documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="../index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.1
<span class="navbar-text navbar-version pull-left"><b>0.6.2
</b></span>
</div>
@ -152,7 +152,7 @@
</p>
<p>
&copy; Copyright 2018 - 2022, SamR1.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
</p>
</div>
</footer>

View File

@ -5,7 +5,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<title>User &#8212; FitTrackee 0.6.1
<title>User &#8212; FitTrackee 0.6.2
documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../_static/bootstrap-sphinx.css" />
@ -40,7 +40,7 @@
</button>
<a class="navbar-brand" href="../index.html">
FitTrackee</a>
<span class="navbar-text navbar-version pull-left"><b>0.6.1
<span class="navbar-text navbar-version pull-left"><b>0.6.2
</b></span>
</div>
@ -143,7 +143,7 @@
</p>
<p>
&copy; Copyright 2018 - 2022, SamR1.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.4.0.<br/>
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 4.5.0.<br/>
</p>
</div>
</footer>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 538 KiB

After

Width:  |  Height:  |  Size: 539 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 368 KiB

After

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 103 KiB

View File

@ -369,13 +369,13 @@ Production environment
.. warning::
| Note that FitTrackee is under heavy development, some features may be unstable.
- Download the last release (for now, it is the release v0.6.1):
- Download the last release (for now, it is the release v0.6.2):
.. code:: bash
$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.1.tar.gz
$ tar -xzf v0.6.1.tar.gz
$ mv FitTrackee-0.6.1 FitTrackee
$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.2.tar.gz
$ tar -xzf v0.6.2.tar.gz
$ mv FitTrackee-0.6.2 FitTrackee
$ cd FitTrackee
- Create **.env** from example and update it
@ -493,13 +493,13 @@ Prod environment
- Change to the directory where FitTrackee directory is located
- Download the last release (for now, it is the release v0.6.1) and overwrite existing files:
- Download the last release (for now, it is the release v0.6.2) and overwrite existing files:
.. code:: bash
$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.1.tar.gz
$ tar -xzf v0.6.1.tar.gz
$ cp -R FitTrackee-0.6.1/* FitTrackee/
$ wget https://github.com/SamR1/FitTrackee/archive/v0.6.2.tar.gz
$ tar -xzf v0.6.2.tar.gz
$ cp -R FitTrackee-0.6.2/* FitTrackee/
$ cd FitTrackee
- Update **.env** if needed (see `Environment variables <installation.html#environment-variables>`__).

View File

@ -19,8 +19,9 @@ from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.exc import ProgrammingError
from fittrackee.emails.email import EmailService
from fittrackee.request import CustomRequest
VERSION = __version__ = '0.6.1'
VERSION = __version__ = '0.6.2'
db = SQLAlchemy()
bcrypt = Bcrypt()
migrate = Migrate()
@ -35,9 +36,17 @@ logging.basicConfig(
appLog = logging.getLogger('fittrackee')
class CustomFlask(Flask):
# add custom Request to handle user-agent parsing
# (removed in Werkzeug 2.1)
request_class = CustomRequest
def create_app() -> Flask:
# instantiate the app
app = Flask(__name__, static_folder='dist/static', template_folder='dist')
app = CustomFlask(
__name__, static_folder='dist/static', template_folder='dist'
)
# set config
with app.app_context():
@ -105,7 +114,7 @@ def create_app() -> Flask:
appLog.setLevel(logging.DEBUG)
# Enable CORS
@app.after_request
@app.after_request # type: ignore
def after_request(response: Response) -> Response:
response.headers.add('Access-Control-Allow-Origin', '*')
response.headers.add(

View File

@ -40,13 +40,14 @@ def get_application_config() -> Union[Dict, HttpResponse]:
{
"data": {
"admin_contact": "admin@example.com",
"gpx_limit_import": 10,
"is_registration_enabled": false,
"max_single_file_size": 1048576,
"max_zip_file_size": 10485760,
"max_users": 0,
"max_zip_file_size": 10485760,
"map_attribution": "&copy; <a href=http://www.openstreetmap.org/copyright>OpenStreetMap</a> contributors"
"version": "0.6.1"
"version": "0.6.2"
},
"status": "success"
}
@ -90,10 +91,12 @@ def update_application_config(auth_user: User) -> Union[Dict, HttpResponse]:
"data": {
"admin_contact": "admin@example.com",
"gpx_limit_import": 10,
"is_registration_enabled": true,
"is_registration_enabled": false,
"max_single_file_size": 1048576,
"max_users": 10,
"max_zip_file_size": 10485760,
"max_users": 10
"map_attribution": "&copy; <a href=http://www.openstreetmap.org/copyright>OpenStreetMap</a> contributors"
"version": "0.6.2"
},
"status": "success"
}
@ -102,8 +105,8 @@ def update_application_config(auth_user: User) -> Union[Dict, HttpResponse]:
:<json integer gpx_limit_import: max number of files in zip archive
:<json boolean is_registration_enabled: is registration enabled ?
:<json integer max_single_file_size: max size of a single file
:<json integer max_zip_file_size: max size of a zip archive
:<json integer max_users: max users allowed to register on instance
:<json integer max_zip_file_size: max size of a zip archive
:reqheader Authorization: OAuth 2.0 Bearer Token

View File

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/static/css/fork-awesome.min.css"/><link rel="stylesheet" href="/static/css/leaflet.css"/><title>FitTrackee</title><script defer="defer" src="/static/js/chunk-vendors.c045258f.js"></script><script defer="defer" src="/static/js/app.5c12d3f9.js"></script><link href="/static/css/app.3729aa92.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="fittrackee_client"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but FitTrackee doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/static/css/fork-awesome.min.css"/><link rel="stylesheet" href="/static/css/leaflet.css"/><title>FitTrackee</title><script defer="defer" src="/static/js/chunk-vendors.63e25135.js"></script><script defer="defer" src="/static/js/app.9fb29e8d.js"></script><link href="/static/css/app.3729aa92.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="fittrackee_client"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but FitTrackee doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

24
fittrackee/request.py Normal file
View File

@ -0,0 +1,24 @@
from typing import Optional, Tuple
from flask import Request
from ua_parser import user_agent_parser
from werkzeug.user_agent import UserAgent as IUserAgent
class UserAgent(IUserAgent):
def __init__(self, string: str):
super().__init__(string)
self.platform, self.browser = self._parse_user_agent(self.string)
@staticmethod
def _parse_user_agent(
user_agent: str,
) -> Tuple[Optional[str], Optional[str]]:
parsed_string = user_agent_parser.Parse(user_agent)
platform = parsed_string.get('os', {}).get('family')
browser = parsed_string.get('user_agent', {}).get('family')
return platform, browser
class CustomRequest(Request):
user_agent_class = UserAgent

View File

@ -4,17 +4,18 @@ from typing import Optional
import pytest
from flask import Flask
import fittrackee
from fittrackee.application.models import AppConfig
from fittrackee.users.models import User
from ..mixins import ApiTestCaseMixin
from ..utils import jsonify_dict
class TestGetConfig(ApiTestCaseMixin):
def test_it_gets_application_config_for_unauthenticated_user(
self, app: Flask
) -> None:
app_config = AppConfig.query.first()
client = app.test_client()
response = client.get('/api/config')
@ -22,18 +23,7 @@ class TestGetConfig(ApiTestCaseMixin):
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert data['data']['admin_contact'] is None
assert data['data']['gpx_limit_import'] == 10
assert data['data']['is_registration_enabled'] is True
assert data['data']['max_single_file_size'] == 1048576
assert data['data']['max_zip_file_size'] == 10485760
assert data['data']['max_users'] == 100
assert data['data']['map_attribution'] == (
'&copy; <a href="http://www.openstreetmap.org/copyright" '
'target="_blank" rel="noopener noreferrer">OpenStreetMap</a> '
'contributors'
)
assert data['data']['version'] == fittrackee.__version__
assert data['data'] == jsonify_dict(app_config.serialize())
def test_it_gets_application_config(
self, app: Flask, user_1: User

View File

@ -1,22 +1,51 @@
from flask import Flask
from fittrackee import VERSION
from fittrackee.application.models import AppConfig
from fittrackee.users.models import User
class TestConfigModel:
def test_application_config(self, app: Flask) -> None:
app_config = AppConfig.query.first()
assert 1 == app_config.id
app_config.admin_contact = 'admin@example.com'
assert app_config.is_registration_enabled is True
assert (
app_config.map_attribution
== app.config['TILE_SERVER']['ATTRIBUTION']
)
serialized_app_config = app_config.serialize()
assert serialized_app_config['gpx_limit_import'] == 10
assert serialized_app_config['is_registration_enabled'] is True
assert serialized_app_config['max_single_file_size'] == 1048576
assert serialized_app_config['max_zip_file_size'] == 10485760
assert serialized_app_config['max_users'] == 100
assert serialized_app_config['map_attribution'] == (
'&copy; <a href="http://www.openstreetmap.org/copyright" '
'target="_blank" rel="noopener noreferrer">OpenStreetMap</a> '
'contributors'
assert (
serialized_app_config['admin_contact'] == app_config.admin_contact
)
assert 'admin_contact' in serialized_app_config
assert (
serialized_app_config['gpx_limit_import']
== app_config.gpx_limit_import
)
assert serialized_app_config['is_registration_enabled'] is True
assert (
serialized_app_config['max_single_file_size']
== app_config.max_single_file_size
)
assert (
serialized_app_config['max_zip_file_size']
== app_config.max_zip_file_size
)
assert serialized_app_config['max_users'] == app_config.max_users
assert (
serialized_app_config['map_attribution']
== app_config.map_attribution
)
assert serialized_app_config['version'] == VERSION
def test_it_returns_registration_disabled_when_users_count_exceeds_limit(
self, app: Flask, user_1: User, user_2: User
) -> None:
app_config = AppConfig.query.first()
app_config.max_users = 2
serialized_app_config = app_config.serialize()
assert app_config.is_registration_enabled is False
assert serialized_app_config['is_registration_enabled'] is False

View File

@ -1,6 +1,6 @@
import datetime
from io import BytesIO
from typing import Generator
from typing import Generator, List
from unittest.mock import Mock, patch
from uuid import uuid4
@ -107,96 +107,105 @@ def workout_running_user_1() -> Workout:
@pytest.fixture()
def seven_workouts_user_1() -> Workout:
workout = Workout(
def seven_workouts_user_1() -> List[Workout]:
workouts = []
workout_1 = Workout(
user_id=1,
sport_id=1,
workout_date=datetime.datetime.strptime('20/03/2017', '%d/%m/%Y'),
distance=5,
duration=datetime.timedelta(seconds=1024),
)
update_workout(workout)
workout.ascent = 120
workout.descent = 200
db.session.add(workout)
update_workout(workout_1)
workout_1.ascent = 120
workout_1.descent = 200
db.session.add(workout_1)
db.session.flush()
workouts.append(workout_1)
workout = Workout(
workout_2 = Workout(
user_id=1,
sport_id=1,
workout_date=datetime.datetime.strptime('01/06/2017', '%d/%m/%Y'),
distance=10,
duration=datetime.timedelta(seconds=3456),
)
update_workout(workout)
workout.ascent = 100
workout.descent = 80
db.session.add(workout)
update_workout(workout_2)
workout_2.ascent = 100
workout_2.descent = 80
db.session.add(workout_2)
db.session.flush()
workouts.append(workout_2)
workout = Workout(
workout_3 = Workout(
user_id=1,
sport_id=1,
workout_date=datetime.datetime.strptime('01/01/2018', '%d/%m/%Y'),
distance=10,
duration=datetime.timedelta(seconds=1024),
)
update_workout(workout)
workout.ascent = 80
workout.descent = 100
db.session.add(workout)
update_workout(workout_3)
workout_3.ascent = 80
workout_3.descent = 100
db.session.add(workout_3)
db.session.flush()
workouts.append(workout_3)
workout = Workout(
workout_4 = Workout(
user_id=1,
sport_id=1,
workout_date=datetime.datetime.strptime('23/02/2018', '%d/%m/%Y'),
distance=1,
duration=datetime.timedelta(seconds=600),
)
update_workout(workout)
workout.ascent = 120
workout.descent = 180
db.session.add(workout)
update_workout(workout_4)
workout_4.ascent = 120
workout_4.descent = 180
db.session.add(workout_4)
db.session.flush()
workouts.append(workout_4)
workout = Workout(
workout_5 = Workout(
user_id=1,
sport_id=1,
workout_date=datetime.datetime.strptime('23/02/2018', '%d/%m/%Y'),
distance=10,
duration=datetime.timedelta(seconds=1000),
)
update_workout(workout)
workout.ascent = 100
workout.descent = 200
db.session.add(workout)
update_workout(workout_5)
workout_5.ascent = 100
workout_5.descent = 200
db.session.add(workout_5)
db.session.flush()
workouts.append(workout_5)
workout = Workout(
workout_6 = Workout(
user_id=1,
sport_id=1,
workout_date=datetime.datetime.strptime('01/04/2018', '%d/%m/%Y'),
distance=8,
duration=datetime.timedelta(seconds=6000),
)
update_workout(workout)
workout.ascent = 40
workout.descent = 20
db.session.add(workout)
update_workout(workout_6)
workout_6.ascent = 40
workout_6.descent = 20
db.session.add(workout_6)
db.session.flush()
workouts.append(workout_6)
workout = Workout(
workout_7 = Workout(
user_id=1,
sport_id=1,
workout_date=datetime.datetime.strptime('09/05/2018', '%d/%m/%Y'),
distance=10,
duration=datetime.timedelta(seconds=3000),
)
update_workout(workout)
db.session.add(workout)
update_workout(workout_7)
db.session.add(workout_7)
db.session.commit()
return workout
workouts.append(workout_7)
return workouts
@pytest.fixture()

View File

@ -4,6 +4,7 @@ from uuid import uuid4
import pytest
from fittrackee.files import display_readable_file_size
from fittrackee.request import UserAgent
from fittrackee.utils import get_readable_duration
@ -42,3 +43,28 @@ class TestReadableDuration:
readable_duration = get_readable_duration(30, locale)
assert readable_duration == expected_duration
class TestParseUserAgent:
string = (
'Mozilla/5.0 (X11; Linux x86_64; rv:98.0) '
'Gecko/20100101 Firefox/98.0'
)
def test_it_returns_browser_name(self) -> None:
user_agent = UserAgent(self.string)
assert user_agent.browser == 'Firefox'
def test_it_returns_other_as_brother_name_when_empty_string_provided(
self,
) -> None:
user_agent = UserAgent('')
assert user_agent.browser == 'Other'
def test_it_returns_operating_system(self) -> None:
user_agent = UserAgent(self.string)
assert user_agent.platform == 'Linux'
def test_it_returns_other_as_os_when_empty_string_provided(self) -> None:
user_agent = UserAgent('')
assert user_agent.platform == 'Other'

View File

@ -9,9 +9,10 @@ from freezegun import freeze_time
from fittrackee.users.models import User, UserSportPreference
from fittrackee.users.utils.token import get_user_token
from fittrackee.workouts.models import Sport, Workout
from fittrackee.workouts.models import Sport
from ..mixins import ApiTestCaseMixin
from ..utils import jsonify_dict
USER_AGENT = (
'Mozilla/5.0 (X11; Linux x86_64; rv:98.0) Gecko/20100101 Firefox/98.0'
@ -284,8 +285,8 @@ class TestUserRegistration(ApiTestCaseMixin):
{
'username': username,
'fittrackee_url': 'http://0.0.0.0:5000',
'operating_system': 'linux',
'browser_name': 'firefox',
'operating_system': 'Linux',
'browser_name': 'Firefox',
'account_confirmation_url': (
'http://0.0.0.0:5000/account-confirmation'
f'?token={expected_token}'
@ -453,9 +454,7 @@ class TestUserProfile(ApiTestCaseMixin):
self.assert_401(response, 'invalid token, please log in again')
def test_it_returns_user_minimal_profile(
self, app: Flask, user_1: User
) -> None:
def test_it_returns_user(self, app: Flask, user_1: User) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
@ -468,92 +467,7 @@ class TestUserProfile(ApiTestCaseMixin):
assert response.status_code == 200
data = json.loads(response.data.decode())
assert data['status'] == 'success'
assert data['data'] is not None
assert data['data']['username'] == 'test'
assert data['data']['email'] == 'test@test.com'
assert data['data']['created_at']
assert not data['data']['admin']
assert data['data']['timezone'] is None
assert data['data']['weekm'] is False
assert data['data']['imperial_units'] is False
assert data['data']['language'] is None
assert data['data']['nb_sports'] == 0
assert data['data']['nb_workouts'] == 0
assert data['data']['records'] == []
assert data['data']['sports_list'] == []
assert data['data']['total_distance'] == 0
assert data['data']['total_duration'] == '0:00:00'
def test_it_returns_user_full_profile(
self, app: Flask, user_1_full: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_full.email
)
response = client.get(
'/api/auth/profile',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 200
data = json.loads(response.data.decode())
assert data['status'] == 'success'
assert data['data'] is not None
assert data['data']['username'] == 'test'
assert data['data']['email'] == 'test@test.com'
assert data['data']['created_at']
assert not data['data']['admin']
assert data['data']['first_name'] == 'John'
assert data['data']['last_name'] == 'Doe'
assert data['data']['birth_date']
assert data['data']['bio'] == 'just a random guy'
assert data['data']['imperial_units'] is False
assert data['data']['location'] == 'somewhere'
assert data['data']['timezone'] == 'America/New_York'
assert data['data']['weekm'] is False
assert data['data']['language'] == 'en'
assert data['data']['nb_sports'] == 0
assert data['data']['nb_workouts'] == 0
assert data['data']['records'] == []
assert data['data']['sports_list'] == []
assert data['data']['total_distance'] == 0
assert data['data']['total_duration'] == '0:00:00'
def test_it_returns_user_profile_with_workouts(
self,
app: Flask,
user_1: User,
sport_1_cycling: Sport,
sport_2_running: Sport,
workout_cycling_user_1: Workout,
workout_running_user_1: Workout,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
response = client.get(
'/api/auth/profile',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
assert response.status_code == 200
data = json.loads(response.data.decode())
assert data['status'] == 'success'
assert data['data'] is not None
assert data['data']['username'] == 'test'
assert data['data']['email'] == 'test@test.com'
assert data['data']['created_at']
assert not data['data']['admin']
assert data['data']['timezone'] is None
assert data['data']['imperial_units'] is False
assert data['data']['nb_sports'] == 2
assert data['data']['nb_workouts'] == 2
assert len(data['data']['records']) == 8
assert data['data']['sports_list'] == [1, 2]
assert data['data']['total_distance'] == 22
assert data['data']['total_duration'] == '2:40:00'
assert data['data'] == jsonify_dict(user_1.serialize(user_1))
class TestUserProfileUpdate(ApiTestCaseMixin):
@ -618,25 +532,7 @@ class TestUserProfileUpdate(ApiTestCaseMixin):
data = json.loads(response.data.decode())
assert data['status'] == 'success'
assert data['message'] == 'user profile updated'
assert data['data']['username'] == user_1.username
assert data['data']['email'] == user_1.email
assert not data['data']['admin']
assert data['data']['created_at']
assert data['data']['first_name'] == first_name
assert data['data']['last_name'] == last_name
assert data['data']['birth_date'] == 'Tue, 01 Jan 1980 00:00:00 GMT'
assert data['data']['bio'] == bio
assert data['data']['imperial_units'] is False
assert data['data']['location'] == location
assert data['data']['timezone'] is None
assert data['data']['weekm'] is False
assert data['data']['language'] is None
assert data['data']['nb_sports'] == 0
assert data['data']['nb_workouts'] == 0
assert data['data']['records'] == []
assert data['data']['sports_list'] == []
assert data['data']['total_distance'] == 0
assert data['data']['total_duration'] == '0:00:00'
assert data['data'] == jsonify_dict(user_1.serialize(user_1))
class TestUserAccountUpdate(ApiTestCaseMixin):
@ -911,8 +807,8 @@ class TestUserAccountUpdate(ApiTestCaseMixin):
{
'username': user_1.username,
'fittrackee_url': 'http://0.0.0.0:5000',
'operating_system': 'linux',
'browser_name': 'firefox',
'operating_system': 'Linux',
'browser_name': 'Firefox',
'new_email_address': new_email,
},
)
@ -953,8 +849,8 @@ class TestUserAccountUpdate(ApiTestCaseMixin):
{
'username': user_1.username,
'fittrackee_url': 'http://0.0.0.0:5000',
'operating_system': 'linux',
'browser_name': 'firefox',
'operating_system': 'Linux',
'browser_name': 'Firefox',
'email_confirmation_url': (
f'http://0.0.0.0:5000/email-update?token={expected_token}'
),
@ -1113,8 +1009,8 @@ class TestUserAccountUpdate(ApiTestCaseMixin):
{
'username': user_1.username,
'fittrackee_url': 'http://0.0.0.0:5000',
'operating_system': 'linux',
'browser_name': 'firefox',
'operating_system': 'Linux',
'browser_name': 'Firefox',
},
)
@ -1270,25 +1166,7 @@ class TestUserPreferencesUpdate(ApiTestCaseMixin):
data = json.loads(response.data.decode())
assert data['status'] == 'success'
assert data['message'] == 'user preferences updated'
assert data['data']['username'] == user_1.username
assert data['data']['email'] == user_1.email
assert not data['data']['admin']
assert data['data']['created_at']
assert data['data']['first_name'] is None
assert data['data']['last_name'] is None
assert data['data']['birth_date'] is None
assert data['data']['bio'] is None
assert data['data']['imperial_units']
assert data['data']['location'] is None
assert data['data']['timezone'] == 'America/New_York'
assert data['data']['weekm'] is True
assert data['data']['language'] == 'fr'
assert data['data']['nb_sports'] == 0
assert data['data']['nb_workouts'] == 0
assert data['data']['records'] == []
assert data['data']['sports_list'] == []
assert data['data']['total_distance'] == 0
assert data['data']['total_duration'] == '0:00:00'
assert data['data'] == jsonify_dict(user_1.serialize(user_1))
class TestUserSportPreferencesUpdate(ApiTestCaseMixin):
@ -1812,8 +1690,8 @@ class TestPasswordResetRequest(ApiTestCaseMixin):
f'http://0.0.0.0:5000/password-reset?token={token}'
),
'fittrackee_url': 'http://0.0.0.0:5000',
'operating_system': 'linux',
'browser_name': 'firefox',
'operating_system': 'Linux',
'browser_name': 'Firefox',
},
)
@ -2025,8 +1903,8 @@ class TestPasswordUpdate(ApiTestCaseMixin):
{
'username': user_1.username,
'fittrackee_url': 'http://0.0.0.0:5000',
'operating_system': 'linux',
'browser_name': 'firefox',
'operating_system': 'Linux',
'browser_name': 'Firefox',
},
)
@ -2252,8 +2130,8 @@ class TestResendAccountConfirmationEmail(ApiTestCaseMixin):
{
'username': inactive_user.username,
'fittrackee_url': 'http://0.0.0.0:5000',
'operating_system': 'linux',
'browser_name': 'firefox',
'operating_system': 'Linux',
'browser_name': 'Firefox',
'account_confirmation_url': (
'http://0.0.0.0:5000/account-confirmation'
f'?token={expected_token}'

View File

@ -10,6 +10,7 @@ from fittrackee.utils import get_readable_duration
from fittrackee.workouts.models import Sport, Workout
from ..mixins import ApiTestCaseMixin
from ..utils import jsonify_dict
class TestGetUser(ApiTestCaseMixin):
@ -28,6 +29,26 @@ class TestGetUser(ApiTestCaseMixin):
self.assert_403(response)
def test_user_can_access_his_profile(
self, app: Flask, user_1: User, user_2: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
response = client.get(
f'/api/users/{user_1.username}',
content_type='application/json',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert data['status'] == 'success'
assert len(data['data']['users']) == 1
user = data['data']['users'][0]
assert user['username'] == user_1.username
def test_it_gets_inactive_user(
self, app: Flask, user_1_admin: User, inactive_user: User
) -> None:
@ -46,22 +67,7 @@ class TestGetUser(ApiTestCaseMixin):
assert data['status'] == 'success'
assert len(data['data']['users']) == 1
user = data['data']['users'][0]
assert user['username'] == inactive_user.username
assert user['email'] == inactive_user.email
assert user['created_at']
assert not user['admin']
assert not user['is_active']
assert user['first_name'] is None
assert user['last_name'] is None
assert user['birth_date'] is None
assert user['bio'] is None
assert user['location'] is None
assert user['nb_sports'] == 0
assert user['nb_workouts'] == 0
assert user['records'] == []
assert user['sports_list'] == []
assert user['total_distance'] == 0
assert user['total_duration'] == '0:00:00'
assert user == jsonify_dict(inactive_user.serialize(user_1_admin))
def test_it_gets_single_user_without_workouts(
self, app: Flask, user_1_admin: User, user_2: User
@ -81,22 +87,7 @@ class TestGetUser(ApiTestCaseMixin):
assert data['status'] == 'success'
assert len(data['data']['users']) == 1
user = data['data']['users'][0]
assert user['username'] == user_2.username
assert user['email'] == user_2.email
assert user['created_at']
assert not user['admin']
assert user['is_active']
assert user['first_name'] is None
assert user['last_name'] is None
assert user['birth_date'] is None
assert user['bio'] is None
assert user['location'] is None
assert user['nb_sports'] == 0
assert user['nb_workouts'] == 0
assert user['records'] == []
assert user['sports_list'] == []
assert user['total_distance'] == 0
assert user['total_duration'] == '0:00:00'
assert user == jsonify_dict(user_2.serialize(user_1_admin))
def test_it_gets_single_user_with_workouts(
self,
@ -123,22 +114,7 @@ class TestGetUser(ApiTestCaseMixin):
assert data['status'] == 'success'
assert len(data['data']['users']) == 1
user = data['data']['users'][0]
assert user['username'] == user_1.username
assert user['email'] == user_1.email
assert user['created_at']
assert not user['admin']
assert user['is_active']
assert user['first_name'] is None
assert user['last_name'] is None
assert user['birth_date'] is None
assert user['bio'] is None
assert user['location'] is None
assert len(user['records']) == 8
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'
assert user == jsonify_dict(user_1.serialize(user_2_admin))
def test_it_returns_error_if_user_does_not_exist(
self, app: Flask, user_1_admin: User
@ -187,106 +163,15 @@ class TestGetUsers(ApiTestCaseMixin):
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 3
assert 'created_at' in data['data']['users'][0]
assert 'created_at' in data['data']['users'][1]
assert 'created_at' in data['data']['users'][2]
assert 'admin' in data['data']['users'][0]['username']
assert 'inactive' in data['data']['users'][1]['username']
assert 'sam' in data['data']['users'][2]['username']
assert 'admin@example.com' in data['data']['users'][0]['email']
assert 'inactive@example.com' in data['data']['users'][1]['email']
assert 'sam@test.com' in data['data']['users'][2]['email']
assert data['data']['users'][0]['is_active']
assert not data['data']['users'][1]['is_active']
assert data['data']['users'][2]['is_active']
assert data['data']['users'][0]['imperial_units'] is False
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_sports'] == 0
assert data['data']['users'][0]['nb_workouts'] == 0
assert data['data']['users'][0]['records'] == []
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]['nb_sports'] == 0
assert data['data']['users'][1]['nb_workouts'] == 0
assert data['data']['users'][1]['records'] == []
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]['records'] == []
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'
assert data['pagination'] == {
'has_next': False,
'has_prev': False,
'page': 1,
'pages': 1,
'total': 3,
}
def test_it_gets_users_list_with_workouts(
self,
app: Flask,
user_1_admin: User,
user_2: User,
user_3: User,
sport_1_cycling: Sport,
workout_cycling_user_1: Workout,
sport_2_running: Sport,
workout_running_user_1: Workout,
workout_cycling_user_2: Workout,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
assert data['data']['users'][0] == jsonify_dict(
user_1_admin.serialize(user_1_admin)
)
response = client.get(
'/api/users',
headers=dict(Authorization=f'Bearer {auth_token}'),
assert data['data']['users'][1] == jsonify_dict(
inactive_user.serialize(user_1_admin)
)
assert data['data']['users'][2] == jsonify_dict(
user_3.serialize(user_1_admin)
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['users']) == 3
assert 'created_at' in data['data']['users'][0]
assert 'created_at' in data['data']['users'][1]
assert 'created_at' in data['data']['users'][2]
assert 'admin' in data['data']['users'][0]['username']
assert 'toto' in data['data']['users'][1]['username']
assert 'sam' in data['data']['users'][2]['username']
assert 'admin@example.com' in data['data']['users'][0]['email']
assert 'toto@toto.com' in data['data']['users'][1]['email']
assert 'sam@test.com' in data['data']['users'][2]['email']
assert data['data']['users'][0]['is_active']
assert data['data']['users'][1]['is_active']
assert data['data']['users'][2]['is_active']
assert data['data']['users'][0]['imperial_units'] is False
assert data['data']['users'][0]['timezone'] is None
assert data['data']['users'][0]['weekm'] is False
assert data['data']['users'][0]['nb_sports'] == 2
assert data['data']['users'][0]['nb_workouts'] == 2
assert len(data['data']['users'][0]['records']) == 8
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]['nb_sports'] == 1
assert data['data']['users'][1]['nb_workouts'] == 1
assert len(data['data']['users'][1]['records']) == 4
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]['nb_sports'] == 0
assert data['data']['users'][2]['nb_workouts'] == 0
assert len(data['data']['users'][2]['records']) == 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'
assert data['pagination'] == {
'has_next': False,
'has_prev': False,

View File

@ -9,77 +9,145 @@ from fittrackee.workouts.models import Sport, Workout
class TestUserModel:
@staticmethod
def assert_serialized_used(serialized_user: Dict) -> None:
assert 'created_at' in serialized_user
assert serialized_user['admin'] is False
assert serialized_user['first_name'] is None
assert serialized_user['is_active']
assert serialized_user['last_name'] is None
assert serialized_user['bio'] is None
assert serialized_user['location'] is None
assert serialized_user['birth_date'] is None
assert serialized_user['picture'] is False
assert serialized_user['nb_workouts'] == 0
def test_user_model_as_auth_user(self, app: Flask, user_1: User) -> None:
def test_it_returns_username_in_string_value(
self, app: Flask, user_1: User
) -> None:
assert '<User \'test\'>' == str(user_1)
class UserModelAssertMixin:
@staticmethod
def assert_user_account(serialized_user: Dict, user: User) -> None:
assert serialized_user['admin'] == user.admin
assert serialized_user['email_to_confirm'] == user.email_to_confirm
assert serialized_user['is_active'] == user.is_active
assert serialized_user['username'] == user.username
@staticmethod
def assert_user_profile(serialized_user: Dict, user: User) -> None:
assert serialized_user['bio'] == user.bio
assert serialized_user['birth_date'] == user.birth_date
assert serialized_user['first_name'] == user.first_name
assert serialized_user['last_name'] == user.last_name
assert serialized_user['location'] == user.location
assert serialized_user['picture'] is False
@staticmethod
def assert_workouts_keys_are_present(serialized_user: Dict) -> None:
assert 'nb_sports' in serialized_user
assert 'nb_workouts' in serialized_user
assert 'records' in serialized_user
assert 'sports_list' in serialized_user
assert 'total_distance' in serialized_user
assert 'total_duration' in serialized_user
class TestUserSerializeAsAuthUser(UserModelAssertMixin):
def test_it_returns_user_account_infos(
self, app: Flask, user_1: User
) -> None:
serialized_user = user_1.serialize(user_1)
self.assert_serialized_used(serialized_user)
assert 'test' == serialized_user['username']
assert 'test@test.com' == serialized_user['email']
assert serialized_user['nb_sports'] == 0
assert serialized_user['records'] == []
assert serialized_user['sports_list'] == []
assert serialized_user['total_distance'] == 0
assert serialized_user['total_duration'] == '0:00:00'
assert serialized_user['imperial_units'] is False
assert serialized_user['language'] is None
assert serialized_user['timezone'] is None
assert serialized_user['weekm'] is False
assert serialized_user['email_to_confirm'] is None
assert 'confirmation_token' not in serialized_user
self.assert_user_account(serialized_user, user_1)
def test_user_model_as_admin(
def test_it_returns_user_profile_infos(
self, app: Flask, user_1: User
) -> None:
serialized_user = user_1.serialize(user_1)
self.assert_user_profile(serialized_user, user_1)
def test_it_returns_user_preferences(
self, app: Flask, user_1: User
) -> None:
serialized_user = user_1.serialize(user_1)
assert serialized_user['imperial_units'] == user_1.imperial_units
assert serialized_user['language'] == user_1.language
assert serialized_user['timezone'] == user_1.timezone
assert serialized_user['weekm'] == user_1.weekm
def test_it_returns_workouts_infos(self, app: Flask, user_1: User) -> None:
serialized_user = user_1.serialize(user_1)
self.assert_workouts_keys_are_present(serialized_user)
def test_it_does_not_return_confirmation_token(
self, app: Flask, user_1_admin: User, user_2: User
) -> None:
serialized_user = user_2.serialize(user_1_admin)
assert 'confirmation_token' not in serialized_user
class TestUserSerializeAsAdmin(UserModelAssertMixin):
def test_it_returns_user_account_infos(
self, app: Flask, user_1_admin: User, user_2: User
) -> None:
serialized_user = user_2.serialize(user_1_admin)
self.assert_user_account(serialized_user, user_2)
def test_it_returns_user_profile_infos(
self, app: Flask, user_1_admin: User, user_2: User
) -> None:
serialized_user = user_2.serialize(user_1_admin)
self.assert_user_profile(serialized_user, user_1_admin)
def test_it_does_return_user_preferences(
self, app: Flask, user_1_admin: User, user_2: User
) -> None:
serialized_user = user_2.serialize(user_1_admin)
self.assert_serialized_used(serialized_user)
assert 'toto' == serialized_user['username']
assert 'toto@toto.com' == serialized_user['email']
assert serialized_user['nb_sports'] == 0
assert serialized_user['records'] == []
assert serialized_user['sports_list'] == []
assert serialized_user['total_distance'] == 0
assert serialized_user['total_duration'] == '0:00:00'
assert serialized_user['email_to_confirm'] is None
assert 'imperial_units' not in serialized_user
assert 'language' not in serialized_user
assert 'timezone' not in serialized_user
assert 'weekm' not in serialized_user
def test_it_returns_workouts_infos(
self, app: Flask, user_1_admin: User, user_2: User
) -> None:
serialized_user = user_2.serialize(user_1_admin)
self.assert_workouts_keys_are_present(serialized_user)
def test_it_does_not_return_confirmation_token(
self, app: Flask, user_1_admin: User, user_2: User
) -> None:
serialized_user = user_2.serialize(user_1_admin)
assert 'confirmation_token' not in serialized_user
class TestInactiveUserSerialize(UserModelAssertMixin):
def test_it_returns_is_active_to_false_for_inactive_user(
self,
app: Flask,
inactive_user: User,
) -> None:
serialized_user = inactive_user.serialize(inactive_user)
assert serialized_user['is_active'] is False
class TestUserSerializeAsRegularUser(UserModelAssertMixin):
def test_user_model_as_regular_user(
self, app: Flask, user_1: User, user_2: User
) -> None:
with pytest.raises(UserNotFoundException):
user_2.serialize(user_1)
def test_encode_auth_token(self, app: Flask, user_1: User) -> None:
auth_token = user_1.encode_auth_token(user_1.id)
assert isinstance(auth_token, str)
def test_encode_password_token(self, app: Flask, user_1: User) -> None:
password_token = user_1.encode_password_reset_token(user_1.id)
assert isinstance(password_token, str)
class TestUserRecords(UserModelAssertMixin):
def test_it_returns_empty_list_when_no_workouts(
self,
app: Flask,
user_1: User,
) -> None:
serialized_user = user_1.serialize(user_1)
def test_decode_auth_token(self, app: Flask, user_1: User) -> None:
auth_token = user_1.encode_auth_token(user_1.id)
assert isinstance(auth_token, str)
assert User.decode_auth_token(auth_token) == user_1.id
assert serialized_user['records'] == []
def test_it_returns_user_records(
self,
@ -100,14 +168,79 @@ class TestUserModel:
)
assert serialized_user['records'][0]['workout_date']
def test_it_returns_is_active_to_false_fot_inactive_user(
class TestUserWorkouts(UserModelAssertMixin):
def test_it_returns_infos_when_no_workouts(
self,
app: Flask,
inactive_user: User,
user_1: User,
) -> None:
serialized_user = inactive_user.serialize(inactive_user)
serialized_user = user_1.serialize(user_1)
assert serialized_user['is_active'] is False
assert serialized_user['nb_sports'] == 0
assert serialized_user['nb_workouts'] == 0
assert serialized_user['sports_list'] == []
assert serialized_user['total_distance'] == 0
assert serialized_user['total_duration'] == '0:00:00'
def test_it_returns_infos_when_only_one_workout_exists(
self,
app: Flask,
user_1: User,
sport_1_cycling: Sport,
workout_cycling_user_1: Workout,
) -> None:
serialized_user = user_1.serialize(user_1)
assert serialized_user['nb_sports'] == 1
assert serialized_user['nb_workouts'] == 1
assert serialized_user['sports_list'] == [sport_1_cycling.id]
assert (
serialized_user['total_distance']
== workout_cycling_user_1.distance
)
assert serialized_user['total_duration'] == str(
workout_cycling_user_1.duration
)
def test_it_returns_infos_when_several_sports(
self,
app: Flask,
user_1: User,
sport_1_cycling: Sport,
sport_2_running: Sport,
workout_cycling_user_1: Workout,
workout_running_user_1: Workout,
) -> None:
serialized_user = user_1.serialize(user_1)
assert serialized_user['nb_sports'] == 2
assert serialized_user['nb_workouts'] == 2
assert serialized_user['sports_list'] == [
sport_1_cycling.id,
sport_2_running.id,
]
assert serialized_user['total_distance'] == (
workout_cycling_user_1.distance + workout_running_user_1.distance
)
assert serialized_user['total_duration'] == str(
workout_cycling_user_1.duration + workout_running_user_1.duration
)
class TestUserModelToken:
def test_encode_auth_token(self, app: Flask, user_1: User) -> None:
auth_token = user_1.encode_auth_token(user_1.id)
assert isinstance(auth_token, str)
def test_encode_password_token(self, app: Flask, user_1: User) -> None:
password_token = user_1.encode_password_reset_token(user_1.id)
assert isinstance(password_token, str)
def test_decode_auth_token(self, app: Flask, user_1: User) -> None:
auth_token = user_1.encode_auth_token(user_1.id)
assert isinstance(auth_token, str)
assert User.decode_auth_token(auth_token) == user_1.id
class TestUserSportModel:

View File

@ -1,6 +1,9 @@
import random
import string
from typing import Optional
from json import loads
from typing import Dict, Optional
from flask import json as flask_json
def random_string(
@ -23,3 +26,7 @@ def random_string(
def random_email() -> str:
return random_string(suffix='@example.com')
def jsonify_dict(data: Dict) -> Dict:
return loads(flask_json.dumps(data))

View File

@ -14,26 +14,34 @@ class TestRecordModel:
sport_1_cycling: Sport,
workout_cycling_user_1: Workout,
) -> None:
record_type = 'LD'
record_ld = Record.query.filter_by(
user_id=workout_cycling_user_1.user_id,
sport_id=workout_cycling_user_1.sport_id,
record_type='LD',
record_type=record_type,
).first()
assert 'test' == record_ld.user.username
assert 1 == record_ld.sport_id
assert 1 == record_ld.workout_id
assert 'LD' == record_ld.record_type
assert '2018-01-01 00:00:00' == str(record_ld.workout_date)
assert record_ld.user.username == user_1.username
assert record_ld.sport_id == sport_1_cycling.id
assert record_ld.workout_id == workout_cycling_user_1.sport_id
assert record_ld.record_type == record_type
assert str(record_ld.workout_date) == str(
workout_cycling_user_1.workout_date
)
assert record_ld.value == workout_cycling_user_1.duration
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 'workout_id' in record_serialize
assert 'record_type' in record_serialize
assert 'workout_date' in record_serialize
assert 'value' in record_serialize
record_serialize['id'] = record_ld.id
record_serialize['record_type'] = record_ld.record_type
record_serialize['sport_id'] = record_ld.sport_id
record_serialize['user'] = record_ld.user.username
record_serialize['value'] = record_ld.value
record_serialize['workout_id'] = record_ld.workout_id
record_serialize['workout_date'] = record_ld.workout_date
def test_record_model_with_none_value(
self,
@ -48,12 +56,7 @@ class TestRecordModel:
record_type='LD',
).first()
record_ld.value = None
assert 'test' == record_ld.user.username
assert 1 == record_ld.sport_id
assert 1 == record_ld.workout_id
assert 'LD' == record_ld.record_type
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
record_serialize = record_ld.serialize()
@ -80,7 +83,7 @@ class TestRecordModel:
assert record_serialize.get('value') == 10.0
assert isinstance(record_serialize.get('value'), float)
def test_add_farest_distance_records(
def test_add_farthest_distance_records(
self,
app: Flask,
user_1: User,

View File

@ -7,41 +7,7 @@ from fittrackee.users.models import User, UserSportPreference
from fittrackee.workouts.models import Sport, Workout
from ..mixins import ApiTestCaseMixin
expected_sport_1_cycling_result = {
'id': 1,
'label': 'Cycling',
'is_active': True,
'is_active_for_user': True,
'color': None,
'stopped_speed_threshold': 1,
}
expected_sport_1_cycling_admin_result = expected_sport_1_cycling_result.copy()
expected_sport_1_cycling_admin_result['has_workouts'] = False
expected_sport_2_running_result = {
'id': 2,
'label': 'Running',
'is_active': True,
'is_active_for_user': True,
'color': None,
'stopped_speed_threshold': 0.1,
}
expected_sport_2_running_admin_result = expected_sport_2_running_result.copy()
expected_sport_2_running_admin_result['has_workouts'] = False
expected_sport_1_cycling_inactive_result = {
'id': 1,
'label': 'Cycling',
'is_active': False,
'is_active_for_user': False,
'color': None,
'stopped_speed_threshold': 1,
}
expected_sport_1_cycling_inactive_admin_result = (
expected_sport_1_cycling_inactive_result.copy()
)
expected_sport_1_cycling_inactive_admin_result['has_workouts'] = False
from ..utils import jsonify_dict
class TestGetSports(ApiTestCaseMixin):
@ -75,8 +41,12 @@ class TestGetSports(ApiTestCaseMixin):
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['sports']) == 2
assert data['data']['sports'][0] == expected_sport_1_cycling_result
assert data['data']['sports'][1] == expected_sport_2_running_result
assert data['data']['sports'][0] == jsonify_dict(
sport_1_cycling.serialize()
)
assert data['data']['sports'][1] == jsonify_dict(
sport_2_running.serialize()
)
def test_it_gets_all_sports_with_inactive_one(
self,
@ -98,11 +68,12 @@ class TestGetSports(ApiTestCaseMixin):
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['sports']) == 2
assert (
data['data']['sports'][0]
== expected_sport_1_cycling_inactive_result
assert data['data']['sports'][0] == jsonify_dict(
sport_1_cycling_inactive.serialize()
)
assert data['data']['sports'][1] == jsonify_dict(
sport_2_running.serialize()
)
assert data['data']['sports'][1] == expected_sport_2_running_result
def test_it_gets_all_sports_with_admin_rights(
self,
@ -124,12 +95,11 @@ class TestGetSports(ApiTestCaseMixin):
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['sports']) == 2
assert (
data['data']['sports'][0]
== expected_sport_1_cycling_inactive_admin_result
assert data['data']['sports'][0] == jsonify_dict(
sport_1_cycling_inactive.serialize(is_admin=True)
)
assert (
data['data']['sports'][1] == expected_sport_2_running_admin_result
assert data['data']['sports'][1] == jsonify_dict(
sport_2_running.serialize(is_admin=True)
)
def test_it_gets_sports_with_auth_user_preferences(
@ -158,11 +128,14 @@ class TestGetSports(ApiTestCaseMixin):
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['sports']) == 2
assert data['data']['sports'][0]['color'] == '#000000'
assert data['data']['sports'][0]['stopped_speed_threshold'] == 0.5
assert data['data']['sports'][0]['is_active_for_user'] is False
assert (
data['data']['sports'][1] == expected_sport_2_running_admin_result
assert data['data']['sports'][0] == jsonify_dict(
sport_1_cycling.serialize(
is_admin=True,
sport_preferences=user_admin_sport_1_preference.serialize(),
)
)
assert data['data']['sports'][1] == jsonify_dict(
sport_2_running.serialize(is_admin=True)
)
@ -183,7 +156,9 @@ class TestGetSport(ApiTestCaseMixin):
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['sports']) == 1
assert data['data']['sports'][0] == expected_sport_1_cycling_result
assert data['data']['sports'][0] == jsonify_dict(
sport_1_cycling.serialize()
)
def test_it_gets_a_sport_with_preferences(
self,
@ -205,7 +180,11 @@ class TestGetSport(ApiTestCaseMixin):
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['sports']) == 1
assert data['data']['sports'][0] == expected_sport_1_cycling_result
assert data['data']['sports'][0] == jsonify_dict(
sport_1_cycling.serialize(
sport_preferences=user_sport_1_preference.serialize()
)
)
def test_it_returns_404_if_sport_does_not_exist(
self, app: Flask, user_1: User
@ -238,9 +217,8 @@ class TestGetSport(ApiTestCaseMixin):
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['sports']) == 1
assert (
data['data']['sports'][0]
== expected_sport_1_cycling_inactive_result
assert data['data']['sports'][0] == jsonify_dict(
sport_1_cycling_inactive.serialize()
)
def test_it_get_an_inactive_sport_with_admin_rights(
@ -259,9 +237,8 @@ class TestGetSport(ApiTestCaseMixin):
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['sports']) == 1
assert (
data['data']['sports'][0]
== expected_sport_1_cycling_inactive_admin_result
assert data['data']['sports'][0] == jsonify_dict(
sport_1_cycling_inactive.serialize(is_admin=True)
)
@ -388,7 +365,6 @@ class TestUpdateSport(ApiTestCaseMixin):
assert len(data['data']['sports']) == 1
assert data['data']['sports'][0]['is_active'] is False
assert data['data']['sports'][0]['is_active_for_user'] is False
assert data['data']['sports'][0]['is_active_for_user'] is False
assert data['data']['sports'][0]['has_workouts'] is False
def test_it_enables_a_sport_with_preferences(

View File

@ -17,15 +17,17 @@ class TestSportModel:
assert '<Sport \'Cycling\'>' == str(sport)
serialized_sport = sport.serialize(is_admin=is_admin)
assert 1 == serialized_sport['id']
assert 'Cycling' == serialized_sport['label']
assert serialized_sport['label'] == sport.label
assert serialized_sport['id'] == sport.id
assert serialized_sport['is_active'] is True
assert serialized_sport['is_active_for_user'] is True
assert serialized_sport['color'] is None
assert serialized_sport['stopped_speed_threshold'] == 1
return serialized_sport
def test_sport_model(self, app: Flask, sport_1_cycling: Sport) -> None:
def test_sport_model_without_workout(
self, app: Flask, sport_1_cycling: Sport
) -> None:
serialized_sport = self.assert_sport_model(sport_1_cycling)
assert 'has_workouts' not in serialized_sport

View File

@ -1,4 +1,5 @@
import json
from typing import List
from unittest.mock import patch
from uuid import uuid4
@ -8,6 +9,7 @@ from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout
from ..mixins import ApiTestCaseMixin
from ..utils import jsonify_dict
from .utils import get_random_short_id
@ -45,25 +47,12 @@ class TestGetWorkouts(ApiTestCaseMixin):
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['workouts']) == 2
assert 'creation_date' in data['data']['workouts'][0]
assert (
'Sun, 01 Apr 2018 00:00:00 GMT'
== data['data']['workouts'][0]['workout_date']
assert data['data']['workouts'][0] == jsonify_dict(
workout_running_user_1.serialize()
)
assert 'test' == data['data']['workouts'][0]['user']
assert 2 == data['data']['workouts'][0]['sport_id']
assert 12.0 == data['data']['workouts'][0]['distance']
assert '1:40:00' == data['data']['workouts'][0]['duration']
assert 'creation_date' in data['data']['workouts'][1]
assert (
'Mon, 01 Jan 2018 00:00:00 GMT'
== data['data']['workouts'][1]['workout_date']
assert data['data']['workouts'][1] == jsonify_dict(
workout_cycling_user_1.serialize()
)
assert 'test' == data['data']['workouts'][1]['user']
assert 1 == data['data']['workouts'][1]['sport_id']
assert 10.0 == data['data']['workouts'][1]['distance']
assert '1:00:00' == data['data']['workouts'][1]['duration']
assert data['pagination'] == {
'has_next': False,
'has_prev': False,
@ -119,7 +108,7 @@ class TestGetWorkoutsWithPagination(ApiTestCaseMixin):
app: Flask,
user_1: User,
sport_1_cycling: Sport,
seven_workouts_user_1: Workout,
seven_workouts_user_1: List[Workout],
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
@ -134,18 +123,22 @@ class TestGetWorkoutsWithPagination(ApiTestCaseMixin):
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['workouts']) == 5
assert 'creation_date' in data['data']['workouts'][0]
assert (
'Wed, 09 May 2018 00:00:00 GMT'
== data['data']['workouts'][0]['workout_date']
assert data['data']['workouts'][0] == jsonify_dict(
seven_workouts_user_1[6].serialize()
)
assert '0:50:00' == data['data']['workouts'][0]['duration']
assert 'creation_date' in data['data']['workouts'][4]
assert (
'Mon, 01 Jan 2018 00:00:00 GMT'
== data['data']['workouts'][4]['workout_date']
assert data['data']['workouts'][1] == jsonify_dict(
seven_workouts_user_1[5].serialize()
)
assert '0:17:04' == data['data']['workouts'][4]['duration']
assert data['data']['workouts'][2] == jsonify_dict(
seven_workouts_user_1[3].serialize()
)
assert data['data']['workouts'][3] == jsonify_dict(
seven_workouts_user_1[4].serialize()
)
assert data['data']['workouts'][4] == jsonify_dict(
seven_workouts_user_1[2].serialize()
)
assert data['pagination'] == {
'has_next': True,
'has_prev': False,
@ -159,7 +152,7 @@ class TestGetWorkoutsWithPagination(ApiTestCaseMixin):
app: Flask,
user_1: User,
sport_1_cycling: Sport,
seven_workouts_user_1: Workout,
seven_workouts_user_1: List[Workout],
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
@ -174,18 +167,21 @@ class TestGetWorkoutsWithPagination(ApiTestCaseMixin):
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['workouts']) == 5
assert 'creation_date' in data['data']['workouts'][0]
assert (
'Wed, 09 May 2018 00:00:00 GMT'
== data['data']['workouts'][0]['workout_date']
assert data['data']['workouts'][0] == jsonify_dict(
seven_workouts_user_1[6].serialize()
)
assert '0:50:00' == data['data']['workouts'][0]['duration']
assert 'creation_date' in data['data']['workouts'][4]
assert (
'Mon, 01 Jan 2018 00:00:00 GMT'
== data['data']['workouts'][4]['workout_date']
assert data['data']['workouts'][1] == jsonify_dict(
seven_workouts_user_1[5].serialize()
)
assert data['data']['workouts'][2] == jsonify_dict(
seven_workouts_user_1[3].serialize()
)
assert data['data']['workouts'][3] == jsonify_dict(
seven_workouts_user_1[4].serialize()
)
assert data['data']['workouts'][4] == jsonify_dict(
seven_workouts_user_1[2].serialize()
)
assert '0:17:04' == data['data']['workouts'][4]['duration']
assert data['pagination'] == {
'has_next': True,
'has_prev': False,
@ -199,7 +195,7 @@ class TestGetWorkoutsWithPagination(ApiTestCaseMixin):
app: Flask,
user_1: User,
sport_1_cycling: Sport,
seven_workouts_user_1: Workout,
seven_workouts_user_1: List[Workout],
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
@ -214,18 +210,12 @@ class TestGetWorkoutsWithPagination(ApiTestCaseMixin):
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['workouts']) == 2
assert 'creation_date' in data['data']['workouts'][0]
assert (
'Thu, 01 Jun 2017 00:00:00 GMT'
== data['data']['workouts'][0]['workout_date']
assert data['data']['workouts'][0] == jsonify_dict(
seven_workouts_user_1[1].serialize()
)
assert '0:57:36' == data['data']['workouts'][0]['duration']
assert 'creation_date' in data['data']['workouts'][1]
assert (
'Mon, 20 Mar 2017 00:00:00 GMT'
== data['data']['workouts'][1]['workout_date']
assert data['data']['workouts'][1] == jsonify_dict(
seven_workouts_user_1[0].serialize()
)
assert '0:17:04' == data['data']['workouts'][1]['duration']
assert data['pagination'] == {
'has_next': False,
'has_prev': True,
@ -239,7 +229,7 @@ class TestGetWorkoutsWithPagination(ApiTestCaseMixin):
app: Flask,
user_1: User,
sport_1_cycling: Sport,
seven_workouts_user_1: Workout,
seven_workouts_user_1: List[Workout],
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
@ -267,7 +257,7 @@ class TestGetWorkoutsWithPagination(ApiTestCaseMixin):
app: Flask,
user_1: User,
sport_1_cycling: Sport,
seven_workouts_user_1: Workout,
seven_workouts_user_1: List[Workout],
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
@ -286,7 +276,7 @@ class TestGetWorkoutsWithPagination(ApiTestCaseMixin):
app: Flask,
user_1: User,
sport_1_cycling: Sport,
seven_workouts_user_1: Workout,
seven_workouts_user_1: List[Workout],
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
@ -301,13 +291,11 @@ class TestGetWorkoutsWithPagination(ApiTestCaseMixin):
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['workouts']) == 6
assert (
'Wed, 09 May 2018 00:00:00 GMT'
== data['data']['workouts'][0]['workout_date']
assert data['data']['workouts'][0] == jsonify_dict(
seven_workouts_user_1[6].serialize()
)
assert (
'Thu, 01 Jun 2017 00:00:00 GMT'
== data['data']['workouts'][5]['workout_date']
assert data['data']['workouts'][5] == jsonify_dict(
seven_workouts_user_1[1].serialize()
)
assert data['pagination'] == {
'has_next': True,
@ -323,7 +311,7 @@ class TestGetWorkoutsWithPagination(ApiTestCaseMixin):
app: Flask,
user_1: User,
sport_1_cycling: Sport,
seven_workouts_user_1: Workout,
seven_workouts_user_1: List[Workout],
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
@ -338,13 +326,14 @@ class TestGetWorkoutsWithPagination(ApiTestCaseMixin):
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['workouts']) == 3
assert (
'Wed, 09 May 2018 00:00:00 GMT'
== data['data']['workouts'][0]['workout_date']
assert data['data']['workouts'][0] == jsonify_dict(
seven_workouts_user_1[6].serialize()
)
assert (
'Fri, 23 Feb 2018 00:00:00 GMT'
== data['data']['workouts'][2]['workout_date']
assert data['data']['workouts'][1] == jsonify_dict(
seven_workouts_user_1[5].serialize()
)
assert data['data']['workouts'][2] == jsonify_dict(
seven_workouts_user_1[4].serialize()
)
assert data['pagination'] == {
'has_next': True,
@ -361,7 +350,7 @@ class TestGetWorkoutsWithOrder(ApiTestCaseMixin):
app: Flask,
user_1: User,
sport_1_cycling: Sport,
seven_workouts_user_1: Workout,
seven_workouts_user_1: List[Workout],
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
@ -397,7 +386,7 @@ class TestGetWorkoutsWithOrder(ApiTestCaseMixin):
app: Flask,
user_1: User,
sport_1_cycling: Sport,
seven_workouts_user_1: Workout,
seven_workouts_user_1: List[Workout],
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
@ -433,7 +422,7 @@ class TestGetWorkoutsWithOrder(ApiTestCaseMixin):
app: Flask,
user_1: User,
sport_1_cycling: Sport,
seven_workouts_user_1: Workout,
seven_workouts_user_1: List[Workout],
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
@ -471,7 +460,7 @@ class TestGetWorkoutsWithOrderBy(ApiTestCaseMixin):
app: Flask,
user_1: User,
sport_1_cycling: Sport,
seven_workouts_user_1: Workout,
seven_workouts_user_1: List[Workout],
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
@ -507,7 +496,7 @@ class TestGetWorkoutsWithOrderBy(ApiTestCaseMixin):
app: Flask,
user_1: User,
sport_1_cycling: Sport,
seven_workouts_user_1: Workout,
seven_workouts_user_1: List[Workout],
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
@ -537,7 +526,7 @@ class TestGetWorkoutsWithOrderBy(ApiTestCaseMixin):
app: Flask,
user_1: User,
sport_1_cycling: Sport,
seven_workouts_user_1: Workout,
seven_workouts_user_1: List[Workout],
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
@ -567,7 +556,7 @@ class TestGetWorkoutsWithOrderBy(ApiTestCaseMixin):
app: Flask,
user_1: User,
sport_1_cycling: Sport,
seven_workouts_user_1: Workout,
seven_workouts_user_1: List[Workout],
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
@ -599,7 +588,7 @@ class TestGetWorkoutsWithFilters(ApiTestCaseMixin):
app: Flask,
user_1: User,
sport_1_cycling: Sport,
seven_workouts_user_1: Workout,
seven_workouts_user_1: List[Workout],
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
@ -625,7 +614,6 @@ class TestGetWorkoutsWithFilters(ApiTestCaseMixin):
'Fri, 23 Feb 2018 00:00:00 GMT'
== data['data']['workouts'][1]['workout_date']
)
assert '0:10:00' == data['data']['workouts'][1]['duration']
assert data['pagination'] == {
'has_next': False,
'has_prev': False,
@ -639,7 +627,7 @@ class TestGetWorkoutsWithFilters(ApiTestCaseMixin):
app: Flask,
user_1: User,
sport_1_cycling: Sport,
seven_workouts_user_1: Workout,
seven_workouts_user_1: List[Workout],
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
@ -667,7 +655,7 @@ class TestGetWorkoutsWithFilters(ApiTestCaseMixin):
app: Flask,
user_1: User,
sport_1_cycling: Sport,
seven_workouts_user_1: Workout,
seven_workouts_user_1: List[Workout],
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
@ -704,7 +692,7 @@ class TestGetWorkoutsWithFilters(ApiTestCaseMixin):
app: Flask,
user_1: User,
sport_1_cycling: Sport,
seven_workouts_user_1: Workout,
seven_workouts_user_1: List[Workout],
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
@ -740,7 +728,7 @@ class TestGetWorkoutsWithFilters(ApiTestCaseMixin):
app: Flask,
user_1: User,
sport_1_cycling: Sport,
seven_workouts_user_1: Workout,
seven_workouts_user_1: List[Workout],
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
@ -776,7 +764,7 @@ class TestGetWorkoutsWithFilters(ApiTestCaseMixin):
app: Flask,
user_1: User,
sport_1_cycling: Sport,
seven_workouts_user_1: Workout,
seven_workouts_user_1: List[Workout],
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
@ -808,7 +796,7 @@ class TestGetWorkoutsWithFilters(ApiTestCaseMixin):
app: Flask,
user_1: User,
sport_1_cycling: Sport,
seven_workouts_user_1: Workout,
seven_workouts_user_1: List[Workout],
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
@ -876,7 +864,7 @@ class TestGetWorkoutsWithFilters(ApiTestCaseMixin):
app: Flask,
user_1: User,
sport_1_cycling: Sport,
seven_workouts_user_1: Workout,
seven_workouts_user_1: List[Workout],
sport_2_running: Sport,
workout_running_user_1: Workout,
) -> None:
@ -912,7 +900,7 @@ class TestGetWorkoutsWithFiltersAndPagination(ApiTestCaseMixin):
app: Flask,
user_1: User,
sport_1_cycling: Sport,
seven_workouts_user_1: Workout,
seven_workouts_user_1: List[Workout],
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
@ -948,7 +936,7 @@ class TestGetWorkoutsWithFiltersAndPagination(ApiTestCaseMixin):
app: Flask,
user_1: User,
sport_1_cycling: Sport,
seven_workouts_user_1: Workout,
seven_workouts_user_1: List[Workout],
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
@ -1001,15 +989,9 @@ class TestGetWorkout(ApiTestCaseMixin):
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['workouts']) == 1
assert 'creation_date' in data['data']['workouts'][0]
assert (
'Mon, 01 Jan 2018 00:00:00 GMT'
== data['data']['workouts'][0]['workout_date']
assert data['data']['workouts'][0] == jsonify_dict(
workout_cycling_user_1.serialize()
)
assert 'test' == data['data']['workouts'][0]['user']
assert 1 == data['data']['workouts'][0]['sport_id']
assert 10.0 == data['data']['workouts'][0]['distance']
assert '1:00:00' == data['data']['workouts'][0]['duration']
def test_it_returns_403_if_workout_belongs_to_a_different_user(
self,

View File

@ -1,15 +1,17 @@
from uuid import UUID
from datetime import timedelta
from flask import Flask
from fittrackee import db
from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout
from fittrackee.workouts.utils.short_id import decode_short_id
from fittrackee.workouts.utils.short_id import encode_uuid
from ..utils import random_string
class TestWorkoutModel:
def test_workout_model(
def test_sport_label_and_date_are_in_string_value(
self,
app: Flask,
sport_1_cycling: Sport,
@ -18,48 +20,109 @@ class TestWorkoutModel:
) -> None:
workout_cycling_user_1.title = 'Test'
db.session.commit()
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
def test_short_id_returns_encoded_workout_uuid(
self,
app: Flask,
sport_1_cycling: Sport,
user_1: User,
workout_cycling_user_1: Workout,
) -> None:
assert workout_cycling_user_1.short_id == encode_uuid(
workout_cycling_user_1.uuid
)
def test_serialize_for_workout_without_gpx(
self,
app: Flask,
sport_1_cycling: Sport,
user_1: User,
workout_cycling_user_1: Workout,
) -> None:
workout = workout_cycling_user_1
serialized_workout = workout.serialize()
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['ave_speed'] == float(workout.ave_speed)
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 'creation_date' in serialized_workout
assert serialized_workout['descent'] is None
assert serialized_workout['distance'] == float(workout.distance)
assert serialized_workout['duration'] == str(workout.duration)
assert serialized_workout['id'] == workout.short_id
assert serialized_workout['map'] is None
assert serialized_workout['weather_start'] is None
assert serialized_workout['weather_end'] is None
assert serialized_workout['max_alt'] is None
assert serialized_workout['max_speed'] == float(workout.max_speed)
assert serialized_workout['min_alt'] is None
assert serialized_workout['modification_date'] is None
assert serialized_workout['moving'] == str(workout.moving)
assert serialized_workout['next_workout'] is None
assert serialized_workout['notes'] is None
assert serialized_workout['pauses'] is None
assert serialized_workout['previous_workout'] is None
assert serialized_workout['records'] == [
record.serialize() for record in workout.records
]
assert serialized_workout['segments'] == []
assert serialized_workout['sport_id'] == workout.sport_id
assert serialized_workout['title'] == workout.title
assert serialized_workout['user'] == workout.user.username
assert serialized_workout['weather_end'] is None
assert serialized_workout['weather_start'] is None
assert serialized_workout['with_gpx'] is False
assert str(serialized_workout['workout_date']) == '2018-01-01 00:00:00'
def test_serialize_for_workout_with_gpx(
self,
app: Flask,
sport_1_cycling: Sport,
user_1: User,
workout_cycling_user_1: Workout,
workout_cycling_user_1_segment: Workout,
) -> None:
workout = workout_cycling_user_1
workout.bounds = [1, 2, 3, 4]
workout.gpx = random_string()
workout.map = random_string()
workout.pauses = timedelta(minutes=15)
serialized_workout = workout.serialize()
assert serialized_workout['ascent'] is None
assert serialized_workout['ave_speed'] == float(workout.ave_speed)
assert serialized_workout['bounds'] == [
float(bound) for bound in workout.bounds
]
assert 'creation_date' in serialized_workout
assert serialized_workout['descent'] is None
assert serialized_workout['distance'] == float(workout.distance)
assert serialized_workout['duration'] == str(workout.duration)
assert serialized_workout['id'] == workout.short_id
assert serialized_workout['map'] is None
assert serialized_workout['max_alt'] is None
assert serialized_workout['max_speed'] == float(workout.max_speed)
assert serialized_workout['min_alt'] is None
assert serialized_workout['modification_date'] is not None
assert serialized_workout['moving'] == str(workout.moving)
assert serialized_workout['next_workout'] is None
assert serialized_workout['notes'] is None
assert serialized_workout['pauses'] == str(workout.pauses)
assert serialized_workout['previous_workout'] is None
assert serialized_workout['records'] == [
record.serialize() for record in workout.records
]
assert serialized_workout['segments'] == [
segment.serialize() for segment in workout.segments
]
assert serialized_workout['sport_id'] == workout.sport_id
assert serialized_workout['title'] == workout.title
assert serialized_workout['user'] == workout.user.username
assert serialized_workout['weather_end'] is None
assert serialized_workout['weather_start'] is None
assert serialized_workout['with_gpx'] is True
assert str(serialized_workout['workout_date']) == '2018-01-01 00:00:00'
def test_workout_segment_model(
self,
@ -74,3 +137,35 @@ class TestWorkoutModel:
f'for workout \'{workout_cycling_user_1.short_id}\'>'
== str(workout_cycling_user_1_segment)
)
def test_it_returns_previous_workout(
self,
app: Flask,
sport_1_cycling: Sport,
sport_2_running: Sport,
user_1: User,
workout_cycling_user_1: Workout,
workout_running_user_1: Workout,
) -> None:
serialized_workout = workout_running_user_1.serialize()
assert (
serialized_workout['previous_workout']
== workout_cycling_user_1.short_id
)
def test_it_returns_next_workout(
self,
app: Flask,
sport_1_cycling: Sport,
sport_2_running: Sport,
user_1: User,
workout_cycling_user_1: Workout,
workout_running_user_1: Workout,
) -> None:
serialized_workout = workout_cycling_user_1.serialize()
assert (
serialized_workout['next_workout']
== workout_running_user_1.short_id
)

View File

@ -791,8 +791,9 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]:
}
:<json string timezone: user time zone
:<json string weekm: does week start on Monday?
:<json boolean weekm: does week start on Monday?
:<json string language: language preferences
:<json boolean imperial_units: display distance in imperial units
:reqheader Authorization: OAuth 2.0 Bearer Token

View File

@ -249,12 +249,13 @@ def get_users(auth_user: User) -> Dict:
@users_blueprint.route('/users/<user_name>', methods=['GET'])
@authenticate_as_admin
@authenticate
def get_single_user(
auth_user: User, user_name: str
) -> Union[Dict, HttpResponse]:
"""
Get single user details. Only user with admin rights can get user details.
Get single user details. Only user with admin rights can get other users
details.
It returns user preferences only for authenticated user.
@ -353,6 +354,9 @@ def get_single_user(
:statuscode 404:
- user does not exist
"""
if user_name != auth_user.username and not auth_user.admin:
return ForbiddenErrorResponse()
try:
user = User.query.filter_by(username=user_name).first()
if user:

View File

@ -1,6 +1,6 @@
{
"name": "fittrackee_client",
"version": "0.6.1",
"version": "0.6.2",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
@ -22,7 +22,7 @@
"chartjs-plugin-datalabels": "^2.0.0",
"core-js": "^3.21.1",
"date-fns": "^2.28.0",
"date-fns-tz": "^1.3.1",
"date-fns-tz": "^1.3.3",
"leaflet": "^1.7.1",
"register-service-worker": "^1.7.1",
"vue": "^3.0.0",
@ -36,8 +36,8 @@
"@intlify/vue-i18n-loader": "^4.0.1",
"@types/chai": "^4.2.11",
"@types/mocha": "^9.1.0",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"@typescript-eslint/eslint-plugin": "^5.17.0",
"@typescript-eslint/parser": "^5.17.0",
"@vue/cli-plugin-babel": "~5.0.1",
"@vue/cli-plugin-eslint": "~5.0.1",
"@vue/cli-plugin-pwa": "~5.0.1",
@ -51,14 +51,14 @@
"chai": "^4.3.6",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-typescript": "^2.7.0",
"eslint-import-resolver-typescript": "^2.7.1",
"eslint-plugin-import": "^2.24.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.5.0",
"prettier": "^2.6.1",
"sass": "^1.49.9",
"prettier": "^2.6.2",
"sass": "^1.49.11",
"sass-loader": "^12.6.0",
"typescript": "~4.5.5",
"typescript": "^4.6.3",
"vue-cli-plugin-i18n": "~2.3.1"
},
"eslintConfig": {

View File

@ -29,10 +29,10 @@
import StatCard from '@/components/Common/StatCard.vue'
import { TUnit } from '@/types/units'
import { IUserProfile } from '@/types/user'
import { IAuthUserProfile } from '@/types/user'
import { convertDistance, units } from '@/utils/units'
interface Props {
user: IUserProfile
user: IAuthUserProfile
}
const props = defineProps<Props>()
@ -47,9 +47,11 @@
const unitTo: TUnit = user.value.imperial_units
? units[defaultUnitFrom].defaultTarget
: defaultUnitFrom
const totalDistance = user.value.imperial_units
? convertDistance(user.value.total_distance, defaultUnitFrom, unitTo, 2)
: parseFloat(user.value.total_distance.toFixed(2))
const totalDistance: ComputedRef<number> = computed(() =>
user.value.imperial_units
? convertDistance(user.value.total_distance, defaultUnitFrom, unitTo, 2)
: parseFloat(user.value.total_distance.toFixed(2))
)
function get_duration(total_duration: ComputedRef<string>) {
const duration = total_duration.value.match(/day/g)

View File

@ -263,7 +263,7 @@ const routes: Array<RouteRecordRaw> = [
},
{
path: 'users/:username',
name: 'User',
name: 'UserFromAdmin',
component: () =>
import(/* webpackChunkName: 'profile' */ '@/views/user/UserView.vue'),
props: { fromAdmin: true },

View File

@ -1456,7 +1456,7 @@
dependencies:
"@types/node" "*"
"@typescript-eslint/eslint-plugin@^5.0.0", "@typescript-eslint/eslint-plugin@^5.4.0":
"@typescript-eslint/eslint-plugin@^5.0.0":
version "5.16.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.16.0.tgz#78f246dd8d1b528fc5bfca99a8a64d4023a3d86d"
integrity sha512-SJoba1edXvQRMmNI505Uo4XmGbxCK9ARQpkvOd00anxzri9RNQk0DDCxD+LIl+jYhkzOJiOMMKYEHnHEODjdCw==
@ -1471,7 +1471,22 @@
semver "^7.3.5"
tsutils "^3.21.0"
"@typescript-eslint/parser@^5.0.0", "@typescript-eslint/parser@^5.4.0":
"@typescript-eslint/eslint-plugin@^5.17.0":
version "5.17.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.17.0.tgz#704eb4e75039000531255672bf1c85ee85cf1d67"
integrity sha512-qVstvQilEd89HJk3qcbKt/zZrfBZ+9h2ynpAGlWjWiizA7m/MtLT9RoX6gjtpE500vfIg8jogAkDzdCxbsFASQ==
dependencies:
"@typescript-eslint/scope-manager" "5.17.0"
"@typescript-eslint/type-utils" "5.17.0"
"@typescript-eslint/utils" "5.17.0"
debug "^4.3.2"
functional-red-black-tree "^1.0.1"
ignore "^5.1.8"
regexpp "^3.2.0"
semver "^7.3.5"
tsutils "^3.21.0"
"@typescript-eslint/parser@^5.0.0":
version "5.16.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.16.0.tgz#e4de1bde4b4dad5b6124d3da227347616ed55508"
integrity sha512-fkDq86F0zl8FicnJtdXakFs4lnuebH6ZADDw6CYQv0UZeIjHvmEw87m9/29nk2Dv5Lmdp0zQ3zDQhiMWQf/GbA==
@ -1481,6 +1496,16 @@
"@typescript-eslint/typescript-estree" "5.16.0"
debug "^4.3.2"
"@typescript-eslint/parser@^5.17.0":
version "5.17.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.17.0.tgz#7def77d5bcd8458d12d52909118cf3f0a45f89d5"
integrity sha512-aRzW9Jg5Rlj2t2/crzhA2f23SIYFlF9mchGudyP0uiD6SenIxzKoLjwzHbafgHn39dNV/TV7xwQkLfFTZlJ4ig==
dependencies:
"@typescript-eslint/scope-manager" "5.17.0"
"@typescript-eslint/types" "5.17.0"
"@typescript-eslint/typescript-estree" "5.17.0"
debug "^4.3.2"
"@typescript-eslint/scope-manager@5.16.0":
version "5.16.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.16.0.tgz#7e7909d64bd0c4d8aef629cdc764b9d3e1d3a69a"
@ -1489,6 +1514,14 @@
"@typescript-eslint/types" "5.16.0"
"@typescript-eslint/visitor-keys" "5.16.0"
"@typescript-eslint/scope-manager@5.17.0":
version "5.17.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.17.0.tgz#4cea7d0e0bc0e79eb60cad431c89120987c3f952"
integrity sha512-062iCYQF/doQ9T2WWfJohQKKN1zmmXVfAcS3xaiialiw8ZUGy05Em6QVNYJGO34/sU1a7a+90U3dUNfqUDHr3w==
dependencies:
"@typescript-eslint/types" "5.17.0"
"@typescript-eslint/visitor-keys" "5.17.0"
"@typescript-eslint/type-utils@5.16.0":
version "5.16.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.16.0.tgz#b482bdde1d7d7c0c7080f7f2f67ea9580b9e0692"
@ -1498,11 +1531,25 @@
debug "^4.3.2"
tsutils "^3.21.0"
"@typescript-eslint/type-utils@5.17.0":
version "5.17.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.17.0.tgz#1c4549d68c89877662224aabb29fbbebf5fc9672"
integrity sha512-3hU0RynUIlEuqMJA7dragb0/75gZmwNwFf/QJokWzPehTZousP/MNifVSgjxNcDCkM5HI2K22TjQWUmmHUINSg==
dependencies:
"@typescript-eslint/utils" "5.17.0"
debug "^4.3.2"
tsutils "^3.21.0"
"@typescript-eslint/types@5.16.0":
version "5.16.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.16.0.tgz#5827b011982950ed350f075eaecb7f47d3c643ee"
integrity sha512-oUorOwLj/3/3p/HFwrp6m/J2VfbLC8gjW5X3awpQJ/bSG+YRGFS4dpsvtQ8T2VNveV+LflQHjlLvB6v0R87z4g==
"@typescript-eslint/types@5.17.0":
version "5.17.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.17.0.tgz#861ec9e669ffa2aa9b873dd4d28d9b1ce26d216f"
integrity sha512-AgQ4rWzmCxOZLioFEjlzOI3Ch8giDWx8aUDxyNw9iOeCvD3GEYAB7dxWGQy4T/rPVe8iPmu73jPHuaSqcjKvxw==
"@typescript-eslint/typescript-estree@5.16.0":
version "5.16.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.16.0.tgz#32259459ec62f5feddca66adc695342f30101f61"
@ -1516,6 +1563,19 @@
semver "^7.3.5"
tsutils "^3.21.0"
"@typescript-eslint/typescript-estree@5.17.0":
version "5.17.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.17.0.tgz#a7cba7dfc8f9cc2ac78c18584e684507df4f2488"
integrity sha512-X1gtjEcmM7Je+qJRhq7ZAAaNXYhTgqMkR10euC4Si6PIjb+kwEQHSxGazXUQXFyqfEXdkGf6JijUu5R0uceQzg==
dependencies:
"@typescript-eslint/types" "5.17.0"
"@typescript-eslint/visitor-keys" "5.17.0"
debug "^4.3.2"
globby "^11.0.4"
is-glob "^4.0.3"
semver "^7.3.5"
tsutils "^3.21.0"
"@typescript-eslint/utils@5.16.0":
version "5.16.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.16.0.tgz#42218b459d6d66418a4eb199a382bdc261650679"
@ -1528,6 +1588,18 @@
eslint-scope "^5.1.1"
eslint-utils "^3.0.0"
"@typescript-eslint/utils@5.17.0":
version "5.17.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.17.0.tgz#549a9e1d491c6ccd3624bc3c1b098f5cfb45f306"
integrity sha512-DVvndq1QoxQH+hFv+MUQHrrWZ7gQ5KcJzyjhzcqB1Y2Xes1UQQkTRPUfRpqhS8mhTWsSb2+iyvDW1Lef5DD7vA==
dependencies:
"@types/json-schema" "^7.0.9"
"@typescript-eslint/scope-manager" "5.17.0"
"@typescript-eslint/types" "5.17.0"
"@typescript-eslint/typescript-estree" "5.17.0"
eslint-scope "^5.1.1"
eslint-utils "^3.0.0"
"@typescript-eslint/visitor-keys@5.16.0":
version "5.16.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.16.0.tgz#f27dc3b943e6317264c7492e390c6844cd4efbbb"
@ -1536,6 +1608,14 @@
"@typescript-eslint/types" "5.16.0"
eslint-visitor-keys "^3.0.0"
"@typescript-eslint/visitor-keys@5.17.0":
version "5.17.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.17.0.tgz#52daae45c61b0211b4c81b53a71841911e479128"
integrity sha512-6K/zlc4OfCagUu7Am/BD5k8PSWQOgh34Nrv9Rxe2tBzlJ7uOeJ/h7ugCGDCeEZHT6k2CJBhbk9IsbkPI0uvUkA==
dependencies:
"@typescript-eslint/types" "5.17.0"
eslint-visitor-keys "^3.0.0"
"@ungap/promise-all-settled@1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44"
@ -3198,10 +3278,10 @@ data-urls@^3.0.1:
whatwg-mimetype "^3.0.0"
whatwg-url "^10.0.0"
date-fns-tz@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-1.3.1.tgz#88b6374e5a74cfb11ec2d2b120cfe0bc02eeb862"
integrity sha512-Uy+wph6HcQ0IG8TWbVyXicgDmB1zdvb0CoIknZQaxiTun4uSfxLR+8gSTC2C3KCLq+0fEIuEtJ/ORDRIn6doQw==
date-fns-tz@^1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-1.3.3.tgz#7884a4b3ed6cd95bfd81831d608e5ef8be500c86"
integrity sha512-Gks46gwbSauBQnV3Oofluj1wTm8J0tM7sbSJ9P+cJq/ZnTCpMohTKmmO5Tn+jQ7dyn0+b8G7cY4O2DZ5P/LXcA==
date-fns@^2.28.0:
version "2.28.0"
@ -3646,10 +3726,10 @@ eslint-import-resolver-node@^0.3.6:
debug "^3.2.7"
resolve "^1.20.0"
eslint-import-resolver-typescript@^2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.7.0.tgz#1f9d391b636dccdbaa4a3b1a87eb9a8237e23963"
integrity sha512-MNHS3u5pebvROX4MjGP9coda589ZGfL1SqdxUV4kSrcclfDRWvNE2D+eljbnWVMvWDVRgT89nhscMHPKYGcObQ==
eslint-import-resolver-typescript@^2.7.1:
version "2.7.1"
resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.7.1.tgz#a90a4a1c80da8d632df25994c4c5fdcdd02b8751"
integrity sha512-00UbgGwV8bSgUv34igBDbTOtKhqoRMy9bFjNehT40bXg6585PNIct8HhXZ0SybqB9rWtXj9crcku8ndDn/gIqQ==
dependencies:
debug "^4.3.4"
glob "^7.2.0"
@ -6317,11 +6397,16 @@ prettier-linter-helpers@^1.0.0:
dependencies:
fast-diff "^1.1.2"
"prettier@^1.18.2 || ^2.0.0", prettier@^2.6.1:
"prettier@^1.18.2 || ^2.0.0":
version "2.6.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.1.tgz#d472797e0d7461605c1609808e27b80c0f9cfe17"
integrity sha512-8UVbTBYGwN37Bs9LERmxCPjdvPxlEowx2urIL6urHzdb3SDq4B/Z6xLFCblrSnE4iKWcS6ziJ3aOYrc1kz/E2A==
prettier@^2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.2.tgz#e26d71a18a74c3d0f0597f55f01fb6c06c206032"
integrity sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==
pretty-bytes@^5.3.0, pretty-bytes@^5.4.1:
version "5.6.0"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
@ -6673,10 +6758,10 @@ sass-loader@^12.6.0:
klona "^2.0.4"
neo-async "^2.6.2"
sass@^1.49.9:
version "1.49.9"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.49.9.tgz#b15a189ecb0ca9e24634bae5d1ebc191809712f9"
integrity sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A==
sass@^1.49.11:
version "1.49.11"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.49.11.tgz#1ffeb77faeed8b806a2a1e021d7c9fd3fc322cb7"
integrity sha512-wvS/geXgHUGs6A/4ud5BFIWKO1nKd7wYIGimDk4q4GFkJicILActpv9ueMT4eRGSsp1BdKHuw1WwAHXbhsJELQ==
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"
@ -7471,10 +7556,10 @@ type-is@~1.6.18:
media-typer "0.3.0"
mime-types "~2.1.24"
typescript@~4.5.5:
version "4.5.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3"
integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
typescript@^4.6.3:
version "4.6.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c"
integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==
unbox-primitive@^1.0.1:
version "1.0.1"

223
poetry.lock generated
View File

@ -93,7 +93,7 @@ typecheck = ["mypy"]
[[package]]
name = "black"
version = "22.1.0"
version = "22.3.0"
description = "The uncompromising code formatter."
category = "dev"
optional = false
@ -104,7 +104,7 @@ click = ">=8.0.0"
mypy-extensions = ">=0.4.3"
pathspec = ">=0.9.0"
platformdirs = ">=2"
tomli = ">=1.1.0"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""}
typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
@ -146,11 +146,11 @@ unicode_backport = ["unicodedata2"]
[[package]]
name = "click"
version = "8.0.4"
version = "8.1.2"
description = "Composable command line interface toolkit"
category = "main"
optional = false
python-versions = ">=3.6"
python-versions = ">=3.7"
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
@ -232,7 +232,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "dramatiq"
version = "1.12.3"
version = "1.13.0"
description = "Background Processing for Python 3."
category = "main"
optional = false
@ -243,8 +243,8 @@ prometheus-client = ">=0.2"
redis = {version = ">=2.0,<5.0", optional = true, markers = "extra == \"redis\""}
[package.extras]
all = ["gevent (>=1.1)", "redis (>=2.0,<5.0)", "watchdog", "pika (>=1.0,<2.0)", "watchdog-gevent", "pylibmc (>=1.5,<2.0)"]
dev = ["gevent (>=1.1)", "redis (>=2.0,<5.0)", "watchdog", "pika (>=1.0,<2.0)", "watchdog-gevent", "pylibmc (>=1.5,<2.0)", "alabaster", "sphinx (<1.8)", "sphinxcontrib-napoleon", "flake8", "flake8-bugbear", "flake8-quotes", "isort", "bumpversion", "hiredis", "twine", "wheel", "pytest", "pytest-benchmark", "pytest-cov", "tox"]
all = ["redis (>=2.0,<5.0)", "pylibmc (>=1.5,<2.0)", "watchdog", "watchdog-gevent", "pika (>=1.0,<2.0)", "gevent (>=1.1)"]
dev = ["redis (>=2.0,<5.0)", "pylibmc (>=1.5,<2.0)", "watchdog", "watchdog-gevent", "pika (>=1.0,<2.0)", "gevent (>=1.1)", "alabaster", "sphinx (<1.8)", "sphinxcontrib-napoleon", "flake8", "flake8-bugbear", "flake8-quotes", "isort", "bumpversion", "hiredis", "twine", "wheel", "pytest", "pytest-benchmark", "pytest-cov", "tox"]
gevent = ["gevent (>=1.1)"]
memcached = ["pylibmc (>=1.5,<2.0)"]
rabbitmq = ["pika (>=1.0,<2.0)"]
@ -267,14 +267,15 @@ pyflakes = ">=2.3.0,<2.4.0"
[[package]]
name = "flask"
version = "2.0.3"
version = "2.1.1"
description = "A simple framework for building complex web applications."
category = "main"
optional = false
python-versions = ">=3.6"
python-versions = ">=3.7"
[package.dependencies]
click = ">=7.1.2"
click = ">=8.0"
importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""}
itsdangerous = ">=2.0"
Jinja2 = ">=3.0"
Werkzeug = ">=2.0"
@ -285,14 +286,14 @@ dotenv = ["python-dotenv"]
[[package]]
name = "flask-bcrypt"
version = "0.7.1"
version = "1.0.0"
description = "Brcrypt hashing for Flask."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
bcrypt = "*"
bcrypt = ">=3.1.1"
Flask = "*"
[[package]]
@ -584,12 +585,16 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
[[package]]
name = "pillow"
version = "9.0.1"
version = "9.1.0"
description = "Python Imaging Library (Fork)"
category = "main"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinx-rtd-theme (>=1.0)", "sphinxext-opengraph"]
tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
[[package]]
name = "platformdirs"
version = "2.5.1"
@ -748,14 +753,14 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.
[[package]]
name = "pytest-base-url"
version = "1.4.2"
version = "2.0.0"
description = "pytest plugin for URL based testing"
category = "dev"
optional = false
python-versions = "*"
python-versions = ">=3.7,<4.0"
[package.dependencies]
pytest = ">=2.7.3"
pytest = ">=3.0.0,<8.0.0"
requests = ">=2.9"
[[package]]
@ -928,7 +933,7 @@ sphinx = ">=1.3.1"
[[package]]
name = "redis"
version = "4.2.0"
version = "4.2.1"
description = "Python client for Redis database and key-value store"
category = "main"
optional = false
@ -939,7 +944,7 @@ async-timeout = ">=4.0.2"
deprecated = ">=1.2.3"
importlib-metadata = {version = ">=1.0", markers = "python_version < \"3.8\""}
packaging = ">=20.4"
typing-extensions = "*"
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
[package.extras]
hiredis = ["hiredis (>=1.0.0)"]
@ -1033,7 +1038,7 @@ python-versions = "*"
[[package]]
name = "sphinx"
version = "4.4.0"
version = "4.5.0"
description = "Python documentation generator"
category = "dev"
optional = false
@ -1269,7 +1274,7 @@ python-versions = ">=3.6"
[[package]]
name = "types-freezegun"
version = "1.1.7"
version = "1.1.8"
description = "Typing stubs for freezegun"
category = "dev"
optional = false
@ -1285,7 +1290,7 @@ python-versions = "*"
[[package]]
name = "types-requests"
version = "2.27.15"
version = "2.27.16"
description = "Typing stubs for requests"
category = "dev"
optional = false
@ -1310,6 +1315,14 @@ category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
name = "ua-parser"
version = "0.10.0"
description = "Python port of Browserscope's user agent parser"
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "urllib3"
version = "1.26.9"
@ -1332,11 +1345,11 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "werkzeug"
version = "2.0.3"
version = "2.1.1"
description = "The comprehensive WSGI web application library."
category = "main"
optional = false
python-versions = ">=3.6"
python-versions = ">=3.7"
[package.extras]
watchdog = ["watchdog"]
@ -1375,7 +1388,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
[metadata]
lock-version = "1.1"
python-versions = "^3.7"
content-hash = "545f5ef79ba67d5a2d2df007fb5ac67ca37630efd4ff04c3a15e181c73b4f466"
content-hash = "e130b306957d6577ecd21cc1a19daa81073d5bbe77ba9f6a60ff83d8af0a2f05"
[metadata.files]
alabaster = [
@ -1416,29 +1429,29 @@ bcrypt = [
{file = "bcrypt-3.2.0.tar.gz", hash = "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29"},
]
black = [
{file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"},
{file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"},
{file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"},
{file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"},
{file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"},
{file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"},
{file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"},
{file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"},
{file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"},
{file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"},
{file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"},
{file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"},
{file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"},
{file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"},
{file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"},
{file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"},
{file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"},
{file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"},
{file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"},
{file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"},
{file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"},
{file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"},
{file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"},
{file = "black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09"},
{file = "black-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb"},
{file = "black-22.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a"},
{file = "black-22.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968"},
{file = "black-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"},
{file = "black-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce"},
{file = "black-22.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82"},
{file = "black-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b"},
{file = "black-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015"},
{file = "black-22.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b"},
{file = "black-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a"},
{file = "black-22.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163"},
{file = "black-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464"},
{file = "black-22.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0"},
{file = "black-22.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176"},
{file = "black-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0"},
{file = "black-22.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20"},
{file = "black-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a"},
{file = "black-22.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad"},
{file = "black-22.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21"},
{file = "black-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265"},
{file = "black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72"},
{file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"},
]
certifi = [
{file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
@ -1501,8 +1514,8 @@ charset-normalizer = [
{file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
]
click = [
{file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"},
{file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"},
{file = "click-8.1.2-py3-none-any.whl", hash = "sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e"},
{file = "click-8.1.2.tar.gz", hash = "sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72"},
]
colorama = [
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
@ -1586,19 +1599,20 @@ docutils = [
{file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"},
]
dramatiq = [
{file = "dramatiq-1.12.3-py3-none-any.whl", hash = "sha256:eccb0f54d44ebd9e2c79e00d67b808397589a1a621ba7c5fd58df5fb6204a0a8"},
{file = "dramatiq-1.12.3.tar.gz", hash = "sha256:380bd77b6b19d642f417b642935049ff71ddf4b4e57d821e4f55b92541430f21"},
{file = "dramatiq-1.13.0-py3-none-any.whl", hash = "sha256:8ef7509ca62bc45c3f1e3b1a0248e9f774337100e32ba1502cfcca15df79ad61"},
{file = "dramatiq-1.13.0.tar.gz", hash = "sha256:b4fe0ca6b55b06bebf82cd14c88044fb267505a57d4aa47378194efa0cef5f47"},
]
flake8 = [
{file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"},
{file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"},
]
flask = [
{file = "Flask-2.0.3-py3-none-any.whl", hash = "sha256:59da8a3170004800a2837844bfa84d49b022550616070f7cb1a659682b2e7c9f"},
{file = "Flask-2.0.3.tar.gz", hash = "sha256:e1120c228ca2f553b470df4a5fa927ab66258467526069981b3eb0a91902687d"},
{file = "Flask-2.1.1-py3-none-any.whl", hash = "sha256:8a4cf32d904cf5621db9f0c9fbcd7efabf3003f22a04e4d0ce790c7137ec5264"},
{file = "Flask-2.1.1.tar.gz", hash = "sha256:a8c9bd3e558ec99646d177a9739c41df1ded0629480b4c8d2975412f3c9519c8"},
]
flask-bcrypt = [
{file = "Flask-Bcrypt-0.7.1.tar.gz", hash = "sha256:d71c8585b2ee1c62024392ebdbc447438564e2c8c02b4e57b56a4cafd8d13c5f"},
{file = "Flask-Bcrypt-1.0.0.tar.gz", hash = "sha256:e622fbd3b0bf63d516b8844fe3431fc30213592412b430036c3928a0c52dfb27"},
{file = "Flask_Bcrypt-1.0.0-py3-none-any.whl", hash = "sha256:64a947e15ff06823c3843f4826d4548dfae038c7df8f98ea82755db62092ec4c"},
]
flask-dramatiq = [
{file = "flask-dramatiq-0.6.0.tar.gz", hash = "sha256:63709e73d7c8d2e5d9bc554d1e859d91c5c5c9a4ebc9461752655bf1e0b87420"},
@ -1806,41 +1820,44 @@ pathspec = [
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
]
pillow = [
{file = "Pillow-9.0.1-1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a5d24e1d674dd9d72c66ad3ea9131322819ff86250b30dc5821cbafcfa0b96b4"},
{file = "Pillow-9.0.1-1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2632d0f846b7c7600edf53c48f8f9f1e13e62f66a6dbc15191029d950bfed976"},
{file = "Pillow-9.0.1-1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9618823bd237c0d2575283f2939655f54d51b4527ec3972907a927acbcc5bfc"},
{file = "Pillow-9.0.1-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:9bfdb82cdfeccec50aad441afc332faf8606dfa5e8efd18a6692b5d6e79f00fd"},
{file = "Pillow-9.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5100b45a4638e3c00e4d2320d3193bdabb2d75e79793af7c3eb139e4f569f16f"},
{file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:528a2a692c65dd5cafc130de286030af251d2ee0483a5bf50c9348aefe834e8a"},
{file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f29d831e2151e0b7b39981756d201f7108d3d215896212ffe2e992d06bfe049"},
{file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:855c583f268edde09474b081e3ddcd5cf3b20c12f26e0d434e1386cc5d318e7a"},
{file = "Pillow-9.0.1-cp310-cp310-win32.whl", hash = "sha256:d9d7942b624b04b895cb95af03a23407f17646815495ce4547f0e60e0b06f58e"},
{file = "Pillow-9.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81c4b81611e3a3cb30e59b0cf05b888c675f97e3adb2c8672c3154047980726b"},
{file = "Pillow-9.0.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:413ce0bbf9fc6278b2d63309dfeefe452835e1c78398efb431bab0672fe9274e"},
{file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80fe64a6deb6fcfdf7b8386f2cf216d329be6f2781f7d90304351811fb591360"},
{file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cef9c85ccbe9bee00909758936ea841ef12035296c748aaceee535969e27d31b"},
{file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d19397351f73a88904ad1aee421e800fe4bbcd1aeee6435fb62d0a05ccd1030"},
{file = "Pillow-9.0.1-cp37-cp37m-win32.whl", hash = "sha256:d21237d0cd37acded35154e29aec853e945950321dd2ffd1a7d86fe686814669"},
{file = "Pillow-9.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ede5af4a2702444a832a800b8eb7f0a7a1c0eed55b644642e049c98d589e5092"},
{file = "Pillow-9.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b5b3f092fe345c03bca1e0b687dfbb39364b21ebb8ba90e3fa707374b7915204"},
{file = "Pillow-9.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:335ace1a22325395c4ea88e00ba3dc89ca029bd66bd5a3c382d53e44f0ccd77e"},
{file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db6d9fac65bd08cea7f3540b899977c6dee9edad959fa4eaf305940d9cbd861c"},
{file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f154d173286a5d1863637a7dcd8c3437bb557520b01bddb0be0258dcb72696b5"},
{file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d4b1341ac07ae07eb2cc682f459bec932a380c3b122f5540432d8977e64eae"},
{file = "Pillow-9.0.1-cp38-cp38-win32.whl", hash = "sha256:effb7749713d5317478bb3acb3f81d9d7c7f86726d41c1facca068a04cf5bb4c"},
{file = "Pillow-9.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:7f7609a718b177bf171ac93cea9fd2ddc0e03e84d8fa4e887bdfc39671d46b00"},
{file = "Pillow-9.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:80ca33961ced9c63358056bd08403ff866512038883e74f3a4bf88ad3eb66838"},
{file = "Pillow-9.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c3c33ac69cf059bbb9d1a71eeaba76781b450bc307e2291f8a4764d779a6b28"},
{file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12875d118f21cf35604176872447cdb57b07126750a33748bac15e77f90f1f9c"},
{file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:514ceac913076feefbeaf89771fd6febde78b0c4c1b23aaeab082c41c694e81b"},
{file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3c5c79ab7dfce6d88f1ba639b77e77a17ea33a01b07b99840d6ed08031cb2a7"},
{file = "Pillow-9.0.1-cp39-cp39-win32.whl", hash = "sha256:718856856ba31f14f13ba885ff13874be7fefc53984d2832458f12c38205f7f7"},
{file = "Pillow-9.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:f25ed6e28ddf50de7e7ea99d7a976d6a9c415f03adcaac9c41ff6ff41b6d86ac"},
{file = "Pillow-9.0.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:011233e0c42a4a7836498e98c1acf5e744c96a67dd5032a6f666cc1fb97eab97"},
{file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253e8a302a96df6927310a9d44e6103055e8fb96a6822f8b7f514bb7ef77de56"},
{file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6295f6763749b89c994fcb6d8a7f7ce03c3992e695f89f00b741b4580b199b7e"},
{file = "Pillow-9.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a9f44cd7e162ac6191491d7249cceb02b8116b0f7e847ee33f739d7cb1ea1f70"},
{file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"},
{file = "Pillow-9.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:af79d3fde1fc2e33561166d62e3b63f0cc3e47b5a3a2e5fea40d4917754734ea"},
{file = "Pillow-9.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:55dd1cf09a1fd7c7b78425967aacae9b0d70125f7d3ab973fadc7b5abc3de652"},
{file = "Pillow-9.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66822d01e82506a19407d1afc104c3fcea3b81d5eb11485e593ad6b8492f995a"},
{file = "Pillow-9.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5eaf3b42df2bcda61c53a742ee2c6e63f777d0e085bbc6b2ab7ed57deb13db7"},
{file = "Pillow-9.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01ce45deec9df310cbbee11104bae1a2a43308dd9c317f99235b6d3080ddd66e"},
{file = "Pillow-9.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:aea7ce61328e15943d7b9eaca87e81f7c62ff90f669116f857262e9da4057ba3"},
{file = "Pillow-9.1.0-cp310-cp310-win32.whl", hash = "sha256:7a053bd4d65a3294b153bdd7724dce864a1d548416a5ef61f6d03bf149205160"},
{file = "Pillow-9.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:97bda660702a856c2c9e12ec26fc6d187631ddfd896ff685814ab21ef0597033"},
{file = "Pillow-9.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:21dee8466b42912335151d24c1665fcf44dc2ee47e021d233a40c3ca5adae59c"},
{file = "Pillow-9.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b6d4050b208c8ff886fd3db6690bf04f9a48749d78b41b7a5bf24c236ab0165"},
{file = "Pillow-9.1.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5cfca31ab4c13552a0f354c87fbd7f162a4fafd25e6b521bba93a57fe6a3700a"},
{file = "Pillow-9.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed742214068efa95e9844c2d9129e209ed63f61baa4d54dbf4cf8b5e2d30ccf2"},
{file = "Pillow-9.1.0-cp37-cp37m-win32.whl", hash = "sha256:c9efef876c21788366ea1f50ecb39d5d6f65febe25ad1d4c0b8dff98843ac244"},
{file = "Pillow-9.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:de344bcf6e2463bb25179d74d6e7989e375f906bcec8cb86edb8b12acbc7dfef"},
{file = "Pillow-9.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:17869489de2fce6c36690a0c721bd3db176194af5f39249c1ac56d0bb0fcc512"},
{file = "Pillow-9.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:25023a6209a4d7c42154073144608c9a71d3512b648a2f5d4465182cb93d3477"},
{file = "Pillow-9.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8782189c796eff29dbb37dd87afa4ad4d40fc90b2742704f94812851b725964b"},
{file = "Pillow-9.1.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:463acf531f5d0925ca55904fa668bb3461c3ef6bc779e1d6d8a488092bdee378"},
{file = "Pillow-9.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f42364485bfdab19c1373b5cd62f7c5ab7cc052e19644862ec8f15bb8af289e"},
{file = "Pillow-9.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3fddcdb619ba04491e8f771636583a7cc5a5051cd193ff1aa1ee8616d2a692c5"},
{file = "Pillow-9.1.0-cp38-cp38-win32.whl", hash = "sha256:4fe29a070de394e449fd88ebe1624d1e2d7ddeed4c12e0b31624561b58948d9a"},
{file = "Pillow-9.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:c24f718f9dd73bb2b31a6201e6db5ea4a61fdd1d1c200f43ee585fc6dcd21b34"},
{file = "Pillow-9.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fb89397013cf302f282f0fc998bb7abf11d49dcff72c8ecb320f76ea6e2c5717"},
{file = "Pillow-9.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c870193cce4b76713a2b29be5d8327c8ccbe0d4a49bc22968aa1e680930f5581"},
{file = "Pillow-9.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69e5ddc609230d4408277af135c5b5c8fe7a54b2bdb8ad7c5100b86b3aab04c6"},
{file = "Pillow-9.1.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35be4a9f65441d9982240e6966c1eaa1c654c4e5e931eaf580130409e31804d4"},
{file = "Pillow-9.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82283af99c1c3a5ba1da44c67296d5aad19f11c535b551a5ae55328a317ce331"},
{file = "Pillow-9.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a325ac71914c5c043fa50441b36606e64a10cd262de12f7a179620f579752ff8"},
{file = "Pillow-9.1.0-cp39-cp39-win32.whl", hash = "sha256:a598d8830f6ef5501002ae85c7dbfcd9c27cc4efc02a1989369303ba85573e58"},
{file = "Pillow-9.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0c51cb9edac8a5abd069fd0758ac0a8bfe52c261ee0e330f363548aca6893595"},
{file = "Pillow-9.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a336a4f74baf67e26f3acc4d61c913e378e931817cd1e2ef4dfb79d3e051b481"},
{file = "Pillow-9.1.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb1b89b11256b5b6cad5e7593f9061ac4624f7651f7a8eb4dfa37caa1dfaa4d0"},
{file = "Pillow-9.1.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:255c9d69754a4c90b0ee484967fc8818c7ff8311c6dddcc43a4340e10cd1636a"},
{file = "Pillow-9.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5a3ecc026ea0e14d0ad7cd990ea7f48bfcb3eb4271034657dc9d06933c6629a7"},
{file = "Pillow-9.1.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5b0ff59785d93b3437c3703e3c64c178aabada51dea2a7f2c5eccf1bcf565a3"},
{file = "Pillow-9.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7110ec1701b0bf8df569a7592a196c9d07c764a0a74f65471ea56816f10e2c8"},
{file = "Pillow-9.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:8d79c6f468215d1a8415aa53d9868a6b40c4682165b8cb62a221b1baa47db458"},
{file = "Pillow-9.1.0.tar.gz", hash = "sha256:f401ed2bbb155e1ade150ccc63db1a4f6c1909d3d378f7d1235a44e90d75fb97"},
]
platformdirs = [
{file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"},
@ -1954,8 +1971,8 @@ pytest = [
{file = "pytest-7.1.1.tar.gz", hash = "sha256:841132caef6b1ad17a9afde46dc4f6cfa59a05f9555aae5151f73bdf2820ca63"},
]
pytest-base-url = [
{file = "pytest-base-url-1.4.2.tar.gz", hash = "sha256:7f1f32e08c2ee751e59e7f5880235b46e83496adc5cba5a01ca218c6fe81333d"},
{file = "pytest_base_url-1.4.2-py2.py3-none-any.whl", hash = "sha256:8b6523a1a3af73c317bdae97b722dfb55a7336733d1ad411eb4a4931347ba77a"},
{file = "pytest-base-url-2.0.0.tar.gz", hash = "sha256:e1e88a4fd221941572ccdcf3bf6c051392d2f8b6cef3e0bc7da95abec4b5346e"},
{file = "pytest_base_url-2.0.0-py3-none-any.whl", hash = "sha256:ed36fd632c32af9f1c08f2c2835dcf42ca8fcd097d6ed44a09f253d365ad8297"},
]
pytest-black = [
{file = "pytest-black-0.3.12.tar.gz", hash = "sha256:1d339b004f764d6cd0f06e690f6dd748df3d62e6fe1a692d6a5500ac2c5b75a5"},
@ -2008,8 +2025,8 @@ recommonmark = [
{file = "recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67"},
]
redis = [
{file = "redis-4.2.0-py3-none-any.whl", hash = "sha256:3cbe235cea80b9c9991b397567aa2d65eb4e6fb09787f61d227ae82eb4eb50b4"},
{file = "redis-4.2.0.tar.gz", hash = "sha256:6758d01dec81af191b98a35cce3402675d115456584c39b500ab485a5e386bbb"},
{file = "redis-4.2.1-py3-none-any.whl", hash = "sha256:69d05fac17bf3f43937afbb775c536eb516bd21355a4f17d59a966f4a531ce71"},
{file = "redis-4.2.1.tar.gz", hash = "sha256:fe45513881229dbee610620b9e0817b1f48c47ba635870320fd44a712204bbdd"},
]
requests = [
{file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
@ -2043,8 +2060,8 @@ sortedcontainers = [
{file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"},
]
sphinx = [
{file = "Sphinx-4.4.0-py3-none-any.whl", hash = "sha256:5da895959511473857b6d0200f56865ed62c31e8f82dd338063b84ec022701fe"},
{file = "Sphinx-4.4.0.tar.gz", hash = "sha256:6caad9786055cb1fa22b4a365c1775816b876f91966481765d7d50e9f0dd35cc"},
{file = "Sphinx-4.5.0-py3-none-any.whl", hash = "sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226"},
{file = "Sphinx-4.5.0.tar.gz", hash = "sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6"},
]
sphinx-bootstrap-theme = [
{file = "sphinx-bootstrap-theme-0.8.1.tar.gz", hash = "sha256:683e3b735448dadd0149f76edecf95ff4bd9157787e9e77e0d048ca6f1d680df"},
@ -2165,16 +2182,16 @@ typed-ast = [
{file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"},
]
types-freezegun = [
{file = "types-freezegun-1.1.7.tar.gz", hash = "sha256:e9d1327e98c6caa8f65de0018ded274347e8e34198d59aa9a5c24ad9265d5e5e"},
{file = "types_freezegun-1.1.7-py3-none-any.whl", hash = "sha256:efa02341ad35e4f24b3346d09b60332e967586a5bc495a56b1c9c927b4c788b6"},
{file = "types-freezegun-1.1.8.tar.gz", hash = "sha256:f4bb08c54aa46816c77a1b5c7a2a54f5393c3ce58e7d4002e67f9081b411e921"},
{file = "types_freezegun-1.1.8-py3-none-any.whl", hash = "sha256:59dd99a6d168bdb5bf884fda6daebd68c35bc86383df183cf182abc6dfcbb7b7"},
]
types-pytz = [
{file = "types-pytz-2021.3.6.tar.gz", hash = "sha256:74547fd90d8d8ab4f1eedf3a344a7d186d97486973895f81221a712e1e2cd993"},
{file = "types_pytz-2021.3.6-py3-none-any.whl", hash = "sha256:6805c72d51118923c5bf98633c39593d5b464d2ab49a803440e2d7ab6b8920df"},
]
types-requests = [
{file = "types-requests-2.27.15.tar.gz", hash = "sha256:2d371183c535208d2cc8fe7473d9b49c344c7077eb70302eb708638fb86086a8"},
{file = "types_requests-2.27.15-py3-none-any.whl", hash = "sha256:77d09182a68e447e9e8b0ffc21abf54618b96f07689dffbb6a41cf0356542969"},
{file = "types-requests-2.27.16.tar.gz", hash = "sha256:c8010c18b291a7efb60b1452dbe12530bc25693dd657e70c62803fcdc4bffe9b"},
{file = "types_requests-2.27.16-py3-none-any.whl", hash = "sha256:2437a5f4d16c0c8bd7539a8126d492b7aeb41e6cda670d76b286c7f83a658d42"},
]
types-urllib3 = [
{file = "types-urllib3-1.26.11.tar.gz", hash = "sha256:24d64e441168851eb05f1d022de18ae31558f5649c8f1117e384c2e85e31315b"},
@ -2184,13 +2201,17 @@ typing-extensions = [
{file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"},
{file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"},
]
ua-parser = [
{file = "ua-parser-0.10.0.tar.gz", hash = "sha256:47b1782ed130d890018d983fac37c2a80799d9e0b9c532e734c67cf70f185033"},
{file = "ua_parser-0.10.0-py2.py3-none-any.whl", hash = "sha256:46ab2e383c01dbd2ab284991b87d624a26a08f72da4d7d413f5bfab8b9036f8a"},
]
urllib3 = [
{file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"},
{file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"},
]
werkzeug = [
{file = "Werkzeug-2.0.3-py3-none-any.whl", hash = "sha256:1421ebfc7648a39a5c58c601b154165d05cf47a3cd0ccb70857cbdacf6c8f2b8"},
{file = "Werkzeug-2.0.3.tar.gz", hash = "sha256:b863f8ff057c522164b6067c9e28b041161b4be5ba4d0daceeaa50a163822d3c"},
{file = "Werkzeug-2.1.1-py3-none-any.whl", hash = "sha256:3c5493ece8268fecdcdc9c0b112211acd006354723b280d643ec732b6d4063d6"},
{file = "Werkzeug-2.1.1.tar.gz", hash = "sha256:f8e89a20aeabbe8a893c24a461d3ee5dad2123b05cc6abd73ceed01d39c3ae74"},
]
wrapt = [
{file = "wrapt-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:5a9a1889cc01ed2ed5f34574c90745fab1dd06ec2eee663e8ebeefe363e8efd7"},

View File

@ -1,6 +1,6 @@
[tool.poetry]
name = "fittrackee"
version = "0.6.1"
version = "0.6.2"
description = "Self-hosted outdoor workout/activity tracker"
authors = ["SamR1"]
license = "AGPL-3.0"
@ -24,9 +24,9 @@ exclude = ["fittrackee/tests"]
[tool.poetry.dependencies]
python = "^3.7"
dramatiq = {version = "^1.12.3", extras = ["redis"]}
flask = "^2.0"
flask-bcrypt = "^0.7.1"
dramatiq = {version = "^1.13", extras = ["redis"]}
flask = "^2.1"
flask-bcrypt = "^1.0"
flask-dramatiq = "^0.6.0"
flask-migrate = "^3.1"
gpxpy = "=1.3.4"
@ -40,9 +40,10 @@ shortuuid = "^1.0.8"
staticmap = "^0.5.4"
SQLAlchemy = "1.4.32"
pyOpenSSL = "^22.0"
ua-parser = "^0.10.0"
[tool.poetry.dev-dependencies]
black = "^22.1"
black = "^22.3"
freezegun = "^1.2"
mypy = "^0.942"
pytest = "^7.1"
@ -58,7 +59,7 @@ sphinxcontrib-httpdomain = "^1.7"
types-pytz = "^2021.3"
types-requests = "^2.27"
types-freezegun = "^1.1"
Sphinx = "^4.4.0"
Sphinx = "^4.5"
[tool.poetry.scripts]
fittrackee = 'fittrackee.__main__:main'