Each Answer to this Q is separated by one/two green lines.
I have an object that has optional fields. I have defined my serializer this way:
class ProductSerializer(serializers.Serializer): code = serializers.Field(source="Code") classification = serializers.CharField(source="Classification", required=False)
required=False would do the job of bypassing the field if it doesn’t exist. However, it is mentioned in the documentation that this affects deserialization rather than serialization.
I’m getting the following error:
'Product' object has no attribute 'Classification'
Which is happening when I try to access
.data of the serialized instance. (Doesn’t this mean it’s deserialization that’s raising this?)
This happens for instances that do not have
Classification. If I omit
Classification from the serializer class it works just fine.
How do I correctly do this? Serialize an object with optional fields, that is.
Django REST Framework 3.0+
Dynamic fields now supported, see http://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields — this approach defines all of the fields in the serializer, and then allows you to selectively remove the ones you don’t want.
Or you could also do something like this for a Model Serializer, where you mess around with Meta.fields in the serializer init:
class ProductSerializer(serializers.ModelSerializer): class Meta: model = Product fields = ('code',) def __init__(self, *args, **kwargs): if SHOW_CLASSIFICATION: # add logic here for optional viewing self.Meta.fields = list(self.Meta.fields) self.Meta.fields.append('classification') super(ProductSerializer, self).__init__(*args, **kwargs)
You’d have to ask Tom though if this is the “correct way” since it may not fit in with the long term plan.
Django REST Framework < 3.0
Try something like this:
class ProductSerializer(serializers.Serializer): ... classification = serializers.SerializerMethodField('get_classification') def get_classification(self, obj): return getattr(obj, 'classification', None)
Another approach would be to create multiple serializers with different sets of fields. One serializer inherits from another and adds additional fields. Then you can choose the appropriate serializer in the view with the
get_serializer_class method. Here’s an actual example of how I use this approach to call different serializers to present different user data if the user object is the same as the request user.
def get_serializer_class(self): """ An authenticated user looking at their own user object gets more data """ if self.get_object() == self.request.user: return SelfUserSerializer return UserSerializer
Removing fields from representation
Another approach that I’ve used in security contexts is to remove fields in the
to_representation method. Define a method like
def remove_fields_from_representation(self, representation, remove_fields): """ Removes fields from representation of instance. Call from .to_representation() to apply field-level security. * remove_fields: a list of fields to remove """ for remove_field in remove_fields: try: representation.pop(remove_field) except KeyError: # Ignore missing key -- a child serializer could inherit a "to_representation" method # from its parent serializer that applies security to a field not present on # the child serializer. pass
and then in your serializer, call that method like
def to_representation(self, instance): """ Apply field level security by removing fields for unauthorized users""" representation = super(ProductSerializer, self).to_representation(instance) if not permission_granted: # REPLACE WITH PERMISSION LOGIC remove_fields = ('classification', ) self.remove_fields_from_representation(representation, remove_fields) return representation
This approach is straightforward and flexible, but it comes at the cost of serializing fields that are sometimes not displayed. But that’s probably okay.
The method describe below did the work for me.
Pretty simple,easy and worked for me.
DRF version used = djangorestframework (3.1.0)
class test(serializers.Serializer): id= serializers.IntegerField() name=serializers.CharField(required=False,default="some_default_value")
The serializers are deliberately designed to use a fixed set of fields so you wouldn’t easily be able to optionally drop out one of the keys.
You could use a SerializerMethodField to either return the field value or
None if the field doesn’t exist, or you could not use serializers at all and simply write a view that returns the response directly.
Update for REST framework 3.0
serializer.fields can be modified on an instantiated serializer. When dynamic serializer classes are required I’d probably suggest altering the fields in a custom
DynamicSerializer for DRF 3, which allows dynamicly specifying which fields will be used in serializer, which will be excluded, and optionally which will become required!
- Create Mixin
class DynamicSerializerMixin: """ A Serializer that takes an additional `fields` argument that controls which fields should be used. """ def __init__(self, *args, **kwargs): # Don't pass the 'fields' arg up to the superclass fields = kwargs.pop("fields", None) excluded_fields = kwargs.pop("excluded_fields", None) required_fields = kwargs.pop("required_fields", None) # Instantiate the superclass normally super().__init__(*args, **kwargs) if fields is not None: # Drop any fields that are not specified in the `fields` argument. allowed = set(fields) existing = set(self.fields) for field_name in existing - allowed: self.fields.pop(field_name) if isinstance(fields, dict): for field, config in fields.items(): set_attrs(self.fields[field], config) if excluded_fields is not None: # Drop any fields that are not specified in the `fields` argument. for field_name in excluded_fields: self.fields.pop(field_name) if required_fields is not None: for field_name in required_fields: self.fields[field_name].required = True
- Initialize/adjust your serializer by adding DynamicSerializerMixin to inheritence
class UserProfileSerializer(DynamicSerializerMixin, serializers.ModelSerializer): class Meta: model = User fields = ( "id", 'first_name', 'last_name' "email", "is_staff", )
- Use it 🙂
class RoleInvitationSerializer(serializers.ModelSerializer): invited_by = UserProfileSerializer(fields=['id', 'first_name', 'last_name'])
or in action apis
@action(detail=True, serializer_class=YourSerialzierClass) def teams_roles(self, request, pk=None): user = self.get_object() queryset = user.roles.all() serializer = self.get_serializer(queryset, many=True, excluded_fields=['user']) return Response(data=serializer.data)
Charfield method has a property
By default it is set to False.
Setting it to
True will allow you to mark the field as optional during “serialization”.
This is the code that you should write
classification = serializers.CharField(source="Classification", allow_blank=True)
required property is used for deserialization.
For this purpose the serializers have the
partial argument. If when the serializer is initialized you can pass
partial=True. If you are using generics or mixins you can overrider the
get_serializer function as follows:
def get_serializer(self, *args, **kwargs): kwargs['partial'] = True return super(YOUR_CLASS, self).get_serializer(*args, **kwargs)
And that will do the trick.
Note: This allows all fields to be optional and not only a specific one. If you want only specifics, you can override the method (i.e. update) and add validations of existence for various fields.
What has worked well for me is to set the serializer like so:
classification = serializers.CharField(max_length=20, allow_blank=True, default=None)
From the “it’s a terrible hack relying on specific implementation details of both DRF and Django, but it works (at least for now)” files, here’s the approach I used to include some additional debugging data in the response from a “create” method implementation on a serializer:
def create(self, validated_data) # Actual model instance creation happens here... self.fields["debug_info"] = serializers.DictField(read_only=True) my_model.debug_info = extra_data return my_model
This is a temporary approach that lets me use the browsable API to display some of the raw response data received from a particular remote service during the creation process. In the future, I’m inclined to keep this capability, but hide it behind a “report debugging info” flag in the creation request rather than returning the lower level info by default.