117

I created a ModelSerializer and want to add a custom field which is not part of my model.

I found a description to add extra fields here and I tried the following:

customField = CharField(source='my_field')

When I add this field and call my validate() function then this field is not part of the attr dict. attr contains all model fields specified except the extra fields. So I cannot access this field in my overwritten validation, can I?

When I add this field to the field list like this:

class Meta:
    model = Account
    fields = ('myfield1', 'myfield2', 'customField')

then I get an error because customField is not part of my model - what is correct because I want to add it just for this serializer.

Is there any way to add a custom field?

ivanleoncz
  • 9,070
  • 7
  • 57
  • 49
Ron
  • 22,128
  • 31
  • 108
  • 206
  • Could you expand on "But when my field is not in the model field list specified in the serializer it is not part of the validate() attr-dictionary.", I'm not sure that's very clear. – Tom Christie Jan 29 '13 at 13:27
  • Also "it complains - correctly - that I don't have a field customField in my model.", could you be explicit about the exception your seeing - thanks! :) – Tom Christie Jan 29 '13 at 13:27
  • I updated my post and hope it's clearer now. I just want to know how I can add a field which is not part of my model... – Ron Jan 29 '13 at 13:36
  • [How to extend model on serializer level with django-rest-framework](http://stackoverflow.com/questions/13471083/how-to-extend-model-on-serializer-level-with-django-rest-framework) – Sjoerd Oct 24 '14 at 09:01
  • 1
    Possible duplicate of [Django REST Framework: adding additional field to ModelSerializer](http://stackoverflow.com/questions/18396547/django-rest-framework-adding-additional-field-to-modelserializer) – Guillaume Vincent Jun 30 '16 at 14:21

8 Answers8

116

In fact there a solution without touching at all the model. You can use SerializerMethodField which allow you to plug any method to your serializer.

class FooSerializer(ModelSerializer):
    foo = serializers.SerializerMethodField()

    def get_foo(self, obj):
        return "Foo id: %i" % obj.pk
Idaho
  • 1,355
  • 1
  • 9
  • 10
  • 10
    As OP mentioned in [this comment](https://stackoverflow.com/questions/14583816/django-rest-framework-how-to-add-custom-field-in-modelserializer/29475731#comment20355742_14584287), they want a writable field, which `SerializerMethodField`s are not – esmail Mar 06 '19 at 17:13
  • 1
    can you also have a `create_foo` method? – Enzo Dtz Feb 12 '22 at 01:22
67

You're doing the right thing, except that CharField (and the other typed fields) are for writable fields.

In this case you just want a simple read-only field, so instead just use:

customField = Field(source='get_absolute_url')
Tom Christie
  • 33,394
  • 7
  • 101
  • 86
  • 4
    thanks, but i want a writeable field. I pass a certain user token which identifies my user. but in my model i have the user and not the token. so i want to pass the token and "convert" it to a user object via a query. – Ron Jan 29 '13 at 13:40
  • the next thing is that source needs to target a model attribute, right? in my case i dont have an attribute to point at. – Ron Jan 29 '13 at 13:41
  • I don't understand the user/token bit of the comment. But if you want to include a field in the serializer that gets stripped out before you restore into a model instance, you could use the `.validate()` method to remove the attribute. See: http://django-rest-framework.org/api-guide/serializers.html#validation That'd do what you need, though I don't really understand the use-case, or why you want a **writable** field that's tied to a **read-only** property `get_absolute_url`? – Tom Christie Jan 29 '13 at 13:47
  • forget about the `get_absolute_url` I just copy&paste'd it from the docs. I just want a normal writable field which I can access in the `validate()`. I just guessed that I'd need the `source` attribute... – Ron Jan 29 '13 at 13:49
  • That makes more sense. :) The value should be getting passed through to validate, so I'd double check how you're instantiating the serializer, and if that value really is being provided. – Tom Christie Jan 29 '13 at 14:35
  • thanks! could you maybe provide a small code sample for the case that `customField` is not in the `fields()` array but is still accessable in the `validate()` function? – Ron Jan 29 '13 at 14:47
  • Great answer, but in my case I needed to use `ReadOnlyField` instead of `Field` – Daniel May 23 '18 at 11:28
  • If you're using a `ModeSerializer` and you want the str of a model in your api response, you can use `custom_field = serializers.CharField(source='__str__')` – Kalob Taulien Apr 06 '20 at 20:12
21

...for clarity, if you have a Model Method defined in the following way:

class MyModel(models.Model):
    ...

    def model_method(self):
        return "some_calculated_result"

You can add the result of calling said method to your serializer like so:

class MyModelSerializer(serializers.ModelSerializer):
    model_method_field = serializers.CharField(source='model_method')

p.s. Since the custom field isn't really a field in your model, you'll usually want to make it read-only, like so:

class Meta:
    model = MyModel
    read_only_fields = (
        'model_method_field',
        )
Lindauson
  • 2,963
  • 1
  • 30
  • 33
  • 3
    What if I want it to be writable? – Csaba Toth Aug 21 '15 at 22:45
  • 1
    @Csaba: You'll just need to write custom save and deletion hooks for the additional content: See "Save and deletion hooks" under "Methods" ([Here](http://www.django-rest-framework.org/api-guide/generic-views/#methods)) You'll need to write custom `perform_create(self, serializer)`, `perform_update(self, serializer)`, `perform_destroy(self, instance)`. – Lindauson Aug 24 '15 at 14:18
16

After reading all the answers here my conclusion is that it is impossible to do this cleanly. You have to play dirty and do something hadkish like creating a write_only field and then override the validate and to_representation methods. This is what worked for me:

class FooSerializer(ModelSerializer):

    foo = CharField(write_only=True)

    class Meta:
        model = Foo
        fields = ["foo", ...]

    def validate(self, data):
        foo = data.pop("foo", None)
        # Do what you want with your value
        return super().validate(data)

    def to_representation(self, instance):
        data = super().to_representation(instance)
        data["foo"] = whatever_you_want
        return data
Ariel
  • 3,383
  • 4
  • 43
  • 58
  • If you set `write_only=True` then why do you also have `def to_representation`? it sounds like you want both read and write.... – chris Frisina Apr 21 '21 at 05:42
14

here answer for your question. you should add to your model Account:

@property
def my_field(self):
    return None

now you can use:

customField = CharField(source='my_field')

source: https://stackoverflow.com/a/18396622/3220916

Community
  • 1
  • 1
va-dev
  • 157
  • 1
  • 7
13

To show self.author.full_name, I got an error with Field. It worked with ReadOnlyField:

class CommentSerializer(serializers.HyperlinkedModelSerializer):
    author_name = ReadOnlyField(source="author.full_name")
    class Meta:
        model = Comment
        fields = ('url', 'content', 'author_name', 'author')
François Constant
  • 5,531
  • 1
  • 33
  • 39
10

I was looking for a solution for adding a writable custom field to a model serializer. I found this one, which has not been covered in the answers to this question.

It seems like you do indeed need to write your own simple Serializer.

class PassThroughSerializer(serializers.Field):
    def to_representation(self, instance):
        # This function is for the direction: Instance -> Dict
        # If you only need this, use a ReadOnlyField, or SerializerField
        return None

    def to_internal_value(self, data):
        # This function is for the direction: Dict -> Instance
        # Here you can manipulate the data if you need to.
        return data

Now you can use this Serializer to add custom fields to a ModelSerializer

class MyModelSerializer(serializers.ModelSerializer)
    my_custom_field = PassThroughSerializer()

    def create(self, validated_data):
        # now the key 'my_custom_field' is available in validated_data
        ...
        return instance

This also works, if the Model MyModel actually has a property called my_custom_field but you want to ignore its validators.

HosseyNJF
  • 491
  • 1
  • 6
  • 18
David Schumann
  • 13,380
  • 9
  • 75
  • 96
  • so it does not work if my_custom_field is not a property of MyModel right ? I got the error The serializer field might be named incorrectly and not match any attribute or key on the `MyModel` instance. – Sandeep Balagopal Sep 16 '20 at 09:18
8

With the last version of Django Rest Framework, you need to create a method in your model with the name of the field you want to add.

class Foo(models.Model):
    . . .
    def foo(self):
        return 'stuff'
    . . .

class FooSerializer(ModelSerializer):
    foo = serializers.ReadOnlyField()

    class Meta:
        model = Foo
        fields = ('foo',)
Guillaume Vincent
  • 13,355
  • 13
  • 76
  • 103