简体   繁体   中英

Django Rest Framework how to forbid users to change their username?

I'm creating UserSerializer and want to allow users to create new accounts but forbid them to change their usernames. There is a read_only attribute that I can apply but then users won't be able to set a username when creating a new one. But without that It allows me to change it. There is also a required attribute which unfortunately cannot be used with read_only. There is no other relevant attribute. One solution is to create 2 different Serializers one for creating User and another from changing him, but that seems the ugly and wrong thing to do. Do you have any suggestions on how to accomplish that without writing 2 serializers?

Thanks for any advice.

PS: I'm using python3.6 and django2.1

EDIT: I'm using generics.{ListCreateAPIView|RetrieveUpdateDestroyAPIView} classes for views. Like this:

class UserList(generics.ListCreateAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer


class UserDetails(generics.RetrieveUpdateAPIView):
    # this magic means (read only request OR accessing user is the same user being edited OR user is admin)
    permission_classes = (perm_or(ReadOnly, perm_or(IsUserOwner, IsAdmin)),)

    queryset = User.objects.all()
    serializer_class = UserSerializer

EDIT2: There is a duplicate question (probably mine is duplicate) here

Assuming you are using a viewset class for your view, then you could override the init method of serializer as,

class UserSerializer(serializers.ModelSerializer):
    

    class Meta:
        ....

If you are trying to update the username field while update ( HTTP PUT ) or partial update ( HTTP PATCH ), the serializer will remove the username field from the list of fields and hence it wont affect the data/model

UPDATE
Why the above answer not woking with documentaion API ?

From the doc

Note: By default include_docs_urls configures the underlying SchemaView to generate public schemas. This means that views will not be instantiated with a request instance. ie Inside the view self.request will be None .


In the answer, the fields are pops out dynamically with the help of a request object.
So, If you wish to handle API documentaion also, define multiple serializer and use get_serializer_class() method efficently. That's the DRF way.

Perhaps, one of the possible approaches would be to create a RegistrationSerializer which you use only in registration process/endpoint.

And then, you create another serializer UserSerializer where you make username read_only field and you use this serializer everywhere else ( eg. when updating user).

Anwser from @JPG is pretty accurate, but it has one limitation. You can use the serializer only in DRF views, because in other views or anywhere else the context will not have view.actions. To fix it self.instance can be used. It will make the code shorter and more versatile. Also instead of popping the field its better to make it read only, so that it can still be viewed but cannot be changed.

class UserSerializer(serializers.ModelSerializer):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self.instance is not None:  # if object is being created the instance doesn't exist yet, otherwise it exists.
            # self.fields.pop('username', None)
            self.fields.get('username').read_only = True  # An even better solution is to make the field read only instead of popping it.

    class Meta:
        ....

Another possible solution is to use CreateOnlyDefault() which is a builtin feature in DRF now. You can read more about it here in the docs

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM