From 7f6112462fc0d9a888e4b93f44ffe35003c6cb6e Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 23 Dec 2019 21:08:05 +0100
Subject: [PATCH 01/80] helper files
---
rowers/rowing_workout_metrics_pb2.py | 203 ++++++++++++++++++++++
rowers/rowing_workout_metrics_pb2_grpc.py | 46 +++++
2 files changed, 249 insertions(+)
create mode 100644 rowers/rowing_workout_metrics_pb2.py
create mode 100644 rowers/rowing_workout_metrics_pb2_grpc.py
diff --git a/rowers/rowing_workout_metrics_pb2.py b/rowers/rowing_workout_metrics_pb2.py
new file mode 100644
index 00000000..53d92d53
--- /dev/null
+++ b/rowers/rowing_workout_metrics_pb2.py
@@ -0,0 +1,203 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: rowing-workout-metrics.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+ name='rowing-workout-metrics.proto',
+ package='rowing_workout_metrics',
+ syntax='proto3',
+ serialized_options=None,
+ serialized_pb=_b('\n\x1crowing-workout-metrics.proto\x12\x16rowing_workout_metrics\"p\n\x15WorkoutMetricsRequest\x12\x10\n\x08\x66ilename\x18\x01 \x01(\t\x12\x0b\n\x03sex\x18\x02 \x01(\t\x12\x0b\n\x03\x66tp\x18\x03 \x01(\x01\x12\r\n\x05hrftp\x18\x04 \x01(\x01\x12\r\n\x05hrmax\x18\x05 \x01(\x01\x12\r\n\x05hrmin\x18\x06 \x01(\x01\"p\n\x16WorkoutMetricsResponse\x12\x0b\n\x03tss\x18\x01 \x01(\x01\x12\r\n\x05normp\x18\x02 \x01(\x01\x12\r\n\x05trimp\x18\x03 \x01(\x01\x12\r\n\x05hrtss\x18\x04 \x01(\x01\x12\r\n\x05normv\x18\x05 \x01(\x01\x12\r\n\x05normw\x18\x06 \x01(\x01\x32w\n\x07Metrics\x12l\n\x0b\x43\x61lcMetrics\x12-.rowing_workout_metrics.WorkoutMetricsRequest\x1a..rowing_workout_metrics.WorkoutMetricsResponseb\x06proto3')
+)
+
+
+
+
+_WORKOUTMETRICSREQUEST = _descriptor.Descriptor(
+ name='WorkoutMetricsRequest',
+ full_name='rowing_workout_metrics.WorkoutMetricsRequest',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='filename', full_name='rowing_workout_metrics.WorkoutMetricsRequest.filename', index=0,
+ number=1, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='sex', full_name='rowing_workout_metrics.WorkoutMetricsRequest.sex', index=1,
+ number=2, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='ftp', full_name='rowing_workout_metrics.WorkoutMetricsRequest.ftp', index=2,
+ number=3, type=1, cpp_type=5, label=1,
+ has_default_value=False, default_value=float(0),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='hrftp', full_name='rowing_workout_metrics.WorkoutMetricsRequest.hrftp', index=3,
+ number=4, type=1, cpp_type=5, label=1,
+ has_default_value=False, default_value=float(0),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='hrmax', full_name='rowing_workout_metrics.WorkoutMetricsRequest.hrmax', index=4,
+ number=5, type=1, cpp_type=5, label=1,
+ has_default_value=False, default_value=float(0),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='hrmin', full_name='rowing_workout_metrics.WorkoutMetricsRequest.hrmin', index=5,
+ number=6, type=1, cpp_type=5, label=1,
+ has_default_value=False, default_value=float(0),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=56,
+ serialized_end=168,
+)
+
+
+_WORKOUTMETRICSRESPONSE = _descriptor.Descriptor(
+ name='WorkoutMetricsResponse',
+ full_name='rowing_workout_metrics.WorkoutMetricsResponse',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='tss', full_name='rowing_workout_metrics.WorkoutMetricsResponse.tss', index=0,
+ number=1, type=1, cpp_type=5, label=1,
+ has_default_value=False, default_value=float(0),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='normp', full_name='rowing_workout_metrics.WorkoutMetricsResponse.normp', index=1,
+ number=2, type=1, cpp_type=5, label=1,
+ has_default_value=False, default_value=float(0),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='trimp', full_name='rowing_workout_metrics.WorkoutMetricsResponse.trimp', index=2,
+ number=3, type=1, cpp_type=5, label=1,
+ has_default_value=False, default_value=float(0),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='hrtss', full_name='rowing_workout_metrics.WorkoutMetricsResponse.hrtss', index=3,
+ number=4, type=1, cpp_type=5, label=1,
+ has_default_value=False, default_value=float(0),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='normv', full_name='rowing_workout_metrics.WorkoutMetricsResponse.normv', index=4,
+ number=5, type=1, cpp_type=5, label=1,
+ has_default_value=False, default_value=float(0),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='normw', full_name='rowing_workout_metrics.WorkoutMetricsResponse.normw', index=5,
+ number=6, type=1, cpp_type=5, label=1,
+ has_default_value=False, default_value=float(0),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=170,
+ serialized_end=282,
+)
+
+DESCRIPTOR.message_types_by_name['WorkoutMetricsRequest'] = _WORKOUTMETRICSREQUEST
+DESCRIPTOR.message_types_by_name['WorkoutMetricsResponse'] = _WORKOUTMETRICSRESPONSE
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+WorkoutMetricsRequest = _reflection.GeneratedProtocolMessageType('WorkoutMetricsRequest', (_message.Message,), {
+ 'DESCRIPTOR' : _WORKOUTMETRICSREQUEST,
+ '__module__' : 'rowing_workout_metrics_pb2'
+ # @@protoc_insertion_point(class_scope:rowing_workout_metrics.WorkoutMetricsRequest)
+ })
+_sym_db.RegisterMessage(WorkoutMetricsRequest)
+
+WorkoutMetricsResponse = _reflection.GeneratedProtocolMessageType('WorkoutMetricsResponse', (_message.Message,), {
+ 'DESCRIPTOR' : _WORKOUTMETRICSRESPONSE,
+ '__module__' : 'rowing_workout_metrics_pb2'
+ # @@protoc_insertion_point(class_scope:rowing_workout_metrics.WorkoutMetricsResponse)
+ })
+_sym_db.RegisterMessage(WorkoutMetricsResponse)
+
+
+
+_METRICS = _descriptor.ServiceDescriptor(
+ name='Metrics',
+ full_name='rowing_workout_metrics.Metrics',
+ file=DESCRIPTOR,
+ index=0,
+ serialized_options=None,
+ serialized_start=284,
+ serialized_end=403,
+ methods=[
+ _descriptor.MethodDescriptor(
+ name='CalcMetrics',
+ full_name='rowing_workout_metrics.Metrics.CalcMetrics',
+ index=0,
+ containing_service=None,
+ input_type=_WORKOUTMETRICSREQUEST,
+ output_type=_WORKOUTMETRICSRESPONSE,
+ serialized_options=None,
+ ),
+])
+_sym_db.RegisterServiceDescriptor(_METRICS)
+
+DESCRIPTOR.services_by_name['Metrics'] = _METRICS
+
+# @@protoc_insertion_point(module_scope)
diff --git a/rowers/rowing_workout_metrics_pb2_grpc.py b/rowers/rowing_workout_metrics_pb2_grpc.py
new file mode 100644
index 00000000..099bf538
--- /dev/null
+++ b/rowers/rowing_workout_metrics_pb2_grpc.py
@@ -0,0 +1,46 @@
+# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
+import grpc
+
+import rowing_workout_metrics_pb2 as rowing__workout__metrics__pb2
+
+
+class MetricsStub(object):
+ """OTW-metrics service definition
+ """
+
+ def __init__(self, channel):
+ """Constructor.
+
+ Args:
+ channel: A grpc.Channel.
+ """
+ self.CalcMetrics = channel.unary_unary(
+ '/rowing_workout_metrics.Metrics/CalcMetrics',
+ request_serializer=rowing__workout__metrics__pb2.WorkoutMetricsRequest.SerializeToString,
+ response_deserializer=rowing__workout__metrics__pb2.WorkoutMetricsResponse.FromString,
+ )
+
+
+class MetricsServicer(object):
+ """OTW-metrics service definition
+ """
+
+ def CalcMetrics(self, request, context):
+ # missing associated documentation comment in .proto file
+ pass
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+ context.set_details('Method not implemented!')
+ raise NotImplementedError('Method not implemented!')
+
+
+def add_MetricsServicer_to_server(servicer, server):
+ rpc_method_handlers = {
+ 'CalcMetrics': grpc.unary_unary_rpc_method_handler(
+ servicer.CalcMetrics,
+ request_deserializer=rowing__workout__metrics__pb2.WorkoutMetricsRequest.FromString,
+ response_serializer=rowing__workout__metrics__pb2.WorkoutMetricsResponse.SerializeToString,
+ ),
+ }
+ generic_handler = grpc.method_handlers_generic_handler(
+ 'rowing_workout_metrics.Metrics', rpc_method_handlers)
+ server.add_generic_rpc_handlers((generic_handler,))
From 2388edcf6d43308ad0cc2e93d313498d78a7cafe Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 23 Dec 2019 21:25:00 +0100
Subject: [PATCH 02/80] async calctrimp task using microservice
---
rowers/rowing_workout_metrics_pb2_grpc.py | 2 +-
rowers/tasks.py | 128 ++++++----------------
2 files changed, 34 insertions(+), 96 deletions(-)
diff --git a/rowers/rowing_workout_metrics_pb2_grpc.py b/rowers/rowing_workout_metrics_pb2_grpc.py
index 099bf538..b58e9986 100644
--- a/rowers/rowing_workout_metrics_pb2_grpc.py
+++ b/rowers/rowing_workout_metrics_pb2_grpc.py
@@ -1,7 +1,7 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
import grpc
-import rowing_workout_metrics_pb2 as rowing__workout__metrics__pb2
+import rowers.rowing_workout_metrics_pb2 as rowing__workout__metrics__pb2
class MetricsStub(object):
diff --git a/rowers/tasks.py b/rowers/tasks.py
index 8c455a4e..727c3b41 100644
--- a/rowers/tasks.py
+++ b/rowers/tasks.py
@@ -38,6 +38,8 @@ from matplotlib import path
import grpc
import rowers.otw_power_calculator_pb2 as calculator_pb2
import rowers.otw_power_calculator_pb2_grpc as calculator_pb2_grpc
+import rowers.rowing_workout_metrics_pb2 as metrics_pb2
+import rowers.rowing_workout_metrics_pb2_grpc as metrics_pb2_grpc
from rowsandall_app.settings import SITE_URL
from rowsandall_app.settings_dev import SITE_URL as SITE_URL_DEV
@@ -609,105 +611,41 @@ def handle_calctrimp(id,
else:
engine = create_engine(database_url, echo=False)
- try:
- rowdata = rdata(csvfile=csvfilename)
- except IOError:
+ tss = 0
+ normp = 0
+ trimp = 0
+ hrtss = 0
+ normv = 0
+ normw = 0
+
+ with grpc.insecure_channel(
+ target='localhost:50052',
+ options=[('grpc.lb_policy_name', 'pick_first'),
+ ('grpc.enable_retries', 0), ('grpc.keepalive_timeout_ms',
+ 10000)]
+ ) as channel:
try:
- rowdata = rdata(csvfile=csvfilename + '.csv')
- except IOError:
- try:
- rowdata = rdata(csvfile=csvfilename + '.gz')
- except IOError:
- return 0
+ grpc.channel_ready_future(channel).result(timeout=10)
+ except grpc.FutureTimeoutError:
+ return 0
- df = rowdata.df
-
- try:
- df['deltat'] = df[' ElapsedTime (sec)'].diff().abs()
- except KeyError:
- return 0
-
- df2 = df.copy()
-
- df2['time'] = df2[' ElapsedTime (sec)']
- df2['time'] = df2['time'].apply(
- lambda x:safetimedelta(x)
+ stub = metrics_pb2_grpc.MetricsStub(channel)
+ req = metrics_pb2.WorkoutMetricsRequest(
+ filename = csvfilename,
+ ftp=ftp,
+ sex=sex,
+ hrftp=hrftp,
+ hrmax=hrmax,
+ hrmin=hrmin,
)
+ response = stub.CalcMetrics(req,timeout=60)
- duration = df['TimeStamp (sec)'].max()-df['TimeStamp (sec)'].min()
- df2 = df2.resample('1s',on='time').mean()
- df2.fillna(method='ffill',inplace=True)
- df2 = df2.rolling(30).mean()
+ tss = response.tss
+ normp = response.normp
+ trimp = response.trimp
+ normv = response.normv
+ normw = response.normw
- df2[' Power (watts)'] = df2[' Power (watts)'].abs()
-
- df2['pwr4'] = df2[' Power (watts)']**(4.0)
- # pwr4mean = wavg(df,'pwr4','deltat')
- pwr4mean = df2['pwr4'].mean()
- pwrmean = df2[' Power (watts)'].mean()
- # pwrmean = wavg(df,' Power (watts)','deltat')
- if pwr4mean > 0:
- normp = (pwr4mean)**(0.25)
- else:
- normp = pwrmean
-
- intensityfactor = normp/float(ftp)
- tss = 100.*((duration*normp*intensityfactor)/(3600.*ftp))
-
-
- if sex == 'male':
- f = 1.92
- else:
- f = 1.67
-
- dt = df['TimeStamp (sec)'].diff()/60.
-
- hrr = (df[' HRCur (bpm)']-hrmin)/(hrmax-hrmin)
- hrrftp = (hrftp-hrmin)/float(hrmax-hrmin)
- trimp1hr = 60.*hrrftp*0.64*np.exp(f*hrrftp)
-
- trimpdata = dt*hrr*0.64*np.exp(f*hrr)
- trimp = trimpdata.sum()
-
- hrtss = 100.*trimp/trimp1hr
-
- pp = 8.0
-
- df['v4'] = df[' AverageBoatSpeed (m/s)']**(pp)
- v4mean = wavg(df,'v4','deltat')
- normv = v4mean**(1./pp)
-
- try:
- df['w4'] = df['driveenergy']**(pp)
- w4mean = wavg(df,'w4','deltat')
- normw = w4mean**(1./pp)
- except KeyError:
- normw = 0
-
-
- if not np.isfinite(normv):
- normv = 500./120.
-
- if not np.isfinite(normw):
- normw = 0
-
- try:
- dum = int(tss)
- except ValueError:
- tss = 0
-
- try:
- dum = int(normp)
- except ValueError:
- normp = 0
- try:
- dump = int(trimp)
- except ValueError:
- trimp = 0
- try:
- dump = int(hrtss)
- except ValueError:
- hrtss = 0
query = 'UPDATE rowers_workout SET rscore = {tss}, normp = {normp}, trimp={trimp}, hrtss={hrtss}, normv={normv}, normw={normw} WHERE id={id}'.format(
tss = int(tss),
@@ -1603,7 +1541,7 @@ def handle_otwsetpower(self,f1, boattype, weightvalue,
try:
rowdata = rdata(csvfile)
except IOError:
- rowdata = rdata(csvfile)
+ rowdata = rdata(csvfile)
else:
boatfile = {
From 9b653d4d1be8d90baf9d59b527268a420bf9f5c1 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 23 Dec 2019 21:30:26 +0100
Subject: [PATCH 03/80] tasks updated
---
rowers/tasks.py | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/rowers/tasks.py b/rowers/tasks.py
index 727c3b41..259a7acd 100644
--- a/rowers/tasks.py
+++ b/rowers/tasks.py
@@ -618,6 +618,16 @@ def handle_calctrimp(id,
normv = 0
normw = 0
+ # check what the real file name is
+ if os.path.exists(csvfilename):
+ csvfile = csvfilename
+ elif os.path.exists(csvfilename+'.csv'):
+ csvfile = csvfilename+'.csv'
+ elif os.path.exists(csvfilename+'.gz'):
+ csvfile = csvfilename+'.gz'
+
+ csvfile = os.path.abspath(csvfile)
+
with grpc.insecure_channel(
target='localhost:50052',
options=[('grpc.lb_policy_name', 'pick_first'),
@@ -631,7 +641,7 @@ def handle_calctrimp(id,
stub = metrics_pb2_grpc.MetricsStub(channel)
req = metrics_pb2.WorkoutMetricsRequest(
- filename = csvfilename,
+ filename = csvfile,
ftp=ftp,
sex=sex,
hrftp=hrftp,
@@ -645,6 +655,7 @@ def handle_calctrimp(id,
trimp = response.trimp
normv = response.normv
normw = response.normw
+ hrtss = response.hrtss
query = 'UPDATE rowers_workout SET rscore = {tss}, normp = {normp}, trimp={trimp}, hrtss={hrtss}, normv={normv}, normw={normw} WHERE id={id}'.format(
From 79afd29b190b8110b86e601b6e51a8be15758917 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Fri, 10 Jan 2020 08:06:18 +0100
Subject: [PATCH 04/80] rules first attempt
---
rowers/models.py | 43 ++++++++++++++++++-
rowers/permissions.py | 2 +-
rowers/teams.py | 85 +++++++++++++++++++-------------------
rowers/views/statements.py | 2 +-
rowers/views/teamviews.py | 74 +++++++++++++++++----------------
rowsandall_app/settings.py | 2 +
6 files changed, 126 insertions(+), 82 deletions(-)
diff --git a/rowers/models.py b/rowers/models.py
index 6d9b7ac9..88b06f95 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -39,6 +39,9 @@ import pandas as pd
from dateutil import parser
import datetime
+import rules
+from rules.contrib.models import RulesModel
+
from rowers.rows import validate_file_extension
from collections import OrderedDict
from timezonefinder import TimezoneFinder
@@ -53,6 +56,37 @@ from rowsandall_app.settings import (
TWEET_CONSUMER_SECRET,
)
+# PERMISSIONS
+
+@rules.predicate
+def is_team_manager(user,team):
+ print('aap')
+ return team.manager == user
+
+@rules.predicate
+def is_team_member(user,team):
+ members = Rower.objects.filter(team__in=[team])
+ return user in [member.user for member in members]
+
+@rules.predicate
+def is_coach(user):
+ r = Rower.objects.get(user=user)
+ return r.rowerplan in ['coach','freecoach']
+
+@rules.predicate
+def can_view_team(user,team):
+ # user based
+ r = Rower.objects.get(user=user)
+ if r.rowerplan == 'basic' and team.manager.rower.rowerplan != 'coach':
+ return False
+ # team is public
+ if team.private == 'open':
+ return True
+ # team is private
+ return is_team_member(user,team) | is_team_manager(user,team)
+
+# END PERMISSIONS
+
tweetapi = twitter.Api(consumer_key=TWEET_CONSUMER_KEY,
consumer_secret=TWEET_CONSUMER_SECRET,
access_token_key=TWEET_ACCESS_TOKEN_KEY,
@@ -331,7 +365,7 @@ def is_not_basic(user):
# For future Team functionality
@python_2_unicode_compatible
-class Team(models.Model):
+class Team(RulesModel):
choices = (
('private','private'),
('open','open'),
@@ -350,6 +384,13 @@ class Team(models.Model):
viewing = models.CharField(max_length=30,choices=viewchoices,default='allmembers',verbose_name='Sharing Behavior')
+ class Meta:
+ rules_permissions = {
+ "add": is_coach,
+ "change": is_team_manager,
+ "delete": is_team_manager,
+ "view": can_view_team,
+ }
def __str__(self):
return self.name
diff --git a/rowers/permissions.py b/rowers/permissions.py
index 2d32add8..4950f910 100644
--- a/rowers/permissions.py
+++ b/rowers/permissions.py
@@ -22,7 +22,7 @@ class IsOwnerOrReadOnly(permissions.BasePermission):
return obj.user == request.user
class IsOwnerOrNot(permissions.BasePermission):
-
+
def has_object_permission(self, request, view, obj):
r = Rower.objects.get(user=request.user)
return (obj.user == r)
diff --git a/rowers/teams.py b/rowers/teams.py
index 4c8ee0a4..2556cf21 100644
--- a/rowers/teams.py
+++ b/rowers/teams.py
@@ -23,7 +23,7 @@ queuehigh = django_rq.get_queue('low')
from rowers.models import (
Rower, Workout, Team, TeamInvite,User,TeamRequest, CoachRequest, CoachOffer,
- CoachingGroup
+ CoachingGroup,is_team_manager,is_team_member,is_coach
)
from rowers.tasks import (
@@ -53,14 +53,15 @@ def handle_remove_workouts_team(ws,t):
return 1
def handle_add_workouts_team(ws,t):
-
+
for w in ws:
w.team.add(t)
return 1
def update_team(t,name,manager,private,notes,viewing):
- if t.manager != manager:
+
+ if not is_team_manager(t,manager):
return (0,'You are not the manager of this team')
try:
t.name = name
@@ -82,7 +83,7 @@ def create_team(name,manager,private='open',notes='',viewing='allmembers'):
ts = Team.objects.filter(manager=manager)
if len(ts)>=1:
return (0,'You need to upgrade to the Coach plan to have more than one team')
-
+
try:
t = Team(name=name,manager=manager,notes=notes,
private=private,viewing=viewing)
@@ -116,7 +117,7 @@ def remove_team(id):
def add_coach(coach,rower):
# get coaching group
-
+
coachgroup = coach.mycoachgroup
if coachgroup is None:
coachgroup = CoachingGroup(name=coach.user.first_name)
@@ -126,7 +127,7 @@ def add_coach(coach,rower):
if get_coach_club_size(coach) invitation.issuedate+timedelta(days=inviteduration):
revoke_invite(invitation.team.manager,invitation.id)
return (0,'The invitation has expired')
-
+
t = invitation.team
result, comment = add_member(t.id,r)
if not result:
@@ -698,7 +699,7 @@ def process_coachoffer_code(user,code):
return result
else:
send_coachoffer_accepted_email(rekwest)
-
+
rekwest.delete()
return result
@@ -732,7 +733,7 @@ def send_coachrequest_accepted_email(rekwest):
res = myqueue(queuehigh,
handle_sendemail_coachrequest_accepted,
email,coachname,name)
-
+
def send_coachoffer_accepted_email(rekwest):
coachname = rekwest.coach.user.first_name + " " + rekwest.coach.user.last_name
@@ -743,5 +744,3 @@ def send_coachoffer_accepted_email(rekwest):
res = myqueue(queuehigh,
handle_sendemail_coachoffer_accepted,
coachemail,coachname,name)
-
-
diff --git a/rowers/views/statements.py b/rowers/views/statements.py
index aab50741..a14d41a4 100644
--- a/rowers/views/statements.py
+++ b/rowers/views/statements.py
@@ -240,7 +240,7 @@ from rq import Queue,cancel_job
from django.core.cache import cache
from django_mailbox.models import Message,Mailbox,MessageAttachment
-
+from rules.contrib.views import permission_required, objectgetter
# Utility to get stroke data in a JSON response
class JSONResponse(HttpResponse):
diff --git a/rowers/views/teamviews.py b/rowers/views/teamviews.py
index 36c6b85b..c767f0d0 100644
--- a/rowers/views/teamviews.py
+++ b/rowers/views/teamviews.py
@@ -6,7 +6,8 @@ from __future__ import unicode_literals
from rowers.views.statements import *
-@login_required()
+#@login_required()
+@permission_required('teams.view_team',fn=objectgetter(Team,'id'))
def team_view(request,id=0,userid=0):
ismember = 0
hasrequested = 0
@@ -16,25 +17,25 @@ def team_view(request,id=0,userid=0):
teams.remove_expired_invites()
-
+
try:
t = Team.objects.get(id=id)
except Team.DoesNotExist:
raise Http404("Team doesn't exist")
- if r.rowerplan == 'basic' and t.manager.rower.rowerplan != 'coach':
- raise PermissionDenied("You need to be on a Paid Plan to see or join this team")
+ #if r.rowerplan == 'basic' and t.manager.rower.rowerplan != 'coach':
+ # raise PermissionDenied("You need to be on a Paid Plan to see or join this team")
+
-
q = User.objects.filter(rower__isnull=False,rower__team__in=myteams).distinct().exclude(rower__team__name=t.name)
mygroups = [request.user.rower.mycoachgroup]
q2 = User.objects.filter(rower__isnull=False,rower__coachinggroups__in=mygroups).distinct().exclude(rower__team__name=t.name)
-
+
q = q | q2
-
+
if request.method == 'POST' and request.user == t.manager and 'email' in request.POST:
inviteform = TeamInviteForm(request.POST)
-
+
inviteform.fields['user'].queryset = q
if inviteform.is_valid():
cd = inviteform.cleaned_data
@@ -59,7 +60,7 @@ def team_view(request,id=0,userid=0):
teams.send_team_message(t,message)
messages.info(request,'Message was sent to all team members')
groupmessageform = TeamMessageForm()
-
+
elif request.user == t.manager:
inviteform = TeamInviteForm()
inviteform.fields['user'].queryset = q
@@ -68,7 +69,7 @@ def team_view(request,id=0,userid=0):
inviteform = ''
groupmessageform = ''
-
+
members = Rower.objects.filter(team=t).order_by('user__last_name','user__first_name')
thisteammyrequests = TeamRequest.objects.filter(team=t,user=request.user)
if len(thisteammyrequests):
@@ -112,7 +113,7 @@ def team_leaveconfirm_view(request,id=0):
raise Http404("Team doesn't exist")
myteams, memberteams, otherteams = get_teams(request)
-
+
breadcrumbs = [
{
'url':reverse(rower_teams_view),
@@ -144,7 +145,7 @@ def rower_calcdps_view(request):
ws = [(w.id,w.csvfilename) for w in Workout.objects.filter(user=r)]
res = myqueue(queue,handle_updatedps,r.user.email,ws,debug=False,
- emailbounced=r.emailbounced)
+ emailbounced=r.emailbounced)
messages.info(request,"Your workouts are being updated in the background. You will receive email when this is done.")
@@ -155,7 +156,7 @@ def rower_calcdps_view(request):
def team_leave_view(request,id=0):
r = getrower(request.user)
teams.remove_member(id,r)
-
+
url = reverse(rower_teams_view)
response = HttpResponseRedirect(url)
return response
@@ -164,7 +165,7 @@ from rowers.forms import TeamInviteCodeForm
def get_teams(request):
r = Rower.objects.get(user=request.user)
-
+
myteams = Team.objects.filter(
manager=request.user).order_by('name')
memberteams = Team.objects.filter(
@@ -191,7 +192,7 @@ def rower_teams_view(request):
messages.error(request,text)
else:
form = TeamInviteCodeForm()
-
+
r = getrower(request.user)
ts = Team.objects.filter(rower=r)
@@ -208,7 +209,7 @@ def rower_teams_view(request):
mycoachoffers = CoachOffer.objects.filter(coach=r)
# user is invited (by coach)
coachoffers = CoachOffer.objects.filter(user=r.user)
-
+
# user requests a coach
mycoachrequests = CoachRequest.objects.filter(user=r.user)
# user is requested to coach
@@ -230,7 +231,7 @@ def rower_teams_view(request):
potentialcoaches = [c for c in potentialcoaches if c.rower not in invitedcoaches+coaches]
potentialcoaches = [c for c in potentialcoaches if teams.get_coach_club_size(c.rower)
Date: Sat, 11 Jan 2020 15:23:26 +0100
Subject: [PATCH 05/80] now working with permissions defined in models.py
---
requirements.txt | 3 ++-
rowers/models.py | 19 ++++++++++---------
rowers/teams.py | 10 +++++-----
rowers/tests/test_permissions.py | 2 +-
rowers/urls.py | 4 ++--
rowers/views/statements.py | 1 +
rowers/views/teamviews.py | 32 +++++++++++++++++---------------
7 files changed, 38 insertions(+), 33 deletions(-)
diff --git a/requirements.txt b/requirements.txt
index 58eb6402..05616a35 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -173,9 +173,10 @@ ratelim==0.1.6
redis==3.2.1
requests==2.21.0
requests-oauthlib==1.2.0
-rowingdata==2.5.7
+rowingdata==2.5.9
rowingphysics==0.5.0
rq==0.13.0
+rules==2.1
scipy==1.2.1
SecretStorage==3.1.1
Send2Trash==1.5.0
diff --git a/rowers/models.py b/rowers/models.py
index 88b06f95..5edf81e7 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -60,7 +60,6 @@ from rowsandall_app.settings import (
@rules.predicate
def is_team_manager(user,team):
- print('aap')
return team.manager == user
@rules.predicate
@@ -85,6 +84,12 @@ def can_view_team(user,team):
# team is private
return is_team_member(user,team) | is_team_manager(user,team)
+# For Team functionality
+rules.add_perm('teams.view_team',can_view_team)
+rules.add_perm('teams.add_team',is_coach)
+rules.add_perm('teams.change_team',is_team_manager)
+rules.add_perm('teams.delete_team',is_team_manager)
+
# END PERMISSIONS
tweetapi = twitter.Api(consumer_key=TWEET_CONSUMER_KEY,
@@ -363,7 +368,9 @@ def is_not_basic(user):
)
-# For future Team functionality
+
+
+
@python_2_unicode_compatible
class Team(RulesModel):
choices = (
@@ -384,13 +391,7 @@ class Team(RulesModel):
viewing = models.CharField(max_length=30,choices=viewchoices,default='allmembers',verbose_name='Sharing Behavior')
- class Meta:
- rules_permissions = {
- "add": is_coach,
- "change": is_team_manager,
- "delete": is_team_manager,
- "view": can_view_team,
- }
+
def __str__(self):
return self.name
diff --git a/rowers/teams.py b/rowers/teams.py
index 2556cf21..6c3b681d 100644
--- a/rowers/teams.py
+++ b/rowers/teams.py
@@ -61,7 +61,7 @@ def handle_add_workouts_team(ws,t):
def update_team(t,name,manager,private,notes,viewing):
- if not is_team_manager(t,manager):
+ if not is_team_manager(manager,t):
return (0,'You are not the manager of this team')
try:
t.name = name
@@ -214,7 +214,7 @@ def coach_remove_athlete(coach,rower):
def mgr_remove_member(id,manager,rower):
t = Team.objects.get(id=id)
- if is_team_manager(t,manager):
+ if is_team_manager(manager,t):
remove_member(id,rower)
send_email_member_dropped(id,rower)
return (id,'Member removed')
@@ -346,7 +346,7 @@ def create_coaching_offer(coach,user):
def create_invite(team,manager,user=None,email=''):
r = Rower.objects.get(user=manager)
- if not is_team_manager(team,manager):
+ if not is_team_manager(manager,team):
return (0,'Not the team manager')
if user:
try:
@@ -434,7 +434,7 @@ def revoke_invite(manager,id):
except TeamInvite.DoesNotExist:
return (0,'The invitation is invalid')
- if is_team_manager(invite.team,manager):
+ if is_team_manager(manager,invite.team):
invite.delete()
return (1,'Invitation revoked')
else:
@@ -446,7 +446,7 @@ def reject_request(manager,id):
except TeamRequest.DoesNotExist:
return (0,'The request is invalid')
- if is_team_manager(rekwest.team,manager):
+ if is_team_manager(manager,rekwest.team):
send_request_reject_email(rekwest)
rekwest.delete()
return (1,'Request rejected')
diff --git a/rowers/tests/test_permissions.py b/rowers/tests/test_permissions.py
index 2213a7b8..bd8cc51d 100644
--- a/rowers/tests/test_permissions.py
+++ b/rowers/tests/test_permissions.py
@@ -1180,7 +1180,7 @@ class PermissionsViewTests(TestCase):
response = self.c.get(url,follow=True)
self.assertEqual(response.status_code,200)
- expected_url = reverse('team_view',kwargs={'id':self.teampro.id})
+ expected_url = reverse('team_view',kwargs={'team_id':self.teampro.id})
self.assertRedirects(response,
expected_url = expected_url,
diff --git a/rowers/urls.py b/rowers/urls.py
index 7146d4da..da05dc02 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -458,12 +458,12 @@ urlpatterns = [
re_path(r'^me/calcdps/$',views.rower_calcdps_view,name='rower_calcdps_view'),
re_path(r'^me/exportsettings/$',views.rower_exportsettings_view,name='rower_exportsettings_view'),
re_path(r'^me/exportsettings/user/(?P\d+)/$',views.rower_exportsettings_view,name='rower_exportsettings_view'),
- re_path(r'^team/(?P\d+)/$',views.team_view,name='team_view'),
+ re_path(r'^team/(?P\d+)/$',views.team_view,name='team_view'),
re_path(r'^team/(?P\d+)/memberstats/$',views.team_members_stats_view,name='team_members_stats_view'),
re_path(r'^team/(?P\d+)/edit/$',views.team_edit_view,name='team_edit_view'),
re_path(r'^team/(?P\d+)/leaveconfirm/$',views.team_leaveconfirm_view,name='team_leaveconfirm_view'),
re_path(r'^team/(?P\d+)/leave/$',views.team_leave_view,name='team_leave_view'),
- re_path(r'^team/(?P\d+)/deleteconfirm/$',views.team_deleteconfirm_view,name='team_deleteconfirm_view'),
+ re_path(r'^team/(?P\d+)/deleteconfirm/$',views.team_deleteconfirm_view,name='team_deleteconfirm_view'),
re_path(r'^team/(?P\d+)/requestmembership/(?P\d+)/$',views.team_requestmembership_view,name='team_requestmembership_view'),
re_path(r'^me/coachrequest/(?P\d+)/reject/$',views.reject_revoke_coach_request,
name='reject_revoke_coach_request'),
diff --git a/rowers/views/statements.py b/rowers/views/statements.py
index a14d41a4..fa81fc24 100644
--- a/rowers/views/statements.py
+++ b/rowers/views/statements.py
@@ -19,6 +19,7 @@ from django.db.models import Q
from django import template
from django.db import IntegrityError, transaction
from django.views.decorators.csrf import csrf_exempt
+from django.shortcuts import get_object_or_404
from matplotlib.backends.backend_agg import FigureCanvas
import gc
from pyparsing import ParseException
diff --git a/rowers/views/teamviews.py b/rowers/views/teamviews.py
index c767f0d0..4d9b1b9f 100644
--- a/rowers/views/teamviews.py
+++ b/rowers/views/teamviews.py
@@ -5,10 +5,12 @@ from __future__ import unicode_literals
from rowers.views.statements import *
+def get_team_by_pk(request,team_id):
+ return get_object_or_404(Team,pk=team_id)
#@login_required()
-@permission_required('teams.view_team',fn=objectgetter(Team,'id'))
-def team_view(request,id=0,userid=0):
+@permission_required('teams.view_team',fn=get_team_by_pk,raise_exception=True)
+def team_view(request,team_id=0,userid=0):
ismember = 0
hasrequested = 0
r = getrequestrower(request,userid=userid)
@@ -19,7 +21,7 @@ def team_view(request,id=0,userid=0):
try:
- t = Team.objects.get(id=id)
+ t = Team.objects.get(id=team_id)
except Team.DoesNotExist:
raise Http404("Team doesn't exist")
@@ -84,7 +86,7 @@ def team_view(request,id=0,userid=0):
'name': 'Groups'
},
{
- 'url':reverse(team_view,kwargs={'id':id}),
+ 'url':reverse(team_view,kwargs={'team_id':team_id}),
'name': t.name
}
]
@@ -120,7 +122,7 @@ def team_leaveconfirm_view(request,id=0):
'name': 'Groups'
},
{
- 'url':reverse(team_view,kwargs={'id':id}),
+ 'url':reverse(team_view,kwargs={'team_id':id}),
'name': t.name
},
{
@@ -451,7 +453,7 @@ def team_requestmembership_view(request,teamid,userid):
url = reverse('team_view',kwargs={
- 'id':int(teamid),
+ 'team_id':int(teamid),
})
@@ -572,7 +574,7 @@ def rower_invitations_view(request,code=None,message='',successmessage=''):
messages.info(request,text)
teamid=res
url = reverse(team_view,kwargs={
- 'id':teamid,
+ 'team_id':teamid,
})
else:
messages.error(request,text)
@@ -610,7 +612,7 @@ def team_edit_view(request,id=0):
url = reverse(team_view,
kwargs={
- 'id':int(id),
+ 'team_id':int(id),
}
)
@@ -628,7 +630,7 @@ def team_edit_view(request,id=0):
'name': 'Groups'
},
{
- 'url':reverse(team_view,kwargs={'id':id}),
+ 'url':reverse(team_view,kwargs={'team_id':id}),
'name': t.name
},
{
@@ -710,11 +712,11 @@ def team_create_view(request):
})
#@login_required()
-@permission_required('teams.delete_team',fn=objectgetter(Team,'id'))
-def team_deleteconfirm_view(request,id):
+@permission_required('teams.delete_team',fn=get_team_by_pk)
+def team_deleteconfirm_view(request,team_id):
r = getrower(request.user)
try:
- t = Team.objects.get(id=id)
+ t = Team.objects.get(id=team_id)
except Team.DoesNotExist:
raise Http404("This team doesn't exist")
if t.manager != request.user:
@@ -728,11 +730,11 @@ def team_deleteconfirm_view(request,id):
'name': 'Groups'
},
{
- 'url':reverse(team_view,kwargs={'id':id}),
+ 'url':reverse(team_view,kwargs={'team_id':team_id}),
'name': t.name
},
{
- 'url':reverse(team_deleteconfirm_view,kwargs={'id':id}),
+ 'url':reverse(team_deleteconfirm_view,kwargs={'team_id':team_id}),
'name': 'Leave'
}
]
@@ -784,7 +786,7 @@ def team_members_stats_view(request,id):
'name': 'Groups'
},
{
- 'url':reverse(team_view,kwargs={'id':id}),
+ 'url':reverse(team_view,kwargs={'team_id':id}),
'name': t.name
},
{
From ad9cab43927f9ad18a6559663b2ad9a32397e852 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Sat, 11 Jan 2020 16:38:53 +0100
Subject: [PATCH 06/80] rules now separately in rower_rules
---
rowers/models.py | 42 ++++-----------------------------------
rowers/rower_rules.py | 33 ++++++++++++++++++++++++++++++
rowers/teams.py | 4 +++-
rowers/views/teamviews.py | 2 ++
4 files changed, 42 insertions(+), 39 deletions(-)
create mode 100644 rowers/rower_rules.py
diff --git a/rowers/models.py b/rowers/models.py
index 5edf81e7..593dfd1e 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -39,8 +39,9 @@ import pandas as pd
from dateutil import parser
import datetime
-import rules
-from rules.contrib.models import RulesModel
+#from rules.contrib.models import RulesModel
+
+
from rowers.rows import validate_file_extension
from collections import OrderedDict
@@ -56,39 +57,7 @@ from rowsandall_app.settings import (
TWEET_CONSUMER_SECRET,
)
-# PERMISSIONS
-@rules.predicate
-def is_team_manager(user,team):
- return team.manager == user
-
-@rules.predicate
-def is_team_member(user,team):
- members = Rower.objects.filter(team__in=[team])
- return user in [member.user for member in members]
-
-@rules.predicate
-def is_coach(user):
- r = Rower.objects.get(user=user)
- return r.rowerplan in ['coach','freecoach']
-
-@rules.predicate
-def can_view_team(user,team):
- # user based
- r = Rower.objects.get(user=user)
- if r.rowerplan == 'basic' and team.manager.rower.rowerplan != 'coach':
- return False
- # team is public
- if team.private == 'open':
- return True
- # team is private
- return is_team_member(user,team) | is_team_manager(user,team)
-
-# For Team functionality
-rules.add_perm('teams.view_team',can_view_team)
-rules.add_perm('teams.add_team',is_coach)
-rules.add_perm('teams.change_team',is_team_manager)
-rules.add_perm('teams.delete_team',is_team_manager)
# END PERMISSIONS
@@ -368,11 +337,8 @@ def is_not_basic(user):
)
-
-
-
@python_2_unicode_compatible
-class Team(RulesModel):
+class Team(models.Model):
choices = (
('private','private'),
('open','open'),
diff --git a/rowers/rower_rules.py b/rowers/rower_rules.py
new file mode 100644
index 00000000..0b89468a
--- /dev/null
+++ b/rowers/rower_rules.py
@@ -0,0 +1,33 @@
+import rules
+
+# PERMISSIONS
+
+@rules.predicate
+def is_team_manager(user,team):
+ return team.manager == user
+
+@rules.predicate
+def is_team_member(user,team):
+ members = team.rower.all()
+ return user in [member.user for member in members]
+
+@rules.predicate
+def is_coach(user):
+ return user.rower.rowerplan in ['coach','freecoach']
+
+@rules.predicate
+def can_view_team(user,team):
+ # user based
+ if user.rower.rowerplan == 'basic' and team.manager.rower.rowerplan != 'coach':
+ return False
+ # team is public
+ if team.private == 'open':
+ return True
+ # team is private
+ return is_team_member(user,team) | is_team_manager(user,team)
+
+# For Team functionality
+rules.add_perm('teams.view_team',can_view_team)
+rules.add_perm('teams.add_team',is_coach)
+rules.add_perm('teams.change_team',is_team_manager)
+rules.add_perm('teams.delete_team',is_team_manager)
diff --git a/rowers/teams.py b/rowers/teams.py
index 6c3b681d..e487c5ea 100644
--- a/rowers/teams.py
+++ b/rowers/teams.py
@@ -23,9 +23,11 @@ queuehigh = django_rq.get_queue('low')
from rowers.models import (
Rower, Workout, Team, TeamInvite,User,TeamRequest, CoachRequest, CoachOffer,
- CoachingGroup,is_team_manager,is_team_member,is_coach
+ CoachingGroup
)
+from rowers.rower_rules import is_team_manager,is_team_member,is_coach
+
from rowers.tasks import (
handle_sendemail_invite,
handle_sendemail_request,
diff --git a/rowers/views/teamviews.py b/rowers/views/teamviews.py
index 4d9b1b9f..73108d9d 100644
--- a/rowers/views/teamviews.py
+++ b/rowers/views/teamviews.py
@@ -5,6 +5,8 @@ from __future__ import unicode_literals
from rowers.views.statements import *
+from rowers.rower_rules import *
+
def get_team_by_pk(request,team_id):
return get_object_or_404(Team,pk=team_id)
From 48a361a43e735ceeccc235384ad2d081b0d1aab4 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Sat, 11 Jan 2020 17:05:08 +0100
Subject: [PATCH 07/80] passing tests
---
rowers/urls.py | 6 ++--
rowers/views/teamviews.py | 58 ++++++++++++---------------------------
2 files changed, 21 insertions(+), 43 deletions(-)
diff --git a/rowers/urls.py b/rowers/urls.py
index da05dc02..6a061af4 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -459,8 +459,8 @@ urlpatterns = [
re_path(r'^me/exportsettings/$',views.rower_exportsettings_view,name='rower_exportsettings_view'),
re_path(r'^me/exportsettings/user/(?P\d+)/$',views.rower_exportsettings_view,name='rower_exportsettings_view'),
re_path(r'^team/(?P\d+)/$',views.team_view,name='team_view'),
- re_path(r'^team/(?P\d+)/memberstats/$',views.team_members_stats_view,name='team_members_stats_view'),
- re_path(r'^team/(?P\d+)/edit/$',views.team_edit_view,name='team_edit_view'),
+ re_path(r'^team/(?P\d+)/memberstats/$',views.team_members_stats_view,name='team_members_stats_view'),
+ re_path(r'^team/(?P\d+)/edit/$',views.team_edit_view,name='team_edit_view'),
re_path(r'^team/(?P\d+)/leaveconfirm/$',views.team_leaveconfirm_view,name='team_leaveconfirm_view'),
re_path(r'^team/(?P\d+)/leave/$',views.team_leave_view,name='team_leave_view'),
re_path(r'^team/(?P\d+)/deleteconfirm/$',views.team_deleteconfirm_view,name='team_deleteconfirm_view'),
@@ -489,7 +489,7 @@ urlpatterns = [
name='coach_accept_coachrequest_view'),
re_path(r'^me/coachoffer/(?P\w+.*)/accept/$',views.rower_accept_coachoffer_view,
name='rower_accept_coachoffer_view'),
- re_path(r'^team/(?P\d+)/delete/$',views.team_delete_view,name='team_delete_view'),
+ re_path(r'^team/(?P\d+)/delete/$',views.team_delete_view,name='team_delete_view'),
re_path(r'^team/create/$',views.team_create_view,name='team_create_view'),
re_path(r'^me/team/(?P\d+)/drop/(?P\d+)/$',views.manager_member_drop_view,name='manager_member_drop_view'),
re_path(r'^me/invitation/(?P\d+)/reject/$',views.invitation_reject_view,name='invitation_reject_view'),
diff --git a/rowers/views/teamviews.py b/rowers/views/teamviews.py
index 73108d9d..67315e2c 100644
--- a/rowers/views/teamviews.py
+++ b/rowers/views/teamviews.py
@@ -20,12 +20,7 @@ def team_view(request,team_id=0,userid=0):
myteams, memberteams, otherteams = get_teams(request)
teams.remove_expired_invites()
-
-
- try:
- t = Team.objects.get(id=team_id)
- except Team.DoesNotExist:
- raise Http404("Team doesn't exist")
+ t = get_object_or_404(Team,pk=team_id)
#if r.rowerplan == 'basic' and t.manager.rower.rowerplan != 'coach':
# raise PermissionDenied("You need to be on a Paid Plan to see or join this team")
@@ -589,12 +584,10 @@ def rower_invitations_view(request,code=None,message='',successmessage=''):
})
return HttpResponseRedirect(url)
-@login_required()
-def team_edit_view(request,id=0):
- try:
- t = Team.objects.get(id=id)
- except Team.DoesNotExist:
- raise Http404("Team does not exist")
+@permission_required('teams.change_team',fn=get_team_by_pk,raise_exception=True)
+def team_edit_view(request, team_id=0):
+ t = get_object_or_404(Team,pk=team_id)
+
if request.method == 'POST':
teamcreateform = TeamForm(request.POST,instance=t)
@@ -614,7 +607,7 @@ def team_edit_view(request,id=0):
url = reverse(team_view,
kwargs={
- 'team_id':int(id),
+ 'team_id':int(team_id),
}
)
@@ -632,11 +625,11 @@ def team_edit_view(request,id=0):
'name': 'Groups'
},
{
- 'url':reverse(team_view,kwargs={'team_id':id}),
+ 'url':reverse(team_view,kwargs={'team_id':team_id}),
'name': t.name
},
{
- 'url':reverse(team_edit_view,kwargs={'id':id}),
+ 'url':reverse(team_edit_view,kwargs={'team_id':team_id}),
'name': 'Edit'
}
]
@@ -714,15 +707,10 @@ def team_create_view(request):
})
#@login_required()
-@permission_required('teams.delete_team',fn=get_team_by_pk)
+@permission_required('teams.delete_team',fn=get_team_by_pk,raise_exception=True)
def team_deleteconfirm_view(request,team_id):
r = getrower(request.user)
- try:
- t = Team.objects.get(id=team_id)
- except Team.DoesNotExist:
- raise Http404("This team doesn't exist")
- if t.manager != request.user:
- raise PermissionDenied("You are not allowed to delete this team")
+ t = get_object_or_404(Team,pk=team_id)
myteams, memberteams, otherteams = get_teams(request)
@@ -750,15 +738,10 @@ def team_deleteconfirm_view(request,team_id):
'active':'nav-teams',
})
-@login_required()
-def team_delete_view(request,id):
+@permission_required('teams.delete_team',fn=get_team_by_pk,raise_exception=True)
+def team_delete_view(request,team_id):
r = getrower(request.user)
- try:
- t = Team.objects.get(id=id)
- except Team.DoesNotExist:
- raise Http404("This team doesn't exist")
- if t.manager != request.user:
- raise PermissionDenied("You are not allowed to delete this team")
+ t = get_object_or_404(Team,pk=team_id)
teams.remove_team(t.id)
@@ -766,15 +749,10 @@ def team_delete_view(request,id):
response = HttpResponseRedirect(url)
return response
-@login_required()
-def team_members_stats_view(request,id):
+@permission_required('teams.change_team',fn=get_team_by_pk,raise_exception=True)
+def team_members_stats_view(request,team_id):
r = getrower(request.user)
- try:
- t = Team.objects.get(id=id)
- except Team.DoesNotExist:
- raise Http404("This team doesn't exist")
- if t.manager != request.user:
- raise PermissionDenied("You are not allowed to see this page")
+ t = get_object_or_404(Team,pk=team_id)
members = Rower.objects.filter(team=t).order_by("user__last_name","user__first_name")
@@ -788,11 +766,11 @@ def team_members_stats_view(request,id):
'name': 'Groups'
},
{
- 'url':reverse(team_view,kwargs={'team_id':id}),
+ 'url':reverse(team_view,kwargs={'team_id':team_id}),
'name': t.name
},
{
- 'url':reverse(team_members_stats_view,kwargs={'id':id}),
+ 'url':reverse(team_members_stats_view,kwargs={'team_id':team_id}),
'name': 'Members Stats'
}
]
From 892b6c0e606a9c86547332ab6a8d2ab45949504c Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Sat, 11 Jan 2020 17:56:38 +0100
Subject: [PATCH 08/80] small changes
---
rowers/rower_rules.py | 26 +++++++++++++++++++++-----
rowers/teams.py | 17 ++---------------
rowers/views/teamviews.py | 12 ++++--------
3 files changed, 27 insertions(+), 28 deletions(-)
diff --git a/rowers/rower_rules.py b/rowers/rower_rules.py
index 0b89468a..386cb5eb 100644
--- a/rowers/rower_rules.py
+++ b/rowers/rower_rules.py
@@ -1,7 +1,26 @@
import rules
+import datetime
# PERMISSIONS
+# USER permissions
+
+@rules.predicate
+def user_is_not_basic(user):
+ if user.rower.rowerplan != 'basic':
+ return True
+
+ if user.rower.protrialexpires >= datetime.date.today() and user.rower.plantrialexpires >= datetime.date.today():
+ return True
+
+ return False
+
+@rules.predicate
+def is_coach(user):
+ return user.rower.rowerplan in ['coach','freecoach']
+
+# TEAM permissions
+
@rules.predicate
def is_team_manager(user,team):
return team.manager == user
@@ -11,10 +30,6 @@ def is_team_member(user,team):
members = team.rower.all()
return user in [member.user for member in members]
-@rules.predicate
-def is_coach(user):
- return user.rower.rowerplan in ['coach','freecoach']
-
@rules.predicate
def can_view_team(user,team):
# user based
@@ -26,8 +41,9 @@ def can_view_team(user,team):
# team is private
return is_team_member(user,team) | is_team_manager(user,team)
+
# For Team functionality
rules.add_perm('teams.view_team',can_view_team)
-rules.add_perm('teams.add_team',is_coach)
+rules.add_perm('teams.add_team',user_is_not_basic)
rules.add_perm('teams.change_team',is_team_manager)
rules.add_perm('teams.delete_team',is_team_manager)
diff --git a/rowers/teams.py b/rowers/teams.py
index e487c5ea..5e3fe126 100644
--- a/rowers/teams.py
+++ b/rowers/teams.py
@@ -81,7 +81,7 @@ def create_team(name,manager,private='open',notes='',viewing='allmembers'):
if manager.rower.rowerplan == 'basic':
if manager.rower.protrialexpires < timezone.now().date() and manager.rower.plantrialexpires < timezone.now().date():
return (0,'You need to upgrade to a paid plan to establish a team')
- if manager.rower.rowerplan not in ('coach','freecoach'):
+ if not is_coach(manager):
ts = Team.objects.filter(manager=manager)
if len(ts)>=1:
return (0,'You need to upgrade to the Coach plan to have more than one team')
@@ -102,19 +102,6 @@ def remove_team(id):
send_team_delete_mail(t,r)
return t.delete()
-#def set_teamplanexpires(rower):
-# ts = Team.objects.filter(rower=rower)
-
-# texp = datetime.date(timezone.now())
-
-# for t in ts:
-# print t.name
-# mr = Rower.objects.get(user=t.manager)
-# if mr.teamplanexpires > texp:
-# rower.teamplanexpires = mr.teamplanexpires
-
-# t.save()
-
return (1,'Updated rower team expiry')
def add_coach(coach,rower):
@@ -194,7 +181,7 @@ def rower_get_coaches(rower):
def coach_getcoachees(coach):
- if coach.mycoachgroup and coach.rowerplan in ('coach','freecoach'):
+ if coach.mycoachgroup and is_coach(coach.user):
return Rower.objects.filter(
coachinggroups__in=[coach.mycoachgroup]
).distinct().order_by("user__last_name","user__first_name")
diff --git a/rowers/views/teamviews.py b/rowers/views/teamviews.py
index 67315e2c..3e9d942e 100644
--- a/rowers/views/teamviews.py
+++ b/rowers/views/teamviews.py
@@ -647,17 +647,13 @@ def team_edit_view(request, team_id=0):
})
#@user_passes_test(cancreateteam,login_url="/rowers/paidplans",redirect_field_name=None)
-@login_required()
def team_create_view(request):
r = getrequestrower(request)
- if r.rowerplan == 'basic':
- if r.protrialexpires < timezone.now().date() and r.plantrialexpires < timezone.now().date():
- messages.error(request,"You must upgrade to Pro or higher to create teams/training groups")
- url = reverse('paidplans')
- return HttpResponseRedirect(url)
-
-
+ if not user_is_not_basic(request.user):
+ messages.error(request,"You must upgrade to Pro or higher to create teams/training groups")
+ url = reverse('paidplans')
+ return HttpResponseRedirect(url)
if request.method == 'POST':
teamcreateform = TeamForm(request.POST)
From c82a60f02e62ff28b674c7e89640d3ce8432c4ca Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Sun, 12 Jan 2020 17:58:55 +0100
Subject: [PATCH 09/80] moved user and workout permission checks to rules
updated workoutviews, rest of views not done doesn't pass tests
---
rowers/c2stuff.py | 3 +-
rowers/imports.py | 25 +-
rowers/models.py | 91 +----
rowers/polarstuff.py | 34 +-
rowers/rower_rules.py | 148 ++++++++
rowers/runkeeperstuff.py | 4 +-
rowers/sporttracksstuff.py | 3 +-
rowers/stravastuff.py | 4 +-
rowers/templatetags/rowerfilters.py | 7 +-
rowers/tests/mocks.py | 2 +-
rowers/tests/statements.py | 24 +-
rowers/tests/test_urls.py | 1 -
rowers/tpstuff.py | 4 +-
rowers/traverselinktest.py | 6 +-
rowers/underarmourstuff.py | 3 +-
rowers/uploads.py | 18 +-
rowers/urls.py | 2 -
rowers/utils.py | 54 ++-
rowers/views/exportviews.py | 31 +-
rowers/views/importviews.py | 561 ++++++++++++++--------------
rowers/views/planviews.py | 11 +-
rowers/views/statements.py | 76 +---
rowers/views/workoutviews.py | 217 ++++-------
23 files changed, 618 insertions(+), 711 deletions(-)
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
From 9cbeb3fc2d85c5d9be00bf8f059514befe2c2019 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Sun, 12 Jan 2020 18:06:23 +0100
Subject: [PATCH 10/80] removed checkaccessuers from planviews not checked
---
rowers/views/planviews.py | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/rowers/views/planviews.py b/rowers/views/planviews.py
index aa158fcf..1b04f7ed 100644
--- a/rowers/views/planviews.py
+++ b/rowers/views/planviews.py
@@ -1959,7 +1959,7 @@ class PlannedSessionDelete(DeleteView):
def get_object(self, *args, **kwargs):
obj = super(PlannedSessionDelete, self).get_object(*args, **kwargs)
m = Rower.objects.get(user=obj.manager)
- if not checkaccessuser(self.request.user,m):
+ if not is_coach_user(m,self.request.user.rower):
raise PermissionDenied('You are not allowed to delete this planned session')
return obj
@@ -2103,7 +2103,7 @@ def rower_delete_trainingtarget(request,id=0):
except TrainingPlan.DoesNotExist:
raise Http404("Training Plan Does Not Exist")
- if checkaccessuser(request.user,target.manager):
+ if is_coach_user(target.manager,request.user.rower):
target.delete()
messages.info(request,"We have deleted the training target")
else:
@@ -2123,7 +2123,7 @@ def rower_delete_trainingplan(request,id=0):
except TrainingPlan.DoesNotExist:
raise Http404("Training Plan Does Not Exist")
- if checkaccessuser(request.user,plan.manager):
+ if is_coach_user(plan.manager.user,request.user.rower):
plan.delete()
messages.info(request,"We have deleted the training plan")
else:
@@ -2140,7 +2140,7 @@ class TrainingPlanDelete(DeleteView):
def get_object(self, *args, **kwargs):
obj = super(TrainingPlanDelete, self).get_object(*args, **kwargs)
- if not checkaccessuser(self.request.user,obj.manager):
+ if not is_coach_user(obj.manager.user,self.request.user):
raise PermissionDenied('You are not allowed to delete this training plan')
return obj
@@ -2206,7 +2206,7 @@ class MicroCycleDelete(DeleteView):
def get_object(self, *args, **kwargs):
obj = super(MicroCycleDelete, self).get_object(*args, **kwargs)
- if not checkaccessuser(self.request.user,obj.plan.plan.plan.manager):
+ if not is_coach_user(obj.plan.plan.plan.manager.user,self.request.user):
raise PermissionDenied('You are not allowed to delete this training plan cycle')
return obj
@@ -2268,7 +2268,7 @@ class MesoCycleDelete(DeleteView):
def get_object(self, *args, **kwargs):
obj = super(MesoCycleDelete, self).get_object(*args, **kwargs)
- if not checkaccessuser(self.request.user,obj.plan.plan.manager):
+ if not is_coach_user(obj.plan.plan.manager.user,self.request.user):
raise PermissionDenied('You are not allowed to delete this training plan cycle')
return obj
@@ -2322,7 +2322,7 @@ class MacroCycleDelete(DeleteView):
def get_object(self, *args, **kwargs):
obj = super(MacroCycleDelete, self).get_object(*args, **kwargs)
- if not checkaccessuser(self.request.user,obj.plan.manager):
+ if not is_coach_user(obj.plan.manager.user,self.request.user):
raise PermissionDenied('You are not allowed to delete this training plan cycle')
return obj
@@ -2345,7 +2345,7 @@ def rower_trainingplan_execution_view(request,
plan = TrainingPlan.objects.get(id=id)
except TrainingPlan.DoesNotExist:
raise Http404("Training Plan Does Not Exist")
- if not checkaccessuser(request.user,plan.manager):
+ if not is_coach_user(plan.manager.user,request.user):
if request.user.rower not in plan.rowers.all():
raise PermissionDenied("Access denied")
@@ -2439,7 +2439,7 @@ def rower_trainingplan_view(request,
r = getrequestrower(request,userid=userid)
- if not checkaccessuser(request.user,plan.manager):
+ if not is_coach_user(plan.manager.user,request.user):
if request.user.rower not in plan.rowers.all():
raise PermissionDenied("Access denied")
@@ -2568,7 +2568,7 @@ class TrainingMacroCycleUpdate(UpdateView):
if obj.plan.manager is not None and self.request.user.rower != obj.plan.manager:
raise PermissionDenied('You are not allowed to edit this training plan cycle')
- if not checkaccessuser(self.request.user,obj.plan.manager):
+ if not is_coach_user(plan.manager.user,self.request.user):
raise PermissionDenied('You are not allowed to edit this training plan cycle')
else:
obj.type = 'userdefined'
@@ -2850,7 +2850,7 @@ def planmesocyclebyweek(request,id=0,userid=0):
except TrainingMesoCycle.DoesNotExist:
raise Http404("Training Cycle does not exist")
- if not checkaccessuser(request.user,cycle.plan.plan.manager):
+ if not is_coach_user(cycle.plan.plan.manager.user,request.user):
raise PermissionDenied("You are not allowed to do this")
micros = TrainingMicroCycle.objects.filter(plan=cycle)
@@ -2905,7 +2905,7 @@ def planmacrocyclebymonth(request,id=0,userid=0):
except TrainingMacroCycle.DoesNotExist:
raise Http404("Training Cycle does not exist")
- if not checkaccessuser(request.user,cycle.plan.manager):
+ if not is_coach_user(cycle.plan.manager.user,request.user):
raise PermissionDenied("You are not allowed to do this")
mesos = TrainingMesoCycle.objects.filter(plan=cycle)
From 09ae7cbe770c1d45d35054bbe21a0b3cd8cecdef Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Sun, 12 Jan 2020 22:10:20 +0100
Subject: [PATCH 11/80] passing tests - user / workouts permissions done
---
rowers/rower_rules.py | 34 ++++++++--------------------------
rowers/tests/test_misc.py | 4 ++--
rowers/views/exportviews.py | 6 +++---
rowers/views/importviews.py | 14 +++++++-------
rowers/views/planviews.py | 22 +++++++++++-----------
rowers/views/statements.py | 7 ++++---
rowers/views/workoutviews.py | 9 +++++----
7 files changed, 40 insertions(+), 56 deletions(-)
diff --git a/rowers/rower_rules.py b/rowers/rower_rules.py
index 258c86b3..1ad9d6f0 100644
--- a/rowers/rower_rules.py
+++ b/rowers/rower_rules.py
@@ -51,6 +51,7 @@ ispromember = is_promember | is_protrial
def can_plan(user):
return user.rower.rowerplan in ['plan','coach','freecoach']
+# checks if rower is coach of user
@rules.predicate
def is_coach_user(user,rower):
try:
@@ -67,7 +68,7 @@ def is_coach_user(user,rower):
newcoaches = group.get_coaches()
for coach in newcoaches:
coaches.append(coach)
- print(coaches)
+
for coach in coaches:
if rower == coach:
return True
@@ -126,36 +127,16 @@ def is_workout_user(user,workout):
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
+ return is_coach_user(workout.user.user,user.rower)
+
@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
-
+ if workout.privacy != 'private':
+ 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
@@ -166,6 +147,7 @@ rules.add_perm('workout.view_workout',can_view_workout) # replaces checkworkoutu
# PLANNING permissions
# checkaccessplanuser (models.py)
+# getrequestrower, getrequestplanrower
# TEAM permissions
diff --git a/rowers/tests/test_misc.py b/rowers/tests/test_misc.py
index cf97672c..e2174bdf 100644
--- a/rowers/tests/test_misc.py
+++ b/rowers/tests/test_misc.py
@@ -8,7 +8,7 @@ from .statements import *
nu = datetime.datetime.now()
-
+from rowers.rower_rules import is_workout_user
#@pytest.mark.django_db
class WorkoutTests(TestCase):
@@ -28,7 +28,7 @@ class WorkoutTests(TestCase):
duration="0:55:00",distance=8000)
def test_checkworkoutuser(self):
- self.assertEqual(checkworkoutuser(self.u,self.w),True)
+ self.assertEqual(is_workout_user(self.u,self.w),True)
#@pytest.mark.django_db
class C2Tests(TestCase):
diff --git a/rowers/views/exportviews.py b/rowers/views/exportviews.py
index 08f2e064..afdfc7c9 100644
--- a/rowers/views/exportviews.py
+++ b/rowers/views/exportviews.py
@@ -6,7 +6,7 @@ from rowers.views.statements import *
# Export workout to TCX and send to user's email address
-@permission_required('workout.change_workout',fn=objectgetter(Workout, 'id'))
+@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_tcxemail_view(request,id=0):
r = getrower(request.user)
w = get_workout(id)
@@ -168,7 +168,7 @@ def course_kmldownload_view(request,id=0):
# Export workout to GPX and send to user's email address
-@permission_required('workout.change_workout',fn=objectgetter(Workout, 'id'))
+@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_gpxemail_view(request,id=0):
r = getrower(request.user)
w = get_workout(id)
@@ -234,7 +234,7 @@ def workouts_summaries_email_view(request):
# Get Workout CSV file and send it to user's email address
-@permission_required('workout.change_workout',fn=objectgetter(Workout, 'id'))
+@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_csvemail_view(request,id=0):
r = getrower(request.user)
diff --git a/rowers/views/importviews.py b/rowers/views/importviews.py
index 7b77b68b..a823c788 100644
--- a/rowers/views/importviews.py
+++ b/rowers/views/importviews.py
@@ -13,7 +13,7 @@ def default(o):
# Send workout to TP
-@permission_required('workout.change_workout',fn=objectgetter(Workout, 'id'))
+@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_tp_upload_view(request,id=0):
message = ""
@@ -25,7 +25,7 @@ def workout_tp_upload_view(request,id=0):
return HttpResponseRedirect("/rowers/me/tpauthorize/")
# ready to upload. Hurray
- w = get_object_or_404(Workout,pk=id)
+ w = get_workout_by_opaqueid(request,id)
r = w.user
@@ -66,7 +66,7 @@ def workout_tp_upload_view(request,id=0):
# Send workout to Strava
# abundance of error logging here because there were/are some bugs
-@permission_required('workout.change_workout',fn=objectgetter(Workout, 'id'))
+@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_strava_upload_view(request,id=0):
message = ""
r = getrower(request.user)
@@ -202,7 +202,7 @@ def workout_c2_upload_view(request,id=0):
return response
# Upload workout to RunKeeper
-@permission_required('workout.change_workout',fn=objectgetter(Workout, 'id'))
+@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_runkeeper_upload_view(request,id=0):
message = ""
w = get_workout(id)
@@ -263,7 +263,7 @@ def workout_runkeeper_upload_view(request,id=0):
return HttpResponseRedirect(url)
# Upload workout to Underarmour
-@permission_required('workout.change_workout',fn=objectgetter(Workout, 'id'))
+@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_underarmour_upload_view(request,id=0):
message = ""
w = get_workout(id)
@@ -324,7 +324,7 @@ def workout_underarmour_upload_view(request,id=0):
return HttpResponseRedirect(url)
# Upload workout to SportTracks
-@permission_required('workout.change_workout',fn=objectgetter(Workout, 'id'))
+@permission_required('workout.change_workout',fn=get_workout_by_opaqueid)
def workout_sporttracks_upload_view(request,id=0):
message = ""
# ready to upload. Hurray
@@ -378,7 +378,7 @@ def workout_sporttracks_upload_view(request,id=0):
s = response
message = "Something went wrong in workout_sporttracks_upload_view: %s" % s.reason
messages.error(request,message)
-
+
url = reverse(r.defaultlandingpage,
kwargs = {
'id':encoder.encode_hex(w.id),
diff --git a/rowers/views/planviews.py b/rowers/views/planviews.py
index 1b04f7ed..ae16fb94 100644
--- a/rowers/views/planviews.py
+++ b/rowers/views/planviews.py
@@ -1697,7 +1697,7 @@ def plannedsession_edit_view(request,id=0,userid=0):
})
-@permission_required('workout.change_workout',fn=objectgetter(Workout, 'id'))
+@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def plannedsession_detach_view(request,id=0,psid=0):
r = getrequestrower(request)
@@ -1959,7 +1959,7 @@ class PlannedSessionDelete(DeleteView):
def get_object(self, *args, **kwargs):
obj = super(PlannedSessionDelete, self).get_object(*args, **kwargs)
m = Rower.objects.get(user=obj.manager)
- if not is_coach_user(m,self.request.user.rower):
+ if not is_coach_user(m.user,self.request.user.rower):
raise PermissionDenied('You are not allowed to delete this planned session')
return obj
@@ -2140,7 +2140,7 @@ class TrainingPlanDelete(DeleteView):
def get_object(self, *args, **kwargs):
obj = super(TrainingPlanDelete, self).get_object(*args, **kwargs)
- if not is_coach_user(obj.manager.user,self.request.user):
+ if not is_coach_user(obj.manager.user,self.request.user.rower):
raise PermissionDenied('You are not allowed to delete this training plan')
return obj
@@ -2206,7 +2206,7 @@ class MicroCycleDelete(DeleteView):
def get_object(self, *args, **kwargs):
obj = super(MicroCycleDelete, self).get_object(*args, **kwargs)
- if not is_coach_user(obj.plan.plan.plan.manager.user,self.request.user):
+ if not is_coach_user(obj.plan.plan.plan.manager.user,self.request.user.rower):
raise PermissionDenied('You are not allowed to delete this training plan cycle')
return obj
@@ -2268,7 +2268,7 @@ class MesoCycleDelete(DeleteView):
def get_object(self, *args, **kwargs):
obj = super(MesoCycleDelete, self).get_object(*args, **kwargs)
- if not is_coach_user(obj.plan.plan.manager.user,self.request.user):
+ if not is_coach_user(obj.plan.plan.manager.user,self.request.user.rower):
raise PermissionDenied('You are not allowed to delete this training plan cycle')
return obj
@@ -2322,7 +2322,7 @@ class MacroCycleDelete(DeleteView):
def get_object(self, *args, **kwargs):
obj = super(MacroCycleDelete, self).get_object(*args, **kwargs)
- if not is_coach_user(obj.plan.manager.user,self.request.user):
+ if not is_coach_user(obj.plan.manager.user,self.request.user.rower):
raise PermissionDenied('You are not allowed to delete this training plan cycle')
return obj
@@ -2345,7 +2345,7 @@ def rower_trainingplan_execution_view(request,
plan = TrainingPlan.objects.get(id=id)
except TrainingPlan.DoesNotExist:
raise Http404("Training Plan Does Not Exist")
- if not is_coach_user(plan.manager.user,request.user):
+ if not is_coach_user(plan.manager.user,request.user.rower):
if request.user.rower not in plan.rowers.all():
raise PermissionDenied("Access denied")
@@ -2439,7 +2439,7 @@ def rower_trainingplan_view(request,
r = getrequestrower(request,userid=userid)
- if not is_coach_user(plan.manager.user,request.user):
+ if not is_coach_user(plan.manager.user,request.user.rower):
if request.user.rower not in plan.rowers.all():
raise PermissionDenied("Access denied")
@@ -2568,7 +2568,7 @@ class TrainingMacroCycleUpdate(UpdateView):
if obj.plan.manager is not None and self.request.user.rower != obj.plan.manager:
raise PermissionDenied('You are not allowed to edit this training plan cycle')
- if not is_coach_user(plan.manager.user,self.request.user):
+ if not is_coach_user(obj.plan.manager.user,self.request.user.rower):
raise PermissionDenied('You are not allowed to edit this training plan cycle')
else:
obj.type = 'userdefined'
@@ -2850,7 +2850,7 @@ def planmesocyclebyweek(request,id=0,userid=0):
except TrainingMesoCycle.DoesNotExist:
raise Http404("Training Cycle does not exist")
- if not is_coach_user(cycle.plan.plan.manager.user,request.user):
+ if not is_coach_user(cycle.plan.plan.manager.user,request.user.rower):
raise PermissionDenied("You are not allowed to do this")
micros = TrainingMicroCycle.objects.filter(plan=cycle)
@@ -2905,7 +2905,7 @@ def planmacrocyclebymonth(request,id=0,userid=0):
except TrainingMacroCycle.DoesNotExist:
raise Http404("Training Cycle does not exist")
- if not is_coach_user(cycle.plan.manager.user,request.user):
+ if not is_coach_user(cycle.plan.manager.user,request.user.rower):
raise PermissionDenied("You are not allowed to do this")
mesos = TrainingMesoCycle.objects.filter(plan=cycle)
diff --git a/rowers/views/statements.py b/rowers/views/statements.py
index ac3867da..b4d4bbf9 100644
--- a/rowers/views/statements.py
+++ b/rowers/views/statements.py
@@ -313,16 +313,18 @@ def getrequestrower(request,rowerid=0,userid=0,notpermanent=False):
if rowerid != 0:
r = Rower.objects.get(id=rowerid)
+ u = r.user
elif userid != 0:
u = User.objects.get(id=userid)
r = getrower(u)
else:
r = getrower(request.user)
+ u = r.user
except Rower.DoesNotExist:
raise Http404("Rower doesn't exist")
- if userid != 0 and not is_coach_user(u,r):
+ if userid != 0 and not is_coach_user(u,request.user.rower):
raise PermissionDenied("You have no access to this user")
if notpermanent == False:
@@ -355,7 +357,7 @@ def getrequestplanrower(request,rowerid=0,userid=0,notpermanent=False):
except Rower.DoesNotExist:
raise Http404("Rower doesn't exist")
- if not is_coach_user(request.user,r):
+ if not is_coach_user(r.user,request.user.rower):
raise PermissionDenied("You have no access to this user")
if notpermanent == False:
@@ -430,7 +432,6 @@ class SessionTaskListener(threading.Thread):
for item in self.pubsub.listen():
if item['data'] == "KILL":
self.pubsub.unsubscribe()
- print(self, "unsubscribed and finished")
break
else:
self.work(item)
diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py
index 36332560..4d70bfa3 100644
--- a/rowers/views/workoutviews.py
+++ b/rowers/views/workoutviews.py
@@ -3023,7 +3023,7 @@ def workout_data_view(request, id=0):
# Stats page
-@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
+@permission_required('workout.view_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_stats_view(request,id=0,message="",successmessage=""):
r = getrower(request.user)
@@ -3299,7 +3299,7 @@ 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)
+@permission_required('workout.view_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
@@ -5876,7 +5876,7 @@ class VideoDelete(DeleteView):
def get_object(self, *args, **kwargs):
obj = super(VideoDelete, self).get_object(*args, **kwargs)
- if not checkaccessuser(self.request.user,obj.workout.user):
+ if not is_coach_user(obj.workout.user,self.request.user):
raise PermissionDenied('You are not allowed to delete this analysis')
return obj
@@ -5929,7 +5929,8 @@ class GraphDelete(DeleteView):
def get_object(self, *args, **kwargs):
obj = super(GraphDelete, self).get_object(*args, **kwargs)
- if not checkaccessuser(self.request.user,obj.workout.user):
+
+ if not is_workout_user(self.request.user,obj.workout):
raise PermissionDenied('You are not allowed to delete this chart')
return obj
From af609dd7e022bd2c03838818e0f775a4b24946b3 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Sun, 12 Jan 2020 22:20:47 +0100
Subject: [PATCH 12/80] fixed obsolete usage of rowingdata(csvfile)
---
rowers/dataprepnodjango.py | 8 ++++----
rowers/mailprocessing.py | 8 ++++----
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/rowers/dataprepnodjango.py b/rowers/dataprepnodjango.py
index f41498fb..15f66293 100644
--- a/rowers/dataprepnodjango.py
+++ b/rowers/dataprepnodjango.py
@@ -140,10 +140,10 @@ def timedeltaconv(x):
def rdata(file,rower=rrower()):
try:
- res = rrdata(file,rower=rower)
+ res = rrdata(csvfile=file,rower=rower)
except IOError:
try:
- res = rrdata(file+'.gz',rower=rower)
+ res = rrdata(csvfile=file+'.gz',rower=rower)
except IOError:
res = 0
@@ -948,7 +948,7 @@ def smalldataprep(therows,xparam,yparam1,yparam2):
f1 = workout.csvfilename
try:
- rowdata = dataprep(rrdata(f1).df)
+ rowdata = dataprep(rrdata(csvfile=f1).df)
rowdata = pd.DataFrame({xparam: rowdata[xparam],
yparam1: rowdata[yparam1],
@@ -960,7 +960,7 @@ def smalldataprep(therows,xparam,yparam1,yparam2):
df = pd.concat([df,rowdata],ignore_index=True)
except IOError:
try:
- rowdata = dataprep(rrdata(f1+'.gz').df)
+ rowdata = dataprep(rrdata(csvfile=f1+'.gz').df)
rowdata = pd.DataFrame({xparam: rowdata[xparam],
yparam1: rowdata[yparam1],
yparam2: rowdata[yparam2],
diff --git a/rowers/mailprocessing.py b/rowers/mailprocessing.py
index 37017602..fb275a11 100644
--- a/rowers/mailprocessing.py
+++ b/rowers/mailprocessing.py
@@ -64,18 +64,18 @@ def send_confirm(user, name, link, options):
def rdata(file, rower=rrower()):
""" Reads rowingdata data or returns 0 on Error """
try:
- result = rrdata(file, rower=rower)
+ result = rrdata(csvfile=file, rower=rower)
except IOError:
try:
- result = rrdata(file + '.gz', rower=rower)
+ result = rrdata(csvfile=file + '.gz', rower=rower)
except IOError:
result = 0
except TypeError:
try:
- result = rrdata(file)
+ result = rrdata(csvfile=file)
except IOError:
try:
- result = rrdata(file+'.gz', rower=rower)
+ result = rrdata(csvfile=file+'.gz', rower=rower)
except IOError:
result = 0
From bf1ea191e780952e3807492b46eeddaecd65d438 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 13 Jan 2020 15:33:50 +0100
Subject: [PATCH 13/80] added annotations to rules on rules to add
---
rowers/rower_rules.py | 108 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 106 insertions(+), 2 deletions(-)
diff --git a/rowers/rower_rules.py b/rowers/rower_rules.py
index 1ad9d6f0..f55cbc61 100644
--- a/rowers/rower_rules.py
+++ b/rowers/rower_rules.py
@@ -5,6 +5,30 @@ import datetime
# USER permissions
+"""
+USER permissions
+- There are 5 types of user: basic, pro, plan, coach, freecoach
+- These methods exist on user
+ - user.rower
+ - user.is_anonymous
+ - user.is_staff
+
+- These methods exist on rower
+ - rower.rowerplan
+ - rower.protrialexpires
+ - rower.plantrialexpires
+ - rower.mycoachgroup
+ - rower.coachinggroups
+ - rower.team
+
+- These Rules apply to Rowers
+ - Free Coach user cannot have workouts
+ - Coach can edit athlete settings (if in coachinggroup)
+ - Coach can run analytics for athlete
+ - Pro and Plan user cannot run analysis for members of their groups
+
+"""
+
@rules.predicate
def user_is_not_basic(user):
if user.rower.rowerplan != 'basic':
@@ -114,6 +138,25 @@ rules.add_perm('rower.is_coach',is_coach_user) # replaces checkaccessuser
# WORKOUT permissions
+"""
+WORKOUT permissions
+- These methods exist on Workout
+ - workout.privacy
+
+- These rules apply to workouts
+ - User can add, delete and change their own workouts
+ - Coach can add and change workouts for their athletes, but not delete
+ - Pro and Plan user cannot add or change an athlete's workout
+ - Pro, Basic users can view team members' workout
+ - Anonymous users can view team members' workout (but not see list of workouts)
+
+ - Rules for Workouts are transferred to objects related to the Workout,
+ - Charts
+ - Video Analysis
+ - WorkoutComment?
+
+"""
+
@rules.predicate
def is_workout_user(user,workout):
if user.is_anonymous:
@@ -128,7 +171,7 @@ def is_workout_user(user,workout):
return True
return is_coach_user(workout.user.user,user.rower)
-
+
@rules.predicate
def can_view_workout(user,workout):
@@ -146,10 +189,52 @@ rules.add_perm('workout.view_workout',can_view_workout) # replaces checkworkoutu
# PLANNING permissions
+"""
+- These rules apply to planning
+ - Free coach can create planned sessions and team planned sessions
+ - Self coach and higher can create planned sessions and team planned sessions
+ - Coach can create planned sessions and team planned sessions
+ - Self Coach and higher can create planned sessions and team planned sessions
+ - Pro or Basic cannot create planned sessions or team planned sessions
+ - WHO can comment (plannedsession_comment_view)
+ - Only Session manager can change session
+ - Strict View rules (stricter than workouts)
+
+ - TrainingTarget
+ - rules for view, add, change, delete
+ - TrainingPlan
+ - rules for view, add, change, delete
+ - Cycle
+ - inherits rules from TrainingPlan
+ - PlannedSession
+ - rules for view, add, change, delete, clone, save as template
+ - check team
+
+ - Special rules for Race (cannot be edited ex post)
+
+"""
+
# checkaccessplanuser (models.py)
# getrequestrower, getrequestplanrower
-# TEAM permissions
+# TEAM (group) permissions
+
+"""
+- These methods exist on team
+ - team.manager
+ - team.private
+
+- These rules apply to a team
+ - A Pro rower can be manager of only 1 team (as manager)
+ - A coach can have any number of teams (as manager)
+ - Basic user cannot manage a group
+ - Basic user cannot join groups led by Free Coach or Pro
+ - Basic athletes can be member of Coach led group
+ - Plan user can create 1 group (as manager) and not more
+ - Pro users (and higher) can join team led by other Pro (or higher) user
+ - On downgrade, Coach users lose all but their oldest team (add test!)
+
+"""
@rules.predicate
def is_team_manager(user,team):
@@ -177,3 +262,22 @@ rules.add_perm('teams.view_team',can_view_team)
rules.add_perm('teams.add_team',user_is_not_basic)
rules.add_perm('teams.change_team',is_team_manager)
rules.add_perm('teams.delete_team',is_team_manager)
+
+# RACING permissions
+
+"""
+ - VirtualRace
+ - rules to add, view, delete, change
+ - GeoCourse
+ - rules to add, view, delete, change
+"""
+
+
+# ANALYSIS permissions
+
+"""
+ - Conditions
+ - Alerts
+ - rules to add, view, delete, change
+ - cpdata
+"""
From d6c492080b72c06f3f92e8974158f708eb3eb4df Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 13 Jan 2020 16:20:50 +0100
Subject: [PATCH 14/80] added is_coach permissions to user views (not tested)
---
rowers/rower_rules.py | 36 ++++++++++++++++++++++++++--
rowers/views/statements.py | 3 +++
rowers/views/userviews.py | 49 ++++++++++----------------------------
3 files changed, 49 insertions(+), 39 deletions(-)
diff --git a/rowers/rower_rules.py b/rowers/rower_rules.py
index f55cbc61..dad9d33d 100644
--- a/rowers/rower_rules.py
+++ b/rowers/rower_rules.py
@@ -43,6 +43,15 @@ def user_is_not_basic(user):
def is_coach(user):
return user.rower.rowerplan in ['coach','freecoach']
+@rules.predicate
+def is_planmember(user):
+ try:
+ r = user.rower
+ except AttributeError:
+ return False
+
+ return r.rowerplan in ['coach','plan'] # freecoach?
+
@rules.predicate
def is_promember(user):
try:
@@ -50,7 +59,7 @@ def is_promember(user):
except AttributeError:
return False
- return r.rowerplan in ['pro','coach','plan']
+ return r.rowerplan in ['pro','coach','plan'] # freecoach?
@rules.predicate
def is_protrial(user):
@@ -69,6 +78,23 @@ def is_protrial(user):
ispromember = is_promember | is_protrial
+@rules.predicate
+def is_plan(user):
+ try:
+ r = user.rower
+ except AttributeError:
+ return False
+
+ if r.rowerplan == 'basic':
+ return r.plantrialexpires >= datetime.date.today()
+ if r.rowerplan == 'freecoach':
+ if r.mycoachgroup is not None:
+ return len(r.mycoachgroup)>=4
+
+ return False
+
+isplanmember = is_planmember | is_plantrial
+
# User / Coach relationships (Rower object)
@rules.predicate
@@ -99,6 +125,7 @@ def is_coach_user(user,rower):
return False
+# check if rower and user are members of the same team
@rules.predicate
def is_rower_team_member(user,rower):
if user.rower == rower:
@@ -115,6 +142,7 @@ def is_rower_team_member(user,rower):
return False
+# check if user can plan for the rower
@rules.predicate
def can_plan_user(user,rower):
try:
@@ -157,6 +185,7 @@ WORKOUT permissions
"""
+# check if user is owner or coach of owner of workout
@rules.predicate
def is_workout_user(user,workout):
if user.is_anonymous:
@@ -172,7 +201,7 @@ def is_workout_user(user,workout):
return is_coach_user(workout.user.user,user.rower)
-
+# check if user can see workout
@rules.predicate
def can_view_workout(user,workout):
if workout.privacy != 'private':
@@ -236,15 +265,18 @@ rules.add_perm('workout.view_workout',can_view_workout) # replaces checkworkoutu
"""
+# check if user is manager of the team
@rules.predicate
def is_team_manager(user,team):
return team.manager == user
+# check is user is member of team
@rules.predicate
def is_team_member(user,team):
members = team.rower.all()
return user in [member.user for member in members]
+# check if user can view team
@rules.predicate
def can_view_team(user,team):
# user based
diff --git a/rowers/views/statements.py b/rowers/views/statements.py
index b4d4bbf9..b3820bca 100644
--- a/rowers/views/statements.py
+++ b/rowers/views/statements.py
@@ -297,6 +297,9 @@ def get_workout_default_page(request,id):
else:
return reverse('workout_workflow_view',kwargs={'id':id})
+def get_user_by_id(request,id):
+ return get_object_or_404(User,pk=id)
+
def getrequestrower(request,rowerid=0,userid=0,notpermanent=False):
userid = int(userid)
diff --git a/rowers/views/userviews.py b/rowers/views/userviews.py
index 205104fe..b9e89509 100644
--- a/rowers/views/userviews.py
+++ b/rowers/views/userviews.py
@@ -5,31 +5,6 @@ from __future__ import unicode_literals
from rowers.views.statements import *
-@login_required()
-def survey(request):
-
- r = getrower(request.user)
-
- surveyform = SurveyForm()
-
- if request.method == 'POST':
- form = SurveyForm(request.POST)
- r.surveydone = True
- r.surveydonedate = timezone.now()
- r.save()
-
- nexturl = request.GET.get('next')
- return HttpResponseRedirect(nexturl)
-
- context = {
- 'teams':get_my_teams(request.user),
- 'rower':r,
- 'form':surveyform,
- }
-
-
- return render(request,'survey.html',context)
-
@login_required()
def start_trial_view(request):
r = getrower(request.user)
@@ -100,11 +75,11 @@ def start_plantrial_view(request):
return HttpResponseRedirect(url)
# Page where user can manage his favorite charts
-@login_required()
-def rower_favoritecharts_view(request,userid=0):
+@permission_required('rower.is_coach',fn=get_user_by_id,raise_exception=True)
+def rower_favoritecharts_view(request,id=0):
message = ''
successmessage = ''
- r = getrequestrower(request,userid=userid,notpermanent=True)
+ r = getrequestrower(request,userid=id,notpermanent=True)
favorites = FavoriteChart.objects.filter(user=r).order_by('id')
aantal = len(favorites)
favorites_data = [{'yparam1':f.yparam1,
@@ -167,9 +142,9 @@ def rower_favoritecharts_view(request,userid=0):
return render(request,'favoritecharts.html',context)
# page where user sets his export settings
-@login_required()
-def rower_exportsettings_view(request,userid=0):
- r = getrequestrower(request,userid=userid)
+@permission_required('rower.is_coach',fn=get_user_by_id,raise_exception=True)
+def rower_exportsettings_view(request,id=0):
+ r = getrequestrower(request,userid=id)
if request.method == 'POST':
form = RowerExportForm(request.POST)
if form.is_valid():
@@ -203,9 +178,9 @@ def rower_exportsettings_view(request,userid=0):
# Page where user can set his details
# Add email address to form so user can change his email address
-@login_required()
-def rower_edit_view(request,rowerid=0,userid=0,message=""):
- r = getrequestrower(request,rowerid=rowerid,userid=userid,notpermanent=True)
+@permission_required('rower.is_coach',fn=get_user_by_id,raise_exception=True)
+def rower_edit_view(request,rowerid=0,id=0,message=""):
+ r = getrequestrower(request,rowerid=rowerid,userid=id,notpermanent=True)
rowerid = r.id
@@ -298,9 +273,9 @@ def rower_edit_view(request,rowerid=0,userid=0,message=""):
# Page where user can set his details
# Add email address to form so user can change his email address
-@login_required()
-def rower_prefs_view(request,userid=0,message=""):
- r = getrequestrower(request,userid=userid,notpermanent=True)
+@permission_required('rower.is_coach',fn=get_user_by_id,raise_exception=True)
+def rower_prefs_view(request,id=0,message=""):
+ r = getrequestrower(request,userid=id,notpermanent=True)
rowerid = r.id
From 8de759425d8f0672196b07f220a901efdb33fbac Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 13 Jan 2020 16:26:35 +0100
Subject: [PATCH 15/80] Adding tests for all 4 settings pages
---
rowers/tests/test_permissions.py | 77 +++++++++++++++++++++++++++++++-
1 file changed, 75 insertions(+), 2 deletions(-)
diff --git a/rowers/tests/test_permissions.py b/rowers/tests/test_permissions.py
index bd8cc51d..d2ddc96a 100644
--- a/rowers/tests/test_permissions.py
+++ b/rowers/tests/test_permissions.py
@@ -644,7 +644,8 @@ class PermissionsViewTests(TestCase):
## Coach can edit on behalf of athlete if permitted
- def test_coach_edit_athlete_settings(self):
+ # 1, Preferences
+ def test_coach_edit_athlete_prefs(self):
self.rbasic.team.add(self.teamcoach)
self.rbasic.coachinggroups.add(self.coachinggroup)
@@ -656,7 +657,7 @@ class PermissionsViewTests(TestCase):
response = self.c.get(url)
self.assertEqual(response.status_code,200)
- def test_coach_edit_athlete_settings_not(self):
+ def test_coach_edit_athlete_prefs_not(self):
self.rbasic.team.add(self.teamcoach)
login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
@@ -667,6 +668,78 @@ class PermissionsViewTests(TestCase):
response = self.c.get(url)
self.assertEqual(response.status_code,403)
+ # 2, Favorite Charts
+ def test_coach_edit_athlete_settings(self):
+ self.rbasic.team.add(self.teamcoach)
+ self.rbasic.coachinggroups.add(self.coachinggroup)
+
+ login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
+ self.assertTrue(login)
+
+ url = reverse('rower_favoritecharts_view',kwargs={'userid':self.ubasic.id})
+
+ response = self.c.get(url)
+ self.assertEqual(response.status_code,200)
+
+ def test_coach_edit_athlete_settings_not(self):
+ self.rbasic.team.add(self.teamcoach)
+
+ login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
+ self.assertTrue(login)
+
+ url = reverse('rower_favoritecharts_view',kwargs={'userid':self.ubasic.id})
+
+ response = self.c.get(url)
+ self.assertEqual(response.status_code,403)
+
+ # 3, Export Settings
+ def test_coach_edit_athlete_settings(self):
+ self.rbasic.team.add(self.teamcoach)
+ self.rbasic.coachinggroups.add(self.coachinggroup)
+
+ login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
+ self.assertTrue(login)
+
+ url = reverse('rower_exportsettings_view',kwargs={'userid':self.ubasic.id})
+
+ response = self.c.get(url)
+ self.assertEqual(response.status_code,200)
+
+ def test_coach_edit_athlete_settings_not(self):
+ self.rbasic.team.add(self.teamcoach)
+
+ login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
+ self.assertTrue(login)
+
+ url = reverse('rower_exportsettings_view',kwargs={'userid':self.ubasic.id})
+
+ response = self.c.get(url)
+ self.assertEqual(response.status_code,403)
+
+ # 4, Account settings
+ def test_coach_edit_athlete_settings(self):
+ self.rbasic.team.add(self.teamcoach)
+ self.rbasic.coachinggroups.add(self.coachinggroup)
+
+ login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
+ self.assertTrue(login)
+
+ url = reverse('rower_edit_view',kwargs={'userid':self.ubasic.id})
+
+ response = self.c.get(url)
+ self.assertEqual(response.status_code,200)
+
+ def test_coach_edit_athlete_settings_not(self):
+ self.rbasic.team.add(self.teamcoach)
+
+ login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
+ self.assertTrue(login)
+
+ url = reverse('rower_edit_view',kwargs={'userid':self.ubasic.id})
+
+ response = self.c.get(url)
+ self.assertEqual(response.status_code,403)
+
## Coach can run analytics for athlete
@patch('rowers.dataprep.read_cols_df_sql', side_effect = mocked_read_df_cols_sql_multistats)
def test_coach_edit_athlete_analysis(self,mocked_df):
From 926ff6d5e5d62eeec0b0d25c1ed1beccc13c1457 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 13 Jan 2020 16:42:02 +0100
Subject: [PATCH 16/80] fixes to pass tests
---
rowers/rower_rules.py | 2 +-
rowers/tests/test_permissions.py | 20 ++++++++++----------
rowers/urls.py | 8 ++++----
rowsandall_app/urls.py | 2 +-
4 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/rowers/rower_rules.py b/rowers/rower_rules.py
index dad9d33d..bac7b6ed 100644
--- a/rowers/rower_rules.py
+++ b/rowers/rower_rules.py
@@ -79,7 +79,7 @@ def is_protrial(user):
ispromember = is_promember | is_protrial
@rules.predicate
-def is_plan(user):
+def is_plantrial(user):
try:
r = user.rower
except AttributeError:
diff --git a/rowers/tests/test_permissions.py b/rowers/tests/test_permissions.py
index d2ddc96a..ad2d745e 100644
--- a/rowers/tests/test_permissions.py
+++ b/rowers/tests/test_permissions.py
@@ -652,7 +652,7 @@ class PermissionsViewTests(TestCase):
login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
self.assertTrue(login)
- url = reverse('rower_prefs_view',kwargs={'userid':self.ubasic.id})
+ url = reverse('rower_prefs_view',kwargs={'id':self.ubasic.id})
response = self.c.get(url)
self.assertEqual(response.status_code,200)
@@ -663,7 +663,7 @@ class PermissionsViewTests(TestCase):
login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
self.assertTrue(login)
- url = reverse('rower_prefs_view',kwargs={'userid':self.ubasic.id})
+ url = reverse('rower_prefs_view',kwargs={'id':self.ubasic.id})
response = self.c.get(url)
self.assertEqual(response.status_code,403)
@@ -676,7 +676,7 @@ class PermissionsViewTests(TestCase):
login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
self.assertTrue(login)
- url = reverse('rower_favoritecharts_view',kwargs={'userid':self.ubasic.id})
+ url = reverse('rower_favoritecharts_view',kwargs={'id':self.ubasic.id})
response = self.c.get(url)
self.assertEqual(response.status_code,200)
@@ -687,7 +687,7 @@ class PermissionsViewTests(TestCase):
login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
self.assertTrue(login)
- url = reverse('rower_favoritecharts_view',kwargs={'userid':self.ubasic.id})
+ url = reverse('rower_favoritecharts_view',kwargs={'id':self.ubasic.id})
response = self.c.get(url)
self.assertEqual(response.status_code,403)
@@ -700,7 +700,7 @@ class PermissionsViewTests(TestCase):
login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
self.assertTrue(login)
- url = reverse('rower_exportsettings_view',kwargs={'userid':self.ubasic.id})
+ url = reverse('rower_exportsettings_view',kwargs={'id':self.ubasic.id})
response = self.c.get(url)
self.assertEqual(response.status_code,200)
@@ -711,7 +711,7 @@ class PermissionsViewTests(TestCase):
login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
self.assertTrue(login)
- url = reverse('rower_exportsettings_view',kwargs={'userid':self.ubasic.id})
+ url = reverse('rower_exportsettings_view',kwargs={'id':self.ubasic.id})
response = self.c.get(url)
self.assertEqual(response.status_code,403)
@@ -724,7 +724,7 @@ class PermissionsViewTests(TestCase):
login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
self.assertTrue(login)
- url = reverse('rower_edit_view',kwargs={'userid':self.ubasic.id})
+ url = reverse('rower_edit_view',kwargs={'id':self.ubasic.id})
response = self.c.get(url)
self.assertEqual(response.status_code,200)
@@ -735,7 +735,7 @@ class PermissionsViewTests(TestCase):
login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
self.assertTrue(login)
- url = reverse('rower_edit_view',kwargs={'userid':self.ubasic.id})
+ url = reverse('rower_edit_view',kwargs={'id':self.ubasic.id})
response = self.c.get(url)
self.assertEqual(response.status_code,403)
@@ -981,7 +981,7 @@ class PermissionsViewTests(TestCase):
login = self.c.login(username=self.uplan2.username, password=self.uplan2password)
self.assertTrue(login)
- url = reverse('rower_prefs_view',kwargs={'userid':self.ubasic.id})
+ url = reverse('rower_prefs_view',kwargs={'id':self.ubasic.id})
response = self.c.get(url)
self.assertEqual(response.status_code,403)
@@ -1103,7 +1103,7 @@ class PermissionsViewTests(TestCase):
login = self.c.login(username=self.upro2.username, password=self.upro2password)
self.assertTrue(login)
- url = reverse('rower_prefs_view',kwargs={'userid':self.ubasic.id})
+ url = reverse('rower_prefs_view',kwargs={'id':self.ubasic.id})
response = self.c.get(url)
self.assertEqual(response.status_code,403)
diff --git a/rowers/urls.py b/rowers/urls.py
index 15b7768f..de413d87 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -447,7 +447,7 @@ urlpatterns = [
re_path(r'^user-multiflex-data/$',views.multiflex_data,name='multiflex_data'),
re_path(r'^me/deactivate/$',views.deactivate_user,name='deactivate_user'),
re_path(r'^me/delete/$',views.remove_user,name='remove_user'),
- re_path(r'^survey/$',views.survey,name='survey'),
+# re_path(r'^survey/$',views.survey,name='survey'),
re_path(r'^me/gdpr-optin-confirm/?/$',views.user_gdpr_confirm,name='user_gdpr_confirm'),
re_path(r'^me/gdpr-optin-confirm/$',views.user_gdpr_confirm,name='user_gdpr_confirm'),
re_path(r'^me/gdpr-optin/?/$',views.user_gdpr_optin,name='user_gdpr_optin'),
@@ -455,7 +455,7 @@ urlpatterns = [
re_path(r'^me/teams/$',views.rower_teams_view,name='rower_teams_view'),
re_path(r'^me/calcdps/$',views.rower_calcdps_view,name='rower_calcdps_view'),
re_path(r'^me/exportsettings/$',views.rower_exportsettings_view,name='rower_exportsettings_view'),
- re_path(r'^me/exportsettings/user/(?P\d+)/$',views.rower_exportsettings_view,name='rower_exportsettings_view'),
+ re_path(r'^me/exportsettings/user/(?P\d+)/$',views.rower_exportsettings_view,name='rower_exportsettings_view'),
re_path(r'^team/(?P\d+)/$',views.team_view,name='team_view'),
re_path(r'^team/(?P\d+)/memberstats/$',views.team_members_stats_view,name='team_members_stats_view'),
re_path(r'^team/(?P\d+)/edit/$',views.team_edit_view,name='team_edit_view'),
@@ -500,10 +500,10 @@ urlpatterns = [
re_path(r'^me/request/(\w+.*)/$',views.manager_requests_view,name='manager_requests_view'),
re_path(r'^me/request/$',views.manager_requests_view,name='manager_requests_view'),
re_path(r'^me/edit/$',views.rower_edit_view,name='rower_edit_view'),
- re_path(r'^me/edit/user/(?P\d+)/$',views.rower_edit_view,name='rower_edit_view'),
+ re_path(r'^me/edit/user/(?P\d+)/$',views.rower_edit_view,name='rower_edit_view'),
re_path(r'^me/preferences/$',views.rower_prefs_view,name='rower_prefs_view'),
re_path(r'^me/transactions/$',views.transactions_view,name='transactions_view'),
- re_path(r'^me/preferences/user/(?P\d+)/$',views.rower_prefs_view,name='rower_prefs_view'),
+ re_path(r'^me/preferences/user/(?P\d+)/$',views.rower_prefs_view,name='rower_prefs_view'),
re_path(r'^me/edit/(.+.*)/$',views.rower_edit_view,name='rower_edit_view'),
re_path(r'^me/c2authorize/$',views.rower_c2_authorize,name='rower_c2_authorize'),
re_path(r'^me/polarauthorize/$',views.rower_polar_authorize,name='rower_polar_authorize'),
diff --git a/rowsandall_app/urls.py b/rowsandall_app/urls.py
index b6bcca18..21c5e454 100644
--- a/rowsandall_app/urls.py
+++ b/rowsandall_app/urls.py
@@ -66,7 +66,7 @@ urlpatterns += [
{'next_page': '/'},
name='logout',),
re_path(r'^rowers/',include('rowers.urls')),
- re_path(r'^survey/',include('survey.urls')),
+# re_path(r'^survey/',include('survey.urls')),
# re_path(r'^cvkbrno/',include('cvkbrno.urls')),
# re_path(r'^admin/rq/',include('django_rq_dashboard.urls')),
re_path(r'^call\_back',rowersviews.rower_process_callback),
From 489271317c7061979c68b1ae95b20ce0db786b19 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 13 Jan 2020 19:40:22 +0100
Subject: [PATCH 17/80] passing tests
---
rowers/rower_rules.py | 15 ++++++---------
rowers/tests/test_permissions.py | 1 +
rowers/urls.py | 2 +-
rowers/views/planviews.py | 24 ++++++++++++------------
rowers/views/statements.py | 12 +++++++++---
5 files changed, 29 insertions(+), 25 deletions(-)
diff --git a/rowers/rower_rules.py b/rowers/rower_rules.py
index bac7b6ed..d60b3682 100644
--- a/rowers/rower_rules.py
+++ b/rowers/rower_rules.py
@@ -103,15 +103,12 @@ def can_plan(user):
# checks if rower is coach of user
@rules.predicate
-def is_coach_user(user,rower):
- try:
- r = user.rower
- except AttributeError:
- return False
-
- if rower == r:
+def is_coach_user(usercoach,userrower):
+ if usercoach == userrower:
return True
+ r = userrower.rower
+
coaches = []
for group in r.coachinggroups.all():
@@ -120,7 +117,7 @@ def is_coach_user(user,rower):
coaches.append(coach)
for coach in coaches:
- if rower == coach:
+ if usercoach.rower == coach:
return True
return False
@@ -199,7 +196,7 @@ def is_workout_user(user,workout):
if workout.user == r:
return True
- return is_coach_user(workout.user.user,user.rower)
+ return is_coach_user(user,workout.user.user)
# check if user can see workout
@rules.predicate
diff --git a/rowers/tests/test_permissions.py b/rowers/tests/test_permissions.py
index ad2d745e..1671d82d 100644
--- a/rowers/tests/test_permissions.py
+++ b/rowers/tests/test_permissions.py
@@ -13,6 +13,7 @@ from django.db import transaction
import rowers.teams as teams
import rowers.plannedsessions as plannedsessions
+from rowers.rower_rules import is_coach_user
@override_settings(TESTING=True)
class PermissionsFreeCoach(TestCase):
diff --git a/rowers/urls.py b/rowers/urls.py
index de413d87..b8924a1a 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -518,7 +518,7 @@ urlpatterns = [
re_path(r'^me/tprefresh/$',views.rower_tp_token_refresh,name='rower_tp_token_refresh'),
re_path(r'^me/c2refresh/$',views.rower_c2_token_refresh,name='rower_c2_token_refresh'),
re_path(r'^me/favoritecharts/$',views.rower_favoritecharts_view,name='rower_favoritecharts_view'),
- re_path(r'^me/favoritecharts/user/(?P\d+)/$',views.rower_favoritecharts_view,name='rower_favoritecharts_view'),
+ re_path(r'^me/favoritecharts/user/(?P\d+)/$',views.rower_favoritecharts_view,name='rower_favoritecharts_view'),
# re_path(r'^me/workflowconfig/$',views.workout_workflow_config_view),
re_path(r'^me/workflowconfig2/$',views.workout_workflow_config2_view,name='workout_workflow_config2_view'),
re_path(r'^me/workflowconfig2/user/(?P\d+)/$',views.workout_workflow_config2_view,name='workout_workflow_config2_view'),
diff --git a/rowers/views/planviews.py b/rowers/views/planviews.py
index ae16fb94..5b7b76d1 100644
--- a/rowers/views/planviews.py
+++ b/rowers/views/planviews.py
@@ -1959,7 +1959,7 @@ class PlannedSessionDelete(DeleteView):
def get_object(self, *args, **kwargs):
obj = super(PlannedSessionDelete, self).get_object(*args, **kwargs)
m = Rower.objects.get(user=obj.manager)
- if not is_coach_user(m.user,self.request.user.rower):
+ if not is_coach_user(self.request.user,m.user):
raise PermissionDenied('You are not allowed to delete this planned session')
return obj
@@ -2103,7 +2103,7 @@ def rower_delete_trainingtarget(request,id=0):
except TrainingPlan.DoesNotExist:
raise Http404("Training Plan Does Not Exist")
- if is_coach_user(target.manager,request.user.rower):
+ if is_coach_user(request.user,target.manager):
target.delete()
messages.info(request,"We have deleted the training target")
else:
@@ -2123,7 +2123,7 @@ def rower_delete_trainingplan(request,id=0):
except TrainingPlan.DoesNotExist:
raise Http404("Training Plan Does Not Exist")
- if is_coach_user(plan.manager.user,request.user.rower):
+ if is_coach_user(request.user,plan.manager.user):
plan.delete()
messages.info(request,"We have deleted the training plan")
else:
@@ -2140,7 +2140,7 @@ class TrainingPlanDelete(DeleteView):
def get_object(self, *args, **kwargs):
obj = super(TrainingPlanDelete, self).get_object(*args, **kwargs)
- if not is_coach_user(obj.manager.user,self.request.user.rower):
+ if not is_coach_user(self.request.user,obj.manager.user):
raise PermissionDenied('You are not allowed to delete this training plan')
return obj
@@ -2206,7 +2206,7 @@ class MicroCycleDelete(DeleteView):
def get_object(self, *args, **kwargs):
obj = super(MicroCycleDelete, self).get_object(*args, **kwargs)
- if not is_coach_user(obj.plan.plan.plan.manager.user,self.request.user.rower):
+ if not is_coach_user(self.request.user,obj.plan.plan.plan.manager.user):
raise PermissionDenied('You are not allowed to delete this training plan cycle')
return obj
@@ -2268,7 +2268,7 @@ class MesoCycleDelete(DeleteView):
def get_object(self, *args, **kwargs):
obj = super(MesoCycleDelete, self).get_object(*args, **kwargs)
- if not is_coach_user(obj.plan.plan.manager.user,self.request.user.rower):
+ if not is_coach_user(self.request.user,obj.plan.plan.manager.user):
raise PermissionDenied('You are not allowed to delete this training plan cycle')
return obj
@@ -2322,7 +2322,7 @@ class MacroCycleDelete(DeleteView):
def get_object(self, *args, **kwargs):
obj = super(MacroCycleDelete, self).get_object(*args, **kwargs)
- if not is_coach_user(obj.plan.manager.user,self.request.user.rower):
+ if not is_coach_user(self.request.user,obj.plan.manager.user):
raise PermissionDenied('You are not allowed to delete this training plan cycle')
return obj
@@ -2345,7 +2345,7 @@ def rower_trainingplan_execution_view(request,
plan = TrainingPlan.objects.get(id=id)
except TrainingPlan.DoesNotExist:
raise Http404("Training Plan Does Not Exist")
- if not is_coach_user(plan.manager.user,request.user.rower):
+ if not is_coach_user(request.user,plan.manager.user):
if request.user.rower not in plan.rowers.all():
raise PermissionDenied("Access denied")
@@ -2439,7 +2439,7 @@ def rower_trainingplan_view(request,
r = getrequestrower(request,userid=userid)
- if not is_coach_user(plan.manager.user,request.user.rower):
+ if not is_coach_user(request.user,plan.manager.user):
if request.user.rower not in plan.rowers.all():
raise PermissionDenied("Access denied")
@@ -2568,7 +2568,7 @@ class TrainingMacroCycleUpdate(UpdateView):
if obj.plan.manager is not None and self.request.user.rower != obj.plan.manager:
raise PermissionDenied('You are not allowed to edit this training plan cycle')
- if not is_coach_user(obj.plan.manager.user,self.request.user.rower):
+ if not is_coach_user(self.request.user,obj.plan.manager.user):
raise PermissionDenied('You are not allowed to edit this training plan cycle')
else:
obj.type = 'userdefined'
@@ -2850,7 +2850,7 @@ def planmesocyclebyweek(request,id=0,userid=0):
except TrainingMesoCycle.DoesNotExist:
raise Http404("Training Cycle does not exist")
- if not is_coach_user(cycle.plan.plan.manager.user,request.user.rower):
+ if not is_coach_user(request.user,cycle.plan.plan.manager.user):
raise PermissionDenied("You are not allowed to do this")
micros = TrainingMicroCycle.objects.filter(plan=cycle)
@@ -2905,7 +2905,7 @@ def planmacrocyclebymonth(request,id=0,userid=0):
except TrainingMacroCycle.DoesNotExist:
raise Http404("Training Cycle does not exist")
- if not is_coach_user(cycle.plan.manager.user,request.user.rower):
+ if not is_coach_user(request.user,cycle.plan.manager.user):
raise PermissionDenied("You are not allowed to do this")
mesos = TrainingMesoCycle.objects.filter(plan=cycle)
diff --git a/rowers/views/statements.py b/rowers/views/statements.py
index b3820bca..b61f1d20 100644
--- a/rowers/views/statements.py
+++ b/rowers/views/statements.py
@@ -297,7 +297,13 @@ def get_workout_default_page(request,id):
else:
return reverse('workout_workflow_view',kwargs={'id':id})
-def get_user_by_id(request,id):
+def get_user_by_id(*args,**kwargs):
+ request = args[0]
+ try:
+ id = args[1]
+ except IndexError:
+ id = request.user.id
+
return get_object_or_404(User,pk=id)
def getrequestrower(request,rowerid=0,userid=0,notpermanent=False):
@@ -327,7 +333,7 @@ def getrequestrower(request,rowerid=0,userid=0,notpermanent=False):
except Rower.DoesNotExist:
raise Http404("Rower doesn't exist")
- if userid != 0 and not is_coach_user(u,request.user.rower):
+ if userid != 0 and not is_coach_user(request.user,u):
raise PermissionDenied("You have no access to this user")
if notpermanent == False:
@@ -360,7 +366,7 @@ def getrequestplanrower(request,rowerid=0,userid=0,notpermanent=False):
except Rower.DoesNotExist:
raise Http404("Rower doesn't exist")
- if not is_coach_user(r.user,request.user.rower):
+ if not is_coach_user(request.user,r.user):
raise PermissionDenied("You have no access to this user")
if notpermanent == False:
From a4363085619d8c4f76c50d86b287da3dfc635b71 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 13 Jan 2020 21:54:05 +0100
Subject: [PATCH 18/80] added first plan test
---
rowers/decorators.py | 1 -
rowers/models.py | 3 +++
rowers/rower_rules.py | 22 +++++++++++++++++++++-
rowers/views/statements.py | 6 ++++++
rowers/views/teamviews.py | 2 ++
rowers/views/workoutviews.py | 20 +++-----------------
6 files changed, 35 insertions(+), 19 deletions(-)
diff --git a/rowers/decorators.py b/rowers/decorators.py
index f9d03ed7..4bd1c7a3 100644
--- a/rowers/decorators.py
+++ b/rowers/decorators.py
@@ -48,4 +48,3 @@ def login_required_message(function=None, message=default_message):
if function:
return actual_decorator(function)
return actual_decorator
-
diff --git a/rowers/models.py b/rowers/models.py
index c0c9c520..b1f9065d 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -907,6 +907,9 @@ class Rower(models.Model):
super(Rower, self).save(*args, **kwargs)
+ def get_managed_teams(self):
+ return Team.objects.filter(manager=self.user)
+
class DeactivateUserForm(forms.ModelForm):
class Meta:
model = User
diff --git a/rowers/rower_rules.py b/rowers/rower_rules.py
index d60b3682..40c63499 100644
--- a/rowers/rower_rules.py
+++ b/rowers/rower_rules.py
@@ -159,7 +159,7 @@ def can_plan_user(user,rower):
rules.add_perm('rower.can_plan',can_plan_user) # replaces checkaccessplanuser
rules.add_perm('rower.is_coach',is_coach_user) # replaces checkaccessuser
-
+rules.add_perm('rower.is_pro',ispromember)
# WORKOUT permissions
@@ -240,6 +240,26 @@ rules.add_perm('workout.view_workout',can_view_workout) # replaces checkworkoutu
"""
+# check if user has view access to session
+@rules.predicate
+def can_view_session(user,session):
+ # session manager can view session
+ if user == session.manager:
+ return True
+ # if you're a rower in the session you can view it
+ if user.rower in session.rower.all():
+ return True
+ # coach users can view sessions created by their team members
+ if is_coach(user):
+ teams = user.rower.get_managed_teams()
+ teamusers = [member.u for member in t.rower.all()]
+ if session.manager in teamusers:
+ return True
+
+ return False
+
+rules.add_perm('plannedsession.view_session',can_view_session)
+
# checkaccessplanuser (models.py)
# getrequestrower, getrequestplanrower
diff --git a/rowers/views/statements.py b/rowers/views/statements.py
index b61f1d20..5d3a969d 100644
--- a/rowers/views/statements.py
+++ b/rowers/views/statements.py
@@ -282,11 +282,17 @@ def getfavorites(r,row):
return favorites,maxfav
+def get_promember(request,*args,**kwargs):
+ return request.user
+
def get_workout_by_opaqueid(request,id,**kwargs):
pk = encoder.decode_hex(id)
return get_object_or_404(Workout,pk=pk)
+def get_session_by_pk(request,id):
+ return get_object_or_404(PlannedSession,pk=id)
+
def get_workout_default_page(request,id):
if request.user.is_anonymous:
return reverse('workout_view',kwargs={'id':id})
diff --git a/rowers/views/teamviews.py b/rowers/views/teamviews.py
index 3e9d942e..0667b472 100644
--- a/rowers/views/teamviews.py
+++ b/rowers/views/teamviews.py
@@ -10,6 +10,8 @@ from rowers.rower_rules import *
def get_team_by_pk(request,team_id):
return get_object_or_404(Team,pk=team_id)
+
+
#@login_required()
@permission_required('teams.view_team',fn=get_team_by_pk,raise_exception=True)
def team_view(request,team_id=0,userid=0):
diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py
index 4d70bfa3..a8628322 100644
--- a/rowers/views/workoutviews.py
+++ b/rowers/views/workoutviews.py
@@ -1129,9 +1129,6 @@ def workouts_join_select(request,
except Team.DoesNotExist:
theteam = 0
- if r.rowerplan == 'basic' and theteam==0:
- raise PermissionDenied("Access denied")
-
if theteam and (theteam.viewing == 'allmembers' or theteam.manager == request.user):
workouts = Workout.objects.filter(team=theteam,
startdatetime__gte=startdate,
@@ -1564,7 +1561,8 @@ def virtualevent_compare_view(request,id=0):
})
-@login_required()
+@permission_required('plannedsession.view_session',
+ fn=get_session_by_pk,raise_exception=True)
def plannedsession_compare_view(request,id=0,userid=0):
r = getrequestrower(request,userid=userid)
@@ -1573,18 +1571,6 @@ def plannedsession_compare_view(request,id=0,userid=0):
except PlannedSession.DoesNotExist:
raise Http404("Planned session does not exist")
- m = ps.manager
- mm = m.rower
-
- if ps.manager != request.user:
- if r.rowerplan == 'coach':
- teams = Team.objects.filter(manager=request.user)
- members = Rower.objects.filter(team__in=teams).distinct()
- teamusers = [m.user for m in members]
- if ps.manager not in teamusers:
- raise PermissionDenied("You do not have access to this session")
- elif r not in ps.rower.all():
- raise PermissionDenied("You do not have access to this session")
workouts = Workout.objects.filter(plannedsession=ps)
@@ -5876,7 +5862,7 @@ class VideoDelete(DeleteView):
def get_object(self, *args, **kwargs):
obj = super(VideoDelete, self).get_object(*args, **kwargs)
- if not is_coach_user(obj.workout.user,self.request.user):
+ if not is_coach_user(self.request.user,obj.workout.user):
raise PermissionDenied('You are not allowed to delete this analysis')
return obj
From f627b5e925b7140104063ff3927784a028eab139 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Tue, 14 Jan 2020 15:59:53 +0100
Subject: [PATCH 19/80] half way through planviews, added can_change_session
---
rowers/rower_rules.py | 17 ++++
rowers/templatetags/rowerfilters.py | 8 +-
rowers/urls.py | 4 +-
rowers/views/analysisviews.py | 2 +-
rowers/views/planviews.py | 122 ++++++++--------------------
rowers/views/statements.py | 19 +----
6 files changed, 57 insertions(+), 115 deletions(-)
diff --git a/rowers/rower_rules.py b/rowers/rower_rules.py
index 40c63499..74709c9d 100644
--- a/rowers/rower_rules.py
+++ b/rowers/rower_rules.py
@@ -243,6 +243,10 @@ rules.add_perm('workout.view_workout',can_view_workout) # replaces checkworkoutu
# check if user has view access to session
@rules.predicate
def can_view_session(user,session):
+ if session.sessiontype in ['race','indoorrace']:
+ return True
+ if user.is_anonymous:
+ return False
# session manager can view session
if user == session.manager:
return True
@@ -258,7 +262,20 @@ def can_view_session(user,session):
return False
+@rules.predicate
+def can_change_session(user,session):
+ if user.is_anonymous:
+ return False
+ # session part of a race should not be changed through the session interface
+ if session.sessiontype in ['race','indoorrace']:
+ return False
+ if user == session.manager:
+ return True
+
+ return False
+
rules.add_perm('plannedsession.view_session',can_view_session)
+rules.add_perm('plannedsession.change_session',can_change_session)
# checkaccessplanuser (models.py)
# getrequestrower, getrequestplanrower
diff --git a/rowers/templatetags/rowerfilters.py b/rowers/templatetags/rowerfilters.py
index cb0ca2d2..60b02cbd 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.rower_rules import is_coach_user, is_workout_user
+from rowers.rower_rules import is_coach_user, is_workout_user, isplanmember,ispromember
from rowers.mytypes import otwtypes
from rowers.utils import NoTokenError
@@ -430,7 +430,7 @@ def get_field_id(id,s,form):
from rowers.models import Rower,Team,TrainingPlan,TrainingTarget
-from rowers.views import ispromember
+
@register.filter
def is_promember(user):
return ispromember(user)
@@ -440,10 +440,10 @@ def is_manager(user):
r = Rower.objects.get(user=user)
return 'coach' in r.rowerplan
-from rowers.views import hasplannedsessions
+
@register.filter
def is_planmember(user):
- return hasplannedsessions(user)
+ return isplanmember(user)
@register.filter
def get_age(r):
diff --git a/rowers/urls.py b/rowers/urls.py
index b8924a1a..b8c65fb6 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -635,9 +635,9 @@ urlpatterns = [
re_path(r'^sessions/teamcreate/team/$',
views.plannedsession_teamcreate_view,
name='plannedsession_teamcreate_view'),
- re_path(r'^sessions/teamedit/(?P\d+)/$',views.plannedsession_teamedit_view,
+ re_path(r'^sessions/teamedit/(?P\d+)/$',views.plannedsession_teamedit_view,
name='plannedsession_teamedit_view'),
- re_path(r'^sessions/teamedit/(?P\d+)/user/(?P\d+)/$',
+ re_path(r'^sessions/teamedit/(?P\d+)/user/(?P\d+)/$',
views.plannedsession_teamedit_view,
name='plannedsession_teamedit_view'),
re_path(r'^sessions/create/$',views.plannedsession_create_view,
diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py
index 4cf62a9e..d7792db5 100644
--- a/rowers/views/analysisviews.py
+++ b/rowers/views/analysisviews.py
@@ -1329,7 +1329,7 @@ def planrequired_view(request):
return HttpResponseRedirect(reverse('paidplans'))
-@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans",
+@user_passes_test(isplanmember,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
def fitnessmetric_view(request,id=0,mode='rower',
diff --git a/rowers/views/planviews.py b/rowers/views/planviews.py
index 5b7b76d1..c5c6d9ac 100644
--- a/rowers/views/planviews.py
+++ b/rowers/views/planviews.py
@@ -5,27 +5,11 @@ from __future__ import unicode_literals
from rowers.views.statements import *
-@login_required()
+@login_required
+@permission_required('plannedsession.view_session',fn=get_session_by_pk,raise_exception=True)
def plannedsession_comment_view(request,id=0,userid=0):
r = getrequestplanrower(request,userid=userid)
-
- try:
- ps = PlannedSession.objects.get(id=id)
- except PlannedSession.DoesNotExist:
- raise Http404("Planned Session does not exist")
-
- m = ps.manager
- mm = Rower.objects.get(user=m)
-
- if ps.manager != request.user and ps.sessiontype not in ['race','indoorrace']:
- if r.rowerplan == 'coach' and r not in ps.rower.all():
- teams = Team.objects.filter(manager=request.user)
- members = Rower.objects.filter(team__in=teams).distinct()
- teamusers = [m.user for m in members]
- if ps.manager not in teamusers:
- raise PermissionDenied("You do not have access to this session")
- elif r not in ps.rower.all():
- raise PermissionDenied("You do not have access to this session")
+ ps = get_object_or_404(PlannedSession,pk=id)
comments = PlannedSessionComment.objects.filter(plannedsession=ps).order_by("created")
@@ -171,7 +155,7 @@ def plannedsession_comment_view(request,id=0,userid=0):
})
# Cloning sessions
-@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans/",
+@user_passes_test(isplanmember,login_url="/rowers/paidplans/",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
def plannedsession_multiclone_view(
@@ -322,7 +306,7 @@ def plannedsession_multiclone_view(
)
# Individual user creates training for himself
-@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans/",
+@user_passes_test(isplanmember,login_url="/rowers/paidplans/",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
def plannedsession_create_view(request,
@@ -477,7 +461,7 @@ def plannedsession_create_view(request,
'timeperiod':timeperiod,
})
-@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans/",
+@user_passes_test(isplanmember,login_url="/rowers/paidplans/",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
def plannedsession_multicreate_view(request,
@@ -613,7 +597,7 @@ def plannedsession_multicreate_view(request,
return render(request,'plannedsession_multicreate.html',context)
# Manager creates sessions for entire team
-@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans/",
+@user_passes_test(isplanmember,login_url="/rowers/paidplans/",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
def plannedsession_teamcreate_view(request,
@@ -786,21 +770,16 @@ def plannedsession_teamcreate_view(request,
})
# Manager edits sessions for entire team
-@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans/",
+@user_passes_test(isplanmember,login_url="/rowers/paidplans/",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
+@permission_required('plannedsession.change_session',fn=get_session_by_pk,raise_exception=True)
def plannedsession_teamedit_view(request,
- sessionid=0,userid=0):
+ id=0,userid=0):
r = getrequestplanrower(request,userid=userid)
-
- try:
- ps = PlannedSession.objects.get(id=sessionid)
- except PlannedSession.DoesNotExist:
- raise Http404("This session doesn't exist")
- if not ps.manager == request.user:
- raise PermissionDenied("You are not the manager of this session")
+ ps = get_object_or_404(PlannedSession,pk=id)
teams = Team.objects.filter(manager=request.user)
teamchoices = [(team.id, team.name) for team in teams]
@@ -1402,7 +1381,8 @@ def plannedsessions_manage_view(request,userid=0,
# Clone an existing planned session
# need clarity on cloning behavior time shift
-@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans/",
+@permission_required('plannedsession.change_session',fn=get_session_by_pk,raise_exception=True)
+@user_passes_test(isplanmember,login_url="/rowers/paidplans/",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
def plannedsession_clone_view(request,id=0,userid=0):
@@ -1420,13 +1400,7 @@ def plannedsession_clone_view(request,id=0,userid=0):
except IndexError:
trainingplan = None
- try:
- ps = PlannedSession.objects.get(id=id)
- except PlannedSession.DoesNotExist:
- raise Http404("Planned Session does not exist")
-
- if ps.manager != request.user:
- raise PermissionDenied("You are not allowed to clone this planned session")
+ ps = get_object_or_404(PlannedSession,pk=id)
rowers = ps.rower.all()
teams = ps.team.all()
@@ -1470,7 +1444,8 @@ def plannedsession_clone_view(request,id=0,userid=0):
# Clone an existing planned session
# need clarity on cloning behavior time shift
-@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans/",
+@permission_required('plannedsession.change_session',fn=get_session_by_pk,raise_exception=True)
+@user_passes_test(isplanmember,login_url="/rowers/paidplans/",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
def plannedsession_teamclone_view(request,id=0):
@@ -1497,13 +1472,7 @@ def plannedsession_teamclone_view(request,id=0):
except IndexError:
trainingplan = None
- try:
- ps = PlannedSession.objects.get(id=id)
- except PlannedSession.DoesNotExist:
- raise Http404("Planned Session does not exist")
-
- if ps.manager != request.user:
- raise PermissionDenied("You are not allowed to clone this planned session")
+ ps = get_object_or_404(PlannedSession,pk=id)
ps.pk = None
@@ -1534,7 +1503,8 @@ def plannedsession_teamclone_view(request,id=0):
return HttpResponseRedirect(url)
-@user_passes_test(hasplannedsessions, login_url="/rowers/paidplans/",
+@permission_required('plannedsession.change_session',fn=get_session_by_pk,raise_exception=True)
+@user_passes_test(isplanmember, login_url="/rowers/paidplans/",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
def plannedsession_totemplate_view(request,id=0):
@@ -1543,13 +1513,7 @@ def plannedsession_totemplate_view(request,id=0):
startdate, enddate = get_dates_timeperiod(request)
- try:
- ps = PlannedSession.objects.get(id=id)
- except PlannedSession.DoesNotExist:
- raise Http404("Planned Session does not exist")
-
- if ps.manager != request.user:
- raise PermissionDenied("You can only store your own sessions as a template")
+ ps = get_object_or_404(PlannedSession,pk=id)
ps.pk = None
ps.id = None
@@ -1567,7 +1531,8 @@ def plannedsession_totemplate_view(request,id=0):
return HttpResponseRedirect(url)
# Edit an existing planned session
-@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans/",
+@permission_required('plannedsession.change_session',fn=get_session_by_pk,raise_exception=True)
+@user_passes_test(isplanmember,login_url="/rowers/paidplans/",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
def plannedsession_edit_view(request,id=0,userid=0):
@@ -1587,16 +1552,7 @@ def plannedsession_edit_view(request,id=0,userid=0):
except IndexError:
trainingplan = None
- try:
- ps = PlannedSession.objects.get(id=id)
- except PlannedSession.DoesNotExist:
- raise Http404("Planned Session does not exist")
-
- if ps.manager != request.user:
- raise PermissionDenied("You are not allowed to edit this planned session")
-
- if ps.sessiontype in ['race','indoorrace']:
- raise PermissionDenied("You are not allowed to edit this planned session because it is a race")
+ ps = get_object_or_404(PlannedSession,pk=id)
if ps.team.all() or ps.rower.all().count()>1:
url = reverse(plannedsession_teamedit_view,
@@ -1714,16 +1670,12 @@ def plannedsession_detach_view(request,id=0,psid=0):
return HttpResponseRedirect(url)
@login_required()
+@permission_required('plannedsession.view_session',fn=get_session_by_pk,raise_exception=True)
def plannedsession_view(request,id=0,userid=0):
r = getrequestplanrower(request,userid=userid)
-
-
- try:
- ps = PlannedSession.objects.get(id=id)
- except PlannedSession.DoesNotExist:
- raise Http404("Planned Session does not exist")
+ ps = get_object_or_404(PlannedSession,pk=id)
if ps.sessiontype in ['race','indoorrace']:
url = reverse('virtualevent_view',
@@ -1740,16 +1692,6 @@ def plannedsession_view(request,id=0,userid=0):
m = ps.manager
mm = Rower.objects.get(user=m)
- if ps.manager != request.user:
- if 'coach' in r.rowerplan and r not in ps.rower.all():
- teams = Team.objects.filter(manager=request.user)
- members = Rower.objects.filter(team__in=teams).distinct()
- teamusers = [m.user for m in members]
- if ps.manager not in teamusers:
- raise PermissionDenied("You do not have access to this session")
- elif r not in ps.rower.all():
- raise PermissionDenied("You do not have access to this session")
-
resultsdict = get_session_metrics(ps)
resultsdict = pd.DataFrame(resultsdict).transpose().to_dict()
@@ -1965,7 +1907,7 @@ class PlannedSessionDelete(DeleteView):
return obj
-@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans",
+@user_passes_test(isplanmember,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
def rower_create_trainingplan(request,userid=0):
@@ -2094,7 +2036,7 @@ def rower_create_trainingplan(request,userid=0):
'old_targets':old_targets,
})
-@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans",
+@user_passes_test(isplanmember,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
def rower_delete_trainingtarget(request,id=0):
@@ -2114,7 +2056,7 @@ def rower_delete_trainingtarget(request,id=0):
return HttpResponseRedirect(url)
-@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans",
+@user_passes_test(isplanmember,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
def rower_delete_trainingplan(request,id=0):
@@ -2328,7 +2270,7 @@ class MacroCycleDelete(DeleteView):
return obj
-@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans",
+@user_passes_test(isplanmember,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
def rower_trainingplan_execution_view(request,
@@ -2418,7 +2360,7 @@ def rower_trainingplan_execution_view(request,
)
-@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans",
+@user_passes_test(isplanmember,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
def rower_trainingplan_view(request,
@@ -2841,7 +2783,7 @@ class TrainingTargetUpdate(UpdateView):
from rowers.utils import allsundays
-@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans",
+@user_passes_test(isplanmember,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
def planmesocyclebyweek(request,id=0,userid=0):
@@ -2896,7 +2838,7 @@ def planmesocyclebyweek(request,id=0,userid=0):
from rowers.utils import allmonths
-@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans",
+@user_passes_test(isplanmember,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
def planmacrocyclebymonth(request,id=0,userid=0):
diff --git a/rowers/views/statements.py b/rowers/views/statements.py
index 5d3a969d..315f4df4 100644
--- a/rowers/views/statements.py
+++ b/rowers/views/statements.py
@@ -40,7 +40,7 @@ from rowers.opaque import encoder
from rowers.rower_rules import (
ispromember,is_coach_user,is_team_member,is_rower_team_member,
- is_workout_user
+ is_workout_user,isplanmember,
)
from django.shortcuts import render
@@ -289,7 +289,6 @@ def get_workout_by_opaqueid(request,id,**kwargs):
pk = encoder.decode_hex(id)
return get_object_or_404(Workout,pk=pk)
-
def get_session_by_pk(request,id):
return get_object_or_404(PlannedSession,pk=id)
@@ -1014,22 +1013,6 @@ def cancreateteam(user):
if len(otherteams) >= 1:
return False
-# Check if a user can create planned sessions
-def hasplannedsessions(user):
- if 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 (r.rowerplan=='coach' or r.rowerplan=='freecoach' or r.rowerplan=='plan')
- if not result and r.plantrialexpires:
- result = user.is_authenticated and r.plantrialexpires >= datetime.date.today()
- else:
- result = False
-
- return result
from rowers.utils import ProcessorCustomerError
From abb7b85f748a6072cb51e2fb7082d4aec5e8a2e3 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Tue, 14 Jan 2020 19:22:03 +0100
Subject: [PATCH 20/80] implemented changes in planviews - not tested
---
rowers/rower_rules.py | 114 ++++++++++++++++++++++++++++++++++++-
rowers/views/planviews.py | 82 +++++++-------------------
rowers/views/statements.py | 27 ++++++++-
3 files changed, 160 insertions(+), 63 deletions(-)
diff --git a/rowers/rower_rules.py b/rowers/rower_rules.py
index 74709c9d..2697ac2c 100644
--- a/rowers/rower_rules.py
+++ b/rowers/rower_rules.py
@@ -240,11 +240,107 @@ rules.add_perm('workout.view_workout',can_view_workout) # replaces checkworkoutu
"""
+# Training Target rules
+@rules.predicate
+def can_view_target(user,target):
+ if user.is_anonymous:
+ return False
+ if user == target.manager.user:
+ return True
+
+ # a target's coach can view as well
+ if is_coach_user(user,target.manager.user):
+ return True
+
+ # the object can view as well
+ if user.rower in target.rowers.all():
+ return True
+
+@rules.predicate
+def can_change_target(user,target):
+ if user.is_anonymous:
+ return False
+ return user == target.manager.user
+
+@rules.predicate
+def can_delete_target(user,target):
+ if user.is_anonymous:
+ return False
+ return user == target.manager.user
+
+rules.add_perm('target.view_target',can_view_target)
+rules.add_perm('target.change_target',can_change_target)
+rules.add_perm('target.delete_target',can_delete_target)
+
+@rules.predicate
+def can_view_plan(user,plan):
+ if user.is_anonymous:
+ return False
+ if user == plan.manager.user:
+ return True
+
+ # a plan's coach can view as well
+ if is_coach_user(user,plan.manager.user):
+ return True
+
+ # the object can view as well
+ if user.rower in plan.rowers.all():
+ return True
+
+@rules.predicate
+def can_change_plan(user,plan):
+ if user.is_anonymous:
+ return False
+ return user == plan.manager.user
+
+@rules.predicate
+def can_delete_plan(user,plan):
+ if user.is_anonymous:
+ return False
+ return user == plan.manager.user
+
+rules.add_perm('plan.view_plan',can_view_plan)
+rules.add_perm('plan.change_plan',can_change_plan)
+rules.add_perm('plan.delete_plan',can_delete_plan)
+
+@rules.predicate
+def can_view_cycle(user,cycle):
+ try:
+ return can_view_cycle(user,cycle.plan)
+ except AttributeError:
+ return can_view_plan(user,cycle.plan)
+
+ return False
+
+@rules.predicate
+def can_change_cycle(user,cycle):
+ try:
+ return can_change_cycle(user,cycle.plan)
+ except AttributeError:
+ return can_change_plan(user,cycle.plan)
+
+ return False
+
+@rules.predicate
+def can_delete_cycle(user,cycle):
+ try:
+ return can_delete_cycle(user,cycle.plan)
+ except AttributeError:
+ return can_delete_plan(user,cycle.plan)
+
+ return False
+
+
+rules.add_perm('cycle.view_cycle',can_view_cycle)
+rules.add_perm('cycle.change_cycle',can_change_cycle)
+rules.add_perm('cycle.delete_cycle',can_delete_cycle)
+
+
# check if user has view access to session
@rules.predicate
def can_view_session(user,session):
if session.sessiontype in ['race','indoorrace']:
- return True
+ return True
if user.is_anonymous:
return False
# session manager can view session
@@ -274,8 +370,24 @@ def can_change_session(user,session):
return False
+@rules.predicate
+def can_delete_session(user,session):
+ if user.is_anonymous:
+ return False
+
+ if session.sessiontype in ['race','indoorrace']:
+ return False
+
+ if user == session.manager:
+ return True
+
+ return False
+
rules.add_perm('plannedsession.view_session',can_view_session)
rules.add_perm('plannedsession.change_session',can_change_session)
+rules.add_perm('plannedsession.delete_session',can_delete_session)
+
+
# checkaccessplanuser (models.py)
# getrequestrower, getrequestplanrower
diff --git a/rowers/views/planviews.py b/rowers/views/planviews.py
index c5c6d9ac..26f6aa22 100644
--- a/rowers/views/planviews.py
+++ b/rowers/views/planviews.py
@@ -1900,8 +1900,7 @@ class PlannedSessionDelete(DeleteView):
def get_object(self, *args, **kwargs):
obj = super(PlannedSessionDelete, self).get_object(*args, **kwargs)
- m = Rower.objects.get(user=obj.manager)
- if not is_coach_user(self.request.user,m.user):
+ if not can_delete_session(self.request.user,obj):
raise PermissionDenied('You are not allowed to delete this planned session')
return obj
@@ -2039,17 +2038,11 @@ def rower_create_trainingplan(request,userid=0):
@user_passes_test(isplanmember,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
+@permission_required('target.delete_target',fn=get_target_by_pk,raise_exception=True)
def rower_delete_trainingtarget(request,id=0):
- try:
- target = TrainingTarget.objects.get(id=id)
- except TrainingPlan.DoesNotExist:
- raise Http404("Training Plan Does Not Exist")
- if is_coach_user(request.user,target.manager):
- target.delete()
- messages.info(request,"We have deleted the training target")
- else:
- raise PermissionDenied("Access denied")
+ target = get_object_or_404(TrainingTarget,pk=id)
+ target.delete()
url = reverse(rower_create_trainingplan)
@@ -2059,17 +2052,10 @@ def rower_delete_trainingtarget(request,id=0):
@user_passes_test(isplanmember,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
+@permission_required('target.delete_plan',fn=get_plan_by_pk,raise_exception=True)
def rower_delete_trainingplan(request,id=0):
- try:
- plan = TrainingPlan.objects.get(id=id)
- except TrainingPlan.DoesNotExist:
- raise Http404("Training Plan Does Not Exist")
-
- if is_coach_user(request.user,plan.manager.user):
- plan.delete()
- messages.info(request,"We have deleted the training plan")
- else:
- raise PermissionDenied("Access denied")
+ plan = get_object_or_404(TrainingPlan,pk=id)
+ plan.delete()
url = reverse(rower_create_trainingplan)
@@ -2082,7 +2068,7 @@ class TrainingPlanDelete(DeleteView):
def get_object(self, *args, **kwargs):
obj = super(TrainingPlanDelete, self).get_object(*args, **kwargs)
- if not is_coach_user(self.request.user,obj.manager.user):
+ if not can_delete_plan(self.request.user,obj):
raise PermissionDenied('You are not allowed to delete this training plan')
return obj
@@ -2148,7 +2134,7 @@ class MicroCycleDelete(DeleteView):
def get_object(self, *args, **kwargs):
obj = super(MicroCycleDelete, self).get_object(*args, **kwargs)
- if not is_coach_user(self.request.user,obj.plan.plan.plan.manager.user):
+ if not can_delete_cycle(self.request.user,obj):
raise PermissionDenied('You are not allowed to delete this training plan cycle')
return obj
@@ -2210,7 +2196,7 @@ class MesoCycleDelete(DeleteView):
def get_object(self, *args, **kwargs):
obj = super(MesoCycleDelete, self).get_object(*args, **kwargs)
- if not is_coach_user(self.request.user,obj.plan.plan.manager.user):
+ if not can_delete_cycle(self.request.user,obj):
raise PermissionDenied('You are not allowed to delete this training plan cycle')
return obj
@@ -2264,7 +2250,7 @@ class MacroCycleDelete(DeleteView):
def get_object(self, *args, **kwargs):
obj = super(MacroCycleDelete, self).get_object(*args, **kwargs)
- if not is_coach_user(self.request.user,obj.plan.manager.user):
+ if not can_delete_cycle(self.request.user,obj):
raise PermissionDenied('You are not allowed to delete this training plan cycle')
return obj
@@ -2363,6 +2349,7 @@ def rower_trainingplan_execution_view(request,
@user_passes_test(isplanmember,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
+@permission_required('plan.can_view',fn=get_plan_by_pk,raise_exception=True)
def rower_trainingplan_view(request,
id=0,
userid=0,
@@ -2373,17 +2360,7 @@ def rower_trainingplan_view(request,
startdate,enddate = get_dates_timeperiod(request)
-
- try:
- plan = TrainingPlan.objects.get(id=id)
- except TrainingPlan.DoesNotExist:
- raise Http404("Training Plan Does Not Exist")
-
- r = getrequestrower(request,userid=userid)
-
- if not is_coach_user(request.user,plan.manager.user):
- if request.user.rower not in plan.rowers.all():
- raise PermissionDenied("Access denied")
+ plan = get_object_or_404(TrainingPlan,pk=id)
createmacrofillers(plan)
macrocycles = TrainingMacroCycle.objects.filter(
@@ -2507,10 +2484,7 @@ class TrainingMacroCycleUpdate(UpdateView):
def get_object(self, *args, **kwargs):
obj = super(TrainingMacroCycleUpdate, self).get_object(*args, **kwargs)
- if obj.plan.manager is not None and self.request.user.rower != obj.plan.manager:
- raise PermissionDenied('You are not allowed to edit this training plan cycle')
-
- if not is_coach_user(self.request.user,obj.plan.manager.user):
+ if not can_change_cycle(self.request.user,obj):
raise PermissionDenied('You are not allowed to edit this training plan cycle')
else:
obj.type = 'userdefined'
@@ -2581,7 +2555,7 @@ class TrainingMesoCycleUpdate(UpdateView):
def get_object(self, *args, **kwargs):
obj = super(TrainingMesoCycleUpdate, self).get_object(*args, **kwargs)
- if obj.plan.plan.manager is not None and self.request.user.rower != obj.plan.plan.manager:
+ if not can_change_cycle(self.request.user,obj):
raise PermissionDenied('You are not allowed to edit this training plan cycle')
else:
@@ -2659,7 +2633,7 @@ class TrainingMicroCycleUpdate(UpdateView):
def get_object(self, *args, **kwargs):
obj = super(TrainingMicroCycleUpdate, self).get_object(*args, **kwargs)
- if obj.plan.plan.plan.manager is not None and self.request.user.rower != obj.plan.plan.plan.manager:
+ if not can_change_cycle(self.request.user,obj):
raise PermissionDenied('You are not allowed to edit this training plan cycle')
@@ -2724,10 +2698,8 @@ class TrainingPlanUpdate(UpdateView):
def get_object(self, *args, **kwargs):
obj = super(TrainingPlanUpdate, self).get_object(*args, **kwargs)
- if obj.manager is not None and self.request.user.rower != obj.manager:
+ if not can_change_plan(self.request.user,obj):
raise PermissionDenied('You are not allowed to edit this training plan cycle')
- if obj.manager.rowerplan not in ['coach','freecoach','plan']:
- raise PermissionDenied('You are not allowed to edit this training plan')
return obj
@@ -2776,7 +2748,7 @@ class TrainingTargetUpdate(UpdateView):
def get_object(self, *args, **kwargs):
obj = super(TrainingTargetUpdate, self).get_object(*args, **kwargs)
- if obj.manager is not None and self.request.user.rower != obj.manager:
+ if not can_change_target(self.request.user,obj):
raise PermissionDenied('You are not allowed to edit this training plan cycle')
return obj
@@ -2786,14 +2758,9 @@ from rowers.utils import allsundays
@user_passes_test(isplanmember,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
+@permission_required('cycle.change_cycle',fn=get_meso_by_pk,raise_exception=True)
def planmesocyclebyweek(request,id=0,userid=0):
- try:
- cycle = TrainingMesoCycle.objects.get(id=id)
- except TrainingMesoCycle.DoesNotExist:
- raise Http404("Training Cycle does not exist")
-
- if not is_coach_user(request.user,cycle.plan.plan.manager.user):
- raise PermissionDenied("You are not allowed to do this")
+ cycle = get_object_or_404(TrainingMesoCycle,pk=id)
micros = TrainingMicroCycle.objects.filter(plan=cycle)
for m in micros:
@@ -2841,14 +2808,9 @@ from rowers.utils import allmonths
@user_passes_test(isplanmember,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
+@permission_required('cycle.change_cycle',fn=get_macro_by_pk,raise_exception=True)
def planmacrocyclebymonth(request,id=0,userid=0):
- try:
- cycle = TrainingMacroCycle.objects.get(id=id)
- except TrainingMacroCycle.DoesNotExist:
- raise Http404("Training Cycle does not exist")
-
- if not is_coach_user(request.user,cycle.plan.manager.user):
- raise PermissionDenied("You are not allowed to do this")
+ cycle = get_object_or_404(TrainingMacroCycle,pk=id)
mesos = TrainingMesoCycle.objects.filter(plan=cycle)
for m in mesos:
diff --git a/rowers/views/statements.py b/rowers/views/statements.py
index 315f4df4..3048a288 100644
--- a/rowers/views/statements.py
+++ b/rowers/views/statements.py
@@ -40,7 +40,9 @@ from rowers.opaque import encoder
from rowers.rower_rules import (
ispromember,is_coach_user,is_team_member,is_rower_team_member,
- is_workout_user,isplanmember,
+ is_workout_user,isplanmember,can_delete_session,
+ can_view_target,can_change_target,can_delete_target,
+ can_view_plan,can_change_plan,can_delete_plan,
)
from django.shortcuts import render
@@ -289,9 +291,30 @@ def get_workout_by_opaqueid(request,id,**kwargs):
pk = encoder.decode_hex(id)
return get_object_or_404(Workout,pk=pk)
-def get_session_by_pk(request,id):
+def get_session_by_pk(request,*args,**kwargs):
+ id = kwargs['id']
return get_object_or_404(PlannedSession,pk=id)
+def get_target_by_pk(request,*args,**kwargs):
+ id = kwargs['id']
+ return get_object_or_404(TrainingTarget,pk=id)
+
+def get_plan_by_pk(request,*args,**kwargs):
+ id = kwargs['id']
+ return get_object_or_404(TrainingPlan,pk=id)
+
+def get_macro_by_pk(request,*args,**kwargs):
+ id = kwargs['id']
+ return get_object_or_404(TrainingMacroCycle,pk=id)
+
+def get_meso_by_pk(request,*args,**kwargs):
+ id = kwargs['id']
+ return get_object_or_404(TrainingMesoCycle,pk=id)
+
+def get_micro_by_pk(request,*args,**kwargs):
+ id = kwargs['id']
+ return get_object_or_404(TrainingMicroCycle,pk=id)
+
def get_workout_default_page(request,id):
if request.user.is_anonymous:
return reverse('workout_view',kwargs={'id':id})
From 5482e5223881414529a04161a42431e935cca747 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Tue, 14 Jan 2020 21:25:19 +0100
Subject: [PATCH 21/80] make it pass tests (planviews)
---
rowers/rower_rules.py | 2 +-
rowers/tests/test_rowerplans.py | 14 ++++++++------
rowers/views/planviews.py | 10 +++++-----
rowers/views/statements.py | 1 +
4 files changed, 15 insertions(+), 12 deletions(-)
diff --git a/rowers/rower_rules.py b/rowers/rower_rules.py
index 2697ac2c..34f6f25e 100644
--- a/rowers/rower_rules.py
+++ b/rowers/rower_rules.py
@@ -85,7 +85,7 @@ def is_plantrial(user):
except AttributeError:
return False
- if r.rowerplan == 'basic':
+ if r.rowerplan in ['basic','pro']:
return r.plantrialexpires >= datetime.date.today()
if r.rowerplan == 'freecoach':
if r.mycoachgroup is not None:
diff --git a/rowers/tests/test_rowerplans.py b/rowers/tests/test_rowerplans.py
index d87f1a56..557d148b 100644
--- a/rowers/tests/test_rowerplans.py
+++ b/rowers/tests/test_rowerplans.py
@@ -6,7 +6,8 @@ from __future__ import unicode_literals
from .statements import *
nu = datetime.datetime.now()
-from rowers.views import hasplannedsessions,iscoachmember,ispromember
+from rowers.views import isplanmember,iscoachmember,ispromember
+from rowers.rower_rules import *
class TrialsTest(TestCase):
def setUp(self):
@@ -33,34 +34,35 @@ class TrialsTest(TestCase):
pass
def test_basictrial(self):
- self.assertEqual(hasplannedsessions(self.u),False)
+ self.assertEqual(isplanmember(self.u),False)
self.assertEqual(iscoachmember(self.u),False)
self.assertEqual(ispromember(self.u),False)
self.r.protrialexpires = (nu+datetime.timedelta(days=10)).date()
self.r.save()
- self.assertEqual(hasplannedsessions(self.u),False)
+ self.assertEqual(isplanmember(self.u),False)
self.assertEqual(iscoachmember(self.u),False)
self.assertEqual(ispromember(self.u),True)
self.r.plantrialexpires = (nu+datetime.timedelta(days=10)).date()
self.r.save()
- self.assertEqual(hasplannedsessions(self.u),True)
+ self.assertEqual(isplanmember(self.u),True)
self.assertEqual(iscoachmember(self.u),False)
self.assertEqual(ispromember(self.u),True)
def test_protrial(self):
self.r.rowerplan = 'pro'
self.r.save()
- self.assertEqual(hasplannedsessions(self.u),False)
+ self.assertEqual(isplanmember(self.u),False)
self.assertEqual(iscoachmember(self.u),False)
self.assertEqual(ispromember(self.u),True)
self.r.plantrialexpires = (nu+datetime.timedelta(days=10)).date()
self.r.save()
- self.assertEqual(hasplannedsessions(self.u),True)
+ self.assertEqual(is_plantrial(self.u),True)
+ self.assertEqual(isplanmember(self.u),True)
self.assertEqual(iscoachmember(self.u),False)
self.assertEqual(ispromember(self.u),True)
diff --git a/rowers/views/planviews.py b/rowers/views/planviews.py
index 26f6aa22..097af6f2 100644
--- a/rowers/views/planviews.py
+++ b/rowers/views/planviews.py
@@ -869,7 +869,7 @@ def plannedsession_teamedit_view(request,
url = reverse(plannedsession_teamedit_view,
kwargs = {
- 'sessionid':sessionid,
+ 'id':id,
})
startdatestring = startdate.strftime('%Y-%m-%d')
@@ -1491,7 +1491,7 @@ def plannedsession_teamclone_view(request,id=0):
url = reverse(plannedsession_teamedit_view,
kwargs = {
- 'sessionid':ps.id,
+ 'id':ps.id,
'userid':r.user.id,
}
)
@@ -1557,7 +1557,7 @@ def plannedsession_edit_view(request,id=0,userid=0):
if ps.team.all() or ps.rower.all().count()>1:
url = reverse(plannedsession_teamedit_view,
kwargs={
- 'sessionid':id,
+ 'id':id,
})
return HttpResponseRedirect(url)
@@ -1910,7 +1910,6 @@ class PlannedSessionDelete(DeleteView):
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
def rower_create_trainingplan(request,userid=0):
-
therower = getrequestrower(request,userid=userid)
theuser = therower.user
themanager = getrower(request.user)
@@ -2349,7 +2348,7 @@ def rower_trainingplan_execution_view(request,
@user_passes_test(isplanmember,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
-@permission_required('plan.can_view',fn=get_plan_by_pk,raise_exception=True)
+@permission_required('plan.view_plan',fn=get_plan_by_pk,raise_exception=True)
def rower_trainingplan_view(request,
id=0,
userid=0,
@@ -2361,6 +2360,7 @@ def rower_trainingplan_view(request,
startdate,enddate = get_dates_timeperiod(request)
plan = get_object_or_404(TrainingPlan,pk=id)
+ r = getrequestrower(request,userid=userid)
createmacrofillers(plan)
macrocycles = TrainingMacroCycle.objects.filter(
diff --git a/rowers/views/statements.py b/rowers/views/statements.py
index 3048a288..2955d3c7 100644
--- a/rowers/views/statements.py
+++ b/rowers/views/statements.py
@@ -43,6 +43,7 @@ from rowers.rower_rules import (
is_workout_user,isplanmember,can_delete_session,
can_view_target,can_change_target,can_delete_target,
can_view_plan,can_change_plan,can_delete_plan,
+ can_view_cycle,can_change_cycle,can_delete_cycle
)
from django.shortcuts import render
From 0cff00eb18cfa087a62ebc789a92648462710b8c Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Tue, 14 Jan 2020 21:29:50 +0100
Subject: [PATCH 22/80] changing to convention
---
rowers/rower_rules.py | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/rowers/rower_rules.py b/rowers/rower_rules.py
index 34f6f25e..97f9ed0d 100644
--- a/rowers/rower_rules.py
+++ b/rowers/rower_rules.py
@@ -434,12 +434,19 @@ def can_view_team(user,team):
# team is private
return is_team_member(user,team) | is_team_manager(user,team)
+@rules.predicate
+def can_change_team(user,team):
+ return is_team_manager(user,team)
+
+@rules.predicate
+def can_delete_team(user,team):
+ return is_team_manager(user,team)
# For Team functionality
rules.add_perm('teams.view_team',can_view_team)
rules.add_perm('teams.add_team',user_is_not_basic)
-rules.add_perm('teams.change_team',is_team_manager)
-rules.add_perm('teams.delete_team',is_team_manager)
+rules.add_perm('teams.change_team',can_change_team)
+rules.add_perm('teams.delete_team',can_delete_team)
# RACING permissions
From 62fe0252184f62df92f3904ea7bdf8933668120a Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Tue, 14 Jan 2020 22:19:29 +0100
Subject: [PATCH 23/80] worked through a few more views. Need to do analysis,
api, error
---
rowers/rower_rules.py | 41 ++++-
rowers/urls.py | 2 +-
rowers/views/racesviews.py | 322 ++++++++++++++++---------------------
rowers/views/statements.py | 12 ++
4 files changed, 189 insertions(+), 188 deletions(-)
diff --git a/rowers/rower_rules.py b/rowers/rower_rules.py
index 97f9ed0d..7ebfc732 100644
--- a/rowers/rower_rules.py
+++ b/rowers/rower_rules.py
@@ -387,11 +387,6 @@ rules.add_perm('plannedsession.view_session',can_view_session)
rules.add_perm('plannedsession.change_session',can_change_session)
rules.add_perm('plannedsession.delete_session',can_delete_session)
-
-
-# checkaccessplanuser (models.py)
-# getrequestrower, getrequestplanrower
-
# TEAM (group) permissions
"""
@@ -455,8 +450,44 @@ rules.add_perm('teams.delete_team',can_delete_team)
- rules to add, view, delete, change
- GeoCourse
- rules to add, view, delete, change
+ - RaceLogo
"""
+@rules.predicate
+def can_change_course(user,course):
+ if user.is_anonymous:
+ return False
+
+ return course.manager == user.rower
+
+@rules.predicate
+def can_delete_course(user,course):
+ if user.is_anonymous:
+ return False
+
+ return course.manager == user.rower
+
+@rules.predicate
+def can_delete_logo(user,logo):
+ if user.is_anonymous:
+ return False
+
+ return logo.user == user
+
+@rules.predicate
+def can_change_race(user,race):
+ if user.is_anonymous:
+ return False
+
+ return race.manager == user
+
+rules.add_perm('course.change_course',can_change_course)
+rules.add_perm('course.delete_course',can_delete_course)
+
+rules.add_perm('racelogo.delete_logo',can_delete_logo)
+
+rules.add_perm('virtualevent.change_race',can_change_race)
+
# ANALYSIS permissions
diff --git a/rowers/urls.py b/rowers/urls.py
index b8c65fb6..4a890095 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -191,7 +191,7 @@ urlpatterns = [
views.virtualevent_submit_result_view,name='virtualevent_submit_result_view'),
re_path(r'^virtualevent/(?P\d+)/submit/(?P\b[0-9A-Fa-f]+\b)/$',
views.virtualevent_submit_result_view,name='virtualevent_submit_result_view'),
- re_path(r'^virtualevent/(?P\d+)/disqualify/(?P\d+)/',
+ re_path(r'^virtualevent/(?P\d+)/disqualify/(?P\d+)/',
views.virtualevent_disqualify_view,name='virtualevent_disqualify_view'),
re_path(r'^list-workouts/$',views.workouts_view,name='workouts_view'),
re_path(r'^list-courses/$',views.courses_view,name='courses_view'),
diff --git a/rowers/views/racesviews.py b/rowers/views/racesviews.py
index 1cc53312..358afddd 100644
--- a/rowers/views/racesviews.py
+++ b/rowers/views/racesviews.py
@@ -45,7 +45,7 @@ def course_map_view(request,id=0):
return Http404("Course doesn't exist")
script,div = course_map(course)
-
+
breadcrumbs = [
{
'url': reverse('virtualevents_view'),
@@ -64,10 +64,10 @@ def course_map_view(request,id=0):
'name': 'Map'
}
]
-
+
r = getrower(request.user)
-
-
+
+
return render(request,
'coursemap.html',
{
@@ -76,21 +76,16 @@ def course_map_view(request,id=0):
'mapscript':script,
'active':'nav-racing',
'rower':r,
- 'breadcrumbs':breadcrumbs,
+ 'breadcrumbs':breadcrumbs,
})
-
+
@login_required()
+@permission_required('course.change_course',fn=get_course_by_pk,raise_exception=True)
def course_replace_view(request,id=0):
- try:
- course = GeoCourse.objects.get(id=id)
- except GeoCourse.DoesNotExist:
- return Http404("Course doesn't exist")
+ course = get_object_or_404(GeoCourse,pk=id)
r = getrower(request.user)
-
- if course.manager != r:
- raise PermissionDenied("Access denied")
thecourses = GeoCourse.objects.filter(manager=r).exclude(id=id)
@@ -141,18 +136,13 @@ def course_replace_view(request,id=0):
'mapdiv':div,
'mapscript':script,
'form':form})
-
+
@login_required()
+@permission_required('course.delete_course',fn=get_course_by_pk,raise_exception=True)
def course_delete_view(request,id=0):
- try:
- course = GeoCourse.objects.get(id=id)
- except GeoCourse.DoesNotExist:
- return Http404("Course doesn't exist")
+ course = get_object_or_404(GeoCourse,pk=id)
r = getrower(request.user)
-
- if course.manager != r:
- raise PermissionDenied("Access denied")
ps = PlannedSession.objects.filter(course=course)
nosessions = len(ps) == 0
@@ -163,22 +153,17 @@ def course_delete_view(request,id=0):
url = reverse(courses_view)
return HttpResponseRedirect(url)
-
+
@login_required()
+@permission_required('course.change_course',fn=get_course_by_pk,raise_exception=True)
def course_edit_view(request,id=0):
- try:
- course = GeoCourse.objects.get(id=id)
- except GeoCourse.DoesNotExist:
- return Http404("Course doesn't exist")
+ course = get_object_or_404(GeoCourse,pk=id)
r = getrower(request.user)
-
- if course.manager != r:
- raise PermissionDenied("Access denied")
ps = PlannedSession.objects.filter(course=course)
nosessions = len(ps) == 0
-
+
script,div = course_map(course)
if request.method == 'POST':
@@ -187,7 +172,7 @@ def course_edit_view(request,id=0):
name = form.cleaned_data['name']
country = form.cleaned_data['country']
notes = form.cleaned_data['notes']
-
+
course.name = name
course.country = country
course.notes = notes
@@ -213,7 +198,7 @@ def course_edit_view(request,id=0):
'name': 'Edit'
}
]
-
+
return render(request, 'course_edit_view.html',
{
'course':course,
@@ -235,9 +220,9 @@ def course_view(request,id=0):
return Http404("Course doesn't exist")
r = getrower(request.user)
-
+
script,div = course_map(course)
-
+
breadcrumbs = [
{
'url': reverse('virtualevents_view'),
@@ -266,11 +251,10 @@ def course_view(request,id=0):
)
@login_required()
+@permission_required('racelogo.delete_logo',fn=get_logo_by_pk,raise_exception=True)
def logo_delete_view(request,id=0):
- try:
- logo = RaceLogo.objects.get(id=id)
- except RaceLogo.DoesNotExist:
- raise Http404("Logo doesn't exist")
+ logo = get_object_or_404(RaceLogo,pk=id)
+
if logo.user == request.user:
logo.delete()
@@ -281,28 +265,18 @@ def logo_delete_view(request,id=0):
return HttpResponseRedirect(url)
@login_required()
+@permission_required('virtualevent.change_race',fn=get_virtualevent_by_pk,raise_exception=True)
def virtualevent_setlogo_view(request,id=0,logoid=0):
- try:
- race = VirtualRace.objects.get(id=id)
- except VirtualRace.DoesNotExist:
- raise Http404("Race doesn't exist")
+ race = get_object_or_404(VirtualRace,pk=id)
+ logo = get_object_or_404(RaceLogo,pk=logoid)
- try:
- logo = RaceLogo.objects.get(id=logoid)
- except RaceLogo.DoesNotExist:
- raise Http404("Logo doesn't exist")
+ otherlogos = race.logos.all()
+ for otherlogo in otherlogos:
+ otherlogo.race.remove(race)
- if logo.user == request.user and race.manager == request.user:
- otherlogos = race.logos.all()
- for otherlogo in otherlogos:
- otherlogo.race.remove(race)
-
- logo.race.add(race)
- logo.save()
- else:
- message = "You do not own this race or this image"
- messages.error(request,message)
+ logo.race.add(race)
+ logo.save()
url = reverse('virtualevent_view',
kwargs={'id':id})
@@ -318,10 +292,7 @@ def virtualevent_uploadimage_view(request,id=0):
r = getrower(request.user)
- try:
- race = VirtualRace.objects.get(id=id)
- except VirtualRace.DoesNotExist:
- raise Http404("Race doesn't exist")
+ race = get_object_or_404(VirtualRace,pk=id)
logos = RaceLogo.objects.filter(user=request.user).order_by("-creationdatetime")
@@ -348,7 +319,7 @@ def virtualevent_uploadimage_view(request,id=0):
kwargs={'id':id})
return HttpResponseRedirect(url)
-
+
form = ImageForm(request.POST, request.FILES)
if form.is_valid():
f = form.cleaned_data['file']
@@ -399,14 +370,14 @@ def virtualevent_uploadimage_view(request,id=0):
else:
return HttpResponseRedirect(url)
else:
-
+
form = ImageForm()
if is_ajax:
return {'result':0}
-
+
return render(request,'logo_form.html',
{'form':form,
'rower':r,
@@ -425,7 +396,7 @@ def course_upload_view(request):
is_ajax = True
r = getrower(request.user)
-
+
if request.method == 'POST':
form = CourseForm(request.POST,request.FILES)
@@ -435,7 +406,7 @@ def course_upload_view(request):
notes = form.cleaned_data['notes']
if f is not None:
filename,path_and_filename = handle_uploaded_file(f)
-
+
cs = courses.kmltocourse(path_and_filename)
@@ -443,11 +414,11 @@ def course_upload_view(request):
cname = name+' - '+course['name']
cnotes = notes+'\n\n'+course['description']
polygons = course['polygons']
-
+
course = courses.createcourse(r,cname,polygons,notes=cnotes)
-
+
os.remove(path_and_filename)
-
+
url = reverse(courses_view)
if is_ajax:
return JSONResponse({'result':1,'url':url})
@@ -492,7 +463,7 @@ def virtualevents_view(request):
evaluation_closure__gte=timezone.now()-datetime.timedelta(days=3),
)
-
+
races = (races1 | races2).order_by("startdate","start_time")
if not request.user.is_anonymous:
@@ -524,7 +495,7 @@ def virtualevents_view(request):
country__in=countries
)
-
+
races = (races1 | races2).order_by("startdate","start_time")
@@ -550,7 +521,7 @@ def virtualevents_view(request):
country__in=countries
).order_by("startdate","start_time")
else:
-
+
form = VirtualRaceSelectForm()
if is_ajax:
@@ -558,7 +529,7 @@ def virtualevents_view(request):
{ 'races':races,
'rower':r,
})
-
+
breadcrumbs = [
{
'url':reverse('virtualevents_view'),
@@ -576,18 +547,13 @@ def virtualevents_view(request):
)
@login_required()
-def virtualevent_disqualify_view(request,raceid=0,recordid=0):
+@permission_required('virtualevent.change_race',fn=get_virtualevent_by_pk,raise_exception=True)
+def virtualevent_disqualify_view(request,id=0,recordid=0):
r = getrower(request.user)
+ race = get_object_or_404(VirtualRace,pk=id)
+
- try:
- race = VirtualRace.objects.get(id=raceid)
- except VirtualRace.DoesNotExist:
- raise Http404("Virtual Race does not exist")
-
- if r.user != race.manager:
- raise PermissionDenied("Access denied")
-
if race.sessiontype == 'race':
recordobj = VirtualRaceResult
else:
@@ -603,7 +569,7 @@ def virtualevent_disqualify_view(request,raceid=0,recordid=0):
if timezone.now() > race.evaluation_closure+datetime.timedelta(hours=1):
messages.error(request,"The evaluation is already closed and the results are official")
url = reverse('virtualevent_view',kwargs={'id':raceid})
-
+
return HttpResponseRedirect(url)
if request.method == 'POST':
@@ -619,16 +585,16 @@ def virtualevent_disqualify_view(request,raceid=0,recordid=0):
job = myqueue(queue,handle_send_disqualification_email,
r.user.email, name,
disqualifier,message,race.name)
-
+
messages.info(request,"We have invalidated the result for: "+str(record))
-
+
record.coursecompleted = False
record.save()
url = reverse('virtualevent_view',kwargs={'id':raceid})
return HttpResponseRedirect(url)
-
+
else:
form = DisqualificationForm(request.POST)
@@ -705,7 +671,7 @@ def virtualevent_disqualify_view(request,raceid=0,recordid=0):
if race_can_edit(r,race):
buttons += ['editbutton']
-
+
return render(request,"disqualification_view.html",
{'workout':workout,
'active':'nav-racing',
@@ -723,13 +689,13 @@ def virtualevent_disqualify_view(request,raceid=0,recordid=0):
def virtualevent_view(request,id=0):
results = []
-
+
if not request.user.is_anonymous:
r = getrower(request.user)
else:
r = None
-
+
try:
race = VirtualRace.objects.get(id=id)
except VirtualRace.DoesNotExist:
@@ -744,8 +710,8 @@ def virtualevent_view(request,id=0):
resultobj = IndoorVirtualRaceResult
records = resultobj.objects.filter(race=race)
-
-
+
+
buttons = []
# to-do - add DNS
@@ -755,7 +721,7 @@ def virtualevent_view(request,id=0):
race=race,
workoutid__isnull=True,
)
-
+
if not request.user.is_anonymous:
if race_can_register(r,race):
@@ -790,14 +756,14 @@ def virtualevent_view(request,id=0):
except KeyError:
boattype = mytypes.waterboattype
- try:
+ try:
boatclass = cd['boatclass']
except KeyError:
if race.sessiontype == 'race':
boatclass = [t for t in mytypes.otwtypes]
else:
boatclass = [t for t in mytypes.otetypes]
-
+
age_min = cd['age_min']
age_max = cd['age_max']
@@ -834,8 +800,8 @@ def virtualevent_view(request,id=0):
age__gte=age_min,
age__lte=age_max
).order_by("duration","-distance")
-
-
+
+
# to-do - add DNS
dns = []
if timezone.now() > race.evaluation_closure:
@@ -860,9 +826,9 @@ def virtualevent_view(request,id=0):
form = RaceResultFilterForm(records=records)
else:
form = None
-
-
+
+
breadcrumbs = [
{
'url':reverse('virtualevents_view'),
@@ -875,7 +841,7 @@ def virtualevent_view(request,id=0):
'name': race.name
}
]
-
+
racelogos = race.logos.all()
if racelogos:
@@ -885,7 +851,7 @@ def virtualevent_view(request,id=0):
comments = PlannedSessionComment.objects.filter(plannedsession=race).order_by("created")
-
+
return render(request,'virtualevent.html',
{
'coursescript':script,
@@ -906,13 +872,13 @@ def virtualevent_view(request,id=0):
def virtualevent_ranking_view(request,id=0):
results = []
-
+
if not request.user.is_anonymous:
r = getrower(request.user)
else:
r = None
-
+
try:
race = VirtualRace.objects.get(id=id)
except VirtualRace.DoesNotExist:
@@ -927,8 +893,8 @@ def virtualevent_ranking_view(request,id=0):
resultobj = IndoorVirtualRaceResult
records = resultobj.objects.filter(race=race)
-
-
+
+
buttons = []
# to-do - add DNS
@@ -938,7 +904,7 @@ def virtualevent_ranking_view(request,id=0):
race=race,
workoutid__isnull=True,
)
-
+
if not request.user.is_anonymous:
if race_can_register(r,race):
@@ -973,14 +939,14 @@ def virtualevent_ranking_view(request,id=0):
except KeyError:
boattype = mytypes.waterboattype
- try:
+ try:
boatclass = cd['boatclass']
except KeyError:
if race.sessiontype == 'race':
boatclass = [t for t in mytypes.otwtypes]
else:
boatclass = [t for t in mytypes.otetypes]
-
+
age_min = cd['age_min']
age_max = cd['age_max']
@@ -1017,8 +983,8 @@ def virtualevent_ranking_view(request,id=0):
age__gte=age_min,
age__lte=age_max
).order_by("duration","-distance")
-
-
+
+
# to-do - add DNS
dns = []
if timezone.now() > race.evaluation_closure:
@@ -1043,9 +1009,9 @@ def virtualevent_ranking_view(request,id=0):
form = RaceResultFilterForm(records=records)
else:
form = None
-
-
+
+
breadcrumbs = [
{
'url':reverse('virtualevents_view'),
@@ -1058,7 +1024,7 @@ def virtualevent_ranking_view(request,id=0):
'name': race.name
}
]
-
+
racelogos = race.logos.all()
if racelogos:
@@ -1132,7 +1098,7 @@ def virtualevent_addboat_view(request,id=0):
boattypes = [record.boattype for record in records]
boatclasses = [record.boatclass for record in records]
allowedboats = tuple([ type for type in mytypes.boattypes if type[0] not in boattypes] )
-
+
# we're still here
if request.method == 'POST':
@@ -1151,14 +1117,14 @@ def virtualevent_addboat_view(request,id=0):
sex = r.sex
if mix:
sex = 'mixed'
-
+
if boattype == '1x' and r.birthdate:
age = calculate_age(r.birthdate)
sex = r.sex
if sex == 'not specified':
sex = 'male'
-
+
if boattype in boattypes and boatclass in boatclasses:
# check if different sexes
therecords = records.filter(
@@ -1167,7 +1133,7 @@ def virtualevent_addboat_view(request,id=0):
thesexes = [record.sex for record in therecords]
if sex in thesexes:
-
+
messages.error(
request,
"You have already registered in that boat class/type"
@@ -1199,11 +1165,11 @@ def virtualevent_addboat_view(request,id=0):
)
record.save()
-
+
add_rower_race(r,race)
-
-
+
+
messages.info(
request,
"You have successfully registered for this race. Good luck!"
@@ -1215,14 +1181,14 @@ def virtualevent_addboat_view(request,id=0):
})
return HttpResponseRedirect(url)
-
+
else:
initial = {
'age': calculate_age(r.birthdate),
'weightcategory': r.weightcategory,
'adaptiveclass': r.adaptiveclass,
}
-
+
form = VirtualRaceResultForm(initial=initial)
breadcrumbs = [
@@ -1243,8 +1209,8 @@ def virtualevent_addboat_view(request,id=0):
'name': 'Add Discipline'
}
]
-
-
+
+
buttons = []
if not request.user.is_anonymous:
@@ -1311,7 +1277,7 @@ def virtualevent_register_view(request,id=0):
sex = r.sex
if mix:
sex = 'mixed'
-
+
if boattype == '1x' and r.birthdate:
age = calculate_age(r.birthdate)
sex = r.sex
@@ -1338,7 +1304,7 @@ def virtualevent_register_view(request,id=0):
)
record.save()
-
+
add_rower_race(r,race)
otherrecords = IndoorVirtualRaceResult.objects.filter(
@@ -1357,8 +1323,8 @@ def virtualevent_register_view(request,id=0):
race.name,
race.id
)
-
-
+
+
messages.info(
request,
"You have successfully registered for this race. Good luck!"
@@ -1370,14 +1336,14 @@ def virtualevent_register_view(request,id=0):
})
return HttpResponseRedirect(url)
-
+
else:
initial = {
'age': calculate_age(r.birthdate),
'weightcategory': r.weightcategory,
'adaptiveclass': r.adaptiveclass,
}
-
+
form = VirtualRaceResultForm(initial=initial)
breadcrumbs = [
@@ -1419,7 +1385,7 @@ def virtualevent_register_view(request,id=0):
if race_can_edit(r,race):
buttons += ['editbutton']
-
+
return render(request,'virtualeventregister.html',
{
'form':form,
@@ -1440,7 +1406,7 @@ def virtualevent_toggle_email_view(request,id=0):
newsetting = False
else:
newsetting = True
-
+
for record in records:
record.emailnotifications = newsetting
record.save()
@@ -1454,7 +1420,7 @@ def virtualevent_toggle_email_view(request,id=0):
def indoorvirtualevent_toggle_email_view(request,id=0):
r = getrower(request.user)
race = VirtualRace.objects.get(id=id)
-
+
records = IndoorVirtualRaceResult.objects.filter(userid=r.id,
race=race)
@@ -1462,7 +1428,7 @@ def indoorvirtualevent_toggle_email_view(request,id=0):
newsetting = False
else:
newsetting = True
-
+
for record in records:
record.emailnotifications = newsetting
record.save()
@@ -1503,7 +1469,7 @@ def indoorvirtualevent_register_view(request,id=0):
boatclass = cd['boatclass']
sex = r.sex
-
+
if r.birthdate:
age = calculate_age(r.birthdate)
sex = r.sex
@@ -1529,7 +1495,7 @@ def indoorvirtualevent_register_view(request,id=0):
)
record.save()
-
+
add_rower_race(r,race)
otherrecords = IndoorVirtualRaceResult.objects.filter(
@@ -1548,8 +1514,8 @@ def indoorvirtualevent_register_view(request,id=0):
race.name,
race.id
)
-
-
+
+
messages.info(
request,
"You have successfully registered for this race. Good luck!"
@@ -1561,14 +1527,14 @@ def indoorvirtualevent_register_view(request,id=0):
})
return HttpResponseRedirect(url)
-
+
else:
initial = {
'age': calculate_age(r.birthdate),
'weightcategory': r.weightcategory,
'adaptiveclass': r.adaptiveclass,
}
-
+
form = IndoorVirtualRaceResultForm(initial=initial)
breadcrumbs = [
@@ -1624,7 +1590,7 @@ def indoorvirtualevent_register_view(request,id=0):
@login_required()
def indoorvirtualevent_create_view(request):
r = getrower(request.user)
-
+
if request.method == 'POST':
racecreateform = IndoorVirtualRaceForm(request.POST)
if racecreateform.is_valid():
@@ -1650,7 +1616,7 @@ def indoorvirtualevent_create_view(request):
startdatetime = datetime.datetime.combine(startdate,start_time)
enddatetime = datetime.datetime.combine(enddate,end_time)
-
+
startdatetime = pytz.timezone(timezone_str).localize(
startdatetime
)
@@ -1681,7 +1647,7 @@ def indoorvirtualevent_create_view(request):
sessionmode = 'time'
else:
sessionmode = 'distance'
-
+
vs = VirtualRace(
name=name,
startdate=startdate,
@@ -1719,7 +1685,7 @@ def indoorvirtualevent_create_view(request):
),
dotweet = dotweet
)
-
+
sa.save()
url = reverse('virtualevents_view')
@@ -1728,7 +1694,7 @@ def indoorvirtualevent_create_view(request):
racecreateform = IndoorVirtualRaceForm(timezone=r.defaulttimezone)
-
+
breadcrumbs = [
{
'url':reverse('virtualevents_view'),
@@ -1753,7 +1719,7 @@ def indoorvirtualevent_create_view(request):
@login_required()
def virtualevent_create_view(request):
r = getrower(request.user)
-
+
if request.method == 'POST':
racecreateform = VirtualRaceForm(request.POST)
if racecreateform.is_valid():
@@ -1779,7 +1745,7 @@ def virtualevent_create_view(request):
startdatetime = datetime.datetime.combine(startdate,start_time)
enddatetime = datetime.datetime.combine(enddate,end_time)
-
+
startdatetime = pytz.timezone(timezone_str).localize(
startdatetime
)
@@ -1805,8 +1771,8 @@ def virtualevent_create_view(request):
registration_closure = enddatetime
else:
registration_closure = evaluation_closure
-
-
+
+
vs = VirtualRace(
name=name,
startdate=startdate,
@@ -1842,7 +1808,7 @@ def virtualevent_create_view(request):
),
dotweet = dotweet
)
-
+
sa.save()
url = reverse('virtualevents_view')
return HttpResponseRedirect(url)
@@ -1850,7 +1816,7 @@ def virtualevent_create_view(request):
racecreateform = VirtualRaceForm()
-
+
breadcrumbs = [
{
'url':reverse('virtualevents_view'),
@@ -1872,15 +1838,12 @@ def virtualevent_create_view(request):
})
@login_required()
+@permission_required('virtualevent.change_race',fn=get_virtualevent_by_pk,raise_exception=True)
def virtualevent_edit_view(request,id=0):
r = getrower(request.user)
-
- try:
- race = VirtualRace.objects.get(id=id)
- if race.manager != request.user:
- raise PermissionDenied("Access denied")
- except VirtualRace.DoesNotExist:
- raise Http404("Virtual Race does not exist")
+ race = get_object_or_404(VirtualRace,pk=id)
+
+
start_time = race.start_time
start_date = race.startdate
@@ -1900,7 +1863,7 @@ def virtualevent_edit_view(request,id=0):
racecreateform = VirtualRaceForm(request.POST,instance=race)
if racecreateform.is_valid():
cd = racecreateform.cleaned_data
-
+
res, message = update_virtualrace(race,cd)
if res:
@@ -1912,7 +1875,7 @@ def virtualevent_edit_view(request,id=0):
kwargs = {
'id':race.id
})
-
+
return HttpResponseRedirect(url)
else:
@@ -1937,7 +1900,7 @@ def virtualevent_edit_view(request,id=0):
'name': 'Edit'
}
]
-
+
buttons = []
if not request.user.is_anonymous:
@@ -1970,15 +1933,10 @@ def virtualevent_edit_view(request,id=0):
})
@login_required()
+@permission_required('virtualevent.change_race',fn=get_virtualevent_by_pk,raise_exception=True)
def indoorvirtualevent_edit_view(request,id=0):
r = getrower(request.user)
-
- try:
- race = VirtualRace.objects.get(id=id)
- if race.manager != request.user:
- raise PermissionDenied("Access denied")
- except VirtualRace.DoesNotExist:
- raise Http404("Virtual Race does not exist")
+ race = get_object_or_404(VirtualRace,pk=id)
start_time = race.start_time
start_date = race.startdate
@@ -1998,7 +1956,7 @@ def indoorvirtualevent_edit_view(request,id=0):
racecreateform = IndoorVirtualRaceForm(request.POST,instance=race)
if racecreateform.is_valid():
cd = racecreateform.cleaned_data
-
+
res, message = update_indoorvirtualrace(race,cd)
if res:
@@ -2010,14 +1968,14 @@ def indoorvirtualevent_edit_view(request,id=0):
kwargs = {
'id':race.id
})
-
+
return HttpResponseRedirect(url)
else:
racecreateform = IndoorVirtualRaceForm(instance=race)
-
+
breadcrumbs = [
{
'url':reverse('virtualevents_view'),
@@ -2057,8 +2015,8 @@ def indoorvirtualevent_edit_view(request,id=0):
if race_can_edit(r,race):
buttons += ['editbutton']
-
-
+
+
return render(request,'virtualeventedit.html',
{
'form':racecreateform,
@@ -2084,7 +2042,7 @@ def virtualevent_submit_result_view(request,id=0,workoutid=0):
start_date = race.startdate
startdatetime = datetime.datetime.combine(start_date, start_time)
startdatetime = pytz.timezone(race.timezone).localize(startdatetime)
-
+
end_time = race.end_time
end_date = race.enddate
enddatetime = datetime.datetime.combine(end_date, end_time)
@@ -2096,7 +2054,7 @@ def virtualevent_submit_result_view(request,id=0,workoutid=0):
resultobj = VirtualRaceResult
else:
resultobj = IndoorVirtualRaceResult
-
+
records = resultobj.objects.filter(
userid = r.id,
race=race
@@ -2104,7 +2062,7 @@ def virtualevent_submit_result_view(request,id=0,workoutid=0):
entrychoices = []
-
+
for record in records:
rtpl = (record.id, record.__str__())
entrychoices.append(rtpl)
@@ -2121,7 +2079,7 @@ def virtualevent_submit_result_view(request,id=0,workoutid=0):
}
)
return HttpResponseRedirect(url)
-
+
ws = Workout.objects.filter(
user=r,
workouttype__in=mytypes.rowtypes,
@@ -2134,14 +2092,14 @@ def virtualevent_submit_result_view(request,id=0,workoutid=0):
request,
'You have no workouts executed during the race window. Please upload a result or enter it manually.'
)
-
+
url = reverse('virtualevent_view',
kwargs = {
'id':id
})
return HttpResponseRedirect(url)
-
+
initialworkouts = [w.id for w in Workout.objects.filter(
user=r,plannedsession=race
@@ -2151,7 +2109,7 @@ def virtualevent_submit_result_view(request,id=0,workoutid=0):
workoutdata['initial'] = []
choices = []
-
+
for w in ws:
wtpl = (w.id, w.__str__())
choices.append(wtpl)
@@ -2183,7 +2141,7 @@ def virtualevent_submit_result_view(request,id=0,workoutid=0):
result,comments,errors,jobid = add_workout_indoorrace(
workouts,race,r,recordid=recordid)
-
+
for c in comments:
messages.info(request,c)
for er in errors:
@@ -2217,13 +2175,13 @@ def virtualevent_submit_result_view(request,id=0,workoutid=0):
race.id
)
-
+
# redirect to race page
url = reverse('virtualevent_view',
kwargs = {
'id':race.id
})
-
+
return HttpResponseRedirect(url)
else:
@@ -2273,7 +2231,7 @@ def virtualevent_submit_result_view(request,id=0,workoutid=0):
if race_can_edit(r,race):
buttons += ['editbutton']
-
+
return render(request,'race_submit.html',
{
'race':race,
diff --git a/rowers/views/statements.py b/rowers/views/statements.py
index 2955d3c7..43fa3315 100644
--- a/rowers/views/statements.py
+++ b/rowers/views/statements.py
@@ -285,9 +285,21 @@ def getfavorites(r,row):
return favorites,maxfav
+def get_logo_by_pk(request,*args,**kwargs):
+ id = kwargs['id']
+ return get_object_or_404(RaceLogo,pk=id)
+
+def get_virtualevent_by_pk(request,*args,**kwargs):
+ id = kwargs['id']
+ return get_object_or_404(VirtualRace,pk=id)
+
def get_promember(request,*args,**kwargs):
return request.user
+def get_course_by_pk(request,*args,**kwargs):
+ id = kwargs['id']
+ return get_object_or_404(GeoCourse,pk=id)
+
def get_workout_by_opaqueid(request,id,**kwargs):
pk = encoder.decode_hex(id)
return get_object_or_404(Workout,pk=pk)
From 7aa03b71ed18c7a3780c7a58959ea4517cad8fb2 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Tue, 14 Jan 2020 22:21:12 +0100
Subject: [PATCH 24/80] some annotations
---
rowers/rower_rules.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/rowers/rower_rules.py b/rowers/rower_rules.py
index 7ebfc732..ca889645 100644
--- a/rowers/rower_rules.py
+++ b/rowers/rower_rules.py
@@ -481,12 +481,15 @@ def can_change_race(user,race):
return race.manager == user
+# everybody can view or add a course
rules.add_perm('course.change_course',can_change_course)
rules.add_perm('course.delete_course',can_delete_course)
rules.add_perm('racelogo.delete_logo',can_delete_logo)
+# everybody can view a race
rules.add_perm('virtualevent.change_race',can_change_race)
+# can races be deleted?
# ANALYSIS permissions
From a6746030e9cc381fb95a05a8ec4b3a72441069dc Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Wed, 15 Jan 2020 10:17:46 +0100
Subject: [PATCH 25/80] added inventory from test_permissions
---
rowers/models.py | 71 +++++++++--------
rowers/rower_rules.py | 131 +++++++++++++++++++++++++++++--
rowers/teams.py | 10 +--
rowers/tests/test_permissions.py | 8 +-
rowers/views/planviews.py | 2 +-
rowers/views/statements.py | 3 +-
rowers/views/workoutviews.py | 2 +-
7 files changed, 170 insertions(+), 57 deletions(-)
diff --git a/rowers/models.py b/rowers/models.py
index b1f9065d..e2083164 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -41,7 +41,7 @@ import datetime
#from rules.contrib.models import RulesModel
-
+from rowers.rower_rules import *
from rowers.rows import validate_file_extension
from collections import OrderedDict
@@ -330,12 +330,6 @@ class C2WorldClassAgePerformance(models.Model):
return thestring
-def is_not_basic(user):
- if user.rower.rowerplan == 'basic':
- raise ValidationError(
- "Basic user cannot be team manager"
- )
-
@python_2_unicode_compatible
class Team(models.Model):
@@ -351,7 +345,7 @@ class Team(models.Model):
name = models.CharField(max_length=150,unique=True,verbose_name='Team Name')
notes = models.CharField(blank=True,max_length=200,verbose_name='Team Purpose')
- manager = models.ForeignKey(User, null=True,on_delete=models.CASCADE) # validators=[is_not_basic])
+ manager = models.ForeignKey(User, null=True,on_delete=models.CASCADE)
private = models.CharField(max_length=30,choices=choices,default='open',
verbose_name='Team Type')
@@ -364,18 +358,15 @@ class Team(models.Model):
def save(self, *args, **kwargs):
manager = self.manager
- if manager.rower.rowerplan == 'basic':
- if manager.rower.protrialexpires < datetime.date.today() and manager.rower.plantrialexpires < datetime.date.today():
+ if not can_have_teams(manager):
raise ValidationError(
"Basic user cannot be team manager"
)
- if manager.rower.rowerplan in ['plan','pro']:
- otherteams = Team.objects.filter(manager=manager)
- if otherteams.count() >= 1:
- raise ValidationError(
- "Pro and Self-Coach users cannot have more than one team"
- )
+ if not can_add_team(manager):
+ raise ValidationError(
+ "Pro and Self-Coach users cannot have more than one team"
+ )
super(Team, self).save(*args,**kwargs)
@@ -910,6 +901,17 @@ class Rower(models.Model):
def get_managed_teams(self):
return Team.objects.filter(manager=self.user)
+ def get_coaches(self):
+ coaches = []
+ for group in self.coachinggroups.all():
+ try:
+ coach = Rower.objects.get(mycoachgroup=group)
+ coaches.append(coach)
+ except Rower.DoesNotExist:
+ pass
+
+ return coaches
+
class DeactivateUserForm(forms.ModelForm):
class Meta:
model = User
@@ -943,14 +945,14 @@ def check_teams_on_change(sender, **kwargs):
instance = kwargs.pop('instance', None)
action = kwargs.pop('action', None)
pk_set = kwargs.pop('pk_set',None)
- if action == 'pre_add' and instance.rowerplan=='basic':
- if instance.protrialexpires < datetime.date.today() and instance.plantrialexpires < datetime.date.today():
- for id in pk_set:
- team = Team.objects.get(id=id)
- if team.manager.rower.rowerplan not in ['coach']:
- raise ValidationError(
- "You cannot join a team led by a Pro, Free Coach Plan or Self-Coach user"
- )
+ if action == 'pre_add':
+ #if instance.protrialexpires < datetime.date.today() and instance.plantrialexpires < datetime.date.today():
+ for id in pk_set:
+ team = Team.objects.get(id=id)
+ if not can_join_team(instance,team):
+ raise ValidationError(
+ "You cannot join a team led by a Pro, Free Coach Plan or Self-Coach user"
+ )
m2m_changed.connect(check_teams_on_change, sender=Rower.team.through)
@@ -1349,12 +1351,10 @@ class TrainingPlan(models.Model):
def save(self, *args, **kwargs):
manager = self.manager
-
- if manager.rowerplan in ['basic','pro']:
- if manager.plantrialexpires < timezone.now().date():
- raise ValidationError(
- "Basic user cannot have a training plan"
- )
+ if not can_add_plan(manager):
+ raise ValidationError(
+ "Basic user cannot have a training plan"
+ )
if self.enddate < self.startdate:
startdate = self.startdate
@@ -2084,11 +2084,10 @@ class PlannedSession(models.Model):
manager = self.manager
if self.sessiontype not in ['race','indoorrace']:
- if manager.rower.rowerplan in ['basic','pro']:
- if manager.rower.plantrialexpires < timezone.now().date():
- raise ValidationError(
- "You must be a Self-Coach user or higher to create a planned session"
- )
+ if not can_add_session(self.manager):
+ raise ValidationError(
+ "You must be a Self-Coach user or higher to create a planned session"
+ )
# sort units
@@ -2666,7 +2665,7 @@ class Workout(models.Model):
def save(self, *args, **kwargs):
user = self.user
- if self.user.rowerplan == 'freecoach':
+ if not can_add_workout(self.user):
raise forms.ValidationError("Free Coach User cannot have any workouts")
super(Workout, self).save(*args, **kwargs)
diff --git a/rowers/rower_rules.py b/rowers/rower_rules.py
index ca889645..c4e58792 100644
--- a/rowers/rower_rules.py
+++ b/rowers/rower_rules.py
@@ -22,10 +22,53 @@ USER permissions
- rower.team
- These Rules apply to Rowers
+ - Coach can have any number of groups
+ - test_permissions.PermissionFreeCoach.test_coach_groupmanager
+ - test_permissions.PermissionsBasicsTests.test_coach_groupmanager
+ - test_permissions.PermissionsViewTests.test_coach_groups_create
+ - Pro and Plan users can have one group and not more
+ - test_permissions.PermissionsFreeCoach.test_pro_groupmanager
+ - test_permissions.PermissionsViewTests.test_pro_groups_create
- Free Coach user cannot have workouts
+ - test_permissions.PermissionFreeCoach.test_add_workout_freecoach
+ - Free coach can create more than one group
+ - test_permissions.PermissionsFreeCoach.test_plan_groupmanager
+ - Free Coach & Plan (Self Coach) can create planned sessions and team planned sessions
+ - test_permissions.PermissionsFreeCoach.test_plan_create_session
+ - test_permissions.PermissionsFreeCoach.test_coach_create_session
+ - Pro cannot create planned sessions or team planned sessions
+ - test_permissions.PermissionsViewTests.test_pro_create_session
+ - Basic cannot join groups led by Free Coach
+ - test_permissions.PermissionFreeCoach.test_add_basic_pro_or_plan
- Coach can edit athlete settings (if in coachinggroup)
+ - test_permissions.PermissionsViewTests.test_coach_edit_athlete_prefs
+ - test_permissions.PermissionsViewTests.test_coach_edit_athlete_prefs_not
+ - test_permissions.PermissionsViewTests.test_coach_edit_athlete_settings
+ - test_permissions.PermissionsViewTests.test_coach_edit_athlete_settings_not
+ - test_permissions.PermissionsViewTests.test_coach_edit_athlete_account
+ - test_permissions.PermissionsViewTests.test_coach_edit_athlete_account_not
+ - test_permissions.PermissionsViewTests.test_coach_edit_athlete_exportsettings
+ - test_permissions.PermissionsViewTests.test_coach_edit_athlete_exportsettings_not
+ - Coach can upload a workout on behalf of athlete
+ - test_permissions.PermissionsViewTests.test_coach_edit_athlete_upload
+ - test_permissions.PermissionsViewTests.test_coach_edit_athlete_upload_not
+ - Pro and Plan cannot upload a workout on behalf of athlete
+ - test_permissions.PermissionsViewTests.test_plan_edit_athlete_upload
- Coach can run analytics for athlete
+ - test_permissions.PermissionsViewTests.test_coach_edit_athlete_analysis
+ - test_permissions.PermissionsViewTests.test_coach_edit_athlete_analysis_not
+ - test_permissions.PermissionsViewTests.test_pro_edit_athlete_analysis
- Pro and Plan user cannot run analysis for members of their groups
+ - test_permissions.PermissionsViewTests.test_plan_edit_athlete_analysis
+ - Pro and Plan user cannot edit athlete sessings
+ - test_permissions.PermissionsViewTests.test_plan_edit_athlete_settings
+ - test_permissions.PermissionsViewTests.test_pro_edit_athlete_settings
+ - Self-Coach and Pro cannot create more than one group
+ - test_permissions.PermissionsFreeCoach.test_plan_groupmanager
+ - Pro users and higher can join group led by other Pro or higher user
+ - test_permissions.PermissionsFreeCoach.test_add_proplan_pro_or_plan
+ - Pro or basic cannot create planned sessions or team planned sessions
+ - test_permissions.PermissionsFreeCoach.test_pro_create_plannedsession
"""
@@ -43,6 +86,9 @@ def user_is_not_basic(user):
def is_coach(user):
return user.rower.rowerplan in ['coach','freecoach']
+def is_paidcoach(user):
+ return user.rower.rowerplan == 'coach'
+
@rules.predicate
def is_planmember(user):
try:
@@ -78,6 +124,31 @@ def is_protrial(user):
ispromember = is_promember | is_protrial
+can_have_teams = ispromember
+
+@rules.predicate
+def can_add_team(user):
+ if is_coach(user):
+ return True
+
+ if ispromember(user):
+ otherteams = user.rower.get_managed_teams()
+ if otherteams.count() == 0:
+ return True
+
+ return False
+
+@rules.predicate
+def can_add_plan(user):
+ return isplanmember(user)
+
+@rules.predicate
+def can_add_workout(user):
+ if user.is_anonymous:
+ return False
+
+ return user.rower.rowerplan != 'freecoach'
+
@rules.predicate
def is_plantrial(user):
try:
@@ -93,8 +164,13 @@ def is_plantrial(user):
return False
+
isplanmember = is_planmember | is_plantrial
+@rules.predicate
+def can_add_session(user):
+ return isplanmember(user)
+
# User / Coach relationships (Rower object)
@rules.predicate
@@ -139,6 +215,16 @@ def is_rower_team_member(user,rower):
return False
+@rules.predicate
+def can_add_workout_member(user,rower):
+ if not user:
+ return False
+ if user.is_anonymous:
+ return False
+ if user == rower.user:
+ return True
+ return isplanmember(user) and user.rower in rower.get_coaches()
+
# check if user can plan for the rower
@rules.predicate
def can_plan_user(user,rower):
@@ -170,10 +256,17 @@ WORKOUT permissions
- These rules apply to workouts
- User can add, delete and change their own workouts
+ - test_aworkouts
- Coach can add and change workouts for their athletes, but not delete
- - Pro and Plan user cannot add or change an athlete's workout
- - Pro, Basic users can view team members' workout
+ - test_permissions.PermissionsViewTests.test_coach_edit_athlete_upload
+ - test_permissions.PermissionsViewTests.test_coach_edit_athlete_upload_not
+ - Basic, Pro and Plan user can view but cannot add or change an athlete's workout
+ - test_permissions.PermissionsViewTests.test_coach_edit_athlete_workout
+ - test_permissions.PermissionsViewTests.test_plan_edit_athlete_upload
+ - test_permissions.PermissionsViewTests.test_plan_edit_athlete_workout
+ - test_permissions.PermissionsViewTests.test_basic_edit_athelte_workout
- Anonymous users can view team members' workout (but not see list of workouts)
+ - test_aworkouts
- Rules for Workouts are transferred to objects related to the Workout,
- Charts
@@ -205,8 +298,9 @@ def can_view_workout(user,workout):
return True
return False
+can_change_workout = is_workout_user
-rules.add_perm('workout.change_workout',is_workout_user) # replaces checkworkoutuser
+rules.add_perm('workout.change_workout',can_change_workout) # replaces checkworkoutuser
rules.add_perm('workout.view_workout',can_view_workout) # replaces checkworkoutuserview
@@ -218,13 +312,22 @@ rules.add_perm('workout.view_workout',can_view_workout) # replaces checkworkoutu
"""
- These rules apply to planning
- Free coach can create planned sessions and team planned sessions
+ - test_permissions.PermissionsViewTests.test_coach_create_session
- Self coach and higher can create planned sessions and team planned sessions
+ - test_permissions.PermissionsFreeCoach.test_plan_create_session
+ - test_permissions.PermissionsFreeCoach.test_coach_create_session
- Coach can create planned sessions and team planned sessions
+ - test_permissions.PermissionsFreeCoach.test_plan_create_session
+ - test_permissions.PermissionsFreeCoach.test_coach_create_session
- Self Coach and higher can create planned sessions and team planned sessions
+ - test_permissions.PermissionsViewTests.test_plan_create_session
- Pro or Basic cannot create planned sessions or team planned sessions
+ - test_permissions.PermissionsFreeCoach.test_pro_create_plannedsession
+ - test_permissions.PermissionsFreeCoach.test_basic_create_plannedsession
- WHO can comment (plannedsession_comment_view)
- Only Session manager can change session
- Strict View rules (stricter than workouts)
+ - Basic user cannot manage a training plan
- TrainingTarget
- rules for view, add, change, delete
@@ -299,9 +402,12 @@ def can_delete_plan(user,plan):
return False
return user == plan.manager.user
+
+
rules.add_perm('plan.view_plan',can_view_plan)
rules.add_perm('plan.change_plan',can_change_plan)
rules.add_perm('plan.delete_plan',can_delete_plan)
+rules.add_perm('plan.can_add_plan',can_add_plan)
@rules.predicate
def can_view_cycle(user,cycle):
@@ -383,6 +489,9 @@ def can_delete_session(user,session):
return False
+
+
+rules.add_perm('plannedsession.add_session',can_add_session)
rules.add_perm('plannedsession.view_session',can_view_session)
rules.add_perm('plannedsession.change_session',can_change_session)
rules.add_perm('plannedsession.delete_session',can_delete_session)
@@ -395,13 +504,21 @@ rules.add_perm('plannedsession.delete_session',can_delete_session)
- team.private
- These rules apply to a team
- - A Pro rower can be manager of only 1 team (as manager)
+ - A Pro or Plan rower can be manager of only 1 team
+ - test_permissions.PermissionsFreeCoach.test_pro_groupmanager
+ - test_permissions.PermissionsViewTests.test_pro_groups_create
- A coach can have any number of teams (as manager)
+ - test_permissions.PermissionsBasicTest.test_coach_groupmanager
- Basic user cannot manage a group
+ - test_permissions.PermissionsFreeCoach.test_basic_groupmanager
- Basic user cannot join groups led by Free Coach or Pro
+ - test_permissions.PermissionFreeCoach.test_add_basic_pro_or_plan
- Basic athletes can be member of Coach led group
- - Plan user can create 1 group (as manager) and not more
+ - test_permissions.PermissionsBasicTest.test_add_coach
- Pro users (and higher) can join team led by other Pro (or higher) user
+ - test_permissions.PermissionsFreeCoach.test_add_proplan_pro_or_plan
+ - test_permissions.PermissionsViewTests.test_team_member_request_pro_pro
+ - test_permissions.PermissionsViewTests.test_team_member_request_basic_pro
- On downgrade, Coach users lose all but their oldest team (add test!)
"""
@@ -437,6 +554,10 @@ def can_change_team(user,team):
def can_delete_team(user,team):
return is_team_manager(user,team)
+@rules.predicate
+def can_join_team(user,team):
+ return is_paid_coach(team.manager) or ispromember(user)
+
# For Team functionality
rules.add_perm('teams.view_team',can_view_team)
rules.add_perm('teams.add_team',user_is_not_basic)
diff --git a/rowers/teams.py b/rowers/teams.py
index 5e3fe126..9aa98b68 100644
--- a/rowers/teams.py
+++ b/rowers/teams.py
@@ -169,15 +169,7 @@ def rower_get_managers(rower):
return managers
def rower_get_coaches(rower):
- coaches = []
- for group in rower.coachinggroups.all():
- try:
- coach = Rower.objects.get(mycoachgroup=group)
- coaches.append(coach)
- except Rower.DoesNotExist:
- pass
-
- return coaches
+ return rower.get_coaches()
def coach_getcoachees(coach):
diff --git a/rowers/tests/test_permissions.py b/rowers/tests/test_permissions.py
index 1671d82d..fae37d38 100644
--- a/rowers/tests/test_permissions.py
+++ b/rowers/tests/test_permissions.py
@@ -694,7 +694,7 @@ class PermissionsViewTests(TestCase):
self.assertEqual(response.status_code,403)
# 3, Export Settings
- def test_coach_edit_athlete_settings(self):
+ def test_coach_edit_athlete_exportsettings(self):
self.rbasic.team.add(self.teamcoach)
self.rbasic.coachinggroups.add(self.coachinggroup)
@@ -706,7 +706,7 @@ class PermissionsViewTests(TestCase):
response = self.c.get(url)
self.assertEqual(response.status_code,200)
- def test_coach_edit_athlete_settings_not(self):
+ def test_coach_edit_athlete_exportsettings_not(self):
self.rbasic.team.add(self.teamcoach)
login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
@@ -718,7 +718,7 @@ class PermissionsViewTests(TestCase):
self.assertEqual(response.status_code,403)
# 4, Account settings
- def test_coach_edit_athlete_settings(self):
+ def test_coach_edit_athlete_account(self):
self.rbasic.team.add(self.teamcoach)
self.rbasic.coachinggroups.add(self.coachinggroup)
@@ -730,7 +730,7 @@ class PermissionsViewTests(TestCase):
response = self.c.get(url)
self.assertEqual(response.status_code,200)
- def test_coach_edit_athlete_settings_not(self):
+ def test_coach_edit_athlete_account_not(self):
self.rbasic.team.add(self.teamcoach)
login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
diff --git a/rowers/views/planviews.py b/rowers/views/planviews.py
index 097af6f2..74fed42a 100644
--- a/rowers/views/planviews.py
+++ b/rowers/views/planviews.py
@@ -1906,7 +1906,7 @@ class PlannedSessionDelete(DeleteView):
return obj
-@user_passes_test(isplanmember,login_url="/rowers/paidplans",
+@user_passes_test(can_add_plan,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
def rower_create_trainingplan(request,userid=0):
diff --git a/rowers/views/statements.py b/rowers/views/statements.py
index 43fa3315..501e37b2 100644
--- a/rowers/views/statements.py
+++ b/rowers/views/statements.py
@@ -43,7 +43,8 @@ from rowers.rower_rules import (
is_workout_user,isplanmember,can_delete_session,
can_view_target,can_change_target,can_delete_target,
can_view_plan,can_change_plan,can_delete_plan,
- can_view_cycle,can_change_cycle,can_delete_cycle
+ can_view_cycle,can_change_cycle,can_delete_cycle,
+ can_add_workout_member,
)
from django.shortcuts import render
diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py
index a8628322..a187f020 100644
--- a/rowers/views/workoutviews.py
+++ b/rowers/views/workoutviews.py
@@ -4875,7 +4875,7 @@ def team_workout_upload_view(request,message="",
workouttype = form.cleaned_data['workouttype']
if rowerform.is_valid():
u = rowerform.cleaned_data['user']
- if u and request.user.rower in teams.rower_get_coaches(u.rower):
+ if can_add_workout_member(user,u.rower):
r = getrower(u)
else:
message = 'Please select a rower'
From 88703bb34ee1f26c32d7532c2bf09d97c0783e14 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Wed, 15 Jan 2020 11:08:32 +0100
Subject: [PATCH 26/80] some fixes
---
rowers/models.py | 12 ++++++------
rowers/urls.py | 2 +-
rowers/views/planviews.py | 9 +++++----
rowers/views/statements.py | 5 +++++
4 files changed, 17 insertions(+), 11 deletions(-)
diff --git a/rowers/models.py b/rowers/models.py
index e2083164..91abb9b4 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -904,11 +904,11 @@ class Rower(models.Model):
def get_coaches(self):
coaches = []
for group in self.coachinggroups.all():
- try:
- coach = Rower.objects.get(mycoachgroup=group)
- coaches.append(coach)
- except Rower.DoesNotExist:
- pass
+ try:
+ coach = Rower.objects.get(mycoachgroup=group)
+ coaches.append(coach)
+ except Rower.DoesNotExist:
+ pass
return coaches
@@ -2665,7 +2665,7 @@ class Workout(models.Model):
def save(self, *args, **kwargs):
user = self.user
- if not can_add_workout(self.user):
+ if not can_add_workout(user.user):
raise forms.ValidationError("Free Coach User cannot have any workouts")
super(Workout, self).save(*args, **kwargs)
diff --git a/rowers/urls.py b/rowers/urls.py
index 4a890095..50b095a1 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -572,7 +572,7 @@ urlpatterns = [
# re_path(r'^workout/compare/(?P\d+)/(?P\d+)/(?P\w+.*)/(?P[\w\ ]+.*)/$',views.workout_comparison_view2),
re_path(r'^test\_callback',views.rower_process_testcallback,name='rower_process_testcallback'),
re_path(r'^createplan/$',views.rower_create_trainingplan,name='rower_create_trainingplan'),
- re_path(r'^createplan/user/(?P\d+)/$',views.rower_create_trainingplan,name='rower_create_trainingplan'),
+ re_path(r'^createplan/user/(?P\d+)/$',views.rower_create_trainingplan,name='rower_create_trainingplan'),
re_path(r'^deleteplan/(?P\d+)/$',login_required(
views.TrainingPlanDelete.as_view()),name='trainingplan_delete_view'),
re_path(r'^deletemicrocycle/(?P\d+)/$',login_required(
diff --git a/rowers/views/planviews.py b/rowers/views/planviews.py
index 74fed42a..3960de10 100644
--- a/rowers/views/planviews.py
+++ b/rowers/views/planviews.py
@@ -1906,11 +1906,12 @@ class PlannedSessionDelete(DeleteView):
return obj
-@user_passes_test(can_add_plan,login_url="/rowers/paidplans",
+@user_passes_test(isplanmember,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
-def rower_create_trainingplan(request,userid=0):
- therower = getrequestrower(request,userid=userid)
+@permission_required('rower.add_plan',fn=get_rower_by_userid,raise_exception=True)
+def rower_create_trainingplan(request,id=0):
+ therower = getrequestrower(request,userid=id)
theuser = therower.user
themanager = getrower(request.user)
@@ -2016,7 +2017,7 @@ def rower_create_trainingplan(request,userid=0):
},
{
'url':reverse(rower_create_trainingplan,
- kwargs={'userid':userid}),
+ kwargs={'id':id}),
'name': 'Manage Plans and Targets'
}
]
diff --git a/rowers/views/statements.py b/rowers/views/statements.py
index 501e37b2..aa984e21 100644
--- a/rowers/views/statements.py
+++ b/rowers/views/statements.py
@@ -348,6 +348,11 @@ def get_user_by_id(*args,**kwargs):
return get_object_or_404(User,pk=id)
+def get_rower_by_userid(*args,**kwargs):
+ userid = kwargs['id']
+ u = User.objects.get(id=userid)
+ return u.rower
+
def getrequestrower(request,rowerid=0,userid=0,notpermanent=False):
userid = int(userid)
From 8683d8eaa4c979ff1473d67416f921adf908d5f8 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Wed, 15 Jan 2020 15:55:41 +0100
Subject: [PATCH 27/80] more fixes
---
rowers/models.py | 2 +-
rowers/rower_rules.py | 7 +++++--
rowers/teams.py | 9 ++++++---
rowers/templates/menu_other.html | 6 ++----
rowers/templates/menu_profile.html | 4 ++--
rowers/views/planviews.py | 6 +++---
rowers/views/statements.py | 26 +++++---------------------
rowers/views/teamviews.py | 7 +------
rowers/views/workoutviews.py | 4 +++-
templates/newbase.html | 2 +-
10 files changed, 29 insertions(+), 44 deletions(-)
diff --git a/rowers/models.py b/rowers/models.py
index 91abb9b4..6590aa57 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -1351,7 +1351,7 @@ class TrainingPlan(models.Model):
def save(self, *args, **kwargs):
manager = self.manager
- if not can_add_plan(manager):
+ if not can_add_plan(manager.user):
raise ValidationError(
"Basic user cannot have a training plan"
)
diff --git a/rowers/rower_rules.py b/rowers/rower_rules.py
index c4e58792..5de0d339 100644
--- a/rowers/rower_rules.py
+++ b/rowers/rower_rules.py
@@ -86,7 +86,7 @@ def user_is_not_basic(user):
def is_coach(user):
return user.rower.rowerplan in ['coach','freecoach']
-def is_paidcoach(user):
+def is_paid_coach(user):
return user.rower.rowerplan == 'coach'
@rules.predicate
@@ -228,6 +228,9 @@ def can_add_workout_member(user,rower):
# check if user can plan for the rower
@rules.predicate
def can_plan_user(user,rower):
+ if not isplanmember(user):
+ return False
+
try:
r = user.rower
except AttributeError:
@@ -243,7 +246,7 @@ def can_plan_user(user,rower):
return False
-rules.add_perm('rower.can_plan',can_plan_user) # replaces checkaccessplanuser
+rules.add_perm('rower.add_plan',can_plan_user) # replaces checkaccessplanuser
rules.add_perm('rower.is_coach',is_coach_user) # replaces checkaccessuser
rules.add_perm('rower.is_pro',ispromember)
diff --git a/rowers/teams.py b/rowers/teams.py
index 9aa98b68..aa1c8903 100644
--- a/rowers/teams.py
+++ b/rowers/teams.py
@@ -173,12 +173,15 @@ def rower_get_coaches(rower):
def coach_getcoachees(coach):
+ rowers = []
if coach.mycoachgroup and is_coach(coach.user):
- return Rower.objects.filter(
+ rs = Rower.objects.filter(
coachinggroups__in=[coach.mycoachgroup]
).distinct().order_by("user__last_name","user__first_name")
- else:
- return []
+ for r in rs:
+ rowers.append(r)
+
+ return rowers
def coach_remove_athlete(coach,rower):
try:
diff --git a/rowers/templates/menu_other.html b/rowers/templates/menu_other.html
index fad56ed0..a3961006 100644
--- a/rowers/templates/menu_other.html
+++ b/rowers/templates/menu_other.html
@@ -14,7 +14,7 @@
{% else %}
Compare
{% endif %}
-
+
Upload
@@ -33,7 +33,7 @@
-
+
-
-
{% include 'menuscript.html' %}
diff --git a/rowers/templates/menu_profile.html b/rowers/templates/menu_profile.html
index bdeb6f54..05cf98f1 100644
--- a/rowers/templates/menu_profile.html
+++ b/rowers/templates/menu_profile.html
@@ -44,13 +44,13 @@
{% if user.is_authenticated and user|is_manager and rower %}
-{% if user|coach_rowers %}
+{% if request.user|coach_rowers %}