Serialize the creation of user and profile django rest framework - python

I'm trying to create an user and his profile through DRF, but I don't find the correct way to do it.
Here's my models.py
from __future__ import unicode_literals
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
from django.db import models
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, blank=True, null=True)
language = models.CharField(max_length=4, default='es')
def __unicode__(self):
return "%s - %s" % (self.user, self.language)
my serializers.py
from rest_framework import serializers
from models import Profile
from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
class ProfileCreateSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username')
class Meta:
model = Profile
fields = [
'username',
'language',
]
def create (self, validated_data):
user = get_user_model().objects.create(username=validated_data['username'])
user.set_password(User.objects.make_random_password())
user.save()
profile = Profile.objects.create(user = user)
return profile
my views.py
from rest_framework.response import Response
from rest_framework.generics import CreateAPIView
from serializers import ProfileCreateSerializer
from models import Profile
class ProfileCreateAPIView(CreateAPIView):
model = Profile
serializer_class = ProfileCreateSerializer
My urls.py
from django.conf.urls import patterns, include, url
import views
from django.contrib import admin
urlpatterns = [
url(r'^', views.ProfileCreateAPIView.as_view(), name='crear perfil'),
]
if I try to create it shows me this error:
Cannot assign "{u'username': u'test'}": "Profile.user" must be a "User" instance.
if i create an user and his profile via admin panel doesn't show me any error.
my admin.py:
from django.contrib import admin
from models import Profile
from django.contrib.auth.admin import UserAdmin
class ProfileInline(admin.StackedInline):
model = Profile
can_delete = False
verbose_name_plural = 'Perfil'
fk_name = 'user'
class UserAdmin(UserAdmin):
inlines = (ProfileInline,)
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
I'm using django 1.9.1 and django rest framework 3.3.2

Why do you need UserSerializer at first place? Change your ProfileCreateSerializer to following. It should work
class ProfileCreateSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username')
class Meta:
model = Profile
fields = [
'username',
'language',
]
def create (self, validated_data):
user = get_user_model().objects.create(username=validated_data['username'])
user.set_password(User.objects.make_random_password())
user.save()
profile = Profile.objects.create(user = user)
return profile
Since the create method is override at your serializer, you need to send the data as a format the serializer process. So, you need to override the POST request at the views and create data as follows :
from rest_framework import status
from rest_framework.response import Response
class ProfileCreateAPIView(CreateAPIView):
model = Profile
serializer_class = ProfileCreateSerializer
def post(self, request, *args, **kwargs):
data = {
'username': request.data.get('username', None),
'language': request.data.get('language', None),
}
serializer = ProfileCreateSerializer(data=data)
if serializer.is_valid():
serializer.save()
return Response(
serializer.data, status=status.HTTP_201_CREATED)
else:
return Response( serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Related

How can I implement a Login system without using django default authentication in Python/Django?

Suppose I have a self-made User Model which holds username and password field. Now I want to login with my self-made username and password in Django. How can I do that?
Besides I need to build a Login Api using this username and password. How to do that?
I can implement the simple login system like the following.
TokenAuthentication can be used in the DRF by adding some configs in settings.py file.
# REST framework
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication'
]
}
Now create the users app by executing the following command.
python manage.py startapp users
And I can create the custom user model in the models.py. Let's say that the app name is "users" and the model name is "Account". You need to set the AUTH_USER_MODEL in settings.py.
# Application definition
INSTALLED_APPS = [
...
'rest_framework',
'corsheaders',
'users',
'rest_framework.authtoken'
]
# specify user model
AUTH_USER_MODEL = 'users.Account'
In models.py of the users app, I can define the user model by deriving from the AbstractBaseUser.
from django.db import models
from django.contrib.auth.models import AbstractBaseUser, UserManager
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token
class Account(AbstractBaseUser):
"""
A model for users
It simply has the three fields `username`, `password`, `last_login`.
In addition, it has several shifts.
"""
username = models.CharField(max_length=50, unique=True)
USERNAME_FIELD = 'username'
objects = UserManager()
#receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Token.objects.create(user=instance)
In serializers.py, you can set the password field as write-only.
from attr import fields
from rest_framework import serializers
from .models import Account
class AccountSerializer(serializers.ModelSerializer):
class Meta:
fields = '__all__'
model = Account
extra_kwargs = {
'password': {'write_only': True}
}
Finally, in urls.py of the users app, login can be implemented using rest_framework.authtoken
from django.urls import path
from rest_framework.authtoken import views
from .views import AccountView
urlpatterns = [
path('login', views.obtain_auth_token, name="login"),
path('register', AccountView.as_view(), name="register")
]
Of course, you can also implement the user register in views.py.
from django.shortcuts import render
from rest_framework.generics import CreateAPIView
from users.serializers import AccountSerializer
from .models import Account
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework import status
class AccountView(CreateAPIView):
queryset = Account.objects.all()
serializer_class = AccountSerializer
permission_classes = [AllowAny]
def post(self, request):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
input_data = serializer.validated_data
username = input_data.get('username')
password = input_data.get('password')
# create user and set password
user = Account.objects.create(username=username)
user.set_password(password)
user.save()
return Response(AccountSerializer(user).data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

NOT NULL constraint when creating a new user in django

I created a Django API to create a new user. However, when I try to create a user I get the error message:
IntegrityError at /api/v1/users/register/ NOT NULL constraint failed: accounts_user.user_id
This is what I have in models.py
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 User(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length=100, blank=True)
location = models.CharField(max_length=100, blank=True)
password = models.CharField(max_length=32)
email = models.EmailField(max_length=150)
signup_confirmation = models.BooleanField(default=False)
def __str__(self):
return self.user.username
#receiver(post_save, sender=User)
def update_profile_signal(sender, instance, created, **kwargs):
if created:
User.objects.create(user=instance)
instance.profile.save()
In serializers.py
from rest_framework import serializers
from .models import User
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('user_id', 'name', 'location', 'password', 'email', 'signup_confirmation')
and my views.py
from rest_framework import viewsets
from .serializers import UserSerializer
from .models import User
from rest_framework.decorators import action
from .forms import SignUpForm
from .tokens import account_activation_token
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all().order_by('name')
serializer_class = UserSerializer
#action (detail=True, methods=['post'])
def register(self, request):
print(request)
Any ideas on what I can do to resolve this error
As John wrote in a comment:
Here you have a problem: fields = ('user_id',...).
I also advise you to change your User model. If you don't need separating (I suppose you don't), it is way better to create your User with inheritance directly from AbstractUser instead of creating in fact doubled User models.
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
...
In this way you will already have username, password and email, but you can add anything else in same model, instead of using user.user etc.

Why is Django authentication is now working for AbstractUser

So I have these Django models in the users app, and I have added AUTH_USER_MODEL = 'users.User' to settings.py
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
login_count = models.PositiveIntegerField(default=0)
class Supplier(User):
company_name= models.CharField(max_length=30)
company_domain=models.CharField(max_length=30)
class Meta:
verbose_name = 'supplier'
verbose_name_plural = 'suppliers'
class Worker(User):
ACCOUNT_TYPE = (
('1', 'Admin'),
('2', 'Regular'),
)
is_hub_manager = models.BooleanField(default=False)
account_type = models.CharField(max_length=1, choices=ACCOUNT_TYPE)
class Meta:
verbose_name = 'worker'
verbose_name_plural = 'workers'
I also created an authenetication endpoint using Django rest framework. Surprisingly when I authenticate the admin everything works well. When I create a supplier and try to authenticate them. They always return invalid credentials. Here are my API views
from rest_framework.generics import GenericAPIView
from .serializers import UserSerializer
from rest_framework.response import Response
from rest_framework import status
from django.conf import settings
from django.contrib import auth
import jwt
class LoginView(GenericAPIView):
serializer_class = UserSerializer
def post(self, request):
data = request.data
username = data.get('username', '')
password = data.get('password', '')
user = auth.authenticate(username=username, password=password)
if user:
auth_token = jwt.encode({'username': user.username}, settings.JWT_SECRET_KEY)
serializer = UserSerializer(user)
data = {'user': serializer.data, 'token': auth_token}
return Response(data, status=status.HTTP_200_OK)
# SEND RES
return Response({'detail': 'Invalid credentials'}, status=status.HTTP_401_UNAUTHORIZED)
What could I be doing wrong?
The first thing, you should check AUTH_USER_MODEL keyword in setting file.
E.g. AUTH_USER_MODEL = '<module>. User'.
Regarding your case, the root cause is not yet custom your authenticate() method in your backend. You defined Supplier and Worker which are inherited from the User class.
Also, you can refer to this link for detail.

Got AttributeError when attempting to get a value for field `Name` on serializer `UserSerializer`

I got the following error:
Got AttributeError when attempting to get a value for field Name on
serializer UserSerializer. 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 'Name'.
Why do I get an error?
Here is my code:
views.py
from .serializers import UserSerializer
from rest_framework import viewsets, status
from django.contrib.auth.models import User
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
serializers.py
from django.contrib.auth.models import User
from rest_framework import serializers
from .models import Profile
from rest_framework.authtoken.models import Token
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ('id', 'Name', 'secondName', 'user', 'nickName', 'phoneNumber')
"""extra_kwargs = {'password': {'write_only': True, 'required': True}}"""
def create(self, validated_data):
user = User.objects.create_user(**validated_data)
token = Token.objects.create(user=user)
print('Loogg')
return user
models.py
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
"""Define Profile Fields"""
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, default=None)
Name = models.CharField(max_length=32)
secondName = models.CharField(max_length=32)
nickName = models.CharField(max_length=32)
phoneNumber = models.IntegerField(max_length=32)
def __str__(self):
return self.user.username
class Meta:
verbose_name = 'Profile'
verbose_name_plural = 'profiles'
urls.py
from django.contrib import admin
from django.urls import path
from django.conf.urls import include
from rest_framework import routers
from .views import UserViewSet
router = routers.DefaultRouter()
router.register('users', UserViewSet)
urlpatterns = [
path('', include(router.urls))
]
admin.py
from django.contrib import admin
from .models import Profile
admin.site.register(Profile)
Thanks,
I am quite new to Django so it could be a stupid error.
queryset = Profile.objects.all() instead of queryset = User.objects.all()
– Arakkal Abu

Django Rest Framework - Displaying the User's Profile

My users/models.py file looks as below.
class User(AbstractUser):
is_customer = models.BooleanField(default=False)
is_courier = models.BooleanField(default=False)
is_merchant = models.BooleanField(default=False)
class Profile(models.Model):
contact_number = models.CharField(max_length=10, unique=True)
rating = models.IntegerField(blank=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
My current users/serializers.py looks like below.
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
My users/api.py looks like below.
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
permission_classes = [
permissions.AllowAny
]
serializer_class = UserSerializer
My users/urls.py has the below:
router.register('api/users', UserViewSet, 'users')
My current setup works well with the UserViewSet. http://127.0.0.1:8000/api/users/ displays all the users and http://127.0.0.1:8000/api/users/1/ displays the user according to the ID.
My question is, How can I load up the user profile when I goto the below the URL http://127.0.0.1:8000/api/users/1/profile
Any help is much appreciated. Thank you in advance.
Create a new serializer for Profile model
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = "__all__"
then create a new view class for the Profile.
from rest_framework.views import APIView
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
class ProfileAPI(APIView):
def get(self, request, *args, **kwargs):
user = get_object_or_404(User, pk=kwargs['user_id'])
profile_serializer = ProfileSerializer(user.profile)
return Response(profile_serializer.data)
Then, wire up the view in urls.py
urlpatterns = [
# your other url configs
path('api/users/<user_id>/profile/', ProfileAPI.as_view())
]
Update-1
Implementation using ViewSet class
from rest_framework import viewsets
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
class ProfileAPI(viewsets.ViewSet):
def get(self, request, *args, **kwargs):
user = get_object_or_404(User, pk=kwargs['user_id'])
profile_serializer = ProfileSerializer(user.profile)
return Response(profile_serializer.data)
Update-2
from rest_framework import viewsets
class ProfileAPI(viewsets.ModelViewSet):
serializer_class = ProfileSerializer
def get_queryset(self):
return Profile.objects.filter(user=self.kwargs['user_id'])
and in your urls.py register the viewset as
router.register('api/users/(?P<user_id>\d+)/profile', ProfileAPI, base_name='profile_api')
i have used **AbstractUser ** and **custom user manager **
i have used ViewSets.ViewSet along with Model Serializers
#urls.py file#
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ProfileViewSet, LoginViewSet, RegisterViewSet
router = DefaultRouter()
router.register(r'register', RegisterViewSet, basename='register')
router.register(r'login', LoginViewSet, basename='login')
router.register(r'profile', ProfileViewSet, basename='profile')
urlpatterns = [
path('', include(router.urls)),
]
#views.py file#
from rest_framework.response import Response
from rest_framework.viewsets import ViewSet
from .models import user_reg
from .serializers import RegisterSerializer
class ProfileViewSet(ViewSet):
def partial_update(self, request, pk=None): #partially update the profile
try:
user_detail = user_reg.objects.get(pk=pk)
serializer = RegisterSerializer(user_detail,data=request.data, partial=True)
if not serializer.is_valid():
return Response({'data':'internal server error','message':'error aa gyi'},500)
serializer.save()
except Exception as e:
return Response('some exception occured' + str(e))
return Response('record Updated successfully')
def retrieve(self,request, pk=None): #get or retrieve the profile from database
queryset = user_reg.objects.get(pk=pk)
serializer_class = RegisterSerializer(queryset)
return Response(serializer_class.data)
#serializer.py file#
from rest_framework import serializers
from .models import user_reg
class RegisterSerializer(serializers.ModelSerializer):
class Meta:
model = user_reg
fields = ('username','first_name','last_name','email') #custom fields i made
u can change this
#models.py#
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import UserManager
class user_reg(AbstractUser):
mobile = models.CharField(max_length=10, blank=True, null=True)
age = models.IntegerField(null=True,blank=False)
gender = models.CharField(max_length= 8,blank=True)
objects = UserManager()
class Meta:
verbose_name='user'

Categories