With a response from my drf just containing the data given by a single serializer, we can implement it as:
@swagger_auto_schema(
operation_id='ID example',
operation_description="Description example.",
responses={status.HTTP_200_OK: Serializer4ModelA(many=True)},
)
Which works fantastic, but with some requests constructing a dictionary, where two or three of the keys correspond to different serializers, eg
response = {
"a": serializer_data_for_model_a,
"b": serializer_data_for_model_b,
"c": serializer_data_for_model_c
}
How can we describe that in the auto schema? I've tried a few different approaches, mostly similar to the following:
@swagger_auto_schema(
operation_id='ID example',
operation_description="Description example.",
responses={status.HTTP_200_OK: openapi.Response(
description='response description',
schema=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'a': Serializer4ModelA(many=True),
'b': Serializer4ModelB(many=True),
'c': Serializer4ModelC(many=True)
})
)}
)
But always fails when loading the documentation, with flex
saying:
"/usr/local/lib/python3.6/site-packages/flex/utils.py", line 125, in
get_type_for_value raise ValueError("Unable to identify type of
{0}".format(repr(value)))
ValueError: Unable to identify type of
Serializer4ModelA(many=True):
I've read the documentation over and over again, and scoured over github for an example, but I couldn't find an example or anyone doing this. So my question is how to successfully manually define a schema for a response that contains different serializers for different keys in the returned response?
What I usually do is to create another serializer (just so that drf-yasg can generate the docs).
For example if I have an endpoint that returns:
{
"results": [..list of serialized results with serializer X...]
}
I create a second serializer:
class Y(serializers.Serializer):
results = X(many=True)
and use Y
serializer in the swagger_auto_schema decorator.
I ended up being able to do it, although probably not the most elegant solution but it does work.
My drf has a custom app-label format, so all my apps are in a folder, and let's call this folder apps
.
In my question, for a serializer, we can replace Serializer4ModelA
in the properties
section of the openapi.Schema
with a custom function, lets say get_serializer(Serializer4ModelA())
.
So my idea was to basically construct the schema myself by getting the information automatically and automatically constructing the properties
dictionary. It's very hacky, but useful for me because in my documentation I also want to pass in the serializers for Dynamodb, so I made a very similar function for Dynamodb serializers.
I only just made it, and it works, but obviously needs more attention to cover all fields in the field mapping
, better dealing with SerializerMethodFields
.
But none the less, it is a solution that works but is not generic, tweaks and stuff will have to be made depending on your particular project.
I implemented the function roughly as follows:
from drf_yasg import openapi
from drf_yasg.inspectors import SwaggerAutoSchema
from drf_yasg.utils import swagger_auto_schema
from drf_yasg.inspectors import FieldInspector
from drf_yasg.utils import swagger_serializer_method
import rest_framework
rest_framework_openapi_field_mapping = {
"ListField": openapi.TYPE_ARRAY,
"CharField": openapi.TYPE_STRING,
"BooleanField": openapi.TYPE_BOOLEAN,
"FloatField": openapi.TYPE_NUMBER,
"DateTimeField": openapi.TYPE_STRING,
"IntegerField": openapi.TYPE_INTEGER,
"SerializerMethodField": openapi.TYPE_STRING
}
def parse_rest_framework_field(field):
rest_framework_field_type = field.split("(")[0]
openapi_field_type =
rest_framework_openapi_field_mapping[rest_framework_field_type]
if "help_text=" in field:
field_description = field.split("help_text='")[-1].split("'")[0]
else:
field_description = None
return openapi.Schema(type=openapi_field_type, description=field_description)
def parse_serializer(serializer):
properties = {}
for k,v in serializer.get_fields().items():
if v.__module__ == "rest_framework.fields":
properties[k] = parse_rest_framework_field(str(v))
elif v.__module__.startswith("apps."):
serializer = str(v).strip().split("(")[0]
exec(f"from {v.__module__} import {serializer}")
eval_serializer = eval(f"{serializer}()")
properties[k] = openapi.Schema(type=openapi.TYPE_OBJECT, properties=parse_serializer(eval_serializer))
else:
pass
return properties
def get_serializer(serializer, description):
""" Needs to return openapi.Schema() """
properties = parse_serializer(serializer)
return_openapi_schema = openapi.Schema( type=openapi.TYPE_OBJECT, properties=properties, description=description)
return return_openapi_schema
I faced this problem and was looking if there is another way than my initial solution (same as how @Hernan explained it) but found none. The code of drf_yasg.openapi.Schema
(drf_yasg==1.20.0) showed that it doesn't accept any serializer object. So as already said by @Hernan, the way around this is to have an additional serializer and define there the nested child serializers. Then, pass it to either the swagger_auto_schema.responses
directly or through an openapi.Response.schema
(as below):
from django.urls import path
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import serializers, status, views
class Serializer4ModelA(serializers.Serializer):
dog = serializers.CharField(label="My dog is a good boy")
class Serializer4ModelB(serializers.Serializer):
perro = serializers.CharField(label="Mi perro es un buen chico")
hund = serializers.CharField(label="Mein Hund ist ein guter Junge")
aso = serializers.CharField(label="Ang aso ko ay mabait na bata")
class Serializer4ModelC(serializers.Serializer):
eey = serializers.CharField(label="Eygaygu waa wiil fiican")
class SampleResponseSerializer(serializers.Serializer):
a = Serializer4ModelA(many=True)
b = Serializer4ModelB(many=True)
c = Serializer4ModelC(many=True)
class SampleView(views.APIView):
@swagger_auto_schema(
responses={
status.HTTP_200_OK: openapi.Response(
description="response description",
schema=SampleResponseSerializer,
)
}
)
def get(self, request):
pass
urlpatterns = [
path("sample/", SampleView.as_view()),
]
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.