From 9bb7c3bef21940dff469f6027a9bcdfedb4712ff Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 15 Feb 2019 14:51:15 +0100 Subject: [PATCH] test writing (initial set) complete might need to add permissions for viewin --- rowers/models.py | 58 +- rowers/tests/test_aavirtualevents.py | 18 +- rowers/tests/test_permissions.py | 822 +++++++++++++++++++++++++- rowers/tests/test_plans.py | 4 +- rowers/tests/testdata/testdata.csv.gz | Bin 11457 -> 11426 bytes rowers/views/teamviews.py | 4 +- rowers/views/workoutviews.py | 2 +- 7 files changed, 859 insertions(+), 49 deletions(-) diff --git a/rowers/models.py b/rowers/models.py index 687bcaa6..7f7d5300 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -349,6 +349,15 @@ class Team(models.Model): raise ValidationError( "Basic user cannot be team manager" ) + + if manager.rower.rowerplan in ['plan','pro']: + otherteams = Team.objects.filter(manager=manager) + if len(otherteams) >= 1: + raise ValidationError( + "Pro and Self-Coach users cannot have more than one team" + ) + + super(Team, self).save(*args,**kwargs) class TeamForm(ModelForm): @@ -844,6 +853,7 @@ class Rower(models.Model): def clean_email(self): return self.user.email.lower() + class DeactivateUserForm(forms.ModelForm): class Meta: model = User @@ -856,13 +866,31 @@ class DeleteUserForm(forms.ModelForm): class Meta: model = User fields = [] + + +from django.db.models.signals import m2m_changed -@receiver(models.signals.post_save,sender=Rower) -def auto_delete_teams_on_change(sender, instance, **kwargs): - if instance.rowerplan != 'coach': - teams = Team.objects.filter(manager=instance.user) - for team in teams: - team.delete() +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': + 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 or Self-Coach user" + ) + +m2m_changed.connect(check_teams_on_change, sender=Rower.team.through) + + +#@receiver(models.signals.post_save,sender=Rower) +#def auto_delete_teams_on_change(sender, instance, **kwargs): +# if instance.rowerplan != 'coach': +# teams = Team.objects.filter(manager=instance.user) +# for team in teams: +# team.delete() from rowers.metrics import axlabels favchartlabelsx = axlabels.copy() @@ -1149,6 +1177,12 @@ class TrainingPlan(models.Model): return stri def save(self, *args, **kwargs): + manager = self.manager + if manager.rowerplan in ['basic','pro']: + raise ValidationError( + "Basic user cannot have a training plan" + ) + if self.enddate < self.startdate: startdate = self.startdate enddate = self.enddate @@ -1194,6 +1228,7 @@ class TrainingPlan(models.Model): else: createmacrofillers(self) + class TrainingPlanForm(ModelForm): class Meta: model = TrainingPlan @@ -1563,6 +1598,8 @@ class TrainingMacroCycle(models.Model): meso.save() else: createmesofillers(self) + + class TrainingMacroCycleForm(ModelForm): class Meta: @@ -1649,6 +1686,7 @@ class TrainingMesoCycle(models.Model): else: createmicrofillers(self) + class TrainingMicroCycle(models.Model): plan = models.ForeignKey(TrainingMesoCycle) name = models.CharField(max_length=150,blank=True) @@ -1863,6 +1901,14 @@ class PlannedSession(models.Model): def save(self, *args, **kwargs): if self.sessionvalue <= 0: self.sessionvalue = 1 + + manager = self.manager + if self.sessiontype not in ['race','indoorrace']: + if manager.rower.rowerplan in ['basic','pro']: + raise ValidationError( + "Basic user cannot be team manager" + ) + # sort units if self.sessionmode == 'distance': diff --git a/rowers/tests/test_aavirtualevents.py b/rowers/tests/test_aavirtualevents.py index 73ad8504..2ca74dbb 100644 --- a/rowers/tests/test_aavirtualevents.py +++ b/rowers/tests/test_aavirtualevents.py @@ -69,12 +69,14 @@ class VirtualEventViewTest(TestCase): yesterday = nu-datetime.timedelta(days=1) tomorrow = nu+datetime.timedelta(days=1) nextweek = nu+datetime.timedelta(days=7) + intwoweeks = nu+datetime.timedelta(days=14) lastweek = nu-datetime.timedelta(days=7) self.yesterday = yesterday self.tomorrow = tomorrow self.nextweek = nextweek self.lastweek = lastweek + self.intwoweeks = intwoweeks # erg races @@ -396,8 +398,8 @@ class VirtualEventViewTest(TestCase): 'registration_form':'deadline', 'registration_closure_0': self.nextweek.strftime('%Y-%m-%d'), 'registration_closure_1': self.nextweek.strftime('%H:%M:%S'), - 'evaluation_closure_0': self.nextweek.strftime('%Y-%m-%d'), - 'evaluation_closure_1': self.nextweek.strftime('%H:%M:%S'), + 'evaluation_closure_0': self.intwoweeks.strftime('%Y-%m-%d'), + 'evaluation_closure_1': self.intwoweeks.strftime('%H:%M:%S'), 'contact_phone': '', 'contact_email': self.u.email, 'timezone': 'UTC' @@ -440,8 +442,8 @@ class VirtualEventViewTest(TestCase): 'registration_form':'deadline', 'registration_closure_0': self.nextweek.strftime('%Y-%m-%d'), 'registration_closure_1': self.nextweek.strftime('%H:%M:%S'), - 'evaluation_closure_0': self.nextweek.strftime('%Y-%m-%d'), - 'evaluation_closure_1': self.nextweek.strftime('%H:%M:%S'), + 'evaluation_closure_0': self.intwoweeks.strftime('%Y-%m-%d'), + 'evaluation_closure_1': self.intwoweeks.strftime('%H:%M:%S'), 'contact_phone': '', 'contact_email': self.u.email, 'timezone': 'UTC' @@ -485,8 +487,8 @@ class VirtualEventViewTest(TestCase): 'registration_form':'deadline', 'registration_closure_0': self.nextweek.strftime('%Y-%m-%d'), 'registration_closure_1': self.nextweek.strftime('%H:%M:%S'), - 'evaluation_closure_0': self.nextweek.strftime('%Y-%m-%d'), - 'evaluation_closure_1': self.nextweek.strftime('%H:%M:%S'), + 'evaluation_closure_0': self.intwoweeks.strftime('%Y-%m-%d'), + 'evaluation_closure_1': self.intwoweeks.strftime('%H:%M:%S'), 'contact_phone': '', 'contact_email': self.u.email, } @@ -522,8 +524,8 @@ class VirtualEventViewTest(TestCase): 'registration_form':'deadline', 'registration_closure_0': self.nextweek.strftime('%Y-%m-%d'), 'registration_closure_1': self.nextweek.strftime('%H:%M:%S'), - 'evaluation_closure_0': self.nextweek.strftime('%Y-%m-%d'), - 'evaluation_closure_1': self.nextweek.strftime('%H:%M:%S'), + 'evaluation_closure_0': self.intwoweeks.strftime('%Y-%m-%d'), + 'evaluation_closure_1': self.intwoweeks.strftime('%H:%M:%S'), 'contact_phone': '', 'contact_email': self.u.email, } diff --git a/rowers/tests/test_permissions.py b/rowers/tests/test_permissions.py index 88f64e22..8c6e2618 100644 --- a/rowers/tests/test_permissions.py +++ b/rowers/tests/test_permissions.py @@ -2,6 +2,8 @@ from statements import * from django.utils import timezone nu = datetime.datetime.now(tz=timezone.utc) +from django.db import transaction + # set up import rowers.teams as teams @@ -50,6 +52,30 @@ class PermissionsBasicsTests(TestCase): self.upro.set_password(self.password) self.upro.save() + self.uplan2 = UserFactory(username='planuser2') + self.rplan2 = Rower.objects.create(user=self.uplan2, + birthdate=faker.profile()['birthdate'], + gdproptin=True,gdproptindate=timezone.now(), + rowerplan='plan') + + self.uplan2_workouts = WorkoutFactory.create_batch(5, user=self.rplan2) + self.factory = RequestFactory() + self.password = faker.word() + self.uplan2.set_password(self.password) + self.uplan2.save() + + self.upro2 = UserFactory(username='prouser2') + self.rpro2 = Rower.objects.create(user=self.upro2, + birthdate=faker.profile()['birthdate'], + gdproptin=True,gdproptindate=timezone.now(), + rowerplan='pro') + + self.upro2_workouts = WorkoutFactory.create_batch(5, user=self.rpro2) + self.factory = RequestFactory() + self.password = faker.word() + self.upro2.set_password(self.password) + self.upro2.save() + self.ubasic = UserFactory(username='basicuser') self.rbasic = Rower.objects.create(user=self.ubasic, birthdate=faker.profile()['birthdate'], @@ -64,35 +90,169 @@ class PermissionsBasicsTests(TestCase): - ## TeamPro, TeamCoach, TeamSelfCoach + ## TeamPro, TeamCoach, TeamSelfCoach + + self.teampro = Team.objects.create( + name=faker.word(), + notes=faker.text(), + manager=self.upro2) + + self.teamplan = Team.objects.create( + name=faker.word(), + notes=faker.text(), + manager=self.uplan2) + + self.teamcoach = Team.objects.create( + name=faker.word(), + notes=faker.text(), + manager=self.ucoach) # Requirements ## Low level ## Coach can have any number of groups + def test_plan_groupmanager(self): + team1 = Team.objects.create( + name = 'FirstTeam', + notes = faker.text(), + manager = self.ucoach, + ) + + self.assertEqual(team1.manager,self.ucoach) + + team2 = Team.objects.create( + name = 'SecondTeam', + notes = faker.text(), + manager = self.ucoach, + ) + + self.assertEqual(team2.manager,self.ucoach) + + + team3 = Team.objects.create( + name = 'SecondTeam', + notes = faker.text(), + manager = self.ucoach, + ) + + self.assertEqual(team3.manager,self.ucoach) + + ## Basic athletes can be member of Coach led group + def test_add_coach(self): + self.rbasic.team.add(self.teamcoach) + self.assertIn(self.teamcoach,self.rbasic.team.all()) - ## Coach can create planned sessions and team planned sessions ## Self coach can create one group - ## Self coach cannot create more than one group + def test_plan_groupmanager(self): + team1 = Team.objects.create( + name = 'FirstTeam', + notes = faker.text(), + manager = self.uplan, + ) + + self.assertEqual(team1.manager,self.uplan) + + with self.assertRaises(ValidationError): + team2 = Team.objects.create( + name = 'SecondTeam', + notes = faker.text(), + manager = self.uplan, + ) + + ## Pro users (and higher) can join group led by other Pro (or higher) user + def test_add_proplan_pro_or_plan(self): + self.rpro.team.add(self.teamplan) + self.assertIn(self.teamplan,self.rpro.team.all()) + + self.rpro.team.add(self.teampro) + self.assertIn(self.teampro,self.rpro.team.all()) + + self.rplan.team.add(self.teamplan) + self.assertIn(self.teamplan,self.rplan.team.all()) + + self.rplan.team.add(self.teampro) + self.assertIn(self.teampro,self.rplan.team.all()) + + self.rcoach.team.add(self.teamplan) + self.assertIn(self.teamplan,self.rcoach.team.all()) + + self.rcoach.team.add(self.teampro) + self.assertIn(self.teampro,self.rcoach.team.all()) + - ## Self Coach 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 + def test_plan_create_session(self): + ps = PlannedSession.objects.create( + manager=self.uplan, + name=faker.word(), + comment=faker.text() + ) + self.assertEqual(ps.manager,self.uplan) + + def test_coach_create_session(self): + ps = PlannedSession.objects.create( + manager=self.ucoach, + name=faker.word(), + comment=faker.text() + ) + self.assertEqual(ps.manager,self.ucoach) ## Pro can have one group - ## Pro cannot create more than one group + def test_pro_groupmanager(self): + team1 = Team.objects.create( + name = 'FirstTeam', + notes = faker.text(), + manager = self.upro, + ) + + self.assertEqual(team1.manager,self.upro) + + with self.assertRaises(ValidationError): + team2 = Team.objects.create( + name = 'SecondTeam', + notes = faker.text(), + manager = self.upro, + ) - ## Pro cannot create planned sessions or team planned sessions + + ## Pro or Basic cannot create planned sessions or team planned sessions + def test_pro_create_plannedsession(self): + with self.assertRaises(ValidationError): + ps = PlannedSession.objects.create( + manager=self.upro, + name = faker.word(), + comment = faker.text() + ) + + def test_basic_create_plannedsession(self): + with self.assertRaises(ValidationError): + ps = PlannedSession.objects.create( + manager=self.ubasic, + name = faker.word(), + comment = faker.text() + ) ## Basic cannot join groups led by Pro or Self Coach + def test_add_basic_pro_or_plan(self): + with transaction.atomic(): + with self.assertRaises(ValidationError): + self.rbasic.team.add(self.teamplan) + + with transaction.atomic(): + with self.assertRaises(ValidationError): + self.rbasic.team.add(self.teampro) - ## Basic can join group led by Coach ## Basic cannot manage a group def test_basic_groupmanager(self): @@ -104,52 +264,652 @@ class PermissionsBasicsTests(TestCase): private = 'open', viewing = 'allmembers') + ## On downgrade, Coach users lose all but their oldest team + # View based +@override_settings(TESTING=True) +class PermissionsViewTests(TestCase): + def setUp(self): + self.c = Client() + ## Users - Pro, Basic, Coach & Self Coach -## Coach can have any number of groups + self.ucoach = UserFactory(username='coachuser') + self.rcoach = Rower.objects.create(user=self.ucoach, + birthdate=faker.profile()['birthdate'], + gdproptin=True,gdproptindate=timezone.now(), + rowerplan='coach') -## Basic athletes can be member of Coach led group + self.ucoach_workouts = WorkoutFactory.create_batch(5, user=self.rcoach) + self.factory = RequestFactory() + self.ucoachpassword = faker.word() + self.ucoach.set_password(self.ucoachpassword) + self.ucoach.save() + + self.uplan = UserFactory(username='planuser') + self.rplan = Rower.objects.create(user=self.uplan, + birthdate=faker.profile()['birthdate'], + gdproptin=True,gdproptindate=timezone.now(), + rowerplan='plan') -## Coach can create planned sessions and team planned sessions + self.uplan_workouts = WorkoutFactory.create_batch(5, user=self.rplan) + self.factory = RequestFactory() + self.uplanpassword = faker.word() + self.uplan.set_password(self.uplanpassword) + self.uplan.save() + + self.upro = UserFactory(username='prouser') + self.rpro = Rower.objects.create(user=self.upro, + birthdate=faker.profile()['birthdate'], + gdproptin=True,gdproptindate=timezone.now(), + rowerplan='pro') -## Coach can edit on behalf of athlete + self.upro_workouts = WorkoutFactory.create_batch(5, user=self.rpro) + self.factory = RequestFactory() + self.upropassword = faker.word() + self.upro.set_password(self.upropassword) + self.upro.save() -## Coach can run analytics for athlete + self.uplan2 = UserFactory(username='planuser2') + self.rplan2 = Rower.objects.create(user=self.uplan2, + birthdate=faker.profile()['birthdate'], + gdproptin=True,gdproptindate=timezone.now(), + rowerplan='plan') -## Coach can upload on behalf of athlete + self.uplan2_workouts = WorkoutFactory.create_batch(5, user=self.rplan2) + self.factory = RequestFactory() + self.uplan2password = faker.word() + self.uplan2.set_password(self.uplan2password) + self.uplan2.save() + + self.upro2 = UserFactory(username='prouser2') + self.rpro2 = Rower.objects.create(user=self.upro2, + birthdate=faker.profile()['birthdate'], + gdproptin=True,gdproptindate=timezone.now(), + rowerplan='pro') -## Coach can edit athlete's workout + self.upro2_workouts = WorkoutFactory.create_batch(5, user=self.rpro2) + self.factory = RequestFactory() + self.upro2password = faker.word() + self.upro2.set_password(self.upro2password) + self.upro2.save() -## Self coach can create one group + self.ubasic = UserFactory(username='basicuser') + self.rbasic = Rower.objects.create(user=self.ubasic, + birthdate=faker.profile()['birthdate'], + gdproptin=True,gdproptindate=timezone.now(), + rowerplan='basic') -## Self coach cannot create more than one group + self.ubasic_workouts = WorkoutFactory.create_batch(5, user=self.rbasic) + self.factory = RequestFactory() + self.ubasicpassword = faker.word() + self.ubasic.set_password(self.ubasicpassword) + self.ubasic.save() + + + + ## TeamPro, TeamCoach, TeamSelfCoach -## Pro users (and higher) can join group led by other Pro (or higher) user + self.teampro = Team.objects.create( + name=faker.word(), + notes=faker.text(), + manager=self.upro2) -## Self Coach can create planned sessions and team planned sessions + self.teamplan = Team.objects.create( + name=faker.word(), + notes=faker.text(), + manager=self.uplan2) -## Self Coach cannot edit on behalf of athlete + self.teamcoach = Team.objects.create( + name=faker.word(), + notes=faker.text(), + manager=self.ucoach) -## Self Coach cannot run analytics on behalf of athlete -## Self Coach cannot upload on behalf of athlete + ## Coach can have any number of groups + def test_coach_groups_create(self): + login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword) + self.assertTrue(login) + + url = reverse('team_create_view') -## Pro can have one group + response = self.c.get(url) + self.assertTrue(response.status_code,200) -## Pro cannot create more than one group + # Create 1st new team + form_data = { + 'name': faker.word(), + 'notes': faker.text(), + 'private': 'open', + 'viewing': 'allmembers' + } -## Pro cannot create planned sessions or team planned sessions + form = TeamForm(form_data) + if not form.is_valid(): + print form.errors + + self.assertTrue(form.is_valid()) -## Pro can create planned sessions and team planned sessions + expected_url = reverse('rower_teams_view') -## Pro cannot edit on behalf of athlete + response = self.c.post(url,form_data,follow=True) + self.assertRedirects(response, + expected_url=expected_url, + status_code=302,target_status_code=200) -## Pro cannot run analytics on behalf of athlete -## Basic cannot join groups from Pro or Self Coach users (redirects to paid plans) + # Create 2nd new team + form_data = { + 'name': faker.word(), + 'notes': faker.text(), + 'private': 'open', + 'viewing': 'allmembers' + } -## Pro users can see team members' workout, but not edit + form = TeamForm(form_data) + if not form.is_valid(): + print form.errors + + self.assertTrue(form.is_valid()) -## Self Coach users can see team members' workout, but not edit + expected_url = reverse('rower_teams_view') + + response = self.c.post(url,form_data,follow=True) + self.assertRedirects(response, + expected_url=expected_url, + status_code=302,target_status_code=200) + + ## Basic athletes can be member of Coach led group + + ## Coach can create planned sessions and team planned sessions + def test_coach_create_session(self): + login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword) + self.assertTrue(login) + + url = reverse('plannedsession_create_view') + + startdate = nu.date() + enddate = (nu+datetime.timedelta(days=3)).date() + preferreddate = startdate + + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + post_data = { + 'comment': faker.text(), + 'criterium': 'none', + 'enddate': enddate.strftime("%Y-%m-%d"), + 'preferreddate': preferreddate.strftime("%Y-%m-%d"), + 'startdate': startdate.strftime("%Y-%m-%d"), + 'sessionmode':'time', + 'sessiontype':'session', + 'sessionunit':'min', + 'sessionvalue': '60', + 'name': faker.word(), + } + + print 'posting to sessions/create' + + form = PlannedSessionForm(post_data) + self.assertTrue(form.is_valid()) + + response = self.c.post(url,post_data) + self.assertEqual(response.status_code,200) + + + + ## Coach can edit on behalf of athlete + def test_coach_edit_athlete_settings(self): + self.rbasic.team.add(self.teamcoach) + + login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword) + self.assertTrue(login) + + url = reverse('rower_prefs_view',kwargs={'userid':self.ubasic.id}) + + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + ## 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): + self.rbasic.team.add(self.teamcoach) + + login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword) + self.assertTrue(login) + + + url = reverse('cumstats', + kwargs={ + 'theuser':self.ubasic.id, + } + ) + + response = self.c.get(url) + + self.assertEqual(response.status_code,200) + + + ## Coach can upload on behalf of athlete + @patch('rowers.dataprep.create_engine') + @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) + def test_coach_edit_athlete_upload(self,mocked_sqlalchemy,mocked_getsmallrowdata_db): + self.rbasic.team.add(self.teamcoach) + + login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword) + self.assertTrue(login) + + url = reverse('team_workout_upload_view') + + aantal = len(Workout.objects.filter(user=self.rbasic)) + + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + filename = 'rowers/tests/testdata/testdata.csv' + f = open(filename,'rb') + file_data = {'file': f} + form_data = { + 'title':'test', + 'workouttype':'rower', + 'boattype':'1x', + 'notes':'aap noot mies', + 'make_plot':False, + 'upload_to_c2':False, + 'plottype':'timeplot', + 'file': f, + 'user': self.ubasic.id + } + + response = self.c.post(url, form_data, follow=True) + f.close() + + self.assertEqual(response.status_code,200) + + self.assertRedirects(response, + expected_url = url, + status_code=302,target_status_code=200) + + aantal2 = len(Workout.objects.filter(user=self.rbasic)) + + self.assertEqual(aantal2,aantal+1) + + ## Coach can edit athlete's workout + def test_coach_edit_athlete_workout(self): + self.rbasic.team.add(self.teamcoach) + + login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword) + self.assertTrue(login) + + url = reverse('workout_edit_view', + kwargs={'id':encoder.encode_hex(self.ubasic_workouts[0].id)} + ) + + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + ## Self coach can create one group + ## Self coach cannot create more than one group + def test_plan_groups_create(self): + login = self.c.login(username=self.uplan.username, password=self.uplanpassword) + self.assertTrue(login) + + url = reverse('team_create_view') + + response = self.c.get(url) + self.assertTrue(response.status_code,200) + + # Create 1st new team + form_data = { + 'name': faker.word(), + 'notes': faker.text(), + 'private': 'open', + 'viewing': 'allmembers' + } + + form = TeamForm(form_data) + if not form.is_valid(): + print form.errors + + self.assertTrue(form.is_valid()) + + expected_url = reverse('rower_teams_view') + + response = self.c.post(url,form_data,follow=True) + self.assertRedirects(response, + expected_url=expected_url, + status_code=302,target_status_code=200) + + + # Create 2nd new team - should redirect to paid plans + form_data = { + 'name': faker.word(), + 'notes': faker.text(), + 'private': 'open', + 'viewing': 'allmembers' + } + + form = TeamForm(form_data) + if not form.is_valid(): + print form.errors + + self.assertTrue(form.is_valid()) + + expected_url = reverse('paidplans') + + response = self.c.post(url,form_data,follow=True) + self.assertRedirects(response, + expected_url=expected_url, + status_code=302,target_status_code=200) + + + ## Self Coach can create planned sessions and team planned sessions + def test_plan_create_session(self): + login = self.c.login(username=self.uplan2.username, password=self.uplan2password) + self.assertTrue(login) + + url = reverse('plannedsession_create_view') + + startdate = nu.date() + enddate = (nu+datetime.timedelta(days=3)).date() + preferreddate = startdate + + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + post_data = { + 'comment': faker.text(), + 'criterium': 'none', + 'enddate': enddate.strftime("%Y-%m-%d"), + 'preferreddate': preferreddate.strftime("%Y-%m-%d"), + 'startdate': startdate.strftime("%Y-%m-%d"), + 'sessionmode':'time', + 'sessiontype':'session', + 'sessionunit':'min', + 'sessionvalue': '60', + 'name': faker.word(), + } + + print 'posting to sessions/create' + + form = PlannedSessionForm(post_data) + self.assertTrue(form.is_valid()) + + response = self.c.post(url,post_data) + self.assertEqual(response.status_code,200) + + + + ## Self Coach cannot edit on behalf of athlete + def test_plan_edit_athlete_settings(self): + self.rbasic.team.add(self.teamplan) + + login = self.c.login(username=self.uplan2.username, password=self.uplan2password) + self.assertTrue(login) + + url = reverse('rower_prefs_view',kwargs={'userid':self.ubasic.id}) + + response = self.c.get(url) + self.assertEqual(response.status_code,404) + + ## Self Coach cannot run analytics on behalf of athlete + @patch('rowers.dataprep.read_cols_df_sql', side_effect = mocked_read_df_cols_sql_multistats) + def test_plan_edit_athlete_analysis(self,mocked_df): + self.rbasic.team.add(self.teamplan) + + login = self.c.login(username=self.uplan2.username, password=self.uplan2password) + self.assertTrue(login) + + + url = reverse('cumstats', + kwargs={ + 'theuser':self.ubasic.id, + } + ) + + response = self.c.get(url) + + self.assertEqual(response.status_code,404) + + ## Self Coach cannot upload on behalf of athlete + @patch('rowers.dataprep.create_engine') + @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) + def test_plan_edit_athlete_upload(self,mocked_sqlalchemy,mocked_getsmallrowdata_db): + self.rbasic.team.add(self.teamplan) + + login = self.c.login(username=self.uplan2.username, password=self.uplan2password) + self.assertTrue(login) + + url = reverse('team_workout_upload_view') + + response = self.c.get(url) + self.assertEqual(response.status_code,404) + + + ## Pro can have one group + ## Pro cannot create more than one group + def test_pro_groups_create(self): + login = self.c.login(username=self.upro.username, password=self.upropassword) + self.assertTrue(login) + + url = reverse('team_create_view') + + response = self.c.get(url) + self.assertTrue(response.status_code,200) + + # Create 1st new team + form_data = { + 'name': faker.word(), + 'notes': faker.text(), + 'private': 'open', + 'viewing': 'allmembers' + } + + form = TeamForm(form_data) + if not form.is_valid(): + print form.errors + + self.assertTrue(form.is_valid()) + + expected_url = reverse('rower_teams_view') + + response = self.c.post(url,form_data,follow=True) + self.assertRedirects(response, + expected_url=expected_url, + status_code=302,target_status_code=200) + + + # Create 2nd new team - should redirect to paid plans + form_data = { + 'name': faker.word(), + 'notes': faker.text(), + 'private': 'open', + 'viewing': 'allmembers' + } + + form = TeamForm(form_data) + if not form.is_valid(): + print form.errors + + self.assertTrue(form.is_valid()) + + expected_url = reverse('paidplans') + + response = self.c.post(url,form_data,follow=True) + self.assertRedirects(response, + expected_url=expected_url, + status_code=302,target_status_code=200) + + + ## Pro cannot create planned sessions or team planned sessions + def test_pro_create_session(self): + login = self.c.login(username=self.upro2.username, password=self.upro2password) + self.assertTrue(login) + + url = reverse('plannedsession_create_view') + + startdate = nu.date() + enddate = (nu+datetime.timedelta(days=3)).date() + preferreddate = startdate + + response = self.c.get(url,follow=True) + self.assertEqual(response.status_code,200) + + expected_url = reverse('paidplans') + + self.assertRedirects(response, + expected_url=expected_url, + status_code=302,target_status_code=200) + + + ## Pro cannot edit on behalf of athlete + def test_pro_edit_athlete_settings(self): + self.rbasic.team.add(self.teampro) + + login = self.c.login(username=self.upro2.username, password=self.upro2password) + self.assertTrue(login) + + url = reverse('rower_prefs_view',kwargs={'userid':self.ubasic.id}) + + response = self.c.get(url) + self.assertEqual(response.status_code,404) + + ## Pro cannot run analytics on behalf of athlete + @patch('rowers.dataprep.read_cols_df_sql', side_effect = mocked_read_df_cols_sql_multistats) + def test_pro_edit_athlete_analysis(self,mocked_df): + self.rbasic.team.add(self.teampro) + + login = self.c.login(username=self.upro2.username, password=self.upro2password) + self.assertTrue(login) + + + url = reverse('cumstats', + kwargs={ + 'theuser':self.ubasic.id, + } + ) + + response = self.c.get(url) + + self.assertEqual(response.status_code,404) + + ## Self Coach cannot upload on behalf of athlete + @patch('rowers.dataprep.create_engine') + @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) + def test_plan_edit_athlete_upload(self,mocked_sqlalchemy,mocked_getsmallrowdata_db): + self.rbasic.team.add(self.teamplan) + + login = self.c.login(username=self.uplan2.username, password=self.uplan2password) + self.assertTrue(login) + + url = reverse('team_workout_upload_view') + + response = self.c.get(url) + self.assertEqual(response.status_code,404) + + + ## Pro users can see team members' workout, but not edit + def test_coach_edit_athlete_workout(self,mocked_sqlalchemy,mocked_getsmallrowdata_db): + self.rbasic.team.add(self.proplan) + self.rpro2.team.add(self.proplan) + + login = self.c.login(username=self.upro2.username, password=self.upro2password) + self.assertTrue(login) + + url = reverse('workout_edit_view', + kwargs={'id':encoder.encode_hex(self.ubasic_workouts[0].id)} + ) + + response = self.c.get(url) + self.assertEqual(response.status_code,404) + + url = reverse('workout_view', + kwargs={'id':encoder.encode_hex(self.ubasic_workouts[0].id)} + ) + + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + + ## Self Coach users can see team members' workout, but not edit + def test_coach_edit_athlete_workout(self,mocked_sqlalchemy,mocked_getsmallrowdata_db): + self.rbasic.team.add(self.teamplan) + + login = self.c.login(username=self.uplan2.username, password=self.uplan2password) + self.assertTrue(login) + + url = reverse('workout_edit_view', + kwargs={'id':encoder.encode_hex(self.ubasic_workouts[0].id)} + ) + + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + url = reverse('workout_view', + kwargs={'id':encoder.encode_hex(self.ubasic_workouts[0].id)} + ) + + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + + ## Basic users can see team members' workout, but not edit + def test_basic_edit_athlete_workout(self,mocked_sqlalchemy,mocked_getsmallrowdata_db): + self.rbasic.team.add(self.teamplan) + self.rplan2.team.add(self.teamplan) + + login = self.c.login(username=self.ubasic.username, password=self.ubasicpassword) + self.assertTrue(login) + + url = reverse('workout_edit_view', + kwargs={'id':encoder.encode_hex(self.uplan2_workouts[0].id)} + ) + + response = self.c.get(url) + self.assertEqual(response.status_code,404) + + url = reverse('workout_view', + kwargs={'id':encoder.encode_hex(self.uplan2_workouts[0].id)} + ) + + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + ## Pro users (and higher) can join group led by other Pro (or higher) user + def test_team_member_request_pro_pro(self): + login = self.c.login(username=self.upro.username,password=self.upropassword) + self.assertTrue(login) + + url = reverse('team_requestmembership_view', + kwargs = { + 'teamid':self.teampro.id, + 'userid':self.upro.id + }) + + response = self.c.get(url,follow=True) + self.assertEqual(response.status_code,200) + + expected_url = reverse('team_view',kwargs={'id':self.teampro.id}) + + self.assertRedirects(response, + expected_url = expected_url, + status_code=302,target_status_code=200) + + ## Basic cannot join groups from Pro or Self Coach users (redirects to paid plans) + def test_team_member_request_basic_pro(self): + login = self.c.login(username=self.ubasic.username,password=self.ubasicpassword) + self.assertTrue(login) + + url = reverse('team_requestmembership_view', + kwargs = { + 'teamid':self.teampro.id, + 'userid':self.upro.id + }) + + response = self.c.get(url,follow=True) + self.assertEqual(response.status_code,200) + + expected_url = reverse('paidplans') + self.assertRedirects(response, + expected_url = expected_url, + status_code=302,target_status_code=200) -## Basic users can see team members' workout, but not edit diff --git a/rowers/tests/test_plans.py b/rowers/tests/test_plans.py index 51395c25..87f138c6 100644 --- a/rowers/tests/test_plans.py +++ b/rowers/tests/test_plans.py @@ -131,7 +131,7 @@ class TrainingPlanTest(TestCase): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) - url = '/rowers/sessions/create/' + url = reverse('plannedsession_create_view') startdate = nu.date() enddate = (nu+datetime.timedelta(days=3)).date() @@ -1051,6 +1051,8 @@ class PlannedSessionsView(TestCase): manager = self.u, ) + self.team.save() + self.r.team.add(self.team) self.r2.team.add(self.team) self.r.save() diff --git a/rowers/tests/testdata/testdata.csv.gz b/rowers/tests/testdata/testdata.csv.gz index b35d3210b753187692b309cae0d57a22a5a32737..3c6cc8df3385d74d5ae9969f9e7fe46cce2ec6f9 100644 GIT binary patch literal 11426 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≧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 bE%OG|dV_eM5SB%T-Ol@e0koFQfOh}@@q`w` diff --git a/rowers/views/teamviews.py b/rowers/views/teamviews.py index 06e8e894..a248c24b 100644 --- a/rowers/views/teamviews.py +++ b/rowers/views/teamviews.py @@ -261,7 +261,7 @@ def team_requestmembership_view(request,teamid,userid): else: messages.error(request,text) - url = reverse(team_view,kwargs={ + url = reverse('team_view',kwargs={ 'id':int(teamid), }) @@ -408,7 +408,7 @@ def team_create_view(request): viewing = cd['viewing'] res,message=teams.create_team(name,manager,private,notes, viewing) - url = reverse(rower_teams_view) + url = reverse('rower_teams_view') response = HttpResponseRedirect(url) return response diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 9f1c51cd..546405a3 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -4331,7 +4331,7 @@ def team_workout_upload_view(request,message="", url = reverse('team_workout_upload_view') response = HttpResponseRedirect(url) - w = Workout.objects.get(id=encoder.decode_hex(id)) + w = Workout.objects.get(id=id) r = getrower(request.user) if (make_plot):