Changes in / [c6219ed:02048a6] in OpenWorkouts-current


Ignore:
Files:
5 deleted
11 edited

Legend:

Unmodified
Added
Removed
  • .boring

    rc6219ed r02048a6  
    138138ow.egg-info/*
    139139ow/static/components
    140 ow/static/maps
  • ow/models/workout.py

    rc6219ed r02048a6  
    1 import os
     1
    22from datetime import datetime, timedelta, timezone
    33from decimal import Decimal
     
    1212    copy_blob,
    1313    create_blob,
    14     mps_to_kmph,
    15     save_map_screenshot
    1614)
    1715
     
    5250        self.duration = kw.get('duration', None)  # a timedelta object
    5351        self.distance = kw.get('distance', None)  # kilometers, Decimal
    54         self.speed = kw.get('speed', {})
    5552        self.hr_min = kw.get('hr_min', None)  # bpm, Decimal
    5653        self.hr_max = kw.get('hr_max', None)  # bpm, Decimal
     
    214211        path = None
    215212        if self.tracking_file:
    216             path = self.tracking_file._uncommitted()
     213            path = self.tracking_file._p_blob_uncommitted
    217214            if path is None:
    218                 path = self.tracking_file.committed()
     215                path = self.tracking_file._p_blob_committed
    219216        return path
    220217
     
    231228        path = None
    232229        if self.fit_file:
    233             path = self.fit_file._uncommitted()
     230            path = self.fit_file._p_blob_uncommitted
    234231            if path is None:
    235                 path = self.fit_file.committed()
     232                path = self.fit_file._p_blob_committed
    236233        return path
    237234
     
    351348        4. Grab some basic info from the fit file and store it in the Workout
    352349        """
    353 
    354         # we can call load_from_fit afterwards for updates. In such case, check
    355         # if the tracking file is a fit file uploaded to override the previous
    356         # one. If not, just reuse the existing fit file
    357         if self.tracking_filetype == 'fit':
    358             # backup the fit file
    359             self.fit_file = copy_blob(self.tracking_file)
     350        # backup the fit file
     351        self.fit_file = copy_blob(self.tracking_file)
    360352
    361353        # create an instance of our Fit class
     
    386378            self.title = fit.name
    387379
    388         if fit.data['max_speed']:
    389             self.speed['max'] = mps_to_kmph(fit.data['max_speed'])
    390 
    391         if fit.data['avg_speed']:
    392             self.speed['avg'] = mps_to_kmph(fit.data['avg_speed'])
    393 
    394380        if fit.data['avg_hr']:
    395381            self.hr_avg = Decimal(fit.data['avg_hr'])
     
    420406    def has_fit(self):
    421407        return self.fit_file is not None
    422 
    423     @property
    424     def map_screenshot(self):
    425         """
    426         Return the static path to the screenshot image of the map for
    427         this workout (works only for workouts with gps tracking)
    428         """
    429         if not self.has_gpx:
    430             return None
    431 
    432         current_path = os.path.abspath(os.path.dirname(__file__))
    433         screenshot_path = os.path.join(
    434             current_path, '../static/maps',
    435             str(self.owner.uid), str(self.workout_id)) + '.png'
    436 
    437         if not os.path.exists(screenshot_path):
    438             # screenshot does not exist, generate it
    439             save_map_screenshot(self)
    440 
    441         # the value returned is relative to the static files served
    442         # by the app, so we can use request.static_url() with it
    443         static_path = os.path.join('static/maps', str(self.owner.uid),
    444                                    str(self.workout_id))
    445         return 'ow:' + static_path + '.png'
  • ow/static/css/main.css

    rc6219ed r02048a6  
    752752}
    753753
    754 .workout-map img {
    755     width: 100%;
    756 }
    757 
    758754.owo-del a:hover {
    759755    color:red
  • ow/static/js/ow.js

    rc6219ed r02048a6  
    1616
    1717    // parameters provided when creating an "instance" of a map
    18     var map_id = spec.map_id;
    1918    var latitude = spec.latitude;
    2019    var longitude = spec.longitude;
     
    2423    var end_icon = spec.end_icon;
    2524    var shadow = spec.shadow;
    26     var elevation = spec.elevation;
    27     var zoom_control = spec.zoom_control;
    2825
    2926    // OpenStreetMap urls and references
    30     var openstreetmap_url = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
    31     var openstreetmap_attr = 'Map data &copy; <a href="http://www.osm.org">OpenStreetMap</a>';
     27    var openstreetmap_url = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
     28    var openstreetmap_attr = 'Map data &copy; <a href="http://www.osm.org">OpenStreetMap</a>'
    3229
    3330    // Some constants reused through the code
     
    3936    var create_map = function create_map(latitude, longitude, zoom) {
    4037        /* Create a Leaflet map, set center point and add tiles */
    41         map = L.map(map_id, {zoomControl: zoom_control});
     38        map = L.map('map');
    4239        map.setView([latitude, longitude], zoom);
    4340        var tile_layer = L.tileLayer(openstreetmap_url, {
     
    9390            },
    9491        });
    95 
    96         gpx.on('loaded', function(e) {
    97             map.fitBounds(e.target.getBounds());
    98         });
    99 
    100         if (elevation) {
    101             gpx.on("addline",function(e){
    102                 elevation.addData(e.line);
    103                 // ow_charts.addData(e.line);
    104             });
    105         };
    106 
     92        gpx.on("addline",function(e){
     93            elevation.addData(e.line);
     94            // ow_charts.addData(e.line);
     95        });
    10796        gpx.addTo(map);
    10897    };
     
    111100        // create the map, add elevation, load gpx
    112101        create_map(latitude, longitude, zoom);
    113         if (elevation) {
    114             add_elevation_chart();
    115         }
     102        add_elevation_chart();
    116103        // add_ow_charts();
    117104        load_gpx(gpx_url);
  • ow/templates/dashboard.pt

    rc6219ed r02048a6  
    9090            <div class="workout-intro" tal:content="workout.notes"></div>
    9191
    92             <div class="workout-map" tal:condition="workout.has_gpx">
    93                 <a href="" tal:attributes="href request.resource_url(workout)">
    94                     <img src="" tal:attributes="src request.static_url(workout.map_screenshot);
    95                               alt workout.title; title workout.title">
    96                 </a>
    97             </div>
    98 
    9992            <ul class="workout-options">
    10093              <li class="owo-edit"><a href="" i18n:translate="" tal:attributes="href request.resource_url(workout, 'edit')"><span>edit</span></a></li>
  • ow/templates/workout.pt

    rc6219ed r02048a6  
    8787          <tal:c tal:content="context.rounded_distance"></tal:c> km
    8888        </li>
    89         <li tal:condition="context.speed">
    90           <tal:t i18n:translate="">Speed:</tal:t>
    91           <tal:t i18n:translate="">Avg.</tal:t>
    92           <tal:c tal:content="round(context.speed['avg'], 1)"></tal:c> km/h |
    93           <tal:t i18n:translate="">Max.</tal:t>
    94           <tal:c tal:content="round(context.speed['max'], 1)"></tal:c> km/h
    95         </li>
    9689        <li tal:condition="hr">
    9790          <tal:t i18n:translate="">Heart Rate:</tal:t>
     
    152145    <script type="text/javascript" tal:condition="context.has_gpx">
    153146     var workout_map = owjs.map({
    154          map_id: 'map',
    155147         latitude: ${start_point['latitude']},
    156148         longitude: ${start_point['longitude']},
     
    160152         end_icon: '${request.static_url('ow:static/components/leaflet-gpx/pin-icon-end.png')}',
    161153         shadow: '${request.static_url('ow:static/components/leaflet-gpx/pin-shadow.png')}',
    162          elevation: true,
    163          zoom_control: true
    164154     });
    165155     workout_map.render();
  • ow/tests/models/test_workout.py

    rc6219ed r02048a6  
    1212from ow.models.root import OpenWorkouts
    1313from ow.utilities import create_blob
    14 
    15 from ow.tests.helpers import join
    1614
    1715
     
    212210        # workout still not saved to the db
    213211        workout.tracking_file = Mock()
    214         workout.tracking_file._uncommitted.return_value = '/tmp/blobtempfile'
    215         workout.tracking_file.committed.return_value = None
     212        workout.tracking_file._p_blob_uncommitted = '/tmp/blobtempfile'
     213        workout.tracking_file._p_blob_committed = None
    216214        assert workout.tracking_file_path == '/tmp/blobtempfile'
    217         workout.tracking_file._uncommitted.return_value = None
    218         workout.tracking_file.committed.return_value = '/var/db/blobs/blobfile'
     215        workout.tracking_file._p_blob_uncommitted = None
     216        workout.tracking_file._p_blob_committed = '/var/db/blobs/blobfile'
    219217        assert workout.tracking_file_path == '/var/db/blobs/blobfile'
    220218
     
    225223        # workout still not saved to the db
    226224        workout.fit_file = Mock()
    227         workout.fit_file._uncommitted.return_value = '/tmp/blobtempfile'
    228         workout.fit_file.committed.return_value = None
     225        workout.fit_file._p_blob_uncommitted = '/tmp/blobtempfile'
     226        workout.fit_file._p_blob_committed = None
    229227        assert workout.fit_file_path == '/tmp/blobtempfile'
    230         workout.fit_file._uncommitted.return_value = None
    231         workout.fit_file.committed.return_value = '/var/db/blobs/blobfile'
     228        workout.fit_file._p_blob_uncommitted = None
     229        workout.fit_file._p_blob_committed = '/var/db/blobs/blobfile'
    232230        assert workout.fit_file_path == '/var/db/blobs/blobfile'
    233231
     
    516514        workout = root['john']['1']
    517515        # without tracking file
    518         assert not workout.has_tracking_file
     516        assert workout.has_tracking_file is False
    519517        # with tracking file
    520518        workout.tracking_file = 'faked tracking file'
    521         assert workout.has_tracking_file
     519        assert workout.has_tracking_file is True
    522520
    523521    def test_has_gpx(self, root):
    524522        workout = root['john']['1']
    525523        # without tracking file
    526         assert not workout.has_gpx
     524        assert workout.has_gpx is False
    527525        workout.tracking_filetype = 'fit'
    528         assert not workout.has_gpx
     526        assert workout.has_gpx is False
    529527        # with non-gpx tracking file
    530528        workout.tracking_file = 'faked tracking file'
    531529        workout.tracking_filetype = 'fit'
    532         assert not workout.has_gpx
     530        assert workout.has_gpx is False
    533531        # with gpx tracking file
    534532        workout.tracking_file = 'faked tracking file'
    535533        workout.tracking_filetype = 'gpx'
    536         assert workout.has_gpx
    537 
    538     def test_has_fit(self, root):
    539         workout = root['john']['1']
    540         # without tracking file
    541         assert not workout.has_fit
    542         # tracking_file is a fit, this should not happen, as uploading a fit
    543         # puts the fit file into .fit_file and generates a gpx for
    544         # .tracking_file
    545         workout.tracking_file = 'faked tracking file'
    546         workout.tracking_filetype = 'fit'
    547         assert not workout.has_fit
    548         # now, having a fit file returns true
    549         workout.fit_file = 'faked fit file'
    550         assert workout.has_fit
    551         # no matter what we have in tracking_file
    552         workout.tracking_filetype = 'gpx'
    553         assert workout.has_fit
    554         workout.tracking_file = None
    555         workout.tracking_filetype = None
    556         assert workout.has_fit
    557 
    558     @patch('ow.models.workout.os')
    559     @patch('ow.models.workout.save_map_screenshot')
    560     def test_map_screenshot_no_gpx(self, sms, os, root):
    561         workout = root['john']['1']
    562         assert workout.map_screenshot is None
    563         assert not os.path.abspath.called
    564         assert not os.path.dirname.called
    565         assert not os.path.join.called
    566         assert not os.path.exists.called
    567         assert not sms.called
    568 
    569     @patch('ow.models.workout.os')
    570     @patch('ow.models.workout.save_map_screenshot')
    571     def test_map_screenshot_save(self, sms, os, root):
    572         """
    573         A workout with a tracking file has no map screenshot, one is
    574         saved to the filesystem.
    575         This test simply asserts the calls to the separate methods that
    576         look for existing screenshots and save a new one
    577         """
    578         os.path.abspath.return_value = 'current_dir'
    579         os.path.join.side_effect = join
    580         # This forces the "save screenshot" code to be run
    581         os.path.exists.return_value = False
    582 
    583         workout = root['john']['1']
    584         workout.tracking_file = 'faked gpx file'
    585         workout.tracking_filetype = 'gpx'
    586 
    587         uid = str(root['john'].uid)
    588         assert workout.map_screenshot == 'ow:/static/maps/' + uid + '/1.png'
    589         assert os.path.abspath.called
    590         assert os.path.dirname.called
    591         assert os.path.join.call_count == 2
    592         assert os.path.exists.called
    593         sms.assert_called_once_with(workout)
    594 
    595     @patch('ow.models.workout.os')
    596     @patch('ow.models.workout.save_map_screenshot')
    597     def test_map_screenshot_do_not_save(self, sms, os, root):
    598         """
    599         A workout with a tracking file has a map screenshot, the path to that
    600         is returned without doing anything else
    601         """
    602         os.path.abspath.return_value = 'current_dir'
    603         os.path.join.side_effect = join
    604         # This forces the "save screenshot" code NOT to be run
    605         os.path.exists.return_value = True
    606 
    607         workout = root['john']['1']
    608         workout.tracking_file = 'faked gpx file'
    609         workout.tracking_filetype = 'gpx'
    610 
    611         uid = str(root['john'].uid)
    612         assert workout.map_screenshot == 'ow:/static/maps/' + uid + '/1.png'
    613         assert os.path.abspath.called
    614         assert os.path.dirname.called
    615         assert os.path.join.call_count == 2
    616         assert os.path.exists.called
    617         assert not sms.called
     534        assert workout.has_gpx is True
  • ow/tests/test_utilities.py

    rc6219ed r02048a6  
    11import os
    2 from datetime import timedelta
    3 from unittest.mock import patch
    42from pyexpat import ExpatError
    53from xml.dom.minidom import Element
    64
    75import pytest
    8 
    9 from ow.models.root import OpenWorkouts
    10 from ow.models.user import User
    11 from ow.models.workout import Workout
    126
    137from ow.utilities import (
     
    2216    mps_to_kmph,
    2317    kmph_to_mps,
    24     save_map_screenshot
    2518)
    26 
    27 from ow.tests.helpers import join
    2819
    2920
    3021class TestUtilities(object):
    31 
    32     @pytest.fixture
    33     def john(self):
    34         john = User(firstname='John', lastname='Doe',
    35                     email='john.doe@example.net')
    36         john.password = 's3cr3t'
    37         return john
    38 
    39     @pytest.fixture
    40     def root(self, john):
    41         root = OpenWorkouts()
    42         root.add_user(john)
    43         john['1'] = Workout(
    44             duration=timedelta(minutes=60),
    45             distance=30
    46         )
    47         return root
    4822
    4923    def test_slugify(self):
     
    8054    def test_kmph_to_mps(self):
    8155        assert kmph_to_mps(30) == 30 * 0.277778
    82 
    83     @patch('ow.utilities.os')
    84     @patch('ow.utilities.subprocess')
    85     def test_save_map_screenshot_no_gpx(self, subprocess, os, root, john):
    86         saved = save_map_screenshot(john['1'])
    87         assert not saved
    88         assert not os.path.abspath.called
    89         assert not os.path.dirname.called
    90         assert not os.path.join.called
    91         assert not os.path.exists.called
    92         assert not os.makedirs.called
    93         assert not subprocess.run.called
    94         # even having a fit tracking file, nothing is done
    95         john['1'].tracking_file = 'faked fit file'
    96         john['1'].tracking_filetype = 'fit'
    97         saved = save_map_screenshot(john['1'])
    98         assert not saved
    99         assert not os.path.abspath.called
    100         assert not os.path.dirname.called
    101         assert not os.path.join.called
    102         assert not os.path.exists.called
    103         assert not os.makedirs.called
    104         assert not subprocess.run.called
    105 
    106     @patch('ow.utilities.os')
    107     @patch('ow.utilities.subprocess')
    108     def test_save_map_screenshot_with_gpx(self, subprocess, os, root, john):
    109         os.path.abspath.return_value = 'current_dir'
    110         os.path.join.side_effect = join
    111         # This mimics what happens when the directory for this user map
    112         # screenshots does not exist, which means we don'have to create one
    113         # (calling os.makedirs)
    114         os.path.exists.return_value = False
    115 
    116         john['1'].tracking_file = 'faked gpx content'
    117         john['1'].tracking_filetype = 'gpx'
    118         saved = save_map_screenshot(john['1'])
    119         assert saved
    120         os.path.abspath.assert_called_once
    121         assert os.path.dirname.called
    122         assert os.path.join.call_count == 3
    123         assert os.path.exists.called
    124         assert os.makedirs.called
    125         subprocess.run.assert_called_once
    126 
    127     @patch('ow.utilities.os')
    128     @patch('ow.utilities.subprocess')
    129     def test_save_map_screenshot_with_gpx_makedirs(
    130             self, subprocess, os, root, john):
    131         os.path.abspath.return_value = 'current_dir'
    132         os.path.join.side_effect = join
    133         # If os.path.exists returns True, makedirs is not called
    134         os.path.exists.return_value = True
    135 
    136         john['1'].tracking_file = 'faked gpx content'
    137         john['1'].tracking_filetype = 'gpx'
    138         saved = save_map_screenshot(john['1'])
    139         assert saved
    140         os.path.abspath.assert_called_once
    141         assert os.path.dirname.called
    142         assert os.path.join.call_count == 3
    143         assert os.path.exists.called
    144         assert not os.makedirs.called
    145         subprocess.run.assert_called_once
    14656
    14757
  • ow/tests/views/test_workout.py

    rc6219ed r02048a6  
    429429                assert response.content_type == 'application/xml'
    430430                assert expected_body in response.body
    431 
    432     def test_workout_map_no_gpx(self, dummy_request):
    433         request = dummy_request
    434         user = request.root['john']
    435         workout = user.workouts()[0]
    436         response = workout_views.workout_map(workout, request)
    437         assert response == {'start_point': {}}
    438 
    439     def test_workout_map(self, dummy_request):
    440         request = dummy_request
    441         user = request.root['john']
    442         workout = user.workouts()[0]
    443         # to ensure has_gpx returns true
    444         workout.tracking_filetype = 'gpx'
    445         gpx_file_path = os.path.join(
    446             os.path.dirname(os.path.dirname(__file__)),
    447             'fixtures/20131013.gpx')
    448         with patch.object(workout, 'tracking_file') as tf:
    449             with open(gpx_file_path, 'r') as gpx_file:
    450                 tf.open.return_value = BytesIO(gpx_file.read().encode('utf-8'))
    451                 response = workout_views.workout_map(workout, request)
    452                 assert response == {
    453                     'start_point': {
    454                         'elevation': None,
    455                         'latitude': 37.108735040304566,
    456                         'longitude': 25.472489344630546
    457                     }
    458                 }
  • ow/utilities.py

    rc6219ed r02048a6  
    11import re
    2 import os
    3 import logging
    4 import subprocess
    52from datetime import datetime
    63from decimal import Decimal
     
    107from xml.dom import minidom
    118from ZODB.blob import Blob
    12 
    13 log = logging.getLogger(__name__)
    149
    1510
     
    189184        open_blob.write(data)
    190185    return blob
    191 
    192 
    193 def save_map_screenshot(workout):
    194     if workout.has_gpx:
    195         current_path = os.path.abspath(os.path.dirname(__file__))
    196         tool_path = os.path.join(current_path, '../bin/screenshot_map')
    197 
    198         screenshots_path = os.path.join(
    199             current_path, 'static/maps', str(workout.owner.uid))
    200         if not os.path.exists(screenshots_path):
    201             os.makedirs(screenshots_path)
    202 
    203         screenshot_path = os.path.join(
    204             screenshots_path, str(workout.workout_id))
    205         screenshot_path += '.png'
    206 
    207         subprocess.run(
    208             [tool_path, str(workout.owner.uid), str(workout.workout_id),
    209              screenshot_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    210 
    211         return True
    212 
    213     return False
  • ow/views/workout.py

    rc6219ed r02048a6  
    214214        content_disposition='attachment; filename="%s"' % gpx_slug,
    215215        body_file=context.tracking_file.open())
    216 
    217 
    218 @view_config(
    219     context=Workout,
    220     name='map',
    221     renderer='ow:templates/workout-map.pt')
    222 def workout_map(context, request):
    223     """
    224     Render a page that has only a map with tracking info
    225     """
    226     start_point = {}
    227     if context.has_gpx:
    228         with context.tracking_file.open() as gpx_file:
    229             gpx_contents = gpx_file.read()
    230             gpx_contents = gpx_contents.decode('utf-8')
    231             gpx = gpxpy.parse(gpx_contents)
    232             if gpx.tracks:
    233                 track = gpx.tracks[0]
    234                 center_point = track.get_center()
    235                 start_point = {'latitude': center_point.latitude,
    236                                'longitude': center_point.longitude,
    237                                'elevation': center_point.elevation}
    238     return {'start_point': start_point}
Note: See TracChangeset for help on using the changeset viewer.