Private
Public Access
1
0

Merge branch 'release/v15.9.0'

This commit is contained in:
Sander Roosendaal
2021-03-18 11:26:54 +01:00
9 changed files with 133 additions and 18 deletions

View File

@@ -12,17 +12,35 @@ from iso8601 import ParseError
import pandas as pd import pandas as pd
import numpy import numpy
import json import json
from json.decoder import JSONDecodeError from json.decoder import JSONDecodeError
from uuid import uuid4 from uuid import uuid4
import logging
from rowsandall_app.settings import ( from rowsandall_app.settings import (
GARMIN_CLIENT_KEY, GARMIN_REDIRECT_URI, GARMIN_CLIENT_SECRET GARMIN_CLIENT_KEY, GARMIN_REDIRECT_URI, GARMIN_CLIENT_SECRET
) )
from pytz import timezone as tz, utc from pytz import timezone as tz, utc
try:
import http.client as http_client
except ImportError:
# Python 2
import httplib as http_client
http_client.HTTPConnection.debuglevel = 1
# You must initialize logging, otherwise you'll not see debug output.
logging.basicConfig()
logging.getLogger().setLevel(logging.DEBUG)
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True
from rowers.tasks import handle_get_garmin_file from rowers.tasks import handle_get_garmin_file
import django_rq import django_rq
queue = django_rq.get_queue('default') queue = django_rq.get_queue('default')
@@ -267,10 +285,30 @@ def ps_to_garmin(ps,r):
signature_method='HMAC-SHA1' signature_method='HMAC-SHA1'
) )
url = 'https://apis.garmin.com/training-api/workout/' url = 'https://apis.garmin.com/training-api/workout/'
garminauth = OAuth1(
client_key=oauth_data['client_id'],
client_secret=oauth_data['client_secret'],
resource_owner_key=r.garmintoken,
resource_owner_secret=r.garminrefreshtoken,
signature_method='HMAC-SHA1'
)
response = garmin.post(url,data=payload) response = garmin.post(url,data=payload)
#POST /training-api/workout?undefined HTTP/1.1
#Authorization: OAuth oauth_nonce="3347376452", oauth_signature="jM8%2BCsflDfmB6SGYFIEFa%2BKRBOU%3D", oauth_token="673806b7-aa7b-4064-8290-2dd1b0236ae6", oauth_consumer_key="ca29ba5e-6868-4468-987d-4ee60a1f04bf", oauth_timestamp="1616050850", oauth_signature_method="HMAC-SHA1", oauth_version="1.0"
#Host: apis.garmin.com
#Accept: */*
#curl -v --header 'Authorization: OAuth oauth_nonce="3347376452", oauth_signature="jM8%2BCsflDfmB6SGYFIEFa%2BKRBOU%3D", oauth_token="673806b7-aa7b-4064-8290-2dd1b0236ae6", oauth_consumer_key="ca29ba5e-6868-4468-987d-4ee60a1f04bf", oauth_timestamp="1616050850", oauth_signature_method="HMAC-SHA1", oauth_version="1.0"' 'https://apis.garmin.com/training-api/workout'
#Authorization: OAuth oauth_nonce="3347376452", oauth_signature="jM8%2BCsflDfmB6SGYFIEFa%2BKRBOU%3D", oauth_token="673806b7-aa7b-4064-8290-2dd1b0236ae6", oauth_consumer_key="ca29ba5e-6868-4468-987d-4ee60a1f04bf", oauth_timestamp="1616050850", oauth_signature_method="HMAC-SHA1", oauth_version="1.0"
return response return response

View File

@@ -31,6 +31,7 @@ import rowers.uploads as uploads
from rowers.mailprocessing import make_new_workout_from_email, send_confirm from rowers.mailprocessing import make_new_workout_from_email, send_confirm
import rowers.polarstuff as polarstuff import rowers.polarstuff as polarstuff
import rowers.c2stuff as c2stuff import rowers.c2stuff as c2stuff
import rowers.rp3stuff as rp3stuff
import rowers.stravastuff as stravastuff import rowers.stravastuff as stravastuff
from rowers.opaque import encoder from rowers.opaque import encoder
@@ -207,6 +208,10 @@ class Command(BaseCommand):
if user_is_not_basic(r.user): if user_is_not_basic(r.user):
c2stuff.get_c2_workouts(r) c2stuff.get_c2_workouts(r)
rowers = Rower.objects.filter(rp3_auto_import=True)
for r in rowers:
if user_is_not_basic(r.user):
res = rp3stuff.get_rp3_workouts(r)
messages = Message.objects.filter(mailbox_id = workoutmailbox.id) messages = Message.objects.filter(mailbox_id = workoutmailbox.id)
message_ids = [m.id for m in messages] message_ids = [m.id for m in messages]

View File

@@ -995,6 +995,8 @@ class Rower(models.Model):
rp3refreshtoken = models.TextField(default='',max_length=1000, rp3refreshtoken = models.TextField(default='',max_length=1000,
blank=True,null=True) blank=True,null=True)
rp3_auto_import = models.BooleanField(default=False)
trainingpeaks_auto_export = models.BooleanField(default=False) trainingpeaks_auto_export = models.BooleanField(default=False)
polartoken = models.CharField(default='',max_length=1000,blank=True,null=True) polartoken = models.CharField(default='',max_length=1000,blank=True,null=True)
@@ -3836,6 +3838,7 @@ class RowerExportForm(ModelForm):
'strava_auto_import', 'strava_auto_import',
'strava_auto_delete', 'strava_auto_delete',
'trainingpeaks_auto_export', 'trainingpeaks_auto_export',
'rp3_auto_import'
] ]
# Simple form to set rower's Functional Threshold Power # Simple form to set rower's Functional Threshold Power

View File

@@ -12,16 +12,24 @@ import gzip
import base64 import base64
from io import BytesIO from io import BytesIO
import django_rq
queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('high')
from rowers.utils import myqueue
from rowsandall_app.settings import ( from rowsandall_app.settings import (
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET, C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET, STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET,
RP3_CLIENT_ID, RP3_CLIENT_SECRET, RP3_CLIENT_ID, RP3_CLIENT_SECRET,
RP3_REDIRECT_URI,RP3_CLIENT_KEY, RP3_REDIRECT_URI,RP3_CLIENT_KEY,
RP3_CLIENT_ID, RP3_CLIENT_KEY, RP3_REDIRECT_URI, RP3_CLIENT_SECRET, RP3_CLIENT_ID, RP3_CLIENT_KEY, RP3_REDIRECT_URI, RP3_CLIENT_SECRET,
UPLOAD_SERVICE_URL, UPLOAD_SERVICE_SECRET UPLOAD_SERVICE_URL, UPLOAD_SERVICE_SECRET
) )
from rowers.tasks import handle_rp3_async_workout
from celery import Celery,app from celery import Celery,app
from django_rq import job from django_rq import job
@@ -60,14 +68,14 @@ def do_refresh_token(refreshtoken):
def get_token(code): def get_token(code):
client_auth = requests.auth.HTTPBasicAuth(RP3_CLIENT_KEY, RP3_CLIENT_SECRET) client_auth = requests.auth.HTTPBasicAuth(RP3_CLIENT_KEY, RP3_CLIENT_SECRET)
post_data = { post_data = {
"client_id":RP3_CLIENT_KEY, "client_id":RP3_CLIENT_KEY,
"grant_type": "authorization_code", "grant_type": "authorization_code",
"code": code, "code": code,
"redirect_uri":RP3_REDIRECT_URI, "redirect_uri":RP3_REDIRECT_URI,
"client_secret": RP3_CLIENT_SECRET, "client_secret": RP3_CLIENT_SECRET,
} }
headers = { headers = {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
} }
response = requests.post( response = requests.post(
@@ -107,13 +115,49 @@ def get_rp3_workout_list(user):
} }
}""" }"""
response = requests.post( response = requests.post(
url=graphql_url, url=graphql_url,
headers=headers, headers=headers,
json={'query': get_workouts_list} json={'query': get_workouts_list}
) )
return response return response
def get_rp3_workouts(rower,do_async=True):
try:
auth_token = rp3_open(rower.user)
except NoTokenError:
return 0
res = get_rp3_workout_list(rower.user)
if (res.status_code != 200):
return 0
workouts_list = pd.json_normalize(res.json()['data']['workouts'])
rp3ids = workouts_list['id'].values
workouts_list.set_index('id',inplace=True)
knownrp3ids = uniqify([
w.uploadedtorp3 for w in Workout.objects.filter(user=rower)
])
newids = [rp3id for rp3id in rp3ids if not rp3id in knownrp3ids]
for id in newids:
startdatetime = workouts_list.loc[id,'executed_at']
job = myqueue(
queuehigh,
handle_rp3_async_workout,
rower.user.id,
auth_token,
id,
startdatetime,
10,
)
return 1
def download_rp3_file(url,auth_token,filename): def download_rp3_file(url,auth_token,filename):
headers = {'Authorization': 'Bearer ' + auth_token } headers = {'Authorization': 'Bearer ' + auth_token }
@@ -129,11 +173,11 @@ def get_rp3_workout_token(workout_id,auth_token,waittime=3,max_attempts=20):
headers = {'Authorization': 'Bearer ' + auth_token } headers = {'Authorization': 'Bearer ' + auth_token }
get_download_link = """{ get_download_link = """{
download(workout_id: """ + str(workout_id) + """, type:csv){ download(workout_id: """ + str(workout_id) + """, type:csv){
id id
status status
link link
} }
}""" }"""
have_link = False have_link = False
@@ -144,7 +188,7 @@ def get_rp3_workout_token(workout_id,auth_token,waittime=3,max_attempts=20):
url=graphql_url, url=graphql_url,
headers=headers, headers=headers,
json={'query': get_download_link} json={'query': get_download_link}
) )
if response.status_code != 200: if response.status_code != 200:
have_link = True have_link = True

View File

@@ -2872,7 +2872,7 @@ def add2(x, y,debug=False,**kwargs):
graphql_url = "https://rp3rowing-app.com/graphql" graphql_url = "https://rp3rowing-app.com/graphql"
@app.task @app.task
def handle_rp3_async_workout(userid,rp3token,rp3id,startdatetime,debug=False,**kwargs): def handle_rp3_async_workout(userid,rp3token,rp3id,startdatetime,max_attempts,debug=False,**kwargs):
headers = {'Authorization': 'Bearer ' + rp3token } headers = {'Authorization': 'Bearer ' + rp3token }
get_download_link = """{ get_download_link = """{
@@ -2886,7 +2886,7 @@ def handle_rp3_async_workout(userid,rp3token,rp3id,startdatetime,debug=False,**k
have_link = False have_link = False
download_url = '' download_url = ''
counter = 0 counter = 0
max_attempts = 20 #max_attempts = 20
waittime = 3 waittime = 3
while not have_link: while not have_link:
response = requests.post( response = requests.post(
@@ -2895,6 +2895,7 @@ def handle_rp3_async_workout(userid,rp3token,rp3id,startdatetime,debug=False,**k
json={'query': get_download_link} json={'query': get_download_link}
) )
if response.status_code != 200: if response.status_code != 200:
have_link = True have_link = True
@@ -2915,6 +2916,7 @@ def handle_rp3_async_workout(userid,rp3token,rp3id,startdatetime,debug=False,**k
return 0 return 0
filename = 'media/RP3Import_'+str(rp3id)+'.csv' filename = 'media/RP3Import_'+str(rp3id)+'.csv'
res = requests.get(download_url,headers=headers) res = requests.get(download_url,headers=headers)
if not startdatetime: if not startdatetime:
@@ -2933,7 +2935,7 @@ def handle_rp3_async_workout(userid,rp3token,rp3id,startdatetime,debug=False,**k
'file': filename, 'file': filename,
'workouttype':'dynamic', 'workouttype':'dynamic',
'boattype':'1x', 'boattype':'1x',
'rp3id':rp3id, 'rp3id':int(rp3id),
'startdatetime':startdatetime, 'startdatetime':startdatetime,
} }

View File

@@ -7,6 +7,7 @@
{% block main %} {% block main %}
<h1>Available on RP3</h1> <h1>Available on RP3</h1>
{% if workouts %} {% if workouts %}
<p><a href="/rowers/workout/rp3import/all/">Import all New</a></p>
<ul class="main-content"> <ul class="main-content">
<li class="grid_4"> <li class="grid_4">
<table width="70%" class="listtable"> <table width="70%" class="listtable">

View File

@@ -569,6 +569,7 @@ urlpatterns = [
re_path(r'^workout/c2import/all/(?P<page>\d+)/$',views.workout_getc2workout_all,name='workout_getc2workout_all'), re_path(r'^workout/c2import/all/(?P<page>\d+)/$',views.workout_getc2workout_all,name='workout_getc2workout_all'),
re_path(r'^workout/rp3import/(?P<externalid>\d+)/$',views.workout_getrp3importview, re_path(r'^workout/rp3import/(?P<externalid>\d+)/$',views.workout_getrp3importview,
name='workout_getrp3importview'), name='workout_getrp3importview'),
re_path(r'^workout/rp3import/all/$',views.workout_getrp3workout_all,name='workout_getrp3workout_all'),
re_path(r'^workout/(?P<source>\w+.*)import/(?P<externalid>\d+)/$',views.workout_getimportview,name='workout_getimportview'), re_path(r'^workout/(?P<source>\w+.*)import/(?P<externalid>\d+)/$',views.workout_getimportview,name='workout_getimportview'),
re_path(r'^workout/stravaimport/all/$',views.workout_getstravaworkout_all,name='workout_getstravaworkout_all'), re_path(r'^workout/stravaimport/all/$',views.workout_getstravaworkout_all,name='workout_getstravaworkout_all'),
re_path(r'^workout/stravaimport/next/$',views.workout_getstravaworkout_next,name='workout_getstravaworkout_next'), re_path(r'^workout/stravaimport/next/$',views.workout_getstravaworkout_next,name='workout_getstravaworkout_next'),

View File

@@ -1002,7 +1002,7 @@ def workout_rp3import_view(request,userid=0):
if (r.stravatoken == '') or (r.stravatoken is None): if (r.stravatoken == '') or (r.stravatoken is None):
s = "Token doesn't exist. Need to authorize" s = "Token doesn't exist. Need to authorize"
return HttpResponseRedirect("/rowers/me/stravaauthorize/") return HttpResponseRedirect("/rowers/me/stravaauthorize/")
message = "Something went wrong in workout_stravaimport_view" message = "Something went wrong in workout_rp3import_view"
messages.error(request,message) messages.error(request,message)
url = reverse('workouts_view') url = reverse('workouts_view')
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@@ -1700,6 +1700,25 @@ def workout_getc2workout_all(request,page=1,message=""):
url = reverse('workouts_view') url = reverse('workouts_view')
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@login_required()
def workout_getrp3workout_all(request):
try:
thetoken = rp3_open(request.user)
except NoTokenError:
return HttpResponseRedirect("/rowers/me/rp3authorize/")
r = getrequestrower(request)
result = rp3stuff.get_rp3_workouts(r,do_async=True)
if result:
messages.info(request,'Your RP3 workouts will be imported in the coming few minutes')
else:
messages.error(request,'Your RP3 workouts import failed')
url = reverse('workouts_view')
return HttpResponseRedirect(url)
# List of workouts available on Concept2 logbook - for import # List of workouts available on Concept2 logbook - for import
@login_required() @login_required()
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True) @permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
@@ -1807,6 +1826,7 @@ def workout_getrp3importview(request,externalid):
token, token,
externalid, externalid,
startdatetime, startdatetime,
20,
) )
#id = rp3stuff.get_rp3_workout(r.user,externalid,startdatetime=startdatetime) #id = rp3stuff.get_rp3_workout(r.user,externalid,startdatetime=startdatetime)

View File

@@ -154,6 +154,7 @@ import datetime
import iso8601 import iso8601
import rowers.c2stuff as c2stuff import rowers.c2stuff as c2stuff
from rowers.c2stuff import c2_open from rowers.c2stuff import c2_open
from rowers.rp3stuff import rp3_open
from rowers.runkeeperstuff import runkeeper_open from rowers.runkeeperstuff import runkeeper_open
from rowers.sporttracksstuff import sporttracks_open from rowers.sporttracksstuff import sporttracks_open
from rowers.tpstuff import tp_open from rowers.tpstuff import tp_open