def test_update_ws_rating(self): perm_utils.make_chair(self.chair.user, enums.Activity.WINTER_SCHOOL) factories.LeaderRatingFactory.create( participant=self.participant, rating="B coC", creator=self.chair, ) resp = self.client.post( '/chair/leaders/', { 'participant': self.participant.pk, 'activity': enums.Activity.WINTER_SCHOOL.value, 'rating': "C coI", 'notes': "Upgraded!", }, ) # We redirect back to the page self.assertEqual(resp.status_code, 302) self.assertEqual(resp.url, '/chair/leaders/') # The participant has an upgrade now (& the old rating is inactive) rating = models.LeaderRating.objects.get( activity=enums.Activity.WINTER_SCHOOL.value, participant=self.participant, active=True, # old rating is now inactive! ) self.assertTrue(rating.active) self.assertEqual(rating.rating, "C coI") self.assertEqual(rating.creator, self.chair)
def test_trips_needing_itinerary(self): perm_utils.make_chair(self.user, enums.Activity.CLIMBING) sat_trip = self._make_climbing_trip(trip_date=date(2019, 7, 6)) sun_trip = self._make_climbing_trip(trip_date=date(2019, 7, 7)) sun_trip_info = self._make_climbing_trip( trip_date=date(2019, 7, 7), info=factories.TripInfoFactory.create()) dean = factories.ParticipantFactory.create(name="Dean Potter", email="*****@*****.**") sun_trip.leaders.add(dean) # Leaders with multiple trips aren't repeated lynn = factories.ParticipantFactory.create(name="Lynn Hill", email="*****@*****.**") sat_trip.leaders.add(lynn) sun_trip.leaders.add(lynn) # This trip is a week away; itineraries aren't open yet next_sat_trip = self._make_climbing_trip(trip_date=date(2019, 7, 13)) # Alex has no trips that need itinerary alex = factories.ParticipantFactory.create(name="Alex Puccio", email="*****@*****.**") sun_trip_info.leaders.add(alex) next_sat_trip.leaders.add(alex) context = self.client.get('/climbing/trips/').context # Leaders are sorted by name self.assertEqual( context['leader_emails_missing_itinerary'], '"Dean Potter" <*****@*****.**>, "Lynn Hill" <*****@*****.**>', )
def test_waiting_on_other_chairs(self): """We still default to recommendations while waiting for other chairs.""" other_chair_1 = factories.ParticipantFactory.create() other_chair_2 = factories.ParticipantFactory.create() perm_utils.make_chair(other_chair_1.user, enums.Activity.HIKING) perm_utils.make_chair(other_chair_2.user, enums.Activity.HIKING) application = factories.HikingLeaderApplicationFactory.create() for chair in (self.participant, other_chair_1): factories.LeaderRecommendationFactory.create( creator=chair, participant=application.participant, activity=enums.Activity.HIKING.value, rating="Full rating", ) url = f'/hiking/applications/{application.pk}/' _response, soup = self._get(url) self._expect_form_contents(soup, rating='Full rating', notes='', submit='Update recommendation') self.client.post(url, { 'rating': 'co', 'notes': '', 'is_recommendation': True }) rec = models.LeaderRecommendation.objects.get( participant=application.participant, creator=self.participant) self.assertEqual(rec.rating, 'co') self.assertFalse( models.LeaderRating.objects.filter( participant=application.participant))
def test_trips_with_itinerary_first(self): """Trips that have an itinerary are first in the approval flow.""" perm_utils.make_chair(self.user, enums.Activity.CLIMBING) sat_with_info = self._make_climbing_trip( trip_date=date(2019, 7, 6), info=factories.TripInfoFactory.create(), ) sat_without_info = self._make_climbing_trip( trip_date=date(2019, 7, 6), info=None ) sun_with_info = self._make_climbing_trip( trip_date=date(2019, 7, 7), info=factories.TripInfoFactory.create(), ) sun_without_info = self._make_climbing_trip( trip_date=date(2019, 7, 7), info=None ) context = self.client.get('/climbing/trips/').context self.assertEqual( context['trips_needing_approval'], [sat_with_info, sat_without_info, sun_with_info, sun_without_info], ) self.assertEqual(context['first_unapproved_trip'], sat_with_info)
def test_no_applications_climbing(self): perm_utils.make_chair(self.participant.user, enums.Activity.CLIMBING) _response, soup = self._get('/climbing/applications/') self.assertEqual( soup.find('p').text, 'Only archived climbing leader applications appear here.', )
def test_all_trips_approved(self): self._make_climbing_trip(chair_approved=True) self._make_climbing_trip(chair_approved=True) perm_utils.make_chair(self.user, enums.Activity.CLIMBING) response = self.client.get('/climbing/trips/') self.assertEqual(response.status_code, 200) self.assertEqual(response.context['trips_needing_approval'], []) self.assertIsNone(response.context['first_unapproved_trip'])
def test_no_active_leaders(self): perm_utils.make_chair(self.participant.user, enums.Activity.CLIMBING) response = self.client.get('/climbing/leaders/') self.assertEqual(response.context['activity_enum'], enums.Activity.CLIMBING) soup = BeautifulSoup(response.content, 'html.parser') self.assertTrue(soup.find('h2', text='Climbing Leaders')) self.assertTrue(soup.find('p', text='No active leaders!'))
def test_wsc_can_always_set(self): """ Regardless of time of year, the WSC can always set attendance. """ user = factories.UserFactory.create() perm_utils.make_chair(user, enums.Activity.WINTER_SCHOOL) par = factories.ParticipantFactory.create(user=user) other_par = factories.ParticipantFactory.create() self.assertTrue(self._can_set_attendance(par, other_par))
def test_wrong_chair(self): trip = factories.TripFactory.create( program=enums.Program.WINTER_SCHOOL.value, chair_approved=False) perm_utils.make_chair(self.user, enums.Activity.CLIMBING) response = self._approve(trip) self.assertEqual(response.status_code, 403) trip.refresh_from_db() self.assertFalse(trip.chair_approved)
def test_chairs_see_wimp_even_if_not_leaders(self): # WS trip exists today! factories.TripFactory.create(trip_date=date(2020, 1, 12), program=enums.Program.WINTER_SCHOOL.value) perm_utils.make_chair(self.user, enums.Activity.WINTER_SCHOOL) wimp_par = self._create_wimp() # There are upcoming WS trips, so the WS chairs should see the WIMP resp = self.client.get('/') self.assertEqual(resp.context['wimp'], wimp_par)
def setUp(self): self.participant = factories.ParticipantFactory.create() self.alice, self.bob, self.charlie = [ factories.ParticipantFactory.create() for _ in 'abc' ] for chair in [self.alice, self.bob, self.charlie]: perm_utils.make_chair(chair.user, enums.Activity.CLIMBING) self.application = factories.ClimbingLeaderApplicationFactory.create( participant=self.participant) super().setUp()
def setUp(self): self.participant = factories.ParticipantFactory.create() self.alice, self.bob, self.charlie = [ factories.ParticipantFactory.create() for _ in 'abc' ] for chair in [self.alice, self.bob, self.charlie]: perm_utils.make_chair(chair.user, models.BaseRating.CLIMBING) self.application = factories.ClimbingLeaderApplicationFactory.create( participant=self.participant ) super().setUp()
def test_chair(self): self._make_climbing_trip(chair_approved=True) unapproved = factories.TripFactory.create( program=enums.Program.SCHOOL_OF_ROCK.value, activity=enums.Activity.CLIMBING.value, chair_approved=False, ) perm_utils.make_chair(self.user, enums.Activity.CLIMBING) response = self.client.get('/climbing/trips/') self.assertEqual(response.status_code, 200) self.assertEqual(response.context['first_unapproved_trip'], unapproved)
def test_everything_needs_rating_with_one_chair(self): """ When there's only one chair, every application needs ratings! """ chair = factories.ParticipantFactory.create() perm_utils.make_chair(chair.user, enums.Activity.CLIMBING) # Filtering is done in Python, no need to save to db app1 = factories.ClimbingLeaderApplicationFactory.create() app2 = factories.ClimbingLeaderApplicationFactory.create() manager = ratings.ApplicationManager( chair=chair, activity=enums.Activity.CLIMBING.value) pending_apps = manager.pending_applications() self.assertEqual(manager.needs_rating(pending_apps), [app1, app2])
def test_view_old_approved_trip(self): trip = self._make_climbing_trip(chair_approved=True, trip_date=date(2018, 3, 4)) perm_utils.make_chair(self.user, enums.Activity.CLIMBING) response = self.client.get(f'/climbing/trips/{trip.pk}/') self.assertEqual(response.status_code, 200) soup = BeautifulSoup(response.content, 'html.parser') # There's no approval form, just an indicator that it's approved self.assertFalse(soup.find('form', action='.')) self.assertTrue(soup.find('button', string='Approved!'))
def test_totally_invalid_activity(self): perm_utils.make_chair(self.chair.user, enums.Activity.CLIMBING) resp = self.client.post( '/chair/leaders/', { 'participant': self.participant.pk, 'activity': "Curling", 'rating': "Curler", 'notes': "Drinks with the best of 'em", }, ) self.assertFalse(resp.context['form'].is_valid()) self.assertFalse(perm_utils.is_leader(self.participant.user))
def test_deactivate_nobady(self): rating = factories.LeaderRatingFactory.create( activity=enums.Activity.CLIMBING.value) perm_utils.make_chair(self.chair.user, enums.Activity.CLIMBING) resp = self.client.post('/climbing/leaders/deactivate/', {'deactivate': []}) self.assertEqual(resp.status_code, 302) self.assertEqual(resp.url, '/climbing/leaders/') # The rating remains active rating.refresh_from_db() self.assertTrue(rating.active)
def test_level_column(self): """The "level" column only appears for activity chairs.""" self._make_climbing_trip(chair_approved=True) perm_utils.make_chair(self.user, enums.Activity.CLIMBING) soup = BeautifulSoup(self.client.get('/climbing/trips/').content, 'html.parser') self.assertFalse(soup.find('th', string='Level')) perm_utils.make_chair(self.user, enums.Activity.WINTER_SCHOOL) factories.TripFactory.create(program=enums.Program.WINTER_SCHOOL.value) ws_soup = BeautifulSoup( self.client.get('/winter_school/trips/').content, 'html.parser' ) self.assertTrue(ws_soup.find('th', string='Level'))
def test_everything_needs_rating_with_one_chair(self): """ When there's only one chair, every application needs ratings! """ chair = factories.ParticipantFactory.create() perm_utils.make_chair(chair.user, models.BaseRating.CLIMBING) # Filtering is done in Python, no need to save to db app1 = factories.ClimbingLeaderApplicationFactory.create() app2 = factories.ClimbingLeaderApplicationFactory.create() manager = ratings.ApplicationManager( chair=chair, activity=models.BaseRating.CLIMBING ) pending_apps = manager.pending_applications() self.assertEqual(manager.needs_rating(pending_apps), [app1, app2])
def test_no_upcoming_trips(self): perm_utils.make_chair(self.user, enums.Activity.WINTER_SCHOOL) response = self.client.get('/trips/medical/') self.assertFalse(response.context['trips'].exists()) soup = BeautifulSoup(response.content, 'html.parser') header = soup.find('h1', string="WIMP Information Sheet") self.assertTrue(header) self.assertEqual( strip_whitespace(header.find_next('p').text), "This page contains all known medical information for trips taking place on or after Jan. 1, 2020.", ) self.assertTrue(soup.find('p', string="No upcoming trips."))
def test_no_navigation_between_old_trips(self): trip = self._make_climbing_trip(chair_approved=False, trip_date=date(2018, 3, 4)) self._make_climbing_trip(chair_approved=False, trip_date=date(2018, 3, 3)) self._make_climbing_trip(chair_approved=False, trip_date=date(2018, 3, 5)) perm_utils.make_chair(self.user, enums.Activity.CLIMBING) response = self.client.get(f'/climbing/trips/{trip.pk}/') # We don't prompt the chair to approve other old trips. self.assertIsNone(response.context['prev_trip']) self.assertIsNone(response.context['next_trip'])
def test_rating_totally_invalid_activity(self): perm_utils.make_chair(self.participant.user, enums.Activity.CLIMBING) other_par = factories.ParticipantFactory.create() resp = self.client.post( '/climbing/leaders/', # Valid activity URL { 'participant': other_par.pk, 'activity': "Curling", # Not a known activity 'rating': "Curler", 'notes': "Drinks with the best of 'em", }, ) self.assertFalse(resp.context['form'].is_valid()) self.assertFalse(perm_utils.is_leader(other_par.user))
def test_upcoming_trips(self): # Make each of these the same trip type, so we sort just by date four = self._make_climbing_trip( trip_date=date(2019, 3, 4), trip_type=enums.TripType.BOULDERING.value, ) two = self._make_climbing_trip( trip_date=date(2019, 3, 2), trip_type=enums.TripType.BOULDERING.value, ) three = self._make_climbing_trip( trip_date=date(2019, 3, 3), trip_type=enums.TripType.BOULDERING.value, ) one = self._make_climbing_trip( trip_date=date(2019, 3, 1), trip_type=enums.TripType.BOULDERING.value, ) perm_utils.make_chair(self.user, enums.Activity.CLIMBING) # "Next" and "previous" are in chronological order! response = self.client.get(f'/climbing/trips/{two.pk}/') self.assertEqual(response.context['prev_trip'], one) self.assertEqual(response.context['next_trip'], three) # Because we have a next trip, the button to approve it links to "& next" soup = BeautifulSoup(response.content, 'html.parser') form = soup.find('form', action='.') self.assertTrue(form.find('button', string='Approve & Next')) # Also, next and previous only navigate between unapproved trips three.chair_approved = True three.save() response = self.client.get(f'/climbing/trips/{two.pk}/') self.assertEqual(response.context['prev_trip'], one) self.assertEqual(response.context['next_trip'], four) # Finally, approving a trip brings us straight to the page for the next. approve_resp = self.client.post(f'/climbing/trips/{two.pk}/') self.assertEqual(approve_resp.status_code, 302) self.assertEqual(approve_resp.url, f'/climbing/trips/{four.pk}/') # The last trip in the series has no "next" button resp = self.client.get(approve_resp.url) self.assertEqual(resp.context['prev_trip'], one) self.assertIsNone(resp.context['next_trip'])
def test_superuser(self): """ An optional flag indicates if superusers should be considered chairs. """ admin = factories.UserFactory.create(is_superuser=True) # True is useful for granting admins access to activity functions self.assertEqual( self._chair_activities(admin, allow_superusers=True), ['Biking', 'Boating', 'Cabin', 'Climbing', 'Hiking', 'Winter School'], ) # False is useful for rendering actual chairships held self.assertEqual(self._chair_activities(admin, allow_superusers=False), []) # We can still report chairships held perm_utils.make_chair(admin, enums.Activity.WINTER_SCHOOL) self.assertEqual( self._chair_activities(admin, allow_superusers=False), ['Winter School'] )
def test_chair_for_wrong_activity(self): perm_utils.make_chair(self.chair.user, enums.Activity.CLIMBING) # Not the biking chair, so can't make biking leaders! self.assertFalse( perm_utils.is_chair(self.chair.user, enums.Activity.BIKING)) resp = self.client.post( '/chair/leaders/', { 'participant': self.participant.pk, 'activity': enums.Activity.BIKING.value, 'rating': "Leader", 'notes': "", }, ) self.assertFalse(resp.context['form'].is_valid()) self.assertFalse(perm_utils.is_leader(self.participant.user))
def test_past_unapproved_trips_ignored(self): """We only prompt chairs to look at trips which are upcoming & unapproved.""" # Unapproved, but it's in the past! self._make_climbing_trip(trip_date=date(2019, 7, 4)) perm_utils.make_chair(self.user, enums.Activity.CLIMBING) response = self.client.get('/climbing/trips/') self.assertEqual(response.status_code, 200) self.assertIsNone(response.context['first_unapproved_trip']) # Make some future trips now - these trips will be ranked by date/itinerary! fri = self._make_climbing_trip(trip_date=date(2019, 7, 5)) sun = self._make_climbing_trip(trip_date=date(2019, 7, 7)) sat = self._make_climbing_trip(trip_date=date(2019, 7, 6)) context = self.client.get('/climbing/trips/').context self.assertEqual(context['trips_needing_approval'], [fri, sat, sun]) self.assertEqual(context['first_unapproved_trip'], fri)
def test_chair_for_wrong_activity(self): perm_utils.make_chair(self.participant.user, enums.Activity.CLIMBING) # Not the biking chair, so can't make biking leaders! other_par = factories.ParticipantFactory.create() self.assertFalse( perm_utils.is_chair(self.participant.user, enums.Activity.BIKING)) resp = self.client.post( '/biking/leaders/', { 'participant': other_par.pk, 'activity': enums.Activity.BIKING.value, 'rating': "Leader", 'notes': "", }, ) self.assertEqual(resp.status_code, 403) self.assertFalse(perm_utils.is_leader(other_par.user))
def test_make_chair(self): """ Users can be promoted to being activity chairs. """ # To begin with, our user is not a chair (nobody is, for that matter) climbing = models.BaseRating.CLIMBING user = UserFactory.create() self.assertFalse(perm_utils.is_chair(user, climbing)) self.assertEqual(perm_utils.num_chairs(climbing), 0) # We promote them to be a climbing chair perm_utils.make_chair(user, climbing) self.assertTrue(perm_utils.is_chair(user, climbing)) self.assertEqual(perm_utils.num_chairs(climbing), 1) # chair_or_admin works now too, and the user is definitely not a superuser self.assertTrue(perm_utils.chair_or_admin(user, climbing)) self.assertFalse(user.is_superuser) # Sanity check: The user wasn't accidentally made the chair of other activities self.assertFalse(perm_utils.is_chair(user, models.BaseRating.BOATING))
def test_make_chair(self): """ Users can be promoted to being activity chairs. """ # To begin with, our user is not a chair (nobody is, for that matter) user = UserFactory.create() self.assertFalse(perm_utils.is_chair(user, enums.Activity.CLIMBING)) self.assertEqual(perm_utils.num_chairs(enums.Activity.CLIMBING), 0) # We promote them to be a climbing chair perm_utils.make_chair(user, enums.Activity.CLIMBING) self.assertTrue(perm_utils.is_chair(user, enums.Activity.CLIMBING)) self.assertEqual(perm_utils.num_chairs(enums.Activity.CLIMBING), 1) # chair_or_admin works now too, and the user is definitely not a superuser self.assertTrue( perm_utils.chair_or_admin(user, enums.Activity.CLIMBING)) self.assertFalse(user.is_superuser) # Sanity check: The user wasn't accidentally made the chair of other activities self.assertFalse(perm_utils.is_chair(user, enums.Activity.BOATING))
def test_deactivate_multiple_activities(self): hiking = factories.LeaderRatingFactory.create( activity=enums.Activity.HIKING.value) climbing = factories.LeaderRatingFactory.create( activity=enums.Activity.CLIMBING.value) perm_utils.make_chair(self.chair.user, enums.Activity.CLIMBING) perm_utils.make_chair(self.chair.user, enums.Activity.HIKING) # Cannot deactivate multiple ratings of different activity types, even if chair! resp = self.client.post('/climbing/leaders/deactivate/', {'deactivate': [climbing.pk, hiking.pk]}) self.assertEqual(resp.status_code, 403) # Both ratings remain active hiking.refresh_from_db() climbing.refresh_from_db() self.assertTrue(hiking.active) self.assertTrue(climbing.active)
def test_old_applications_climbing(self): with freeze_time("2018-08-13 18:45 EDT"): self._create_application_and_approve() with freeze_time("2019-06-22 12:23 EDT"): self._create_application_and_approve() perm_utils.make_chair(self.participant.user, enums.Activity.CLIMBING) _response, soup = self._get('/climbing/applications/') self.assertEqual( soup.find('p').text, 'Only archived climbing leader applications appear here.', ) self.assertEqual( [h2.text for h2 in soup.find_all('h2')], [ 'Leader Applications', 'Past Applications - 2019', 'Past Applications - 2018', ], )
def test_successfully_deactivate(self): remove1, remove2, keep = [ factories.LeaderRatingFactory.create( activity=enums.Activity.HIKING.value) for _i in range(3) ] perm_utils.make_chair(self.chair.user, enums.Activity.HIKING) resp = self.client.post('/hiking/leaders/deactivate/', {'deactivate': [remove1.pk, remove2.pk]}) self.assertEqual(resp.status_code, 302) self.assertEqual(resp.url, '/hiking/leaders/') remove1.refresh_from_db() remove2.refresh_from_db() keep.refresh_from_db() self.assertTrue(keep.active) self.assertFalse(remove1.active) self.assertFalse(remove2.active)
def test_all_chairs_unanimous(self): """When all chairs gave the same recommendation, we pre-fill.""" other_chair_1 = factories.ParticipantFactory.create(name='Pooh Bear') other_chair_2 = factories.ParticipantFactory.create() perm_utils.make_chair(other_chair_1.user, enums.Activity.HIKING) perm_utils.make_chair(other_chair_2.user, enums.Activity.HIKING) application = factories.HikingLeaderApplicationFactory.create() for chair in (self.participant, other_chair_1, other_chair_2): factories.LeaderRecommendationFactory.create( creator=chair, participant=application.participant, activity=enums.Activity.HIKING.value, rating="Full rating", # Consensus doesn't care about this. notes=f'Confident - sincerely, {chair.name} (#{chair.pk})', ) _response, soup = self._get(f'/hiking/applications/{application.pk}/') # We prompt them to make a rating, with rating pre-filled! self._expect_form_contents(soup, rating='Full rating', notes='', submit='Create rating') # Note that should an admin create an extra rating, we no longer pre-fill # This shouldn't really happen (admins don't make recs), but it's handled. factories.LeaderRecommendationFactory.create( creator=factories.ParticipantFactory.create( user=factories.UserFactory.create(is_superuser=True)), participant=application.participant, activity=enums.Activity.HIKING.value, rating="I don't think we should", ) _response, soup2 = self._get(f'/hiking/applications/{application.pk}/') self._expect_form_contents(soup2, rating='', notes='', submit='Create rating')
def test_cannot_make_chair_for_open_activity(self): """ You can't make somebody the chair of an open activity. """ valid_participant = ParticipantFactory.create() open_activity = models.BaseRating.OFFICIAL_EVENT with self.assertRaises(ValueError): perm_utils.make_chair(valid_participant.user, open_activity)