Building a simple application using Nuxt.js/Vue.js and Django — Part II

In the first part of this tutorial, we have implemented the authentication module of our solution. Here, we are going the develop the initial CRUD implementation: the backend endpoints.

Photo by Thomas Park on Unsplash

The scenario

We are going to implement a pool system, similar to that one presented on the official Django tutorial, but with some customization. In this tutorial part, we will focus just on the backend operations relative to the registration of new pools (questions) and their respective option choices.

The below diagram illustrates the basic class diagram considered on our example.

Basic Class Diagram of Pool Syste

The endpoints

We are going to make available all our REST endpoints under to /api/ URL:

  • POST /api/pools/ - Create a new question with the respective choice options.
  • GET /api/pools- Get the list of questions (pools) and their respective choice options.
  • GET /api/pools/closed/- Get the list of closed/expired questions (pools) and their respective choice options.
  • GET /api/pools/active/- Get the list of active questions (pools) and their respective choice options.
  • GET /api/pools/{id}/- Get the attributes and choices of the question (pool) with the corresponding id.
  • PUT /api/pools/{id}/- Update the attributes and choices of the question (pool) with the corresponding id.
  • DELETE /api/pools/{id}/-Delete the question (pool) with the corresponding id.

Note that more endpoints will be required for the complete implementation of the pool system, but now we are focusing only on the registration of questions and their respective choices.

Photo by Kevin Ku on Unsplash

The models

We suggest the creation of one file to each create Django Model class. In our example, we added a directory called models inside thebackend/core directory. Then, we created a file to the Question model class, and another to the Choice model class.

The first file (question.py) corresponds to the Question model class. It is presented in the below code.

Question model — backend/core/models/question.py file

The essential Question model is defined in lines 17–22, with the following attributes: text, creation_date, start_date, end_date, and owner. The last one is a relationship with the Django User model.

Besides, we defined three attributes to handle the QuerySet of our queries. These attributes (objects, active, and closed) are objects from the Manager Model.

Note that we created two auxiliary Manager class, ActiveQuestionManager and ClosedQuestionManager, respectively. According to the Django documentation, a Manager is an interface through which database query operations are provided to Django models.

Also, it is possible to use a custom Manager in a particular model by extending the base Manager class and instantiating the custom Manager in the model class. There are two reasons to customize a Manager: to add extra Manager methods, and/or to modify the initial QuerySet the Manager returns.

In our example, our two customized Manager classes are modifying the initial QuerySet. The first one (ActiveQuestionManager — lines 7–9) modifies the QuerySet filtering just the questions with a start date less than the current date, and an end date greater than the current date. In other words, just the questions/pools that are active and must be shown to be answered.

The second one (ClosedQuestionManager — lines 12–14) modifies the QuerySet filtering just the questions with end date less than the current date. In other words, just those questions/pools that are already expired.

The definition of the Choice model is detailed on the below code:

Choice model — backend/core/models/choice.py file

Note that this implementation is simpler, just defining the Choice model and the corresponding attributes (text, votes, and question). The attribute question is a relation with the Question model, imported on line 3.

Also, we defined the related_name as choices, to use for the relation from the Question objects back to this one.

The make such model files available to the system we create the __init__.py file inside the backend/core/models directory, with the following content:

from backend.core.models.choice import Choice
from backend.core.models.question import Question

The serializers

Similar to the models, we suggest creating the serializers directory inside the backend/core, with a file to each model serialization. We created a file to the Choice model serialization, and another to the Question model one.

The first file (choice.py) corresponds to the Choice model serialization. It is presented in the below code.

Choice serializer — backend/core/serializers/choice.py file

The Choice serialization is simple but presents some important points. Line 11 ensures that the question field will be read-only since the serializer will be invoked from question endpoints. Then, in creation or update operations such a field must be that one from the URL.

Also, line 6 informs that the id field is not required, which allows the update of choice_text without the creation of a new choice row on the database.

Then, we create the Question model serialization (question.py), presented in the below code:

Question serializer — backend/core/serializers/question.py file

This serialization is more complex, since we must to handle the questions and the choices corresponding to each question. Line 8 defines that the owner field will be hidden to the user and that the default value will be the current user.

In addition, line 9 corresponds to the relationship with the Choice model. In this case, the choices field will get the Choice serialization, previously defined. The many=True option ensures that more than one choice can be represented on the serializer.

Next, we defined the create method. Note that .create() and .update() are default methods invoked by serializers. Here, we implement the create method to handle the question choices. We defined a variable, called choices, with the choices sent on POST request to Django REST Framework (line 16). Also, the pop() method removes these choices from validated_data, which will be processed by the Question serializer (line 17).

Finally, for each choice sent to Django REST Framework (stored on choices variable), we create the corresponding item on the Choice model, ensuring that question field will the newly created question (line 19).

We also defined the update method. As on the create method, we removed the choices from the validated_data (line 23). In addition, we ensured that required fields that will not be modified (not sent on PUT request) will keep the current value (lines 24–26). And line 27 updates (saves) the instance.

Lines 29–43 handle the update of corresponding choices. In such a case, choices may be added, updated, and deleted. As choice and question serializers are nested, such validations are required to ensure consistency.

For simplicity and to keep the objectivity of this tutorial, I will not focus on such an explanation. In a future story, I can detail the Nested Serialization on DRF.

Finally, as on models, to make such serializers files available to the system we create the __init__.py file inside the backend/core/serializers directory, with the following content:

from backend.core.serializers.choice import ChoiceSerializer
from backend.core.serializers.question import QuestionSerializer

The views

Finally, we created the views to handle the requests sent to the DRF. As previous scenarios, we suggest creating the following directory: backend/core/views. In our current scenario, we are going to create just a file to question viewset, since choices will be handled from their corresponding questions.

The Question view file is presented below:

Question viewset — backend/core/views/question.py

In our example, we defined lines 10 and 11, respectively the queryset and serializer_class, and then we have considered the default methods from ModelViewSet class. In addition, we just defined two customized methods to handle the current and closed pools.

In these cases, we just redefined the queryset (lines 15 and 22, to active and closed pools respectively). Then, we parameterized the serializer with the customized queryset and returned it to the requester.

Also, as on models and serializer, to make such a viewset file available to the system we create the __init__.py file inside the backend/core/views directory, with the following content:

from backend.core.views.question import QuestionViewSet

Defining the routes

Finally, we define the routes to allow end-users to access the created viewset. We create a router file inside the backend/core directory, as follow:

Router file — backend/core/router.py file

Note that we used DefaultRouter class from rest_framework, that already provide some default routes. More detailed information can be found on documentation. Then, we add a pools registry linking with the previously created QuestionViewSet.

Then, we edited the backend/urls.py file pointing all /api/ requests to the newly created router file. The updated urls.py file is presented below:

URLs definitions — backend/urls.py file

Note that we added lines 5 and 9, corresponding to the new router file. Also, we also imported include method on line 2.

Running and testing the new changes

Now, we can test the new changes. First, we must migrate the change to the SQL database. Then, run on the root directory (and inside Poetry virtual environment):

$ ./manage.py makemigrations
$ ./manage.py migrate

And run the Django (if it were not running):

$ ./manage.py runserver

To check the operations of our backend module, we used the Postman, a platform for API development. Postman is available for Linux, macOS, and Windows.

The use of the Postman

The below figure shows the initial page of Postman. To perform an API request, click on Create a request link (depicted by the red oval line)

Postman — Main page

First, we will get the list of pools stored on our database. The URL is http://localhost:8000/api/pools, via the GET method. As illustrated in the below figure, we selected the GET method, filled the URL (red oval line — item 1), and clicked on the Send button (red oval line — item 2). The results are shown on the bottom frame (red oval line — item 3). As depicted, the result is “Authentication credentials were not provided”, since no user is previously authenticated.

Getting the pool list — without the authorization token

The authentication and authorization

To send authenticated requests, we must get a token from the backend. To this, we send a request to http://localhost:8000/api/token/ (defined on the previous tutorial). As illustrated in the below figure, it is necessary to define the Content-Type as application/json on the Headers tab.

Requesting an authorization token (I)

Then, on the Body tab we defined the content of the message as:

{
"username": "admin",
"password": "admin"
}

Finally, we sent a POST message to the backend. On the resulting frame, we get the reply message, with two information: refresh and access. We must annotate (or copy) the access one, that is our token code.

Requesting an authorization token (II)

Then, with the token code, we are able to send messages with the authorization code to the backend. To do this, we can resend the GET on URL http://localhost:8000/api/pools, but with the token defined on the Authorization tab, as depicted on the below figure. Note that we defined the Type as Bearer Token, and filled the Token field with the value received on the login process. As a result, we get an empty Array, since no record is stored on the database.

Getting the pool list — with the authorization token

Creating new pools

Another request is the POST to create a new pool/question with the respective choices. To this, we send a POST method to http://localhost:8000/api/posts, as depicted on the below figure. Note that on Body tab we typed a JSON code with the question data. On the resulting frame will be presented with the newly created question.

Creating new pools

Now, if we perform again the GET method on http://localhost:8000/api/posts/, the backend will reply a JSON message with an Array of Objects. In our example, the newly created pool/question will be presented on the message.

Getting the list of pools

Updating a pool information

Finally, backend also allows the update or partial update of a pool/question information by using the PUT method. In our example, depicted on the below figure, the choice with ID 3 is changed from Go to Go Lang, the choice with ID 4 is removed, and a new choice with text Other is added. Note that the PUT method is sent to http://localhost:8000/api/pools/1/, in which 1 is the ID of the question to be changed.

Updating the pool information, by ID

The results of such a change can be queried through a GET request to http://localhost:8000/api/pools/1/.

Getting the pool information by ID

Final comments

In this tutorial, we presented the initial backend solution using the DRF (Django REST Framework) to provide endpoints to handle questions and choices of a pool system.

In our next tutorial, we will describe how to access such endpoints from a NuxtJS application.

Professor at Catarinense Federal Institute (Brazil), PhD on Computer Science.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store