Changeset b3374f6 in OpenWorkouts-current for ow


Ignore:
Timestamp:
Feb 12, 2019, 5:55:33 PM (5 years ago)
Author:
Borja Lopez <borja@…>
Branches:
current, feature/docs, master
Children:
c999b73e
Parents:
f713dbc
Message:

(#52) - Make map screenshot generation async and non-blocker of the dashboard
and user profile pages:

  • workout.map_screenshot is a property again, returns a string with the static path (suitable for use with request.static_url()) for the map screenshot file if exists, none otherwise
  • added a couple of helpers to build the proper screenshot name and full path on the filesystem
  • added a view to generate the map if needed, returning the full static url to the screenshot file in a json-encoded stream
  • added a new js ow tool that looks for workouts that still don't have a map screenshot, calling the view to generate that screenshot and updating the map screenshot when the file is ready
  • modified the dashboard and user profile templates, so a dummy/placeholder image is shown when no map screenshot is yet ready, plus setup and calls for the js tool that generates the maps.
Location:
ow
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • ow/models/workout.py

    rf713dbc rb3374f6  
    1414    create_blob,
    1515    mps_to_kmph,
    16     save_map_screenshot,
    1716    timedelta_to_hms
    1817)
     
    423422        return self.fit_file is not None
    424423
    425     def map_screenshot(self, request):
     424    @property
     425    def map_screenshot_name(self):
     426        return os.path.join(
     427            str(self.owner.uid), str(self.workout_id) + '.png')
     428
     429    @property
     430    def map_screenshot_path(self):
     431        current_path = os.path.abspath(os.path.dirname(__file__))
     432        return os.path.join(
     433            current_path, '../static/maps', self.map_screenshot_name)
     434
     435    @property
     436    def map_screenshot(self):
    426437        """
    427438        Return the static path to the screenshot image of the map for
     
    431442            return None
    432443
    433         screenshot_name = os.path.join(
    434             str(self.owner.uid), str(self.workout_id) + '.png')
    435         current_path = os.path.abspath(os.path.dirname(__file__))
    436         screenshot_path = os.path.join(
    437             current_path, '../static/maps', screenshot_name)
    438 
    439         if not os.path.exists(screenshot_path):
    440             # screenshot does not exist, generate it
    441             save_map_screenshot(self, request)
    442 
    443         static_path = os.path.join('static/maps', screenshot_name)
    444         return request.static_url('ow:' + static_path)
     444        if not os.path.exists(self.map_screenshot_path):
     445            return None
     446
     447        static_path = os.path.join('static/maps', self.map_screenshot_name)
     448        # return a string we can use with request.static_url
     449        return 'ow:' + static_path
  • ow/static/js/ow.js

    rf713dbc rb3374f6  
    452452
    453453};
     454
     455
     456owjs.map_shots = function(spec) {
     457
     458    "use strict";
     459
     460    var img_selector = spec.img_selector;
     461
     462    var run = function run(){
     463        $(img_selector).each(function(){
     464            var img = $(this);
     465            var a = $(this).parent();
     466            var url = a.attr('href') + 'map-shot';
     467            var jqxhr = $.getJSON(url, function(info) {
     468                img.fadeOut('fast', function () {
     469                    img.attr('src', info['url']);
     470                    img.fadeIn('fast');
     471                });
     472                img.removeClass('js-needs-map');
     473            });
     474        });
     475    };
     476
     477    var that = {}
     478    that.run = run;
     479    return that
     480
     481};
  • ow/templates/dashboard.pt

    rf713dbc rb3374f6  
    7979
    8080            <div class="workout-map" tal:condition="workout.has_gpx">
    81                 <a href="" tal:attributes="href request.resource_url(workout)">
    82                     <img src="" tal:attributes="src workout.map_screenshot(request);
    83                               alt workout.title; title workout.title">
    84                 </a>
     81              <a href="" tal:attributes="href request.resource_url(workout)">
     82                <tal:has-screenshot tal:condition="workout.map_screenshot is not None">
     83                  <img src="" tal:attributes="src request.static_url(workout.map_screenshot);
     84                            alt workout.title; title workout.title">
     85                </tal:has-screenshot>
     86                <tal:has-not-screenshot tal:condition="workout.map_screenshot is None">
     87                  <img src="" tal:attributes="src request.static_url('ow:static/media/img/no_map.png');
     88                            alt workout.title; title workout.title; class 'js-needs-map'">
     89                </tal:has-not-screenshot>
     90              </a>
    8591            </div>
    8692
     
    193199
    194200    <script type="text/javascript">
     201
     202     var map_shots = owjs.map_shots({
     203         img_selector: 'img.js-needs-map',
     204     })
     205     map_shots.run();
     206
    195207     var week_chart = owjs.week_chart({
    196208         chart_selector: 'div.js-week-stats svg',
  • ow/templates/profile.pt

    rf713dbc rb3374f6  
    144144              <div class="workout-map" tal:condition="workout.has_gpx">
    145145                <a href="" tal:attributes="href request.resource_url(workout)">
    146                   <img src="" tal:attributes="src workout.map_screenshot(request);
    147                             alt workout.title; title workout.title">
     146                  <tal:has-screenshot tal:condition="workout.map_screenshot is not None">
     147                    <img src="" tal:attributes="src request.static_url(workout.map_screenshot);
     148                              alt workout.title; title workout.title">
     149                  </tal:has-screenshot>
     150                  <tal:has-not-screenshot tal:condition="workout.map_screenshot is None">
     151                    <img src="" tal:attributes="src request.static_url('ow:static/media/img/no_map.png');
     152                              alt workout.title; title workout.title; class 'js-needs-map'">
     153                  </tal:has-not-screenshot>
    148154                </a>
    149155              </div>
     
    187193
    188194    <script type="text/javascript">
     195     var map_shots = owjs.map_shots({
     196         img_selector: 'img.js-needs-map',
     197     })
     198     map_shots.run();
     199
    189200     var y_axis_labels = {
    190201         "distance": "Kilometers",
  • ow/tests/helpers.py

    rf713dbc rb3374f6  
     1
    12def join(*args, **kwargs):
    2     """ Faked join method, for mocking purposes """
    3     return '/' + '/'.join([arg for arg in args])
     3    """
     4    Faked join method, for mocking purposes
     5    """
     6    return '/' + '/'.join([arg.strip('/') for arg in args])
  • ow/tests/models/test_workout.py

    rf713dbc rb3374f6  
    77import pytest
    88from pyramid.security import Allow, Everyone, Deny, ALL_PERMISSIONS
    9 from pyramid.testing import DummyRequest
    109
    1110from ow.models.workout import Workout
     
    573572        assert workout.has_fit
    574573
     574    def test_map_screenshot_name(self, root):
     575        workout = root['john']['1']
     576        assert workout.map_screenshot_name == (
     577            str(root['john'].uid) + '/' + str(workout.workout_id) + '.png')
     578
     579    def test_map_screenshot_path(self, root):
     580        workout = root['john']['1']
     581        assert workout.map_screenshot_path.endswith(
     582            'static/maps/' + workout.map_screenshot_name)
     583
    575584    @patch('ow.models.workout.os')
    576     @patch('ow.models.workout.save_map_screenshot')
    577     def test_map_screenshot_no_gpx(self, sms, os, root):
    578         workout = root['john']['1']
    579         request = DummyRequest()
    580         assert workout.map_screenshot(request) is None
    581         assert not os.path.abspath.called
    582         assert not os.path.dirname.called
     585    def test_map_screenshot_no_gpx(self, os, root):
     586        workout = root['john']['1']
     587        assert workout.map_screenshot is None
    583588        assert not os.path.join.called
    584589        assert not os.path.exists.called
    585         assert not sms.called
    586590
    587591    @patch('ow.models.workout.os')
    588     @patch('ow.models.workout.save_map_screenshot')
    589     def test_map_screenshot_save(self, sms, os, root):
    590         """
    591         A workout with a tracking file has no map screenshot, one is
    592         saved to the filesystem.
    593         This test simply asserts the calls to the separate methods that
    594         look for existing screenshots and save a new one
    595         """
    596         os.path.abspath.return_value = 'current_dir'
    597         os.path.join.side_effect = join
    598         # This forces the "save screenshot" code to be run
     592    def test_map_screenshot_no_shot(self, os, root):
     593        """
     594        A workout with a tracking file has no map screenshot
     595        """
     596        # This says "no screenshot found"
    599597        os.path.exists.return_value = False
    600598
     
    603601        workout.tracking_filetype = 'gpx'
    604602
    605         uid = str(root['john'].uid)
    606         request = DummyRequest()
    607         # dummyrequest can't resolve static assets without adding a lot
    608         # of boilerplate, no need for that here
    609         request.static_url = Mock()
    610         request.static_url.return_value = 'ow:/static/maps/' + uid + '/1.png'
    611         res = workout.map_screenshot(request)
    612         assert res == '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 == 3
    616         assert os.path.exists.called
    617         sms.assert_called_once_with(workout, request)
     603        assert workout.map_screenshot is None
     604        assert os.path.join.called
     605        os.path.exists.assert_called_once_with(workout.map_screenshot_path)
    618606
    619607    @patch('ow.models.workout.os')
    620     @patch('ow.models.workout.save_map_screenshot')
    621     def test_map_screenshot_do_not_save(self, sms, os, root):
     608    def test_map_screenshot_has_shot(self, os, root):
    622609        """
    623610        A workout with a tracking file has a map screenshot, the path to that
    624611        is returned without doing anything else
    625612        """
    626         os.path.abspath.return_value = 'current_dir'
     613        # This says "yeah, we have a screenshot"
     614        os.path.exists.return_value = True
     615        os.path.abspath.return_value = '/'
    627616        os.path.join.side_effect = join
    628         # This forces the "save screenshot" code NOT to be run
    629         os.path.eisxts.return_value = True
    630617
    631618        workout = root['john']['1']
    632619        workout.tracking_file = 'faked gpx file'
    633620        workout.tracking_filetype = 'gpx'
    634 
    635621        uid = str(root['john'].uid)
    636         request = DummyRequest()
    637         # dummyrequest can't resolve static assets without adding a lot
    638         # of boilerplate, no need for that here
    639         request.static_url = Mock()
    640         request.static_url.return_value = 'ow:/static/maps/' + uid + '/1.png'
    641         res = workout.map_screenshot(request)
    642         assert res == 'ow:/static/maps/' + uid + '/1.png'
    643         assert os.path.abspath.called
    644         assert os.path.dirname.called
    645         assert os.path.join.call_count == 3
    646         assert os.path.exists.called
    647         assert not sms.called
     622        assert workout.map_screenshot == 'ow:/static/maps/' + uid + '/1.png'
     623        os.path.exists.assert_called_once_with(workout.map_screenshot_path)
     624        assert os.path.join.called
  • ow/views/workout.py

    rf713dbc rb3374f6  
    11from decimal import Decimal
    22from datetime import datetime, timedelta, time, timezone
     3import json
    34
    45import gpxpy
     
    1718from ..models.workout import Workout
    1819from ..models.user import User
    19 from ..utilities import slugify
     20from ..utilities import slugify, save_map_screenshot
    2021from ..catalog import get_catalog, reindex_object, remove_from_catalog
    2122
     
    255256                               'elevation': center_point.elevation}
    256257    return {'start_point': start_point}
     258
     259
     260@view_config(
     261    context=Workout,
     262    permission='edit',
     263    name='map-shot')
     264def workout_map_shot(context, request):
     265    """
     266    Ask for the screenshot of a map, creating one if it does not exist.
     267    A json object is returned, containing the info for the needed screenshot
     268    """
     269    if context.map_screenshot is None:
     270        save_map_screenshot(context, request)
     271
     272    info = {'url': request.static_url(context.map_screenshot)}
     273    return Response(content_type='application/json',
     274                    charset='utf-8',
     275                    body=json.dumps(info))
Note: See TracChangeset for help on using the changeset viewer.