API to create users

Example of an API that uses POST data to add a new entry to database

1) Create a new app user

python manage.py startapp user

2) Remove the following redandunt files

# all database stufffs will be in core app
a) migrations folder 
b) models.py 
c) admin.py
# remove test.py and create a directory test with __init__.py

3) Add rest_framework and user to INSTALLED_APPS in settings.py

What should this API do ?

For now, let us target for an API that will manage creation of users and write testcases for each senario

We ll use rest_framework.test.APIClient to simulate the API client

Remember, users created in each test will not be saved to the actual db. So for each test we have to create new users.

from django.test import TestCase
from django.contrib.auth import get_user_model
from django.urls import reverse
from rest_framework.test import APIClient
from rest_framework import status


CREATE_USER_URL = reverse("user:create")


def create_user(**kwargs):
    return get_user_model().objects.create_user(**kwargs)


# 'Public' because we dont check for authentication
class PublicUserApiTests(TestCase):
    """Test the users API (public)"""

    def setUp(self):
        self.client = APIClient()

    # test API call to create new user
    def test_create_valid_user_success(self):
        """test creating user with valid payload
        payload is a POST dict"""

        payload = {
            'email': 'test@test.com',
            'password': 'testpass',
            'name': 'tester'
        }

        # This call to API should create a new user
        res = self.client.post(CREATE_USER_URL, payload)

        # assert if the response is 201 created
        self.assertEqual(res.status_code, status.HTTP_201_CREATED)
        # get this user and check if password is correct
        user = get_user_model().objects.get(**res.data)
        self.assertTrue(user.check_password(payload['password']))
        # assert that the password is not returned in the response data
        self.assertNotIn('password', res.data)

    # Test api when trying to create existing users
    def test_user_exists(self):
        # Note, the users created in previous test cases do not exist
        payload = {'email': 'test@test.com', 'password': 'testpass'}
        # create this user
        create_user(**payload)

        # try to create this user again with APIClient
        res = self.client.post(CREATE_USER_URL, payload)
        # assert that the response is 400
        self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)

    # Test if user is not created when password is too short
    def test_password_too_short(self):
        # Note, the users created in previous test cases do not exist
        payload = {'email': 'test@test.com', 'password': 'tp'}
        res = self.client.post(CREATE_USER_URL, payload)

        # assert response failed
        self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)

        # assert user was not created
        user_exists = get_user_model().objects.filter(
            email=payload['email']
        ).exists()
        self.assertFalse(user_exists)

Implementing the API

1) For an API to create user, we ll have to define a serializer for user model

2) Create a new file named serializers.py under user app

from django.contrib.auth import get_user_model
from rest_framework import serializers


class UserSerializer(serializers.ModelSerializer):
    """serializer for user object"""

    class Meta:
        model = get_user_model()
        fields = ('email', 'password', 'name')

        # the password should be write only.
        # it should not be serialized when get is called
        # we specify extra kwargs for each field
        # list of accepted args for can be found under core argument section of
        # https://www.django-rest-framework.org/api-guide/fields/
        # for password field, args under serializer.CharField are also valid
        extra_kwargs = {'password': {'write_only': True, 'min_length': 5}}

    # create is called when we use the CreateAPI view
    # which takes a POST request to create a user
    def create(self, validated_data):
        return get_user_model().objects.create_user(**validated_data)

3) define a view to create users in views.py

from rest_framework import generics
from user.serializers import UserSerializer


# Create your views here.
class CreateUserView(generics.CreateAPIView):
    """Create a new user"""
    serializer_class = UserSerializer

4) create urls

#user/urls.py
from django.urls import path
from user import views

app_name = 'user'

urlpatterns = [
    path('create/', views.CreateUserView.as_view(), name='create'),
]

#app/urls.py
urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/user/', include('user.urls')),
]

5) On typing the url on the browser, (but this link should be called from client code with POST dict)