# All the functionality needed to connect to Runkeeper # Python import oauth2 as oauth import cgi import requests import requests.auth import json from django.utils import timezone from datetime import datetime from datetime import timedelta import numpy as np from dateutil import parser import time import pytz import math import gzip from math import sin,cos,atan2,sqrt import os,sys import urllib import base64 from io import BytesIO from time import strftime # Django from django.shortcuts import render_to_response from django.http import HttpResponseRedirect, HttpResponse,JsonResponse from django.conf import settings from django.contrib.auth import authenticate, login, logout from django.contrib.auth.models import User from django.contrib.auth.decorators import login_required #from django.contrib import messages # Project # from .models import Profile from rowingdata import rowingdata import pandas as pd from rowers.models import Rower,Workout,checkworkoutuser from rowsandall_app.settings import ( C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET, STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET, TP_CLIENT_ID, TP_CLIENT_SECRET, TP_REDIRECT_URI,TP_CLIENT_KEY, ) tpapilocation = "https://api.trainingpeaks.com" from celery import Celery,app from django_rq import job import time from async_messages import message_user,messages # Custom error class - to raise a NoTokenError class TPNoTokenError(Exception): def __init__(self,value): self.value=value def __str__(self): return repr(self.value) # Exponentially weighted moving average # Used for data smoothing of the jagged data obtained by Strava # See bitbucket issue 72 def ewmovingaverage(interval,window_size): # Experimental code using Exponential Weighted moving average try: intervaldf = pd.DataFrame({'v':interval}) idf_ewma1 = intervaldf.ewm(span=window_size) idf_ewma2 = intervaldf[::-1].ewm(span=window_size) i_ewma1 = idf_ewma1.mean().ix[:,'v'] i_ewma2 = idf_ewma2.mean().ix[:,'v'] interval2 = np.vstack((i_ewma1,i_ewma2[::-1])) interval2 = np.mean( interval2, axis=0) # average except ValueError: interval2 = interval return interval2 from utils import geo_distance # Custom exception handler, returns a 401 HTTP message # with exception details in the json data def custom_exception_handler(exc,message): response = { "errors": [ { "code": str(exc), "detail": message, } ] } res = HttpResponse(message) res.status_code = 401 res.json = json.dumps(response) return res # Checks if user has UnderArmour token, renews them if they are expired def tp_open(user): r = Rower.objects.get(user=user) if (r.tptoken == '') or (r.tptoken is None): s = "Token doesn't exist. Need to authorize" raise TPNoTokenError("User has no token") else: if (timezone.now()>r.tptokenexpirydate): res = do_refresh_token(r.tprefreshtoken) if res[0] != 0: r.tptoken = res[0] r.tprefreshtoken = res[2] expirydatetime = timezone.now()+timedelta(seconds=res[1]) r.tptokenexpirydate = expirydatetime r.save() thetoken = r.tptoken else: raise TPNoTokenError("Refresh token invalid") else: thetoken = r.tptoken return thetoken # Refresh ST token using refresh token def do_refresh_token(refreshtoken): client_auth = requests.auth.HTTPBasicAuth(TP_CLIENT_KEY, TP_CLIENT_SECRET) post_data = {"grant_type": "refresh_token", "client_secret": TP_CLIENT_SECRET, "client_id":TP_CLIENT_KEY, "refresh_token": refreshtoken, } headers = {'user-agent': 'sanderroosendaal', 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded', } url = "https://oauth.trainingpeaks.com/oauth/token" response = requests.post(url, data=post_data, headers=headers) if response.status_code == 200 or response.status_code == 201: token_json = response.json() thetoken = token_json['access_token'] expires_in = token_json['expires_in'] try: refresh_token = token_json['refresh_token'] except KeyError: refresh_token = refreshtoken else: return [0,0,0] return [thetoken,expires_in,refresh_token] # Exchange access code for long-lived access token def get_token(code): client_auth = requests.auth.HTTPBasicAuth(TP_CLIENT_KEY, TP_CLIENT_SECRET) post_data = { "client_id":TP_CLIENT_KEY, "grant_type": "authorization_code", "code": code, "redirect_uri":TP_REDIRECT_URI, "client_secret": TP_CLIENT_SECRET, } headers = { 'Content-Type': 'application/x-www-form-urlencoded', } response = requests.post("https://oauth.trainingpeaks.com/oauth/token", data=post_data) try: token_json = response.json() thetoken = token_json['access_token'] expires_in = token_json['expires_in'] refresh_token = token_json['refresh_token'] except KeyError: thetoken = 0 expires_in = 0 refresh_token = 0 return thetoken,expires_in,refresh_token # Make authorization URL including random string def make_authorization_url(request): # Generate a random string for the state parameter # Save it for use later to prevent xsrf attacks from uuid import uuid4 state = str(uuid4()) params = {"client_id": TP_CLIENT_KEY, "response_type": "code", "redirect_uri": TP_REDIRECT_URI, "scope": "file:write", } url = "https://oauth.trainingpeaks.com/oauth/authorize?" +urllib.urlencode(params) return HttpResponseRedirect(url) def getidfromresponse(response): t = json.loads(response.text) links = t["_links"] id = links["self"][0]["id"] return int(id) def createtpworkoutdata(w): filename = w.csvfilename row = rowingdata(filename) tcxfilename = filename[:-4]+'.tcx' try: newnotes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com' except TypeError: newnotes = 'from '+w.workoutsource+' via rowsandall.com' row.exporttotcx(tcxfilename,notes=newnotes) return tcxfilename def tp_check(access_token): headers = { "Content-Type": "application/json", 'Accept': 'application/json', 'authorization': 'Bearer %s' % access_token } resp = requests.post(tpapilocation+"/v1/info/version", headers=headers) return resp def uploadactivity(access_token,filename,description='', name='Rowsandall.com workout'): data_gz = BytesIO() with file(filename,'rb') as inF: s = inF.read() with gzip.GzipFile(fileobj=data_gz,mode="w") as gzf: gzf.write(s) headers = { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': 'Bearer %s' % access_token } data = { "UploadClient": "rowsandall", "Filename": filename, "SetWorkoutPublic": True, "Title":name, "Type": "rowing", "Comment": description, "Data": base64.b64encode(data_gz.getvalue()).decode("ascii") } resp = requests.post(tpapilocation+"/v1/file", data = json.dumps(data), headers=headers) print resp.status_code if resp.status_code != 200: if settings.DEBUG: print resp.status_code print resp.reason print "" print headers print "" with open("media/tperrors.log","a") as errorlog: errorlog.write(str(resp.status_code)) errorlog.write("\r\n") timestr = strftime("%Y%m%d-%H%M%S") errorlog.write(timestr+"\r\n") errorlog.write("\r\n") errorlog.write(str(resp.reason)) errorlog.write("\r\n") try: errorlog.write(str(resp.json())) except: pass errorlog.write("\r\n") return 0,resp.reason,resp.status_code,headers else: return resp.json()[0]["Id"],"ok",200,"" return 0 def workout_tp_upload(user,w): message = "Uploading to TrainingPeaks" tpid = 0 r = w.user thetoken = tp_open(r.user) if (checkworkoutuser(user,w)): tcxfile = createtpworkoutdata(w) if tcxfile: res,reason,status_code,headers = uploadactivity( thetoken,tcxfile, name=w.name ) if res == 0: message = "Upload to TrainingPeaks failed with status code "+str(status_code)+": "+reason w.tpid = -1 try: os.remove(tcxfile) except WindowsError: pass return message,tpid else: # res != 0 w.uploadedtotp = res tpid = res w.save() os.remove(tcxfile) return 'Successfully synchronized to TrainingPeaks',tpid else: # no tcxfile message = "Upload to TrainingPeaks failed" w.uploadedtotp = -1 tpid = -1 w.save() return message,tpid else: # not allowed to upload message = "You are not allowed to export this workout to TP" tpid = 0 return message,tpid return message,tpid