Django Rest Framework for React: Tests, Profile and Authentication.

Vhutshilo Mbedzi
7 min readMar 27, 2021

Here’s the full code: github

Project Description:

Tools: Django( with Django Rest Framework), ReactJs(with Redux and Material-UI).

What: A simple web application. Where users register, login, edit their profiles, and search other users’ profiles.

Why: I picked this problem because, the skills required to solve it, can be applied to a variety of problems. E.g. This can be modified to create a social media application.

Part 1: Tested DRF endpoints.

This part will cover, testing our endpoints and their models. Writing custom code by overriding some methods, to suit our needs.

Required skills or knowledge:

I’m going to assume that you have very little knowledge of Django, Django Rest Framework, and testing.

I will try to explain everything. You may skip the “explanation” section, if you already understand the concept.

Note:

I will use the “greater than sign”, to show commands. E.g:

> pip

I will use four dots “….”, in place of older code.

To start things off. Let’s create a folder or directory, for our project:

I will name mine “Django-Auth”.

> mkdir Django-Auth

To create a virtual environment:

I will use pipenv . Inside the created directory:

> pipenv shell

Installing Django and starting a Django project, with an app:

> pip install django> django-admin startproject django_react_auth .> python manage.py startapp account

After starting our app. We need to add it in our Installed Apps.

#In django_react_auth/settings.py:INSTALLED_APPS = [
....
'account.apps.AccountConfig',
]

Before we write our any code. Let’s clean up our directory.

Inside our accounts app.

Remove:

  • test.py
  • views.py

The add:

  • tests/test_model.py

Our new directory should be:

Django-Auth/:
-account/:
-admin.py
-apps.py
-__init__.py
-migrations
-models.py
-tests/:
-__init__.py
-test_model.py
-django_react_auth/:
-asgi.py
-__init__.py
-settings.py
-urls.py
-wsgi.py
-manage.py
-Pipfile

Testing overview: Read this if you don’t understand tests in Django.

You’ll notice that, I import models that I haven’t written yet. I got that from a concept called “Test-Driven Development”. That’s where you write tests 1st, then write application code for your failing tests(until they run successfully).

The setUp() method runs before every test method. We usually initialize data needed for our tests there. That’s if the model/api-endpoint being used to create data, doesn’t need testing or it already has been tested.

Since we’ll be testing 2 major points of our app flow.

#For our models, we’ll use these simple tests:
assertEqual - takes 2 arguments. Examples:
self.assertEqual(a, b) - The test passes if a equals b.
assertTrue and assertFalse - Each takes 1 argument.
assertTrue(a) - Test passes if a is true.
assertFalse(a) - Test passes if a is false.
for more info: unittest and Django TestingFor our API endpoints, we’ll follow a request-response cycle:
- We type in the url: reverse(app_name:url_name)
- We write the data: json data for post & put requests
- We request the data to the server: self.client.get()
- We compare the response with our expectations: self.assertEqual()
for more info: DRF Testing

We’ll use the built-in user model, for user authentication. Then, have our own model, for the profiles.

Our 1st code will be a test. Let’s test the model we are about to write.

In account/tests/test_model.py:

> python manage.py test

We get an error: We don’t have a model “Profile”.

Let’s create it. In account/models.py:

Explanation:

To organize our profile images, after being uploaded. We defined a method “ upload_profile_picture”. i.e. media-root/profile/username/

The property decorator is only defined to help us write cleaner code. It returns the user through instance.owner, instead of us using instance.user. It’s not much. Hehe.

The user models has a One to One relationship with our Profile model. That is: One user will have one Profile.

I do apologize, if I’m explaining the obvious.

Since we are working with images. Let’s set-up our media root, and also install pillow.

> pip install pillow

In django_react_auth/settings.py:

....

MEDIA_ROOT = BASE_DIR / 'static-server' / 'media-root'
MEDIA_URL = '/media/'

In django_react_auth/ulrs.py:

> python manage.py makemigrations
> python manage.py migrate

If we run the test again. We get a failure, instead of an error.
This is good. The test is running.
The profile is still not being created.

To get the profile created. We need to use signals.

Let’s create signals.py file in the account app. In account/signals.py:

This signal creates a profile when a user is created. With the user’s username, as the initial profile name.

Since we are using the receiver decorator. We need to import it in the ready() method. For more info: signals

In account/apps.py:

> python manage.py test

The test runs successfully.

Just to make sure that our code, does what it should. Let’s add more tests.

  • Deleting a user should delete their profile.
  • The profile should initially have the user’s username, as its name.
  • Updating the profile without an image, should be possible.
  • Updating the profile with an image, should also be possible.

In account/tests/test_model.py: Complete model test.

Now that we have 5 successfully running tests. Let’s work on our API endpoints.

Let’s install what we need.

From the Django Rest Framework Docs:

> pip install djangorestframework
> pip install markdown
> pip install django-filter

In django_react_auth/settings.py:

INSTALLED_APPS = [
'rest_framework',
....
]
.... REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',]
}

While testing models, we didn’t write tests for registering a user nor signing-in one. That’s because, we didn’t write any custom code for them.

For our endpoints. We will start with them.

In account/tests/ create a file: test_api.py

In account/tests/test_api.py:

> python manage.py test
#When we run the test, we get an error:
#'account' is not a registered namespace

The namespace ‘account’, will require the account app to have a urls.py file.
We can’t have all that, without a view.
Instead of just using views, let’s user serializers too.

In our account app, create:

  • api/serializers.py
  • api/views.py
  • api/urls.py

Our new directory should be:

Django-Auth/:
-account:
-api/:
-__init__.py
-serializers.py
-urls.py
-views.py
-admin.py
-apps.py
-__init__.py
-migrations/
-models.py
-tests/:
-__init__.py
-test_model.py
-django_react_auth:
-asgi.py
-__init__.py
-settings.py
-urls.py
-wsgi.py
-manage.py
-Pipfile

In account/api/serializers.py:

Explanation:

For registering new users. Our serializer class has an overridden method “create”. Why?

The Django user model uses its “create_user” to create new users. That comes with a password hashing algorithm.

Since the default “create” method is what the serializer uses.

The password won’t be recognized when the user tries to login.

“Invalid password format or unknown hashing algorithm.”

For someone to register. They shouldn’t logged-in.

We’ll need a custom permission for that. Since this is a small app. Let’s create a permission file, inside our account app: account/api/permissions.py

In account/api/permissions.py:

For more info: permissions

In account/api/views.py:

In account/api/urls.py:

In django_react_auth/ulrs.py:

> python manage.py test
#Our test runs successfully.

Let’s add more test for our endpoint:

  • Duplicating a username should shouldn’t be allowed.
  • Passwords should be longer than 5 characters.
  • A profile must be created, after registering through the endpoint.

In account/tests/test_api.py:

With those working. Let’s login a user.

In account/tests/test_api.py:

> python manage.py test
#Error: Reverse for 'login' not found

We’ll need to use tokens, to authenticate users in our React frontend.

> pip install djangorestframework-simplejwt
For more info: Simple JWT

In django_react_auth/settings.py:

REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
}

Let’s write code for our login test:

In account/api/serializers.py:

Explanation:

The UserSerializer serializer class, will be used if the login is successful. It’s data will be part of the response, and will be used by the frontend.

The UserLoginSerializer serializer class, has an overridden validate method. Why? Two reasons. 1. We need a Validation Error Message. 2. We also need to “validate”, whether the user is active or not.

In account/api/views.py:

Explanation:

In the UserLoginAPIView, we had to override the post method. Why? To customize our response.

When the user is authenticated, the response should have the user data(from our UserSerializer) and tokens for our frontend, for further requests.

In account/api/urls.py:

> python manage.py test
#Seems like the code is working.

We also need to write tests for other scenarios:

  • When the user enters incorrect Credentials.
  • When the user trying to login is not registered.

In account/tests/test_api.py:

With those test running successfully. Let’s focus on our profile section.

Since we know/tested that, registering a user creates a profile.

Let’s try to update the created profile.

In account/tests/test_api.py:

> python manage.py test
# Error: Reverse for 'profile' not found.

The code for updating a profile:

In account/api/serializers.py:

Explanation:

The ProfileSerializer serializer class has two serializer method fields.

The “username” is going to be used to identify the Profile’s user.

The “is_owner” gets its value from the view, which we’ll write in a second. This value will be used by the frontend, to determine what to show, to whom. i.e. If it’s the owner, show an edit button.

In account/api/permissions.py:

Explanation:

The IsOwnerOrReadOnly custom permission class. For more info: Permissions

Allows the owner to make all the CRUD requests, and limits the other user to just reading/retrieving.

In account/api/views.py:

Explanation:

In the ProfileAPIView, we have two overridden methods.

The get_object receives the username from the url, as “kwargs”. Which is the used to get the user’s profile object.

Since we used a custom permission. We MUST check_object_permissions. For more info: Object Level Permissions.

The get_serializer_context, is responsible for passing context to the serializer. Here, we are simply passing the is_owner boolean value.

In account/api/urls.py:

> python manage.py test
#The test runs as expected.

Just to be sure that we wrote functional code. Let’s write more tests.

  • A user should only update their profile. i.e. The custom permission.
  • A user should be able to update their profile image.

In account/tests/test_api.py:

> python manage.py test
#We have 15 tests that run as expected.

To view other users’ profiles. There needs to a search function.

In account/tests/test_api.py:

> python manage.py test
# Error: Reverse for 'search_profiles' not found.

Let’s write code for our test.

In account/api/views.py:

Explanation:

In the ProfileSearchAPIView, we are just adding the search fields.

We’ll search through the user’s username, their profile bio and profile name.

The get_serializer_context method, is doing the same thing. i.e. Passing context to the serializer. Why? Because the serializer expects it, and because the frontend should be able to identify your profile from the results and mark it as such.

--

--