yezyilomo / django-restql

Turn your API made with Django REST Framework(DRF) into a GraphQL like API.
https://yezyilomo.github.io/django-restql
MIT License
621 stars 43 forks source link

Is it possible to exclude fields by default? #303

Open Alejandroid17 opened 2 years ago

Alejandroid17 commented 2 years ago

Is it possible to exclude fields by default? I have a serializer with a serialized field with a lot of data. This data will be used in some specific cases, but not always.

class BookSerializer(DynamicFieldsMixin, serializer.ModelSerializer):
    ....

    class Meta:
        model = Book
        fields = ['id', 'name',  '...']

class UserSerializer(DynamicFieldsMixin, serializer.ModelSerializer):
    books = BookSerializer(many=True, read_only=True)
    ....

    class Meta:
        model = User
        fields = ['id', 'username', 'email', '...', 'books']

Imagine a user with 500 books. In my logic I normally don't need to know information about those 500 books, but I do need to know information about the user (this is not a real example).

I could exclude using the query GET /user/1?query={username, -books} but it forces me to put it everywhere where it is consumed.

The idea would be something like:

class UserSerializer(DynamicFieldsMixin, serializer.ModelSerializer):
    books = BookSerializer(many=True, read_only=True)
    ....

    class Meta:
        model = User
        fields = ['id', 'username', 'email', '...', 'books']
        default_exclude = ['books']

Default:

 # `GET /user/1`

{
    "id": 100
    "username": "dummy",
    "....": "....", # without the "books" field
},

With books field:

 # `GET /user/1?query={id, username, books}`

{
    "id": 100
    "username": "dummy",
    "books": [ ..... ]
}

Thank you for everything!

sommelon commented 2 years ago

The example you wrote looks possible by overriding the DynamicFieldsMixin.get_parsed_restql_query like this:

    def get_parsed_restql_query(self):
        request = self.context.get('request')

        if request is not None and self.has_restql_query_param(request):  # Check query parameter first
            # Get from request query parameter
            return self.get_parsed_restql_query_from_req(request)
        elif self.dynamic_fields_mixin_kwargs["query"] is not None:  # Check serializer argument second
            # Get from query kwarg
            return self.get_parsed_restql_query_from_query_kwarg()
        elif self.dynamic_fields_mixin_kwargs["parsed_query"] is not None:
            # Get from parsed_query kwarg
            return self.dynamic_fields_mixin_kwargs["parsed_query"]
        return None  # There is no query so we return None as a parsed query

and overriding GenericAPIView.get_serializer like this:

    def get_serializer(self, *args, **kwargs):
        serializer_class = self.get_serializer_class()
        kwargs.setdefault('context', self.get_serializer_context())
        return serializer_class(*args, query="{-books}", **kwargs)

(or just passing the query="{-books}" to a serializer, if you aren't using the get_serializer method). BUT this just prioritizes the query parameter from the request over the query argument to serializer. So if you did provide the query parameter (e.g. ?query={-username}), you would still get books, they won't be excluded.

{
    id,
    books,
    ... # everything except username
}
dnievas04 commented 12 months ago

Would it be possible to extend the concept more generically? We could add an extra parameter, named for example "lazy_load" in the serializer we are using, where fields marked as such would only be loaded when explicitly specified in the query parameters, allowing for on-demand data retrieval. 

I love this project, and we are currently using it in our production software. However, I believe that this feature would help sometimes minimize data overfetching for our endpoints while reusing existing serializers.

The example at the beginning of the thread perfectly illustrates the requirement. Keeping in mind that we are working with a REST architectural style, if we want to GET information about users, the ideal approach would be to bring only the information associated with the user initially. If we eventually need more information, we can explicitly ask for it with this excellent tool