django-ninja
django-ninja copied to clipboard
Accept multiple request types?
In django rest framework it is possible for views to get either urlencoded form body or json body.
Is there a way to allow multiple request types, and, to choose them in the swagger UI? currently it shows only one.

@asaff1 well to me this seems like a very uncommon practice to use both
(and form/url-ecoded does not allow you to have nested sub objects)
do you see any practical use case where you would want to send both ?
Technically you can make a custom Parser that will accept both content types
but to transfer this information to openapi schema would be some extra trick..
For json body that need to be embedded into form data, you can stringify and use another key for it, eg: jsonData={\"a\": 2021}. Please don't try mixing body type.
do you see any practical use case where you would want to send both ?
A common use case is to be more accommodating in public APIs. Different request libraries choose different default content types (application/json vs application/x-www-form-urlencoded ) when posting data, so it adds extra friction for users to make sure that they're using the correct content type. This is especially true because the validation errors don't mention the content type as the problem. Consider running
curl -X POST https://example.com/public-api \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"argument": "value"}'
and getting
{"detail": [{"loc": ["form", "argument"], "msg": "field required", "type": "value_error.missing"}
as a response. It's not obvious what the issue is here compared to an error like "The application/json content type is not supported, please use application/x-www-form-urlencoded instead."
you can make a custom Parser that will accept both content types
This isn't perfect, but it allows form models to parse JSON bodies when the appropriate content type header is set.
class JsonCompatibleParser(Parser):
def parse_querydict(
self, data: MultiValueDict, list_fields: List[str], request: HttpRequest
) -> DictStrAny:
if request.META.get('CONTENT_TYPE') == 'application/json':
return self.parse_body(request)
return super().parse_querydict(data, list_fields, request)
An easy way to solve this is to have parsers define a media_type, just like response renderers.
Then an API/Router definition would look like this:
NinjaAPI(parsers=[JSONParser(), XMLParser()])
As part of the request pipeline, the request Content-Type header would then be checked against a dict[parser.media_type, parser] to select the appropriate Parser.
If no appropriate parsers are found then we just respond with HTTP 415 Unsupported Media Type.
Corner case: if there is no Content-Type header then we just match it to the first parser.
With O(1) complexity for matching the parsers, I don't expect to see a performance overhead.
Ideally, the same should be done for renderers, where we check against the request Accept header so that we can have end-to-end support for different accept and content types.
The only problem that needs to be solved is how to handle the OpenAPI generation. I'm not familiar with how this is currently handled.
Ideally, the Schema class would also need changes to support different media types than just JSON and each parser would also need to define a converter but IMO graceful handling of accept & content type is a good first step.