From 1d41e4c59461e7a5db39e73dc42cc6fb22a4f1eb Mon Sep 17 00:00:00 2001 From: ghazi Date: Thu, 4 Dec 2025 09:14:10 +0100 Subject: [PATCH] Document 404 error response when the API accepts a format query parameter or a url suffix for content negotiation fixes #109 --- docs/changelog.md | 10 ++++++++-- docs/openapi.md | 11 +++++++++++ drf_standardized_errors/openapi.py | 8 ++++++++ tests/settings.py | 1 + tests/test_openapi.py | 13 +++++++++++++ 5 files changed, 41 insertions(+), 2 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index acf9a02..bd544a9 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -6,10 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [UNRELEASED] ### Added -- add support for python 3.14 +- Add support for python 3.14 ### Changed (backward-incompatible) -- set minimum version of drf-spectacular to 0.29.0 +- Set the minimum version of drf-spectacular to 0.29.0 +- Account for the `format` query parameter raising 404 errors when generating the API schema for error responses. This + will result in any project using the default value for `REST_FRAMEWORK["URL_FORMAT_OVERRIDE"]"` showing 404 errors for + every operation. That's because DRF enables the `format` query parameter in every endpoint by default. If you don't + need the `format` query parameter for content negotiation, you can disable it with by setting `"URL_FORMAT_OVERRIDE"` + to `None` in DRF settings. Refer + to [DRF docs](https://www.django-rest-framework.org/api-guide/settings/#url_format_override) for more information. ## [0.15.0] - 2025-06-09 ### Added diff --git a/docs/openapi.md b/docs/openapi.md index 0b04c15..36e443f 100644 --- a/docs/openapi.md +++ b/docs/openapi.md @@ -107,6 +107,17 @@ class CustomSerializer(serializers.Serializer): ## Tips and Tricks +### Hide 404 error responses due to the `format` query parameter + +Set `REST_FRAMEWORK["URL_FORMAT_OVERRIDE"]"` to `None` if you're not relying on a query parameter for content +negotiation. This avoids showing a 404 error response in the API schema for every operation. + +By default, DRF sets the value for `REST_FRAMEWORK["URL_FORMAT_OVERRIDE"]"` to `format`. That allows API consumers to +send a `format` query parameter in every operation for content negotiation. If the API cannot handle the requested +`format`, it will return a 404 error response. From the perspective of this package, that means every operation can +return a 404 response when using the default content negotiator. Refer +to [DRF docs](https://www.django-rest-framework.org/api-guide/settings/#url_format_override) for more information. + ### Hide error responses that show in every operation By default, the error response for all supported status codes will be added to the schema. Some of these status diff --git a/drf_standardized_errors/openapi.py b/drf_standardized_errors/openapi.py index 803d900..13a33bb 100644 --- a/drf_standardized_errors/openapi.py +++ b/drf_standardized_errors/openapi.py @@ -17,6 +17,7 @@ from rest_framework.pagination import CursorPagination, PageNumberPagination from rest_framework.parsers import FileUploadParser, JSONParser, MultiPartParser from rest_framework.permissions import AllowAny, IsAuthenticated +from rest_framework.settings import api_settings as drf_settings from rest_framework.versioning import ( AcceptHeaderVersioning, HostNameVersioning, @@ -203,10 +204,17 @@ def _should_add_http404_error_response(self) -> bool: if parameter["in"] == "path" ] ) + # the default content negotiator can raise a 404 when no renderer can handle + # to the format parameter in the URL + content_negotiator = self.view.get_content_negotiator() + view_can_have_no_renderers = ( + self.view.format_kwarg or drf_settings.URL_FORMAT_OVERRIDE + ) and isinstance(content_negotiator, DefaultContentNegotiation) return ( paginator_can_raise_404 or versioning_scheme_can_raise_404 or has_path_parameters + or view_can_have_no_renderers ) def _should_add_http405_error_response(self) -> bool: diff --git a/tests/settings.py b/tests/settings.py index 56379ce..742d716 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -23,6 +23,7 @@ "DEFAULT_AUTHENTICATION_CLASSES": [], "DEFAULT_PERMISSION_CLASSES": [], "TEST_REQUEST_DEFAULT_FORMAT": "json", + "URL_FORMAT_OVERRIDE": None, "DEFAULT_SCHEMA_CLASS": "drf_standardized_errors.openapi.AutoSchema", } diff --git a/tests/test_openapi.py b/tests/test_openapi.py index 928a9f0..a924d95 100644 --- a/tests/test_openapi.py +++ b/tests/test_openapi.py @@ -381,6 +381,19 @@ def test_404_error_when_url_parameters(): assert "404" in responses +def test_404_error_when_url_format_enabled(settings): + settings.REST_FRAMEWORK = { + **settings.REST_FRAMEWORK, + "URL_FORMAT_OVERRIDE": "format", + } + + route = "not-found/" + view = DummyView.as_view() + schema = generate_view_schema(route, view) + responses = get_responses(schema, route) + assert "404" in responses + + def test_no_404_error(): route = "not-found/" view = DummyView.as_view()