Private
Public Access
1
0

added auto export for all platforms

This commit is contained in:
Sander Roosendaal
2018-06-06 10:36:30 +02:00
parent a277f71af3
commit 63d52eb42a
8 changed files with 179 additions and 28 deletions

View File

@@ -91,6 +91,9 @@ def make_new_workout_from_email(rower, datafile, name, cntr=0,testing=False):
path='media/')[6:] path='media/')[6:]
fileformat = fileformat[2] fileformat = fileformat[2]
if testing:
print 'Fileformat = ',fileformat
if fileformat == 'unknown': if fileformat == 'unknown':
# extension = datafilename[-4:].lower() # extension = datafilename[-4:].lower()
# fcopy = "media/"+datafilename[:-4]+"_copy"+extension # fcopy = "media/"+datafilename[:-4]+"_copy"+extension

View File

@@ -20,6 +20,7 @@ from rowingdata import rowingdata as rrdata
import rowers.uploads as uploads 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
workoutmailbox = Mailbox.objects.get(name='workouts') workoutmailbox = Mailbox.objects.get(name='workouts')
failedmailbox = Mailbox.objects.get(name='Failed') failedmailbox = Mailbox.objects.get(name='Failed')
@@ -147,6 +148,9 @@ class Command(BaseCommand):
"""Run the Email processing command """ """Run the Email processing command """
def handle(self, *args, **options): def handle(self, *args, **options):
polar_available = polarstuff.get_polar_notifications()
res = polarstuff.get_all_new_workouts(polar_available)
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]
attachments = MessageAttachment.objects.filter( attachments = MessageAttachment.objects.filter(

View File

@@ -642,24 +642,29 @@ class Rower(models.Model):
c2token = models.CharField(default='',max_length=200,blank=True,null=True) c2token = models.CharField(default='',max_length=200,blank=True,null=True)
tokenexpirydate = models.DateTimeField(blank=True,null=True) tokenexpirydate = models.DateTimeField(blank=True,null=True)
c2refreshtoken = models.CharField(default='',max_length=200,blank=True,null=True) c2refreshtoken = models.CharField(default='',max_length=200,blank=True,null=True)
c2_auto_export = models.BooleanField(default=False)
sporttrackstoken = models.CharField(default='',max_length=200,blank=True,null=True) sporttrackstoken = models.CharField(default='',max_length=200,blank=True,null=True)
sporttrackstokenexpirydate = models.DateTimeField(blank=True,null=True) sporttrackstokenexpirydate = models.DateTimeField(blank=True,null=True)
sporttracksrefreshtoken = models.CharField(default='',max_length=200, sporttracksrefreshtoken = models.CharField(default='',max_length=200,
blank=True,null=True) blank=True,null=True)
sporttracks_auto_export = models.BooleanField(default=False)
underarmourtoken = models.CharField(default='',max_length=200,blank=True,null=True) underarmourtoken = models.CharField(default='',max_length=200,blank=True,null=True)
underarmourtokenexpirydate = models.DateTimeField(blank=True,null=True) underarmourtokenexpirydate = models.DateTimeField(blank=True,null=True)
underarmourrefreshtoken = models.CharField(default='',max_length=200, underarmourrefreshtoken = models.CharField(default='',max_length=200,
blank=True,null=True) blank=True,null=True)
mapmyfitness_auto_export = models.BooleanField(default=False)
tptoken = models.CharField(default='',max_length=1000,blank=True,null=True) tptoken = models.CharField(default='',max_length=1000,blank=True,null=True)
tptokenexpirydate = models.DateTimeField(blank=True,null=True) tptokenexpirydate = models.DateTimeField(blank=True,null=True)
tprefreshtoken = models.CharField(default='',max_length=1000, tprefreshtoken = models.CharField(default='',max_length=1000,
blank=True,null=True) blank=True,null=True)
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)
polartokenexpirydate = models.DateTimeField(blank=True,null=True) polartokenexpirydate = models.DateTimeField(blank=True,null=True)
polarrefreshtoken = models.CharField(default='',max_length=1000, polarrefreshtoken = models.CharField(default='',max_length=1000,
blank=True,null=True) blank=True,null=True)
polaruserid = models.IntegerField(default=0) polaruserid = models.IntegerField(default=0)
polar_auto_import = models.BooleanField(default=False)
stravatoken = models.CharField(default='',max_length=200,blank=True,null=True) stravatoken = models.CharField(default='',max_length=200,blank=True,null=True)
stravaexportas = models.CharField(default="Rowing", stravaexportas = models.CharField(default="Rowing",
@@ -667,8 +672,10 @@ class Rower(models.Model):
choices=stravatypes, choices=stravatypes,
verbose_name="Export Workouts to Strava as") verbose_name="Export Workouts to Strava as")
strava_auto_export = models.BooleanField(default=False)
runkeepertoken = models.CharField(default='',max_length=200, runkeepertoken = models.CharField(default='',max_length=200,
blank=True,null=True) blank=True,null=True)
runkeeper_auto_export = models.BooleanField(default=False)
# Plan # Plan
plans = ( plans = (
@@ -1996,6 +2003,22 @@ class RowerPowerZonesForm(ModelForm):
return cleaned_data return cleaned_data
# Form to set rower's Auto Import and Export settings
class RowerImportExportForm(ModelForm):
class Meta:
model = Rower
fields = [
'polar_auto_import',
'c2_auto_export',
'mapmyfitness_auto_export',
'runkeeper_auto_export',
'sporttracks_auto_export',
'strava_auto_export',
'trainingpeaks_auto_export',
]
# Form to set rower's Email and Weight category # Form to set rower's Email and Weight category
class AccountRowerForm(ModelForm): class AccountRowerForm(ModelForm):
class Meta: class Meta:

View File

@@ -17,6 +17,7 @@ import os,sys
import gzip import gzip
import base64 import base64
import yaml import yaml
from uuid import uuid4
# Django # Django
from django.shortcuts import render_to_response from django.shortcuts import render_to_response
@@ -46,7 +47,8 @@ from rowsandall_app.settings import (
POLAR_CLIENT_ID, POLAR_REDIRECT_URI, POLAR_CLIENT_SECRET, POLAR_CLIENT_ID, POLAR_REDIRECT_URI, POLAR_CLIENT_SECRET,
) )
baseurl = 'https://polaraccesslink.com/v3-example' #baseurl = 'https://polaraccesslink.com/v3-example'
baseurl = 'https://polaraccesslink.com/v3'
# Custom exception handler, returns a 401 HTTP message # Custom exception handler, returns a 401 HTTP message
# with exception details in the json data # with exception details in the json data
@@ -111,7 +113,6 @@ def get_token(code):
def make_authorization_url(): def make_authorization_url():
# Generate a random string for the state parameter # Generate a random string for the state parameter
# Save it for use later to prevent xsrf attacks # Save it for use later to prevent xsrf attacks
from uuid import uuid4
state = str(uuid4()) state = str(uuid4())
params = {"client_id": POLAR_CLIENT_ID, params = {"client_id": POLAR_CLIENT_ID,
@@ -124,6 +125,43 @@ def make_authorization_url():
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
def get_polar_notifications():
url = baseurl+'/notifications'
state = str(uuid4())
auth_string = '{id}:{secret}'.format(
id= POLAR_CLIENT_ID,
secret=POLAR_CLIENT_SECRET
)
headers = { 'Authorization': 'Basic %s' % base64.b64encode(auth_string) }
response = requests.get(url, headers=headers)
available_data = []
if response.status_code == 200:
available_data = response.json()['available-user-data']
return available_data
def get_all_new_workouts(available_data,testing=False):
for record in available_data:
if testing:
print record
if record['data-type'] == 'EXERCISE':
try:
r = Rower.objects.get(polaruserid=record['user-id'])
u = r.user
if r.polar_auto_import:
exercise_list = get_polar_workouts(u)
if testing:
print exercise_list
except Rower.DoesNotExist:
pass
return 1
def get_polar_workouts(user): def get_polar_workouts(user):
r = Rower.objects.get(user=user) r = Rower.objects.get(user=user)
@@ -180,8 +218,9 @@ def get_polar_workouts(user):
tcxuri = exerciseurl+'/tcx' tcxuri = exerciseurl+'/tcx'
response = requests.get(tcxuri,headers=headers2) response = requests.get(tcxuri,headers=headers2)
if response.status_code == 200: if response.status_code == 200:
filename = 'media/mailbox_attachments/polarimport{id}.tcx'.format( filename = 'media/mailbox_attachments/{code}_{id}.tcx'.format(
id = exercise_dict['id'] id = exercise_dict['id'],
code = uuid4().hex[:16]
) )
with open(filename,'wb') as fop: with open(filename,'wb') as fop:

View File

@@ -53,6 +53,16 @@
<p>Import workouts from MapMyFitness/UnderArmour</p> <p>Import workouts from MapMyFitness/UnderArmour</p>
</div> </div>
</div> </div>
<div class="grid_6">
<div class="grid_3 alpha">
<p>
<a href="/rowers/workout/polarimport"><img src="/static/img/Polar_connectwith_btn_white.png" alt="Polar logo" width="140"></a>
</p>
</div>
<div class="grid_3 omega">
<p>Import workouts from Polar Flow. </p><p><b>Note: No workout selection possible. Automatically imports all new workouts</b></p>
</div>
</div>
</div> </div>
<div class="grid_6 omega"> <div class="grid_6 omega">
@@ -60,7 +70,8 @@
<div class="grid_6 alpha"> <div class="grid_6 alpha">
<p>Click one of the below logos to connect to the service of your choice. <p>Click one of the below logos to connect to the service of your choice.
You only need to do this once. After that, the site will have access until you You only need to do this once. After that, the site will have
access until you
revoke the authorization for the "rowingdata" app.</p> revoke the authorization for the "rowingdata" app.</p>
<div class="grid_2 alpha"> <div class="grid_2 alpha">
@@ -89,6 +100,31 @@
alt="connect with Polar" width="130"></a></p> alt="connect with Polar" width="130"></a></p>
</div> </div>
</div> </div>
<h2>Auto Import/Export Settings</h2>
<p>Use the form below to set your auto import/export settings.
As we implement auto import/export for various partner sites, this form will be expanded.</p>
<p>Auto Import = rowsandall.com will regularly poll the partner site for new
workouts and automatically import new workouts
</p>
<p>Auto Export = New workouts uploaded to rowsandall.com will be automatically synchronized
to the partner site</p>
<div class="grid_6 alpha">
<form id="importexportform" method="post">
<table width=70%>
{{ form.as_table }}
</table>
{% csrf_token %}
<div id="formbutton" class="grid_2 prefix_2 alpha">
<input class="button green grid_2" type="submit" value="Save">
</div>
</form>
</div>
</div> </div>

View File

@@ -374,14 +374,14 @@ def make_private(w,options):
return 1 return 1
def do_sync(w,options): def do_sync(w,options):
if 'upload_to_C2' in options and options['upload_to_C2']: if ('upload_to_C2' in options and options['upload_to_C2']) or w.user.c2_auto_export:
try: try:
message,id = c2stuff.workout_c2_upload(w.user.user,w) message,id = c2stuff.workout_c2_upload(w.user.user,w)
except c2stuff.C2NoTokenError: except c2stuff.C2NoTokenError:
id = 0 id = 0
message = "Something went wrong with the Concept2 sync" message = "Something went wrong with the Concept2 sync"
if 'upload_to_Strava' in options and options['upload_to_Strava']: if ('upload_to_Strava' in options and options['upload_to_Strava']) or w.user.strava_auto_export:
try: try:
message,id = stravastuff.workout_strava_upload( message,id = stravastuff.workout_strava_upload(
w.user.user,w w.user.user,w
@@ -390,8 +390,8 @@ def do_sync(w,options):
id = 0 id = 0
message = "Please connect to Strava first" message = "Please connect to Strava first"
if 'upload_to_SportTracks' in options and options['upload_to_SportTracks']: if ('upload_to_SportTracks' in options and options['upload_to_SportTracks']) or w.user.sporttracks_auto_export:
try: try:
message,id = sporttracksstuff.workout_sporttracks_upload( message,id = sporttracksstuff.workout_sporttracks_upload(
w.user.user,w w.user.user,w
@@ -401,7 +401,7 @@ def do_sync(w,options):
id = 0 id = 0
if 'upload_to_RunKeeper' in options and options['upload_to_RunKeeper']: if ('upload_to_RunKeeper' in options and options['upload_to_RunKeeper']) or w.user.runkeeper_auto_export:
try: try:
message,id = runkeeperstuff.workout_runkeeper_upload( message,id = runkeeperstuff.workout_runkeeper_upload(
w.user.user,w w.user.user,w
@@ -410,7 +410,7 @@ def do_sync(w,options):
message = "Please connect to Runkeeper first" message = "Please connect to Runkeeper first"
id = 0 id = 0
if 'upload_to_MapMyFitness' in options and options['upload_to_MapMyFitness']: if ('upload_to_MapMyFitness' in options and options['upload_to_MapMyFitness']) or w.user.mapmyfitness_auto_export:
try: try:
message,id = underarmourstuff.workout_ua_upload( message,id = underarmourstuff.workout_ua_upload(
w.user.user,w w.user.user,w
@@ -420,7 +420,7 @@ def do_sync(w,options):
id = 0 id = 0
if 'upload_to_TrainingPeaks' in options and options['upload_to_TrainingPeaks']: if ('upload_to_TrainingPeaks' in options and options['upload_to_TrainingPeaks']) or w.user.trainingpeaks_auto_export:
try: try:
message,id = tpstuff.workout_tp_upload( message,id = tpstuff.workout_tp_upload(
w.user.user,w w.user.user,w

View File

@@ -120,7 +120,8 @@ urlpatterns = [
url(r'^404/$', TemplateView.as_view(template_name='404.html'),name='404'), url(r'^404/$', TemplateView.as_view(template_name='404.html'),name='404'),
url(r'^400/$', TemplateView.as_view(template_name='400.html'),name='400'), url(r'^400/$', TemplateView.as_view(template_name='400.html'),name='400'),
url(r'^403/$', TemplateView.as_view(template_name='403.html'),name='403'), url(r'^403/$', TemplateView.as_view(template_name='403.html'),name='403'),
url(r'^imports/$', TemplateView.as_view(template_name='imports.html'), name='imports'), # url(r'^imports/$', TemplateView.as_view(template_name='imports.html'), name='imports'),
url(r'^imports/$', views.imports_view),
url(r'^exportallworkouts/?$',views.workouts_summaries_email_view), url(r'^exportallworkouts/?$',views.workouts_summaries_email_view),
url(r'^update_empower$',views.rower_update_empower_view), url(r'^update_empower$',views.rower_update_empower_view),
url(r'^agegroupcp/(?P<age>\d+)$',views.agegroupcpview), url(r'^agegroupcp/(?P<age>\d+)$',views.agegroupcpview),

View File

@@ -72,7 +72,7 @@ from rowers.models import (
WorkoutComment,WorkoutCommentForm,RowerExportForm, WorkoutComment,WorkoutCommentForm,RowerExportForm,
CalcAgePerformance,PowerTimeFitnessMetric,PlannedSessionForm, CalcAgePerformance,PowerTimeFitnessMetric,PlannedSessionForm,
PlannedSessionFormSmall,GeoCourseEditForm,VirtualRace, PlannedSessionFormSmall,GeoCourseEditForm,VirtualRace,
VirtualRaceForm,VirtualRaceResultForm, VirtualRaceForm,VirtualRaceResultForm,RowerImportExportForm
) )
from rowers.models import ( from rowers.models import (
FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement,BasePlannedSessionFormSet FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement,BasePlannedSessionFormSet
@@ -1234,7 +1234,10 @@ def add_workout_from_strokedata(user,importid,data,strokedata,
# Create CSV file name and save data to CSV file # Create CSV file name and save data to CSV file
csvfilename ='media/Import_'+str(importid)+'.csv' csvfilename ='media/{code}_{importid}.csv'.format(
importid=importid,
code = uuid4().hex[:16]
)
res = df.to_csv(csvfilename+'.gz',index_label='index', res = df.to_csv(csvfilename+'.gz',index_label='index',
compression='gzip') compression='gzip')
@@ -1424,7 +1427,11 @@ def add_workout_from_runkeeperdata(user,importid,data):
timestr = strftime("%Y%m%d-%H%M%S") timestr = strftime("%Y%m%d-%H%M%S")
csvfilename ='media/Import_'+str(importid)+'.csv' # csvfilename ='media/Import_'+str(importid)+'.csv'
csvfilename ='media/{code}_{importid}.csv'.format(
importid=importid,
code = uuid4().hex[:16]
)
res = df.to_csv(csvfilename+'.gz',index_label='index', res = df.to_csv(csvfilename+'.gz',index_label='index',
compression='gzip') compression='gzip')
@@ -1590,7 +1597,11 @@ def add_workout_from_stdata(user,importid,data):
timestr = strftime("%Y%m%d-%H%M%S") timestr = strftime("%Y%m%d-%H%M%S")
csvfilename ='media/Import_'+str(importid)+'.csv' # csvfilename ='media/Import_'+str(importid)+'.csv'
csvfilename ='media/{code}_{importid}.csv'.format(
importid=importid,
code = uuid4().hex[:16]
)
res = df.to_csv(csvfilename+'.gz',index_label='index', res = df.to_csv(csvfilename+'.gz',index_label='index',
compression='gzip') compression='gzip')
@@ -1757,7 +1768,11 @@ def add_workout_from_underarmourdata(user,importid,data):
timestr = strftime("%Y%m%d-%H%M%S") timestr = strftime("%Y%m%d-%H%M%S")
csvfilename ='media/Import_'+str(importid)+'.csv' # csvfilename ='media/Import_'+str(importid)+'.csv'
csvfilename ='media/{code}_{importid}.csv'.format(
importid=importid,
code = uuid4().hex[:16]
)
res = df.to_csv(csvfilename+'.gz',index_label='index', res = df.to_csv(csvfilename+'.gz',index_label='index',
compression='gzip') compression='gzip')
@@ -2612,12 +2627,31 @@ def rower_process_callback(request):
# The imports page # The imports page
@login_required() @login_required()
def imports_view(request,successmessage="",message=""): def imports_view(request):
messages.info(request,successmessage)
messages.error(request,message) r = getrower(request.user)
if request.method == 'POST':
form = RowerImportExportForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
for attr, value in cd.items():
setattr(r,attr,value)
r.save()
# polar_auto_import = cd['polar_auto_import']
# r.polar_auto_import = polar_auto_import
# r.save()
else:
form = RowerImportExportForm(instance=r)
return render(request,"imports.html", return render(request,"imports.html",
{ {
'teams':get_my_teams(request.user), 'teams':get_my_teams(request.user),
'form':form,
}) })
# Just for testing purposes # Just for testing purposes
@@ -9650,12 +9684,22 @@ def workout_underarmourimport_view(request,message=""):
def workout_polarimport_view(request): def workout_polarimport_view(request):
exercises = polarstuff.get_polar_workouts(request.user) exercises = polarstuff.get_polar_workouts(request.user)
workouts = [] workouts = []
try:
a = exercises.status_code
if a == 401:
messages.error(request,'Not authorized. You need to connect to Polar first')
url = reverse(imports_view)
return HttpResponseRedirect(url)
except:
pass
for exercise in exercises: for exercise in exercises:
try: try:
d = exercise['distance'] d = exercise['distance']
except KeyError: except KeyError:
d = 0 d = 0
i = exercise['id'] i = exercise['id']
transactionid = exercise['transaction-id'] transactionid = exercise['transaction-id']
starttime = exercise['start-time'] starttime = exercise['start-time']
@@ -9667,6 +9711,7 @@ def workout_polarimport_view(request):
res = dict(zip(keys,values)) res = dict(zip(keys,values))
workouts.append(res) workouts.append(res)
return render(request, 'polar_list_import.html', return render(request, 'polar_list_import.html',
{ {
'workouts':workouts, 'workouts':workouts,
@@ -10428,7 +10473,7 @@ def workout_upload_view(request,
request.session['async_tasks'] = [(jobid,'make_plot')] request.session['async_tasks'] = [(jobid,'make_plot')]
# upload to C2 # upload to C2
if (upload_to_c2): if (upload_to_c2) or (w.user.c2_auto_export):
try: try:
message,id = c2stuff.workout_c2_upload(request.user,w) message,id = c2stuff.workout_c2_upload(request.user,w)
except C2NoTokenError: except C2NoTokenError:
@@ -10439,7 +10484,7 @@ def workout_upload_view(request,
else: else:
messages.error(request,message) messages.error(request,message)
if (upload_to_strava): if (upload_to_strava) or (w.user.strava_auto_export):
try: try:
message,id = stravastuff.workout_strava_upload( message,id = stravastuff.workout_strava_upload(
request.user,w request.user,w
@@ -10452,7 +10497,7 @@ def workout_upload_view(request,
else: else:
messages.error(request,message) messages.error(request,message)
if (upload_to_st): if (upload_to_st) or (w.user.sporttracks_auto_export):
try: try:
message,id = sporttracksstuff.workout_sporttracks_upload( message,id = sporttracksstuff.workout_sporttracks_upload(
request.user,w request.user,w
@@ -10465,7 +10510,7 @@ def workout_upload_view(request,
else: else:
messages.error(request,message) messages.error(request,message)
if (upload_to_rk): if (upload_to_rk) or (w.user.runkeeper_auto_export):
try: try:
message,id = runkeeperstuff.workout_runkeeper_upload( message,id = runkeeperstuff.workout_runkeeper_upload(
request.user,w request.user,w
@@ -10480,7 +10525,7 @@ def workout_upload_view(request,
messages.error(request,message) messages.error(request,message)
if (upload_to_ua): if (upload_to_ua) or (w.user.mapmyfitness_auto_export):
try: try:
message,id = underarmourstuff.workout_ua_upload( message,id = underarmourstuff.workout_ua_upload(
request.user,w request.user,w
@@ -10495,7 +10540,7 @@ def workout_upload_view(request,
messages.error(request,message) messages.error(request,message)
if (upload_to_tp): if (upload_to_tp) or (w.user.trainingpeaks_auto_export):
try: try:
message,id = tpstuff.workout_tp_upload( message,id = tpstuff.workout_tp_upload(
request.user,w request.user,w