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'