diff --git a/rest_framework/views.py b/rest_framework/views.py index 832f1723309..bec10560a93 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -356,7 +356,15 @@ def check_throttles(self, request): throttle_durations.append(throttle.wait()) if throttle_durations: - self.throttled(request, max(throttle_durations)) + # Filter out `None` values which may happen in case of config / rate + # changes, see #1438 + durations = [ + duration for duration in throttle_durations + if duration is not None + ] + + duration = max(durations, default=None) + self.throttled(request, duration) def determine_version(self, request, *args, **kwargs): """ diff --git a/tests/test_throttling.py b/tests/test_throttling.py index 3c172e2636e..d5a61232d92 100644 --- a/tests/test_throttling.py +++ b/tests/test_throttling.py @@ -159,6 +159,27 @@ def test_request_throttling_multiple_throttles(self): assert response.status_code == 429 assert int(response['retry-after']) == 58 + def test_throttle_rate_change_negative(self): + self.set_throttle_timer(MockView_DoubleThrottling, 0) + request = self.factory.get('/') + for dummy in range(24): + response = MockView_DoubleThrottling.as_view()(request) + assert response.status_code == 429 + assert int(response['retry-after']) == 60 + + previous_rate = User3SecRateThrottle.rate + try: + User3SecRateThrottle.rate = '1/sec' + + for dummy in range(24): + response = MockView_DoubleThrottling.as_view()(request) + + assert response.status_code == 429 + assert int(response['retry-after']) == 60 + finally: + # reset + User3SecRateThrottle.rate = previous_rate + def ensure_response_header_contains_proper_throttle_field(self, view, expected_headers): """ Ensure the response returns an Retry-After field with status and next attributes