Skip to content

Instantly share code, notes, and snippets.

@koliber
Created September 8, 2015 14:28
Show Gist options
  • Save koliber/ae5c2df1468c2fdcac9a to your computer and use it in GitHub Desktop.
Save koliber/ae5c2df1468c2fdcac9a to your computer and use it in GitHub Desktop.
`ExtensibleModelSerializer` prototype for rest_framework
class ExtensibleModelSerializer(serializers.ModelSerializer):
"""
Allows for specifying ``non_native_fields`` in the Meta, which allow for custom handling
of some fields, while letting the ``ModelSerializer`` do its magic with the rest of the fields.
For any fields which are not read from or set directly on the model using ``ModelSerializer``'s
logic, you need to provide a mechanism for:
* getting the value from the model
* changing the incoming data to an internal representation (for updates and creates)
* saving the internal representation in the appropriate place
There are three ways to get the value from the model for a field. One way is to add a
method named `get_FIELD_NAME`, where `FIELD_NAME` is the name of the non-native field.
This method will be passed an instance of the object being serialized and should return a
representation value, which is ready for serialization.
If you do not wish to implement such a magic method, you can override
the ``get_field_representation`` method instead. If you need to fetch some values together
instead of one-by-one, you can override the ``to_representation_non_native_fields``
method.
Writing values requires two steps. The first step is to convert the incoming data into
an internal representation. After that, all of the internal data is verified using the
serializer's verification mechanism. Lastly, it needs to be saved somewhere.
To change the incoming data into an internal representation, there exist three ways to do it.
They are analogous to the ways of getting the value. One way, is to implement a magic method
named `to_internal_value_FIELD_NAME`, where `FIELD_NAME` is the name of the non-native field.
If you do not wish to implement such a magic method, you can override
the ``get_field_internal_value`` method instead. If you need to convert values together
instead of one-by-one, you can override the ``to_internal_value_non_native_fields``
method.
The last step is to save the data. To do this, override the ``update`` and ``create``
methods in your serializer to handle the additional validated data which was generated
by the `to_internal_value` calls on the non_native_fields.
"""
def save(self, **kwargs):
return super(ExtensibleModelSerializer, self).save(**kwargs)
def to_representation(self, instance):
model_serializer_readable_fields = self._readable_fields
self._readable_fields = [
_f for _f in self._readable_fields if _f.field_name not in self.Meta.non_native_fields
]
ret_data = super(ExtensibleModelSerializer, self).to_representation(instance)
self._readable_fields = model_serializer_readable_fields
non_native_fields = [
_f for _f in self._readable_fields if _f.field_name in self.Meta.non_native_fields
]
non_native_field_data = self.to_representation_non_native_fields(instance,
non_native_fields)
ret_data.update(non_native_field_data)
return ret_data
def to_internal_value(self, data):
model_serializer_writeable_fields = self._writable_fields
self._writable_fields = [
_f for _f in self._writable_fields if _f.field_name not in self.Meta.non_native_fields
]
ret_data = super(ExtensibleModelSerializer, self).to_internal_value(data)
self._writable_fields = model_serializer_writeable_fields
non_native_fields = [
_f for _f in self._writable_fields if _f.field_name in self.Meta.non_native_fields
]
non_native_field_data = self.to_internal_value_non_native_fields(data, non_native_fields)
ret_data.update(non_native_field_data)
return ret_data
def to_internal_value_non_native_fields(self, data, non_native_fields):
ret_data = {}
for field in non_native_fields:
try:
ret_data[field.field_name] = self.get_field_internal_value(field, data)
except SkipField:
continue
return ret_data
def to_representation_non_native_fields(self, instance, non_native_fields):
ret_data = {}
for field in non_native_fields:
try:
ret_data[field.field_name] = self.get_field_representation(field, instance)
except SkipField:
continue
return ret_data
def get_field_representation(self, field, instance):
default_method_name = 'get_{field_name}'.format(field_name=field.field_name)
method = getattr(self, default_method_name, None)
if not method:
raise SkipField
return method(instance)
def get_field_internal_value(self, field, data):
default_method_name = 'to_internal_value_{field_name}'.format(field_name=field.field_name)
method = getattr(self, default_method_name, None)
if not method:
raise SkipField
return method(data)
@contracode
Copy link

Love this, and I'm not only a fan, but I'm a user.

One thing-- since non-native fields are specified in Meta, shouldn't all references to non_native_fields be self.Meta.non_native_fields?

@fidiego
Copy link

fidiego commented Oct 9, 2015

Not sure what I'm doing wrong, but i'm getting an error:
'AccountSerializer' object has no attribute '_readable_fields'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment