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>
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?
SerializerMethodField is a read only field and therefore it doesn't even have a to_internal_value method.
@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>
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.
Seconding @maryokhin's comment above.
"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.
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())
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 }
Most helpful comment
Does not seem cleaner to me, especially, because I had to override
__init__without calling direct parent method (this is becausekwargs['read_only'] = Trueis used instead of more compatiblekwargs.setdefault('read_only', True)- developers who know what they are doing could benefit from it).Usage example: