Merge pull request #211 from SamR1/fix-staticmap-generation

Fix staticmap generation
This commit is contained in:
Sam 2022-07-13 13:50:27 +02:00 committed by GitHub
commit 6a84902656
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 285 additions and 33 deletions

View File

@ -25,6 +25,7 @@ export SENDER_EMAIL=
# Workouts # Workouts
# export TILE_SERVER_URL= # export TILE_SERVER_URL=
# export STATICMAP_SUBDOMAINS=
# export MAP_ATTRIBUTION= # export MAP_ATTRIBUTION=
# export DEFAULT_STATICMAP=False # export DEFAULT_STATICMAP=False
# export WEATHER_API_KEY= # export WEATHER_API_KEY=

View File

@ -175,6 +175,16 @@ deployment method.
:default: `https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png` :default: `https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png`
.. envvar:: STATICMAP_SUBDOMAINS 🆕
.. versionadded:: 0.6.10
| Some tile servers require a subdomain, see `Map tile server <installation.html#map-tile-server>`__.
| For instance: "a,b,c" for OSM France.
:default: empty string
.. envvar:: MAP_ATTRIBUTION .. envvar:: MAP_ATTRIBUTION
.. versionadded:: 0.4.0 .. versionadded:: 0.4.0
@ -184,11 +194,17 @@ deployment method.
:default: `&copy; <a href="http://www.openstreetmap.org/copyright" target="_blank" rel="noopener noreferrer">OpenStreetMap</a> contributors` :default: `&copy; <a href="http://www.openstreetmap.org/copyright" target="_blank" rel="noopener noreferrer">OpenStreetMap</a> contributors`
.. envvar:: DEFAULT_STATICMAP 🆕 .. envvar:: DEFAULT_STATICMAP
.. versionadded:: 0.4.9 .. versionadded:: 0.4.9
If `True`, it keeps using default tile server to generate static maps. | If `True`, it keeps using default tile server to generate static maps (Komoot.de tile server).
| Otherwise, it uses the tile server set in `TILE_SERVER_URL <installation.html#envvar-TILE_SERVER_URL>`__.
.. versionchanged:: 0.6.10
| This variable is now case-insensitive.
| If `False`, depending on tile server, `subdomains <installation.html#envvar-STATICMAP_SUBDOMAINS>`__ may be mandatory.
:default: False :default: False
@ -256,6 +272,20 @@ To keep using **ThunderForest Outdoors**, the configuration is:
.. note:: .. note::
| Check the terms of service of tile provider for map attribution | Check the terms of service of tile provider for map attribution
.. versionchanged:: 0.6.10
Since the tile server can be used for static map generation, some servers require a subdomain.
For instance, to set OSM France tile server, the expected values are:
- ``TILE_SERVER_URL=https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png``
- ``MAP_ATTRIBUTION=fond de carte par <a href="http://www.openstreetmap.fr/mentions-legales/" target="_blank" rel="nofollow noopener">OpenStreetMap France</a>, sous&nbsp;<a href="http://creativecommons.org/licenses/by-sa/2.0/fr/" target="_blank" rel="nofollow noopener">licence CC BY-SA</a>``
- ``STATICMAP_SUBDOMAINS=a,b,c``
The subdomain will be chosen randomly.
Installation Installation
~~~~~~~~~~~~ ~~~~~~~~~~~~

View File

@ -135,7 +135,7 @@
</li> </li>
<li><a href="installation.html#envvar-DATABASE_URL">DATABASE_URL</a> <li><a href="installation.html#envvar-DATABASE_URL">DATABASE_URL</a>
</li> </li>
<li><a href="installation.html#envvar-DEFAULT_STATICMAP">DEFAULT_STATICMAP 🆕</a> <li><a href="installation.html#envvar-DEFAULT_STATICMAP">DEFAULT_STATICMAP</a>
</li> </li>
<li><a href="installation.html#envvar-EMAIL_URL">EMAIL_URL</a> <li><a href="installation.html#envvar-EMAIL_URL">EMAIL_URL</a>
</li> </li>
@ -150,6 +150,8 @@
<li><a href="installation.html#envvar-REDIS_URL">REDIS_URL</a> <li><a href="installation.html#envvar-REDIS_URL">REDIS_URL</a>
</li> </li>
<li><a href="installation.html#envvar-SENDER_EMAIL">SENDER_EMAIL</a> <li><a href="installation.html#envvar-SENDER_EMAIL">SENDER_EMAIL</a>
</li>
<li><a href="installation.html#envvar-STATICMAP_SUBDOMAINS">STATICMAP_SUBDOMAINS 🆕</a>
</li> </li>
<li><a href="installation.html#envvar-TILE_SERVER_URL">TILE_SERVER_URL</a> <li><a href="installation.html#envvar-TILE_SERVER_URL">TILE_SERVER_URL</a>
</li> </li>

View File

@ -420,6 +420,23 @@ see <a class="reference external" href="https://docs.sqlalchemy.org/en/13/core/p
</dl> </dl>
</dd></dl> </dd></dl>
<dl class="std envvar">
<dt class="sig sig-object std" id="envvar-STATICMAP_SUBDOMAINS">
<span class="sig-name descname"><span class="pre">STATICMAP_SUBDOMAINS</span> <span class="pre">🆕</span></span><a class="headerlink" href="#envvar-STATICMAP_SUBDOMAINS" title="Permalink to this definition"></a></dt>
<dd><div class="versionadded">
<p><span class="versionmodified added">New in version 0.6.10.</span></p>
</div>
<div class="line-block">
<div class="line">Some tile servers require a subdomain, see <a class="reference external" href="installation.html#map-tile-server">Map tile server</a>.</div>
<div class="line">For instance: “a,b,c” for OSM France.</div>
</div>
<dl class="field-list simple">
<dt class="field-odd">Default<span class="colon">:</span></dt>
<dd class="field-odd"><p>empty string</p>
</dd>
</dl>
</dd></dl>
<dl class="std envvar"> <dl class="std envvar">
<dt class="sig sig-object std" id="envvar-MAP_ATTRIBUTION"> <dt class="sig sig-object std" id="envvar-MAP_ATTRIBUTION">
<span class="sig-name descname"><span class="pre">MAP_ATTRIBUTION</span></span><a class="headerlink" href="#envvar-MAP_ATTRIBUTION" title="Permalink to this definition"></a></dt> <span class="sig-name descname"><span class="pre">MAP_ATTRIBUTION</span></span><a class="headerlink" href="#envvar-MAP_ATTRIBUTION" title="Permalink to this definition"></a></dt>
@ -436,11 +453,21 @@ see <a class="reference external" href="https://docs.sqlalchemy.org/en/13/core/p
<dl class="std envvar"> <dl class="std envvar">
<dt class="sig sig-object std" id="envvar-DEFAULT_STATICMAP"> <dt class="sig sig-object std" id="envvar-DEFAULT_STATICMAP">
<span class="sig-name descname"><span class="pre">DEFAULT_STATICMAP</span> <span class="pre">🆕</span></span><a class="headerlink" href="#envvar-DEFAULT_STATICMAP" title="Permalink to this definition"></a></dt> <span class="sig-name descname"><span class="pre">DEFAULT_STATICMAP</span></span><a class="headerlink" href="#envvar-DEFAULT_STATICMAP" title="Permalink to this definition"></a></dt>
<dd><div class="versionadded"> <dd><div class="versionadded">
<p><span class="versionmodified added">New in version 0.4.9.</span></p> <p><span class="versionmodified added">New in version 0.4.9.</span></p>
</div> </div>
<p>If <cite>True</cite>, it keeps using default tile server to generate static maps.</p> <div class="line-block">
<div class="line">If <cite>True</cite>, it keeps using default tile server to generate static maps (Komoot.de tile server).</div>
<div class="line">Otherwise, it uses the tile server set in <a class="reference external" href="installation.html#envvar-TILE_SERVER_URL">TILE_SERVER_URL</a>.</div>
</div>
<div class="versionchanged">
<p><span class="versionmodified changed">Changed in version 0.6.10.</span></p>
</div>
<div class="line-block">
<div class="line">This variable is now case-insensitive.</div>
<div class="line">If <cite>False</cite>, depending on tile server, <a class="reference external" href="installation.html#envvar-STATICMAP_SUBDOMAINS">subdomains</a> may be mandatory.</div>
</div>
<dl class="field-list simple"> <dl class="field-list simple">
<dt class="field-odd">Default<span class="colon">:</span></dt> <dt class="field-odd">Default<span class="colon">:</span></dt>
<dd class="field-odd"><p>False</p> <dd class="field-odd"><p>False</p>
@ -527,6 +554,17 @@ The tile server can be changed by updating <code class="docutils literal notrans
<div class="line">Check the terms of service of tile provider for map attribution</div> <div class="line">Check the terms of service of tile provider for map attribution</div>
</div> </div>
</div> </div>
<div class="versionchanged">
<p><span class="versionmodified changed">Changed in version 0.6.10.</span></p>
</div>
<p>Since the tile server can be used for static map generation, some servers require a subdomain.</p>
<p>For instance, to set OSM France tile server, the expected values are:</p>
<ul class="simple">
<li><p><code class="docutils literal notranslate"><span class="pre">TILE_SERVER_URL=https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png</span></code></p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">MAP_ATTRIBUTION=fond</span> <span class="pre">de</span> <span class="pre">carte</span> <span class="pre">par</span> <span class="pre">&lt;a</span> <span class="pre">href=&quot;http://www.openstreetmap.fr/mentions-legales/&quot;</span> <span class="pre">target=&quot;_blank&quot;</span> <span class="pre">rel=&quot;nofollow</span> <span class="pre">noopener&quot;&gt;OpenStreetMap</span> <span class="pre">France&lt;/a&gt;,</span> <span class="pre">sous&amp;nbsp;&lt;a</span> <span class="pre">href=&quot;http://creativecommons.org/licenses/by-sa/2.0/fr/&quot;</span> <span class="pre">target=&quot;_blank&quot;</span> <span class="pre">rel=&quot;nofollow</span> <span class="pre">noopener&quot;&gt;licence</span> <span class="pre">CC</span> <span class="pre">BY-SA&lt;/a&gt;</span></code></p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">STATICMAP_SUBDOMAINS=a,b,c</span></code></p></li>
</ul>
<p>The subdomain will be chosen randomly.</p>
</section> </section>
</section> </section>
<section id="id1"> <section id="id1">

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -175,6 +175,16 @@ deployment method.
:default: `https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png` :default: `https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png`
.. envvar:: STATICMAP_SUBDOMAINS 🆕
.. versionadded:: 0.6.10
| Some tile servers require a subdomain, see `Map tile server <installation.html#map-tile-server>`__.
| For instance: "a,b,c" for OSM France.
:default: empty string
.. envvar:: MAP_ATTRIBUTION .. envvar:: MAP_ATTRIBUTION
.. versionadded:: 0.4.0 .. versionadded:: 0.4.0
@ -184,11 +194,17 @@ deployment method.
:default: `&copy; <a href="http://www.openstreetmap.org/copyright" target="_blank" rel="noopener noreferrer">OpenStreetMap</a> contributors` :default: `&copy; <a href="http://www.openstreetmap.org/copyright" target="_blank" rel="noopener noreferrer">OpenStreetMap</a> contributors`
.. envvar:: DEFAULT_STATICMAP 🆕 .. envvar:: DEFAULT_STATICMAP
.. versionadded:: 0.4.9 .. versionadded:: 0.4.9
If `True`, it keeps using default tile server to generate static maps. | If `True`, it keeps using default tile server to generate static maps (Komoot.de tile server).
| Otherwise, it uses the tile server set in `TILE_SERVER_URL <installation.html#envvar-TILE_SERVER_URL>`__.
.. versionchanged:: 0.6.10
| This variable is now case-insensitive.
| If `False`, depending on tile server, `subdomains <installation.html#envvar-STATICMAP_SUBDOMAINS>`__ may be mandatory.
:default: False :default: False
@ -256,6 +272,20 @@ To keep using **ThunderForest Outdoors**, the configuration is:
.. note:: .. note::
| Check the terms of service of tile provider for map attribution | Check the terms of service of tile provider for map attribution
.. versionchanged:: 0.6.10
Since the tile server can be used for static map generation, some servers require a subdomain.
For instance, to set OSM France tile server, the expected values are:
- ``TILE_SERVER_URL=https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png``
- ``MAP_ATTRIBUTION=fond de carte par <a href="http://www.openstreetmap.fr/mentions-legales/" target="_blank" rel="nofollow noopener">OpenStreetMap France</a>, sous&nbsp;<a href="http://creativecommons.org/licenses/by-sa/2.0/fr/" target="_blank" rel="nofollow noopener">licence CC BY-SA</a>``
- ``STATICMAP_SUBDOMAINS=a,b,c``
The subdomain will be chosen randomly.
Installation Installation
~~~~~~~~~~~~ ~~~~~~~~~~~~

View File

@ -43,8 +43,9 @@ class BaseConfig:
' contributors', ' contributors',
), ),
'DEFAULT_STATICMAP': ( 'DEFAULT_STATICMAP': (
os.environ.get('DEFAULT_STATICMAP', 'False') == 'True' os.environ.get('DEFAULT_STATICMAP', 'false').lower() == 'true'
), ),
'STATICMAP_SUBDOMAINS': os.environ.get('STATICMAP_SUBDOMAINS', ''),
} }
TRANSLATIONS_FOLDER = os.path.join( TRANSLATIONS_FOLDER = os.path.join(
current_app.root_path, 'emails/translations' current_app.root_path, 'emails/translations'

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.7123d468.js"></script><script defer="defer" src="/static/js/app.51b2bdb0.js"></script><link href="/static/css/app.32d0ced1.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.7123d468.js"></script><script defer="defer" src="/static/js/app.685ff4cc.js"></script><link href="/static/css/app.32d0ced1.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

View File

@ -1,2 +1,2 @@
"use strict";(self["webpackChunkfittrackee_client"]=self["webpackChunkfittrackee_client"]||[]).push([[193],{9161:function(e,s,t){t.r(s),t.d(s,{default:function(){return A}});t(6699);var a=t(6252),r=t(2262),l=t(3577),o=t(3324),n=t(9996);const c={class:"chart-menu"},i={class:"chart-arrow"},u={class:"time-frames custom-checkboxes-group"},d={class:"time-frames-checkboxes custom-checkboxes"},p=["id","name","checked","onInput"],m={class:"chart-arrow"};var v=(0,a.aZ)({__name:"StatsMenu",emits:["arrowClick","timeFrameUpdate"],setup(e,{emit:s}){const t=(0,r.iH)("month"),o=["week","month","year"];function n(e){t.value=e,s("timeFrameUpdate",e)}return(e,r)=>((0,a.wg)(),(0,a.iD)("div",c,[(0,a._)("div",i,[(0,a._)("i",{class:"fa fa-chevron-left","aria-hidden":"true",onClick:r[0]||(r[0]=e=>s("arrowClick",!0))})]),(0,a._)("div",u,[(0,a._)("div",d,[((0,a.wg)(),(0,a.iD)(a.HY,null,(0,a.Ko)(o,(s=>(0,a._)("div",{class:"time-frame custom-checkbox",key:s},[(0,a._)("label",null,[(0,a._)("input",{type:"radio",id:s,name:s,checked:t.value===s,onInput:e=>n(s)},null,40,p),(0,a._)("span",null,(0,l.zw)(e.$t(`statistics.TIME_FRAMES.${s}`)),1)])]))),64))])]),(0,a._)("div",m,[(0,a._)("i",{class:"fa fa-chevron-right","aria-hidden":"true",onClick:r[1]||(r[1]=e=>s("arrowClick",!1))})])]))}}),k=t(3744);const _=(0,k.Z)(v,[["__scopeId","data-v-22d55de2"]]);var S=_,w=t(631);const f={class:"sports-menu"},h=["id","name","checked","onInput"],U={class:"sport-label"};var b=(0,a.aZ)({__name:"StatsSportsMenu",props:{userSports:null,selectedSportIds:{default:()=>[]}},emits:["selectedSportIdsUpdate"],setup(e,{emit:s}){const t=e,{t:n}=(0,o.QT)(),c=(0,a.f3)("sportColors"),{selectedSportIds:i}=(0,r.BK)(t),u=(0,a.Fl)((()=>(0,w.xH)(t.userSports,n)));function d(e){s("selectedSportIdsUpdate",e)}return(e,s)=>{const t=(0,a.up)("SportImage");return(0,a.wg)(),(0,a.iD)("div",f,[((0,a.wg)(!0),(0,a.iD)(a.HY,null,(0,a.Ko)((0,r.SU)(u),(e=>((0,a.wg)(),(0,a.iD)("label",{type:"checkbox",key:e.id,style:(0,l.j5)({color:e.color?e.color:(0,r.SU)(c)[e.label]})},[(0,a._)("input",{type:"checkbox",id:e.id,name:e.label,checked:(0,r.SU)(i).includes(e.id),onInput:s=>d(e.id)},null,40,h),(0,a.Wm)(t,{"sport-label":e.label,color:e.color},null,8,["sport-label","color"]),(0,a._)("span",U,(0,l.zw)(e.translatedLabel),1)],4)))),128))])}}});const I=b;var g=I,T=t(9318);const y={key:0,id:"user-statistics"};var C=(0,a.aZ)({__name:"index",props:{sports:null,user:null},setup(e){const s=e,{t:t}=(0,o.QT)(),{sports:l,user:c}=(0,r.BK)(s),i=(0,r.iH)("month"),u=(0,r.iH)(v(i.value)),d=(0,a.Fl)((()=>(0,w.xH)(s.sports,t))),p=(0,r.iH)(_(s.sports));function m(e){i.value=e,u.value=v(i.value)}function v(e){return(0,T.aZ)(new Date,e,s.user.weekm)}function k(e){u.value=(0,T.FN)(u.value,e,s.user.weekm)}function _(e){return e.map((e=>e.id))}function f(e){p.value.includes(e)?p.value=p.value.filter((s=>s!==e)):p.value.push(e)}return(0,a.YP)((()=>s.sports),(e=>{p.value=_(e)})),(e,s)=>(0,r.SU)(d)?((0,a.wg)(),(0,a.iD)("div",y,[(0,a.Wm)(S,{onTimeFrameUpdate:m,onArrowClick:k}),(0,a.Wm)(n.Z,{sports:(0,r.SU)(l),user:(0,r.SU)(c),chartParams:u.value,"displayed-sport-ids":p.value,fullStats:!0},null,8,["sports","user","chartParams","displayed-sport-ids"]),(0,a.Wm)(g,{"selected-sport-ids":p.value,"user-sports":(0,r.SU)(l),onSelectedSportIdsUpdate:f},null,8,["selected-sport-ids","user-sports"])])):(0,a.kq)("",!0)}});const F=(0,k.Z)(C,[["__scopeId","data-v-d693c7da"]]);var Z=F,x=t(5630),D=t(8602),H=t(9917);const E={id:"statistics",class:"view"},R={key:0,class:"container"};var W=(0,a.aZ)({__name:"StatisticsView",setup(e){const s=(0,H.o)(),t=(0,a.Fl)((()=>s.getters[D.YN.GETTERS.AUTH_USER_PROFILE])),o=(0,a.Fl)((()=>s.getters[D.O8.GETTERS.SPORTS].filter((e=>t.value.sports_list.includes(e.id)))));return(e,s)=>{const n=(0,a.up)("Card");return(0,a.wg)(),(0,a.iD)("div",E,[(0,r.SU)(t).username?((0,a.wg)(),(0,a.iD)("div",R,[(0,a.Wm)(n,null,{title:(0,a.w5)((()=>[(0,a.Uk)((0,l.zw)(e.$t("statistics.STATISTICS")),1)])),content:(0,a.w5)((()=>[(0,a.Wm)(Z,{class:(0,l.C_)({"stats-disabled":0===(0,r.SU)(t).nb_workouts}),user:(0,r.SU)(t),sports:(0,r.SU)(o)},null,8,["class","user","sports"])])),_:1}),0===(0,r.SU)(t).nb_workouts?((0,a.wg)(),(0,a.j4)(x.Z,{key:0})):(0,a.kq)("",!0)])):(0,a.kq)("",!0)])}}});const P=(0,k.Z)(W,[["__scopeId","data-v-2e341d4e"]]);var A=P}}]); "use strict";(self["webpackChunkfittrackee_client"]=self["webpackChunkfittrackee_client"]||[]).push([[193],{9161:function(e,s,t){t.r(s),t.d(s,{default:function(){return A}});t(6699);var a=t(6252),r=t(2262),l=t(3577),o=t(3324),n=t(9996);const c={class:"chart-menu"},i={class:"chart-arrow"},u={class:"time-frames custom-checkboxes-group"},d={class:"time-frames-checkboxes custom-checkboxes"},p=["id","name","checked","onInput"],m={class:"chart-arrow"};var v=(0,a.aZ)({__name:"StatsMenu",emits:["arrowClick","timeFrameUpdate"],setup(e,{emit:s}){const t=(0,r.iH)("month"),o=["week","month","year"];function n(e){t.value=e,s("timeFrameUpdate",e)}return(e,r)=>((0,a.wg)(),(0,a.iD)("div",c,[(0,a._)("div",i,[(0,a._)("i",{class:"fa fa-chevron-left","aria-hidden":"true",onClick:r[0]||(r[0]=e=>s("arrowClick",!0))})]),(0,a._)("div",u,[(0,a._)("div",d,[((0,a.wg)(),(0,a.iD)(a.HY,null,(0,a.Ko)(o,(s=>(0,a._)("div",{class:"time-frame custom-checkbox",key:s},[(0,a._)("label",null,[(0,a._)("input",{type:"radio",id:s,name:s,checked:t.value===s,onInput:e=>n(s)},null,40,p),(0,a._)("span",null,(0,l.zw)(e.$t(`statistics.TIME_FRAMES.${s}`)),1)])]))),64))])]),(0,a._)("div",m,[(0,a._)("i",{class:"fa fa-chevron-right","aria-hidden":"true",onClick:r[1]||(r[1]=e=>s("arrowClick",!1))})])]))}}),k=t(3744);const _=(0,k.Z)(v,[["__scopeId","data-v-22d55de2"]]);var S=_,w=t(631);const f={class:"sports-menu"},h=["id","name","checked","onInput"],U={class:"sport-label"};var b=(0,a.aZ)({__name:"StatsSportsMenu",props:{userSports:null,selectedSportIds:{default:()=>[]}},emits:["selectedSportIdsUpdate"],setup(e,{emit:s}){const t=e,{t:n}=(0,o.QT)(),c=(0,a.f3)("sportColors"),{selectedSportIds:i}=(0,r.BK)(t),u=(0,a.Fl)((()=>(0,w.xH)(t.userSports,n)));function d(e){s("selectedSportIdsUpdate",e)}return(e,s)=>{const t=(0,a.up)("SportImage");return(0,a.wg)(),(0,a.iD)("div",f,[((0,a.wg)(!0),(0,a.iD)(a.HY,null,(0,a.Ko)((0,r.SU)(u),(e=>((0,a.wg)(),(0,a.iD)("label",{type:"checkbox",key:e.id,style:(0,l.j5)({color:e.color?e.color:(0,r.SU)(c)[e.label]})},[(0,a._)("input",{type:"checkbox",id:e.id,name:e.label,checked:(0,r.SU)(i).includes(e.id),onInput:s=>d(e.id)},null,40,h),(0,a.Wm)(t,{"sport-label":e.label,color:e.color},null,8,["sport-label","color"]),(0,a._)("span",U,(0,l.zw)(e.translatedLabel),1)],4)))),128))])}}});const I=b;var g=I,T=t(9318);const y={key:0,id:"user-statistics"};var C=(0,a.aZ)({__name:"index",props:{sports:null,user:null},setup(e){const s=e,{t:t}=(0,o.QT)(),{sports:l,user:c}=(0,r.BK)(s),i=(0,r.iH)("month"),u=(0,r.iH)(v(i.value)),d=(0,a.Fl)((()=>(0,w.xH)(s.sports,t))),p=(0,r.iH)(_(s.sports));function m(e){i.value=e,u.value=v(i.value)}function v(e){return(0,T.aZ)(new Date,e,s.user.weekm)}function k(e){u.value=(0,T.FN)(u.value,e,s.user.weekm)}function _(e){return e.map((e=>e.id))}function f(e){p.value.includes(e)?p.value=p.value.filter((s=>s!==e)):p.value.push(e)}return(0,a.YP)((()=>s.sports),(e=>{p.value=_(e)})),(e,s)=>(0,r.SU)(d)?((0,a.wg)(),(0,a.iD)("div",y,[(0,a.Wm)(S,{onTimeFrameUpdate:m,onArrowClick:k}),(0,a.Wm)(n.Z,{sports:(0,r.SU)(l),user:(0,r.SU)(c),chartParams:u.value,"displayed-sport-ids":p.value,fullStats:!0},null,8,["sports","user","chartParams","displayed-sport-ids"]),(0,a.Wm)(g,{"selected-sport-ids":p.value,"user-sports":(0,r.SU)(l),onSelectedSportIdsUpdate:f},null,8,["selected-sport-ids","user-sports"])])):(0,a.kq)("",!0)}});const F=(0,k.Z)(C,[["__scopeId","data-v-d693c7da"]]);var Z=F,x=t(5630),D=t(8602),H=t(9917);const E={id:"statistics",class:"view"},R={key:0,class:"container"};var W=(0,a.aZ)({__name:"StatisticsView",setup(e){const s=(0,H.o)(),t=(0,a.Fl)((()=>s.getters[D.YN.GETTERS.AUTH_USER_PROFILE])),o=(0,a.Fl)((()=>s.getters[D.O8.GETTERS.SPORTS].filter((e=>t.value.sports_list.includes(e.id)))));return(e,s)=>{const n=(0,a.up)("Card");return(0,a.wg)(),(0,a.iD)("div",E,[(0,r.SU)(t).username?((0,a.wg)(),(0,a.iD)("div",R,[(0,a.Wm)(n,null,{title:(0,a.w5)((()=>[(0,a.Uk)((0,l.zw)(e.$t("statistics.STATISTICS")),1)])),content:(0,a.w5)((()=>[(0,a.Wm)(Z,{class:(0,l.C_)({"stats-disabled":0===(0,r.SU)(t).nb_workouts}),user:(0,r.SU)(t),sports:(0,r.SU)(o)},null,8,["class","user","sports"])])),_:1}),0===(0,r.SU)(t).nb_workouts?((0,a.wg)(),(0,a.j4)(x.Z,{key:0})):(0,a.kq)("",!0)])):(0,a.kq)("",!0)])}}});const P=(0,k.Z)(W,[["__scopeId","data-v-2e341d4e"]]);var A=P}}]);
//# sourceMappingURL=statistics.145d19e3.js.map //# sourceMappingURL=statistics.c817d0d3.js.map

File diff suppressed because one or more lines are too long

View File

@ -81,6 +81,8 @@ def app(monkeypatch: pytest.MonkeyPatch) -> Generator:
monkeypatch.setenv('WEATHER_API_KEY', '') monkeypatch.setenv('WEATHER_API_KEY', '')
if os.getenv('TILE_SERVER_URL'): if os.getenv('TILE_SERVER_URL'):
monkeypatch.delenv('TILE_SERVER_URL') monkeypatch.delenv('TILE_SERVER_URL')
if os.getenv('STATICMAP_SUBDOMAINS'):
monkeypatch.delenv('STATICMAP_SUBDOMAINS')
if os.getenv('MAP_ATTRIBUTION'): if os.getenv('MAP_ATTRIBUTION'):
monkeypatch.delenv('MAP_ATTRIBUTION') monkeypatch.delenv('MAP_ATTRIBUTION')
if os.getenv('DEFAULT_STATICMAP'): if os.getenv('DEFAULT_STATICMAP'):

View File

@ -1,5 +1,5 @@
import json import json
from typing import Any, Dict, Optional, Tuple from typing import Dict, Optional, Tuple
from flask import Flask from flask import Flask
from flask.testing import FlaskClient from flask.testing import FlaskClient
@ -115,10 +115,20 @@ class ApiTestCaseMixin(RandomMixin):
class CallArgsMixin: class CallArgsMixin:
"""call args are returned differently between Python 3.7 and 3.7+"""
@staticmethod @staticmethod
def get_args(call_args: Any) -> Any: def get_args(call_args: Tuple) -> Tuple:
if len(call_args) == 2: if len(call_args) == 2:
args, _ = call_args args, _ = call_args
else: else:
_, args, _ = call_args _, args, _ = call_args
return args return args
@staticmethod
def get_kwargs(call_args: Tuple) -> Dict:
if len(call_args) == 2:
_, kwargs = call_args
else:
_, _, kwargs = call_args
return kwargs

View File

@ -0,0 +1,63 @@
from unittest.mock import patch
import pytest
from fittrackee.workouts.utils.maps import get_static_map_tile_server_url
class TestGetStaticMapTileServerUrl:
@pytest.mark.parametrize(
'input_tile_server_url,'
'input_tile_server_subdomains,'
'expected_tile_server_url',
[
(
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
'',
'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
),
(
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
'a',
'https://a.tile.openstreetmap.org/{z}/{x}/{y}.png',
),
(
'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
'',
'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
),
(
'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
'a',
'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
),
],
)
def test_it_returns_tile_server_url(
self,
input_tile_server_url: str,
input_tile_server_subdomains: str,
expected_tile_server_url: str,
) -> None:
tile_config = {
'URL': input_tile_server_url,
'STATICMAP_SUBDOMAINS': input_tile_server_subdomains,
}
assert (
get_static_map_tile_server_url(tile_config)
== expected_tile_server_url
)
def test_it_returns_tile_server_url_with_random_subdomain(self) -> None:
"""in case multiple subdomains are provided"""
tile_config = {
'URL': 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
'STATICMAP_SUBDOMAINS': 'a,b,c',
}
with patch('random.choice', return_value='b'):
assert (
get_static_map_tile_server_url(tile_config)
== 'https://b.tile.openstreetmap.org/{z}/{x}/{y}.png'
)

View File

@ -9,6 +9,7 @@ from unittest.mock import Mock
import pytest import pytest
from flask import Flask from flask import Flask
from fittrackee import VERSION
from fittrackee.users.models import User from fittrackee.users.models import User
from fittrackee.workouts.models import Sport, Workout from fittrackee.workouts.models import Sport, Workout
@ -442,7 +443,7 @@ class TestPostWorkoutWithGpx(ApiTestCaseMixin, CallArgsMixin):
assert len(data['data']['workouts']) == 1 assert len(data['data']['workouts']) == 1
assert data['data']['workouts'][0]['notes'] == input_notes assert data['data']['workouts'][0]['notes'] == input_notes
def test_it_calls_configured_tile_server_for_static_map( def test_it_calls_configured_tile_server_for_static_map_when_default_static_map_to_false( # noqa
self, self,
app: Flask, app: Flask,
user_1: User, user_1: User,
@ -473,7 +474,36 @@ class TestPostWorkoutWithGpx(ApiTestCaseMixin, CallArgsMixin):
in call_args[0] in call_args[0]
) )
def test_it_calls_default_tile_server_for_static_map( def test_it_calls_static_map_with_fittrackee_user_agent_when_default_static_map_to_false( # noqa
self,
app: Flask,
user_1: User,
sport_1_cycling: Sport,
gpx_file: str,
static_map_get_mock: Mock,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)
client.post(
'/api/workouts',
data=dict(
file=(BytesIO(str.encode(gpx_file)), 'example.gpx'),
data='{"sport_id": 1}',
),
headers=dict(
content_type='multipart/form-data',
Authorization=f'Bearer {auth_token}',
),
)
call_kwargs = self.get_kwargs(static_map_get_mock.call_args)
assert call_kwargs['headers'] == {
'User-Agent': f'FitTrackee v{VERSION}'
}
def test_it_calls_default_tile_server_for_static_map_when_default_static_map_to_true( # noqa
self, self,
app_default_static_map: Flask, app_default_static_map: Flask,
user_1: User, user_1: User,
@ -504,6 +534,35 @@ class TestPostWorkoutWithGpx(ApiTestCaseMixin, CallArgsMixin):
not in call_args[0] not in call_args[0]
) )
def test_it_calls_static_map_with_fittrackee_user_agent_when_default_static_map_to_true( # noqa
self,
app_default_static_map: Flask,
user_1: User,
sport_1_cycling: Sport,
gpx_file: str,
static_map_get_mock: Mock,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app_default_static_map, user_1.email
)
client.post(
'/api/workouts',
data=dict(
file=(BytesIO(str.encode(gpx_file)), 'example.gpx'),
data='{"sport_id": 1}',
),
headers=dict(
content_type='multipart/form-data',
Authorization=f'Bearer {auth_token}',
),
)
call_kwargs = self.get_kwargs(static_map_get_mock.call_args)
assert call_kwargs['headers'] == {
'User-Agent': f'FitTrackee v{VERSION}'
}
def test_it_returns_500_if_gpx_file_has_not_tracks( def test_it_returns_500_if_gpx_file_has_not_tracks(
self, self,
app: Flask, app: Flask,
@ -527,7 +586,7 @@ class TestPostWorkoutWithGpx(ApiTestCaseMixin, CallArgsMixin):
), ),
) )
data = self.assert_500(response, 'Error during gpx processing.') data = self.assert_500(response, 'error during gpx processing')
assert 'data' not in data assert 'data' not in data
def test_it_returns_500_if_gpx_has_invalid_xml( def test_it_returns_500_if_gpx_has_invalid_xml(
@ -556,7 +615,7 @@ class TestPostWorkoutWithGpx(ApiTestCaseMixin, CallArgsMixin):
), ),
) )
data = self.assert_500(response, 'Error during gpx file parsing.') data = self.assert_500(response, 'error during gpx file parsing')
assert 'data' not in data assert 'data' not in data
def test_it_returns_400_if_workout_gpx_has_invalid_extension( def test_it_returns_400_if_workout_gpx_has_invalid_extension(
@ -895,7 +954,7 @@ class TestPostWorkoutWithZipArchive(ApiTestCaseMixin):
), ),
) )
data = self.assert_500(response, 'Error during gpx processing.') data = self.assert_500(response, 'error during gpx processing')
assert 'data' not in data assert 'data' not in data
def test_it_imports_only_max_number_of_files( def test_it_imports_only_max_number_of_files(

View File

@ -1,20 +1,32 @@
import hashlib import hashlib
from typing import List import random
from typing import Dict, List
from flask import current_app from flask import current_app
from staticmap import Line, StaticMap from staticmap import Line, StaticMap
from fittrackee import VERSION
from fittrackee.files import get_absolute_file_path from fittrackee.files import get_absolute_file_path
def get_static_map_tile_server_url(tile_server_config: Dict) -> str:
if tile_server_config['STATICMAP_SUBDOMAINS']:
subdomains = tile_server_config['STATICMAP_SUBDOMAINS'].split(',')
subdomain = f'{random.choice(subdomains)}.' # nosec
else:
subdomain = ''
return tile_server_config['URL'].replace('{s}.', subdomain)
def generate_map(map_filepath: str, map_data: List) -> None: def generate_map(map_filepath: str, map_data: List) -> None:
""" """
Generate and save map image from map data Generate and save map image from map data
""" """
m = StaticMap(400, 225, 10) m = StaticMap(400, 225, 10)
m.headers = {'User-Agent': f'FitTrackee v{VERSION}'}
if not current_app.config['TILE_SERVER']['DEFAULT_STATICMAP']: if not current_app.config['TILE_SERVER']['DEFAULT_STATICMAP']:
m.url_template = current_app.config['TILE_SERVER']['URL'].replace( m.url_template = get_static_map_tile_server_url(
'{s}.', '' current_app.config['TILE_SERVER']
) )
line = Line(map_data, '#3388FF', 4) line = Line(map_data, '#3388FF', 4)
m.add_line(line) m.add_line(line)

View File

@ -307,9 +307,9 @@ def process_one_gpx_file(
absolute_map_filepath = get_absolute_file_path(map_filepath) absolute_map_filepath = get_absolute_file_path(map_filepath)
generate_map(absolute_map_filepath, map_data) generate_map(absolute_map_filepath, map_data)
except (gpxpy.gpx.GPXXMLSyntaxException, TypeError) as e: except (gpxpy.gpx.GPXXMLSyntaxException, TypeError) as e:
raise WorkoutException('error', 'Error during gpx file parsing.', e) raise WorkoutException('error', 'error during gpx file parsing', e)
except Exception as e: except Exception as e:
raise WorkoutException('error', 'Error during gpx processing.', e) raise WorkoutException('error', 'error during gpx processing', e)
try: try:
new_workout = create_workout( new_workout = create_workout(

View File

@ -2,6 +2,8 @@
"ERROR": { "ERROR": {
"UNKNOWN": "Error. Please try again or contact the administrator.", "UNKNOWN": "Error. Please try again or contact the administrator.",
"email: valid email must be provided": "Email: valid email must be provided.", "email: valid email must be provided": "Email: valid email must be provided.",
"error during gpx processing": "Error during gpx processing.",
"error during gpx file parsing": "Error during gpx file parsing.",
"error on getting configuration": "Error on getting configuration.", "error on getting configuration": "Error on getting configuration.",
"error when updating configuration": "Error when updating configuration", "error when updating configuration": "Error when updating configuration",
"error, please try again or contact the administrator": "Error, please try again or contact the administrator.", "error, please try again or contact the administrator": "Error, please try again or contact the administrator.",

View File

@ -2,6 +2,8 @@
"ERROR": { "ERROR": {
"UNKNOWN": "Erreur. Veuillez réessayer ou contacter l'administrateur.", "UNKNOWN": "Erreur. Veuillez réessayer ou contacter l'administrateur.",
"email: valid email must be provided": "Courriel : une adresse électronique valide doit être fournie.", "email: valid email must be provided": "Courriel : une adresse électronique valide doit être fournie.",
"error during gpx processing": "Erreur lors du traitement du fichier gpx.",
"error during gpx file parsing": "Erreur lors de l'analyse du fichier.",
"error on getting configuration": "Erreur lors de la récupération de la configuration.", "error on getting configuration": "Erreur lors de la récupération de la configuration.",
"error when updating configuration": "Erreur lors de la mise à jour de la configuration", "error when updating configuration": "Erreur lors de la mise à jour de la configuration",
"error, please try again or contact the administrator": "Erreur, veuillez réessayer ou contacter l'administrateur.", "error, please try again or contact the administrator": "Erreur, veuillez réessayer ou contacter l'administrateur.",