rsinger86 / drf-flex-fields

Dynamically set fields and expand nested resources in Django REST Framework serializers.
MIT License
740 stars 61 forks source link

Support for expandable `SerializerMethodFields` #54

Open J-Priebe opened 4 years ago

J-Priebe commented 4 years ago

Hello, I have a few large SerializerMethodFields on my serializer, which return iterables that are not a FK to my model. Something like this:

Class FooSerializer(FlexFieldsModelSerializer):
    things = SerializerMethodField()

    def get_things(self, instance):
        return ThingSerializer(get_things_related_to_this_instance(instance), many=True).data

As far as I know I cannot directly leverage drf-flex-fields to make this field expandable, because expandable_fields are a statically defined dict that takes a serializer class or tuple.

I have hacked around this by making these fields omitted by default, unless declared in the expand argument:

def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)

    expandable_method_fields = ('things', 'more_things')

    if '~all' in self._flex_options['expand'] or '*' in self._flex_options['expand']:
        return

    for field in expandable_method_fields:
        if (
            field not in self._flex_options['expand']
            and field not in self._flex_options['omit']
        ):
            self._flex_options['omit'].append(field)

It works well enough for my purposes, but I thought I'd pitch this as a feature request, in case it is helpful or others have solved the problem differently. Cheers, and thanks for the library!

rsinger86 commented 4 years ago

I like the idea of supporting DRF's SerializerMethodField as an expandable field. One thing, though, is that it doesn't take keyword arguments for expand, omit, fields. This would only matter if you wanted dynamic behavior within that method.

class UserSerializer(serializers.ModelSerializer):
    friends = HyperlinkFIeld()

    class Meta:
        model = User
        expandable_fields = {'friends': serializer.SerializerMethodField}

    def get_friends(self, obj, expand, fields, omit):
        # serialize friends using custom logic

We could add a custom FlexSerializerMethodField - a subclass of SerializerMethodField that accepts those keyword arguments. Another possibility is making it a little smarter so it doesn't try to pass the keyword arguments if the expanded field is an instance of SerializerMethodField.

vikrambombhi commented 3 years ago

Is it not possible to do something like: FooSerializer.Meta.exclude = ("friends", ) and ask for it in query params if we want it /route?fields=friends ?

I think this breaks the model of expanding a field. Getting back the non expanded version of friends is different from not getting anything at all. I really like the idea of having something like FlexSerializerMethodField as @rsinger86 mentioned. Are there any plans on getting something like this implemented?

michaelschem commented 2 years ago

One workaround that I've used is to convert your method field to an annotation on the queryset and expand on that. It also has the benefit of being much faster in most cases.