def try_to_place(self, signup): """ Try to place participant (and partner) on the trip. Returns if successful. """ trip = signup.trip if trip.open_slots >= self.slots_needed: self.place_all_on_trip(signup) return True elif self.is_driver and not trip.open_slots and not self.paired: # A driver may displace somebody else # (but a couple with a driver cannot displace two people) if self.count_drivers_on_trip(trip) < self.min_drivers: self.logger.info( f"{trip} is full, but doesn't have {self.min_drivers} drivers" ) self.logger.info( f"Adding {signup} to '{trip}', as they're a driver") par_to_bump = self.runner.participant_to_bump(trip) add_to_waitlist(par_to_bump, prioritize=True) self.logger.info( f"Moved {par_to_bump} to the top of the waitlist") # TODO: Try to move the bumped participant to a preferred, open trip! signup.on_trip = True signup.save() return True return False
def _place_or_waitlist(self, future_signups, desired_signups): # JSON-serializable object we can use to analyze outputs. info = { 'participant_pk': self.participant.pk, 'paired_with_pk': self.paired_par and self.paired_par.pk, 'is_paired': bool(self.paired), 'affiliation': self.participant.affiliation, 'ranked_trips': [signup.trip_id for signup in future_signups], 'placed_on_choice': None, # One-indexed rank 'waitlisted': False, } if not future_signups: self.logger.info("%s did not choose any trips this week", self._par_text) return info if not desired_signups: # This can happen if the Participant paired, but their partner ranked no trips. self.logger.info("%s has no remaining desired trips", self._par_text) return info # Try to place participants on their first choice available trip skipped_to_avoid_driver_bump: List[Tuple[int, models.SignUp]] = [] for rank, signup in enumerate(future_signups, start=1): if signup not in desired_signups: self.logger.debug("Ignoring undesired signup %s", signup) continue trip_name = signup.trip.name if self._placement_would_jeopardize_driver_bump(signup): self.logger.debug("Placing on %r risks bump from a driver", trip_name) skipped_to_avoid_driver_bump.append((rank, signup)) continue if self._try_to_place(signup): self.logger.debug( f"Placed on trip #{rank} of {len(future_signups)}") return {**info, 'placed_on_choice': rank} self.logger.info("Can't place %s on %r", self._par_text, trip_name) # At this point, there were no trips that could take the participant or pair # It's possible that some were skipped because few spaces remained & a driver may bump. # If any potential placements remain, take those & risk a future bump. for rank, signup in skipped_to_avoid_driver_bump: if self._try_to_place(signup): self.logger.debug( f"Placed on trip #{rank} of {len(future_signups)}") return {**info, 'placed_on_choice': rank} self.logger.info(f"None of {self._par_text}'s desired trips are open.") favorite_trip = desired_signups.first().trip for participant in self.to_be_placed: favorite_signup = models.SignUp.objects.get( participant=participant, trip=favorite_trip) add_to_waitlist(favorite_signup) with_email = f"{self._par_text} ({participant.email})" self.logger.info( f"Waitlisted {with_email} on {favorite_trip.name}") return {**info, 'waitlisted': True}
def place_participant(self): if self.paired: logger.debug("{} is paired with {}".format(self.participant, self.paired_par)) if not self.runner.handled(self.paired_par): logger.debug("Will handle signups when {} comes".format(self.paired_par)) self.runner.mark_handled(self.participant) return if not self.future_signups: logger.debug("{} did not choose any trips this week".format(self.par_text)) self.runner.mark_handled(self.participant) return # Try to place participants on their first choice available trip for signup in self.future_signups: if self.try_to_place(signup): break else: logger.info("Can't place {} on {}".format(self.par_text, signup.trip)) else: # No trips are open logger.info("None of {}'s trips are open.".format(self.par_text)) favorite_trip = self.future_signups.first().trip for participant in self.to_be_placed: find_signup = Q(participant=participant, trip=favorite_trip) favorite_signup = models.SignUp.objects.get(find_signup) add_to_waitlist(favorite_signup) self.runner.mark_handled(self.participant)
def place_participant(self): if self.paired: self.logger.info( f"{self.participant} is paired with {self.paired_par}") if not self.runner.handled(self.paired_par): self.logger.info( f"Will handle signups when {self.paired_par} comes") self.runner.mark_handled(self.participant) return if not self.future_signups: self.logger.info( f"{self.par_text} did not choose any trips this week") self.runner.mark_handled(self.participant) return # Try to place participants on their first choice available trip for signup in self.future_signups: if self.try_to_place(signup): break else: self.logger.info( f"Can't place {self.par_text} on {signup.trip}") else: # No trips are open self.logger.info(f"None of {self.par_text}'s trips are open.") favorite_trip = self.future_signups.first().trip for participant in self.to_be_placed: find_signup = Q(participant=participant, trip=favorite_trip) favorite_signup = models.SignUp.objects.get(find_signup) add_to_waitlist(favorite_signup) with_email = f"{self.par_text} ({participant.email})" self.logger.info( f"Waitlisted {with_email} on {favorite_signup.trip.name}") self.runner.mark_handled(self.participant)
def place_participant(self): if self.paired: logger.info("{} is paired with {}".format(self.participant, self.paired_par)) if not self.runner.handled(self.paired_par): logger.info("Will handle signups when {} comes".format(self.paired_par)) self.runner.mark_handled(self.participant) return # Try to place all participants, otherwise add them to the waitlist signup = models.SignUp.objects.get(participant=self.participant, trip=self.trip) if not self.try_to_place(signup): for par in self.to_be_placed: add_to_waitlist(models.SignUp.objects.get(trip=self.trip, participant=par)) self.runner.mark_handled(self.participant)
def test_adds_message_on_request(self): request = RequestFactory().get('/') signup = factories.SignUpFactory.create(on_trip=False) with patch.object(messages, 'success') as success: wl_signup = signup_utils.add_to_waitlist(signup, request=request) success.assert_called_once_with(request, "Added to waitlist.") self.assertEqual(wl_signup.signup, signup) self.assertFalse(wl_signup.signup.on_trip)
def place_participant(self): if self.paired: self.logger.info( f"{self.participant} is paired with {self.paired_par}") if not self.runner.handled(self.paired_par): self.logger.info( f"Will handle signups when {self.paired_par} comes") self.runner.mark_handled(self.participant) return # Try to place all participants, otherwise add them to the waitlist signup = models.SignUp.objects.get(participant=self.participant, trip=self.trip) if not self.try_to_place(signup): for par in self.to_be_placed: self.logger.info(f"Adding {par.name} to the waitlist") add_to_waitlist( models.SignUp.objects.get(trip=self.trip, participant=par)) self.runner.mark_handled(self.participant)
def test_can_add_to_top_of_list(self): """We can add somebody to the waitlist, passing all others.""" trip = factories.TripFactory() # Build a waitlist with a mixture of ordered by time added & manually ordered spot_1 = factories.SignUpFactory.create(trip=trip, on_trip=False) spot_2 = factories.SignUpFactory.create(trip=trip, on_trip=False) spot_3 = factories.SignUpFactory.create(trip=trip, on_trip=False) spot_4 = factories.SignUpFactory.create(trip=trip, on_trip=False) factories.WaitListSignupFactory(signup=spot_3) factories.WaitListSignupFactory(signup=spot_4) factories.WaitListSignupFactory(signup=spot_2, manual_order=10) factories.WaitListSignupFactory(signup=spot_1, manual_order=11) self.assertEqual(list(trip.waitlist.signups), [spot_1, spot_2, spot_3, spot_4]) signup = factories.SignUpFactory.create(trip=trip, on_trip=True) signup_utils.add_to_waitlist(signup, prioritize=True, top_spot=True) self.assertEqual(list(trip.waitlist.signups), [signup, spot_1, spot_2, spot_3, spot_4])
def try_to_place(self, signup): """ Try to place participant (and partner) on the trip. Returns if successful. """ trip = signup.trip if trip.open_slots >= self.slots_needed: self.place_all_on_trip(signup) return True elif self.is_driver and not trip.open_slots and not self.paired: # A driver may displace somebody else # (but a couple with a driver cannot displace two people) if self.count_drivers_on_trip(trip) < self.min_drivers: logger.info("{} is full, but doesn't have {} drivers".format(trip, self.min_drivers)) logger.info("Adding {} to '{}', as they're a driver".format(signup, trip)) par_to_bump = self.runner.participant_to_bump(trip) add_to_waitlist(par_to_bump, prioritize=True) signup.on_trip = True signup.save() return True return False
def test_already_on_waitlist(self): """Leaders can't add a participant already on the waitlist!""" self.trip.algorithm = 'fcfs' self.trip.maximum_participants = 1 self.trip.save() # Trip is full now factories.SignUpFactory.create(trip=self.trip) signup = factories.SignUpFactory.create( trip=self.trip, participant__name='Jane McJaney') add_to_waitlist(signup) response = self.client.post( self.url, {'participant_id': signup.participant_id}, content_type='application/json', ) self.assertEqual(response.status_code, 409) self.assertEqual( response.json(), {'message': "Jane McJaney is already on the waitlist"})
def place_participant(self): # Indicate that this participant's number has come up! # (The issue of ranking is external to this module) self.runner.mark_seen(self.participant) if self.paired: self.logger.info( f"{self.participant} is paired with {self.paired_par}") if not self.runner.seen(self.paired_par): self.logger.info( f"Will handle signups when {self.paired_par} comes") return # Try to place all participants, otherwise add them to the waitlist signup = models.SignUp.objects.get(participant=self.participant, trip=self.trip) if not self._try_to_place(signup): for par in self.to_be_placed: self.logger.info(f"Adding {par.name} to the waitlist") add_to_waitlist( models.SignUp.objects.get(trip=self.trip, participant=par)) self.runner.mark_handled(self.participant) if self.paired_par: self.runner.mark_handled(self.paired_par)
def bump_participant(self, signup): add_to_waitlist(signup, prioritize=True) self.logger.info("Moved %s to the top of the waitlist", signup)
def test_can_add_to_bottom_of_priority(self): """Adding signups with priority puts them beneath other priorities, but above non.""" trip = factories.TripFactory() spot_3 = factories.SignUpFactory.create(trip=trip, on_trip=False) spot_1 = factories.SignUpFactory.create(trip=trip, on_trip=False) spot_2 = factories.SignUpFactory.create(trip=trip, on_trip=False) # Start with a simple waitlist with no manual ordering spot_5 = factories.SignUpFactory.create(trip=trip, on_trip=False) spot_4 = factories.SignUpFactory.create(trip=trip, on_trip=False) signup_utils.add_to_waitlist(spot_4) signup_utils.add_to_waitlist(spot_5) self.assertEqual(list(trip.waitlist.signups), [spot_4, spot_5]) # Add each new signup to priority, but not the top spot signup_utils.add_to_waitlist(spot_1, prioritize=True, top_spot=False) self.assertEqual(list(trip.waitlist.signups), [spot_1, spot_4, spot_5]) signup_utils.add_to_waitlist(spot_2, prioritize=True, top_spot=False) self.assertEqual(list(trip.waitlist.signups), [spot_1, spot_2, spot_4, spot_5]) signup_utils.add_to_waitlist(spot_3, prioritize=True, top_spot=False) self.assertEqual(list(trip.waitlist.signups), [spot_1, spot_2, spot_3, spot_4, spot_5]) # Adding to the top spot still works! signup = factories.SignUpFactory.create(trip=trip, on_trip=True) signup_utils.add_to_waitlist(signup, prioritize=True, top_spot=True) self.assertEqual( list(trip.waitlist.signups), [signup, spot_1, spot_2, spot_3, spot_4, spot_5], )
def test_already_has_waitlist_entry(self): wl_signup = factories.WaitListSignupFactory.create() self.assertIs(wl_signup, signup_utils.add_to_waitlist(wl_signup.signup))
def test_already_on_trip(self): """Participants already on the trip will be waitlisted.""" signup = factories.SignUpFactory.create(on_trip=True) wl_signup = signup_utils.add_to_waitlist(signup) self.assertEqual(wl_signup.signup, signup) self.assertFalse(wl_signup.signup.on_trip)