Search code examples
jqueryajaxdjangodjango-rest-frameworkdjango-csrf

How to use django only a backend and post with django-rest-framework


I will be using Django only as the backend. The front end will be done using React and no django templates. I am using django-rest-framework to create a rest api for my website.

I made a serializer for the user.

class CustomUserSerializer(serializers.ModelSerializer):
    class Meta:
        model = CustomUser
        fields = (
            'id', 'email', 'password', 'username', 'first_name', 'last_name', 'date_of_birth', 'gender', 'mobile_number'
        )
        extra_kwargs = {
            'password': {'write_only': True},
            'id': {'read_only': True}
        }

    def create(self, validated_data):
        user = CustomUser.objects.create(
            email=validated_data['email'],
            username=validated_data['email'],
            first_name=validated_data['first_name'],
            last_name=validated_data['last_name'],
            date_of_birth=validated_data['date_of_birth'],
            gender=validated_data['gender'],
            mobile_number=validated_data['mobile_number']
        )
        user.set_password(validated_data['password'])
        user.save()
        return user

class CustomUserViewSet(viewsets.ModelViewSet):
    queryset = CustomUser.objects.all()
    serializer_class = CustomUserSerializer

In the browser, when I go to /custom/users/ I can view the users. I can also create new users which after successful registration returns back the user. Also it works if I use httpie/curl.

(djangoweb) vagrant@precise32:~$ http --json POST http://55.55.55.5/custom/users/ email="[email protected]" password="terminal2123" username="t223erm" first_name="te2er" last_name="mi2nal" date_of_birth=1992-12-12 gender=2 mobile_number=66222666666336

It creates and returns the new user object.

So I made a form to register a user which I am not serving from the django server:

<form action="http://55.55.55.5/custom/users/" method="post" id="register-form">
    <input type="text" placeholder="email" name="email"/>
    ...
    ...
    <button id="post">Register</button>
</form>

And ajax to post the form.

// using the javscript Cookies library
var csrftoken = Cookies.get('csrftoken');

function csrfSafeMethod(method) {
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
    beforeSend: function(xhr, settings) {
        if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken);
        }
    }
});

$('#post').click(function(event) {
    event.preventDefault();
    var $form = $('#register-form');
    var data = $form.serialize();        
    $.ajax({
        type: "POST",
        url: "http://55.55.55.5/custom/users/",
        data: JSON.stringify(data),
        sucess: function() { console.log("Success!"); },
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        crossDomain:false,
        beforeSend: function(xhr, settings) {
          xhr.setRequestHeader("X-CSRFToken", csrftoken);
        }
    });
});

Now after if I click the button, the problem starts:

  • Even though I gave event.preventDefault() the page is automatically loaded to the url of the django server (i.e., http://55.55.55.5/custom/users/).
  • I get an error: "detail": "CSRF Failed: CSRF token missing or incorrect."

How can I handle the post to the django server using django-rest-framework? I didn't find any helping material for this problem. Could you please guide me how to? Thank you.


Solution

  • You can use csrf_exempt for the registration and login functions. As an example, here how you can create the registration and login APIs. See how my login API returns the token. See http://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication.

    I tried to edit my code to replace with your model names, but I did not test it, so you may need to fix any typos.

    class AccountViewSet(viewsets.ModelViewSet):
        queryset = CustomUser.objects.all()
        serializer_class = CustomUserSerializer
    
        def get_permissions(self):
    
            if self.request.method in permissions.SAFE_METHODS:
                return (permissions.IsAuthenticated(),)
    
            if self.request.method == 'POST':
                return (permissions.AllowAny(),)
    
            return (permissions.IsAuthenticated(), IsAccountOwner(),)
    
        @csrf_exempt
        def create(self, request):
            '''
            When you create an object using the serializer\'s .save() method, the
            object\'s attributes are set literally. This means that a user registering with
            the password \'password\' will have their password stored as \'password\'. This is bad
            for a couple of reasons: 1) Storing passwords in plain text is a massive security
            issue. 2) Django hashes and salts passwords before comparing them, so the user
            wouldn\'t be able to log in using \'password\' as their password.
    
            We solve this problem by overriding the .create() method for this viewset and
            using Account.objects.create_user() to create the Account object.
            '''
    
            serializer = self.serializer_class(data=request.data)
    
            if serializer.is_valid():
                password = serializer.validated_data['password']
                confirm_password = serializer.validated_data['confirm_password']
    
                if password and confirm_password and password == confirm_password:
    
                    user = CustomUser.objects.create_user(**serializer.validated_data)
    
                    user.set_password(serializer.validated_data['password'])
                    user.save()
    
                    return Response(serializer.validated_data, status=status.HTTP_201_CREATED)
    
            return Response({'status': 'Bad request',
                             'message': 'Account could not be created with received data.'
                            }, status=status.HTTP_400_BAD_REQUEST)
    
    class APILoginViewSet(APIView):
    
        @csrf_exempt
        def post(self, request, format=None):
            data = JSONParser().parse(request)
            serializer = LoginCustomSerializer(data=data)
    
            if serializer.is_valid():
                email = serializer.data.get('email')
                password = serializer.data.get('password')
    
                if not request.user.is_anonymous():
                    return Response('Already Logged-in', status=status.HTTP_403_FORBIDDEN)
    
                user = authenticate(email=email, password=password)
    
                if user is not None:
                    if user.is_active:
                        login(request, account)
    
                        serialized = UserSerializer(user)
                        data = serialized.data
    
                        # Add the token to the return serialization
                        try:
                            token = Token.objects.get(user=user)
                        except:
                            token = Token.objects.create(user=user)
    
                        data['token'] = token.key
    
                        return Response(data)
                    else:
                        return Response('This account is not Active.', status=status.HTTP_401_UNAUTHORIZED)
                else:
                    return Response('Username/password combination invalid.', status=status.HTTP_401_UNAUTHORIZED)
    
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    
        def get(self, request, format=None):
            data_dic = {"Error":"GET not supported for this command"}
            return Response(data_dic, status=status.HTTP_400_BAD_REQUEST)
    

    You can see a full working example at https://github.com/dkarchmer/django-aws-template (disclaimer, that's my code).