This project was working before, but then Heroku took away free tier so I have been trying to deploy somewhere else, but now all of a sudden I cannot even create a user locally even though I could before... Now when I create a user I get the error mentioned in the title.
serializers folder
common.py file
from xml.dom import ValidationErr
from rest_framework import serializers
from django.contrib.auth import get_user_model, password_validation
from django.contrib.auth.hashers import make_password
User = get_user_model()
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
confirm_password = serializers.CharField(write_only=True)
def validate(self, data):
password = data.pop('password')
confirm_password = data.pop('confirm_password')
if password != confirm_password:
raise ValidationErr({ 'confirm_password': 'Does not match the password'})
password_validation.validate_password(password)
data['password'] = make_password(password)
return data
class Meta:
model = User
fields = ('id', 'username', 'email', 'password', 'confirm_password')
views.py file
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.exceptions import PermissionDenied
from django.contrib.auth import get_user_model
import jwt
User = get_user_model()
from datetime import datetime, timedelta
from jwt_auth.serializers.common import UserSerializer
from django.conf import settings
class RegisterView(APIView):
def post (self, request):
create_user = UserSerializer(data=request.data)
try:
create_user.is_valid(True)
create_user.save()
return Response(create_user.data, status=status.HTTP_201_CREATED)
except Exception as e:
print(str(e))
return Response(e.__dict__ if e.__dict__ else str(e), status=status.HTTP_422_UNPROCESSABLE_ENTITY)
class LoginView(APIView):
def post(self, request):
password = request.data.get('password')
username = request.data.get('username')
try:
user_login = User.objects.get(username=username)
except User.DoesNotExist:
raise PermissionDenied('Credentials are incorrect!')
if not user_login.check_password(password):
raise PermissionDenied('Credentials are incorrect!')
dt = datetime.now() + timedelta(days=7)
token = jwt.encode(
{
'sub': user_login.id,
'exp': int(dt.strftime('%s'))
},
settings.SECRET_KEY,
'HS256'
)
print('TOKEN ----->', token)
return Response({ 'token': token, 'message': f'Welcome back {user_login.username}' })
rest-framework branch "3.9.x" (github)
def is_valid(self, raise_exception=False):
...
rest-framework branch "master" (github)
def is_valid(self, *, raise_exception=False):
...
so "master" now: is_valid() only accepts keyword argument "raise_exception=True":
class RegisterView(APIView):
def post (self, request):
create_user = UserSerializer(data=request.data)
try:
create_user.is_valid(raise_exception=True)
....
Change it into something like this:
def post(self, request):
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
return Response(user.data, status=status.HTTP_201_CREATED)
the rest of your code...
Related
from django.db import models
from django.contrib.auth.models import (
AbstractBaseUser, BaseUserManager, PermissionsMixin)
from rest_framework_simplejwt.tokens import RefreshToken
class UserManager(BaseUserManager):
def create_user(self,username,email, password=None ):
if username is None:
raise TypeError("Users should have a username")
if email is None:
raise TypeError("Users should have an Email")
user =self.model(username=username, email=self.normalize_email(email))
user.set_password(password)
user.save()
return user
def create_superuser(self, username, email, password=None):
if password is None:
raise TypeError("Password should not be none")
user = self.create_user(username, email, password)
user.is_superuser = True
user.is_staff = True
user.save()
return user
class User(AbstractBaseUser, PermissionsMixin):
username = models.CharField(max_length=100, unique=True, db_index=True)
email = models.EmailField(max_length=100, unique=True, db_index=True)
is_verified = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["username"]
objects = UserManager()
def __str__(self):
return self.email
def tokens(self):
refresh = RefreshToken.for_user(self)
return {
"refresh":str(refresh),
"access": str(refresh.access_token)
}
Below is the serializers.py file
from .models import User
from rest_framework import serializers
from django.contrib import auth
from rest_framework.exceptions import AuthenticationFailed
class RegisterSerializer(serializers.ModelSerializer):
password = serializers.CharField(max_length=50, min_length=6, write_only =True)
class Meta:
model = User
fields = ["email", "username", "password"]
def validate(self, attrs):
email = attrs.get("email", '')
username = attrs.get("username", '')
if not username.isalnum():
raise serializers.ValidationError("The username should contain only alphanumeric characters")
return attrs
def create(self, validated_data):
return User.objects.create_user(**validated_data)
class EmailVerificationSerializer(serializers.ModelSerializer):
token = serializers.CharField(max_length=555)
class Meta:
model = User
fields = ["token"]
class LoginSerializer(serializers.ModelSerializer):
email = serializers.EmailField(max_length=255,min_length=3)
password = serializers.CharField(max_length=68, min_length=6, write_only=True)
username = serializers.CharField(max_length=255,min_length=3, read_only=True)
tokens = serializers.CharField(max_length=68, min_length=6,read_only=True)
class Meta:
model = User
fields = ["email", "password","username","tokens"]
def validate(self,attrs):
email = attrs.get("email", "")
password = attrs.get("password", "")
user = auth.authenticate(email=email, password=password)
if not user:
raise AuthenticationFailed("Invalid Credentials, try again")
if not user.is_active:
raise AuthenticationFailed("Account disabled, contact admin")
if not user.is_verified:
raise AuthenticationFailed("Email is not verified")
return super().validate(attrs,{
"email":user.email,
"username": user.username,
"tokens":user.tokens
})
Below is the views.py file
**from django.shortcuts import render
from rest_framework import generics, status, views
from .serializers import EmailVerificationSerializer, RegisterSerializer, LoginSerializer
from rest_framework.response import Response
from rest_framework_simplejwt.tokens import RefreshToken, AccessToken
from .models import User
from .utils import Util
from django.contrib.sites.shortcuts import get_current_site
from django.urls import reverse
from django.conf import settings
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
import jwt**
# Create your views here.
class RegisterView(generics.GenericAPIView):
serializer_class = RegisterSerializer
def post(self, request):
user = request.data
serializer = self.serializer_class(data=user)
serializer.is_valid(raise_exception=True)
serializer.save()
user_data= serializer.data
user = User.objects.get(email=user_data["email"])
token = RefreshToken.for_user(user).access_token
current_site = get_current_site(request).domain
relativeLink = reverse("email-verify")
absurl= "http://" + current_site + relativeLink + "?token="+ str(token)
email_body ="Hi "+ user.username + " Use link below to verify your email \n" + absurl
data = {"email_body":email_body,"to_email":user.email, "email_subject":"Verify your email"}
Util.send_email(data)
return Response(user_data, status=status.HTTP_201_CREATED)
class VerifyEmail(views.APIView):
serializer_class = EmailVerificationSerializer
token_param_config = openapi.Parameter(
"token", in_=openapi.IN_QUERY, description="Description",type=openapi.TYPE_STRING)
#swagger_auto_schema(manual_parameters=[token_param_config])
def get(self,request):
token = request.GET.get("token")
try:
payload = jwt.decode(token, settings.SECRET_KEY)
user = User.objects.get(id=payload["user_id"])
if not user.is_verified:
user.is_verified = True
user.save()
return Response({"email":"Successfully activated"}, status=status.HTTP_200_OK)
except jwt.ExpiredSignatureError as identifier:
return Response({"error": "Activation Link Expired"}, status=status.HTTP_400_BAD_REQUEST)
except jwt.exceptions.DecodeError as identifier:
return Response({"error": "Invalid token"}, status=status.HTTP_400_BAD_REQUEST)
class LoginAPIView(generics.GenericAPIView):
serializer_class = LoginSerializer
def post(self, request):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
return Response(serializer.data, status=status.HTTP_200_OK)
Below is the utils.py for sending a mail
from django.core.mail import EmailMessage
class Util:
#staticmethod #We'll use the class method without instantiating it
def send_email(data):
email =EmailMessage(
subject=data["email_subject"], body=data["email_body"], to=[data["to_email"]])
email.send()
Below is the urls.py
from django.urls import path
from .views import RegisterView, VerifyEmail, LoginAPIView
urlpatterns = [
path("register/", RegisterView.as_view(), name="register"),
path("email-verify/", VerifyEmail.as_view(), name="email-verify"),
path("login/", LoginAPIView.as_view(), name="login")
]
Below is the verification mail sent
A verification mail sent
Below are the error gotten while trying to verify the token
A screenshot image description
at VerifyEmailView class the payload needs the algorithm with which you decode your token.`class VerifyEmail(views.APIView):
serializer_class = EmailVerificationSerializer
token_param_config = openapi.Parameter(
"token", in_=openapi.IN_QUERY, description="Description",type=openapi.TYPE_STRING)
#swagger_auto_schema(manual_parameters=[token_param_config])
def get(self,request):
token = request.GET.get("token")
try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithms='HS256')
user = User.objects.get(id=payload["user_id"])
if not user.is_verified:
user.is_verified = True
user.save()
return Response({"email":"Successfully activated"}, status=status.HTTP_200_OK)
except jwt.ExpiredSignatureError as identifier:
return Response({"error": "Activation Link Expired"}, status=status.HTTP_400_BAD_REQUEST)
except jwt.exceptions.DecodeError as identifier:
return Response({"error": "Invalid token"}, status=status.HTTP_400_BAD_REQUEST)`
Im having a lot of problems in my first django, rest-framework application.
Im not getting the token after Im signing up, instead of
"user" : {
"_id":"1",
"username":"you",
"email":"a#a.com"
},
"token" : 'fjdjfkljgkghgjkl'
}
Im getting
{
"_id":"1",
"username":"you",
"email":"a#a.com"
}
what could be the problem here?
When Im trying to sign in, postman tells me:
{"username" :["user with this username already exist.]}
and Im trying to sign in not sign up.
why is this happening?
When Im trying to get all the todos that belongs to the user I get this error:
{"details":"Authentication credantials were not provided" }
, instead of an empty list, why is that?
how can I get the user if I have only the token?
serializer code:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('_id', 'username', 'email', 'password')
extra_kwarg = { 'password' : {
'write_only' : True
}}
class SignUpSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('_id', 'username', 'email', 'password')
extra_kwarg = { 'password' : {
'write_only' : True
}}
def create_user(self, validated_data):
user = User.objects.create_user(validated_data['username'], validated_data['email'], validated_data['password'])
return user
class SignInSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'password')
def validate(self, data):
username = data.get("username", None)
password = data.get("password", None)
user = authenticate(username=username, password=password)
if user is None:
raise serializers.ValidationError('A user with this username and password is not found.')
return user
viewset code:
class SignUpViewSet(viewsets.ModelViewSet):
serializer_class = SignUpSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data = request.data)
serializer.is_valid(raise_exception = True)
user = serializer.save()
return Response({
'user': SignUpSerializer(user, context = self.get_serializer_context()).data,
'token': AuthToken.objects.create(user)
})
class SignInViewSet(viewsets.ModelViewSet):
serializer_class = SignInSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data = request.data)
serializer.is_valid(raise_exception = True)
user = serializer.validated_data
return Response({
'user': UserSerializer(user, context = self.get_serializer_context()).data,
'token': AuthToken.objects.create(user)
})
class GetUserViewSet(viewsets.ModelViewSet):
permission_classes = [
permissions.IsAuthenticated
]
serializer_class = UserSerializer
def get_object(self):
return self.request.user
Issues which you have listed are mainly cause of point 2.
{"username" :["user with this username already exist.]}
If you read it more carefully, it says user already exist. So it must be trying to create a user instead of letting them login. And this behavior is excepted because the way you have used serializer in your SignInViewSet.
class SignInViewSet(viewsets.ModelViewSet):
serializer_class = SignInSerializer
def post(self, request, *args, **kwargs):
serializer = SignInSerializer()
user = serializer.validate(attrs=request.data)
return Response({
'user': UserSerializer(user, context = self.get_serializer_context()).data,
'token': AuthToken.objects.create(user)
})
I have removed get_serializer because I am not fimilar with it, but it should work fine. Main take away is that use serializer.is_valid() only when you are updating or creating stuffs in database. As you can see right now it is trying to create a new user instance and that's why the error user with this username already exists. After this you should have your token and then set Headers in your Postman, if everything goes well you can access your user with request.user in your views.
UPDATE:
views.py
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from knox.models import AuthToken
from .serializers import SignInSerializer, SignUpSerializer, UserSerializer
# Create your views here.
class SignUpView(APIView):
serializer_class = SignUpSerializer
def post(self, request):
serializer = SignUpSerializer(data = request.data)
if serializer.is_valid():
user = serializer.save()
_, token = AuthToken.objects.create(user)
return Response({'user': serializer.data, 'token': token})
else:
return Response(serializer.errors)
serializers.py
class SignUpSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email', 'password')
extra_kwarg = { 'password' : {'write_only' : True }}
def create_user(self, validated_data):
user = User.objects.create_user(username=validated_data['username'], email=validated_data['email'], password=validated_data['password'])
user.save()
return user
Since you are not using viewsets I have shifted SignUpView to views.py and I have removed your extra validation from serializers as it won't work, instead if there is any error in your serializer it will be handled in your views, check if serializer.is_valid() and else statement. And your urls.py in user app will be something like this.
from rest_framework import routers
from .views import SignUpView
from django.urls import path
# user_router = routers.DefaultRouter()
# user_router.register(r'sign_up', SignUpViewSet, basename='sign_up')
# user_router.register(r'sign_in', SignInViewSet, basename='sign_in')
# user_router.register(r'user', GetUserViewSet, basename='user')
urlpatterns = [
path('sign_up/', SignUpView.as_view()),
]
and backend/urls.py will be
from django.contrib import admin
from django.urls import include,path
from todo.urls import todo_router
from django.views.generic import TemplateView
urlpatterns = [
path('admin/', admin.site.urls),
path('api/todo/', include(todo_router.urls)),
path('api/auth/', include('user.urls')),
path('', TemplateView.as_view(template_name = 'index.html'))
]
Check output:
NOTE: self.get_serializer(data = request.data) is only available for ViewSets, that's why views.py has been edited. You can similarly update your other views too.
POST request for creating user is working fine, but when I preform PUT method on a user and change the password I'm getting Invalid password format or unknown hashing algorithm, so I'm a bit confused why is this happening, so can someone please help me overcome this.
MyUserSerializer
from rest_framework import serializers
from business_accounts.models.my_user import MyUser
class MyUserSerializer(serializers.ModelSerializer):
"""
TODO: MyUser model Serializers
:return: TODO
"""
password = serializers.CharField(min_length=8, write_only=True)
class Meta:
model = MyUser
fields = '__all__'
def create(self, validated_data):
user = super(MyUserSerializer, self).create(validated_data)
user.set_password(validated_data['password'])
user.save()
return user
User Detailed APIView
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from ..serializers.my_user_serializers import MyUserSerializer
from business_accounts.models.my_user import MyUser
class UserDetailView(APIView):
"""
User detail api view
"""
def get_object(self, pk):
try:
return MyUser.objects.get(pk=pk)
except MyUser.DoesNotExist:
raise Http404
def get(self, request, pk):
user = self.get_object(pk)
serializer = MyUserSerializer(user)
return Response(serializer.data)
def put(self, request, pk):
user = self.get_object(pk)
serializer = MyUserSerializer(user, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
user = self.get_object(pk)
user.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
You need to use hashing algorithm.
check -> this
You need to override serializer's update method also to correctly set password for PUT request:
class MyUserSerializer(serializers.ModelSerializer):
"""
TODO: MyUser model Serializers
:return: TODO
"""
password = serializers.CharField(min_length=8, write_only=True)
class Meta:
model = MyUser
fields = '__all__'
def create(self, validated_data):
user = super(MyUserSerializer, self).create(validated_data)
user.set_password(validated_data['password'])
user.save()
return user
def update(self, instance, validated_data):
password = validated_data.pop('password')
user = super(MyUserSerializer, self).update(instance, validated_data)
if password:
user.set_password(password)
user.save()
return user
Note set_password method is using for password hasing.
I'm a bit confused about token authentication. After a few question and attempts I've manage to create url gateway for auto-login my users but I'm managing to do that only by using user_id and passing it out in the url for example http://example.com/auth/login/?user_id=12 but I would rather do it with ?token=.
I'm using DRF example on how make custom auth token and return some more data about my user with that so after I curl to the url I'm returning
{"token":"d5d86e55fd5ddd48298b2ac72c3ed96b7e30dd86","user_id":52}
Now the problem that I'm facing is this MyUser maching query does not exists which is normal I didn't have token in my model so I've created one
token = models.CharField(max_length=125, null=True, blank=True)
so I could overcome DoesNotExist error but the error is still there.
I'm using this hack for the gateway login
from django.contrib.auth import authenticate, login
from django.core.urlresolvers import reverse
from django.views.generic import View
from django.http import HttpResponseRedirect
from business_accounts.models.my_user import MyUser
class UrlGatewayLogin(View):
def get(self, request, **kwargs):
page_group = kwargs.get('page_group')
token = request.GET.get('token')
user = MyUser.objects.get(token=token)
user.backend = 'django.contrib.auth.backends.ModelBackend'
login(request, user)
return HttpResponseRedirect(reverse('dashboard', args=(page_group, )))
Is DRF token unique per user, and how can I generate token in django and use it for my gateway?
You need to install Django-Rest-Framework and enable TokenAuthentication in the settings.
First you have to write the a serializer for your User Model, and then create an api view with the serialized data for token authentication.
For example (You can also use username instead of email):
In your users/api/serializers.py file:
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from rest_framework.compat import authenticate
from ..models import User
class AuthTokenSerializer(serializers.Serializer):
email = serializers.EmailField(label=_("Email Address"))
password = serializers.CharField(
label=_("Password"),
style={'input_type': 'password'},
trim_whitespace=False
)
def validate(self, data):
email = data.get('email')
password = data.get('password')
if email and password:
user = authenticate(email=email, password=password)
if user:
if not user.is_active:
msg = _('User account is deactivated.')
raise serializers.ValidationError(msg)
else:
msg = _('Unable to log in with provided credentials.')
raise serializers.ValidationError(msg)
else:
msg = _('Must include "email" and "password".')
raise serializers.ValidationError(msg)
data['user'] = user
return data
Then in your users/api/views.py file:
from rest_framework.response import Response
from .serializers import AuthTokenSerializer
from ..models import User
class ObtainAuthToken(APIView):
throttle_classes = ()
permission_classes = ()
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
renderer_classes = (renderers.JSONRenderer,)
serializer_class = AuthTokenSerializer
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)
return Response({'token': token.key, 'username':user.username})
Then in settings, make sure you have token authentication turned on as such:
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
}
In order to return data about the user with that token, you need to write another serializer on the User model including the fields you want to return, for instance:
class UserDetailSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = [
'username', 'email', 'hobbies', 'language',
'gender', 'birthday'
]
And write another api view such as:
class UserDetailAPIView(RetrieveAPIView):
permission_classes = [IsAuthenticated]
serializer_class = UserDetailSerializer
def get_object(self):
obj = get_object_or_404(User, username=self.request.user.username)
return obj
The key is to write serializers for your model so that the data can be transported on the Internet.
I am having problems with my login API view. When trying to login I get a POST 404 but I know the url path is correct because if I put a simple GET request in the view it returns data. There are still some rough parts of my code but I thought this would work.
login.py
from django.contrib.auth import authenticate
from django.shortcuts import get_object_or_404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from apiV1.v1.accounts.models.user import User
from apiV1.v1.accounts.serializers.user import UserSerializerLogin
# login
class LoginView(APIView):
authentication_classes = ()
permission_classes = ()
#staticmethod
def post(request):
user = get_object_or_404(User, email=request.data.get('email'))
user = authenticate(username=user.email, password=request.data.get('password'))
if user:
serializer = UserSerializerLogin(user)
return Response(serializer.data)
return Response(status=status.HTTP_400_BAD_REQUEST)
serializers.py
class UserSerializerLogin(UserSerializer):
token = serializers.SerializerMethodField()
#staticmethod
def get_token(user):
"""
Get or create token
"""
token, created = Token.objects.get_or_create(user=user)
return token.key
class Meta:
model = User
fields = ('id', 'email', 'first_name', 'last_name', 'profile', 'role', 'token')
POST
{
"email": "admin",
"password": "password"
}