diff --git a/rowers/mailprocessing.py b/rowers/mailprocessing.py index f27ca372..ffedcf99 100644 --- a/rowers/mailprocessing.py +++ b/rowers/mailprocessing.py @@ -91,6 +91,9 @@ def make_new_workout_from_email(rower, datafile, name, cntr=0,testing=False): path='media/')[6:] fileformat = fileformat[2] + if testing: + print 'Fileformat = ',fileformat + if fileformat == 'unknown': # extension = datafilename[-4:].lower() # fcopy = "media/"+datafilename[:-4]+"_copy"+extension diff --git a/rowers/management/commands/processemail.py b/rowers/management/commands/processemail.py index f2a1c65e..c8c3f2f9 100644 --- a/rowers/management/commands/processemail.py +++ b/rowers/management/commands/processemail.py @@ -20,6 +20,7 @@ from rowingdata import rowingdata as rrdata import rowers.uploads as uploads from rowers.mailprocessing import make_new_workout_from_email, send_confirm +import rowers.polarstuff as polarstuff workoutmailbox = Mailbox.objects.get(name='workouts') failedmailbox = Mailbox.objects.get(name='Failed') @@ -147,6 +148,9 @@ class Command(BaseCommand): """Run the Email processing command """ 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) message_ids = [m.id for m in messages] attachments = MessageAttachment.objects.filter( diff --git a/rowers/models.py b/rowers/models.py index 28abaf6f..5649595a 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -642,24 +642,29 @@ class Rower(models.Model): c2token = models.CharField(default='',max_length=200,blank=True,null=True) tokenexpirydate = models.DateTimeField(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) sporttrackstokenexpirydate = models.DateTimeField(blank=True,null=True) sporttracksrefreshtoken = models.CharField(default='',max_length=200, blank=True,null=True) + sporttracks_auto_export = models.BooleanField(default=False) underarmourtoken = models.CharField(default='',max_length=200,blank=True,null=True) underarmourtokenexpirydate = models.DateTimeField(blank=True,null=True) underarmourrefreshtoken = models.CharField(default='',max_length=200, blank=True,null=True) + mapmyfitness_auto_export = models.BooleanField(default=False) tptoken = models.CharField(default='',max_length=1000,blank=True,null=True) tptokenexpirydate = models.DateTimeField(blank=True,null=True) tprefreshtoken = models.CharField(default='',max_length=1000, blank=True,null=True) + trainingpeaks_auto_export = models.BooleanField(default=False) 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) + polar_auto_import = models.BooleanField(default=False) stravatoken = models.CharField(default='',max_length=200,blank=True,null=True) stravaexportas = models.CharField(default="Rowing", @@ -667,8 +672,10 @@ class Rower(models.Model): choices=stravatypes, verbose_name="Export Workouts to Strava as") + strava_auto_export = models.BooleanField(default=False) runkeepertoken = models.CharField(default='',max_length=200, blank=True,null=True) + runkeeper_auto_export = models.BooleanField(default=False) # Plan plans = ( @@ -1996,6 +2003,22 @@ class RowerPowerZonesForm(ModelForm): 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 class AccountRowerForm(ModelForm): class Meta: diff --git a/rowers/polarstuff.py b/rowers/polarstuff.py index 3bad578a..5bf1a535 100644 --- a/rowers/polarstuff.py +++ b/rowers/polarstuff.py @@ -17,6 +17,7 @@ import os,sys import gzip import base64 import yaml +from uuid import uuid4 # Django 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, ) -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 # with exception details in the json data @@ -111,7 +113,6 @@ def get_token(code): def make_authorization_url(): # 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, @@ -124,6 +125,43 @@ def make_authorization_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): r = Rower.objects.get(user=user) @@ -180,8 +218,9 @@ def get_polar_workouts(user): tcxuri = exerciseurl+'/tcx' response = requests.get(tcxuri,headers=headers2) if response.status_code == 200: - filename = 'media/mailbox_attachments/polarimport{id}.tcx'.format( - id = exercise_dict['id'] + filename = 'media/mailbox_attachments/{code}_{id}.tcx'.format( + id = exercise_dict['id'], + code = uuid4().hex[:16] ) with open(filename,'wb') as fop: diff --git a/rowers/templates/imports.html b/rowers/templates/imports.html index 17f27503..857802a2 100644 --- a/rowers/templates/imports.html +++ b/rowers/templates/imports.html @@ -53,6 +53,16 @@

Import workouts from MapMyFitness/UnderArmour

+
+
+

+ Polar logo +

+
+
+

Import workouts from Polar Flow.

Note: No workout selection possible. Automatically imports all new workouts

+
+
@@ -60,7 +70,8 @@

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.

@@ -89,6 +100,31 @@ alt="connect with Polar" width="130">

+ +

Auto Import/Export Settings

+ +

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.

+ +

Auto Import = rowsandall.com will regularly poll the partner site for new + workouts and automatically import new workouts +

+ +

Auto Export = New workouts uploaded to rowsandall.com will be automatically synchronized + to the partner site

+ +
+
+ + {{ form.as_table }} +
+ {% csrf_token %} +
+ +
+
+
+
diff --git a/rowers/uploads.py b/rowers/uploads.py index a5a4fc61..a667e200 100644 --- a/rowers/uploads.py +++ b/rowers/uploads.py @@ -374,14 +374,14 @@ def make_private(w,options): return 1 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: message,id = c2stuff.workout_c2_upload(w.user.user,w) except c2stuff.C2NoTokenError: id = 0 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: message,id = stravastuff.workout_strava_upload( w.user.user,w @@ -390,8 +390,8 @@ def do_sync(w,options): id = 0 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: message,id = sporttracksstuff.workout_sporttracks_upload( w.user.user,w @@ -401,7 +401,7 @@ def do_sync(w,options): 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: message,id = runkeeperstuff.workout_runkeeper_upload( w.user.user,w @@ -410,7 +410,7 @@ def do_sync(w,options): message = "Please connect to Runkeeper first" 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: message,id = underarmourstuff.workout_ua_upload( w.user.user,w @@ -420,7 +420,7 @@ def do_sync(w,options): 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: message,id = tpstuff.workout_tp_upload( w.user.user,w diff --git a/rowers/urls.py b/rowers/urls.py index be8f6ea8..b8d22ef1 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -120,7 +120,8 @@ urlpatterns = [ 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'^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'^update_empower$',views.rower_update_empower_view), url(r'^agegroupcp/(?P\d+)$',views.agegroupcpview), diff --git a/rowers/views.py b/rowers/views.py index 266bd7b3..1c41a8ba 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -72,7 +72,7 @@ from rowers.models import ( WorkoutComment,WorkoutCommentForm,RowerExportForm, CalcAgePerformance,PowerTimeFitnessMetric,PlannedSessionForm, PlannedSessionFormSmall,GeoCourseEditForm,VirtualRace, - VirtualRaceForm,VirtualRaceResultForm, + VirtualRaceForm,VirtualRaceResultForm,RowerImportExportForm ) from rowers.models import ( 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 - 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', compression='gzip') @@ -1424,7 +1427,11 @@ def add_workout_from_runkeeperdata(user,importid,data): 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', compression='gzip') @@ -1590,7 +1597,11 @@ def add_workout_from_stdata(user,importid,data): 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', compression='gzip') @@ -1757,7 +1768,11 @@ def add_workout_from_underarmourdata(user,importid,data): 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', compression='gzip') @@ -2612,12 +2627,31 @@ def rower_process_callback(request): # The imports page @login_required() -def imports_view(request,successmessage="",message=""): - messages.info(request,successmessage) - messages.error(request,message) +def imports_view(request): + + 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", { 'teams':get_my_teams(request.user), + 'form':form, }) # Just for testing purposes @@ -9650,12 +9684,22 @@ def workout_underarmourimport_view(request,message=""): def workout_polarimport_view(request): exercises = polarstuff.get_polar_workouts(request.user) 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: try: d = exercise['distance'] except KeyError: d = 0 - + i = exercise['id'] transactionid = exercise['transaction-id'] starttime = exercise['start-time'] @@ -9667,6 +9711,7 @@ def workout_polarimport_view(request): res = dict(zip(keys,values)) workouts.append(res) + return render(request, 'polar_list_import.html', { 'workouts':workouts, @@ -10428,7 +10473,7 @@ def workout_upload_view(request, request.session['async_tasks'] = [(jobid,'make_plot')] # upload to C2 - if (upload_to_c2): + if (upload_to_c2) or (w.user.c2_auto_export): try: message,id = c2stuff.workout_c2_upload(request.user,w) except C2NoTokenError: @@ -10439,7 +10484,7 @@ def workout_upload_view(request, else: messages.error(request,message) - if (upload_to_strava): + if (upload_to_strava) or (w.user.strava_auto_export): try: message,id = stravastuff.workout_strava_upload( request.user,w @@ -10452,7 +10497,7 @@ def workout_upload_view(request, else: messages.error(request,message) - if (upload_to_st): + if (upload_to_st) or (w.user.sporttracks_auto_export): try: message,id = sporttracksstuff.workout_sporttracks_upload( request.user,w @@ -10465,7 +10510,7 @@ def workout_upload_view(request, else: messages.error(request,message) - if (upload_to_rk): + if (upload_to_rk) or (w.user.runkeeper_auto_export): try: message,id = runkeeperstuff.workout_runkeeper_upload( request.user,w @@ -10480,7 +10525,7 @@ def workout_upload_view(request, messages.error(request,message) - if (upload_to_ua): + if (upload_to_ua) or (w.user.mapmyfitness_auto_export): try: message,id = underarmourstuff.workout_ua_upload( request.user,w @@ -10495,7 +10540,7 @@ def workout_upload_view(request, messages.error(request,message) - if (upload_to_tp): + if (upload_to_tp) or (w.user.trainingpeaks_auto_export): try: message,id = tpstuff.workout_tp_upload( request.user,w