- Timestamp:
- Jan 9, 2019, 12:31:33 PM (5 years ago)
- Branches:
- current, feature/docs, master
- Children:
- 119412d
- Parents:
- e3d7b13
- Location:
- ow
- Files:
-
- 1 added
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
ow/models/workout.py
re3d7b13 r53bb3e5 7 7 from repoze.folder import Folder 8 8 from pyramid.security import Allow, Everyone 9 from ow.utilities import GPXMinidomParser 9 10 from ow.utilities import ( 11 GPXMinidomParser, 12 copy_blob, 13 create_blob, 14 ) 15 16 from ow.fit import Fit 10 17 11 18 … … 56 63 self.tracking_file = kw.get('tracking_file', None) # Blob 57 64 self.tracking_filetype = '' # unicode string 65 # attr to store ANT fit files. For now this file is used to 66 # generate a gpx-encoded tracking file we then use through 67 # the whole app 68 self.fit_file = kw.get('fit_file', None) # Blob 58 69 59 70 @property … … 188 199 return None 189 200 201 @property 202 def tracking_file_path(self): 203 """ 204 Get the path to the blob file attached as a tracking file. 205 206 First check if the file was not committed to the db yet (new workout 207 not saved yet) and use the path to the temporary file on the fs. 208 If none is found there, go for the real blob file in the blobs 209 directory 210 """ 211 path = None 212 if self.tracking_file: 213 path = self.tracking_file._p_blob_uncommitted 214 if path is None: 215 path = self.tracking_file._p_blob_committed 216 return path 217 218 @property 219 def fit_file_path(self): 220 """ 221 Get the path to the blob file attached as a fit file. 222 223 First check if the file was not committed to the db yet (new workout 224 not saved yet) and use the path to the temporary file on the fs. 225 If none is found there, go for the real blob file in the blobs 226 directory 227 """ 228 path = None 229 if self.fit_file: 230 path = self.fit_file._p_blob_uncommitted 231 if path is None: 232 path = self.fit_file._p_blob_committed 233 return path 234 190 235 def load_from_file(self): 191 236 """ … … 195 240 if self.tracking_filetype == 'gpx': 196 241 self.load_from_gpx() 242 elif self.tracking_filetype == 'fit': 243 self.load_from_fit() 197 244 198 245 def load_from_gpx(self): … … 283 330 return parser.tracks 284 331 332 def load_from_fit(self): 333 """ 334 Try to load data from an ANT-compatible .fit file (if any has been 335 added to this workout). 336 337 "Load data" means: 338 339 1. Copy over the uploaded fit file to self.fit_file, so we can keep 340 that copy around for future use 341 342 2. generate a gpx object from the fit file 343 344 3. save the gpx object as the tracking_file, which then will be used 345 by the current code to display and gather data to be displayed/shown 346 to the user. 347 348 4. Grab some basic info from the fit file and store it in the Workout 349 """ 350 # backup the fit file 351 self.fit_file = copy_blob(self.tracking_file) 352 353 # create an instance of our Fit class 354 fit = Fit(self.fit_file_path) 355 fit.load() 356 357 # fit -> gpx and store that as the main tracking file 358 self.tracking_file = create_blob(fit.gpx, 'gpx') 359 self.tracking_filetype = 'gpx' 360 361 # grab the needed data from the fit file, update the workout 362 self.start = fit.data['start'] 363 # ensure this datetime start object is timezone-aware 364 self.start = self.start.replace(tzinfo=timezone.utc) 365 # duration comes in seconds, store a timedelta 366 self.duration = timedelta(seconds=fit.data['duration']) 367 # distance comes in meters 368 self.distance = Decimal(fit.data['distance']) / Decimal(1000.00) 369 self.uphill = Decimal(fit.data['uphill']) 370 self.downhill = Decimal(fit.data['downhill']) 371 # If the user did not provide us with a title, build one from the 372 # info in the fit file 373 if not self.title: 374 self.title = fit.name 375 376 if fit.data['avg_hr']: 377 self.hr_avg = Decimal(fit.data['avg_hr']) 378 self.hr_min = Decimal(fit.data['min_hr']) 379 self.hr_max = Decimal(fit.data['max_hr']) 380 381 if fit.data['avg_cad']: 382 self.cad_avg = Decimal(fit.data['avg_cad']) 383 self.cad_min = Decimal(fit.data['min_cad']) 384 self.cad_max = Decimal(fit.data['max_cad']) 385 386 if fit.data['avg_atemp']: 387 self.atemp_avg = Decimal(fit.data['avg_atemp']) 388 self.atemp_min = Decimal(fit.data['min_atemp']) 389 self.atemp_max = Decimal(fit.data['max_atemp']) 390 391 return True 392 285 393 @property 286 394 def has_tracking_file(self): … … 290 398 def has_gpx(self): 291 399 return self.has_tracking_file and self.tracking_filetype == 'gpx' 400 401 @property 402 def has_fit(self): 403 return self.fit_file is not None -
ow/tests/models/test_workout.py
re3d7b13 r53bb3e5 3 3 from datetime import datetime, timedelta, timezone 4 4 from decimal import Decimal 5 from unittest.mock import patch 5 from unittest.mock import patch, Mock 6 6 7 7 import pytest … … 195 195 assert workout.atemp['max'] == 12 196 196 assert workout.atemp['avg'] == 5 197 198 def test_tracking_file_path(self): 199 workout = Workout() 200 # no tracking file, path is None 201 assert workout.tracking_file_path is None 202 # workout still not saved to the db 203 workout.tracking_file = Mock() 204 workout.tracking_file._p_blob_uncommitted = '/tmp/blobtempfile' 205 workout.tracking_file._p_blob_committed = None 206 assert workout.tracking_file_path == '/tmp/blobtempfile' 207 workout.tracking_file._p_blob_uncommitted = None 208 workout.tracking_file._p_blob_committed = '/var/db/blobs/blobfile' 209 assert workout.tracking_file_path == '/var/db/blobs/blobfile' 197 210 198 211 def test_load_from_file_invalid(self): -
ow/utilities.py
re3d7b13 r53bb3e5 1 1 import re 2 import datetime 2 from datetime import datetime 3 from decimal import Decimal 4 from shutil import copyfileobj 5 3 6 from unidecode import unidecode 4 7 from xml.dom import minidom 5 from decimal import Decimal8 from ZODB.blob import Blob 6 9 7 10 … … 79 82 rfc3339 = trkpt.getElementsByTagName('time')[0].firstChild.data 80 83 try: 81 t = datetime. datetime.strptime(84 t = datetime.strptime( 82 85 rfc3339, '%Y-%m-%dT%H:%M:%S.%fZ') 83 86 except ValueError: 84 t = datetime. datetime.strptime(87 t = datetime.strptime( 85 88 rfc3339, '%Y-%m-%dT%H:%M:%SZ') 86 89 … … 113 116 'cad': cad, 114 117 'atemp': atemp}) 118 119 120 def semicircles_to_degrees(semicircles): 121 return semicircles * (180 / pow(2, 31)) 122 123 124 def degrees_to_semicircles(degrees): 125 return degrees * (pow(2, 31) / 180) 126 127 128 def miles_to_kms(miles): 129 factor = 0.62137119 130 return miles / factor 131 132 133 def kms_to_miles(kms): 134 factor = 0.62137119 135 return kms * factor 136 137 138 def meters_to_kms(meters): 139 return meters / 1000 140 141 142 def kms_to_meters(kms): 143 return kms * 1000 144 145 146 def mps_to_kmph(mps): 147 """ 148 Transform a value from meters-per-second to kilometers-per-hour 149 """ 150 return mps * 3.6 151 152 153 def kmph_to_mps(kmph): 154 """ 155 Transform a value from kilometers-per-hour to meters-per-second 156 """ 157 return kmph * 0.277778 158 159 160 def copy_blob(blob): 161 """ 162 Create a copy of a blob object, returning another blob object that is 163 the copy of the given blob file. 164 """ 165 new_blob = Blob() 166 if getattr(blob, 'file_extension', None): 167 new_blob.file_extension = blob.file_extension 168 with blob.open('r') as orig_blob, new_blob.open('w') as dest_blob: 169 orig_blob.seek(0) 170 copyfileobj(orig_blob, dest_blob) 171 return new_blob 172 173 174 def create_blob(data, file_extension): 175 """ 176 Create a ZODB blob file from some data, return the blob object 177 """ 178 blob = Blob() 179 blob.file_extension = file_extension 180 with blob.open('w') as open_blob: 181 # use .encode() to convert the string to bytes if needed 182 if not isinstance(data, bytes): 183 data = data.encode('utf-8') 184 open_blob.write(data) 185 return blob
Note: See TracChangeset
for help on using the changeset viewer.