axnsan12 / drf-yasg

Automated generation of real Swagger/OpenAPI 2.0 schemas from Django REST Framework code.
https://drf-yasg.readthedocs.io/en/stable/
Other
3.39k stars 434 forks source link

Fix ListField(chield=FileField) #805

Open wlccgp3 opened 2 years ago

wlccgp3 commented 2 years ago

Bug Report

ListField(chield=ImageField) can not use

Description

SwaggerGenerationError: FileField is supported only in a formData Parameter or response Schema

my solution, and it's work

# in FileFieldInspector

def file_field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
    SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type, use_references, **kwargs)

    if isinstance(field, serializers.FileField):
        # swagger 2.0 does not support specifics about file fields, so ImageFile gets no special treatment
        # OpenAPI 3.0 does support it, so a future implementation could handle this better
        err = SwaggerGenerationError("FileField is supported only in a formData Parameter or response Schema")
        if swagger_object_type == openapi.Schema:
            # FileField.to_representation returns URL or file name
            result = SwaggerType(type=openapi.TYPE_STRING, read_only=True)
            if getattr(field, 'use_url', rest_framework_settings.UPLOADED_FILES_USE_URL):
                result.format = openapi.FORMAT_URI
            return result
        elif swagger_object_type in [openapi.Parameter, openapi.Items]:  # support ListField
            param = SwaggerType(type=openapi.TYPE_FILE)
            if param['in'] != openapi.IN_FORM:
                raise err  # pragma: no cover
            return param
        else:
            raise err  # pragma: no cover

    return NotHandled
# in InlineSerializerInspector
def inline_field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
    SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type, use_references, **kwargs)

    if isinstance(field, (serializers.ListSerializer, serializers.ListField)):
        child_schema = self.probe_field_inspectors(field.child, ChildSwaggerType, use_references, **kwargs)  # add kwargs
        limits = find_limits(field) or {}
        return SwaggerType(
            type=openapi.TYPE_ARRAY,
            items=child_schema,
            **limits
        )
    ......
image

My Environment

drf-yasg==1.21.3
django==3.2.5
djangorestframework==3.12.4
verhovensky commented 1 year ago

Why hasn't it been merged? Pretty sure somebody has the same problem

psunny28 commented 10 months ago

I tried your solution but not getting multiple files just blank

drf-yasg==1.21.7
Django==4.2.2
djangorestframework==3.14.0

image

Not getting the fields on Swagger.

from collections import OrderedDict

from drf_yasg import openapi
from drf_yasg.errors import SwaggerGenerationError
from drf_yasg.inspectors.base import NotHandled
from drf_yasg.inspectors.field import FieldInspector, find_limits, filter_none, SerializerInspector, \
    get_serializer_ref_name, get_serializer_class
from rest_framework import serializers
from rest_framework.settings import api_settings as rest_framework_settings

class CustomFileFieldInspector(FieldInspector):

    def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
        SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type, use_references,
                                                                **kwargs)

        if isinstance(field, serializers.FileField):
            # swagger 2.0 does not support specifics about file fields, so ImageFile gets no special treatment
            # OpenAPI 3.0 does support it, so a future implementation could handle this better
            err = SwaggerGenerationError("FileField is supported only in a formData Parameter or response Schema")
            if swagger_object_type == openapi.Schema:
                # FileField.to_representation returns URL or file name
                result = SwaggerType(type=openapi.TYPE_STRING, read_only=True)
                if getattr(field, 'use_url', rest_framework_settings.UPLOADED_FILES_USE_URL):
                    result.format = openapi.FORMAT_URI
                return result
            elif swagger_object_type in [openapi.Parameter,openapi.Items]:
                param = SwaggerType(type=openapi.TYPE_FILE)
                if param['in'] != openapi.IN_FORM:
                    raise err  # pragma: no cover
                return param
            else:
                raise err  # pragma: no cover

        return NotHandled

class InlineSerializerInspector(SerializerInspector):
    """Provides serializer conversions using :meth:`.FieldInspector.field_to_swagger_object`."""

    #: whether to output :class:`.Schema` definitions inline or into the ``definitions`` section
    use_definitions = False

    def get_schema(self, serializer):
        return self.probe_field_inspectors(serializer, openapi.Schema, self.use_definitions)

    def add_manual_parameters(self, serializer, parameters):
        """Add/replace parameters from the given list of automatically generated request parameters. This method
        is called only when the serializer is converted into a list of parameters for use in a form data request.

        :param serializer: serializer instance
        :param list[openapi.Parameter] parameters: generated parameters
        :return: modified parameters
        :rtype: list[openapi.Parameter]
        """
        return parameters

    def get_request_parameters(self, serializer, in_):
        fields = getattr(serializer, 'fields', {})
        parameters = [
            self.probe_field_inspectors(
                value, openapi.Parameter, self.use_definitions,
                name=self.get_parameter_name(key), in_=in_
            )
            for key, value
            in fields.items()
            if not getattr(value, 'read_only', False)
        ]

        return self.add_manual_parameters(serializer, parameters)

    def get_property_name(self, field_name):
        return field_name

    def get_parameter_name(self, field_name):
        return field_name

    def get_serializer_ref_name(self, serializer):
        return get_serializer_ref_name(serializer)

    def _has_ref_name(self, serializer):
        serializer_meta = getattr(serializer, 'Meta', None)
        return hasattr(serializer_meta, 'ref_name')

    def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
        SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type, use_references, **kwargs)

        if isinstance(field, (serializers.ListSerializer, serializers.ListField)):
            child_schema = self.probe_field_inspectors(field.child, ChildSwaggerType, use_references,
                                                       **kwargs)  # add kwargs
            limits = find_limits(field) or {}
            return SwaggerType(
                type=openapi.TYPE_ARRAY,
                items=child_schema,
                **limits
            )
        elif isinstance(field, serializers.Serializer):
            if swagger_object_type != openapi.Schema:
                raise SwaggerGenerationError("cannot instantiate nested serializer as " + swagger_object_type.__name__)

            ref_name = self.get_serializer_ref_name(field)

            def make_schema_definition(serializer=field):
                properties = OrderedDict()
                required = []
                for property_name, child in serializer.fields.items():
                    property_name = self.get_property_name(property_name)
                    prop_kwargs = {
                        'read_only': bool(child.read_only) or None
                    }
                    prop_kwargs = filter_none(prop_kwargs)

                    child_schema = self.probe_field_inspectors(
                        child, ChildSwaggerType, use_references, **prop_kwargs
                    )
                    properties[property_name] = child_schema

                    if child.required and not getattr(child_schema, 'read_only', False):
                        required.append(property_name)

                result = SwaggerType(
                    # the title is derived from the field name and is better to
                    # be omitted from models
                    use_field_title=False,
                    type=openapi.TYPE_OBJECT,
                    properties=properties,
                    required=required or None,
                )

                setattr(result, '_NP_serializer', get_serializer_class(serializer))
                return result

            if not ref_name or not use_references:
                return make_schema_definition()

            definitions = self.components.with_scope(openapi.SCHEMA_DEFINITIONS)
            actual_schema = definitions.setdefault(ref_name, make_schema_definition)
            actual_schema._remove_read_only()

            actual_serializer = getattr(actual_schema, '_NP_serializer', None)
            this_serializer = get_serializer_class(field)
            if actual_serializer and actual_serializer != this_serializer:
                explicit_refs = self._has_ref_name(actual_serializer) and self._has_ref_name(this_serializer)
                if not explicit_refs:
                    raise SwaggerGenerationError(
                        "Schema for %s would override distinct serializer %s because they implicitly share the same "
                        "ref_name; explicitly set the ref_name attribute on both serializers' Meta classes"
                        % (actual_serializer, this_serializer))

            return openapi.SchemaRef(definitions, ref_name)

        return NotHandled

class ReferencingSerializerInspector(InlineSerializerInspector):
    use_definitions = True

This is not supported on Newer versions.

Waiting for OpenAPI 3.0

Can you please help on this right now ?

@wlccgp3

wlccgp3 commented 10 months ago

@psunny28 your should set something like this

change parser_classes

parser_classes=[MultiPartParser]

serializer

@swagger_auto_schema(
    request_body=serializers.UploadSerializer,
)
class UploadSerializer(serializers.Serializer):
    files = serializers.ListField(
        child=serializers.ImageField(max_length=100000,
                                     allow_empty_file=False,
                                     use_url=False)
    )