def mark_election_as_complete(self, election_id: str, complete_reason: ElectionStatus): if (complete_reason != ElectionStatus.MANUALLY_COMPLETE or complete_reason != ElectionStatus.MARKED_COMPLETE): raise ValueError( f"Invalid complete reason: {complete_reason.value}") db_session = DbSession() # could lead to race conditions, but exact completed_at not support important partial_election = Election.get_election_by_id( db_session, election_id, query_modifier=lambda x: x.options( load_only(Election.id, Election.election_status)), ) if partial_election == complete_reason: return if partial_election.election_status != ElectionStatus.VOTING: raise InvalidElectionStateException( message= f"Cannot move an election to complete with status: {partial_election.election_status.value}" ) partial_election.election_status = complete_reason partial_election.election_completed_at = datetime.now() db_session.commit() self.__push_election_status_change_to_update_stream( election_id, complete_reason)
def move_election_to_voting(self, election_id: str): db_session = DbSession() partial_election = Election.get_election_by_id( db_session, election_id, query_modifier=lambda x: x.options( load_only(Election.id, Election.election_status)), ) if partial_election is None: raise HttpException( message=f"Election with election id: {election_id} not found", status_code=404, ) # TODO: Refactor election status "is already status" check (not changing) for both move_election_to_voting and # mark_as_complete into one function that fetches the partial electoin if partial_election.election_status == ElectionStatus.VOTING: return if partial_election.election_status != ElectionStatus.IN_CREATION: raise InvalidElectionStateException( message= f"Cannot move an election to status {ElectionStatus.VOTING.name} when status is {partial_election.election_status.value}" ) # TODO: validate change made by using WHERE partial_election.election_status = ElectionStatus.VOTING db_session.commit() self.__push_election_status_change_to_update_stream( election_id, ElectionStatus.VOTING)
def consume(self, election_id: str): db_session = DbSession() Election.delete_election_results_for_election(db_session, election_id) db_session.flush() db_session.commit() rankings_by_voter: Dict[ str, List[str] ] = Election.get_rankings_by_user_for_election(db_session, election_id) self.__run_election_round( db_session=db_session, election_id=election_id, rankings=list(rankings_by_voter.values()), ) db_session.commit() ElectionUpdateStream.for_election(election_id).publish_message( ServerSentEvent( type=ElectionUpdateEventType.RESULTS_UPDATED, payload=None ) )
def get_election_update_stream(self, election_id: str) -> ElectionUpdateStream: db_session = DbSession() if (Election.get_election_by_id( db_session, election_id, lambda x: x.options(load_only(Election.id))) is None): raise HttpException( message=f"Election with id: {election_id} not found", status_code=404) return ElectionUpdateStream.for_election(election_id)
def vote(self, user_id: str, election_id: str, votes: [str]): db_session = DbSession() partial_election = Election.get_election_by_id( db_session, election_id, query_modifier=lambda x: x.options( load_only(Election.id, Election.election_status)), ) if partial_election is None: raise HttpException( message=f"Election with id: {election_id} not found", status_code=404) if partial_election.election_status != ElectionStatus.VOTING: raise InvalidElectionStateException( f"Cannot vote unless election is in voting status. Current status: {partial_election.election_status.name}" ) candidate_ids = set([ candidate.business_id for candidate in partial_election.candidates ]) for business_id in votes: if business_id not in candidate_ids: raise HttpException( message=f"Unknown or duplicate candidate id {business_id}", status_code=404, ) candidate_ids.remove(business_id) if len(candidate_ids) > 0: raise HttpException( message= f"Missing votes for candidates: {', '.join(candidate_ids)}", status_code=400, ) rankings = [ Ranking( user_id=user_id, election_id=election_id, business_id=business_id, rank=index, ) for index, business_id in enumerate(votes) ] Ranking.delete_users_rankings_for_election(db_session, user_id=user_id, election_id=election_id) db_session.add_all(rankings) db_session.commit() self.__queue_election_for_result_update(election=partial_election)
def get_candidates(self, active_id: str) -> [Candidate]: db_session = DbSession() partial_election = Election.get_active_election_by_active_id( db_session, active_id, query_modifier=lambda x: x.options(load_only(Election.id)), ) if partial_election is None: raise HttpException( message=f"Election with active id: {active_id} not found", status_code=404, ) return partial_election.candidates
def create_election(self, user: SerializableBasicUser) -> Election: db_session = DbSession() election_id = str(uuid4()) while True: try: election = Election( id=election_id, active_id=self.__generate_election_active_id(), election_creator_id=user.id, ) db_session.add(election) db_session.commit() return election except BaseException as error: raise error
def add_candidate(self, active_id: str, business_id: str, user_id: str) -> bool: db_session = DbSession() partial_election = Election.get_active_election_by_active_id( db_session=db_session, active_id=active_id, query_modifier=lambda x: x.options( load_only(Election.id, Election.election_status)), ) if partial_election is None: raise HttpException( message=f"Election with active id: {active_id} not found", status_code=404, ) if partial_election.election_status != ElectionStatus.IN_CREATION: raise InvalidElectionStateException( message= f"Cannot add a candidate to an election with status: {partial_election.election_status.value}" ) # Verify business exists? Cache business # Calculate distance candidate = Candidate( election_id=partial_election.id, business_id=business_id, distance=0, nominator_id=user_id, ) db_session.add(candidate) try: db_session.commit() except IntegrityError as error: if is_unique_key_error(error): return False raise error # TODO: Accelerate fetching process here business: DisplayableBusiness = self.__business_manager.get_displayable_business( business_id=business_id) # TODO: Maybe just pass the name stored within the cookie instead of refetching from the database nickname = self.__user_manager.get_nickname_by_user_id(user_id) ElectionUpdateStream.for_election(partial_election.id).publish_message( CandidateAddedEvent(business_id=business_id, name=business.name, nominator_nickname=nickname if nickname is not None else "Unknown")) return True
def get_displayable_election_by_id( self, id: str) -> Optional[DisplayableElection]: db_session = DbSession() election = Election.get_election_by_id(db_session=db_session, id=id) if election is None: return None return DisplayableElection( id=election.id, active_id=election.active_id, election_status=election.election_status, candidates=[ DisplayableCandidate( business_id=candidate.business_id, name=self.__business_manager.get_displayable_business( candidate.business_id).name, nominator_nickname=candidate.nominator.nickname) for candidate in election.candidates ])
def get_active_election_by_active_id(self, active_id: str) -> Election: db_session = DbSession() return Election.get_active_election_by_active_id(db_session, active_id)