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):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if 'view' in self.context and self.context['view'].action in ['update', 'partial_update']:
self.fields.pop('username', None)
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. i.e. 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