Adding addition field in Django rest framework modelserializer (write_only && read_write) in mixin

Viewed 413

I want to create a mixin for additional fields in Modelserializer. Please refer the code below

class AdditionalFieldsMixin(object):
    additional_fields = dict()

    def __init__(self, *args, **kwargs):
        super(AdditionalFieldsMixin, self).__init__(*args, **kwargs)
        for field_name, field_instance in self.additional_fields.items():
            self.fields[field_name] = field_instance
        print(self.fields)

    def additional_field_before_create(self, additional_field_data):
        pass

    def additional_field_after_create(self, additional_field_data, instance):
        pass

    def additional_field_before_update(self, additional_field_data):
        pass

    def additional_field_after_update(self, additional_field_data, instance):
        pass

    def create(self, validated_data):
        additional_field_data = {}

        for additional_field in self.additional_fields.keys():
            additional_field_data[additional_field] = validated_data.pop(additional_field, None)

        self.additional_field_before_create(additional_field_data)
        instance = super(AdditionalFieldsMixin, self).create(validated_data)
        self.additional_field_after_create(additional_field_data, instance)
        return instance

    def update(self, instance, validated_data):
        additional_field_data = {}

        for additional_field in self.additional_fields.keys():
            additional_field_data[additional_field] = validated_data.pop(additional_field, None)

        self.additional_field_before_update(additional_field_data)
        instance = super(AdditionalFieldsMixin, self).update(instance, validated_data)
        self.additional_field_after_update(additional_field_data, instance)
        return instance

In main class I declared

additional_fields = dict(
        reference=CharField(write_only=True, allow_blank=True, allow_null=True, default=None),
    )

but when retrieving the data in list mode the it gives me an error

AssertionError: It is redundant to specify `source='reference'` on field 'CharField' in serializer 'MySerializer', because it is the same as the field name. Remove the `source` keyword argument.

why this is happening ? also i noted that after starting runserver first time it doesn't give that error but if I query again it gives error second time and so on.

and another general question

what is the proper way to add fields in seialiser mixin class ? i.e self.fields[field_name] = field_instance is right way to do it?

2 Answers

You can do this, because if you use a modelerializer you can't request

class Name(serializers.Serializer):
    name = serializers.CharField(required=True)
    ...
    
    def create(self, validated_data):
        ...
    def update(self, instance, validated_data):
        ...

Extra read and write fields on Modelserializer

The extra read and write fields must be derived from model instance so you can add @property or def reference on model and declare field on serializer as

reference = CharField(source='reference')

and pop the field data from validated data overriding create update of modelserializer.

In short, extra read and write field is very difficult to implement in straight forward code, BUT IT IS POSSIBLE.

Extra read only fields on Modelserializer

Use the SerializerMethodField, ReadOnlyField with source argument or similar to above code with read_only=True argument. e.g

reference = CharField(source='reference', read_only=True)

Extra write only fields on Modelserializer (Required Answer)

Writing field on mainserializer as

reference = CharField(write_only=True)

is fine the field will show in validated data in create and update so override them, pop the field, do what you what to do with it, make super() call with new validated data.

Make a mixin as asked in question (for write only fields)

Turns out you can override the to_internal_value method of serializer and add fields to it. the to_internal_value is called when the serializer is saving data (creating, updating) so you can override it as follows

def to_internal_value(self, data):
    for field_name, field_instance in self.additional_write_only_fields.items():
        self.fields[field_name] = field_instance
    return super(AdditionalWriteOnlyFieldsMixin, self).to_internal_value(data)

this would solve the problem of adding many write only fields to model serializer. thats why I created the mixin as follows

class AdditionalWriteOnlyFieldsMixin(object):
    additional_write_only_fields = dict()

    def to_internal_value(self, data):
        for field_name, field_instance in self.additional_write_only_fields.items():
            self.fields[field_name] = field_instance
        return super(AdditionalWriteOnlyFieldsMixin, self).to_internal_value(data)

    def additional_fields_before_create(self, additional_data):
        pass

    def additional_fields_after_create(self, additional_data, instance):
        pass

    def additional_fields_before_update(self, additional_data):
        pass

    def additional_fields_after_update(self, additional_data, instance):
        pass

    def create(self, validated_data):
        additional_data = {key: validated_data.pop(key, None) for key in self.additional_write_only_fields.keys()}
        self.additional_fields_before_create(additional_data)
        instance = super(AdditionalWriteOnlyFieldsMixin, self).create(validated_data)
        self.additional_fields_after_create(additional_data, instance)
        return instance

    def update(self, instance, validated_data):
        additional_data = {key: validated_data.pop(key, None) for key in self.additional_write_only_fields.keys()}
        self.additional_fields_before_update(additional_data)
        instance = super(AdditionalWriteOnlyFieldsMixin, self).update(instance, validated_data)
        self.additional_fields_after_update(additional_data, instance)
        return instance

So include the mixin in your modelserializer class and declare the additional_write_only_fields as dict with key as field name and value as field instance and override additional_fields_* function to implement your desired functionality e.g.

class MySerializer(AdditionalWriteOnlyFieldsMixin, ModelSerializer):
    additional_write_only_fields = dict(
        reference=CharField(write_only=True, allow_blank=True, allow_null=True, default=None),
    )
    
    def additional_fields_after_create(self, additional_data, instance):
        print(additional_data)
        
    class Meta:
        model=MyModel
        fields='__all__'

Additional Notes

You can also override the to_representation method of the serializer for adding read only fields or make a combination of to_representation and to_internal_value to make additional read write field!. but I leave this job for another answer-er.

References

Related