Changes in / [c6219ed:02048a6] in OpenWorkouts-current
- Files:
-
- 5 deleted
- 11 edited
Legend:
- Unmodified
- Added
- Removed
-
.boring
rc6219ed r02048a6 138 138 ow.egg-info/* 139 139 ow/static/components 140 ow/static/maps -
ow/models/workout.py
rc6219ed r02048a6 1 import os 1 2 2 from datetime import datetime, timedelta, timezone 3 3 from decimal import Decimal … … 12 12 copy_blob, 13 13 create_blob, 14 mps_to_kmph,15 save_map_screenshot16 14 ) 17 15 … … 52 50 self.duration = kw.get('duration', None) # a timedelta object 53 51 self.distance = kw.get('distance', None) # kilometers, Decimal 54 self.speed = kw.get('speed', {})55 52 self.hr_min = kw.get('hr_min', None) # bpm, Decimal 56 53 self.hr_max = kw.get('hr_max', None) # bpm, Decimal … … 214 211 path = None 215 212 if self.tracking_file: 216 path = self.tracking_file._ uncommitted()213 path = self.tracking_file._p_blob_uncommitted 217 214 if path is None: 218 path = self.tracking_file. committed()215 path = self.tracking_file._p_blob_committed 219 216 return path 220 217 … … 231 228 path = None 232 229 if self.fit_file: 233 path = self.fit_file._ uncommitted()230 path = self.fit_file._p_blob_uncommitted 234 231 if path is None: 235 path = self.fit_file. committed()232 path = self.fit_file._p_blob_committed 236 233 return path 237 234 … … 351 348 4. Grab some basic info from the fit file and store it in the Workout 352 349 """ 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) 360 352 361 353 # create an instance of our Fit class … … 386 378 self.title = fit.name 387 379 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 394 380 if fit.data['avg_hr']: 395 381 self.hr_avg = Decimal(fit.data['avg_hr']) … … 420 406 def has_fit(self): 421 407 return self.fit_file is not None 422 423 @property424 def map_screenshot(self):425 """426 Return the static path to the screenshot image of the map for427 this workout (works only for workouts with gps tracking)428 """429 if not self.has_gpx:430 return None431 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 it439 save_map_screenshot(self)440 441 # the value returned is relative to the static files served442 # by the app, so we can use request.static_url() with it443 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 752 752 } 753 753 754 .workout-map img {755 width: 100%;756 }757 758 754 .owo-del a:hover { 759 755 color:red -
ow/static/js/ow.js
rc6219ed r02048a6 16 16 17 17 // parameters provided when creating an "instance" of a map 18 var map_id = spec.map_id;19 18 var latitude = spec.latitude; 20 19 var longitude = spec.longitude; … … 24 23 var end_icon = spec.end_icon; 25 24 var shadow = spec.shadow; 26 var elevation = spec.elevation;27 var zoom_control = spec.zoom_control;28 25 29 26 // OpenStreetMap urls and references 30 var openstreetmap_url = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' ;31 var openstreetmap_attr = 'Map data © <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 © <a href="http://www.osm.org">OpenStreetMap</a>' 32 29 33 30 // Some constants reused through the code … … 39 36 var create_map = function create_map(latitude, longitude, zoom) { 40 37 /* Create a Leaflet map, set center point and add tiles */ 41 map = L.map( map_id, {zoomControl: zoom_control});38 map = L.map('map'); 42 39 map.setView([latitude, longitude], zoom); 43 40 var tile_layer = L.tileLayer(openstreetmap_url, { … … 93 90 }, 94 91 }); 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 }); 107 96 gpx.addTo(map); 108 97 }; … … 111 100 // create the map, add elevation, load gpx 112 101 create_map(latitude, longitude, zoom); 113 if (elevation) { 114 add_elevation_chart(); 115 } 102 add_elevation_chart(); 116 103 // add_ow_charts(); 117 104 load_gpx(gpx_url); -
ow/templates/dashboard.pt
rc6219ed r02048a6 90 90 <div class="workout-intro" tal:content="workout.notes"></div> 91 91 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 99 92 <ul class="workout-options"> 100 93 <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 87 87 <tal:c tal:content="context.rounded_distance"></tal:c> km 88 88 </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/h95 </li>96 89 <li tal:condition="hr"> 97 90 <tal:t i18n:translate="">Heart Rate:</tal:t> … … 152 145 <script type="text/javascript" tal:condition="context.has_gpx"> 153 146 var workout_map = owjs.map({ 154 map_id: 'map',155 147 latitude: ${start_point['latitude']}, 156 148 longitude: ${start_point['longitude']}, … … 160 152 end_icon: '${request.static_url('ow:static/components/leaflet-gpx/pin-icon-end.png')}', 161 153 shadow: '${request.static_url('ow:static/components/leaflet-gpx/pin-shadow.png')}', 162 elevation: true,163 zoom_control: true164 154 }); 165 155 workout_map.render(); -
ow/tests/models/test_workout.py
rc6219ed r02048a6 12 12 from ow.models.root import OpenWorkouts 13 13 from ow.utilities import create_blob 14 15 from ow.tests.helpers import join16 14 17 15 … … 212 210 # workout still not saved to the db 213 211 workout.tracking_file = Mock() 214 workout.tracking_file._ uncommitted.return_value= '/tmp/blobtempfile'215 workout.tracking_file. committed.return_value= None212 workout.tracking_file._p_blob_uncommitted = '/tmp/blobtempfile' 213 workout.tracking_file._p_blob_committed = None 216 214 assert workout.tracking_file_path == '/tmp/blobtempfile' 217 workout.tracking_file._ uncommitted.return_value= None218 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' 219 217 assert workout.tracking_file_path == '/var/db/blobs/blobfile' 220 218 … … 225 223 # workout still not saved to the db 226 224 workout.fit_file = Mock() 227 workout.fit_file._ uncommitted.return_value= '/tmp/blobtempfile'228 workout.fit_file. committed.return_value= None225 workout.fit_file._p_blob_uncommitted = '/tmp/blobtempfile' 226 workout.fit_file._p_blob_committed = None 229 227 assert workout.fit_file_path == '/tmp/blobtempfile' 230 workout.fit_file._ uncommitted.return_value= None231 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' 232 230 assert workout.fit_file_path == '/var/db/blobs/blobfile' 233 231 … … 516 514 workout = root['john']['1'] 517 515 # without tracking file 518 assert not workout.has_tracking_file516 assert workout.has_tracking_file is False 519 517 # with tracking file 520 518 workout.tracking_file = 'faked tracking file' 521 assert workout.has_tracking_file 519 assert workout.has_tracking_file is True 522 520 523 521 def test_has_gpx(self, root): 524 522 workout = root['john']['1'] 525 523 # without tracking file 526 assert not workout.has_gpx524 assert workout.has_gpx is False 527 525 workout.tracking_filetype = 'fit' 528 assert not workout.has_gpx526 assert workout.has_gpx is False 529 527 # with non-gpx tracking file 530 528 workout.tracking_file = 'faked tracking file' 531 529 workout.tracking_filetype = 'fit' 532 assert not workout.has_gpx530 assert workout.has_gpx is False 533 531 # with gpx tracking file 534 532 workout.tracking_file = 'faked tracking file' 535 533 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 1 1 import os 2 from datetime import timedelta3 from unittest.mock import patch4 2 from pyexpat import ExpatError 5 3 from xml.dom.minidom import Element 6 4 7 5 import pytest 8 9 from ow.models.root import OpenWorkouts10 from ow.models.user import User11 from ow.models.workout import Workout12 6 13 7 from ow.utilities import ( … … 22 16 mps_to_kmph, 23 17 kmph_to_mps, 24 save_map_screenshot25 18 ) 26 27 from ow.tests.helpers import join28 19 29 20 30 21 class TestUtilities(object): 31 32 @pytest.fixture33 def john(self):34 john = User(firstname='John', lastname='Doe',35 email='john.doe@example.net')36 john.password = 's3cr3t'37 return john38 39 @pytest.fixture40 def root(self, john):41 root = OpenWorkouts()42 root.add_user(john)43 john['1'] = Workout(44 duration=timedelta(minutes=60),45 distance=3046 )47 return root48 22 49 23 def test_slugify(self): … … 80 54 def test_kmph_to_mps(self): 81 55 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 saved88 assert not os.path.abspath.called89 assert not os.path.dirname.called90 assert not os.path.join.called91 assert not os.path.exists.called92 assert not os.makedirs.called93 assert not subprocess.run.called94 # even having a fit tracking file, nothing is done95 john['1'].tracking_file = 'faked fit file'96 john['1'].tracking_filetype = 'fit'97 saved = save_map_screenshot(john['1'])98 assert not saved99 assert not os.path.abspath.called100 assert not os.path.dirname.called101 assert not os.path.join.called102 assert not os.path.exists.called103 assert not os.makedirs.called104 assert not subprocess.run.called105 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 = join111 # This mimics what happens when the directory for this user map112 # screenshots does not exist, which means we don'have to create one113 # (calling os.makedirs)114 os.path.exists.return_value = False115 116 john['1'].tracking_file = 'faked gpx content'117 john['1'].tracking_filetype = 'gpx'118 saved = save_map_screenshot(john['1'])119 assert saved120 os.path.abspath.assert_called_once121 assert os.path.dirname.called122 assert os.path.join.call_count == 3123 assert os.path.exists.called124 assert os.makedirs.called125 subprocess.run.assert_called_once126 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 = join133 # If os.path.exists returns True, makedirs is not called134 os.path.exists.return_value = True135 136 john['1'].tracking_file = 'faked gpx content'137 john['1'].tracking_filetype = 'gpx'138 saved = save_map_screenshot(john['1'])139 assert saved140 os.path.abspath.assert_called_once141 assert os.path.dirname.called142 assert os.path.join.call_count == 3143 assert os.path.exists.called144 assert not os.makedirs.called145 subprocess.run.assert_called_once146 56 147 57 -
ow/tests/views/test_workout.py
rc6219ed r02048a6 429 429 assert response.content_type == 'application/xml' 430 430 assert expected_body in response.body 431 432 def test_workout_map_no_gpx(self, dummy_request):433 request = dummy_request434 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_request441 user = request.root['john']442 workout = user.workouts()[0]443 # to ensure has_gpx returns true444 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.472489344630546457 }458 } -
ow/utilities.py
rc6219ed r02048a6 1 1 import re 2 import os3 import logging4 import subprocess5 2 from datetime import datetime 6 3 from decimal import Decimal … … 10 7 from xml.dom import minidom 11 8 from ZODB.blob import Blob 12 13 log = logging.getLogger(__name__)14 9 15 10 … … 189 184 open_blob.write(data) 190 185 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 True212 213 return False -
ow/views/workout.py
rc6219ed r02048a6 214 214 content_disposition='attachment; filename="%s"' % gpx_slug, 215 215 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 info225 """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.