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
RevLine 
[5bdfbfb]1from decimal import Decimal
[5ec3a0b]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,
[78af3d1]25    permission='edit',
[5ec3a0b]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,
[78af3d1]62    permission='edit',
[5ec3a0b]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    """
[74b9c4d]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
[5ec3a0b]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,
[78af3d1]97    permission='edit',
[5ec3a0b]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
[5bdfbfb]127        # ensure distance is a decimal
128        context.distance = Decimal(context.distance)
[5ec3a0b]129        catalog = get_catalog(context)
130        reindex_object(catalog, context)
131        return HTTPFound(location=request.resource_url(context))
132
[fb327e1]133    # round some values before rendering
134    if form.data['distance']:
135        form.data['distance'] = round(form.data['distance'], 2)
136
[5ec3a0b]137    return {
138        'form': FormRenderer(form)
139    }
140
141
142@view_config(
143    context=Workout,
[78af3d1]144    permission='edit',
[5ec3a0b]145    name='update-from-file',
146    renderer='ow:templates/update_workout_from_file.pt')
147def update_workout_from_file(context, request):
[74b9c4d]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
[5ec3a0b]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,
[78af3d1]173    permission='delete',
[5ec3a0b]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,
[78af3d1]191    permission='view',
[5ec3a0b]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.
[78af3d1]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.
[5ec3a0b]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())
[d1c4782]232
233
234@view_config(
235    context=Workout,
236    name='map',
237    renderer='ow:templates/workout-map.pt')
238def workout_map(context, request):
239    """
[78af3d1]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.
[d1c4782]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.