source: OpenWorkouts-current/ow/views/workout.py @ d1c4782

currentfeature/docs
Last change on this file since d1c4782 was d1c4782, checked in by borja <borja@…>, 5 years ago

Show a capture of the workout map, as an image, in the dashboard:

  • Added a view to render the tracking map of a workout full screen
  • Added a small shell script that uses chrome to grabs a screenshot of the full screen map view of a workout, then uses imagemagick convert to crop/resize it and finally saves it in a given location
  • Added a static/maps directory to store maps captures
  • Added static/maps to the boring/ignore file
  • Added a tool in utilities.py to call the shell script that captures the screenshot of the map
  • Added a method to the Workout model, that returns the static path to the workout map capture (valid to use with request.static_url()). If there is no capture yet, call the tool to make one
  • Added code to dashboard.pt to show the capture of the map
  • Added a new parameter to te ow maps js code, allowing us to hide/show the zoom controls of the map when building a new one
  • Property mode set to 100644
File size: 8.6 KB
Line 
1from datetime import datetime, timedelta, time, timezone
2
3import gpxpy
4
5from pyramid.httpexceptions import HTTPFound, HTTPNotFound
6from pyramid.view import view_config
7from pyramid.response import Response
8from pyramid_simpleform import Form
9from pyramid_simpleform.renderers import FormRenderer
10
11from ..schemas.workout import (
12    UploadedWorkoutSchema,
13    ManualWorkoutSchema,
14    UpdateWorkoutSchema
15)
16from ..models.workout import Workout
17from ..models.user import User
18from ..utilities import slugify
19from ..catalog import get_catalog, reindex_object, remove_from_catalog
20
21
22@view_config(
23    context=User,
24    name='add-workout-manually',
25    renderer='ow:templates/add_manual_workout.pt')
26def add_workout_manually(context, request):
27    form = Form(request, schema=ManualWorkoutSchema(),
28                defaults={'duration_hours': '0',
29                          'duration_minutes': '0',
30                          'duration_seconds': '0'})
31
32    if 'submit' in request.POST and form.validate():
33        # exclude the three duration_* and start_* fields, so they won't be
34        # "bind" to the object, we do calculate both the full duration in
35        # seconds and the full datetime "start" and we save that
36        excluded = ['duration_hours', 'duration_minutes', 'duration_seconds',
37                    'start_date', 'start_time']
38        workout = form.bind(Workout(), exclude=excluded)
39        duration = timedelta(hours=form.data['duration_hours'],
40                             minutes=form.data['duration_minutes'],
41                             seconds=form.data['duration_seconds'])
42        workout.duration = duration
43        # create a time object first using the given hours and minutes
44        start_time = time(form.data['start_time'][0],
45                          form.data['start_time'][1])
46        # combine the given start date with the built time object
47        start = datetime.combine(form.data['start_date'], start_time,
48                                 tzinfo=timezone.utc)
49        workout.start = start
50        context.add_workout(workout)
51        return HTTPFound(location=request.resource_url(workout))
52
53    return {
54        'form': FormRenderer(form)
55    }
56
57
58@view_config(
59    context=User,
60    name='add-workout',
61    renderer='ow:templates/add_workout.pt')
62def add_workout(context, request):
63    """
64    Add a workout uploading a tracking file
65    """
66    # if not given a file there is an empty byte in POST, which breaks
67    # our blob storage validator.
68    # dirty fix until formencode fixes its api.is_empty method
69    if isinstance(request.POST.get('tracking_file', None), bytes):
70        request.POST['tracking_file'] = ''
71
72    form = Form(request, schema=UploadedWorkoutSchema())
73
74    if 'submit' in request.POST and form.validate():
75        # Grab some information from the tracking file
76        trackfile_ext = request.POST['tracking_file'].filename.split('.')[-1]
77        # Create a Workout instance based on the input from the form
78        workout = form.bind(Workout())
79        # Add the type of tracking file
80        workout.tracking_filetype = trackfile_ext
81        # Add basic info gathered from the file
82        workout.load_from_file()
83        # Add the workout
84        context.add_workout(workout)
85        return HTTPFound(location=request.resource_url(workout))
86
87    return {
88        'form': FormRenderer(form)
89    }
90
91
92@view_config(
93    context=Workout,
94    name='edit',
95    renderer='ow:templates/edit_manual_workout.pt')
96def edit_workout(context, request):
97    """
98    Edit manually an existing workout. This won't let users attach/update
99    tracking files, just manually edit of the values.
100    """
101    form = Form(request, schema=ManualWorkoutSchema(), obj=context)
102    if 'submit' in request.POST and form.validate():
103        # exclude the three duration_* and start_* fields, so they won't be
104        # "bind" to the object, we do calculate both the full duration in
105        # seconds and the full datetime "start" and we save that
106        excluded = ['duration_hours', 'duration_minutes', 'duration_seconds',
107                    'start_date', 'start_time']
108
109        form.bind(context, exclude=excluded)
110
111        duration = timedelta(hours=form.data['duration_hours'],
112                             minutes=form.data['duration_minutes'],
113                             seconds=form.data['duration_seconds'])
114        context.duration = duration
115
116        # create a time object first using the given hours and minutes
117        start_time = time(form.data['start_time'][0],
118                          form.data['start_time'][1])
119        # combine the given start date with the built time object
120        start = datetime.combine(form.data['start_date'], start_time,
121                                 tzinfo=timezone.utc)
122        context.start = start
123        catalog = get_catalog(context)
124        reindex_object(catalog, context)
125        return HTTPFound(location=request.resource_url(context))
126
127    return {
128        'form': FormRenderer(form)
129    }
130
131
132@view_config(
133    context=Workout,
134    name='update-from-file',
135    renderer='ow:templates/update_workout_from_file.pt')
136def update_workout_from_file(context, request):
137    # if not given a file there is an empty byte in POST, which breaks
138    # our blob storage validator.
139    # dirty fix until formencode fixes its api.is_empty method
140    if isinstance(request.POST.get('tracking_file', None), bytes):
141        request.POST['tracking_file'] = ''
142
143    form = Form(request, schema=UpdateWorkoutSchema())
144    if 'submit' in request.POST and form.validate():
145        # Grab some information from the tracking file
146        trackfile_ext = request.POST['tracking_file'].filename.split('.')[-1]
147        # Update the type of tracking file
148        context.tracking_filetype = trackfile_ext
149        form.bind(context)
150        # Override basic info gathered from the file
151        context.load_from_file()
152        catalog = get_catalog(context)
153        reindex_object(catalog, context)
154        return HTTPFound(location=request.resource_url(context))
155    return {
156        'form': FormRenderer(form)
157    }
158
159
160@view_config(
161    context=Workout,
162    name='delete',
163    renderer='ow:templates/delete_workout.pt')
164def delete_workout(context, request):
165    """
166    Delete a workout
167    """
168    if 'submit' in request.POST:
169        if request.POST.get('delete', None) == 'yes':
170            catalog = get_catalog(context)
171            remove_from_catalog(catalog, context)
172            del request.root[request.authenticated_userid][context.workout_id]
173            return HTTPFound(location=request.resource_url(request.root))
174    return {}
175
176
177@view_config(
178    context=Workout,
179    renderer='ow:templates/workout.pt')
180def workout(context, request):
181    """
182    Details page for a workout
183    """
184    start_point = {}
185    if context.has_gpx:
186        with context.tracking_file.open() as gpx_file:
187            gpx_contents = gpx_file.read()
188            gpx_contents = gpx_contents.decode('utf-8')
189            gpx = gpxpy.parse(gpx_contents)
190            if gpx.tracks:
191                track = gpx.tracks[0]
192                center_point = track.get_center()
193                start_point = {'latitude': center_point.latitude,
194                               'longitude': center_point.longitude,
195                               'elevation': center_point.elevation}
196    return {'start_point': start_point}
197
198
199@view_config(
200    context=Workout,
201    name='gpx')
202def workout_gpx(context, request):
203    """
204    Return a gpx file with the workout tracking information, if any.
205    For now, simply return the gpx file if it has been attached to the
206    workout.
207    """
208    if not context.has_gpx:
209        return HTTPNotFound()
210    # Generate a proper file name to suggest on the download
211    gpx_slug = slugify(context.title) + '.gpx'
212    return Response(
213        content_type='application/xml',
214        content_disposition='attachment; filename="%s"' % gpx_slug,
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')
222def 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 TracBrowser for help on using the repository browser.