@@ -80,6 +80,14 @@ class ScrimmageRateLimited(APIException):
8080 default_code = "scrimmages_rate_limited"
8181
8282
83+ class ScrimmageLowerRank (APIException ):
84+ status_code = status .HTTP_409_CONFLICT
85+ default_detail = (
86+ "You cannot request a ranked scrimmage against a team ranked lower than you."
87+ )
88+ default_code = "scrimmage_lower_rank"
89+
90+
8391class EpisodeTeamUserContextMixin :
8492 """Add the current episode, team and user to the serializer context."""
8593
@@ -926,24 +934,54 @@ def create(self, request, *, episode_id):
926934 serializer = self .get_serializer (data = request .data )
927935 serializer .is_valid (raise_exception = True )
928936
929- if serializer .validated_data ["is_ranked" ]:
930- # Get the global settings (assuming only one instance exists)
931- episode = Episode .objects .get (pk = self .kwargs ["episode_id" ])
932- if episode and (not episode .is_allowed_ranked_scrimmage ):
933- raise RankedMatchesDisabed
937+ # if serializer.validated_data["is_ranked"]:
938+ # Get the global settings (assuming only one instance exists)
939+ episode = Episode .objects .get (pk = self .kwargs ["episode_id" ])
940+ is_ranked = serializer .validated_data ["is_ranked" ]
941+ if episode and (not episode .is_allowed_ranked_scrimmage ) and is_ranked :
942+ raise RankedMatchesDisabed
934943
935- active_statuses = {
936- SaturnStatus .CREATED ,
937- SaturnStatus .QUEUED ,
938- SaturnStatus .RUNNING ,
939- SaturnStatus .RETRY ,
940- }
941- existing_requests = ScrimmageRequest .objects .filter (
942- requested_by = serializer .validated_data ["requested_by_id" ],
944+ past_hour = timezone .now () - timezone .timedelta (hours = 1 )
945+
946+ active_statuses = {
947+ SaturnStatus .CREATED ,
948+ SaturnStatus .QUEUED ,
949+ SaturnStatus .RUNNING ,
950+ SaturnStatus .RETRY ,
951+ }
952+ # requests initiated by requestor in past hour
953+ existing_requests_from_requestor = ScrimmageRequest .objects .filter (
954+ requested_by = serializer .validated_data ["requested_by_id" ],
955+ is_ranked = is_ranked ,
956+ created__gte = past_hour ,
957+ )
958+
959+ # matches involving requestor in past hour
960+ match_count = MatchParticipant .objects .filter (
961+ team_id = serializer .validated_data ["requested_by_id" ],
962+ match__episode = episode ,
963+ match__tournament_round__isnull = True ,
964+ match__is_ranked = is_ranked ,
965+ match__created__gte = past_hour ,
966+ ).count ()
967+ # max matches that can be requested by participant in current mode
968+ max_matches = (
969+ episode .ranked_scrimmage_hourly_limit
970+ if serializer .validated_data ["is_ranked" ]
971+ else episode .unranked_scrimmage_hourly_limit
972+ )
973+
974+ # check number of matches requested + run in past hour (rate limiting)
975+ # note this checks both unranked and ranked caps
976+ if existing_requests_from_requestor .count () + match_count >= max_matches :
977+ raise ScrimmageRateLimited
978+ # check if too many ranked scrimmages requested between two teams
979+ if is_ranked :
980+ # existing request between two teams
981+ existing_requests = existing_requests_from_requestor .filter (
943982 requested_to = serializer .validated_data ["requested_to" ],
944- is_ranked = True ,
945- status = ScrimmageRequestStatus .PENDING ,
946983 ).count ()
984+ # existing active matches between the two participants
947985 existing_matches = (
948986 Match .objects .annotate (
949987 has_requester = Exists (
@@ -963,20 +1001,27 @@ def create(self, request, *, episode_id):
9631001 has_requester = True ,
9641002 has_requestee = True ,
9651003 status__in = active_statuses ,
966- is_ranked = True ,
1004+ is_ranked = is_ranked ,
9671005 )
9681006 .count ()
9691007 )
1008+
9701009 if (
9711010 existing_requests + existing_matches
9721011 >= settings .MAX_SCRIMMAGES_AGAINST_TEAM
9731012 ):
9741013 raise TooManyScrimmages
9751014
976- # Check if we should reject based on rate-limiting
977- requestor = Team .objects .get (pk = serializer .validated_data ["requested_by_id" ])
978- if not requestor .can_scrimmage (serializer .validated_data ["is_ranked" ]):
979- raise ScrimmageRateLimited
1015+ # stop teams from scrimmaging lower ranked teams
1016+ if is_ranked :
1017+ # fetch ranks of the two teams
1018+ team1 = Team .objects .select_related ("profile__rating" ).get (
1019+ pk = serializer .validated_data ["requested_by_id" ]
1020+ )
1021+ team2 = serializer .validated_data ["requested_to" ]
1022+ # compare ratings
1023+ if team1 .profile .rating .value > team2 .profile .rating .value :
1024+ raise ScrimmageLowerRank
9801025
9811026 serializer .save ()
9821027 # Generate the Location header, as supplied by CreateModelMixin
0 commit comments