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)
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 have two simple models
class User(AbstractUser):
pass
class Vacation(Model):
id = models.AutoField(primary_key=True)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
I am not really sure what is the scalable way of doing user permissions for Django Rest Framework. In particular:
Users should only be able to see their own vacations
On the /vacation endpoint, user would see a filtered list
On the /vacation/$id endpoint, user would get a 403 if not owner
Users should only be able to Create/Update vacations as long as they are the owners of that object (through Foreign Key)
What is the best way to achieve this in a future-proof fashion. Say if further down the line:
I add a different user type, which can view all vacations, but can only create/update/delete their own
I add another model, where users can read, but cannot write
Thank you!
From the docs:
Permissions in REST framework are always defined as a list of permission classes. Before running the main body of the view each permission in the list is checked. If any permission check fails an exceptions.PermissionDenied or exceptions.NotAuthenticated exception will be raised, and the main body of the view will not run.
REST framework permissions also support object-level permissioning. Object level permissions are used to determine if a user should be allowed to act on a particular object, which will typically be a model instance.
For your current need you can define your own Permission class:
class IsVacationOwner(permissions.BasePermission):
# for view permission
def has_permission(self, request, view):
return request.user and request.user.is_authenticated
# for object level permissions
def has_object_permission(self, request, view, vacation_obj):
return vacation_obj.owner.id == request.user.id
And add this permission to your view. For example on a viewset:
class VacationViewSet(viewsets.ModelViewSet):
permission_classes = (IsVacationOwner,)
One thing is important to notice here, since you will respond with a filtered list for '/vacations', make sure you filter them using the request.user. Because object level permission will not be applicable for lists.
For performance reasons the generic views will not automatically apply object level permissions to each instance in a queryset when returning a list of objects.
For your future requirement, you can always set the permissions conditionally with the help of get_permissions method.
class VacationViewSet(viewsets.ModelViewSet):
def get_permissions(self):
if self.action == 'list':
# vacations can be seen by anyone
# remember to remove the filter for list though
permission_classes = [IsAuthenticated]
# or maybe that special type of user you mentioned
# write a `IsSpecialUser` permission class first btw
permission_classes = [IsSpecialUser]
else:
permission_classes = [IsVacationOwner]
return [permission() for permission in permission_classes]
DRF has great documentation. I hope this helps you to get started and helps you to approach different use cases according to your future needs.
I would suggest you to use drf-viewsets link. We are going to use vacation viewset to do this work.
our urls.py
from your_app.views import VacationViewSet
router.register('api/vacations/', VacationViewSet)
our serializers.py
from rest_framework import serializers
from your_app.models import Vacation
class VacationSerializer(serializers.ModelSerializer):
class Meta:
model = Vacation
fields = ('id', 'owner',)
read_only_fields = ('id',)
our views.py
Here we are going to overwrite viewset's retrive and list method. There are other possible way to do that but i like this most as i can able to see what is happening in code. Django model viewset inherited link of drf-mixins retrive and list method.
from rest_framework import viewsets, permissions, exceptions, status
from your_app.models import Vacation, User
from your_app.serializers import VacationSerializer
class VacationViewSet(viewsets.ModelViewSet):
queryset = Vacation.objects.all()
permission_classes = [IsAuthenticated]
serializer = VacationSerializer
# we are going to overwrite list and retrive
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
# now we are going to filter on user
queryset = queryset.filter(owner=self.request.user)
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
# not permitted check
if instance.owner is not self.request.user:
raise exceptions.PermissionDenied()
serializer = self.get_serializer(instance)
return Response(serializer.data)
Django rest framework provides in-build settings for this
Just import the required permission and add it to you class variable permission_classes
in my_name.api.views
from rest_framework.permissions import ( AllowAny, IsAuthenticated, IsAdminUser, IsAuthenticatedOrReadOnly,)
class Vacation(ListAPIView):
serializer_class = VacationListSerializer
permission_classes = [IsAuthenticated]
You can add multiple permission classes as a list
Furthur, in case this is not helpful, you can always filter the model objects as
Mymodel.objects.filter(owner = self.request.user)
I have a nested serializer with overridden create method:
class OrderSerializer(serializers.ModelSerializer):
data_model=Order
user = UserSerializer(many=False)
class Meta:
model = Order
fields = ['uid', 'user','price']
def create(self, validated_data):
validated_data=validated_data.pop('user')
order=self.data_model.objects.create(**validated_data)
order.user=self.context['request'].user
order.save()
return order
class LifeOrderSerializer(OrderSerializer):
data_model =LifeOrder
class Meta(OrderSerializer.Meta):
model = LifeOrder
fields = OrderSerializer.Meta.fields + [ "birth_year",
"contract_duration",]
and in the views.py
class OrderViewSet(viewsets.ModelViewSet):
queryset_model = LifeOrder
serializer_class = LifeOrderSerializer
def get_queryset(self):
self.queryset_model.objects.all()
but when I send a post request to create , model serializers defualt create method gets called! what is the problem?
The serializer_class mentioned in the viewset is LifeOrderSerializer, I think it must be OrderSerializer, since that is the serializer where the create() is overridden.
In the case where LifeOrderSerializer is the child of OrderSerializer, please make sure that you have overridden the create() method of LifeOrderSerializer to make it work.
Something like:
def create(self, validated_data):
order = super(OrderSerializer, self).create(validated_data)
return order
It was ModelViewSet's CreateModelMixin that raised the exception before calling my own create function.
I ovveride create of modelview set and the problem was solved!
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.
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().