def get(self, id, pair_no):
        ''' Returns the opaque pair ID for pair with pair nubmer pair_no in this
        Tournament.

        Args: 
          id: Tournament ID for this tournament.
          pair_no: The pair number whose ID has to be looked up.
    '''
        if not is_int(pair_no):
            SetErrorStatus(
                self.response, 404, "Invalid Pair Number",
                "Pair number must be an integer, was {}".format(pair_no))
            return

        tourney = GetTourneyWithIdAndMaybeReturnStatus(self.response, id)
        if not tourney:
            return

        if not CheckUserOwnsTournamentAndMaybeReturnStatus(
                self.response, users.get_current_user(), tourney):
            return
        player_pairs = PlayerPair._query(
            ndb.GenericProperty('pair_no') == int(pair_no),
            ancestor=tourney.key).fetch(1, projection=[PlayerPair.id])
        if not player_pairs:
            SetErrorStatus(
                self.response, 404, "Invalid Id",
                "Pair pair number {} does not exist in this " +
                "tournament".format(pair_no))
            return
        self.response.headers['Content-Type'] = 'application/json'
        self.response.set_status(200)
        self.response.out.write(
            json.dumps({'pair_id': player_pairs[0].id}, indent=2))
    def _ValidateHandResultMaybeSetStatus(self, board_no, ns_pair, ew_pair,
                                          ns_score, ew_score, calls):
        ''' Validates the proposed hand results as a real Tichu score.

    Args:
      board_no: Integer. Hand number.
      ns_pair: Integer. Pair number of team playing North/South.
      ew_pair: Integer. Pair number of team playing East/West.
      ns_score: Integer or String. Score of the North/South team. If string,
         must be one of AVG, AVG+, AVG++, AVG-, AVG-- allowing for any
         capitalization.
      ew_score: Integer or String. Score of the East/West team. If string,
         must be one of AVG, AVG+, AVG++, AVG-, AVG-- allowing for any
         capitalization.
      calls: Dictionary. Holds calls from each team. Can be None.
    Returns:
      True iff the proposed score is a valid Tichu score.s
    '''
        error = "Invalid Score"
        try:
            HandResult(board_no, ns_pair, ew_pair, ns_score, ew_score,
                       Calls.FromDict(calls))
        except InvalidScoreError as err:
            SetErrorStatus(self.response, 400, error,
                           "These scores are not a valid Tichu score")
            return False
        except InvalidCallError as err:
            SetErrorStatus(self.response, 400, error,
                           "{} are not valid Tichu calls".format(calls))
            return False
        return True
Example #3
0
 def _CheckValidTournamentInfoAndMaybeSetStatus(self,
                                                name,
                                                no_pairs,
                                                no_boards,
                                                players=None):
     ''' Checks if the input is valid and sane. 
     If not sets the response with the appropriate status and error message.
     Assumes no_pairs and no_boards are integers.
 '''
     if name == "":
         SetErrorStatus(self.response, 400, "Invalid input",
                        "Tournament name must be nonempty")
         return False
     elif no_pairs < 2:
         SetErrorStatus(
             self.response, 400, "Invalid input",
             "Number of pairs must be > 1, was {}".format(no_pairs))
         return False
     elif no_boards < 1:
         SetErrorStatus(
             self.response, 400, "Invalid input",
             "Number of boards must be > 0, was {}".format(no_boards))
         return False
     elif players:
         for player in players:
             if player['pair_no'] < 1 or player['pair_no'] > no_pairs:
                 SetErrorStatus(
                     self.response, 400, "Invalid Input",
                     "Player pair must be between 1 and no_pairs, was {}.".
                     format(player['pair_no']))
                 return False
     return BuildMovementAndMaybeSetStatus(self.response, no_pairs,
                                           no_boards) is not None
Example #4
0
 def _ParseRequestInfoAndMaybeSetStatus(self):
   ''' Parses the body of the request. Checks if the body is valid JSON with
       all the proper fields set. Checks if the number of boards and number of 
       pairs are valid numbers. If not sets the response with the appropriate
       status and error message.
   '''
   try:
     request_dict = json.loads(self.request.body)
   except ValueError:
     SetErrorStatus(self.response, 500, "Invalid Input",
                    "Unable to parse request body as JSON object")
     return None
   if not isinstance(request_dict.get('no_pairs'), int):
     SetErrorStatus(self.response, 400, "Invalid Input",
                    "no_pairs must be an integer")
     return None
   elif not isinstance(request_dict.get('no_boards'), int):
     SetErrorStatus(self.response, 400, "Invalid Input",
                    "no_boards must be an integer")
     return None
   elif request_dict.get('players'):
     player_list = request_dict.get('players')
     for player in player_list:
       if not player['pair_no']:
         SetErrorStatus(self.response, 400, "Invalid Input",
                        "Player must have corresponding pair number if present.")
         return None
       if not isinstance(player['pair_no'], int):
         SetErrorStatus(self.response, 400, "Invalid Input",
                        "Player pair number must be an integer, was {}".format(
                            player['pair_no']))
         return None
   return request_dict
Example #5
0
  def get(self, id, pair_no):
    ''' Fetch movement for tournament with id and team pair_no. 

    Args:
      id: String. Tournament id. 
      pair_no: Integer. Pair number for the team whose movement we're getting.

    See api for request and response documentation.
    '''
    tourney = GetTourneyWithIdAndMaybeReturnStatus(self.response, id)
    if not tourney:
      return

    if not self._CheckValidPairMaybeSetStatus(tourney, pair_no):
      return

    player_pair = PlayerPair.GetByPairNo(tourney, int(pair_no))
    if not player_pair:
      SetErrorStatus(self.response, 404, "Invalid Request",
                     "Player pair {} not in tournament".format(pair_no))
      return

    if not self._CheckUserAllowedToSeeMovementMaybeSetStatus(
        tourney, player_pair):
      return

    no_hands_per_round, no_rounds = Movement.NumBoardsPerRoundFromTotal(
        tourney.no_pairs, tourney.no_boards)
    try:
      movement = Movement.CreateMovement(
          tourney.no_pairs, no_hands_per_round, no_rounds).GetMovement(
              int(pair_no))
    except ValueError:
      SetErrorStatus(self.response, 500, "Corrupted Data",
                     "No valid movement for this tourney's config")
      return

    movement_list = []
    for round in movement:
      movement_list.append(
          self._GetJsonRoundWithScore(round, tourney, int(pair_no)))

    combined_dict = {
      'name' : tourney.name,
      'players' : player_pair.player_list(),
      'movement': movement_list
    }

    self.response.headers['Content-Type'] = 'application/json'
    self.response.set_status(200)
    self.response.out.write(json.dumps(combined_dict, indent=2))
Example #6
0
    def _CheckUserHasAccessMaybeSetStatus(self, tourney, hand_no):
        '''Tests if the current user has access to the results of this hand.

    Uses the pair id code, if any, set in the request header to see if the user
    is in one of the teams that is set to play this hand in this movement. 
    Directors always have access.

    Args:
      tourney: Tournament. Current tournament.
      hand_no: Integer. Number of the hand.

    Returns:
      A (Boolean, Integer, [(Integer, Integer)]) tuple. First member is True iff
      the user should have access to the hand provided they played it (or is a
      director). The second member is the pair number of the user (0 for director).
      Only set if first member is True. The last member is the list of 
      (nw_pair, ew_pair) tuples that correspond to the pairs that play hand_no in
      this movement.
    '''
        user = users.get_current_user()
        movement = tourney.GetMovement()
        all_matchups = movement.GetListOfPlayersForHand(hand_no)
        if user and tourney.owner_id == user.user_id():
            return (True, 0, all_matchups)
        if tourney.IsUnlocked():
            SetErrorStatus(
                self.response, 403, "Forbidden User",
                "The tournament is not set up to show hand " +
                "results to players.")
            return (False, None, None)

        error = "Forbidden User"
        pair_id = GetPairIdFromRequest(self.request)
        player_pairs = PlayerPair._query(
            ndb.GenericProperty('id') == pair_id).fetch(
                1, projection=[PlayerPair.pair_no])
        if not pair_id or not player_pairs:
            SetErrorStatus(
                self.response, 403, error,
                "User does not own tournament and is not authenticated " +
                "with a pair code to see the results of this hand.")
            return (False, None, None)
        pair_no = player_pairs[0].pair_no
        if not self._PlayerInMatchupList(pair_no, all_matchups):
            SetErrorStatus(self.response, 403, error,
                           "User does not play this hand.")
            return (False, None, None)
        return (True, pair_no, all_matchups)
    def get(self, id):
        ''' Returns tournament and pair number information this pair_id.

        Args: 
          id: Tournament ID for this tournament
        Returns: 
          a list of all unique ids in order. Length will necessarily equal
          to the number of pairs in this tournament.
    '''
        tourney = GetTourneyWithIdAndMaybeReturnStatus(self.response, id)
        if not tourney:
            return

        if not CheckUserOwnsTournamentAndMaybeReturnStatus(
                self.response, users.get_current_user(), tourney):
            return

        player_pairs = PlayerPair._query(ancestor=tourney.key).fetch(
            projection=[PlayerPair.id, PlayerPair.pair_no])
        player_pairs.sort(key=lambda p: p.pair_no)
        if not player_pairs:
            SetErrorStatus(
                self.response, 500, "Corrupted Data",
                "Could not find any players for this tournament. " +
                "Consider resetting tournament info.")

        self.response.headers['Content-Type'] = 'application/json'
        self.response.set_status(200)
        self.response.out.write(
            json.dumps({"pair_ids": [p.id for p in player_pairs]}, indent=2))
Example #8
0
    def put(self, id):
        user = users.get_current_user()
        tourney = GetTourneyWithIdAndMaybeReturnStatus(self.response, id)
        if not tourney:
            return

        if not CheckUserOwnsTournamentAndMaybeReturnStatus(
                self.response, user, tourney):
            return

        request_dict = self._ParseRequestInfoAndMaybeSetStatus()
        if not request_dict:
            return

        name = request_dict['name']
        no_pairs = request_dict['no_pairs']
        no_boards = request_dict['no_boards']
        player_list = request_dict.get('players')
        if not self._CheckValidTournamentInfoAndMaybeSetStatus(
                name, no_pairs, no_boards, player_list):
            return
        if ((tourney.no_pairs != no_pairs or tourney.no_boards != no_boards)
                and len(tourney.GetScoredHandList()) != 0):
            SetErrorStatus(self.response, 400, "Invalid Request",
                           "Tournament already has registered hands")
            return
        self.response.set_status(204)
        tourney.no_pairs = no_pairs
        tourney.no_boards = no_boards
        tourney.name = name
        tourney_key = tourney.put()
        tourney.PutPlayers(player_list, no_pairs)
  def delete(self, id, board_no, ns_pair, ew_pair):
    ''' Delete hand with these hand number and opponents from this tournament.

    Args:
      id: String. Tournament id. 
      board_no: Integer. Hand number.
      ns_pair: Integer. Pair number of team playing North/South.
      ew_pair: Integer. Pair number of team playing East/West.
    
    See api for request and response documentation.
    '''
    tourney = GetTourneyWithIdAndMaybeReturnStatus(self.response, id)
    if not tourney:
      return

    if not CheckUserOwnsTournamentAndMaybeReturnStatus(self.response,
        users.get_current_user(), tourney):
      return

    if not CheckValidHandPlayersCombinationAndMaybeSetStatus(
        self.response, tourney, board_no, ns_pair, ew_pair):
      return

    hand_score = HandScore.GetByHandParams(tourney, board_no, ns_pair, ew_pair)
    if not hand_score:
      SetErrorStatus(self.response, 404, "Invalid Request",
                     "Hand {} between pairs {} and {} is not set".format(
                         board_no, ns_pair, ew_pair))
      return
    hand_score.Delete()
    self.response.set_status(204) 
Example #10
0
 def _CheckUserAllowedToSeeMovementMaybeSetStatus(self, tourney, player_pair):
   error  = "Forbidden User"
   user = users.get_current_user()
   if user and tourney.owner_id == user.user_id():
     return True
   pair_id = GetPairIdFromRequest(self.request)
   if not pair_id:
     SetErrorStatus(self.response, 403, error,
                    "User does not own tournament and is not authenticated " +
                    "with a pair code to see this movement")
     return False
   if pair_id != player_pair.id:
     SetErrorStatus(self.response, 403, error,
                    "User does not own tournament and is authenticated with " +
                    "the wrong code for pair {}".format(player_pair.pair_no))
     return False
   return True
Example #11
0
    def _ParsePutRequestInfoAndMaybeSetStatus(self):
        ''' Parse the body of the request.

    Checks if the body is valid JSON with all the proper fields set. If not,
    sets the response with the appropriate status and error message.

    Returns:
      a dict with all the request parameters if the required parameters are set.
      None if any of the required fields are unset or have the wrong type.
    '''
        try:
            request_dict = json.loads(self.request.body)
        except ValueError:
            SetErrorStatus(self.response, 500, "Invalid Input",
                           "Unable to parse request body as JSON object")
            return None
        ns_score = request_dict.get('ns_score')
        ew_score = request_dict.get('ew_score')
        if not isinstance(ns_score, int):
            if not isinstance(ns_score, basestring):
                SetErrorStatus(
                    self.response, 400, "Invalid Input",
                    "ns_score must be int or string, was " +
                    type(ns_score).__name__)
                return None
            if ns_score.strip().upper() not in AVG_VALUES:
                SetErrorStatus(
                    self.response, 400, "Invalid Input",
                    "ns_score must be an integer or avg, was " + str(ns_score))
                return None
            if isinstance(ew_score, int):
                SetErrorStatus(
                    self.response, 400, "Invalid Input",
                    "Cannot have one team with an avg score and another "
                    "with a real Tichu value ")
                return None
        if not isinstance(ew_score, int):
            if not isinstance(ew_score, basestring):
                SetErrorStatus(
                    self.response, 400, "Invalid Input",
                    "ew_score must be int or string, was " +
                    type(ew_score).__name__)
                return None
            if ew_score.strip().upper() not in AVG_VALUES:
                SetErrorStatus(
                    self.response, 400, "Invalid Input",
                    "ew_score must be an integer or avg, was " + str(ew_score))
                return None
            if isinstance(ns_score, int):
                SetErrorStatus(
                    self.response, 400, "Invalid Input",
                    "Cannot have one team with an avg score and another "
                    "with a real Tichu value")
                return None
        return request_dict
Example #12
0
  def _CheckValidPairMaybeSetStatus(self, tourney, pair_no):
    ''' Test if the provided pair number is valid for tourney. 

    Args:
      tourney: Tournament. Tournament the pair number is being validated for.
      pair_no: Integer. Pair number for the team we are validating.
    '''
    error = "Invalid Input"
    if (not is_int(pair_no)) or int(pair_no) < 1 or int(pair_no) > tourney.no_pairs:
      SetErrorStatus(self.response, 404, error,
                     "Pair number {} is invalid".format(pair_no))
      return False
    return True
Example #13
0
    def _CheckUserHasAccessMaybeSetStatus(self, tourney, ns_pair, ew_pair):
        ''' Tests if the current user has access to a hand with given players.

    Uses the pair id code, if any, set in the request header to see if the user
    is in one of the teams playing the hand. Directors always have access.

    Args:
      tourney. Tournament. Current tournament.
      ns_pair: Integer. Pair number of team playing North/South.
      ew_pair: Integer. Pair number of team playing East/West.

    Returns:
      A (Boolean, Integer) pair. First member is True iff the user has access
      to the hand between ns_pair and ew_pair. Second member is the pair number
      of the user. Only set if first member is True.
    '''
        user = users.get_current_user()
        error = "Forbidden User"
        if user and tourney.owner_id == user.user_id():
            return (True, 0)
        pair_id = GetPairIdFromRequest(self.request)
        if not pair_id:
            SetErrorStatus(
                self.response, 403, error,
                "User does not own tournament and is not authenticated " +
                "with a pair code to overwrite this hand.")
            return (False, None)
        player_pairs = PlayerPair.query(ndb.OR(
            PlayerPair.pair_no == int(ns_pair),
            PlayerPair.pair_no == int(ew_pair)),
                                        ancestor=tourney.key).fetch()
        if (not player_pairs) or (pair_id not in [p.id for p in player_pairs]):
            SetErrorStatus(
                self.response, 403, error,
                "User does not own tournament and is authenticated with " +
                "the wrong code for involved pairs")
            return (False, None)
        return (True, next(p.pair_no for p in player_pairs if p.id == pair_id))
  def _ParseHandsFromRequestAndMaybeSetStatus(self):
    ''' Parses the hands from the body of the request. Checks if the body is
    valid JSON with all the proper fields set. Checks if each hand has valid
    scores. If not sets the response with the appropriate status and error
    message.

    Returns:
      List of hand dicts. If the request or its memebrs were invalid returns
      None.
    '''
    try:
      request_dict = json.loads(self.request.body)
    except ValueError:
      SetErrorStatus(self.response, 500, "Invalid Input",
                     "Unable to parse request body as JSON object")
      return None
    for hand in request_dict.get("hands", []):
      ns_score = hand.get('ns_score')
      ew_score = hand.get('ew_score')
      if not isinstance(ns_score, int):
        if not isinstance(ns_score, basestring):
          SetErrorStatus(self.response, 400, "Invalid Input", 
                         "ns_score must be int or string, was " + 
                         type(ns_score).__name__)
          return None
        if ns_score.strip().upper() not in AVG_VALUES:
          SetErrorStatus(self.response, 400, "Invalid Input",
                         "ns_score must be an integer or avg, was " + 
                         str(ns_score))
          return None
        if isinstance(ew_score, int):
          SetErrorStatus(self.response, 400, "Invalid Input",
                         "Cannot have one team with an avg score and another "
                         "with a real Tichu value ")
          return None
      if not isinstance(ew_score, int):
        if not isinstance(ew_score, basestring):
          SetErrorStatus(self.response, 400, "Invalid Input", 
                         "ew_score must be int or string, was " + 
                         type(ew_score).__name__)
          return None
        if ew_score.strip().upper() not in AVG_VALUES:
          SetErrorStatus(self.response, 400, "Invalid Input",
                         "ew_score must be an integer or avg, was " + 
                         str(ew_score))
          return None
        if isinstance(ns_score, int):
          SetErrorStatus(self.response, 400, "Invalid Input",
                         "Cannot have one team with an avg score and another "
                         "with a real Tichu value")
          return None
    return request_dict.get("hands", [])
Example #15
0
    def _CheckValidPositionAndMaybeSetStatus(self, position):
        '''Returns true if the position is one of allowed positions.

    If not, set an error in the response.

    Args:
      position: String. Position returned by the get header.
    '''
        if position != "N" and position != "E":
            SetErrorStatus(
                self.response, 404, "Invalid Request",
                "Position {} is not valid. Must be one of N or E".format(
                    position))
            return False
        return True
Example #16
0
  def put(self, id):
    user = users.get_current_user()
    tourney = GetTourneyWithIdAndMaybeReturnStatus(self.response, id)
    if not tourney:
      return

    if not CheckUserOwnsTournamentAndMaybeReturnStatus(self.response, user,
                                                       tourney):
      return

    request_dict = self._ParseRequestInfoAndMaybeSetStatus()
    if not request_dict:
      return

    name = request_dict['name']
    no_pairs = request_dict['no_pairs']
    no_boards = request_dict['no_boards']
    player_list = request_dict.get('players')
    allow_score_overwrites = request_dict.get('allow_score_overwrites', False)
    if not self._CheckValidTournamentInfoAndMaybeSetStatus(name, no_pairs,
                                                           no_boards,
                                                           player_list,
                                                           tourney.legacy_version_id):
      return
    if ((tourney.no_pairs != no_pairs or tourney.no_boards != no_boards) and
         len(tourney.GetScoredHandList()) != 0):
      SetErrorStatus(self.response, 400, "Invalid Request",
                     "Tournament already has registered hands")
      return
   
    old_no_pairs = tourney.no_pairs   
    tourney.no_pairs = no_pairs
    tourney.no_boards = no_boards
    tourney.name = name
    if allow_score_overwrites:
      tourney.Unlock()
    else:
      tourney.MakeLockable()
    tourney.PutPlayers(player_list, old_no_pairs)
    tourney_key = tourney.put_async()
    self.response.set_status(204)
Example #17
0
    def get(self, id, board_no):
        '''Gets all the scored results for a particular hand in the tournament.

    Args:
      id: String. Tournament id. 
      board_no: Integer. Hand number.

    See api for request and response documentation.
    '''
        tourney = GetTourneyWithIdAndMaybeReturnStatus(self.response, id)
        if not tourney:
            return

        if (not is_int(board_no)
            ) or int(board_no) < 1 or int(board_no) > tourney.no_boards:
            SetErrorStatus(self.response, 404, "Invalid Hand Parameters",
                           "Board number {} is invalid".format(board_no))
            return

        has_access, pair_no, all_matchups = self._CheckUserHasAccessMaybeSetStatus(
            tourney, int(board_no))
        if not has_access:
            return

        position = self.request.headers.get('X-position')
        if not self._CheckValidPositionAndMaybeSetStatus(position):
            return

        scores = self._GetAllPlayedScores(tourney, int(board_no), all_matchups)
        if not self._CheckPlayerScoredHandsAndMaybeSetStatus(
                pair_no, all_matchups, scores):
            return

        list_of_results = self._ScoresToJson(position, scores, all_matchups)
        self.response.headers['Content-Type'] = 'application/json'
        self.response.set_status(200)
        self.response.out.write(
            json.dumps({"results": list_of_results}, indent=2))
    def get(self, pair_id):
        ''' Returns tournament and pair number information this pair_id.

        Args: 
          pair_id: Opaque secret ID used to look up information for the user.
    '''
        player_pairs = PlayerPair._query(
            ndb.GenericProperty('id') == pair_id).fetch(
                projection=[PlayerPair.pair_no])
        if not player_pairs:
            SetErrorStatus(self.response, 404, "Invalid Id",
                           "Pair number with this ID does not exist")
            return
        info_dict = {
            'tournament_infos': [{
                'pair_no': p.pair_no,
                'tournament_id': str(p.key.parent().id())
            } for p in player_pairs]
        }

        self.response.headers['Content-Type'] = 'application/json'
        self.response.set_status(200)
        self.response.out.write(json.dumps(info_dict, indent=2))
Example #19
0
    def _CheckPlayerScoredHandsAndMaybeSetStatus(self, pair, all_matchups,
                                                 scores):
        '''Returns true if pair is present in one of the scored matchups

    Directors always return true.
    Args:
      pair: Integer. Pair number we are checking.
      all_matchups: List of (Integer, Integer) pairs. All matchups for this
                    board possible in the tournament.
      scores: List of HandScores. List of all scored hands with their scores.
    '''
        if pair == 0:
            return True

        scored_matchups = []
        for i in xrange(len(scores)):
            if scores[i]:
                scored_matchups.append(all_matchups[i])
        if not self._PlayerInMatchupList(pair, scored_matchups):
            SetErrorStatus(
                self.response, 403, "Forbidden User",
                "Pair {} has not yet played this hand.".format(pair))
            return False
        return True
  def _CanPutHandAndMaybeSetResponse(self, tourney, board_no, ns_pair, ew_pair):
    ''' Tests whether the tournament lock stats allows this user to write a hand.
  
    The owner is always allowed to write a hand. Pair code players can write a hand
    if the tournament is UNLOCKED or if it is LOCKABLE and no hand is currently 
    written.
  
    Args:
      tourney: Tournament. Current tournament. 
      board_no: Integer. Hand number.
      ns_pair: Integer. Pair number of team playing North/South.  
      ew_pair: Integer. Pair number of team playing East/West.

    Returns:
      Boolean. True iff the put call is allowed.
    '''
    user = users.get_current_user()
    if (user and tourney.owner_id == user.user_id()) or tourney.IsUnlocked():
      return True
    if tourney.IsLocked():
      SetErrorStatus(self.response, 405, "Forbidden by Tournament Status",
                     "This tournament is locked. No hands can be edited by non-directors")
      return False
    hand_score = HandScore.GetByHandParams(tourney, board_no, ns_pair, ew_pair)
    if not hand_score:
	  return True
    self.response.headers['Content-Type'] = 'application/json'
    response = {
        'calls' : hand_score.calls_dict(),
        'ns_score' : hand_score.get_ns_score(),
        'ew_score' : hand_score.get_ew_score(),
        'notes' : hand_score.notes,
    }
    self.response.set_status(405)
    self.response.out.write(json.dumps(response, indent=2))
    return False