I'm very new to Django and preparing for the DRF Session.
I have to write code in Serializers.py and views.py for Logout function but I don't know what to do and where to start.
Can you please help? Here's some code for register and login
Serialzers.py
from rest_framework import serializers
from .models import *
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'email', 'password']
def create(self, validated_data):
user = User.objects.create(
email=validated_data['email'], username=validated_data['username'],)
user.set_password(validated_data['password'])
user.save()
return user
class UserLoginSerializer(serializers.Serializer):
email = serializers.CharField(max_length=64)
password = serializers.CharField(max_length=128, write_only=True)
def validate(self, data):
email = data.get("email", None)
password = data.get("password", None)
if User.objects.filter(email=email).exists():
user = User.objects.get(email=email)
if not user.check_password(password):
raise serializers.ValidationError()
else:
return user
else:
raise serializers.ValidationError()
class UserLogoutSerializer(serializers.Serializer):
# I have to do this part
views.py
from django.shortcuts import get_object_or_404, render
from .serializers import *
from .models import *
from rest_framework import views
from rest_framework.response import Response
class SignUpView(views.APIView):
def post(self, request, format=None):
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({'message': 'Success', 'data': serializer.data})
return Response({'message': 'Fail', 'error': serializer.errors})
class LoginView(views.APIView):
def post(self, request):
serializer = UserLoginSerializer(data=request.data)
if serializer.is_valid():
return Response({'message': "Success", 'data': serializer.data})
return Response({'message': "Fail", 'data': serializer.errors})
class LogoutView(views.APIView):
You do not necessarily have to use a serializer. Logout can be as simple as calling an endpoint once (logout just disables any active authorization token). Try this:
from django.contrib.auth import logout
class LogoutView(views.APIView):
def post(self, request):
logout(request)
return Response({'message': "Logout successful"})
It depends on how you are authenticating your user. From you code, I do not see how you actually authenticate your user (you only seem to check if the user exists, you do not give them an authorization token anywhere). Basically I am not sure how one would log out in your current architecture as there is no user logged in ever to log them out.
You might want to consider token authentication.
Related
I'm new to django rest framework. I'm implementing a simple login, signup and forgot password functionality using reactJs and django. All the functionalities are working fine but the problem I'm facing is that, on signup, the passwords in the database encrypted and then saved in the database but on updating password, the new password saved exactly same as user typed it. I want it also be encrypted in the database just like it encrypted in signup.
My serializer.py file
from dataclasses import fields
from rest_framework import serializers
from rest_framework_jwt.settings import api_settings
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields='__all__'
# fields = ('username','first_name', 'last_name', 'email')
class UserSerializerWithToken(serializers.ModelSerializer):
token = serializers.SerializerMethodField()
password = serializers.CharField(write_only=True)
def get_token(self, obj):
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(obj)
token = jwt_encode_handler(payload)
return token
def create(self, validated_data):
password = validated_data.pop('password', None)
instance = self.Meta.model(**validated_data)
if password is not None:
instance.set_password(password)
instance.save()
return instance
class Meta:
model = User
fields = ('token','first_name', 'last_name','email', 'username', 'password')
My views.py file
from asyncio.windows_events import NULL
from django.http import Http404, HttpResponseRedirect
from django.contrib.auth.models import User
from rest_framework import viewsets, permissions, status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.views import APIView
from .serializers import UserSerializer
from .serializers import UserSerializerWithToken
class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
queryset = User.objects.all()
#api_view(['GET'])
def current_user(request):
"""
Determine the current user by their token, and return their data
"""
serializer = UserSerializer(request.user)
return Response(serializer.data)
#api_view(['GET'])
def get_user(request):
"""
Filter the user wrt username and return the user data and token
"""
user=User.objects.filter(email=request.query_params['email'])
serializer = UserSerializer(instance=user, many=True)
if user.exists():
return Response(serializer.data)
else:
return Response(serializer.errors)
# ########################################################
# This is my update user api to update password
# it updates the password but not encrypting the password
# like signup encrypts in database
# ########################################################
#api_view(['PUT'])
def update_user(request,id):
user=User.objects.get(pk=id)
# userdata={'id':user.id,'username':user.username,'first_name':user.first_name,'last_name':user.last_name,'password':user.password}
serializer=UserSerializer(instance=user, data=request.data[0])
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
else:
return Response(serializer.errors)
class UserList(APIView):
"""
Create a new user. It's called 'UserList' because normally we'd have a get
method here too, for retrieving a list of all User objects.
"""
# permission_classes = (permissions.AllowAny,)
def post(self, request, format=None):
serializer = UserSerializerWithToken(data=request.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)
# ############################################################################
# I tried the following way as well but it doesn't even update the password
# ############################################################################
#
# def put(self,request,id,format=None):
# user=User.objects.get(pk=id)
# serializer=UserSerializerWithToken(instance=user, data=request.data)
# if serializer.is_valid():
# serializer.save()
# return Response(serializer.data)
# else:
# return Response(serializer.errors)
This is the url I'm using for api
path('users/<int:id>/',UserList.as_view()),
And this is how I called this api from reactjs
function handleSubmit(e) {
let user=JSON.parse(localStorage.getItem('matched_user_token'))[0]
user.password=new_password
e.preventDefault()
if (ValidatePassword()) {
fetch('http://localhost:8000/core/users/'+user.id+'/', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(user)
})
.then(res => res.json())
.then(json => {
console.log("password changed successfully!"+json)
localStorage.setItem('changed',true)
navigate('/login')
}).catch(errors=>console.log(errors))
}
}
You need to override serializer's update method like this:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields='__all__'
def update(self, instance, validated_data):
if 'password' in validated_data:
password = validated_data.pop('password', None)
instance.set_password(password)
return super().update(instance, validated_data)
I'm trying to create a well-rounded authentication/registration/login system in Django using the rest framework. I've slowly started wrapping my head on how to get it done well.
I want to be satisfied with my token situation, however after reading all the docs and looking at other Git repos, I've seem to dug myself in a hole of confusion. To my understanding, there are some libraries that do token authentication/refreshing for you, others give you the tools to create one.
I'm not sure if I have a solid base to build a token refresh function given my code below. How can I implement a token refresh system for users that log-in?
Here is my code, it works fine and I see registration data in my User model.
serializers.py
from rest_framework import serializers
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(max_length=65, min_length=8, write_only=True)
email = serializers.EmailField(max_length=255, min_length=4)
username = serializers.RegexField("^(?!.*\.\.)(?!.*\.$)[^\W][\w.]{3,29}$")
first_name = serializers.RegexField("^[A-Za-z]+((\s)?((\'|\-|\.)?([A-Za-z])+))*$", max_length=32, min_length=2)
last_name = serializers.RegexField("^[A-Za-z]+((\s)?((\'|\-|\.)?([A-Za-z])+))*$", max_length=32, min_length=2)
class Meta:
model = User
fields = ['first_name', 'last_name', 'username', 'email', 'password']
def validate(self, attrs):
email = attrs.get('email', '')
if User.objects.filter(email=email).exists():
raise serializers.ValidationError(
{'email': ('Email is already in use')})
return super().validate(attrs)
def create(self, validated_data):
return User.objects.create_user(**validated_data)
backend.py [my authenitication system]
import jwt
from rest_framework import authentication, exceptions
from django.conf import settings
from django.contrib.auth.models import User
class JWTAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
auth_data = authentication.get_authorization_header(request)
if not auth_data:
return None
prefix, token = auth_data.decode('utf-8').split(' ')
try:
payload = jwt.decode(token, settings.JWT_SECRET_KEY)
user = User.objects.get(email=payload['email'])
return (email, token)
except jwt.DecodeError as identifier:
raise exceptions.AuthenticationFailed('Your token is invalid')
except jwt.ExpiredSignatureError as identifier:
raise exceptions.AuthenticationFailed('Your token has expired, please try again')
return super().authenticate(request)
views.py :
from django.shortcuts import render
from rest_framework import status
from rest_framework.generics import GenericAPIView
from .serializers import UserSerializer
from rest_framework.response import Response
from django.conf import settings
from django.contrib import auth
import jwt
# Create your views here.
class RegisterView(GenericAPIView):
serializer_class = UserSerializer
def post(self, request):
serializer = UserSerializer(data=request.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)
class LoginView(GenericAPIView):
def post(self, request):
data = request.data
email = data.get('email','')
password = data.get('password','')
user = auth.authenticate(email=email, password=password)
if user:
auth_token = jwt.encode({'email':user.email}, settings.JWT_SECRET_KEY)
serializer = UserSerializer(user, many=True)
data = {
"email": serializer.data,
"token": auth_token,
}
return Response(data, status=status.HTTP_200_OK)
return Response({"detail": "Invalid credentials"}, status=status.HTTP_401_UNAUTHORIZED)
Should I re-do my authentication system to include a token refresh system?
I am trying to create a user creation api and post create api, the 'Get' request works fine but the post request is always empty. If there is anything I missed or need to add to the views, because the get request works fine for the post app. What could be the possible errors that are occuring.
Serializer.py
from rest_framework import serializers
from .models import User
class RegisterSerializer(serializers.ModelSerializer):
password2 = serializers.CharField(
style={'input_type': 'password'}, write_only=True)
class Meta:
model = User
fields = ['username', 'email',
'date_of_birth', 'password', 'password2']
extra_kwargs = {'password': {'write_only': True}}
def save(self):
print("saved")
user = User(email=self.validated_data['email'],
username=self.validated_data['username'],
date_of_birth=self.validated_data['date_of_birth']
)
password = self.validated_data['password']
password2 = self.validated_data['password2']
if password != password2:
raise serializers.ValidationError(
{'password': 'Passwords must match.'})
user.set_password(password)
user.save()
return user
Below is the views.py
from rest_framework import status
from rest_framework.response import Response
from rest_framework.decorators import api_view
from .models import User
from .serializers import RegisterSerializer
from rest_framework.authtoken.models import Token
from rest_framework.parsers import JSONParser
#api_view(['POST', ])
def registration_view(request):
if request.method == 'POST':
serializer = RegisterSerializer(data=request.data)
data = {}
if serializer.is_valid():
print("Hello")
user = serializer.save()
data['response'] = 'Successfuly registered a new User'
data['date_of_birth'] = user.date_of_birth
data['username'] = user.username
data['email'] = user.email
token = Token.objects.get(user=user)
data['token'] = token
return Response(data)
else:
return Response(serializer.errors)
No matter the post request I always get an error saying this field is required,
Here is the error -- screenshot
Can anybody help me please,
Thank you
According to your screenshot you are sending data as query params with POST request. In Postman you should use body section with, for example, form-data option.
I'm trying to implement account activation by e-mail link. My User model is very simple, inherits from django.contrib.auth.models.AbstractUser, so it has is_active field by default. Upon registration, new user is created with is_active=False param and I want to handle case, when user tries to log in and even though credentials are fine, should not be logged in because account is not activated. I'm using Knox Token Authentication. My serializer:
from django.contrib.auth import authenticate
from rest_framework import serializers, exceptions
class LoginUserSerializer(serializers.ModelSerializer):
class Meta:
model = UserModel
fields = ('username', 'password')
def validate(self, data):
user = authenticate(**data)
if user:
if user.is_active:
return user
raise exceptions.AuthenticationFailed('Account is not activated')
raise exceptions.AuthenticationFailed()
And view:
from django.contrib.auth import login
from rest_framework.permissions import AllowAny
from rest_framework.authtoken.serializers import AuthTokenSerializer
from knox.views import LoginView
from .serializers import LoginUserSerializer
class LoginUserView(LoginView):
serializer_class = LoginUserSerializer
permission_classes = [AllowAny]
def post(self, request, *args, **kwargs):
serializer = AuthTokenSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
login(request, user)
return super(LoginUserView, self).post(request)
And with that code, I stumbled upon problem: when I try to log in with already activated account, everything looks fine, but when I try unactivated one, instead of Account is not activated, I get:
{
"non_field_errors": [
"Unable to log in with provided credentials."
]
}
Which I think, comes rather from view than the serializer.
Okay, so thanks to Shafikur Rahman suggestion I was able to make it work. After I tried to debug it with pdb and set trace inside LoginUserSerializer but nothing happened, I realized that in my views I'm not pointing to the serializer I wrote, but to AuthTokenSerializer. Even after that it still didn't work, because of my lack of understanding of how django login() and DRF validate() works. Below fixed code for reference:
view:
class LoginUserView(LoginView):
serializer_class = LoginUserSerializer
permission_classes = [AllowAny]
def post(self, request, *args, **kwargs):
serializer = LoginUserSerializer(data=request.data) # changed to desired serializer
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
login(request, user)
return super(LoginUserView, self).post(request)
and serializer:
class LoginUserSerializer(serializers.ModelSerializer):
username = serializers.CharField() # added missing fields for serializer
password = serializers.CharField()
class Meta:
model = UserModel
fields = ('username', 'password')
def validate(self, data):
user = authenticate(**data)
if user:
if user.is_active:
data['user'] = user # added user model to OrderedDict that serializer is validating
return data # and in sunny day scenario, return this dict, as everything is fine
raise exceptions.AuthenticationFailed('Account is not activated')
raise exceptions.AuthenticationFailed()
Additionally to be able to authenticate() not active user, I had to add
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.AllowAllUsersModelBackend'
]
in project settings.
As per the DRF documentation, the serializer_class attribute should be set when using GenericAPIView. But why does the serializer_class attribute even works with APIView?
Here is my API code:
class UserView(APIView):
serializer_class = SignupSerializer
#transaction.atomic
def post(self, request):
email = get_data_param(request, 'email', None)
password = get_data_param(request, 'password', None)
params = request.POST.copy()
params["username"] = email
serializer = UserSerializer(data=params)
if serializer.is_valid(raise_exception=True):
user = serializer.save()
user.set_password(password)
user.save()
params["user"] = user.id
serializer = CustomProfileSerializer(data=params)
if serializer.is_valid(raise_exception=True):
profile = serializer.save()
return Response(response_json(True, profile.to_dict(), None))
class SignupSerializer(serializers.Serializer):
email = serializers.EmailField(max_length=100)
password = serializers.CharField(max_length=50)
When I browse this API in the browser it does show the email and password fields as input but if I don't set this serializer_class attribute, no input fields are shown.
Ideally, this serializer_class attribute should not work with APIView. I have searched a lot but there is nothing available related to this.
Can anyone please provide an explanation for this behavior? Thanks.
I think this can help you.
create serializer.py and write:
from rest_framework import serializers
from .models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('email', 'password')
and views.py:
from rest_framework.response import Response
from rest_framework.views import APIView
from .serializers import UserSerializer
from .models import User
class AddUserAPIView(APIView):
def post(self, request):
user_serializer = UserSerializer(data=request.data)
if user_serializer.is_valid():
user = user_serializer.save()
user.set_password(user_serializer.data["password"])
return Response({'message': 'user added successfully!'})
return Response({'message': user_serializer.errors})
You are absolutely right!!:
APIView doesn't utilize a serializer_class (by default) because it is not meant to handle any request processing logic!
What happens though is that the BrowsableAPIRenderer that is used to render the API in the browser checks for a serializer_class attribute and set's it as the View serializer if it exists. We can see this in the BrowsableAPIRenderer code:
The _get_serializer class of the renderer:
def _get_serializer(self, serializer_class, view_instance, request, *args, **kwargs):
kwargs['context'] = {
'request': request,
'format': self.format,
'view': view_instance
}
return serializer_class(*args, **kwargs)
And the way it is used to set the renderer serializer if it exists, inside the get_rendered_html_form:
Line 483: has_serializer_class = getattr(view, 'serializer_class', None)
Lines 497 to 509:
if has_serializer:
if method in ('PUT', 'PATCH'):
serializer = view.get_serializer(instance=instance, **kwargs)
else:
serializer = view.get_serializer(**kwargs)
else:
# at this point we must have a serializer_class
if method in ('PUT', 'PATCH'):
serializer = self._get_serializer(view.serializer_class, view,
request, instance=instance, **kwargs)
else:
serializer = self._get_serializer(view.serializer_class, view,
request, **kwargs)
In essence, you accidentally override the BrowserAPIRenderer's default behavior regarding the APIView by providing the serializer_class attribute. For what is worth, my opinion on the matter is that this should not be possible!
I use the django rest framework default get_schema_view() to provide auto-generated openapi schema from which I auto generate a javascript client for.
This works for ViewSets, but the payload wasn't being provided for views defined by APIView.
Where I have defined serializers, I found that adding get_serializer() method to my APIView classes allowed the schema to be generated with the serializer defined payload.
from rest_framework.response import Response
from rest_framework.views import APIView
from .serializers import UserSerializer
from .models import User
class AddUserAPIView(APIView):
def get_serializer(self, *args, **kwargs):
return UserSerializer(*args, **kwargs)
def post(self, request):
user_serializer = UserSerializer(data=request.data)
if user_serializer.is_valid():
user = user_serializer.save()
user.set_password(user_serializer.data["password"])
return Response({'message': 'user added successfully!'})
return Response({'message': user_serializer.errors})