Django REST Framework full serialization for personal data - django

What is the proper way to let authenticated users access their own private information, while other users would only be able to access the public information ?
I tried creating 2 serializers:
from rest_framework import serializers
from app.models import User
class PublicUserSerializer(serializers.HyperlinkedModelSerializer):
image_url = serializers.ImageField(use_url=False)
class Meta:
model = User
fields = (
'pk',
'first_name',
'last_name',
'image_url',
)
lookup_field = 'pk'
extra_kwargs = {
'url': {'lookup_field': 'pk'}
}
class PrivateUserSerializer(PublicUserSerializer):
class Meta:
fields = (
'pk',
'first_name',
'last_name',
'image_url',
'details',
'email',
)
But now, I'm wondering how should I update the viewset to choose the proper serializer.
from rest_framework import viewsets
from app.authentication import FirebaseAuthentication
from app.models import User
from app.serializers import PublicUserSerializer, PrivateUserSerializer
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = PublicUserSerializer
lookup_field = 'pk'
authentication_classes = (FirebaseAuthentication,)
I can override the get_serializer or get_serializer_class, but how can I access my user and check permissions within this method ?
Solution:
Serializer:
from rest_framework import serializers
from app.models import User
class UserSerializer(serializers.HyperlinkedModelSerializer):
image_url = serializers.ImageField(use_url=False)
class Meta:
model = User
fields = [
'pk',
'first_name',
'last_name',
'image_url',
'details',
]
lookup_field = 'pk'
extra_kwargs = {
'url': {'lookup_field': 'pk'}
}
class PrivateUserSerializer(UserSerializer):
class Meta(UserSerializer.Meta):
fields = UserSerializer.Meta.fields + [
'email',
]
Viewset:
from django.shortcuts import get_object_or_404
from rest_framework import viewsets
from rest_framework.response import Response
from app.authentication import FirebaseAuthentication
from app.models import User
from app.serializers import UserSerializer, PrivateUserSerializer
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
lookup_field = 'pk'
authentication_classes = (FirebaseAuthentication,)
def retrieve(self, request, pk=None):
queryset = User.objects.all()
user = get_object_or_404(queryset, pk=pk)
serializer = UserSerializer(user)
if self.request.user == user:
serializer = PrivateUserSerializer(user)
return Response(serializer.data)

I think, this would do the whole magic :)
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = PublicUserSerializer
lookup_field = 'pk'
authentication_classes = (FirebaseAuthentication,)
def get_queryset(self):
if self.request.user: # If the user authenticated
return User.objects.filter(pk=self.request.user.id)
return User.objects.all() # user not authenticated
def get_serializer_class(self):
if self.request.user: # If the user authenticated
return PrivateUserSerializer
else: # user not authenticated
return PublicUserSerializer
def list(self, request, *args, **kwargs):
if self.request.user:
private_data = PrivateUserSerializer(User.objects.filter(pk=self.request.user.id),many=True).data
public_data = PublicUserSerializer(User.objects.exclude(pk=self.request.user.id),many=True).data
return Response(data=private_data+public_data)
return Response(data=PublicUserSerializer(User.objects.all(),many=True).data)
UPDATE
The suggested answer by Fandekasp is,
def retrieve(self, request, pk=None):
queryset = User.objects.all()
user = get_object_or_404(queryset, pk=pk)
if self.request.user == user:
serializer = PrivateUserSerializer(user)
else:
serializer = PublicUserSerializer(user)
return Response(serializer.data)

You'd use Django Rest Framework's permissions
# Add this
from rest_framework.permissions import AllowAny
from rest_framework.permissions import IsAuthenticated
# Your other imports
from rest_framework import viewsets
from app.authentication import FirebaseAuthentication
from app.models import User
from app.serializers import PublicUserSerializer, PrivateUserSerializer
#permission_classes([AllowAny,])
class AllUsersViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = PublicUserSerializer
lookup_field = 'pk'
authentication_classes = (FirebaseAuthentication,)
# Apply whatever filters, permissions or logic specific for this level
#permission_classes([IsAuthenticated,])
class AuthenticatedUsersViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = PublicUserSerializer
lookup_field = 'pk'
authentication_classes = (FirebaseAuthentication,)
# Apply whatever filters, permissions or logic specific for this level
Once you have your viewsets separated, you can adjust their logic.

Related

'type' object is not iterable django-rest-framework

I am new to mobile app development and I am trying to make my first app with react-native and Django rest-framework as the backend. When I try to run the server and access any model through the django-rest-framework I get
"TypeError: 'type' object is not iterable." I have tried to look up a way to solve it but every way I found online did not help.
Here is my code:
models.py
from django.db import models
from django.contrib.auth.models import User
from django.core.validators import MaxValueValidator, MinValueValidator
class Movie(models.Model):
title = models.CharField(max_length=32)
description = models.TextField()
def no_of_ratings(self):
ratings = Rating.objects.filter(movie=self)
return len(ratings)
def avg_rating(self):
sum = 0
ratings = Rating.objects.filter(movie=self)
for rating in ratings:
sum += rating.stars
if len(ratings) > 0:
return sum / len(ratings)
else:
return 0
class Rating(models.Model):
movie = models.ForeignKey(Movie, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
stars = models.IntegerField(validators=[MinValueValidator(1), MaxValueValidator(5)])
class Meta:
unique_together = (('user', 'movie'))
index_together = (('user', 'movie'))
serializers.py
from rest_framework import serializer
from .models import Movie, Rating
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields =('id', 'title', 'description', 'no_of_ratings', 'avg_rating')
class RatingSerializer(serializers.ModelSerializer):
class Meta:
model = Rating
fields =('id', 'stars', 'user', 'movie')
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields =('id', 'username', 'password')
extra_kwargs = {'password': {'write_only': True, 'required': True}}
def create(self, validated_data):
user = User.objects.create_user(**validated_data)
Token.objects.create(user=user)
return user
views.py
from django.shortcuts import render
from rest_framework import viewsets, status
from .serializers import MovieSerializer, RatingSerializer, UserSerializer
from .models import Movie, Rating
from rest_framework.response import Response
from rest_framework.decorators import action
from django.contrib.auth.models import User
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.permissions import IsAuthenticatedOrReadOnly, BasePermission
# Create your views here.
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
class MovieViewSet(viewsets.ModelViewSet):
queryset = Movie.objects.all()
serializer_class = MovieSerializer
authentication_classes = (TokenAuthentication)
permission_classes = (IsAuthenticated)
#action(detail=True, methods=['POST'])
def rate_movie(self, request, pk=None):
if 'stars' in request.data:
movie = Movie.objects.get(id=pk)
stars = request.data['stars']
user = request.user
try:
rating = Rating.objects.get(user=user.id, movie=movie.id)
rating.stars = stars
rating.save()
serializer = RatingSerializer(rating, mamy=False)
Rating.objects.create(user=user, movie=movie, stars=stars)
response = {'message': 'Rating Updated', 'result': serializer.data}
except:
rating = Rating.objects.create(user=user, movie=movie, stars=stars)
serializer = RatingSerializer(rating, mamy=False)
response = {'message': 'Rating created', 'result': serializer.data}
return Response(response, status=status.HTTP_200_OK)
else:
response = {'message': 'you need to provide stars'}
return Response(response, status=status.HTTP_400_BAD_REQUEST)
class RatingViewSet(viewsets.ModelViewSet):
queryset = Rating.objects.all()
serializer_class = RatingSerializer
authentication_classes = (TokenAuthentication)
permission_classes = (AllowAny)
def update(self, request, *args, **kwargs):
response = {'message': 'You cant update rating like that'}
return Response(response, status=status.HTTP_400_BAD_REQUEST)
def create(self, request, *args, **kwargs):
response = {'message': 'You cant create rating like that'}
return Response(response, status=status.HTTP_400_BAD_REQUEST)
How can I fix this error?
authentication_classes and permission_classes must be tuple or list. When you put single class in parantheses, it will not behave with it as tuple but just one, so cannot iterate over it.
Change them as below in all your viewsets (Put an extra comma after them to show python that these parantheses demonstrate tuple):
#...
authentication_classes = (TokenAuthentication,)
permission_classes = (AllowAny,)
#...

Django Rest Framework - Passing 'user' from ModelViewSet to Serializer

I've been stumped by this for a couple of hours now, and have read through a bunch of documentation and different tutorials, but I still do not know what I'm doing wrong.
I'm trying to make a POST request to create a Comment (my model, views, etc. defined below) and make an association with the User that is making the POST request, but I keep getting the error {"user":["This field is required."]}
I thought that adding
def perform_create(self, serializer):
serializer.save(user=self.request.user)
to my viewset would make this easy, but that doesn't appear to be working...
My models.py looks like:
import uuid
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth import get_user_model
from django.db import models
User = get_user_model()
class Comment(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, on_delete=models.DO_NOTHING)
text = models.TextField(blank=False, null=False)
created = models.DateTimeField(auto_now_add=True)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.CharField(max_length=256)
content_object = GenericForeignKey('content_type', 'object_id')
serializer.py
from rest_framework import serializers
from .models import Comment
from organization.serializers import UserSerializer
from django.contrib.auth import get_user_model
User = get_user_model()
class CommentSerializer(serializers.ModelSerializer):
id = serializers.CharField(read_only=True, required=False)
user = UserSerializer(required=True)
text = serializers.CharField(required=True)
created = serializers.DateTimeField(read_only=True)
object_id = serializers.CharField(required=False)
class Meta:
model = Comment
fields = ('id', 'user', 'text', 'created', 'object_id')
read_only_fields = ('id', 'created')
views.py
from rest_framework import viewsets
from rest_framework import permissions
from .models import Comment
from .serializers import CommentSerializer
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return self.queryset.filter(object_id=self.kwargs['object_id'])
def perform_create(self, serializer):
serializer.save(user=self.request.user)
urls.py
from django.urls import include, path
from rest_framework import routers
from .views import CommentViewSet
router = routers.DefaultRouter()
router.register(r'(?P<content_type>[\w\-]+)/(?P<object_id>[\w\-]+)', CommentViewSet)
urlpatterns = [
path('', include(router.urls)),
]
Serializer validation works before perform_create, change user field to read only and it works as expected.
class CommentSerializer(serializers.ModelSerializer):
id = serializers.CharField(read_only=True, required=False)
user = UserSerializer(read_only=True)
text = serializers.CharField(required=True)
created = serializers.DateTimeField(read_only=True)
object_id = serializers.CharField(required=False)
class Meta:
model = Comment
fields = ('id', 'user', 'text', 'created', 'object_id')
read_only_fields = ('id', 'created')
Ok - I got this working by overriding the create method of CommentViewSet and simplifying my CommentSerializer
serializer.py
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ('id', 'user', 'text', 'created', 'edited', 'object_id', 'content_type')
read_only_fields = ('id', 'created')
views.py
from rest_framework import viewsets
from rest_framework import permissions
from rest_framework import status
from rest_framework.response import Response
from django.contrib.contenttypes.models import ContentType
from .models import Comment
from .serializers import CommentSerializer
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return self.queryset.filter(object_id=self.kwargs['object_id'])
def create(self, request, **kwargs):
"""
Overrides the standard create method so that we can set User,
object_id and content_type based on requesting user and kwargs
passed in URL
"""
comment_data = self.request.data
# change request data so that it's mutable, otherwise this will raise
# a "This QueryDict instance is immutable." error
comment_data._mutable = True
# set the requesting user ID for the User ForeignKey
comment_data['user'] = self.request.user.id
comment_data['object_id'] = self.kwargs['object_id']
# pass kwarg from URL to `model` to get the corresponding object
content_type = ContentType.objects.get( model=self.kwargs['content_type'] )
# pass the ID from the ContentType object to `content_type`, expected
# by serializer
comment_data['content_type'] = content_type.id
serializer = CommentSerializer(data=comment_data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

TypeError at / can only concatenate str (not "builtin_function_or_method") to str

Thank you for your time.
I was working on django rest framework documentation, and only with Localhost "http://127.0.0.1:8000/" URL I get this error.
TypeError at /
can only concatenate str (not "builtin_function_or_method") to str
I have attached Views.py, snippets/url.py and snippets/serializers.py
views.py
from snippets.permissions import IsOwnerOrReadOnly
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer, UserSerializer
from rest_framework import generics, permissions
from django.contrib.auth.models import User
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import renderers
from rest_framework.reverse import reverse
#api_view(['GET']
def api_root(request, fromat=None):
return Response({
'users': reverse('user-list', request=request, format=format),
'snippets': reverse('snippet-list', request=request, format=format)
})
class SnippetHighlight(generics.GenericAPIView):
queryset = Snippet.objects.all()
renderer_classes = [renderers.StaticHTMLRenderer]
def get(self, request, *args, **kwargs):
snippet = self.get_object()
return Response(snippet.highlighted
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
snippets/urls.py
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
urlpatterns = format_suffix_patterns([
path('', views.api_root),
path('snippets/', views.SnippetList.as_view(), name='snippet-list'),
path('snippets/<int:pk>/', views.SnippetDetail.as_view(), name='snippet-detail'),
path('snippets/<int:pk>/highlight/', views.SnippetHighlight.as_view(), name='snippet-
highlight'),
path('users/', views.UserList.as_view(), name='user-list'),
path('users/<int:pk>/', views.UserDetail.as_view(), name='user-detail')
])
serializer.py
from rest_framework import serializer
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
from django.contrib.auth.models import User
class SnippetSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight',
format='html')
class Meta:
model = Snippet
fields = ['url','id','highlight', 'owner' ,'title', 'code', 'linenos', 'language',
'style']
class UserSerializer(serializers.ModelSerializer):
snippets = serializers.HyperlinkedIdentityField(many=True, view_name='snippet-detail',
read_only=True)
class Meta:
model = User
fields = ['url', 'id', 'username', 'snippets']
irrespective of this everything's working perfectly, like http://127.0.0.1:8000/snippets/

Django Rest-Framework-Simplejwt not working with modheader

I am working on some projects and trying to the list view of the book. I used Django Rest-Framework-Simplejwt to generate tokens and mod header for authentication. When I tried to request a token for a user such as the admin user, and enter it into the mod header, the request is still unauthorized. I tried to do it a couple of times, but still not working.
Views.py
from rest_framework import generics, permissions
from rest_framework.permissions import IsAuthenticated
from rest_framework.exceptions import ValidationError
from django.contrib.auth.models import User
from .models import Book
from .serializers import (
BookSerializer,
RegistrationSerializer
)
class BookCreateView(generics.CreateAPIView):
"""Create a Book"""
queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = (IsAuthenticated,)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
class BookListView(generics.ListAPIView):
"""Show all books"""
serializer_class = BookSerializer
permission_classes = (IsAuthenticated,)
def get_queryset(self):
user = self.request.user
return Book.objects.filter(user=user)
class BookDetailView(generics.RetrieveAPIView):
"""Show detail of the book"""
serializer_class = BookSerializer
permission_classes = (IsAuthenticated,)
def get_queryset(self):
user = self.request.user
return Book.objects.filter(user=user)
class BookUpdateView(generics.RetrieveUpdateDestroyAPIView):
"""update detail of the book"""
queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = (IsAuthenticated,)
def delete(self, request, *args, **kwargs):
book = Book.objects.filter(user=self.request.user, pk=kwargs['pk'])
if book.exists():
return self.destroy(request, *args, **kwargs)
else:
raise ValidationError('Book is not yours!')
def perform_update(self, serializer, **kwargs):
book = Book.objects.get(pk=self.kwargs['pk'])
if self.request.user != book.user:
raise ValidationError("You are not the owner of this book")
serializer.save(user=self.request.user, book=book)
class UserRegistrationView(generics.CreateAPIView):
queryset = User.objects.all()
serializer_class = RegistrationSerializer
permission_classes = [permissions.AllowAny]
Serializers.py
from rest_framework import serializers
from django.contrib.auth.models import User
from .models import Book
class BookSerializer(serializers.ModelSerializer):
"""Serializer for Book"""
class Meta:
model = Book
fields = (
'id','user',
'title', 'author',
'description', 'image')
read_only_fields = ('id', 'user')
class RegistrationSerializer(serializers.ModelSerializer):
password = serializers.CharField(style={'input type':'password'}, write_only=True)
class Meta:
model = User
fields = ('username', 'email', 'password')
def create(self, validated_data):
user = User.objects.create(
username=validated_data['username'],
email=validated_data['email'])
user.set_password(validated_data['password'])
user.save()
return user
I entered the correct Token (copy&paste), but still not able to authenticate.
You are using "Token <eyJ..>". But instead your token should be like "Bearer <eyJ..>"
Some of Simple JWT’s behavior can be customized through settings variables in settings.py

How to register users in Django REST framework?

I'm coding a REST API with Django REST framework. The API will be the backend of a social mobile app. After following the tutorial, I can serialise all my models and I am able to create new resources and update them.
I'm using AuthToken for authentication.
My question is:
Once I have the /users resource, I want the app user to be able to register. So, is it better to have a separate resource like /register or allow anonymous users to POST to /users a new resource?
Also, some guidance about permissions would be great.
Django REST Framework 3 allow override create method in serializers:
from rest_framework import serializers
from django.contrib.auth import get_user_model # If used custom user model
UserModel = get_user_model()
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
def create(self, validated_data):
user = UserModel.objects.create_user(
username=validated_data['username'],
password=validated_data['password'],
)
return user
class Meta:
model = UserModel
# Tuple of serialized model fields (see link [2])
fields = ( "id", "username", "password", )
Serialized fields for classes inherited from ModelSerializer must be declared patently in Meta for Django Rest Framework v3.5 and newest.
File api.py:
from rest_framework import permissions
from rest_framework.generics import CreateAPIView
from django.contrib.auth import get_user_model # If used custom user model
from .serializers import UserSerializer
class CreateUserView(CreateAPIView):
model = get_user_model()
permission_classes = [
permissions.AllowAny # Or anon users can't register
]
serializer_class = UserSerializer
I went ahead and made my own custom view for handling registration since my serializer doesn't expect to show/retrieve the password. I made the url different from the /users resource.
My url conf:
url(r'^users/register', 'myapp.views.create_auth'),
My view:
#api_view(['POST'])
def create_auth(request):
serialized = UserSerializer(data=request.DATA)
if serialized.is_valid():
User.objects.create_user(
serialized.init_data['email'],
serialized.init_data['username'],
serialized.init_data['password']
)
return Response(serialized.data, status=status.HTTP_201_CREATED)
else:
return Response(serialized._errors, status=status.HTTP_400_BAD_REQUEST)
I may be wrong, but it doesn't seem like you'll need to limit permissions on this view since you'd want unauthenticated requests ...
The simplest solution, working in DRF 3.x:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'password', 'email', 'first_name', 'last_name')
write_only_fields = ('password',)
read_only_fields = ('id',)
def create(self, validated_data):
user = User.objects.create(
username=validated_data['username'],
email=validated_data['email'],
first_name=validated_data['first_name'],
last_name=validated_data['last_name']
)
user.set_password(validated_data['password'])
user.save()
return user
No need for other changes, just make sure that unauthenticated users have the permission to create a new user object.
write_only_fields will make sure passwords (actually: their hash we store) are not displayed, while the overwritten create method ensures that the password is not stored in clear text, but as a hash.
I typically treat the User view just like any other API endpoint that required authorization, except I just override the view class's permission set with my own for POST (aka create). I typically use this pattern:
from django.contrib.auth import get_user_model
from rest_framework import viewsets
from rest_framework.permissions import AllowAny
class UserViewSet(viewsets.ModelViewSet):
queryset = get_user_model().objects
serializer_class = UserSerializer
def get_permissions(self):
if self.request.method == 'POST':
self.permission_classes = (AllowAny,)
return super(UserViewSet, self).get_permissions()
For good measure, here is the serializer I typically use with it:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = (
'id',
'username',
'password',
'email',
...,
)
extra_kwargs = {
'password': {'write_only': True},
}
def create(self, validated_data):
user = get_user_model().objects.create_user(**validated_data)
return user
def update(self, instance, validated_data):
if 'password' in validated_data:
password = validated_data.pop('password')
instance.set_password(password)
return super(UserSerializer, self).update(instance, validated_data)
djangorestframework 3.3.x / Django 1.8.x
I updated Cahlan's answer to support custom user models from Django 1.5 and return the user's ID in the response.
from django.contrib.auth import get_user_model
from rest_framework import status, serializers
from rest_framework.decorators import api_view
from rest_framework.response import Response
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
#api_view(['POST'])
def register(request):
VALID_USER_FIELDS = [f.name for f in get_user_model()._meta.fields]
DEFAULTS = {
# you can define any defaults that you would like for the user, here
}
serialized = UserSerializer(data=request.DATA)
if serialized.is_valid():
user_data = {field: data for (field, data) in request.DATA.items() if field in VALID_USER_FIELDS}
user_data.update(DEFAULTS)
user = get_user_model().objects.create_user(
**user_data
)
return Response(UserSerializer(instance=user).data, status=status.HTTP_201_CREATED)
else:
return Response(serialized._errors, status=status.HTTP_400_BAD_REQUEST)
#cpury above suggested using write_only_fields option. This however did not work for me in DRF 3.3.3
In DRF 3.0 the write_only_fields option on ModelSerializer has been moved to PendingDeprecation and in DRF 3.2 replaced with a more generic extra_kwargs:
extra_kwargs = {'password': {'write_only': True}}
All of the answers so far create the user, then update the user's password. This results in two DB writes. To avoid an extra unnecessary DB write, set the user's password before saving it:
from rest_framework.serializers import ModelSerializer
class UserSerializer(ModelSerializer):
class Meta:
model = User
def create(self, validated_data):
user = User(**validated_data)
# Hash the user's password.
user.set_password(validated_data['password'])
user.save()
return user
A little late to the party, but might help someone who do not want to write more lines of code.
We can user the super method to achieve this.
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(
write_only=True,
)
class Meta:
model = User
fields = ('password', 'username', 'first_name', 'last_name',)
def create(self, validated_data):
user = super(UserSerializer, self).create(validated_data)
if 'password' in validated_data:
user.set_password(validated_data['password'])
user.save()
return user
A Python 3, Django 2 & Django REST Framework viewset based implementation:
File: serializers.py
from rest_framework.serializers import ModelSerializers
from django.contrib.auth import get_user_model
UserModel = get_user_model()
class UserSerializer(ModelSerializer):
password = serializers.CharField(write_only=True)
def create(self, validated_data):
user = UserModel.objects.create_user(
username=validated_data['username'],
password=validated_data['password'],
first_name=validated_data['first_name'],
last_name=validated_data['last_name'],
)
return user
class Meta:
model = UserModel
fields = ('password', 'username', 'first_name', 'last_name',)
File views.py:
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import CreateModelMixin
from django.contrib.auth import get_user_model
from .serializers import UserSerializer
class CreateUserView(CreateModelMixin, GenericViewSet):
queryset = get_user_model().objects.all()
serializer_class = UserSerializer
File urls.py
from rest_framework.routers import DefaultRouter
from .views import CreateUserView
router = DefaultRouter()
router.register(r'createuser', CreateUserView)
urlpatterns = router.urls
While there are many answers to this question, none of the answers (as of my writing) addresses the critical security concern, the password validation that is defined in settings.AUTH_PASSWORD_VALIDATORS. So it is possible to create a password like '1' which must not be acceptable. So I have fixed this major security issue. Here is my solution:
In serializers.py:
from django.contrib.auth import get_user_model
from django.contrib.auth.password_validation import validate_password
from rest_framework import serializers
class SignupSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ['username', 'first_name', 'last_name', 'email', 'password', ]
extra_kwargs = {
'password': {'write_only': True}
}
def validate_password(self, value):
validate_password(value)
return value
def create(self, validated_data):
user = get_user_model()(**validated_data)
user.set_password(validated_data['password'])
user.save()
return user
In views.py:
from rest_framework import mixins, viewsets
from rest_framework.permissions import AllowAny, IsAuthenticated
from . import forms, serializers
class SignupViewSet(mixins.CreateModelMixin,
viewsets.GenericViewSet):
permission_classes = [AllowAny]
serializer_class = serializers.SignupSerializer
API Response:
Now, if you try with a simple password like '1', this response will be returned automatically:
{
"password": [
"This password is too short. It must contain at least 8 characters.",
"This password is too common.",
"This password is entirely numeric."
]
}
In case of a password like '12345678', the response is:
{
"password": [
"This password is too common.",
"This password is entirely numeric."
]
}
In this way, the end-client will know exactly what else are required for the password to be valid.
# This work nicely, but serializer will reamain as it is, like
from django.contrib.auth import get_user_model
from django.contrib.auth.password_validation import validate_password
from rest_framework import serializers
class SignupSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ['username', 'first_name', 'last_name', 'email', 'password', ]
extra_kwargs = {
'password': {'write_only': True}
}
def validate_password(self, value):
validate_password(value)
return value
def create(self, validated_data):
user = get_user_model()(**validated_data)
user.set_password(validated_data['password'])
user.save()
return user
To simplify, modify your view to
from rest_framework import mixins, viewsets
from rest_framework.permissions import AllowAny, IsAuthenticated
from . import forms, serializers
class SignUpUserView(mixins.CreateModelMixin, viewsets.GenericViewSet):
permission_classes = [AllowAny]
queryset = get_user_model().objects.all() #Add this line
serializer_class = SignUpSerializer