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

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

Fix permissions. From now on users can see (and edit, delete, etc) their own data

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