diff --git a/rowers/models.py b/rowers/models.py index 945bbd39..28abaf6f 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -655,6 +655,12 @@ class Rower(models.Model): tprefreshtoken = models.CharField(default='',max_length=1000, blank=True,null=True) + polartoken = models.CharField(default='',max_length=1000,blank=True,null=True) + polartokenexpirydate = models.DateTimeField(blank=True,null=True) + polarrefreshtoken = models.CharField(default='',max_length=1000, + blank=True,null=True) + polaruserid = models.IntegerField(default=0) + stravatoken = models.CharField(default='',max_length=200,blank=True,null=True) stravaexportas = models.CharField(default="Rowing", max_length=30, diff --git a/rowers/polarstuff.py b/rowers/polarstuff.py new file mode 100644 index 00000000..65ac068d --- /dev/null +++ b/rowers/polarstuff.py @@ -0,0 +1,168 @@ +# All the functionality needed to connect to Strava + +# Python +import oauth2 as oauth +import cgi +import requests +import requests.auth +import json +from django.utils import timezone +from datetime import datetime +import numpy as np +from dateutil import parser +import time +import math +from math import sin,cos,atan2,sqrt +import os,sys +import gzip +import base64 + +# 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 + +# Project +# from .models import Profile +from rowingdata import rowingdata +import pandas as pd +from rowers.models import Rower,Workout +from rowers.models import checkworkoutuser +import dataprep +from dataprep import columndict + +import stravalib +from stravalib.exc import ActivityUploadFailed,TimeoutExceeded + +from rowsandall_app.settings import ( + POLAR_CLIENT_ID, POLAR_REDIRECT_URI, POLAR_CLIENT_SECRET, + ) + +# 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 + +# Custom error class - to raise a NoTokenError +class PolarNoTokenError(Exception): + def __init__(self,value): + self.value=value + + def __str__(self): + return repr(self.value) + + +# Exchange access code for long-lived access token +def get_token(code): + + post_data = {"grant_type": "authorization_code", + "code": code, + "redirect_uri": POLAR_REDIRECT_URI, + } + + auth_string = '{id}:{secret}'.format( + id= POLAR_CLIENT_ID, + secret=POLAR_CLIENT_SECRET + ) + + headers = { 'Authorization': 'Basic %s' % base64.b64encode(auth_string) } + + response = requests.post("https://polarremote.com/v2/oauth2/token", + data=post_data, + headers=headers) + + + try: + token_json = response.json() + thetoken = token_json['access_token'] + expires_in = token_json['expires_in'] + user_id = token_json['x_user_id'] + except KeyError: + thetoken = 0 + expires_in = 0 + + return [thetoken,expires_in,user_id] + +# 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": POLAR_CLIENT_ID, + "response_type": "code", + "redirect_uri": POLAR_REDIRECT_URI, + "scope":"write"} + import urllib + url = "https://flow.polar.com/oauth2/authorization" +urllib.urlencode(params) + + return HttpResponseRedirect(url) + +def get_polar_workout_list(user): + r = Rower.objects.get(user=user) + if (r.polartoken == '') or (r.polartoken is None): + s = "Token doesn't exist. Need to authorize" + return custom_exception_handler(401,s) + elif (timezone.now()>r.polartokenexpirydate): + s = "Token expired. Needs to refresh" + return custom_exception_handler(401,s) + else: + authorizationstring = str('Bearer ' + r.polartoken) + headers = {'Authorization':authorizationstring, + 'Accept': 'application/json'} + + url = 'https://polaraccesslink.com/v3/users/{userid}/exercise-transactions'.format( + userid = r.polaruserid + ) + + response = requests.post(url, headers=headers) + + return response + +def get_polar_user_info(user): + r = Rower.objects.get(user=user) + if (r.polartoken == '') or (r.polartoken is None): + s = "Token doesn't exist. Need to authorize" + return custom_exception_handler(401,s) + elif (timezone.now()>r.polartokenexpirydate): + s = "Token expired. Needs to refresh" + return custom_exception_handler(401,s) + else: + authorizationstring = str('Bearer ' + r.polartoken) + headers = { + 'Authorization':authorizationstring, + 'Accept': 'application/json' + } + + params = { + 'user-id': r.polaruserid + } + + url = 'https://polaraccesslink.com/v3/users/{userid}'.format( + userid = r.polaruserid + ) + + + print url + response = requests.get(url, headers=headers) + + return response + diff --git a/rowers/templates/email.html b/rowers/templates/email.html index 02f23929..562f8615 100644 --- a/rowers/templates/email.html +++ b/rowers/templates/email.html @@ -92,7 +92,7 @@ When the site is down, this is the appropriate channel to look for apologies, up 602 00 Brno
Czech Republic
IČ: 070 48 572
- DIČ: CZ 070 48 572
+ DIČ: CZ 070 48 572 (Nejsme plátce DPH)
Datová schránka: 7897syr
Email: info@rowsandall.com
The company is registered in the business register at the diff --git a/rowers/templates/imports.html b/rowers/templates/imports.html index d6e5a6af..17f27503 100644 --- a/rowers/templates/imports.html +++ b/rowers/templates/imports.html @@ -1,9 +1,9 @@ {% extends "base.html" %} - {% block title %}Contact Us{% endblock title %} + {% block title %}Import Workouts{% endblock title %} {% block content %}
-

Import Workouts

+

Import Workouts

@@ -56,7 +56,7 @@
-

Connect

+

Connect

Click one of the below logos to connect to the service of your choice. @@ -81,9 +81,13 @@

connect with RunKeeper

-
+

connect with Under Armour

+
+

connect with Polar

+
diff --git a/rowers/urls.py b/rowers/urls.py index de5d498d..5e29e8b5 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -379,6 +379,7 @@ urlpatterns = [ url(r'^rower/edit/(?P\d+)$',views.rower_edit_view), url(r'^me/edit/(.+.*)/$',views.rower_edit_view), url(r'^me/c2authorize/$',views.rower_c2_authorize), + url(r'^me/polarauthorize/$',views.rower_polar_authorize), url(r'^me/revokeapp/(?P\d+)$',views.rower_revokeapp_view), url(r'^me/stravaauthorize/$',views.rower_strava_authorize), url(r'^me/sporttracksauthorize/$',views.rower_sporttracks_authorize), diff --git a/rowers/views.py b/rowers/views.py index 78f6c367..af6e4264 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -21,6 +21,7 @@ from django.views.decorators.csrf import csrf_exempt from matplotlib.backends.backend_agg import FigureCanvas import gc from pyparsing import ParseException +from uuid import uuid4 from django.shortcuts import render from django.http import ( @@ -93,6 +94,8 @@ from sporttracksstuff import SportTracksNoTokenError,sporttracks_open from tpstuff import TPNoTokenError,tp_open from iso8601 import ParseError import stravastuff +import polarstuff +from polarstuff import PolarNoTokenError from stravastuff import StravaNoTokenError import sporttracksstuff import underarmourstuff @@ -104,6 +107,7 @@ from ownapistuff import TEST_CLIENT_ID, TEST_CLIENT_SECRET, TEST_REDIRECT_URI from rowsandall_app.settings import ( C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET, STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET, + POLAR_CLIENT_ID, POLAR_REDIRECT_URI, POLAR_CLIENT_SECRET, SPORTTRACKS_CLIENT_ID, SPORTTRACKS_REDIRECT_URI, SPORTTRACKS_CLIENT_SECRET, UNDERARMOUR_CLIENT_ID, UNDERARMOUR_REDIRECT_URI, @@ -2358,7 +2362,7 @@ def workout_sporttracks_upload_view(request,id=0): def rower_c2_authorize(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()) scope = "user:read,results:write" params = {"client_id": C2_CLIENT_ID, @@ -2373,7 +2377,7 @@ def rower_c2_authorize(request): def rower_strava_authorize(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": STRAVA_CLIENT_ID, @@ -2385,12 +2389,30 @@ def rower_strava_authorize(request): return HttpResponseRedirect(url) +# Polar Authorization +@login_required() +def rower_polar_authorize(request): + + state = str(uuid4()) + + params = {"client_id": POLAR_CLIENT_ID, + "response_type": "code", + "redirect_uri": POLAR_REDIRECT_URI, + "state": state, +# "scope":"accesslink.read_all" + } + url = "https://flow.polar.com/oauth2/authorization?" +urllib.urlencode(params) + + return HttpResponseRedirect(url) + + + # Runkeeper authorization @login_required() def rower_runkeeper_authorize(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": RUNKEEPER_CLIENT_ID, @@ -2408,7 +2430,7 @@ def rower_runkeeper_authorize(request): def rower_sporttracks_authorize(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": SPORTTRACKS_CLIENT_ID, @@ -2426,7 +2448,7 @@ def rower_sporttracks_authorize(request): def rower_underarmour_authorize(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()) redirect_uri = UNDERARMOUR_REDIRECT_URI @@ -2443,7 +2465,7 @@ def rower_underarmour_authorize(request): def rower_tp_authorize(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", @@ -2608,6 +2630,40 @@ def test_reverse_view(request): def rower_process_twittercallback(request): return "dummy" +# Process Polar Callback +@login_required() +def rower_process_polarcallback(request): + try: + code = request.GET['code'] + except MultiValueDictKeyError: + try: + message = request.GET['error'] + except MultiValueDictKeyError: + message = "access error" + + messages.error(request,message) + return imports_view(request) + + res = polarstuff.get_token(code) + + access_token = res[0] + expires_in = res[1] + user_id = res[2] + + expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in) + + r = getrower(request.user) + r.polartoken = access_token + r.polartokenexpirydate = expirydatetime + r.polaruserid = user_id + + r.save() + + successmessage = "Tokens stored. Good to go" + messages.info(request,successmessage) + return imports_view(request) + + # Process Strava Callback @login_required() def rower_process_stravacallback(request): diff --git a/rowsandall_app/settings.py b/rowsandall_app/settings.py index 9bfc9d1e..661ae2ad 100644 --- a/rowsandall_app/settings.py +++ b/rowsandall_app/settings.py @@ -254,6 +254,11 @@ RUNKEEPER_CLIENT_ID = CFG['runkeeper_client_id'] RUNKEEPER_CLIENT_SECRET = CFG['runkeeper_client_secret'] RUNKEEPER_REDIRECT_URI = CFG['runkeeper_callback'] +# Polar Flow + +POLAR_CLIENT_ID = CFG['polarflow_client_id'] +POLAR_CLIENT_SECRET = CFG['polarflow_client_secret'] +POLAR_REDIRECT_URI = CFG['polarflow_callback'] # Under Armour diff --git a/rowsandall_app/urls.py b/rowsandall_app/urls.py index 64e2a3dc..5fdc8511 100644 --- a/rowsandall_app/urls.py +++ b/rowsandall_app/urls.py @@ -71,6 +71,7 @@ urlpatterns += [ url(r'^stravacall\_back',rowersviews.rower_process_stravacallback), url(r'^sporttracks\_callback',rowersviews.rower_process_sporttrackscallback), url(r'^underarmour\_callback',rowersviews.rower_process_underarmourcallback), + url(r'^polarflowcallback',rowersviews.rower_process_polarcallback), url(r'^runkeeper\_callback',rowersviews.rower_process_runkeepercallback), url(r'^tp\_callback',rowersviews.rower_process_tpcallback), url(r'^twitter\_callback',rowersviews.rower_process_twittercallback), diff --git a/static/img/Polar_API_logo_3-horizontal.png b/static/img/Polar_API_logo_3-horizontal.png new file mode 100644 index 00000000..7f6dc054 Binary files /dev/null and b/static/img/Polar_API_logo_3-horizontal.png differ diff --git a/static/img/Polar_connectwith_btn_white.png b/static/img/Polar_connectwith_btn_white.png new file mode 100644 index 00000000..1f63c5a7 Binary files /dev/null and b/static/img/Polar_connectwith_btn_white.png differ