I am beginning to use the Django Rest Framework and making a user registration process. I have used this to create a rudimental version and it works fine, but I get the hashed password back in my response, which I don't want. Tried using write_only_fields, but that made no difference.
This is my current serializer:
class UserSerializer(serializers.ModelSerializer):
def create(self, validated_data):
user = User(email=validated_data['email'], username=validated_data['username'])
user.set_password(validated_data['password'])
user.save()
return user
class Meta:
model = User
fields = ('id', 'username', 'email', 'password',)
write_only_fields = ('password',)
How can I prevent DRF to return the created password in the response?
Declare the password field explicitly like this and rest of the code will remain same:
password = serializers.CharField(write_only=True)
Other method can be to delete the password from the to_representation method:
def to_representation(self, instance):
ret = super(MySerializer, self).to_representation(instance)
del ret['password']
return ret
You may use different serializers for creating a user and for showing the user's data. For example, you may inherit from the basic UserSerializer class and thus create something like ReadOnlyUserSerializer, where you completely remove the password field from the Meta.fields property.
The only thing you will need to do is to switch between these serializers properly in ViewSets or whatever you use to render the output.
Related
Here's a serializer for registering a user.
class RegisterSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email', 'password')
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = User.objects.create_user(validated_data['username'], validated_data['email'], validated_data['password'])
return user
Here's the api view:
class RegisterView(generics.GenericAPIView):
serializer_class = RegisterSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
# "token": AuthToken.objects.create(user)[1]
})
On the api view, if I try to pass in a name that is exactly the same as an existing name, it will say that it already exists. However, I can still make the emails the same which I don't want. Is there a way to get it so that I can also tell DRF that I would like the email to have to be unique as well?
There are 2 options:
Enforcing the unique criterion on the serializer level:
from rest_framework import serializers
from rest_framework.validators import UniqueValidator
class RegisterSerializer(serializers.ModelSerializer):
email = serializers.EmailField(
validators=[UniqueValidator(queryset=User.objects.all())]
) # recommend using `get_user_model`
class Meta:
model = User # recommend using `get_user_model`
...
...
Using a custom User model that enforces the unique criterion on the model level. More details here:
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
email = models.EmailField(unique=True)
Note: The 2nd option also requires making changes to the settings and potentially other areas of the code if you reference the User model directly in your other models. If you are directly using User in other areas of your code, please take a look at using get_user_model.
Since you are using the ModelSerializer I think you can achieve it by having emails as unique field in the model itself and the serializer will handle the validation part for you.
I'm trying to use Django Rest Framework 3.1.1 to create a user in a POST. I need to use the built-in method to create the encrypted password so I've tried to override the save method on the ModelSerializer but I clearly don't know Django / DRF well enough to do this. Is there a straightforward way to accomplish this?
When I try the code below, I get an error:
unbound method set_password() must be called with User instance as first argument (got unicode instance
instead)
from django.contrib.auth.models import User
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
def save(self):
email = self.validated_data['email']
username = self.validated_data['username']
password = User.set_password(self.validated_data['password'])
Try doing something like this instead:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('email', 'username', 'password')
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = User(
email=validated_data['email']
username=validated_data['username'],
)
user.set_password(validated_data['password'])
user.save()
return user
Since you are using a ModelSerializer, you can override the perform_create() function in your view and then set the password for the user.
DRF has provided this hook to add some custom actions which should occur before or after saving an object.
As per DRF3 documentation:
These override points are particularly useful for adding behavior
that occurs before or after saving an object, such as emailing a
confirmation, or logging the update.
from django.contrib.auth.hashers import make_password
class MyView(..):
...
def perform_create(self, serializer):
hashed_password = make_password(serializer.validated_data['password']) # get the hashed password
serializer.validated_data['password'] = hashed_password
user = super(MyView, self).perform_create(serializer) # create a user
Since, Django stores the password in hashed format and not as raw passwords, we use Django's make_password() to get the raw password in hashed format. We then set the password in validated_data of the serializer to this hashed password. This hashed password is then used by the serializer when creating the user by calling the super().
I wanted to make email field unique in default django User model. So i made it unique_together=[('email',)] . Now in serializer I want it to be a read_only field.
But Django Rest Framework 3.0 docs says:
There is a special-case where a read-only field is part of a
unique_together constraint at the model level. In this case the field
is required by the serializer class in order to validate the
constraint, but should also not be editable by the user.
The right way to deal with this is to specify the field explicitly on
the serializer, providing both the read_only=True and default=…
keyword arguments.
One example of this is a read-only relation to the currently
authenticated User which is unique_together with another identifier.
In this case you would declare the user field like so:
user = serializers.PrimaryKeyRelatedField(read_only=True,
default=serializers.CurrentUserDefault())
serializers.CurrentUserDefault() represents the current user.I want to set default as user's email . Isn't serializers.CurrentUserDefault() equivalent to request.user . serializers.CurrentUserDefault().email is giving error 'CurrentUserDefault' object has no attribute 'email' How to set email default as user's email ?
This is what the documentation of CurrentUserDefault says:
A default class that can be used to represent the current user. In
order to use this, the 'request' must have been provided as part of
the context dictionary when instantiating the serializer.
You can either do that or you can provide the email id passed in the context data in your views. Override the function get_serializer_context
def get_serializer_context(self):
context = super(YourClass, self).get_serializer_context()
context['email'] = request.user.email
return context
in your views. Your view should be extended from GenericAPIView at some level of inheritance. Now in your serializer, override your __init__ and get your email data.
def __init__(self, instance = None, data = serializers.empty, *args, **kwargs):
self.email = kwargs['context']['email']
Now you can use that in your serializer.
Please check solution 2 from this answer
The code can be:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'email',)
extra_kwargs = {
'email': {'read_only': True},
}
def create(self, validated_data):
"""Override create to provide a email via request.user by default."""
if 'email' not in validated_data:
validated_data['email'] = self.context['request'].user.email
return super(UserSerializer, self).create(validated_data)
hope it help :)
I'm trying to create and manage a custom user in django.
I saw there are two possibilities, and i've chosen to extend (not create a new auth).
Models
models.py
class Artists(models.Model):
user = models.OneToOneField(User)
artist_image = models.ImageField(null=True, blank=True, upload_to="/artist_image/")
def __str__(self):
return 'Profil de {0}'.format(self.username)
def get_absolute_url(self):
return reverse('artist-details', kwargs={'pk': self.pk})
As i read in doc, I just make a OneToOne field with the User class of django auth models, so I can access method and properties, such as username, email, on my own user (here Artists).
form.py
class CreateArtistForm(UserCreationForm):
class Meta:
model = User
fields = ('username', 'email')
def save(self, commit=True):
user = super(CreateArtistForm, self).save(commit=False)
user.email = self.cleaned_data['email']
if commit:
user.save()
return user
Here I extend UserCreationForm to prepare a form a little different (I want to have email field on my register form).
But here is my question : I first tried with
class Meta:
model = Artists
fields = ('user.username', 'user.email')
But I the error fields unknown in model Artist.
So I tried just with username and email and same error.
So I changed the model = Artists to User, and it works fine.
But now how i register my Artist Object when the user is saved?
Do I have to make something like (in save()):
artist = Artists()
artist.user = user
artist.save()
Or override create_user()?
I'm quite lost here and i'm looking docs and questions not able to find something because most of example people define their own auth.
Thanks in advance
Besta
edit : i'm using django 1.8.2 and python 3.4
Using django-rest-framework 3 and django 1.8
I am trying to create a user using django-rest-framework ModelViewSerializer. problem is that the default objects.create method used by DRF leave the password as plain text.
The problem is that DRF serialzer create method is using objects.create querysets/#create method instead of using objects.create_user method.
code from serializers.py line 775
instance = ModelClass.objects.create(**validated_data)
What is the best solution for this? i can override the serializer.create method to use objects.user_create instead of objects.create but it does not feel like the right solution.
rest of code:
from django.contrib.auth.models import User
from rest_framework import viewsets
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'email','password')
write_only_fields = ('password',)
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer = UserSerializer()
you can override create in UserSerializer:
class UserSerializer(serializers.ModelSerializer):
# ....
def create(self, validated_data):
user = User.objects.create_user(**validated_data)
return user
other solutions can be overriding perform_create in ViewSet class or you can write your own create method in your viewset class
class UserViewSet(viewsets.ModelViewSet):
def create(self, request, format=None):
# create user here
# do not call seriailzer.save()
UPDATE: after #freethebees commented, overriding perform_create also works, so here is the code snippet:
class UserViewSet(viewsets.ModelViewSet, mixins.CreateModelMixin):
def perform_create(self, serializer):
# use User.objects.create_user to create user
pass
NOTE:
this answer gives 3 solutions, choose the one you think it better fits your needs and your project's ecosystem
NOTE 2
I personally prefer overriding create in UserViewSet (second code snippet) because there you can simply return your custom Response (for example return user profile after login)
In addition to #aliva's answer where you miss out on the functionalities in serializers.Modelserializer.create() (which could be quite nice to preserve, for example handling of many-to-many relations), there is a way to keep this.
By using the user.set_password() method, the password can also be correctly set, like:
class UserSerializer(serializers.ModelSerializer):
def create(self, validated_data):
user = super().create(validated_data)
user.set_password(validated_data['password'])
user.save()
return user
This has the benefit of keeping the super class' functionality, but the downside of an additional write to the database. Decide which trade-off is more important to you :-).
See documentation for set_password.
There is even better option to validate password in serializer
from django.contrib.auth.hashers import make_password
class UserSerializer(serializers.ModelSerializer):
def validate_password(self, value: str) -> str:
return make_password(value)
A complete example that support POST and PUT/PATCH without another SQL UPDATE statement.
class MyUserSerializer(serializers.ModelSerializer):
class Meta:
model = models.User
fields = '__all__'
def create(self, validated_data):
if "password" in validated_data:
from django.contrib.auth.hashers import make_password
validated_data["password"] = make_password(validated_data["password"])
return super().create(validated_data)
def update(self, instance, validated_data):
if "password" in validated_data:
from django.contrib.auth.hashers import make_password
validated_data["password"] = make_password(validated_data["password"])
return super().update(instance, validated_data)