diff --git a/rowers/c2stuff.py b/rowers/c2stuff.py index 4a082f35..432b5634 100644 --- a/rowers/c2stuff.py +++ b/rowers/c2stuff.py @@ -14,6 +14,7 @@ import datetime from requests import Request, Session import rowers.mytypes as mytypes from rowers.mytypes import otwtypes +from rowers.rower_rules import is_workout_user from iso8601 import ParseError import numpy @@ -863,7 +864,7 @@ def workout_c2_upload(user,w): r = Rower.objects.get(user=user) # ready to upload. Hurray - if (checkworkoutuser(user,w)): + if (is_workout_user(user,w)): c2userid = get_userid(r.c2token) if not c2userid: raise NoTokenError("User has no token") diff --git a/rowers/imports.py b/rowers/imports.py index fe1a85e6..d3a3ab01 100644 --- a/rowers/imports.py +++ b/rowers/imports.py @@ -43,7 +43,7 @@ from django.contrib.auth.decorators import login_required # from .models import Profile from rowingdata import rowingdata, make_cumvalues import pandas as pd -from rowers.models import Rower,Workout,checkworkoutuser,TombStone +from rowers.models import Rower,Workout,TombStone import rowers.mytypes as mytypes from rowsandall_app.settings import ( C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET, @@ -54,7 +54,7 @@ from rowsandall_app.settings import ( from rowers.utils import ( NoTokenError, custom_exception_handler, ewmovingaverage, - geo_distance,isprorower,uniqify + geo_distance,uniqify ) @@ -144,7 +144,7 @@ def imports_do_refresh_token(refreshtoken,oauth_data,access_token=''): else: grant_type = post_data.pop('grant_type',None) - if oauth_data['bearer_auth']: + if oauth_data['bearer_auth']: headers['authorization'] = 'Bearer %s' % access_token baseurl = oauth_data['base_url'] @@ -159,7 +159,7 @@ def imports_do_refresh_token(refreshtoken,oauth_data,access_token=''): headers=headers) - + if response.status_code == 200 or response.status_code == 201: token_json = response.json() else: @@ -186,7 +186,7 @@ def imports_do_refresh_token(refreshtoken,oauth_data,access_token=''): expires_in = int(expires_in) except (TypeError,ValueError): expires_in = 0 - + return [thetoken,expires_in,refresh_token] @@ -199,20 +199,20 @@ def imports_get_token( client_secret = oauth_data['client_secret'] client_id = oauth_data['client_id'] base_uri = oauth_data['base_url'] - + client_auth = requests.auth.HTTPBasicAuth( client_id,client_secret ) - + post_data = {"grant_type": "authorization_code", "code": code, "redirect_uri": redirect_uri, "client_secret": client_secret, "client_id": client_id, } - + try: headers = oauth_data['headers'] except KeyError: @@ -229,7 +229,7 @@ def imports_get_token( else: grant_type = post_data.pop('grant_type',None) - + if 'json' in oauth_data['content_type']: response = requests.post( base_uri, @@ -262,7 +262,7 @@ def imports_get_token( else: return [0,0,0] - + return [thetoken,expires_in,refresh_token] # Make authorization URL including random string @@ -293,8 +293,8 @@ def imports_token_refresh(user,tokenname,refreshtokenname,expirydatename,oauth_d # for Strava transition if not refreshtoken: refreshtoken = getattr(r,tokenname) - - + + res = imports_do_refresh_token(refreshtoken,oauth_data) access_token = res[0] expires_in = res[1] @@ -309,4 +309,3 @@ def imports_token_refresh(user,tokenname,refreshtokenname,expirydatename,oauth_d r.save() return access_token - diff --git a/rowers/models.py b/rowers/models.py index 593dfd1e..c0c9c520 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -666,6 +666,9 @@ class CoachingGroup(models.Model): rs = Rower.objects.filter(coachinggroups__in=[self]) return rs.count() + def get_coaches(self): + return Rower.objects.filter(mycoachgroup=self) + # Extension of User with rowing specific data @python_2_unicode_compatible class Rower(models.Model): @@ -1173,95 +1176,7 @@ class BasePlannedSessionFormSet(BaseFormSet): return -# Check if workout is owned by this user -def checkworkoutuser(user,workout): - if user.is_anonymous: - return False - try: - r = Rower.objects.get(user=user) - if workout.user == r: - return True - coaches = [] - for group in workout.user.coachinggroups.all(): - coach = Rower.objects.get(mycoachgroup=group) - coaches.append(coach) - for coach in coaches: - if user.rower == coach and workout.privacy == 'visible': - return True - else: - return False - except Rower.DoesNotExist: - return False -# Check if workout may be viewed by this user -def checkworkoutuserview(user,workout): - if user.is_anonymous: - return False - try: - r = Rower.objects.get(user=user) - if workout.user == r: - return True - teams = workout.user.team.all() - - for team in teams: - if team in r.team.all(): - return True - return False - except Rower.DoesNotExist: - return False - - return False - -def checkviewworkouts(user,rower): - try: - r = user.rower - if rower == r: - return True - teams = Team.objects.filter(manager=user) - - if rower in Rower.objects.filter(team__in=teams): - return True - - if rower in Rower.objects.filter(coachinggroups__in=[user.rower.mycoachgroup]): - return True - - - except Rower.DoesNotExist: - return False - -# check if user is plan and rower is in his group -def checkaccessplanuser(user,rower): - try: - r = Rower.objects.get(user=user) - if rower == r: - return True - team_managers = [t.manager for t in rower.team.all() if t.manager.rower.rowerplan in ['plan','coach','freecoach']] - if user.rower.rowerplan != 'basic': - return user in team_managers - else: - return False - - return False - except Rower.DoesNotExist: - return False - -# Check if user is coach or rower -def checkaccessuser(user,rower): - try: - r = Rower.objects.get(user=user) - if rower == r: - return True - coaches = [] - for group in rower.coachinggroups.all(): - coach = Rower.objects.get(mycoachgroup=group) - coaches.append(coach) - for coach in coaches: - if user.rower == coach: - return True - else: - return False - except Rower.DoesNotExist: - return False timezones = ( (x,x) for x in pytz.common_timezones diff --git a/rowers/polarstuff.py b/rowers/polarstuff.py index dc4f397a..2466c1e7 100644 --- a/rowers/polarstuff.py +++ b/rowers/polarstuff.py @@ -38,7 +38,7 @@ from django.contrib.auth.decorators import login_required from rowingdata import rowingdata import pandas as pd from rowers.models import Rower,Workout -from rowers.models import checkworkoutuser + import rowers.dataprep as dataprep from rowers.dataprep import columndict @@ -50,7 +50,7 @@ from stravalib.exc import ActivityUploadFailed,TimeoutExceeded from django_mailbox.models import Message,Mailbox,MessageAttachment 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' @@ -79,7 +79,7 @@ def get_token(code): except TypeError: headers = { 'Authorization': 'Basic %s' % base64.b64encode( bytes(auth_string,'utf-8')).decode('utf-8') } - + response = requests.post("https://polarremote.com/v2/oauth2/token", data=post_data, headers=headers) @@ -110,7 +110,7 @@ def make_authorization_url(): import urllib url = "https://flow.polar.com/oauth2/authorization" +urllib.parse.urlencode(params) - + return HttpResponseRedirect(url) def get_polar_notifications(): @@ -130,13 +130,13 @@ def get_polar_notifications(): response = requests.get(url, headers=headers) available_data = [] - + if response.status_code == 200: available_data = response.json()['available-user-data'] - + return available_data -from rowers.utils import isprorower +from rowers.rower_rules import ispromember def get_all_new_workouts(available_data,testing=False): for record in available_data: @@ -146,7 +146,7 @@ def get_all_new_workouts(available_data,testing=False): try: r = Rower.objects.get(polaruserid=record['user-id']) u = r.user - if r.polar_auto_import and isprorower(r): + if r.polar_auto_import and ispromember(u): exercise_list = get_polar_workouts(u) if testing: print(exercise_list) @@ -154,13 +154,13 @@ def get_all_new_workouts(available_data,testing=False): pass return 1 - + def get_polar_workouts(user): r = Rower.objects.get(user=user) exercise_list = [] - + if (r.polartoken == '') or (r.polartoken is None): s = "Token doesn't exist. Need to authorize" return custom_exception_handler(401,s) @@ -195,7 +195,7 @@ def get_polar_workouts(user): uploadoptions, default_flow_style=False ) - + transactionid = response.json()['transaction-id'] url = baseurl+'/users/{userid}/exercise-transactions/{transactionid}'.format( transactionid = transactionid, @@ -229,11 +229,11 @@ def get_polar_workouts(user): a = MessageAttachment(message=msg,document=filename[6:]) a.save() - + exercise_dict['filename'] = filename else: exercise_dict['filename'] = '' - + exercise_list.append(exercise_dict) # commit transaction @@ -280,7 +280,7 @@ def get_polar_user_info(user,physical=False): def get_polar_workout(user,id,transactionid): - + r = Rower.objects.get(user=user) if (r.polartoken == '') or (r.polartoken is None): s = "Token doesn't exist. Need to authorize" @@ -326,7 +326,7 @@ def get_polar_workout(user,id,transactionid): ) response = requests.get(url,headers = headers2) - + if response.status_code == 200: result = response.text # commit transaction @@ -337,7 +337,3 @@ def get_polar_workout(user,id,transactionid): return result return None - - - - diff --git a/rowers/rower_rules.py b/rowers/rower_rules.py index 386cb5eb..258c86b3 100644 --- a/rowers/rower_rules.py +++ b/rowers/rower_rules.py @@ -19,6 +19,154 @@ def user_is_not_basic(user): def is_coach(user): return user.rower.rowerplan in ['coach','freecoach'] +@rules.predicate +def is_promember(user): + try: + r = user.rower + except AttributeError: + return False + + return r.rowerplan in ['pro','coach','plan'] + +@rules.predicate +def is_protrial(user): + try: + r = user.rower + except AttributeError: + return False + + if r.rowerplan == 'basic': + return r.protrialexpires >= datetime.date.today() + if r.rowerplan == 'freecoach': + if r.mycoachgroup is not None: + return len(r.mycoachgroup)>=4 + + return False + +ispromember = is_promember | is_protrial + +# User / Coach relationships (Rower object) + +@rules.predicate +def can_plan(user): + return user.rower.rowerplan in ['plan','coach','freecoach'] + +@rules.predicate +def is_coach_user(user,rower): + try: + r = user.rower + except AttributeError: + return False + + if rower == r: + return True + + coaches = [] + + for group in r.coachinggroups.all(): + newcoaches = group.get_coaches() + for coach in newcoaches: + coaches.append(coach) + print(coaches) + for coach in coaches: + if rower == coach: + return True + + return False + +@rules.predicate +def is_rower_team_member(user,rower): + if user.rower == rower: + return True + + teams = user.rower.team.all() + + for team in teams: + if team.private == 'open': + if team in rower.team.all(): + return True + if team.manager == rower.user: + return True + + return False + +@rules.predicate +def can_plan_user(user,rower): + try: + r = user.rower + except AttributeError: + return False + + if rower == r: + return True + + # below + team_managers = [t.manager for t in rower.team.all() and can_plan(t.manager)] + if user_is_not_basic(user): + return user in team_managers + + return False + +rules.add_perm('rower.can_plan',can_plan_user) # replaces checkaccessplanuser +rules.add_perm('rower.is_coach',is_coach_user) # replaces checkaccessuser + + +# WORKOUT permissions + +@rules.predicate +def is_workout_user(user,workout): + if user.is_anonymous: + return False + + try: + r = user.rower + except AttributeError: + return False + + if workout.user == r: + return True + + coaches = [] + for group in workout.user.coachinggroups.all(): + coach = group.coachingrole + coaches.append(coach) + for coach in coaches: + if r == coach and workout.privacy == 'visible': + return True + + return False + +@rules.predicate +def can_view_workout(user,workout): + if user.is_anonymous: + if workout.privacy != 'private': + return True + return False + + try: + r = user.rower + except AttributeError: + return False + + teams = workout.user.team.all() + + for team in teams: + if team in r.team.all(): + return True + + return False + +rules.add_perm('workout.change_workout',is_workout_user) # replaces checkworkoutuser +rules.add_perm('workout.view_workout',can_view_workout) # replaces checkworkoutuserview + + + +# checkviewworkouts + +# PLANNING permissions + +# checkaccessplanuser (models.py) + # TEAM permissions @rules.predicate diff --git a/rowers/runkeeperstuff.py b/rowers/runkeeperstuff.py index 5e21b11a..4b2b3322 100644 --- a/rowers/runkeeperstuff.py +++ b/rowers/runkeeperstuff.py @@ -7,6 +7,8 @@ from __future__ import unicode_literals, absolute_import from rowers.imports import * import re +from rowers.rower_rules import is_workout_user + from rowsandall_app.settings import ( C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET, STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET, @@ -285,7 +287,7 @@ def workout_runkeeper_upload(user,w): # ready to upload. Hurray - if (checkworkoutuser(user,w)): + if (is_workout_user(user,w)): data = createrunkeeperworkoutdata(w) if not data: message = "Data error in Runkeeper Upload" diff --git a/rowers/sporttracksstuff.py b/rowers/sporttracksstuff.py index 2828a18f..28fc3168 100644 --- a/rowers/sporttracksstuff.py +++ b/rowers/sporttracksstuff.py @@ -18,6 +18,7 @@ from rowsandall_app.settings import ( ) import rowers.mytypes as mytypes +from rowers.rower_rules import is_workout_user oauth_data = { 'client_id': SPORTTRACKS_CLIENT_ID, @@ -265,7 +266,7 @@ def workout_sporttracks_upload(user,w): thetoken = sporttracks_open(user) - if (checkworkoutuser(user,w)): + if (is_workout_user(user,w)): data = createsporttracksworkoutdata(w) if not data: message = "Data error" diff --git a/rowers/stravastuff.py b/rowers/stravastuff.py index d8663c97..37e33dbc 100644 --- a/rowers/stravastuff.py +++ b/rowers/stravastuff.py @@ -18,6 +18,8 @@ queuehigh = django_rq.get_queue('low') from rowers.dataprep import columndict +from rowers.rower_rules import is_workout_user + import stravalib from stravalib.exc import ActivityUploadFailed,TimeoutExceeded @@ -632,7 +634,7 @@ def workout_strava_upload(user,w): s = "Token doesn't exist. Need to authorize" raise NoTokenError("Your hovercraft is full of eels") else: - if (checkworkoutuser(user,w)): + if (is_workout_user(user,w)): try: tcxfile,tcxmesg = createstravaworkoutdata(w) if tcxfile: diff --git a/rowers/templatetags/rowerfilters.py b/rowers/templatetags/rowerfilters.py index b3b7b5d5..cb0ca2d2 100644 --- a/rowers/templatetags/rowerfilters.py +++ b/rowers/templatetags/rowerfilters.py @@ -21,7 +21,7 @@ from rowers.plannedsessions import ( from rowers import c2stuff, runkeeperstuff from rowers.c2stuff import c2_open from rowers.runkeeperstuff import runkeeper_open -from rowers.models import checkaccessuser +from rowers.rower_rules import is_coach_user, is_workout_user from rowers.mytypes import otwtypes from rowers.utils import NoTokenError @@ -385,14 +385,13 @@ def is_session_manager(id,user): return ps.manager == user -from rowers.models import checkworkoutuser @register.filter def may_edit(workout,request): mayedit = 0 if request.user == workout.user.user: mayedit = True - if checkworkoutuser(request.user,workout): + if is_workout_user(request.user,workout): mayedit = True return mayedit @@ -413,7 +412,7 @@ def mayeditplan(obj,request): return request.user == obj.manager.user rr = Rower.objects.get(user=request.user) - if checkaccessuser(request.user,obj.rower) and rr.rowerplan not in ['basic','pro']: + if is_coach_user(request.user,obj.rower) and rr.rowerplan not in ['basic','pro']: mayedit = True diff --git a/rowers/tests/mocks.py b/rowers/tests/mocks.py index 278c1b03..7acce340 100644 --- a/rowers/tests/mocks.py +++ b/rowers/tests/mocks.py @@ -18,7 +18,7 @@ from django.test import TestCase, Client,override_settings from django.core.management import call_command from django.utils.six import StringIO from django.test.client import RequestFactory -from rowers.views import checkworkoutuser,c2_open +from rowers.views import c2_open from rowers.models import Workout, User, Rower, WorkoutForm,RowerForm,GraphImage from rowers.forms import DocumentsForm,CNsummaryForm,RegistrationFormUniqueEmail import rowers.plots as plots diff --git a/rowers/tests/statements.py b/rowers/tests/statements.py index 0276fe67..4b4e50a4 100644 --- a/rowers/tests/statements.py +++ b/rowers/tests/statements.py @@ -7,12 +7,12 @@ try: FileNotFoundError except NameError: FileNotFoundError = None - + try: OSError except NameError: OSError = None - + import pytest pytestmark = pytest.mark.django_db @@ -26,7 +26,7 @@ from django.core.management import call_command from django.core.files.uploadedfile import SimpleUploadedFile from django.utils.six import StringIO from django.test.client import RequestFactory -from rowers.views import checkworkoutuser,c2_open, multi_compare_view +from rowers.views import c2_open, multi_compare_view from rowers.forms import ( DocumentsForm,CNsummaryForm,RegistrationFormUniqueEmail, @@ -101,22 +101,22 @@ def get_random_file(filename='rowers/tests/testdata/testdata.csv',name=''): fromstring = 'test_%s_' % mod.__name__ except AttributeError: fromstring = 'none_' - + row = rdata(filename) totaldist = row.df['cum_dist'].max() totaltime = row.df['TimeStamp (sec)'].max()-row.df['TimeStamp (sec)'].min() totaltime = totaltime+row.df.loc[row.df.index[0],' ElapsedTime (sec)'] - - + + hours = int(totaltime/3600.) minutes = int((totaltime - 3600.*hours)/60.) seconds = int(totaltime - 3600.*hours - 60.*minutes) tenths = int(10*(totaltime - 3600.*hours - 60.*minutes - seconds)) - + duration = "%s:%s:%s.%s" % (hours,minutes,seconds,tenths) duration = datetime.time(hour=hours,minute=minutes,second=seconds) - + workoutdate = row.rowdatetime.date() workoutstarttime = row.rowdatetime @@ -139,7 +139,7 @@ def get_random_file(filename='rowers/tests/testdata/testdata.csv',name=''): 'duration':duration, 'totaldist':totaldist, } - + return thedict class UserFactory(factory.DjangoModelFactory): @@ -168,11 +168,11 @@ class RaceFactory(factory.DjangoModelFactory): sessiontype = 'indoorrace' sessionvalue = 1 sessionmode = 'time' - + class WorkoutFactory(factory.DjangoModelFactory): class Meta: model = Workout - + name = factory.LazyAttribute(lambda _: faker.word()) notes = faker.text() startdatetime = get_random_file(name=faker.word())['startdatetime'] @@ -220,5 +220,3 @@ def cleanup(request): request.addfinalizer(remove_test_files) - - diff --git a/rowers/tests/test_urls.py b/rowers/tests/test_urls.py index 1f2eeee2..e3be6bac 100644 --- a/rowers/tests/test_urls.py +++ b/rowers/tests/test_urls.py @@ -209,7 +209,6 @@ class URLTests(TestCase): '/rowers/workout/'+encoded1+'/editintervals/', '/rowers/workout/'+encoded1+'/flexchart/', '/rowers/workout/'+encoded1+'/forcecurve/', - '/rowers/workout/'+encoded1+'/get-testscript/', '/rowers/workout/'+encoded1+'/get-thumbnails/', '/rowers/workout/'+encoded1+'/histo/', '/rowers/workout/'+encoded1+'/image/', diff --git a/rowers/tpstuff.py b/rowers/tpstuff.py index efdf9e8f..010e9f59 100644 --- a/rowers/tpstuff.py +++ b/rowers/tpstuff.py @@ -42,6 +42,8 @@ oauth_data = { 'scope':'write', } +from rowers.rower_rules import is_workout_user + # Checks if user has UnderArmour token, renews them if they are expired def tp_open(user): @@ -171,7 +173,7 @@ def workout_tp_upload(user,w): # need some code if token doesn't refresh - if (checkworkoutuser(user,w)): + if (is_workout_user(user,w)): tcxfile = createtpworkoutdata(w) if tcxfile: res,reason,status_code,headers = uploadactivity( diff --git a/rowers/traverselinktest.py b/rowers/traverselinktest.py index 546a5ccf..3856fb44 100644 --- a/rowers/traverselinktest.py +++ b/rowers/traverselinktest.py @@ -9,7 +9,7 @@ from django.test import TestCase, Client,override_settings from django.core.management import call_command from django.utils.six import StringIO from django.test.client import RequestFactory -from .views import checkworkoutuser,c2_open +from .views import c2_open from rowers.models import Workout, User, Rower, WorkoutForm,RowerForm,GraphImage from rowers.forms import DocumentsForm,CNsummaryForm,RegistrationFormUniqueEmail import rowers.plots as plots @@ -55,12 +55,12 @@ class TraverseLinksTest(TestCase): name='testworkout',workouttype='On-water', user=self.r,date=nu.strftime('%Y-%m-%d'), starttime=nu.strftime('%H:%M:%S'), - duration="0:55:00",distance=8000) + duration="0:55:00",distance=8000) self.w2 = Workout.objects.create( name='testworkout 2',workouttype='On-water', user=self.r,date=nu.strftime('%Y-%m-%d'), starttime=nu.strftime('%H:%M:%S'), - duration="0:55:00",distance=8000) + duration="0:55:00",distance=8000) if self.client.login( username="superuser1", password="pwd"): if VERBOSE: diff --git a/rowers/underarmourstuff.py b/rowers/underarmourstuff.py index 2d18a21f..7d68d2c3 100644 --- a/rowers/underarmourstuff.py +++ b/rowers/underarmourstuff.py @@ -9,6 +9,7 @@ import numpy import rowers.mytypes as mytypes from rowers.mytypes import otwtypes +from rowers.rower_rules import is_workout_user from rowsandall_app.settings import ( UNDERARMOUR_CLIENT_KEY, @@ -331,7 +332,7 @@ def workout_ua_upload(user,w): # ready to upload. Hurray - if (checkworkoutuser(user,w)): + if (is_workout_user(user,w)): data = createunderarmourworkoutdata(w) # return HttpResponse(json.dumps(data)) if not data: diff --git a/rowers/uploads.py b/rowers/uploads.py index 2d63b557..b0676914 100644 --- a/rowers/uploads.py +++ b/rowers/uploads.py @@ -14,6 +14,8 @@ from rowers.tasks import ( from rowers.models import GraphImage +from rowers.rower_rules import ispromember + from PIL import Image import numpy as np @@ -472,6 +474,8 @@ import rowers.runkeeperstuff as runkeeperstuff import rowers.underarmourstuff as underarmourstuff import rowers.tpstuff as tpstuff +from rowers.rower_rules import is_promember + def set_workouttype(w,options): try: w.workouttype = options['workouttype'] @@ -500,8 +504,6 @@ def make_private(w,options): return 1 -from rowers.utils import isprorower - def do_sync(w,options): try: if options['stravaid'] != 0: @@ -511,7 +513,7 @@ def do_sync(w,options): except KeyError: pass - if ('upload_to_C2' in options and options['upload_to_C2']) or (w.user.c2_auto_export and isprorower(w.user)): + if ('upload_to_C2' in options and options['upload_to_C2']) or (w.user.c2_auto_export and ispromember(w.user)): if ('upload_to_C2' in options and not options['upload_to_C2']): pass else: @@ -521,7 +523,7 @@ def do_sync(w,options): id = 0 message = "Something went wrong with the Concept2 sync" - if ('upload_to_Strava' in options and options['upload_to_Strava']) or (w.user.strava_auto_export and isprorower(w.user)): + if ('upload_to_Strava' in options and options['upload_to_Strava']) or (w.user.strava_auto_export and ispromember(w.user)): if ('upload_to_Strava' in options and not options['upload_to_Strava']): pass else: @@ -534,7 +536,7 @@ def do_sync(w,options): message = "Please connect to Strava first" - if ('upload_to_SportTracks' in options and options['upload_to_SportTracks']) or (w.user.sporttracks_auto_export and isprorower(w.user)): + if ('upload_to_SportTracks' in options and options['upload_to_SportTracks']) or (w.user.sporttracks_auto_export and ispromember(w.user)): if ('upload_to_SportTracks' in options and not options['upload_to_SportTracks']): pass else: @@ -548,7 +550,7 @@ def do_sync(w,options): id = 0 - if ('upload_to_RunKeeper' in options and options['upload_to_RunKeeper']) or (w.user.runkeeper_auto_export and isprorower(w.user)): + if ('upload_to_RunKeeper' in options and options['upload_to_RunKeeper']) or (w.user.runkeeper_auto_export and ispromember(w.user)): if ('upload_to_RunKeeper' in options and not options['upload_to_RunKeeper']): pass else: @@ -561,7 +563,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']) or (w.user.mapmyfitness_auto_export and isprorower(w.user)): + if ('upload_to_MapMyFitness' in options and options['upload_to_MapMyFitness']) or (w.user.mapmyfitness_auto_export and ispromember(w.user)): if ('upload_to_MapMyFitness' in options and not options['upload_to_MapMyFitness']): pass else: @@ -574,7 +576,7 @@ def do_sync(w,options): id = 0 - if ('upload_to_TrainingPeaks' in options and options['upload_to_TrainingPeaks']) or (w.user.trainingpeaks_auto_export and isprorower(w.user)): + if ('upload_to_TrainingPeaks' in options and options['upload_to_TrainingPeaks']) or (w.user.trainingpeaks_auto_export and ispromember(w.user)): if ('upload_to_TrainingPeaks' in options and not options['upload_to_TrainingPeaks']): pass else: diff --git a/rowers/urls.py b/rowers/urls.py index 6a061af4..15b7768f 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -289,8 +289,6 @@ urlpatterns = [ name='otw_use_gps'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/toggle-ranking/$',views.workout_toggle_ranking, name='workout_toggle_ranking'), - re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/get-testscript/$',views.get_testscript, - name='get_testscript'), re_path(r'^workout/upload/team/$',views.team_workout_upload_view,name='team_workout_upload_view'), re_path(r'^workout/upload/$',views.workout_upload_view,name='workout_upload_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/histo/$',views.workout_histo_view, diff --git a/rowers/utils.py b/rowers/utils.py index d4e47993..a1258b1c 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -109,7 +109,7 @@ def get_call():

""" % (call1, call2) - + return call def absolute(request): @@ -131,7 +131,7 @@ def trcolors(r1,g1,b1,r2,g2,b2): h1,s1,v1 = colorsys.rgb_to_hsv(r1,g1,b1) h2,s2,v2 = colorsys.rgb_to_hsv(r2,g2,b2) - + return 360*h1,360*(h2-h1),s1,(s2-s1),v1,(v2-v1) palettes = { @@ -167,7 +167,7 @@ def is_ranking_piece(workout): return True elif workout.duration in rankingdurations: return True - + return False def range_to_color_hex(groupcols,palette='monochrome_blue'): @@ -176,20 +176,20 @@ def range_to_color_hex(groupcols,palette='monochrome_blue'): plt = palettes[palette] except KeyError: plt = palettes['monochrome_blue'] - + rgb = [colorsys.hsv_to_rgb((plt[0]+plt[1]*x)/360., plt[2]+plt[3]*x, plt[4]+plt[5]*x) for x in groupcols] RGB = [(int(255.*r),int(255.*g),int(255.*b)) for (r, g, b) in rgb] colors = ["#%02x%02x%02x" % (r, g, b) for (r, g, b) in RGB] - + return colors def str2bool(v): return v.lower() in ("yes", "true", "t", "1") -def uniqify(seq, idfun=None): +def uniqify(seq, idfun=None): # order preserving if idfun is None: def idfun(x): return x @@ -222,12 +222,12 @@ def geo_distance(lat1,lon1,lat2,lon2): This is a slight underestimate but is close enough for our purposes, We're never moving more than 10 meters between trackpoints - Bearing calculation fails if one of the points is a pole. - (Hey, from the North pole you can walk South, East, North and end up + Bearing calculation fails if one of the points is a pole. + (Hey, from the North pole you can walk South, East, North and end up on the same spot!) - + """ - + # radius of our earth in km --> should be moved to settings if # rowing takes off on other planets R = 6373.0 @@ -257,7 +257,7 @@ def geo_distance(lat1,lon1,lat2,lon2): return [distance,bearing] - + def isbreakthrough(delta,cpvalues,p0,p1,p2,p3,ratio): pwr = abs(p0)/(1+(delta/abs(p2))) @@ -268,7 +268,7 @@ def isbreakthrough(delta,cpvalues,p0,p1,p2,p3,ratio): pwr *= ratio - + delta = delta.values.astype(int) cpvalues = cpvalues.values.astype(int) pwr = pwr.astype(int) @@ -276,7 +276,7 @@ def isbreakthrough(delta,cpvalues,p0,p1,p2,p3,ratio): res = np.sum(cpvalues>pwr) res2 = np.sum(cpvalues>pwr2) - + btdf = pd.DataFrame( { 'delta':delta[cpvalues>pwr], @@ -288,7 +288,7 @@ def isbreakthrough(delta,cpvalues,p0,p1,p2,p3,ratio): btdf.sort_values('delta',axis=0,inplace=True) - + return res>1,btdf,res2>1 @@ -300,7 +300,7 @@ def myqueue(queue,function,*args,**kwargs): def revoke(self): return 1 - + if settings.TESTING: return MockJob() elif settings.CELERY: @@ -310,7 +310,7 @@ def myqueue(queue,function,*args,**kwargs): else: if settings.DEBUG: kwargs['debug'] = True - + job_id = str(uuid.uuid4()) kwargs['job_id'] = job_id kwargs['jobkey'] = job_id @@ -335,7 +335,7 @@ def my_dict_from_instance(instance,model): thedict['id'] = instance.id for f in instance._meta.fields: - + fname = f.name try: @@ -405,24 +405,13 @@ def totaltime_sec_to_string(totaltime): return duration -def isprorower(r): - result = False - result = r.rowerplan in ['pro','coach','plan'] - - if not result and r.protrialexpires: - result = r.rowerplan == 'basic' and r.protrialexpires >= datetime.date.today() - if not result and r.rowerplan == 'freecoach': - if r.mycoachgroup is not None: - result = len(r.mycoachgroup)>=4 - - return result def iscoach(m,r): result = False result = m in r.coaches return result - + # Exponentially weighted moving average # Used for data smoothing of the jagged data obtained by Strava # See bitbucket issue 72 @@ -433,7 +422,7 @@ def ewmovingaverage(interval,window_size): 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().loc[:,'v'] i_ewma2 = idf_ewma2.mean().loc[:,'v'] @@ -478,7 +467,7 @@ def custom_exception_handler(exc,message): res.json = json.dumps(response) return res - + def get_strava_stream(r,metric,stravaid,series_type='time',fetchresolution='high'): authorizationstring = str('Bearer ' + r.stravatoken) headers = {'Authorization': authorizationstring, @@ -503,7 +492,7 @@ def get_strava_stream(r,metric,stravaid,series_type='time',fetchresolution='high return np.array(data['data']) except TypeError: return None - + return None def allmonths(startdate,enddate): @@ -518,4 +507,3 @@ def allsundays(startdate,enddate): while d<=enddate: yield d d += datetime.timedelta(days=7) - diff --git a/rowers/views/exportviews.py b/rowers/views/exportviews.py index 4b5f4684..08f2e064 100644 --- a/rowers/views/exportviews.py +++ b/rowers/views/exportviews.py @@ -1,18 +1,17 @@ from rowers.views.statements import * - + # Export workout to TCX and send to user's email address -@login_required() +@permission_required('workout.change_workout',fn=objectgetter(Workout, 'id')) def workout_tcxemail_view(request,id=0): r = getrower(request.user) w = get_workout(id) - if not checkworkoutuser(request.user,w): - raise PermissionDenied("Access denied") + row = rdata(w.csvfilename) @@ -31,8 +30,8 @@ def workout_tcxemail_view(request,id=0): return response - - + + @login_required() def plannedsessions_icsemail_view(request,userid=0): @@ -66,7 +65,7 @@ def plannedsessions_icsemail_view(request,userid=0): d1 = startdate.strftime("%Y%m%d"), d2 = enddate.strftime("%Y%m%d"), ) - + response['Content-Type'] = 'application/octet-stream' return response @@ -96,7 +95,7 @@ def plannedsessions_coach_icsemail_view(request,userid=0): rowers += ps.rower.filter(team__in=rteams).exclude(rowerplan='freecoach') rowers = list(set(rowers)) - + cal = Calendar() cal.add('prodid','rowsandall') cal.add('version','1.0') @@ -169,13 +168,12 @@ def course_kmldownload_view(request,id=0): # Export workout to GPX and send to user's email address -@login_required() +@permission_required('workout.change_workout',fn=objectgetter(Workout, 'id')) def workout_gpxemail_view(request,id=0): r = getrower(request.user) w = get_workout(id) - if not checkworkoutuser(request.user,w): - raise PermissionDenied("Access denied") + row = rdata(w.csvfilename) @@ -233,17 +231,16 @@ def workouts_summaries_email_view(request): { 'form':form }) - + # Get Workout CSV file and send it to user's email address -@login_required() +@permission_required('workout.change_workout',fn=objectgetter(Workout, 'id')) def workout_csvemail_view(request,id=0): r = getrower(request.user) w = get_workout(id) - if not checkworkoutuser(request.user,w): - raise PermissionDenied("Access denied") + rowdata = rdata(w.csvfilename) code = str(uuid4()) @@ -254,13 +251,13 @@ def workout_csvemail_view(request,id=0): df = rowdata.df df[' ElapsedTime (sec)'] = df['TimeStamp (sec)'] df['TimeStamp (sec)'] = df['TimeStamp (sec)'] + starttimeunix - + response = HttpResponse(df.to_csv()) response['Content-Disposition'] = 'attachment; filename="%s"' % filename response['Content-Type'] = 'application/octet-stream' return response - + # Get Workout CSV file and send it to user's email address @login_required() diff --git a/rowers/views/importviews.py b/rowers/views/importviews.py index 2f97d00c..7b77b68b 100644 --- a/rowers/views/importviews.py +++ b/rowers/views/importviews.py @@ -8,14 +8,14 @@ from rowers.views.statements import * import numpy def default(o): - if isinstance(o, numpy.int64): return int(o) + if isinstance(o, numpy.int64): return int(o) raise TypeError # Send workout to TP -@login_required() +@permission_required('workout.change_workout',fn=objectgetter(Workout, 'id')) def workout_tp_upload_view(request,id=0): - + message = "" r = getrower(request.user) res = -1 @@ -25,52 +25,48 @@ def workout_tp_upload_view(request,id=0): return HttpResponseRedirect("/rowers/me/tpauthorize/") # ready to upload. Hurray - w = get_workout_permitted(request.user,id) + w = get_object_or_404(Workout,pk=id) r = w.user - if (checkworkoutuser(request.user,w)): - tcxfile = tpstuff.createtpworkoutdata(w) - if tcxfile: - res,reason,status_code,headers = tpstuff.uploadactivity( - r.tptoken,tcxfile, - name=w.name - ) - if res == 0: - message = "Upload to TrainingPeaks failed with status code "+str(status_code)+": "+reason - try: - os.remove(tcxfile) - except WindowsError: - pass - messages.error(request,message) - - else: # res != 0 - w.uploadedtotp = res - w.save() + tcxfile = tpstuff.createtpworkoutdata(w) + if tcxfile: + res,reason,status_code,headers = tpstuff.uploadactivity( + r.tptoken,tcxfile, + name=w.name + ) + if res == 0: + message = "Upload to TrainingPeaks failed with status code "+str(status_code)+": "+reason + try: os.remove(tcxfile) - messages.info(request,'Uploaded to TrainingPeaks') + except WindowsError: + pass - else: # no tcxfile - message = "Upload to TrainingPeaks failed" - w.uploadedtotp = -1 - w.save() messages.error(request,message) - else: # not allowed to upload - message = "You are not allowed to export this workout to TP" + else: # res != 0 + w.uploadedtotp = res + w.save() + os.remove(tcxfile) + messages.info(request,'Uploaded to TrainingPeaks') + + else: # no tcxfile + message = "Upload to TrainingPeaks failed" + w.uploadedtotp = -1 + w.save() messages.error(request,message) url = reverse(r.defaultlandingpage, kwargs = { 'id':encoder.encode_hex(w.id), }) - + return HttpResponseRedirect(url) - - + + # Send workout to Strava # abundance of error logging here because there were/are some bugs -@login_required() +@permission_required('workout.change_workout',fn=objectgetter(Workout, 'id')) def workout_strava_upload_view(request,id=0): message = "" r = getrower(request.user) @@ -80,7 +76,7 @@ def workout_strava_upload_view(request,id=0): thetoken = strava_open(request.user) except NoTokenError: return HttpResponseRedirect("/rowers/me/stravaauthorize/") - + if (r.stravatoken == '') or (r.stravatoken is None): s = "Token doesn't exist. Need to authorize" return HttpResponseRedirect("/rowers/me/stravaauthorize/") @@ -88,94 +84,94 @@ def workout_strava_upload_view(request,id=0): # ready to upload. Hurray w = get_workout_permitted(request.user,id) r = w.user - if (checkworkoutuser(request.user,w)): - try: - tcxfile,tcxmessg = stravastuff.createstravaworkoutdata(w) - if tcxfile: - with open(tcxfile,'rb') as f: + + try: + tcxfile,tcxmessg = stravastuff.createstravaworkoutdata(w) + if tcxfile: + with open(tcxfile,'rb') as f: + try: + newnotes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com' + except TypeError: + newnotes = 'from '+w.workoutsource+' via rowsandall.com' + if w.workouttype in mytypes.rowtypes: + activity_type = r.stravaexportas + else: try: - newnotes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com' - except TypeError: - newnotes = 'from '+w.workoutsource+' via rowsandall.com' - if w.workouttype in mytypes.rowtypes: - activity_type = r.stravaexportas - else: - try: - activity_type = mytypes.stravamapping[w.workouttype] - except KeyError: - activity_type = 'Ride' - - res,mes = stravastuff.handle_stravaexport( - f,w.name, - r.stravatoken, - description=newnotes, - activity_type=activity_type) - if res==0: - messages.error(request,mes) - w.uploadedtostrava = -1 - w.save() - try: - os.remove(tcxfile) - except WindowsError: - pass - url = reverse(r.defaultlandingpage, - kwargs = { - 'id':encoder.encode_hex(w.id), - }) - response = HttpResponseRedirect(url) - return response - + activity_type = mytypes.stravamapping[w.workouttype] + except KeyError: + activity_type = 'Ride' + + res,mes = stravastuff.handle_stravaexport( + f,w.name, + r.stravatoken, + description=newnotes, + activity_type=activity_type) + if res==0: + messages.error(request,mes) + w.uploadedtostrava = -1 + w.save() try: - w.uploadedtostrava = res - w.save() - try: - os.remove(tcxfile) - except WindowsError: - pass - url = reverse('workout_edit_view',kwargs={'id':w.id}) + os.remove(tcxfile) + except WindowsError: + pass + url = reverse(r.defaultlandingpage, + kwargs = { + 'id':encoder.encode_hex(w.id), + }) + response = HttpResponseRedirect(url) + return response + + try: + w.uploadedtostrava = res + w.save() + try: + os.remove(tcxfile) + except WindowsError: + pass + url = reverse('workout_edit_view',kwargs={'id':w.id}) - messages.info(request,mes) - except: - with open("media/stravaerrors.log","a") as errorlog: - errorstring = str(sys.exc_info()[0]) - timestr = strftime("%Y%m%d-%H%M%S") - errorlog.write(timestr+errorstring+"\r\n") - errorlog.write("views.py line 826\r\n") - message = 'Error: '+errorstring - messages.error(request,message) - else: # No tcxfile - message = "Strava Data error "+tcxmessg - messages.error(request,message) - w.uploadedtostrava = -1 - w.save() - url = reverse(r.defaultlandingpage, - kwargs = { - 'id':encoder.encode_hex(w.id), - }) - response = HttpResponseRedirect(url) - - - url = reverse(r.defaultlandingpage, - kwargs = { - 'id':encoder.encode_hex(w.id), - } - ) - response = HttpResponseRedirect(url) - except ActivityUploadFailed as e: - message = "Strava Upload error: %s" % e + messages.info(request,mes) + except: + with open("media/stravaerrors.log","a") as errorlog: + errorstring = str(sys.exc_info()[0]) + timestr = strftime("%Y%m%d-%H%M%S") + errorlog.write(timestr+errorstring+"\r\n") + errorlog.write("views.py line 826\r\n") + message = 'Error: '+errorstring + messages.error(request,message) + else: # No tcxfile + message = "Strava Data error "+tcxmessg messages.error(request,message) w.uploadedtostrava = -1 w.save() - os.remove(tcxfile) url = reverse(r.defaultlandingpage, kwargs = { 'id':encoder.encode_hex(w.id), - }) + }) response = HttpResponseRedirect(url) - return response - + + url = reverse(r.defaultlandingpage, + kwargs = { + 'id':encoder.encode_hex(w.id), + } + ) + response = HttpResponseRedirect(url) + except ActivityUploadFailed as e: + message = "Strava Upload error: %s" % e + messages.error(request,message) + w.uploadedtostrava = -1 + w.save() + os.remove(tcxfile) + url = reverse(r.defaultlandingpage, + kwargs = { + 'id':encoder.encode_hex(w.id), + }) + response = HttpResponseRedirect(url) + + return response + # Upload workout to Concept2 logbook @login_required() def workout_c2_upload_view(request,id=0): @@ -183,7 +179,7 @@ def workout_c2_upload_view(request,id=0): # ready to upload. Hurray w = get_workout(id) r = w.user - + try: message,c2id = c2stuff.workout_c2_upload(request.user,w) except NoTokenError: @@ -199,14 +195,14 @@ def workout_c2_upload_view(request,id=0): kwargs = { 'id':encoder.encode_hex(w.id) }) - + response = HttpResponseRedirect(url) return response # Upload workout to RunKeeper -@login_required() +@permission_required('workout.change_workout',fn=objectgetter(Workout, 'id')) def workout_runkeeper_upload_view(request,id=0): message = "" w = get_workout(id) @@ -219,48 +215,45 @@ def workout_runkeeper_upload_view(request,id=0): # ready to upload. Hurray - if (checkworkoutuser(request.user,w)): - data = runkeeperstuff.createrunkeeperworkoutdata(w) - if not data: - message = "Data error" - messages.error(request,message) - url = reverse(r.defaultlandingpage, - kwargs = { - 'id':id, - }) - return HttpResponseRedirect(url) - - authorizationstring = str('Bearer ' + thetoken) - headers = {'Authorization': authorizationstring, - 'user-agent': 'sanderroosendaal', - 'Content-Type': 'application/vnd.com.runkeeper.NewFitnessActivity+json', - 'Content-Length':'nnn'} - url = "https://api.runkeeper.com/fitnessActivities" - response = requests.post(url,headers=headers,data=json.dumps(data,default=default)) - - # check for duplicate error first - if (response.status_code == 409 ): - message = "Duplicate error" - messages.error(request,message) - w.uploadedtorunkeeper = -1 - w.save() - elif (response.status_code == 201 or response.status_code==200): - runkeeperid = runkeeperstuff.getidfromresponse(response) - w.uploadedtorunkeeper = runkeeperid - w.save() - url = reverse('workout_edit_view', - kwargs={'id':encoder.encode_hex(w.id)}) - - return HttpResponseRedirect(url) - else: - s = response - message = "Something went wrong in workout_runkeeper_upload_view: %s - %s" % (s.reason,s.text) - messages.error(request,message) - - else: - message = "You are not authorized to upload this workout" + data = runkeeperstuff.createrunkeeperworkoutdata(w) + if not data: + message = "Data error" messages.error(request,message) + url = reverse(r.defaultlandingpage, + kwargs = { + 'id':id, + }) + return HttpResponseRedirect(url) + + authorizationstring = str('Bearer ' + thetoken) + headers = {'Authorization': authorizationstring, + 'user-agent': 'sanderroosendaal', + 'Content-Type': 'application/vnd.com.runkeeper.NewFitnessActivity+json', + 'Content-Length':'nnn'} + + url = "https://api.runkeeper.com/fitnessActivities" + response = requests.post(url,headers=headers,data=json.dumps(data,default=default)) + + # check for duplicate error first + if (response.status_code == 409 ): + message = "Duplicate error" + messages.error(request,message) + w.uploadedtorunkeeper = -1 + w.save() + elif (response.status_code == 201 or response.status_code==200): + runkeeperid = runkeeperstuff.getidfromresponse(response) + w.uploadedtorunkeeper = runkeeperid + w.save() + url = reverse('workout_edit_view', + kwargs={'id':encoder.encode_hex(w.id)}) + + return HttpResponseRedirect(url) + else: + s = response + message = "Something went wrong in workout_runkeeper_upload_view: %s - %s" % (s.reason,s.text) + messages.error(request,message) + url = reverse(r.defaultlandingpage, kwargs = { @@ -270,7 +263,7 @@ def workout_runkeeper_upload_view(request,id=0): return HttpResponseRedirect(url) # Upload workout to Underarmour -@login_required() +@permission_required('workout.change_workout',fn=objectgetter(Workout, 'id')) def workout_underarmour_upload_view(request,id=0): message = "" w = get_workout(id) @@ -282,48 +275,45 @@ def workout_underarmour_upload_view(request,id=0): return HttpResponseRedirect("/rowers/me/underarmourauthorize/") # ready to upload. Hurray - - if (checkworkoutuser(request.user,w)): - data = underarmourstuff.createunderarmourworkoutdata(w) - if not data: - message = "Data error" - messages.error(request,message) - url = reverse(r.defaultlandingpage, - kwargs = { - 'id':encoder.encode_hex(w.id), - }) - return HttpResponseRedirect(url) - - authorizationstring = str('Bearer ' + thetoken) - headers = {'Authorization': authorizationstring, - 'Api-Key': UNDERARMOUR_CLIENT_KEY, - 'user-agent': 'sanderroosendaal', - 'Content-Type': 'application/json', - } - url = "https://api.ua.com/v7.1/workout/" - response = requests.post(url,headers=headers,data=json.dumps(data,default=default)) - - # check for duplicate error first - if (response.status_code == 409 ): - message = "Duplicate error" - messages.error(request,message) - w.uploadedtounderarmour = -1 - w.save() - elif (response.status_code == 201 or response.status_code==200): - underarmourid = underarmourstuff.getidfromresponse(response) - w.uploadedtounderarmour = underarmourid - w.save() - url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)}) + data = underarmourstuff.createunderarmourworkoutdata(w) + if not data: + message = "Data error" + messages.error(request,message) + url = reverse(r.defaultlandingpage, + kwargs = { + 'id':encoder.encode_hex(w.id), + }) + return HttpResponseRedirect(url) - return HttpResponseRedirect(url) - else: - s = response - message = "Something went wrong in workout_underarmour_upload_view: %s " % s.reason - messages.error(request,message) + authorizationstring = str('Bearer ' + thetoken) + headers = {'Authorization': authorizationstring, + 'Api-Key': UNDERARMOUR_CLIENT_KEY, + 'user-agent': 'sanderroosendaal', + 'Content-Type': 'application/json', + } + + url = "https://api.ua.com/v7.1/workout/" + response = requests.post(url,headers=headers,data=json.dumps(data,default=default)) + + + # check for duplicate error first + if (response.status_code == 409 ): + message = "Duplicate error" + messages.error(request,message) + w.uploadedtounderarmour = -1 + w.save() + elif (response.status_code == 201 or response.status_code==200): + underarmourid = underarmourstuff.getidfromresponse(response) + w.uploadedtounderarmour = underarmourid + w.save() + url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)}) + + return HttpResponseRedirect(url) else: - message = "You are not authorized to upload this workout" + s = response + message = "Something went wrong in workout_underarmour_upload_view: %s " % s.reason messages.error(request,message) url = reverse(r.defaultlandingpage, @@ -334,7 +324,7 @@ def workout_underarmour_upload_view(request,id=0): return HttpResponseRedirect(url) # Upload workout to SportTracks -@login_required() +@permission_required('workout.change_workout',fn=objectgetter(Workout, 'id')) def workout_sporttracks_upload_view(request,id=0): message = "" # ready to upload. Hurray @@ -347,49 +337,46 @@ def workout_sporttracks_upload_view(request,id=0): return HttpResponseRedirect("/rowers/me/sporttracksauthorize/") - if (checkworkoutuser(request.user,w)): - data = sporttracksstuff.createsporttracksworkoutdata(w) - if not data: - message = "Data error" - messages.error(request,message) - url = reverse(r.defaultlandingpage, - kwargs = { - 'id':encoder.encode_hex(w.id), - }) - return HttpResponseRedirect(url) + data = sporttracksstuff.createsporttracksworkoutdata(w) - authorizationstring = str('Bearer ' + thetoken) - headers = {'Authorization': authorizationstring, - 'user-agent': 'sanderroosendaal', - 'Content-Type': 'application/json'} + if not data: + message = "Data error" + messages.error(request,message) + url = reverse(r.defaultlandingpage, + kwargs = { + 'id':encoder.encode_hex(w.id), + }) + return HttpResponseRedirect(url) - url = "https://api.sporttracks.mobi/api/v2/fitnessActivities.json" - response = requests.post(url,headers=headers,data=json.dumps(data,default=default)) + authorizationstring = str('Bearer ' + thetoken) + headers = {'Authorization': authorizationstring, + 'user-agent': 'sanderroosendaal', + 'Content-Type': 'application/json'} - - # check for duplicate error first - if (response.status_code == 409 ): - message = "Duplicate error" - messages.error(request,message) - w.uploadedtosporttracks = -1 - w.save() - elif (response.status_code == 201 or response.status_code==200): - s= response.json() - sporttracksid = sporttracksstuff.getidfromresponse(response) - w.uploadedtosporttracks = sporttracksid - w.save() - message = "Upload to SportTracks was successful" - messages.info(request,message) + url = "https://api.sporttracks.mobi/api/v2/fitnessActivities.json" + response = requests.post(url,headers=headers,data=json.dumps(data,default=default)) - url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)}) - return HttpResponseRedirect(url) - else: - s = response - message = "Something went wrong in workout_sporttracks_upload_view: %s" % s.reason - messages.error(request,message) + + # check for duplicate error first + if (response.status_code == 409 ): + message = "Duplicate error" + messages.error(request,message) + w.uploadedtosporttracks = -1 + w.save() + elif (response.status_code == 201 or response.status_code==200): + s= response.json() + sporttracksid = sporttracksstuff.getidfromresponse(response) + w.uploadedtosporttracks = sporttracksid + w.save() + message = "Upload to SportTracks was successful" + messages.info(request,message) + + url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)}) + return HttpResponseRedirect(url) else: - message = "You are not authorized to upload this workout" + s = response + message = "Something went wrong in workout_sporttracks_upload_view: %s" % s.reason messages.error(request,message) url = reverse(r.defaultlandingpage, @@ -399,7 +386,7 @@ def workout_sporttracks_upload_view(request,id=0): return HttpResponseRedirect(url) -# Concept2 authorization +# Concept2 authorization @login_required() def rower_c2_authorize(request): # Generate a random string for the state parameter @@ -444,10 +431,10 @@ def rower_polar_authorize(request): # "scope":"accesslink.read_all" } url = "https://flow.polar.com/oauth2/authorization?" +urllib.parse.urlencode(params) - + return HttpResponseRedirect(url) - + # Runkeeper authorization @login_required() @@ -494,7 +481,7 @@ def rower_underarmour_authorize(request): state = str(uuid4()) redirect_uri = UNDERARMOUR_REDIRECT_URI - + url = 'https://www.mapmyfitness.com/v7.1/oauth2/authorize/?' \ 'client_id={0}&response_type=code&redirect_uri={1}'.format( UNDERARMOUR_CLIENT_KEY, redirect_uri @@ -647,7 +634,7 @@ def rower_process_callback(request): url = reverse('workouts_view') return HttpResponseRedirect(url) - + access_token = res[0] if access_token == 0: message = res[1] @@ -657,8 +644,8 @@ def rower_process_callback(request): url = reverse('workouts_view') return HttpResponseRedirect(url) - - + + expires_in = res[1] refresh_token = res[2] expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in) @@ -674,12 +661,12 @@ def rower_process_callback(request): messages.info(request,successmessage) url = reverse('workouts_view') - + return HttpResponseRedirect(url) -# dummy +# dummy @login_required() def rower_process_twittercallback(request): return "dummy" @@ -697,13 +684,13 @@ def rower_process_polarcallback(request): messages.error(request,message) url = reverse('workouts_view') - + return HttpResponseRedirect(url) access_token, expires_in, user_id = polarstuff.get_token(code) expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in) - + r = getrower(request.user) r.polartoken = access_token r.polartokenexpirydate = expirydatetime @@ -714,7 +701,7 @@ def rower_process_polarcallback(request): successmessage = "Tokens stored. Good to go" messages.info(request,successmessage) url = reverse('workouts_view') - + return HttpResponseRedirect(url) @@ -734,10 +721,10 @@ def rower_process_stravacallback(request): messages.error(request,message) url = reverse('workouts_view') - + return HttpResponseRedirect(url) - + res = stravastuff.get_token(code) if res[0]: @@ -762,7 +749,7 @@ def rower_process_stravacallback(request): message = "Something went wrong with the Strava authorization" messages.error(request,message) url = reverse('workouts_view') - + return HttpResponseRedirect(url) @@ -776,7 +763,7 @@ def rower_process_runkeepercallback(request): if access_token == 0: messages.error(request,"Something went wrong importing the token") url = reverse('workouts_view') - + return HttpResponseRedirect(url) @@ -789,7 +776,7 @@ def rower_process_runkeepercallback(request): successmessage = "Tokens stored. Good to go" messages.info(request,successmessage) url = reverse('workouts_view') - + return HttpResponseRedirect(url) @@ -806,7 +793,7 @@ def rower_process_sporttrackscallback(request): refresh_token = res[2] expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in) - + r = getrower(request.user) r.sporttrackstoken = access_token r.sporttrackstokenexpirydate = expirydatetime @@ -817,7 +804,7 @@ def rower_process_sporttrackscallback(request): successmessage = "Tokens stored. Good to go" messages.info(request,successmessage) url = reverse('workouts_view') - + return HttpResponseRedirect(url) @@ -833,7 +820,7 @@ def rower_process_underarmourcallback(request): expires_in = res[1] refresh_token = res[2] expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in) - + r = getrower(request.user) r.underarmourtoken = access_token r.underarmourtokenexpirydate = expirydatetime @@ -844,7 +831,7 @@ def rower_process_underarmourcallback(request): successmessage = "Tokens stored. Good to go" messages.info(request,successmessage) url = reverse('workouts_view') - + return HttpResponseRedirect(url) @@ -859,7 +846,7 @@ def rower_process_tpcallback(request): expires_in = res[1] refresh_token = res[2] expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in) - + r = getrower(request.user) r.tptoken = access_token r.tptokenexpirydate = expirydatetime @@ -870,7 +857,7 @@ def rower_process_tpcallback(request): successmessage = "Tokens stored. Good to go" messages.info(request,successmessage) url = reverse('workouts_view') - + return HttpResponseRedirect(url) @@ -892,7 +879,7 @@ def rower_process_testcallback(request): text += "\n\nRefresh Token:\n" text += refresh_token - + return HttpResponse(text) @@ -909,10 +896,10 @@ def workout_stravaimport_view(request,message="",userid=0): except NoTokenError: return HttpResponseRedirect("/rowers/me/stravaauthorize/") - + res = stravastuff.get_strava_workout_list(request.user) - + if (res.status_code != 200): if (res.status_code == 401): r = getrower(request.user) @@ -950,7 +937,7 @@ def workout_stravaimport_view(request,message="",userid=0): w.uploadedtostrava = int(stravaid) w.save() - + knownstravaids = uniqify([ w.uploadedtostrava for w in Workout.objects.filter(user=r) ]) @@ -985,7 +972,7 @@ def workout_stravaimport_view(request,message="",userid=0): r = getrower(request.user) - + return render(request,'strava_list_import.html', {'workouts':workouts, 'rower':r, @@ -1024,7 +1011,7 @@ def workout_runkeeperimport_view(request,message="",userid=0): r = item['type'] keys = ['id','distance','duration','starttime','type'] values = [i,d,ttot,s,r] - + res = dict(zip(keys,values)) workouts.append(res) @@ -1072,11 +1059,11 @@ def workout_underarmourimport_view(request,message="",userid=0): ttot = item['aggregates']['active_time_total'] except KeyError: ttot = 0 - + keys = ['id','distance','duration','starttime','type'] values = [i,d,ttot,s,r] thedict = dict(zip(keys,values)) - + workouts.append(thedict) rower = getrower(request.user) @@ -1090,7 +1077,7 @@ def workout_underarmourimport_view(request,message="",userid=0): 'name':'Concept2' }, ] - + return render(request,'underarmour_list_import.html', {'workouts':workouts, 'breadcrumbs':breadcrumbs, @@ -1115,13 +1102,13 @@ def workout_polarimport_view(request,userid=0): 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'] @@ -1156,13 +1143,13 @@ def workout_polarimport_view(request,userid=0): }) - + # The page where you select which SportTracks workout to import @login_required() def workout_sporttracksimport_view(request,message="",userid=0): - + res = sporttracksstuff.get_sporttracks_workout_list(request.user) if (res.status_code != 200): if (res.status_code == 401): @@ -1215,7 +1202,7 @@ def workout_sporttracksimport_view(request,message="",userid=0): 'name':'SportTracks' }, ] - + return render(request,'sporttracks_list_import.html', {'workouts':workouts, 'breadcrumbs':breadcrumbs, @@ -1225,7 +1212,7 @@ def workout_sporttracksimport_view(request,message="",userid=0): }) return HttpResponse(res) - + # List of workouts on Concept2 logbook. This view only used for debugging @login_required() def c2listdebug_view(request,page=1,message=""): @@ -1262,7 +1249,7 @@ def c2listdebug_view(request,page=1,message=""): res = dict(zip(keys,values)) workouts.append(res) - + return render(request, 'c2_list_import2.html', {'workouts':workouts, @@ -1288,20 +1275,20 @@ def workout_getc2workout_all(request,page=1,message=""): alldata = {} for item in res.json()['data']: alldata[item['id']] = item - + knownc2ids = uniqify([ w.uploadedtoc2 for w in Workout.objects.filter(user=r) ]) newids = [c2id for c2id in c2ids if not c2id in knownc2ids] - + for c2id in newids: workoutid = c2stuff.create_async_workout(alldata, request.user,c2id) url = reverse('workouts_view') return HttpResponseRedirect(url) - - + + # List of workouts available on Concept2 logbook - for import @login_required() def workout_c2import_view(request,page=1,userid=0,message=""): @@ -1312,7 +1299,7 @@ def workout_c2import_view(request,page=1,userid=0,message=""): messages.info(request,"You cannot import other people's workouts from Concept2") r = getrower(request.user) - + try: thetoken = c2_open(request.user) except NoTokenError: @@ -1366,7 +1353,7 @@ def workout_c2import_view(request,page=1,userid=0,message=""): ] r = getrower(request.user) - + return render(request, 'c2_list_import2.html', {'workouts':workouts, @@ -1394,14 +1381,14 @@ def workout_getimportview(request,externalid,source = 'c2'): if not res[0]: messages.error(request,res[1]) url = reverse('workouts_view') - + return HttpResponseRedirect(url) strokedata = res[1] data = res[0] - + # Now works only for C2 try: if strokedata == 0: @@ -1433,16 +1420,16 @@ def workout_getimportview(request,externalid,source = 'c2'): if timezone_str is None: timezone_str = 'UTC' - + workoutdate = startdatetime.astimezone( pytz.timezone(timezone_str) ).strftime('%Y-%m-%d') starttime = startdatetime.astimezone( pytz.timezone(timezone_str) ).strftime('%H:%M:%S') - + r = getrower(request.user) - + id, message = dataprep.create_row_df(r, distance, duration, @@ -1463,7 +1450,7 @@ def workout_getimportview(request,externalid,source = 'c2'): }) return HttpResponseRedirect(url) - + # strokedata not empty - continue id,message = importsources[source].add_workout_from_data( request.user, @@ -1504,7 +1491,7 @@ def workout_getimportview(request,externalid,source = 'c2'): rowdata.write_csv(w.csvfilename,gzip=True) dataprep.update_strokedata(w.id,rowdata.df) - + if source == 'strava': w.uploadedtostrava = externalid @@ -1534,7 +1521,7 @@ def workout_getimportview(request,externalid,source = 'c2'): }) return HttpResponseRedirect(url) - + @@ -1599,7 +1586,7 @@ def workout_getstravaworkout_next(request): alldata = {} for item in res.json(): alldata[item['id']] = item - + knownstravaids = uniqify([ w.uploadedtostrava for w in Workout.objects.filter(user=r) ]) @@ -1608,10 +1595,8 @@ def workout_getstravaworkout_next(request): theid = newids[0] workoutid = stravastuff.create_async_workout(alldata,r.user,stravaid,debug=True) - - + + url = reverse('workouts_view') return HttpResponseRedirect(url) - - diff --git a/rowers/views/planviews.py b/rowers/views/planviews.py index 05910fbc..aa158fcf 100644 --- a/rowers/views/planviews.py +++ b/rowers/views/planviews.py @@ -1697,21 +1697,16 @@ def plannedsession_edit_view(request,id=0,userid=0): }) -@login_required() +@permission_required('workout.change_workout',fn=objectgetter(Workout, 'id')) def plannedsession_detach_view(request,id=0,psid=0): r = getrequestrower(request) - try: - ps = PlannedSession.objects.get(id=psid) - except PlannedSession.DoesNotExist: - raise Http404("Planned Session does not exist") + + ps = get_object_or_404(PlannedSession,pk=psid) w = get_workout(id) - if (checkworkoutuser(request.user,w)==False): - return HttpResponseForbidden("Permission Error") - remove_workout_plannedsession(w,ps) url = reverse(plannedsession_view,kwargs={'id':psid}) diff --git a/rowers/views/statements.py b/rowers/views/statements.py index fa81fc24..ac3867da 100644 --- a/rowers/views/statements.py +++ b/rowers/views/statements.py @@ -32,10 +32,17 @@ from icalendar import Calendar, Event from functools import reduce +from rules.contrib.views import PermissionRequiredMixin + import rowers.braintreestuff as braintreestuff import rowers.payments as payments from rowers.opaque import encoder +from rowers.rower_rules import ( + ispromember,is_coach_user,is_team_member,is_rower_team_member, + is_workout_user + ) + from django.shortcuts import render from django.template.loader import render_to_string @@ -98,7 +105,7 @@ from rowers.models import ( TrainingMesoCycleForm, TrainingMicroCycleForm, RaceLogo,RowerBillingAddressForm,PaidPlan, AlertEditForm, ConditionEditForm, - PlannedSessionComment,CoachRequest,CoachOffer,checkaccessplanuser, + PlannedSessionComment,CoachRequest,CoachOffer, VideoAnalysis ) from rowers.models import ( @@ -275,6 +282,11 @@ def getfavorites(r,row): return favorites,maxfav +def get_workout_by_opaqueid(request,id,**kwargs): + pk = encoder.decode_hex(id) + return get_object_or_404(Workout,pk=pk) + + def get_workout_default_page(request,id): if request.user.is_anonymous: return reverse('workout_view',kwargs={'id':id}) @@ -310,7 +322,7 @@ def getrequestrower(request,rowerid=0,userid=0,notpermanent=False): except Rower.DoesNotExist: raise Http404("Rower doesn't exist") - if not checkaccessuser(request.user,r): + if userid != 0 and not is_coach_user(u,r): raise PermissionDenied("You have no access to this user") if notpermanent == False: @@ -343,7 +355,7 @@ def getrequestplanrower(request,rowerid=0,userid=0,notpermanent=False): except Rower.DoesNotExist: raise Http404("Rower doesn't exist") - if not checkaccessplanuser(request.user,r): + if not is_coach_user(request.user,r): raise PermissionDenied("You have no access to this user") if notpermanent == False: @@ -377,21 +389,6 @@ def get_workout(id): return w -def get_workout_permitted(user,id): - w = get_workout(id) - - if (checkworkoutuser(user,w)==False): - raise PermissionDenied("Access denied") - - return w - -def get_workout_permittedview(user,id): - w = get_workout(id) - - if (checkworkoutuserview(user,w)==False): - raise PermissionDenied("Access denied") - - return w def getvalue(data): perc = 0 @@ -774,9 +771,9 @@ def get_stored_tasks_status(request): return taskstatus -@login_required() +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) def get_thumbnails(request,id): - row = get_workout_permitted(request.user,id) + row = get_workout_by_opaqueid(request,id) r = getrower(request.user) @@ -861,27 +858,6 @@ def get_blog_posts_old(request): -@login_required() -def get_testscript(request,id): - row = get_workout_permitted(request.user,id) - r = getrower(request.user) - - object = { - "script":""" -
- -
- """, - "div":""" -
-Hoi -
-""" - } - - - return JSONResponse([object,object]) - @login_required() def session_jobs_view(request): taskstatus = get_stored_tasks_status(request) @@ -990,10 +966,6 @@ from rowers.utils import ( import rowers.datautils as datautils -from rowers.models import ( - checkworkoutuser,checkaccessuser,checkviewworkouts,checkworkoutuserview - ) - # Check if a user is a Coach member def iscoachmember(user): if not user.is_anonymous: @@ -1044,21 +1016,7 @@ def hasplannedsessions(user): return result from rowers.utils import ProcessorCustomerError -from rowers.utils import isprorower -# Check if a user is a Pro member -def ispromember(user): - if user and not user.is_anonymous: - try: - r = Rower.objects.get(user=user) - except Rower.DoesNotExist: - r = Rower(user=user) - r.save() - - result = user.is_authenticated and isprorower(r) - else: - result = False - return result # More User/Rower utils def add_defaultfavorites(r): diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index f72537f9..36332560 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -64,8 +64,10 @@ def workout_video_view_mini(request,id=''): else: mode = 'erg' + + if request.user.is_authenticated: - mayedit = checkworkoutuser(request.user,w) and isprorower(request.user.rower) + mayedit = is_workout_user(request.user,w) and is_promember(request.user) rower = request.user.rower else: mayedit = False @@ -176,7 +178,7 @@ def workout_video_view(request,id=''): mode = 'erg' if request.user.is_authenticated: - mayedit = checkworkoutuser(request.user,w) and isprorower(request.user.rower) + mayedit = is_promember(request.user) and is_workout_user(request.user,w) rower = request.user.rower else: mayedit = False @@ -271,20 +273,18 @@ def workout_video_view(request,id=''): # Create a video compared with data +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) @user_passes_test(ispromember,login_url="/rowers/paidplans/", message="This functionality requires a Pro plan or higher", redirect_field_name=None) def workout_video_create_view(request,id=0): # get workout - w = get_workout_permitted(request.user,id) + w = get_workout_by_opaqueid(request,id) if w.workouttype in mytypes.otwtypes: mode = 'water' else: mode = 'erg' - - mayedit = checkworkoutuser(request.user,w) and isprorower(request.user.rower) - # get video ID and offset if request.method == 'POST': form = VideoAnalysisCreateForm(request.POST) @@ -457,12 +457,10 @@ def workout_forcecurve_view(request,id=0,workstrokesonly=False): }) # Switch from GPS to Impeller (only for SpeedCoach 2, if impeller data) -@login_required +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) def otw_use_impeller(request,id=0): w = get_workout(id) - if (checkworkoutuser(request.user,w)==False): - raise PermissionDenied("Access denied") row = rdata(w.csvfilename) success = row.use_impellerdata() @@ -481,12 +479,10 @@ def otw_use_impeller(request,id=0): # Switch from Impeller to GPS (only for SpeedCoach 2, if impeller data) -@login_required +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) def otw_use_gps(request,id=0): w = get_workout(id) - if (checkworkoutuser(request.user,w)==False): - raise PermissionDenied("Access denied") row = rdata(w.csvfilename) success = row.use_gpsdata() @@ -748,20 +744,13 @@ def fitness_metric_view(request,mode='rower',days=42): return HttpResponse("job queued") -@login_required() +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) @user_passes_test(ispromember,login_url="/rowers/paidplans", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality. If you are already a Pro user, please log in to access this functionality", redirect_field_name=None) def workout_update_cp_view(request,id=0): row = get_workout(id) - if (checkworkoutuser(request.user,row)==False): - message = "You are not allowed to edit this workout" - messages.error(request,message) - url = reverse('workouts_view') - - return HttpResponseRedirect(url) - row.rankingpiece = True row.save() @@ -778,17 +767,10 @@ def workout_update_cp_view(request,id=0): # Reload the workout and calculate the summary from the stroke data (lapIDx) -@login_required() +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) def workout_recalcsummary_view(request,id=0): row = get_workout(id) - if (checkworkoutuser(request.user,row)==False): - message = "You are not allowed to edit this workout" - messages.error(request,message) - url = reverse('workouts_view') - - return HttpResponseRedirect(url) - filename = row.csvfilename rowdata = rdata(filename) if rowdata: @@ -1359,7 +1341,7 @@ def team_comparison_select(request, if id: firstworkout = get_workout(id) - if not checkworkoutuserview(request.user,firstworkout): + if not is_workout_user(request.user,firstworkout): raise PermissionDenied("You are not allowed to use this workout") firstworkoutquery = Workout.objects.filter(id=encoder.decode_hex(id)) @@ -1791,7 +1773,7 @@ def workouts_view(request,message='',successmessage='', # check if access is allowed - if not checkviewworkouts(request.user,r): + if not is_rower_team_member(request.user,r): raise PermissionDenied("Access denied") @@ -2104,6 +2086,7 @@ def workout_fusion_list(request,id=0,message='',successmessage='', }) # Basic view of workout +@permission_required('workout.view_workout',fn=get_workout_by_opaqueid,raise_exception=True) def workout_view(request,id=0): request.session['referer'] = absolute(request)['PATH'] @@ -2112,19 +2095,13 @@ def workout_view(request,id=0): else: rower = None - try: - row = Workout.objects.get(id=encoder.decode_hex(id)) - except Workout.DoesNotExist: - raise Http404("Workout doesn't exist") + row = get_workout_by_opaqueid(request,id) comments = WorkoutComment.objects.filter(workout=row) aantalcomments = len(comments) - if row.privacy == 'private' and not checkworkoutuser(request.user,row): - raise PermissionDenied("Access denied") - g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime") for i in g: try: @@ -2204,6 +2181,7 @@ def workout_view(request,id=0): # Resets stroke data to raw data (pace) +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) @user_passes_test(ispromember,login_url="/rowers/paidplans", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality. If you are already a Pro user, please log in to access this functionality", redirect_field_name=None) @@ -2213,13 +2191,6 @@ def workout_undo_smoothenpace_view( row = get_workout(id) r = getrower(request.user) - if (checkworkoutuser(request.user,row)==False): - message = "You are not allowed to edit this workout" - messages.error(request,message) - url = reverse('workouts_view') - - return HttpResponseRedirect(url) - filename = row.csvfilename row = rdata(filename) if row == 0: @@ -2243,6 +2214,7 @@ def workout_undo_smoothenpace_view( # Data smoothing of pace data +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) @user_passes_test(ispromember,login_url="/rowers/paidplans", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality. If you are already a Pro user, please log in to access this functionality", redirect_field_name=None) @@ -2253,12 +2225,6 @@ def workout_smoothenpace_view(request,id=0,message="",successmessage=""): r = getrower(request.user) - if (checkworkoutuser(request.user,row)==False): - message = "You are not allowed to edit this workout" - messages.error(request,message) - url = reverse('workouts_view') - - return HttpResponseRedirect(url) filename = row.csvfilename row = rdata(filename) @@ -2373,6 +2339,7 @@ def workout_crewnerd_summary_view(request,id=0,message="",successmessage=""): 'id':row.id}) # Get weather for given location and date/time +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) @user_passes_test(ispromember,login_url="/rowers/paidplans", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality", redirect_field_name=None) @@ -2382,12 +2349,6 @@ def workout_downloadwind_view(request,id=0, row = get_workout(id) f1 = row.csvfilename - if (checkworkoutuser(request.user,row)==False): - message = "You are not allowed to edit this workout" - messages.error(request,message) - url = reverse('workouts_view') - - return HttpResponseRedirect(url) # create bearing rowdata = rdata(f1) @@ -2448,6 +2409,7 @@ def workout_downloadwind_view(request,id=0, return response # Get weather for given location and date/time +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) @user_passes_test(ispromember,login_url="/rowers/paidplans",message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality",redirect_field_name=None) def workout_downloadmetar_view(request,id=0, airportcode=None, @@ -2455,12 +2417,7 @@ def workout_downloadmetar_view(request,id=0, row = get_workout(id) f1 = row.csvfilename - if (checkworkoutuser(request.user,row)==False): - message = "You are not allowed to edit this workout" - messages.error(request,message) - url = reverse('workouts_view') - return HttpResponseRedirect(url) # create bearing rowdata = rdata(f1) @@ -2523,6 +2480,7 @@ def workout_downloadmetar_view(request,id=0, # Show form to update wind data +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) @user_passes_test(ispromember,login_url="/rowers/paidplans",message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality",redirect_field_name=None) def workout_wind_view(request,id=0,message="",successmessage=""): row = get_workout(id) @@ -2543,13 +2501,6 @@ def workout_wind_view(request,id=0,message="",successmessage=""): ] - if (checkworkoutuser(request.user,row)==False): - message = "You are not allowed to edit this workout" - messages.error(request,message) - url = reverse('workouts_view') - - return HttpResponseRedirect(url) - # get data f1 = row.csvfilename @@ -2659,17 +2610,13 @@ def workout_wind_view(request,id=0,message="",successmessage=""): # Show form to update River stream data (for river dwellers) +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) @user_passes_test(ispromember,login_url="/rowers/paidplans",message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality",redirect_field_name=None) def workout_stream_view(request,id=0,message="",successmessage=""): row = get_workout(id) r = getrower(request.user) - if (checkworkoutuser(request.user,row)==False): - message = "You are not allowed to edit this workout" - messages.error(request,message) - url = reverse('workouts_view') - return HttpResponseRedirect(url) # create interactive plot @@ -2745,23 +2692,15 @@ def workout_stream_view(request,id=0,message="",successmessage=""): 'the_div':div}) # Form to set average crew weight and boat type, then run power calcs +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) @user_passes_test(ispromember, login_url="/rowers/paidplans",redirect_field_name=None) def workout_otwsetpower_view(request,id=0,message="",successmessage=""): w = get_workout(id) r = getrower(request.user) - mayedit = 0 - if request.user == w.user.user: - mayedit=1 - if checkworkoutuser(request.user,w): - mayedit=1 + mayedit = 1 - if (checkworkoutuser(request.user,w)==False): - message = "You are not allowed to edit this workout" - messages.error(request,message) - url = reverse('workouts_view') - return HttpResponseRedirect(url) if request.method == 'POST': @@ -2879,15 +2818,11 @@ def workout_otwsetpower_view(request,id=0,message="",successmessage=""): 'form':form, }) -@login_required() +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) def instroke_view(request,id=0): w = get_workout(id) r = getrower(request.user) - mayedit = 0 - if request.user == w.user.user: - mayedit=1 - if checkworkoutuser(request.user,w): - mayedit=1 + mayedit = 1 breadcrumbs = [ { @@ -2909,12 +2844,7 @@ def instroke_view(request,id=0): g = GraphImage.objects.filter(workout=w).order_by("-creationdatetime") # check if user is owner of this workout - if (checkworkoutuser(request.user,w)==False): - message = "You are not allowed to edit this workout" - messages.error(request,message) - url = reverse('workouts_view') - return HttpResponseRedirect(url) rowdata = rrdata(csvfile=w.csvfilename) try: @@ -2937,16 +2867,11 @@ def instroke_view(request,id=0): # generate instroke chart -@login_required() +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) def instroke_chart(request,id=0,metric=''): w = get_workout(id) - if (checkworkoutuser(request.user,w)==False): - message = "You are not allowed to edit this workout" - messages.error(request,message) - url = reverse('workouts_view') - return HttpResponseRedirect(url) rowdata = rrdata(csvfile=w.csvfilename) instrokemetrics = rowdata.get_instroke_columns() @@ -2995,14 +2920,12 @@ def instroke_chart(request,id=0,metric=''): # data explorer -@login_required() +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) def workout_data_view(request, id=0): r = getrower(request.user) w = get_workout(id) - if not checkworkoutuser(request.user,w): - raise PermissionDenied('Access Denied') breadcrumbs = [ { @@ -3100,7 +3023,7 @@ def workout_data_view(request, id=0): # Stats page -@login_required() +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) def workout_stats_view(request,id=0,message="",successmessage=""): r = getrower(request.user) @@ -3109,7 +3032,7 @@ def workout_stats_view(request,id=0,message="",successmessage=""): mayedit = 0 if request.user == w.user.user: mayedit=1 - if checkworkoutuser(request.user,w): + if is_workout_user(request.user,w): mayedit=1 breadcrumbs = [ @@ -3135,8 +3058,7 @@ def workout_stats_view(request,id=0,message="",successmessage=""): # prepare data frame datadf,row = dataprep.getrowdata_db(id=encoder.decode_hex(id)) - if (checkworkoutuserview(request.user,row)==False): - raise PermissionDenied('Access Denied') + datadf = dataprep.clean_df_stats(datadf,workstrokesonly=workstrokesonly, ignoreadvanced=False) @@ -3377,11 +3299,12 @@ def workout_workflow_config2_view(request,userid=0): # Workflow View @login_required() +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) def workout_workflow_view(request,id): request.session['referer'] = absolute(request)['PATH'] request.session['lastworkout'] = id request.session[translation.LANGUAGE_SESSION_KEY] = USER_LANGUAGE - row = get_workout_permittedview(request.user,id) + row = get_workout_by_opaqueid(request,id) r = getrower(request.user) result = request.user.is_authenticated and ispromember(request.user) @@ -3463,7 +3386,7 @@ def workout_workflow_view(request,id): }) # The famous flex chart -@login_required() +@permission_required('workout.view_workout',fn=get_workout_by_opaqueid,raise_exception=True) def workout_flexchart3_view(request,*args,**kwargs): try: @@ -3487,12 +3410,12 @@ def workout_flexchart3_view(request,*args,**kwargs): mayedit=0 if not request.user.is_anonymous: r = getrower(request.user) - result = request.user.is_authenticated and ispromember(request.user) + result = ispromember(request.user) if result: promember=1 if request.user == row.user.user: mayedit=1 - if checkworkoutuser(request.user,row): + if is_workout_user(request.user,row): mayedit=1 workouttype = 'ote' @@ -3977,7 +3900,7 @@ def workout_comment_view(request,id=0): # The basic edit page -@login_required() +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) def workout_edit_view(request,id=0,message="",successmessage=""): request.session[translation.LANGUAGE_SESSION_KEY] = USER_LANGUAGE request.session['referer'] = absolute(request)['PATH'] @@ -3986,8 +3909,7 @@ def workout_edit_view(request,id=0,message="",successmessage=""): row = get_workout(id) - if (checkworkoutuser(request.user,row)==False): - raise PermissionDenied("Access denied") + if request.user.rower.rowerplan == 'basic' and 'speedcoach2' in row.workoutsource: data = getsmallrowdata_db(['wash'],ids=[encoder.decode_hex(id)]) @@ -4293,7 +4215,7 @@ def workout_map_view(request,id=0): # Image upload -@login_required() +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) def workout_uploadimage_view(request,id): is_ajax = False if request.is_ajax(): @@ -4319,8 +4241,7 @@ def workout_uploadimage_view(request,id): ] - if not checkworkoutuser(request.user,w): - raise PermissionDenied("You are not allowed to edit this workout") + images = GraphImage.objects.filter(workout=w) @@ -4398,7 +4319,7 @@ def workout_uploadimage_view(request,id): # Generic chart creation -@login_required() +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) def workout_add_chart_view(request,id,plotnr=1): w = get_workout(id) @@ -4406,26 +4327,24 @@ def workout_add_chart_view(request,id,plotnr=1): plotnr = int(plotnr) - if (checkworkoutuser(request.user,w)==False): - raise PermissionDenied("You are not allowed add plots to this workout") + + f1 = w.csvfilename[6:-4] + timestr = strftime("%Y%m%d-%H%M%S") + imagename = f1+timestr+'.png' + u = w.user.user + r = getrower(u) + title = w.name + res,jobid = uploads.make_plot( + r,w,f1,w.csvfilename,'timeplot',title,plotnr=plotnr, + imagename=imagename + ) + if res == 0: + messages.error(request,jobid) else: - f1 = w.csvfilename[6:-4] - timestr = strftime("%Y%m%d-%H%M%S") - imagename = f1+timestr+'.png' - u = w.user.user - r = getrower(u) - title = w.name - res,jobid = uploads.make_plot( - r,w,f1,w.csvfilename,'timeplot',title,plotnr=plotnr, - imagename=imagename - ) - if res == 0: - messages.error(request,jobid) - else: - try: - request.session['async_tasks'] += [(jobid,'make_plot')] - except KeyError: - request.session['async_tasks'] = [(jobid,'make_plot')] + try: + request.session['async_tasks'] += [(jobid,'make_plot')] + except KeyError: + request.session['async_tasks'] = [(jobid,'make_plot')] url = reverse(r.defaultlandingpage,kwargs={'id':encoder.encode_hex(w.id)}) @@ -4437,12 +4356,13 @@ def workout_add_chart_view(request,id,plotnr=1): @login_required +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) def workout_toggle_ranking(request,id=0): is_ajax = False if request.is_ajax(): is_ajax = True - row = get_workout_permitted(request.user,id) + row = get_workout_by_opaqueid(request,id) row.rankingpiece = not row.rankingpiece row.save() @@ -5237,9 +5157,9 @@ def graph_show_view(request,id): raise Http404("This workout doesn't exist") # Restore original stroke data and summary -@login_required() +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) def workout_summary_restore_view(request,id,message="",successmessage=""): - row = get_workout_permitted(request.user,id) + row = get_workout_by_opaqueid(request,id) s = "" # still here - this is a workout we may edit @@ -5287,11 +5207,12 @@ def workout_summary_restore_view(request,id,message="",successmessage=""): return HttpResponseRedirect(url) # Split a workout +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) @user_passes_test(ispromember,login_url="/rowers/paidplans", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality", redirect_field_name=None) def workout_split_view(request,id=0): - row = get_workout_permitted(request.user,id) + row = get_workout_by_opaqueid(request,id) r = row.user @@ -5398,8 +5319,8 @@ def workout_fusion_view(request,id1=0,id2=1): w1 = Workout.objects.get(id=id1) w2 = Workout.objects.get(id=id2) r = w1.user - if (checkworkoutuser(request.user,w1)==False) or \ - (checkworkoutuser(request.user,w2)==False): + if (is_workout_user(request.user,w1)==False) or \ + (is_workout_user(request.user,w2)==False): raise PermissionDenied("You are not allowed to use these workouts") except Workout.DoesNotExist: raise Http404("One of the workouts doesn't exist") @@ -5475,10 +5396,10 @@ def workout_fusion_view(request,id1=0,id2=1): # Edit the splits/summary -@login_required() +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) def workout_summary_edit_view(request,id,message="",successmessage="" ): - row = get_workout_permitted(request.user,id) + row = get_workout_by_opaqueid(request,id) r = getrower(request.user) breadcrumbs = [ { @@ -6024,10 +5945,11 @@ def workout_code_delete_view(request,id=0): return HttpResponseRedirect(url) -class WorkoutDelete(DeleteView): +class WorkoutDelete(PermissionRequiredMixin,DeleteView): login_required = True model = Workout template_name = 'workout_delete_confirm.html' + permission_required = 'workout.change_workout' # extra parameters def get_context_data(self, **kwargs): @@ -6082,7 +6004,6 @@ class WorkoutDelete(DeleteView): except Workout.DoesNotExist: raise Http404("One of the workouts doesn't exist") # obj = super(WorkoutDelete, self).get_object(*args, **kwargs) - if not checkaccessuser(self.request.user,obj.user): - raise PermissionDenied('You are not allowed to delete this workout') + return obj