Building a carpool web API with Django (part 4)

4 minute read

In part 3 of our series we added more models to our system. In this post we are going to start working on the REST API. We are going to install the library that we will use as well as serialize our User model, create a ViewSet for it and add some permissions to it. It is going to be a very short post at the end of which we will have our first REST endpoint for our API.

We are going to use Django REST Framework which is the most popular toolkit for building web APIs with Django. It allows us to build a browsable API which I personally think is very cool. Let’s install the library by running the command below – remember to first activate your virtual environment by running pipenv shell:

pipenv install djangorestframework==3.10.2

Add it to INSTALLED_APPS:

# kapool_project/settings.py
INSTALLED_APPS = [
    # ...
    'rest_framework', # new
]

Now let’s configure our REST frame work by adding default permission classes:

# kapool_project/settings.py
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
    ]
}

The default permission class we have added (DjangoModelPermissionsOrAnonReadOnly) will grant read-only access to unauthenticated users.

Authentication views

Since we want our API to be browsable we need to add REST framework’s authentication views to enable us to login and logout. Go to kapool_project/urls.py and add the following to your urlpatterns list:

# kapool_project/urls.py
urlpatterns = [
    # ...
    path('api-auth/', include('rest_framework.urls', namespace='rest_framework')), # new
]

User serializer

Now we are going to create a serializer for our User model. Serializers are used to convert complex data such as querysets and model instances to navite Python datatypes that can be rendered into JSON, XML or other content types. For more information about serializers visit the official site. We want our API to use JSON content type so we need to create serializers for our models. Inside the users/ directory create serializers.py and add the following:

# users/serializers.py
from datetime import date
from dateutil.relativedelta import relativedelta
from django.contrib.auth import get_user_model
from rest_framework import serializers
from django.contrib.auth.models import User


class UserSerializer(serializers.HyperlinkedModelSerializer):

    def validate_birth_date(self, value):
        """
        Validates `birth_date`.
        """
        if value:
            if value > date.today():
                raise serializers.ValidationError(
                    'Birth date cannot be in the future'
                )
            age = relativedelta(date.today(), value).years
            if age < 18:
                raise serializers.ValidationError(
                    'User should be 18 years or older'
                )
        return value

    class Meta:
        model = get_user_model()
        fields = [
            'username',
            'email',
            'first_name',
            'last_name',
            'gender',
            'birth_date',
            'url',
        ]

User ViewSet

We are going to create a ViewSet for our User model. ViewSets are classes that provide you will all the CRUD actions for a given model. Go to users/views.py and create the UserViewSet:

# users/views.py
from django.contrib.auth import get_user_model
from rest_framework import viewsets, mixins

from .serializers import UserSerializer

User = get_user_model()


class UserViewSet(
        mixins.ListModelMixin,
        mixins.RetrieveModelMixin,
        mixins.UpdateModelMixin,
        viewsets.GenericViewSet):

    queryset = User.objects.all()
    serializer_class = UserSerializer

Putting everything together

Now that we have a serializer and a ViewSet for our model. Let’s create an endpoint through which we can access it. We do this by registering our ViewSet to a router. Inside kapool_project/ create api.py and add the following:

# kapool_project/api.py
from rest_framework import routers
from users.views import UserViewSet

router = routers.DefaultRouter()
router.register('users', UserViewSet)

Now let’s go to kapool_project/urls.py and register our API router:

# kapool_project/urls.py
# ...
from .api import router # new


urlpatterns = [
    # ...
    path('api/v1/', include(router.urls)), # new
]

Run your server (python manage.py runserver) and go to http://localhost:8000/api/v1/. You will see the API root that gives you a list of all end points in your API.

Django welcome page
KaPool API root

We have successfully created a browsable web API! You may navigate to the users endpoint to list and edit your users. Since we added urls to the authentication authentication views you may also login to the API.

Permissions

As you may have noticed, any authenticated user can update details of any user in our API. This obviously is not very good so we need to address that by adding permissions to our user ViewSet. Django REST framework comes with some permissions that we may use but in most cases we have to create our own permissions that fit our needs. Inside users/ create permissions.py and add the following:

# users/permissions.py
from rest_framework import permissions


class IsAuthenticatedUserOrReadOnly(permissions.BasePermission):
    """
    Object-level permission that allows users to edit only their own profiles.
    """

    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS:
            return True
        return obj == request.user

The custom permission above only gives write access to a user if they are viewing their own profile. Now let’s add our permission to the UserViewSet:

# users/views.py
from django.contrib.auth import get_user_model
from rest_framework import viewsets, mixins

from .serializers import UserSerializer
from .permissions import IsAuthenticatedUserOrReadOnly # new


User = get_user_model()


class UserViewSet(
        mixins.ListModelMixin,
        mixins.RetrieveModelMixin,
        mixins.UpdateModelMixin,
        viewsets.GenericViewSet):

    queryset = User.objects.all()
    serializer_class = UserSerializer

    permission_classes = [
        IsAuthenticatedUserOrReadOnly,
    ] # new

Now if you go to your users endpoint you will see that you cannot update other users’ profile except yours which is what we wanted.

What did we do?

In this post we added Django REST framework to our project. We also created a serializer and ViewSet for our User model which we then registered to our API router. Finally we added a custom permission to our UserViewSet that allows users to only have write access to their own profiles. In the next posts we are going to add more endpoints to our REST API. Thanks once again for reading.

You may view the full source code on GitHub.