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

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

Fixed title(s) on the edit workout template.
Round distance value shown in the edit workout template.

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