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
Related
I am new in django. I would like to create registration profile. I found some code but it doesn´t work for me. When I want to makemigrations I always get this error AttributeError: Manager isn't available; 'auth.User' has been swapped for 'user.User'
I read that I could fix it with User = get_user_model() but it looks it is doesn´t work for me.
My models.py
import random
from django.contrib.auth import get_user_model
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
User = get_user_model()
def code_generator(length=5):
numbers = '0123456789'
return ''.join(random.choice(numbers) for _ in range(length))
class RegistrationProfile(models.Model):
code = models.CharField(default=code_generator, max_length=5)
user = models.OneToOneField(to=User, on_delete=models.CASCADE, related_name='registration_profile', primary_key=True)
#receiver(post_save, sender=User)
def create_registration_profile(sender, instance, **kwargs):
profile, created = RegistrationProfile.objects.get_or_create(user=instance)
if created:
profile.save()
serializer.py
from rest_framework import serializers
from rest_framework.validators import UniqueValidator
from django.contrib.auth.password_validation import validate_password
from .models import User
class RegisterSerializer(serializers.ModelSerializer):
email = serializers.EmailField(
required=True,
validators=[UniqueValidator(queryset=User.objects.all())]
)
password = serializers.CharField(write_only=True, required=True, validators=[validate_password])
password2 = serializers.CharField(write_only=True, required=True)
class Meta:
model = User
fields = ('username', 'password', 'password2', 'email', 'first_name', 'last_name')
extra_kwargs = {
'first_name': {'required': True},
'last_name': {'required': True}
}
def validate(self, attrs):
if attrs['password'] != attrs['password2']:
raise serializers.ValidationError({"password": "Password fields didn't match."})
return attrs
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
and views.py
from django.contrib.auth.models import User
from rest_framework import generics
from rest_framework.permissions import AllowAny
from .serializers import RegisterSerializer
class RegisterView(generics.CreateAPIView):
queryset = User.objects.all()
permission_classes = (AllowAny,)
serializer_class = RegisterSerializer
In the end I would like to send code to email for validation. Any ideas how I could do it by most effective way?
you can do this
from django.contrib.auth.models import User
class RegistrationProfile(models.Model):
code = models.CharField(default=code_generator, max_length=5)
user = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name='registration_profile')
#receiver(post_save, sender=User)
def create_registration_profile(sender, instance, **kwargs):
profile, created = RegistrationProfile.objects.get_or_create(user=instance)
if created:
profile.save()
i am checking password of user to give user info.but when i give correct password it throws password not correct error.
models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
# username = None
email = models.EmailField(verbose_name='email',max_length=50,unique=True)
phone = models.CharField(max_length=17,blank=True)
REQUIRED_FIELDS = [
'first_name',
'last_name',
'phone',
'username',
]
USERNAME_FIELD = 'email'
def get_username(self):
return self.email
views.py
from rest_framework.authtoken.models import Token
from django.contrib.auth.hashers import check_password,make_password
## My Login View
class LoginView(APIView):
serializer_class = CustomTokenCreateSerializer
permission_classes = (AllowAny,)
def post(self,request,*args, **kwargs):
# Cheking email Exists
if User.objects.filter(email=request.POST['email']).exists():
user = User.objects.get(email=request.POST['email'])
## check password matches
if check_password(request.POST['password'],user.password):
## Creating TOken
token, _ = Token.objects.get_or_create(user=user)
serializer = UserCreateSerializerCustom(user)
return Response({'status':True,
"message":"Login SuccessFull",
"user_info":serializer.data,
'token':token.key})
else:
return Response({'status':False,
"message":"Passord is not correct,Check Passsword again"})
else:
return Response({'status':False,
"message":"User Doesn't Exist,Check Email again"}
)
this is login api for my project.i'm using check_password function for checking the password,i can't find a solution for this problem.
I can login with admin username and password without any error .but can't login with user data. I think the problem is password of user is not hashing in the correct way ,How will i do this...??
Edit
Registration VIew
#### User registration View
class UserRegistrationView(generics.CreateAPIView):
serializer_class = UserCreateSerializerCustom
# queryset = User.objects.all()
permission_classes = (AllowAny,)
def post(self,request,format=None):
serializer = UserCreateSerializerCustom(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({"Status":True,"message":"User Registered Successfully",
"User_info":serializer.data},
status=status.HTTP_201_CREATED)
return Response(serializer.errors,status=status.HTTP_400_BAD_REQUEST)
serializer.py
from djoser.serializers import UserCreateSerializer
class UserCreateSerializerCustom(UserCreateSerializer):
class Meta(UserCreateSerializer.Meta):
model = User
fields = (
'id',
'email',
'username',
'first_name',
'last_name',
'phone',
)
The problem was not adding password in Fields
from djoser.serializers import UserCreateSerializer
class UserCreateSerializerCustom(UserCreateSerializer):
class Meta(UserCreateSerializer.Meta):
model = User
fields = (
'id',
'email',
'username',
'first_name',
'last_name',
'password',
'phone',
)
I'm having the following problem: the login request returns "Unable to login with provided credentials" after I do a PUT request (changing fields like first_name, last_name, address) even though the username and password are correct in the DB.
The following view I use to make my Login Request.
class RetrieveAuthToken(ObtainAuthToken):
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data,
context={'request': request})
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
resp = Response({
'token': token.key,
'user_id': user.pk,
'email': user.email,
'role': user.role,
})
return resp
And these are for my users and registration of a user:
from users.models import User
from users.serializers import UserSerializer, RegistrationSerializer
from rest_framework import viewsets, generics
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
# Create your views here.
class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
queryset = User.objects.all()
class RegistrationView(generics.CreateAPIView):
model = User
serializer_class = RegistrationSerializer
My serializers look like this:
from rest_framework import serializers, status
from users.models import User
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(style={'input_type': 'password'})
class Meta:
model = User
fields = '__all__'
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = super(UserSerializer, self).create(validated_data)
user.set_password(validated_data['password'])
user.save()
return user
def update(self, instance, validated_data):
instance = super(UserSerializer, self).update(instance, validated_data)
instance.set_password(validated_data['password'])
instance.save()
return instance
class RegistrationSerializer(serializers.ModelSerializer):
password = serializers.CharField(style={'input_type': 'password'}, write_only=True)
def create(self, validated_data):
user = super(RegistrationSerializer, self).create(validated_data)
user.set_password(validated_data['password'])
user.save()
return user
class Meta:
model = User
fields = ['email', 'username', 'password']
It looks like the problem is coming from the serializer:
Solved the problem guys! So, the problem was coming from the is_active field from User. On my update method from the serializer, that field was set to False after I did that request. In order to solve this, on my update method I set the is_active field to True.
def update(self, instance, validated_data):
instance = super(UserSerializer, self).update(instance, validated_data)
instance.set_password(validated_data['password'])
instance.is_active = True
instance.save()
return instance
I am trying to create a api for user Registration using the django rest framework.
I have the following models.py file
from __future__ import unicode_literals
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE , primary_key = True)
mobileNumber = models.IntegerField(default=0)
avatar= models.ImageField(upload_to = 'User/' , default = '/static/User/defaultProfileImage.png')
def create_user_profile(sender, **kwargs):
if kwargs['created']:
profile = UserProfile.objects.create(user=kwargs['instance'])
post_save.connect(create_user_profile, sender=User)
This is my Serializers.py file
from rest_framework import serializers
from User.models import UserProfile
from django.contrib.auth.models import User
class UserSerializer(serializers.HyperlinkedModelSerializer):
username = serializers.CharField()
password1 = serializers.CharField(
style={'input_type': 'password'},
write_only=True)
password2 = serializers.CharField(
style={'input_type': 'password'},
write_only=True)
email = serializers.EmailField()
class Meta:
model = User
fields = (
'id',
'username',
'password1',
'password2',
'email',
'first_name',
'last_name',
)
class UserProfileSerializer(serializers.HyperlinkedModelSerializer):
user = UserSerializer()
class Meta:
model = UserProfile
fields = (
'user',
'mobileNumber',
'avatar')
And following is my views.py file
from User.models import UserProfile
from .serializers import UserProfileSerializer
from rest_framework.viewsets import ModelViewSet
class UserProfileViewSet(ModelViewSet):
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
What is the best way to create a User Registeration using the api view that i have created. I tried many alternatives like overriding the create method in the UserProfile Serializer class and also the drf-writable-nested but got errors.
Please suggest me a way out. Also i want that the api is able to register users when called on by an android app.
You can do this in your Serializers.py file, this should work.
class UserSerializer(serializers.HyperlinkedModelSerializer):
mobileNumber = serializers.IntegerField()
avatar= serializers.ImageField()
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email', 'password', 'mobileNumber', 'avatar')
def create(self, validated_data):
mobile_number = validated_data.pop('mobileNumber', None)
user = super(UserSerializer, self).create(validated_data)
user.set_password(raw_password=validated_data['password'])
user.save()
userprofile = user.userprofile
userprofile.mobileNumber = mobile_number
userprofile.save()
return user
def update(self, instance, validated_data):
mobile_number = validated_data.pop('mobileNumber', None)
userprofile = instance.userprofile
userprofile.mobileNumber = mobile_number
userprofile.save()
return super(UserSerializer, self).update(instance, validated_data)
Chuck the UserProfileSerializer for this use case, i feel here its not really needed.
Your views.py and models.py look cool to me.
Hope this helps you :-)
I'm trying to do a login app including registration, authentication etc...
I'm getting this error:
AttributeError: Got AttributeError when attempting to get a value for field 'user' on serializer 'PictureUserSerializer'.
The serializer field might be named incorrectly and not match any attribute or key on the 'User' instance.
Original exception text was: 'User' object has no attribute 'user'.
But, when I check my admin page. The user has been registered with the right informations. I'm not feeling like this error come from nowhere and I must do something wrong. Here is my code:
logginapp/models.py
from django.db import models
from django.contrib.auth.models import User
class PictureUser(models.Model):
user = models.OneToOneField(User) #OneToOne link to the Django's User Model
avatar = models.ImageField(null=True, blank=True, upload_to="avatars/")
def username(self, user):
return user.username
def password(self, user):
return user.password
def email(self, user):
return user.email
loginapp/serializer.py
from django.core.validators import validate_email
from django import forms
from rest_framework import serializers
from loginapp.models import PictureUser
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'password', 'email')
write_only_fields = ('password',)
class PictureUserSerializer(serializers.ModelSerializer):
"""
Serializer to put the PictureUser model into JSON
"""
user = UserSerializer()
class Meta:
model = PictureUser
fields = ('user', 'avatar')
read_only_fields = ('created',)
def create(self, validated_data):
print (validated_data)
return User.objects.create_user(validated_data['user']['username'], validated_data['user']['email'],
validated_data['user']['password']
)
loginapp/view.py
from django.shortcuts import render
from rest_framework import generics
from .serializer import PictureUserSerializer
from .models import PictureUser
class PictureUserCreateView(generics.ListCreateAPIView):
"""
This class define the Create behaviour of a PictureUser
"""
queryset = PictureUser.objects.all()
serializer_class = PictureUserSerializer
def perform_create(self, serializer):
serializer.save()
If you need to create user from PictureUserSerializer, then,
def create(self, validated_data):
user_data = validated_data.pop('user')
user = User.objects.create_user(**user_data)
return PictureUser.objects.create(user = user, **validated_data)
PictureUserSerializer create method needs to return a PictureUser instance, not a User instance.