Sapnesh Naik
Sapnesh Naik

Reputation: 11656

May not set both `read_only` and `write_only`

In my project I want to set password as both read_only(because I have a separate endpoint to reset password) and write_only(because I don't want password send in the response).

Here is my serializer:

class UserSerializer(serializers.ModelSerializer):
    """A Serizlier class for User """

    class Meta:
        model = models.User
        fields = ('id', 'email', 'phone_number', 'user_type', 'password')
        extra_kwargs = { 'password': { 'write_only': True} }
        read_only_fields = ('password',)

But I get an error saying:

AssertionError at /api/user/21/

May not set both read_only and write_only

How can I have a field be both read_only and write_only?

Upvotes: 3

Views: 5415

Answers (4)

Himalaya Dave
Himalaya Dave

Reputation: 23

You can check the type of method as well:

class UserSerializer(serializers.ModelSerializer):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        if request and request.method in ['POST', 'PUT', 'PATCH']:
            self.fields.get('password').write_only = True
        else:
            self.fields.get('password').read_only = True

Upvotes: 0

PythonWolf
PythonWolf

Reputation: 1195

So extending on the answer of @JPG

The Serializer

class ProfileSerializer(serializers.ModelSerializer):
    name = serializers.CharField()

def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    if 'create_method' in self.context:
        self.fields['name'].write_only = True
    else:
        self.fields['name'].read_only = True

And the view set

class ProfileViewSet(viewsets.ModelViewSet):
    serializer_class = ProfileSerializer

def get_serializer_context(self):

    # Call Parent
    context = super().get_serializer_context()

    # Check If This Is a POST
    if self.request.method == 'POST':
        context['create_method'] = True

    # Handoff
    return context

This will allow name to be written on POST and read only on everything else

Upvotes: 2

Giannis Katsini
Giannis Katsini

Reputation: 1299

well - normally if you have two endpoints using a similar serializer which needs to differ only with a certain field/functionality you would create a base class and abstract it and only change/modify the parts of it that need to change. Here is what I would do.

class (serializers.ModelSerializer):
    """A Serizlier class for User """

    class Meta:
        model = models.User
        fields = ('id', 'email', 'phone_number', 'user_type', 'password')
        extra_kwargs = { 'password': { 'read_only': True} }


class UserSerializerForOtherView(UserSerializer):

    class Meta(UserSerializer.Meta):
        extra_kwargs = { 'password': { 'write_only': True} }

Now UserSerializerForOtherView inherits the same behaviour as UserSerializer and you now also have a new serializer should you want to further expand on functionality on this serializer alone.

All you will need to do is tell the other view to use the other serializer.

Upvotes: 0

JPG
JPG

Reputation: 88689

Override the __init__() method of the serializer as,

class UserSerializer(serializers.ModelSerializer):
    """A Serizlier class for User """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self.context['some_flag']:
            self.fields['password'].read_only = True
        else:
            self.fields['password'].write_only = True

    class Meta:
        model = models.User
        fields = ('id', 'email', 'phone_number', 'user_type', 'password')
        # extra_kwargs = { 'password': { 'write_only': True} } # remove this
        # read_only_fields = ('password',) # remove this

The some_flag variable is something that you should pass to the serializer either from the password reset view or from the other view

Upvotes: 3

Related Questions