Skip to content

Use serializers as a source for APIView schema parameters #7163

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
domingues opened this issue Jan 28, 2020 · 8 comments
Closed

Use serializers as a source for APIView schema parameters #7163

domingues opened this issue Jan 28, 2020 · 8 comments

Comments

@domingues
Copy link
Contributor

domingues commented Jan 28, 2020

I want to add filter like parameters to my APIView methods, they should appear in the schema and validate the incoming data, serializers are a perfect fit for this.

In this repo:
https://github.com/domingues/django-rest-framework/blob/0997748e4fbd0f330cfcac580b446d849e50f80e/rest_framework/schemas/openapi.py#L542-L586
we have a class APIViewSchema(AutoSchema) that provides (at the moment) two decorators: @APIViewSchema.serializer(serializer: serializers.Serializer) and @APIViewSchema.query_parameters(parameters: serializers.Serializer)
and use them like this:

class MySerializer(serializers.Serializer):
    count = serializers.IntegerField(help_text='Number of results.')


class MyView(views.APIView):
    schema = APIViewSchema()

    class Parameters(serializers.Serializer):
        q = serializers.CharField(required=True, min_length=3, help_text='Search by.')
        page_size = serializers.IntegerField(required=False, max_value=100, help_text='Number of results to return.')
        ordering = serializers.ChoiceField(choices=['title', 'rank'], default='rank',
                                           help_text='Order by.')

    @APIViewSchema.serializer(MySerializer())
    @APIViewSchema.query_parameters(Parameters())
    def get(self, request):
        parameters = self.Parameters(data=request.query_params)
        parameters.is_valid(raise_exception=True)
        data = ...
        return Response(MySerializer(data).data)

imagem

I think that would be really useful if we have something like this.

@Lucidiot
Copy link
Contributor

I think you could implement this using either the existing DRF filters—in your example, q would be provided by a SearchFilter and ordering by an OrderingFilter, and page_size from a pagination class—or writing your own. Filters can override the get_schema_operation_parameters to provide OpenAPI query parameters. You may have a custom filter inheriting from rest_framework.filters.BaseFilterBackend that uses a serializer on an APIView to add query parameters and possibly handle them.

@carltongibson
Copy link
Collaborator

In this idea, I don't see the benefit of the decorator and Parameters class over just subclassing AutoSchema, and overriding the appropriate method there. (It just seems to be introducing another way of doing the same thing.)

Possibly, like maybe, we could introduce an __init__ kwarg to AutoSchema so you could do something like:

class MyView(APIView):
    schema = AutoSchema(
        extra_parameters=[...]
    )

But I'm not yet convinced of the need for that. (The subclass is hardly a lot of code.)

@Lucidiot
Copy link
Contributor

Using a serializer means you also get validation on query parameters, which is always useful to reduce code duplication for example; why do a if len(xxx) < 20: raise ValidationError(...) when you could just have a CharField(max_length=20).

I think having more documentation and public methods on the AutoSchema would make subclassing easier and help point folks to how they can customize their schemas.

@carltongibson
Copy link
Collaborator

I think having more documentation and public methods on the AutoSchema would make subclassing easier and help point folks to how they can customize their schemas.

Yes, absolutely. I want to move ≈all the methods to public (i.e. no _) and document them for the next release. Easy PR land if you're looking. 😀

@domingues
Copy link
Contributor Author

Possibly, like maybe, we could introduce an __init__ kwarg to AutoSchema so you could do something like:

class MyView(APIView):
    schema = AutoSchema(
        extra_parameters=[...]
    )

The problem of that approach is that you will have the same parameters in all methods, which may not be desirable. The same for the serializer.

Using a serializer means you also get validation on query parameters, which is always useful to reduce code duplication for example; why do a if len(xxx) < 20: raise ValidationError(...) when you could just have a CharField(max_length=20).

Exactly.

@domingues
Copy link
Contributor Author

I think having more documentation and public methods on the AutoSchema would make subclassing easier and help point folks to how they can customize their schemas.

Yes, absolutely. I want to move ≈all the methods to public (i.e. no _) and document them for the next release. Easy PR land if you're looking. 😀

Maybe that's the way to go.

@carltongibson
Copy link
Collaborator

@Lucidiot is right here: filter parameters come from filter backends (be they django-filter, the DRF shipped ones, or your own.)

You can implement a filter backend to use serializers, to give you validation etc (which is exactly what django-filters does, except with FilterSets and forms under the hood). The relevant AutoSchema method is _get_filter_parameters(), which we'll deprecate and promote to public for 3.12.

@elonzh
Copy link

elonzh commented Nov 16, 2020

@carlfarrington The filter can be used to generate query parameters but it's not the aim for generating query parameters.

The default behavior of REST framework's generic list views is to return the entire queryset for a model manager. Often you will want your API to restrict the items that are returned by the queryset.

The simplest way to filter the queryset of any view that subclasses GenericAPIView is to override the .get_queryset() method.

Overriding this method allows you to customize the queryset returned by the view in a number of different ways.


class BaseFilterBackend:
    """
    A base class from which all filter backend classes should inherit.
    """

    def filter_queryset(self, request, queryset, view):
        """
        Return a filtered queryset.
        """
        raise NotImplementedError(".filter_queryset() must be overridden.")

    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()`'
        return []

    def get_schema_operation_parameters(self, view):
        return []

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants