djangodjango-rest-frameworkdjango-rest-knox

cant sign in user, not getting the right response, trying to get user with token


Im having a lot of problems in my first django, rest-framework application.

  1. 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?

  1. 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?

  1. 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?

  1. 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


Solution

  • 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: here

    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.