122

I have models like this:

class ModelA(models.Model):
    name = models.CharField()


class ModelB(models.Model):
    f1 = models.CharField()
    model_a = models.ForeignKey(ModelA)

Serializers:

class ASerializer(serializers.ModelSerializer):
    model_b_ids = serializers.CharField()
    class Meta:
        model = ModelA
        write_only_fields = ('model_b_ids',)

views:

class AView(CreateModelMixin, GenericViewSet):

    def perform_create(self, serializer): 
        model_b_ids = parse_somehow(serializer.validated_data["model_b_ids"])
        #do something...

The problem I am getting is the with the "model_b_ids"

The user should submit it while sending post data.

I use it in perform_create to link to related models.

But thats not "real column" in ModelA so when I try to save it is raising exception.

I tried to it pop from validated_data but then again getting error somewhere that cannot read model_b_ids from model. Any idea on using this kind of field correctly ?

6 Answers 6

202

The Django Rest Framework does not have Meta attribute write_only_fields anymore

According to their docs you set write-only fields in extra_kwargs

e.g

class UserSerializer(ModelSerializer):
    """
    ``Serializer`` for ``User`` ..
    """

    class Meta:
        model = User
        fields = ('id', 'email', 'first_name', 'last_name' ,'security_question', 'security_question_answer', 'password', 'is_active', 'is_staff')
        read_only_fields = ('is_active', 'is_staff')
        extra_kwargs = {
            'security_question': {'write_only': True},
            'security_question_answer': {'write_only': True},
            'password': {'write_only': True}
        }

Update

As @AKHIL MATHEW highlighted in his answer below

From DRF v3 onwards, setting a field as read-only or write-only can use serializer field core arguments mentioned as follows.

write_only

Set this to True to ensure that the field may be used when updating or creating an instance, but is not included when serializing the representation.

Defaults to False Eg:

company = serializers.PrimaryKeyRelatedField(write_only=True)

Sign up to request clarification or add additional context in comments.

1 Comment

Please find my answer below for the latest approach in the DRF version 3
73

In accordance with the Django REST Framework documentation:

The write_only_fields option on ModelSerializer has been moved to PendingDeprecation and replaced with a more generic extra_kwargs

that's why it's recommended to do like this: you should use extra_kwargs:

extra_kwargs = {
    'model_b_ids': {'write_only': True},
    'another_field': {'read_only': True}
}

or:

 model_b_ids = serializers.IntegerField(write_only=True)

2 Comments

I just updated DRF from 3.1.3 to 3.3.3 and suddenly my Users' passwords were being sent back (I had before the write_only Meta atribute. Your solution fixes this issue. Thanks!
@vabada I hope you mean hashes 😅
14

From DRF v3 onwards, setting a field as read-only or write-only can use serializer field core arguments mentioned as follows.

Core arguments

Each serializer field class constructor takes at least these arguments. Some Field classes take additional, field-specific arguments, but the following should always be accepted:

read_only

Read-only fields are included in the API output but should not be included in the input during create or update operations. Any 'read_only' fields that are incorrectly included in the serializer input will be ignored.

Set this to True to ensure that the field is used when serializing a representation, but is not used when creating or updating an instance during deserialization.

Defaults to False

Eg:

price = serializers.IntegerField(read_only=True)

write_only

Set this to True to ensure that the field may be used when updating or creating an instance, but is not included when serializing the representation.

Defaults to False

Eg:

company = serializers.PrimaryKeyRelatedField(write_only=True)

Comments

9

Probably you're overseeing that your ModelA has the property modelb_set. In Django you describe the relationship in one model class. Django offers a backward relationship by lower-casing the target model and suffixing it with _set. So you could do:

a = ModelA.objects.get(pk=1)
a.modelb_set.all()

This would get the element with ID (or primary key) 1 from ModelA and retrieve all related ModelB elements.

You can set a value for related_name to overwrite the default value:

class ModelB(models.Model):
    f1 = models.CharField()
    model_a = models.ForeignKey(ModelA, related_name='model_b')

In DRF you can slightly adapt your serializer:

class ASerializer(serializers.ModelSerializer):
    model_b = serializers.PrimaryKeyRelatedField(many=True, read_only=False)

    class Meta:
        model = ModelA
        write_only_fields = ('model_b',)

With serializers.CharField() you can't post values and write them to the model, because it isn't a model field.

Give this example a try. Tinker and experiment. It should bring you closer to the solution.

EDIT: I'm not really sure how Django creates the name for backward relationship for PascalCase class names. Is it model_b_set for ModelB? Or is it modelb_set? You can try and find it out.

3 Comments

write_only_fields is no longer valid with Django version 3. Instead they suggested to use extra_kwargs like this- extra_kwargs = { "model_b": {"write_only": True, },}
Please edit your answer base on @xalien comment
According to deprecation list we need to reconsider write_only_fields django-rest-framework.org/community/3.2-announcement/…
8

From docs you can use read_only

Read-only fields are included in the API output, but should not be included in the input during create or update operations. Any 'read_only' fields that are incorrectly included in the serializer input will be ignored.

Set this to True to ensure that the field is used when serializing a representation, but is not used when creating or updating an instance during deserialization.

Defaults to False

As example:

We can use it on Serializer fields:

model_b_ids = serializers.IntegerField(read_only=True)

or we can use it in extra_kwargs:

extra_kwargs = {
    'model_b_ids': {'read_only': True}
}

1 Comment

Ellusive, but this is the way it works in Django 3.1
1

Well you could override the serializer.save() method on ASerializer to instantiate modelA object, set its attributes, save it, then set relations on existing modelB objects, save them as well and drink to success. But I think maybe setting that related_name and RelatedField on serializer as was suggested would do exactly the same thing.... with less typing.. and overall better:)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.