encode / django-rest-framework

Web APIs for Django. 🎸
https://www.django-rest-framework.org
Other
28.18k stars 6.81k forks source link

Add Serializer per action to ViewSets #4632

Closed dyve closed 7 years ago

dyve commented 7 years ago

A common use case is to list objects first, and then retrieve details.

The current implementation of ViewSet is to use one Serializer for every action (list, retrieve, etc).

This means listing and then retrieving yields no additional data.

A good solution would be to allow a default Serializer (using the serializer_class attribute of a ViewSet), and an optional dict of action to Serializer, as described in this StackOverflow answer: http://stackoverflow.com/a/22922156/117831

class MultiSerializerViewSetMixin(object):
    def get_serializer_class(self):
        """
        Look for serializer class in self.serializer_action_classes, which
        should be a dict mapping action name (key) to serializer class (value),
        i.e.:

        class MyViewSet(MultiSerializerViewSetMixin, ViewSet):
            serializer_class = MyDefaultSerializer
            serializer_action_classes = {
               'list': MyListSerializer,
               'my_action': MyActionSerializer,
            }

            @action
            def my_action:
                ...

        If there's no entry for that action then just fallback to the regular
        get_serializer_class lookup: self.serializer_class, DefaultSerializer.

        Thanks gonz: http://stackoverflow.com/a/22922156/11440

        """
        try:
            return self.serializer_action_classes[self.action]
        except (KeyError, AttributeError):
            return super(MultiSerializerViewSetMixin, self).get_serializer_class()

Note: I am not the author of this answer and code, this is just an answer to a problem I have with DRF, and I think it's worth looking at this as an enhancement.

dyve commented 7 years ago

This might relate to #2062, and even be a partial solution for that issue.

ekzobrain commented 7 years ago

Yes, the problem is very actual. Currently I have to create a new serializer per any viewset action, because serializer fields most of the time differ for each method:

  1. List - a short summary of fields to display in a table, most often with nested relations
  2. Retrieve - get all model fields, most often with nested relations
  3. Create - all models fields, but without nested relations, because when we pass model data to api from a form - we most often pass relations as primary keys, retrieved from "select" elements.
  4. Update - mostly the same as create.

And then configure an appropriate serializer to be used for certain viewset actions. Suggested approach would make it easier than now, but I would advise a little bit different way: We should try to create just one Serializer and one Viewset per Model. So Serializer should be more flexible with "fields" configuration out of the box. This can be achieved rof example this way:

Allow to configure several lists of fields in the serilizer. For example:

class MyModelSerializer(serializers.ModelSerializer):
    class Meta:
        list_fields: [...],
        retrieve_fields: [...],
        create_fields: [...]
        fields: [...] # default fallback

Fields here are specified as "viewset action"_fields. So appropriate fields will be invoked for viewset actions. Allow to configure several Serializer fields per one Model field. For example (asume that "relation" is a gjango model foreign key field):

class MyModelSerializer(serializers.ModelSerializer):
    relation_obj = RelationSerializer(model_field='relation', action='list')

    class Meta:
        list_fields: ['relation_obj', 'relation'],
        retrieve_fields: ['relation', ...],
        create_fields: ['relation', ...]
        fields: [...] # default fallback

In this example list action will return "relation" primary key and the whole "relation_obj" object, as specified in RelationSerializer "list_fields" meta property.

dyve commented 7 years ago

@soulhunter1987 That solution is a bit more complex than what I'd like to see (and suggested).

tomchristie commented 7 years ago

I'd like to see someone tackle this as a third party mixin class rather than bringing in anything else to core right now.

gregschmit commented 5 years ago

Hey @tomchristie, I wrote something that I think fixes this (drf-action-serializer) but rather than making a new ViewSet, I created a ModelActionSerializer that inspects the view action and renders different fields based on the action, and defaults to the normal fields/exclude/extra_kwargs. It accepts a property in Meta called action_fields. If you want to see how it works, I explain it in more detail here: https://stackoverflow.com/questions/37061718/django-rest-framework-hide-specific-fields-in-list-display/57244475#57244475

gregschmit commented 5 years ago

@tomchristie I am considering opening a pull request to pull this functionality into the ModelSerializer since it doesn't break anything and is really a simple change in two methods of that serializer. If I do that and write tests, would you consider pulling it in?

devmgod commented 2 years ago

IMO, I'd like to show Data structure.