Skip to content
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

SerializerMethodField also supporting a setter #2734

Closed
glemmaPaul opened this issue Mar 20, 2015 · 7 comments
Closed

SerializerMethodField also supporting a setter #2734

glemmaPaul opened this issue Mar 20, 2015 · 7 comments

Comments

@glemmaPaul
Copy link

A good enhancement (in my opinion) will be a possibility to also have a setter in the SerializerMethodField.

So of only having get_<field_name> an option to have set_<field_name>

Proposed solution

Set it as a possible kwarg as in setter="set_something". When the SerializerMethodField's to_internal_value is fired it can check if this setter kwarg is set up and fire this function:

something = SerializerMethodField(setter="set_something")


def set_something(self, value, obj):
   obj.value = value
   return obj

Just an enhancement I thought would be nice to have, any thoughts?

@maryokhin
Copy link
Contributor

SerializerMethodField is a read only field and therefore it doesn't even have a to_internal_value method.

@glemmaPaul
Copy link
Author

@maryokhin I get that, maybe my explanation is not very clear, what I would like to achieve is that there is also a setter available inside the Serializer.

It doesn't have to be the SerializerMethodField itself, maybe a new Field that extends for SerializerMethodField and makes it possible to have a set_<field_name>

@maryokhin
Copy link
Contributor

Ah, okay, I didn't understand the whole picture then.

I would say that if you need both input and output for a field then the intent is cleaner to implement your own field with to_internal_value and to_representation, but that's just my opinion of course, maybe the setter approach does make sense in some use-cases.

@tomchristie
Copy link
Member

Seconding @maryokhin's comment above.

@dmugtasimov
Copy link
Contributor

"I would say that if you need both input and output for a field then the intent is cleaner to implement your own field with to_internal_value and to_representation" - or implement your own DRF :). I also need this feature. I can use setter (point source to it) in most of the cases, but when I need data from serializer context (for example self.context['request'].user) it is not longer enough. Having set_<field_name> would be really nice.

@dmugtasimov
Copy link
Contributor

dmugtasimov commented Mar 29, 2019

Does not seem cleaner to me, especially, because I had to override __init__ without calling direct parent method (this is because kwargs['read_only'] = True is used instead of more compatible kwargs.setdefault('read_only', True) - developers who know what they are doing could benefit from it).

class WritableSerializerMethodField(serializers.SerializerMethodField):

    def __init__(self, method_name=None, **kwargs):
        self.method_name = method_name
        self.setter_method_name = kwargs.pop('setter_method_name', None)
        self.deserializer_field = kwargs.pop('deserializer_field')

        kwargs['source'] = '*'
        super(serializers.SerializerMethodField, self).__init__(**kwargs)

    def bind(self, field_name, parent):
        retval = super().bind(field_name, parent)
        if not self.setter_method_name:
            self.setter_method_name = f'set_{field_name}'

        return retval

    def to_internal_value(self, data):
        value = self.deserializer_field.to_internal_value(data)
        method = getattr(self.parent, self.setter_method_name)
        method(value)
        return {}

Usage example:

class UserJobSerializer(CustomModelSerializer):

    status = WritableSerializerMethodField(
        deserializer_field=serializers.ChoiceField(JOB_STATUS_CHOICES))

    def get_status(self, obj):
        return obj.get_attr_for_user('status', self.get_current_user())

    def set_status(self, value):
        self.instance.set_status_for_user(value, self.get_current_user())

@aj07mm
Copy link

aj07mm commented Jun 25, 2020

This is another implementation:

class ReadWriteSerializerMethodField(serializers.Field):
    def __init__(self, method_name=None, **kwargs):
        self.method_name = method_name
        kwargs['source'] = '*'
        #kwargs['read_only'] = True
        super(ReadWriteSerializerMethodField, self).__init__(**kwargs)

    def bind(self, field_name, parent):
        self.field_name = field_name
        # In order to enforce a consistent style, we error if a redundant
        # 'method_name' argument has been used. For example:
        # my_field = serializer.SerializerMethodField(method_name='get_my_field')
        default_method_name = 'get_{field_name}'.format(field_name=field_name)
        assert self.method_name != default_method_name, (
            "It is redundant to specify `%s` on SerializerMethodField '%s' in "
            "serializer '%s', because it is the same as the default method name. "
            "Remove the `method_name` argument." %
            (self.method_name, field_name, parent.__class__.__name__)
        )

        # The method name should default to `get_{field_name}`.
        if self.method_name is None:
            self.method_name = default_method_name

        super(ReadWriteSerializerMethodField, self).bind(field_name, parent)

    def to_representation(self, value):
        method = getattr(self.parent, self.method_name)
        return method(value)

    def to_internal_value(self, data):
        return { self.field_name: data }

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

No branches or pull requests

5 participants