marcgibbons / django-rest-swagger

Swagger Documentation Generator for Django REST Framework: deprecated
https://marcgibbons.com/django-rest-swagger/
BSD 2-Clause "Simplified" License
2.59k stars 599 forks source link

No way to document parameters #549

Open leonardoarroyo opened 8 years ago

leonardoarroyo commented 8 years ago

New django-rest-swagger versions have deprecated YAML docstrings. There's no documentation demonstrating how to document request fields anymore.

There's some things you can do by changing the filter_backends on ViewSets, for example, but it just doesn't feel right. I still haven't found a way to do this on function based views.

ameade commented 8 years ago

It also seems that coreapi is less descriptive than openapi so the openapi schema will be incomplete since we generate from rest_frameworks coreapi document (via https://github.com/core-api/python-openapi-codec). I need the ability to specify response codes and bodies. IIUC the old way to get around this was with YAML docstrings but those are deprecated.

daimon99 commented 8 years ago

I still do not know how to document parameters. Hope there will be an example in the tutorial app. for example, how to document the parameters(mobile) in the method sendcode?

class UserViewSet(mixins.CreateModelMixin,
                  mixins.RetrieveModelMixin,
                  mixins.UpdateModelMixin,
                  viewsets.GenericViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = (IsUserOrReadOnly, IsAuthenticated)

    @list_route(methods=['post'])
    def sendcode(self, request):
        """send verify code"""
        from moore.services.yimei import send_sms

        username = request.data.get('mobile')
        verifycode = random.randint(1000, 9999)
        send_sms(username, verifycode)
        return Response('data': 'send ok')
daimon99 commented 8 years ago

I write a custom schemagenerator to deal with parameter:

class SchemaGenerator(schemas.SchemaGenerator):
    def get_link(self, path, method, callback, view):
        """Custom the coreapi using the func.__doc__ .

        if __doc__ of the function exsit, use the __doc__ building the coreapi. else use the default serializer.

        __doc__ in yaml format, eg:

        desc: the desc of this api.
        ret: when success invoked, return xxxx
        err: when error occured, return xxxx
        input:
        - name: mobile
          desc: the mobile number
          type: string
          required: true
          location: form
        - name: promotion
          desc: the activity id
          type: int
          required: true
          location: form
        """
        fields = self.get_path_fields(path, method, callback, view)
        yaml_doc = None
        func = getattr(view, view.action) if getattr(view, 'action', None) else None
        if func and func.__doc__:
            try:
                yaml_doc = yaml.load(func.__doc__)
            except:
                yaml_doc = None
        if yaml_doc and 'desc' in yaml_doc:
            desc = yaml_doc.get('desc', '')
            ret = yaml_doc.get('ret', '')
            err = yaml_doc.get('err', '')
            _method_desc = desc + '<br>' + 'return: ' + ret + '<br>' + 'error: ' + err
            params = yaml_doc.get('input', [])
            for i in params:
                _name = i.get('name')
                _desc = i.get('desc')
                _required = i.get('required', True)
                _type = i.get('type', 'string')
                _location = i.get('location', 'form')
                field = coreapi.Field(
                    name=_name,
                    location=_location,
                    required=_required,
                    description=_desc,
                    type=_type
                )
                fields.append(field)
        else:
            _method_desc = func.__doc__ if func and func.__doc__ else ''
            fields += self.get_serializer_fields(path, method, callback, view)
        fields += self.get_pagination_fields(path, method, callback, view)
        fields += self.get_filter_fields(path, method, callback, view)

        if fields and any([field.location in ('form', 'body') for field in fields]):
            encoding = self.get_encoding(path, method, callback, view)
        else:
            encoding = None

        if self.url and path.startswith('/'):
            path = path[1:]

        return coreapi.Link(
            url=urlparse.urljoin(self.url, path),
            action=method.lower(),
            encoding=encoding,
            fields=fields,
            description=_method_desc
        )
leonardoarroyo commented 8 years ago

@daimon99, on list routes, you can set the filter_backends property on the ViewSet to correctly generate the schema, still it feels a little hacky.

daimon99 commented 8 years ago

just wait rest framework 3.5. tomchristie said: "Gothca, understand the issue now! Yes, this has been an issue in how we generate the schema for swagger UI. It's resolved as part of the upcoming 3.5 release."

daimon99 commented 8 years ago

https://github.com/tomchristie/django-rest-framework/issues/4241. image

psychok7 commented 8 years ago

So no updates yet regarding this issue??

kevin-brown commented 8 years ago

Given that the 3.5 release is out, has this ticket been handled?

angvp commented 8 years ago

Maybe mentioning @tomchristie ?

tomchristie commented 8 years ago

There's some level of support for this, but it's a little patchy.

You'd need to look at the implementation of get_link() to see how the fields are built up. The description attribute on fields can then be pulled out by the swagger UI renderer...

slykar commented 7 years ago

Does CoreAPI even support custom type definition? I know you can specify anything in the field.type.

I think you would need to change rest_framework_swagger.renderers.OpenAPICodec so that the returned Swagger object would be extended with definitions:.

Those definitions could be based on Serializers and pushed to coreapi.document.Document with SchemaGenerator maybe. The coreapi.document.Document seem to be accepting any data.

class Document(itypes.Dict):
    # ...
    self._data = {key: _to_immutable(value) for key, value in content.items()}
tomchristie commented 7 years ago

@slykar - I think you're mixing up different concerns in that comment. (What datatypes can be included in a Document, vs. how do we describe parameter help text and parameter types.

I'm currently working on a documentation generator to tie in with Core API, which is helping tease out some of the docs related requirements. I'd suggest we hold further discussion until we can use that as a starting point.

Aside: Also worth re-iterating that I want to see any spec changes to Core API be driven by very explicit tooling requirements. Eg. "Adding X to Core API will allow me to do Y".

slykar commented 7 years ago

@tomchristie - To give some background. What I was trying to achieve was a documentation of profile parameter on my endpoint, which is an embedded document with it's own schema. SchemaGenerator specifies the type as 'object', where it should be a type on it's own, like 'UserProfile'.

When using OpenAPI, you can document such field by setting custom type. In OpenAPI this type is defined by definitions section. This is something you can not do with CoreAPI as far as I know, even tough the CoreAPI Field.type takes arbitrary string and you could put 'UserProfile' there.

The spec mismatch makes it hard to properly document endpoint parameters, utilizing OpenAPI type definitions. This is something I require, otherwise the generated OpenAPI spec is worthless for me.

I was even trying to figure out how to push this type information through SchemaGenerator to CoreAPI to OpenAPI.

Right now I'm back to writing the OpenAPI spec file by hand.

tomchristie commented 7 years ago

Gotcha. And yes, we may well move towards allowing for annotation information about more complex types. Out of interest what tooling are you using to document the API - Swagger UI, or something else?

slykar commented 7 years ago

@tomchristie I'm using Swagger UI, tough, when writing spec by hand I write it using API Blueprint (IMO easier to write and read than OpenAPI), then convert it to OpenAPI spec for Swagger UI.

nikparmar commented 7 years ago

Hey guys any update on this issue?

tomchristie commented 7 years ago

Yeah upcoming version of REST framework is working towards improving this.

cliffxuan commented 7 years ago

look forward to the new version of REST framework!

for the time being i'm using this class decorator: https://github.com/ministryofjustice/moj-product-dashboard/blob/develop/dashboard/libs/swagger_tools.py#L5

use case: https://github.com/ministryofjustice/moj-product-dashboard/blob/develop/dashboard/apps/dashboard/views.py#L117

marcgibbons commented 7 years ago

@cliffxuan Interesting solution. @tomchristie What are your plans for the next version? Would you consider extending the override methods like get_path_fields and get_filter_fields currently on the schema generator onto APIView?

gauravvjn commented 7 years ago

@cliffxuan https://github.com/marcgibbons/django-rest-swagger/issues/549#issuecomment-276993383 Your solution works fine for list action. what if we want to customize that at action level, i.e i want a specific query param for retrieve method or update or create method for that matter. How are we going to do that. can we modify your decorator to support at method level?

cliffxuan commented 7 years ago

@gjain0 my decorator works only for list view. i don't have a use case for yours yet. feel free to modify it.

FilmCoder commented 7 years ago

Has this issue been addressed in any way over the past 8 months? It appears that currently there is no concise readable way to specify parameters for API endpoints, which effectively renders Swagger as unusable.

gauravvjn commented 7 years ago

@FilmCoder I'm using @daimon99 solution. modified for my use case. It's working perfect.

here is the complete code https://gist.github.com/gjain0/f536d3988eb61e693ea306305c441bb7

JorisBenschop commented 7 years ago

It really is mind boggling that this issue remains open after all this time. I should also note that all the presented solutions above are not applicable to function based views

frennkie commented 7 years ago

Using help_text on Serializer works great for parameters - but I'm still missing a way to add a description to the Path parameters.

Would be great if that could be implemented!

bwmello commented 7 years ago

If all you care about is showing parameter types, is required or optional, and help_text, the "Model" view on the swagger page is an easy alternative, as described here (edit: just part 1, rest of article is out of date): http://idratherbewriting.com/2015/12/10/ten-realizations-using-swagger-and-swagger-ui/

Pros: Can be implemented with just Serializers and isn't a hack. Cons: Bad at displaying request data's nested objects and lists.

JorisBenschop commented 7 years ago

@bwmello , this is about documenting input parameters and (more importantly) the ability to enter parameter content within swagger. Your page suggests using markdown, but this ability was removed when the software went to coreAPI, without any viable replacement. This thread is not related to documenting the output parameters, which is what your page also describes.

fijemax commented 7 years ago

Hi, Any update on this issue ?

litchfield commented 7 years ago

Bizarre that a documentation tool for an API, doesn't- wait for it- allow you to document parameters. Kinda like a car that doesn't drive, or a plane that doesn't fly. Can I have the past hour of googling in disbelief back?

Here's my workaround, param_schema.py --

from rest_framework.compat import coreapi, coreschema
from rest_framework.filters import BaseFilterBackend

class ParamSchemaFilter(BaseFilterBackend):
    def get_schema_fields(self, view):
        assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
        assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`'
        fields = super().get_schema_fields(view)
        if hasattr(view, 'get_param_fields'):
            print(view)
            fields += view.get_param_fields(view)
        return fields

And in your settings --

REST_FRAMEWORK = {
    ...
    'DEFAULT_FILTER_BACKENDS': (
        ...
        'param_schema.ParamSchemaFilter', 
    ),

And in your view/viewset --

class MyAwesomeView(viewsets.GenericViewSet):

    def get_param_fields(self, view):
        fields = [
            coreapi.Field(
                name='my_field',
                required=True,
                location='query',
                schema=coreschema.String(
                    title='My awesome field',
                    description='This is my really awesome field right here, so awesome'
                )
            ),
        ]
        return fields
fazpu commented 7 years ago

Seems like things are moving forward: https://github.com/encode/django-rest-framework/issues/4502

m-haziq commented 7 years ago

Hi all, I have recently integrated Django Rest Swagger 2 into an existing project, I faced alot of issues, and resolved them by finding solutions at different forums. Now I have documented all the required steps here: https://github.com/m-haziq/django-rest-swagger-docs with all possible issues I could face and their solutions.

fazpu commented 6 years ago

This is the way to go: https://github.com/axnsan12/drf-yasg

There are currently two decent Swagger schema generators that I could find for django-rest-framework:

django-rest-swagger drf-openapi Out of the two, django-rest-swagger is just a wrapper around DRF 3.7 schema generation with an added UI, and thus presents the same problems. drf-openapi is a bit more involved and implements some custom handling for response schemas, but ultimately still falls short in code generation because the responses are plain of lacking support for named schemas.

gregorypease280 commented 6 years ago

Is anyone currently working on this?

m-haziq commented 6 years ago

@gregorypease280 yes I am. Try reading https://github.com/m-haziq/django-rest-swagger-docs

GuillaumeCisco commented 6 years ago

For people interested by making swagger work with the last version of django rest framework (3.7.7), you'll notice the tweak gave by @daimon99 in this comment https://github.com/marcgibbons/django-rest-swagger/issues/549#issuecomment-250145712 which I used is no longer working.

I've just ported the new code for making it work with django rest framework 3.7.7 and django rest swagger 2.1.1

Create a file schema_view.py:

from django.utils.six.moves.urllib import parse as urlparse
from rest_framework.schemas import AutoSchema
import yaml
import coreapi
from rest_framework_swagger.views import get_swagger_view

class CustomSchema(AutoSchema):
    def get_link(self, path, method, base_url):

        view = self.view
        method_name = getattr(view, 'action', method.lower())
        method_docstring = getattr(view, method_name, None).__doc__
        _method_desc = ''

        fields = self.get_path_fields(path, method)

        try:
            a = method_docstring.split('---')
        except:
            fields += self.get_serializer_fields(path, method)
        else:
            yaml_doc = None
            if method_docstring:
                try:
                    yaml_doc = yaml.load(a[1])
                except:
                    yaml_doc = None

            # Extract schema information from yaml

            if yaml_doc and type(yaml_doc) != str:
                _desc = yaml_doc.get('desc', '')
                _ret = yaml_doc.get('ret', '')
                _err = yaml_doc.get('err', '')
                _method_desc = _desc + '\n<br/>' + 'return: ' + _ret + '<br/>' + 'error: ' + _err
                params = yaml_doc.get('input', [])

                for i in params:
                    _name = i.get('name')
                    _desc = i.get('desc')
                    _required = i.get('required', False)
                    _type = i.get('type', 'string')
                    _location = i.get('location', 'form')
                    field = coreapi.Field(
                        name=_name,
                        location=_location,
                        required=_required,
                        description=_desc,
                        type=_type
                    )
                    fields.append(field)
            else:
                _method_desc = a[0]
                fields += self.get_serializer_fields(path, method)

        fields += self.get_pagination_fields(path, method)
        fields += self.get_filter_fields(path, method)

        manual_fields = self.get_manual_fields(path, method)
        fields = self.update_fields(fields, manual_fields)

        if fields and any([field.location in ('form', 'body') for field in fields]):
            encoding = self.get_encoding(path, method)
        else:
            encoding = None

        if base_url and path.startswith('/'):
            path = path[1:]

        return coreapi.Link(
            url=urlparse.urljoin(base_url, path),
            action=method.lower(),
            encoding=encoding,
            fields=fields,
            description=_method_desc
        )

schema_view = get_swagger_view(title='Product Testing API')

Your urls.py file references it like:

from .schema_view import schema_view

urlpatterns = [
    url(r'^v1/api/', include([
        url(r'^doc/', schema_view),
    ])),

No the sad part is that you have to override all your APIView or ModelViewSet with

schema = CustomSchema()

as described in the 3.7.7 release : http://www.django-rest-framework.org/api-guide/schemas/#per-view-schema-customisation

@m-haziq Hope it will help for making the migration in your README file ;)

Swich1987 commented 6 years ago

There are also problems with adding parameters to function-based views, like this:

def register(request):
    #read parameter "code" and check it
    response_data = {'code': 'success.', 'token': 'sometoken'}
    return Response(response_data, status = status.HTTP_201_CREATED)

how to add "code" from here to swagger I can't find. Unfortunately, don't have enough time to work on it and post an answer, so maybe someone else will post the answer here.

m-haziq commented 6 years ago

@GuillaumeCisco Great. Thanks for sharing it, I will definitely take some time out of my routine to add this update to my documentation sometimes soon!

m-haziq commented 6 years ago

@Swich1987 Please read this part of doc. You can document FBVs smoothly: https://github.com/m-haziq/django-rest-swagger-docs#advance-usage