This chapter will focus on working with an Application Programming Interface (API). An API is actually a set of tools and communication protocols working to allow two different applications to communicate with each other effectively; it is what acts as the middleman between two systems. A REST API adopts the design principles set forth in a Representational State Transfer (REST) software architecture and is most commonly used with web-based applications. Every time we mention the word API in this chapter, we are really referring to a REST API as they are technically slightly different but usually interpreted as the same thing.
Django itself relies on third-party packages to work with an existing API or to create an API yourself. A common Python package that is available is called the requests package. The requests package is used to send and receive requests to and from an existing API found on the server side. More information about this package can be found here: https://pypi.org/project/requests/. On the other hand, a JavaScript-based framework, such as React, AngularJS, or Vue.js, to name a few, will all perform these requests on the client side, within the user’s browser. There is no right or wrong way to communicate with an API, in terms of choosing tools that operate on the client versus the server side. Those decisions are made as a result of the technical requirements obtained for your project. We won’t actually be using the requests package or any of the client-side JavaScript-based frameworks; instead, we will focus on just the Django REST framework, which is used to build a model-based API for the models we previously created.
The Django REST framework is licensed as open source, allowing developers to use it within their commercial or private applications. It is used to construct APIs based on the models of a Django project, where endpoints execute HTTP requests that perform Create, Read, Update, and Delete (CRUD) operations. This means it is used to serialize and deserialize related models of a Django project into JSON format, commonly used in web-based APIs. When using the Django REST framework, there is no need to use the pip requests package, but it won’t hurt either if you use this package in combination with the framework, which is sometimes done in projects. This chapter will focus entirely on using the Django REST framework to create an API for all of the vehicle models that we created in Chapter 3, Models, Relations, and Inheritance. We will be serializing those models and registering URL patterns, which are the API endpoints, to views and viewsets that we write. We will also be using routers to generate some of those URL patterns for us, based entirely on the data that exists in our database tables.
In this chapter, we will cover the following:
To work with the code in this chapter, the following tools will need to be installed on your local machine:
We will continue to work with the solution created in Chapter 2, Project Configuration. However, it is not necessary to use the Visual Studio IDE. The main project itself can be run using another IDE or run independently using a terminal or command-line window from within the project root folder, which is where the manage.py file resides. Whatever editor or IDE you are using, a virtual environment will also be needed to work with the Django project. Instructions for how to create a project and virtual environment can be found in Chapter 2, Project Configuration. You will need a database to store the data contained in your project. PostgreSQL was chosen for the examples in the previous chapter; however, any database type that you choose for your project can be used to work with the examples in this chapter.
We will also be using data that is in the form of a Django fixture, provided in Chapter 3, Models, Relations, and Inheritance, in the subsection titled Loading the chapter_3 data fixture. Make sure the chapter_3 fixture is loaded into your database. If this has already been done, then you may skip the next command. If you have already created the tables found in Chapter 3, Models, Relations, and Inheritance, and have not loaded that fixture yet, then run the following command, after activating your virtual environment:
(virtual_env) PS > python manage.py loaddata chapter_3
All of the code created in this chapter can be found in the GitHub repository for this book: https://github.com/PacktPublishing/Becoming-an-Enterprise-Django-Developer. The bulk of the code depicted in this chapter can be found in the /becoming_a_django_entdev/becoming_a_django_entdev/chapter_8/ directory.
Check out the following video to see the Code in Action: https://bit.ly/3Ojocdx.
Start by creating a new app in your project called chapter_8 by following the steps discussed in Chapter 2, Project Configuration, in the subsection titled Creating a Django app. As discussed in that section, don’t forget to change the value of the name = variable for your app class found in the /becoming_a_django_entdev/becoming_a_django_entdev/chapter_8/apps.py file to now point to the path where you installed your app. Be sure to also include this app in the INSTALLED_APPS variable found in the settings.py file as well.
In the main urls.py file of the site, add the following path, which points to the URL patterns of this chapter that we will be creating:
# /becoming_a_django_entdev/urls.py
...
urlpatterns = [
path(
‘’,
include(
‘becoming_a_django_entdev.chapter_8.urls’
)
),
]
To install the Django REST framework in any Django project and enable the bare-minimum settings needed to begin working with it, follow these steps:
PS C:ProjectsPacktRepoecoming_a_django_entdev> virtual_env/Scripts/activate
(virtual_env) PS > pip install djangorestframework
(virtual_env) PS > pip install markdown
(virtual_env) PS > pip install django-filter
There is no need to run the Django migration commands when installing these three packages as no additional tables will be created by them.
# /becoming_a_django_entdev/settings.py
INSTALLED_APPS = [
...
‘rest_framework’,
]
REST_FRAMEWORK = {
‘DEFAULT_PERMISSION_CLASSES’: [
‘rest_framework.permissions.
DjangoModelPermissionsOrAnonReadOnly’
],
}
This permission class allows us to check for model-based CRUD rights and allows anonymous users to only view or read items. For a complete breakdown of the over half-dozen other permission classes, visit https://www.django-rest-framework.org/api-guide/permissions/. We will only be working with one permission class throughout this chapter.
# /becoming_a_django_entdev/chapter_8/urls.py
from django.contrib import admin
from django.urls import include, path
from django.views.generic import TemplateView
urlpatterns = [
path(‘admin/’, admin.site.urls),
path(
‘’,
TemplateView.as_view(
template_name = ‘chapter_8/index.html’
)
),
path(‘api-auth/’, include(‘rest_framework.urls’))
]
Now, the Django REST framework is installed and ready to use in your project. Currently, we have no API URLs except for the authentication URLs that come with this framework. Currently, those authentication URLs don’t provide us with anything to do. You can navigate to the login page just to see whether it loads properly by visiting the URL http://localhost:8000/api-auth/login/. If you log in with your superuser account, there will currently be nothing to display and it will show you a 404 Page not found message.
Note
The reason we enabled the Django admin site URL patterns for the chapter_8 app is to be able to log into the Django admin site with a superuser account and authenticate a user when working with some of the exercises in this chapter. For those exercises, if you are not logged in, you will find a message in your results stating Authentication credentials were not provided. For other exercises toward the end of this chapter, you will not need to be logged into the Django admin site; authentication will be performed by using token-based authorization measures.
To begin using this framework and creating new API endpoints, we will start by creating a serializer class for each of the models created in Chapter 3, Models, Relations, and Inheritance.
Creating an API starts with creating a serializer class and then creating a view, in particular a ModelViewSet view class. Serializing objects means converting a model object into JSON format to represent the data of that object. The last thing we need to do is create URL patterns that map to the view classes that we wrote; this will be done using URL routers. These URL patterns are considered your API endpoints.
One thing to note in this section is that we need to create serializers for all models that relate to other models when using related fields. This is why the following exercises will show examples for all four models of the chapter_3 app. This has to be done in order to ensure that we do not get errors when using the Browsable API, which we will introduce later in this chapter, and when performing API requests. This means if you have multiple Seller that have been assigned a Group or Permission, that Group and/or Permission object will also have to be serialized. Remember, the Seller object replaced the default User object found in the django.contrib.auth.models library when we changed the AUTH_USER_MODEL setting to now equal ‘chapter_3.Seller’ in Chapter 3, Models, Relations, and Inheritance. Examples for serializing the Group or Permission objects are not shown because there is only one Seller provided in the chapter_3 data fixture, as shown:
The Seller here is not assigned to a Group or Permission, so as a result, we should not experience errors in the following exercises. Instead, that Seller has the is_superuser field set to true, which allows us to perform all of the CRUD operations when logged into the Django admin site.
Note
If you experience errors, either delete all but the Seller data shown previously or it is recommended to just create the additional serializers, viewsets, and routers for the Group and Permission objects. Follow the same code format that is used in the following examples. The same applies to the ContentType object found in the django.contrib.contenttypes.models library. This will be needed if you have a depth property defined in the Meta subclass of that serializer class, more specifically, if depth is set to a value of 2 or greater. We will discuss what this property does soon.
Next, let’s begin writing our serializer and learn more about the classes that are available to use.
The rest_framework.serializers library provides us with five classes, as follows:
Begin by following these steps to create a ModelSerializer class for each model created in Chapter 3, Models, Relations, and Inheritance, the Engine, Vehicle, VehicleModel, and Seller models:
# /becoming_a_django_entdev/chapter_8/serializers.py
from rest_framework.serializers import ModelSerializer
from ..chapter_3.models import (
Seller,
Vehicle,
Engine,
VehicleModel
)
# /becoming_a_django_entdev/chapter_8/serializers.py
...
class EngineSerializer(ModelSerializer):
class Meta:
model = Engine
fields = ‘__all__’
# /becoming_a_django_entdev/chapter_8/serializers.py
...
class VehicleModelSerializer(ModelSerializer):
class Meta:
model = VehicleModel
fields = ‘__all__’
# /becoming_a_django_entdev/chapter_8/serializers.py
...
class VehicleSerializer(ModelSerializer):
class Meta:
model = Vehicle
fields = ‘__all__’
# /becoming_a_django_entdev/chapter_8/serializers.py
...
class SellerSerializer(ModelSerializer):
class Meta:
model = Seller
fields = ‘__all__’
You might notice that the preceding classes resemble some of the classes used in the exercises found in previous chapters. Here, we defined the fields using the ‘__all__’ value but we can provide a list of only the fields needed and the order in which we need them, as was done with the form classes of Chapter 5, Django Forms.
The Meta subclass provides additional options, similar to how we customized the Meta subclasses in the models written for Chapter 3, Models, Relations, and Inheritance. For a complete breakdown of all the Meta class options available and anything about serializers in general, visit https://www.django-rest-framework.org/api-guide/serializers/. Other options include the following:
Now that we have serializer classes to work with, we need to create a view class for them. We can do that with the ModelViewSet class provided by the Django REST framework.
Instead of creating views/methods for each CRUD operation, the Django REST framework offers a class that combines them all into one. It starts by creating a view class within the views.py file, similar to what we did in Chapter 4, URLs, Views, and Templates, except they are constructed using one of the following viewset classes. The Django REST framework provides the following four viewset classes:
Follow these steps to prepare your viewset classes:
# /becoming_a_django_entdev/chapter_8/views.py
from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import ModelViewSet
from .serializers import (
EngineSerializer,
SellerSerializer,
VehicleSerializer,
VehicleModelSerializer
)
from ..chapter_3.models import (
Engine,
Seller,
Vehicle,
VehicleModel
)
# /becoming_a_django_entdev/chapter_8/views.py
...
class EngineViewSet(ModelViewSet):
queryset = Engine.objects.all().order_by(‘name’)
serializer_class = EngineSerializer
permission_classes = [IsAuthenticated]
# /becoming_a_django_entdev/chapter_8/views.py
...
class VehicleModelViewSet(ModelViewSet):
queryset = VehicleModel.objects.all().order_by(
‘name’
)
serializer_class = VehicleModelSerializer
permission_classes = [IsAuthenticated]
# /becoming_a_django_entdev/chapter_8/views.py
...
class VehicleViewSet(ModelViewSet):
queryset = Vehicle.objects.all().order_by(‘price’)
serializer_class = VehicleSerializer
permission_classes = [IsAuthenticated]
# /becoming_a_django_entdev/chapter_8/views.py
...
class SellerViewSet(ModelViewSet):
queryset = Seller.objects.all()
serializer_class = SellerSerializer
permission_classes = [IsAuthenticated]
In each class depicted, we define only three properties for each of those classes, the queryset, serializer_class, and permission_classes properties. In the preceding examples, we are only using the all() method to search for all records within that table. Instead of using the all() method, the filter() and get() functions can also be used to look up specific records. The serializer_class property is used to map your view to the serializer class that we constructed in the previous subsection; it maps to the model class that we are performing the query on. The permission_classes property is used to define the permissions for that request. Permissions differ from the authentication token that we will discuss toward the end of this chapter. Permissions ensure the user who is accessing the system is allowed to perform those specific CRUD operations on the model in question. These are the only three properties available and only the first two are required when using a ModelViewSet or GenericViewSet class; the last is optional. You can also customize these using a callable method or even override the default action methods yourself. To learn more about viewsets, visit https://www.django-rest-framework.org/api-guide/viewsets/.
Next, let’s configure those URL routers to map to the viewsets that we just created. These will be the API endpoints of your project.
URL routers are used as a way to prevent developers from having to write individual URL patterns for each of the CRUD operations pertaining to each model in your API. That can get very complicated after a while and the Django REST framework provides these URL routers as a means to automatically generate each endpoint for you.
Follow these steps to configure your routers:
# /becoming_a_django_entdev/chapter_8/urls.py
...
from rest_framework import routers
from .views import (
EngineViewSet,
SellerViewSet,
VehicleViewSet,
VehicleModelViewSet
)
# /becoming_a_django_entdev/chapter_8/urls.py
...
router = routers.DefaultRouter()
router.register(r’engines’, EngineViewSet)
router.register(r’sellers’, SellerViewSet)
router.register(r’vehicles’, VehicleViewSet)
router.register(
r’vehicle-models’,
VehicleModelViewSet
)
# /becoming_a_django_entdev/chapter_8/urls.py
...
urlpatterns = [
...
path(‘chapter-8/’, include(router.urls)),
path(‘api-auth/’, include(‘rest_framework.urls’)),
]
The include(router.urls) path shown previously is placed between the api-auth path and below your admin and home page paths. A router variable is defined for each model using the routers.DefaultRouter() class provided by the Django REST framework. Each router.register() function creates a set of URL patterns for that model. For example, where the engines path is shown in this URL, http://localhost:8000/chapter-8/engines/, that is what is defined in the first parameter of the first register function, as r’engines’. The router generated a set of URL patterns, one for each object in that table, which gets added to your urlpatterns list using the path(‘chapter-8/’, include(router.urls)) path. Adding chapter-8 to this path() function is where we are telling Django to prefix http://localhost:8000/chapter-8/ for every path created using this set of routers.
That’s it; you now have a very basic API to use with the four models created in Chapter 3, Models, Relations, and Inheritance. To test your API and see the data that would be sent and received in each request, we will use the Browsable API next, which is provided with the Django REST framework.
The Browsable API is a built-in tool that allows for easy browsing and testing of your API. It allows us to read and view data in JSON and API format. This section will teach us how to use and access this tool. When we added the chapter-8 path to the URL routers in the previous section, we activated that path as what is called the API root, which provides all of the URLs available in your API, with some exceptions. Visit http://localhost:8000/chapter-8/ to see these URLs, as depicted here:
When building custom API endpoints, as we will do later in this chapter, you will likely not see them displayed in your API root. You’ll see that the serializers for the groups, permissions, and content types have all been included with the code of this book. There is a dropdown at the top right of every main router path that we created, to switch between the two formats, API and JSON, as shown in the following screenshot:
If using HyperlinkedModelSerializer to construct your serializer classes, each object will be displayed as a clickable URL versus an ID that is not clickable. The hyperlinked version is shown in the following screenshot, when visiting the main URL path that the router created for the Seller model at http://localhost:8000/chapter-8/sellers/:
To view the same results as in the preceding screenshot, just change ModelSerializer to HyperlinkedModelSerializer in all of your serializer classes. Also, change your SellerSerializer class to exclude the fields shown in the following code in order to prevent an error indicating an incorrectly configured lookup_field, which is an advanced topic that goes beyond the scope of this book to resolve:
# /becoming_a_django_entdev/chapter_8/serializers.py
...
from rest_framework.serializers import (
HyperlinkedModelSerializer,
ModelSerializer
)
class SellerSerializer(HyperlinkedModelSerializer):
class Meta:
model = Seller
#fields = ‘__all__’
exclude = [‘groups’, ‘user_permissions’]
On each of the routers registered in the previous subsection, the main URL, such as the http://localhost:8000/chapter-8/sellers/ link shown previously, will allow you to perform a create operation (a POST request) using the form at the bottom of that page. Just viewing this page performs a read operation (a GET request). A detail page, such as http://localhost:8000/chapter-8/sellers/1/, includes a form at the bottom of that page that will allow you to perform PUT, PATCH, and DELETE operations for the object being viewed, as shown here:
By default, Django will show the Raw data tab that is shown in the preceding screenshot. The HTML form tab will only allow PUT operations. The Delete button is found at the top of this page, not where the PUT and PATCH buttons are located in the preceding screenshot. If you are logged into the Django admin site using a Seller that does not have superuser status, then that user/seller must have group or individual permission-level access granted in order to perform any of the CRUD operations on that model object. If a user/seller only has permission to do one thing and not the other, such as update but not delete or create and not update, then only those action buttons will appear. If you do not see any of the action buttons where you would expect to see them, double-check your permission settings for that user.
Now that we have a working API and have explored how to use the Browsable API, let’s build pages that change content without needing to reload or redirect that page.
Single-Page App (SPA) pages are web pages where content gets updated within containers/nodes rather than reloading or redirecting the page to display that data. Usually, some of the work of the server is offloaded to the client’s browser to perform these requests and/or render the HTML, usually with JavaScript or jQuery. When an event is triggered, such as the clicking of a button or submission of a form, JavaScript is used to obtain the data from the server and then render that content onto the page, wherever we want it to display.
In this exercise, we will use the API endpoint of the Seller created by the router, at http://localhost:8000/chapter-8/sellers/1/, to render JSON as a string within a container found in the body of a query page. The query page is just a standard page that uses JavaScript to communicate with an API endpoint.
In this subsection, we will build the view to handle a page where the user can enter a number relating to the ID of a Seller that they want to query. This will be known as the GetSellerView class and it will be used as the backbone for the remaining two exercises of this chapter.
To get started, take the following step:
# /becoming_a_django_entdev/chapter_8/views.py
...
from django.template.response import TemplateResponse
from django.views.generic import View
...
class GetSellerView(View):
template_name =
‘chapter_8/spa_pages/get_seller.html’
def get(self, request, *args, **kwargs):
context = {}
return TemplateResponse(
request,
self.template_name,
context
)
In the preceding example, we are constructing a class-based view the same as we did in Chapter 4, URLs, Views, and Templates. The only thing we are doing differently here is we are not including the post() method; we only provided the get() method. Since we are not working with form submissions. The call-to-action button will be controlled using JavaScript as a button of type=’button’ and not type=’submit’. There is no need to use the post() method when that is done. Also, we are creating a standard Django view class and not a REST API view class because this page is only used to communicate with API endpoints and not to serve as an API endpoint itself.
Now, let’s create the template to format the HTML that gets rendered to the page.
The previous subsection constructed the view for this exercise. This subsection will create the template that is used in our exercise.
Follow these steps to create your template:
# /becoming_a_django_entdev/chapter_8/templates/chapter_
8/spa_pages/get_seller.html
{% extends ‘chapter_8/base/base_template_1.html’ %}
{% load static %}
...
{% block body_content %}
<form>
<div class=”field-box input-box”>
<label for=”seller-id”>Seller ID:</label>
<div class=”form-group”>
<input id=”seller-id” type=”text” />
<span class=”help-text”>Please enter
the ID of the seller you want to
lookup</span>
</div>
</div>
<button type=”button” id=”get-sellers” onclick
=”$gotoSPA_Page()”>
Get Seller Details</button>
</form>
<div id=”details”>
<p>!!! No Details to Display !!!</p>
</div>
{% endblock %}
The idea here is that we will replace all of the content of the <div id=”details”> container with the contents of what is received from the API request. A <form> container is not necessary for this particular setup; it has been added only to conform to the same CSS styles and HTML node structuring that was written in previous chapters. It is acceptable to deviate from this structure and create your own. Please use the preceding structure for the purpose of demonstrating this exercise.
Next, let’s add the JavaScript responsible for performing an API request. We will be performing this action on the client side and not on the server side.
We don’t need much JavaScript, just one small function that utilizes the native JavaScript fetch() function. This is almost identical to the jQuery .ajax() function, but it does differ slightly. The fetch() function differs in that it won’t send cross-origin headers, the default mode is set to no-cors, and the .ajax() function sets the default mode to same-origin. That could be important depending on your project’s requirements. The result of the request will then be displayed in the container with the CSS ID attribute of details, better known as the details container.
If you copied your JavaScript file from chapter_4, that file should be blank right now. Take the following steps to prepare your JavaScript. If you copied this file from chapter_8 found in the code of the book, make sure to comment out all but the following code:
# /becoming_a_django_entdev/chapter_8/static/chapter_8/js/site-js.js
function $gotoSPA_Page() {
const input = document.getElementById(
‘seller-id’
);
const container = document.getElementById(
‘details’
);
const id = input.value;
var url = `/chapter-8/sellers/${id}/`;
}
# /becoming_a_django_entdev/chapter_8/static/chapter_8/js/site-js.js
function $gotoSPA_Page() {
...
fetch(url, {
method: ‘GET’,
headers: {
‘Content-Type’: ‘application/json’,
}}).then(response => {
return response.json();
}).then(data => {
container.innerHTML = JSON.stringify(data);
});
}
This is the $gotoSPA_Page() function that we configured in the previous subsection to execute when the onclick action of the Get Seller Details button is triggered. That’s it! This is all the JavaScript that we need to complete a single task of retrieving an individual record from a database, using the API that we created.
In the preceding code, we wrote three constants, one called input to target the input field node and another called container to target the details container node. The third, called id, is used to capture the value of the input field at the time that this function is executed. The url variable is used to construct a string using the value of the input field as the keyword argument of that path converter. In JavaScript, this is known as concatenating strings, and because we are doing this, you need to make sure the backtick character (`) is used instead of a single-quote character (‘). They look almost identical; if you are just glancing at the preceding code, be careful. Here, we are telling that url variable to point to the URL created by the router of the Seller API.
The fetch() function accepts the url variable as the first positional argument of that function, which is a required argument. We then pass in additional optional arguments, such as the method that accepts these values (GET, POST, PUT, PATCH, and DELETE). All we want to demonstrate for now is retrieving the data, so we will use the GET method in this exercise. The headers argument is sometimes used to specify ‘Content-Type’; in this case, it is set to ‘application/json’. The method and headers shown previously are the defaults to using the fetch() function. They are not needed for the read operation since they are the default values but they are provided for illustrative purposes.
The fetch() function also uses the two then() methods shown previously; they each return a promise as a response object in JSON format. In simple terms, a promise is an object that consists of a state and a result. The second then() method uses the returned promise as the data variable, which we then use by writing a simple statement to place that data into the details container. We use the JSON.stringify() method to convert that JSON object into a readable format, particularly a string placed inside that container. Without using the JSON.stringify() function, we would only see a single object printed to the screen in brackets, which won’t make much sense to us when we are looking at it. We will see screenshots of this in action in the subsection titled First demo of this chapter.
Currently, all we are doing is printing the string of JSON into the <div> container. We are not creating HTML nodes and/or CSS styles for those nodes. This is where you would have to either write additional JavaScript to do that for you manually or use the power of a JavaScript-based framework. Let’s finish this exercise to see it working first, and then we will show you how to render that HTML and CSS on the server side, in the section titled Writing custom API endpoints of this chapter.
Traditional JavaScript is synchronous and single-threaded. It will run one process after the other and if one process gets hung up on, say, an API request where the server takes a long time to respond, then processes that take place after will hang up too. The problem is that a page can become unresponsive when this occurs. Asynchronous JavaScript allows functions to run side by side while other functions might be waiting for a response from the server. A then() function returning a promise is already an asynchronous function and is the reason why we gravitated toward using the fetch() function. JavaScript provides the async and await keywords, which make using and working with asynchronous functions a little bit easier, especially when your code begins to grow beyond these basic usage examples.
Take the following step to modify your JavaScript.
Make the highlighted changes in the following code block to your $gotoSPA_Page() function from the previous example:
# /becoming_a_django_entdev/chapter_8/static/chapter_8/js/site-js.js
function $gotoSPA_Page() {
...
fetch(url, {
method: ‘GET’,
headers: {
‘Content-Type’: ‘application/json’,
}
}).then(async(response) => {
return await response.json();
}).then(async(data) => {
const thisData = await data;
container.innerHTML = JSON.stringify(
thisData
);
});
}
The variable and constants are still needed. They are left unchanged and are represented by the previous three-dot notation. We now have enough to almost run our project and demonstrate this exercise in action. We just need to map a URL pattern to the view we create next.
Now, we are going to wire up the view that we created to a URL pattern, listening for the /chapter-8/get-seller/ path.
Take the following step to configure your URL pattern.
In your /chapter_8/urls.py file, add the following path to the urlpatterns list:
# /becoming_a_django_entdev/chapter_8/urls.py
from .views import ..., GetSellerView
...
urlpatterns = [
...
path(
‘chapter-8/get-seller/’,
GetSellerView.as_view(),
name = ‘get-seller’
),
]
You also need to import the GetSellerView class to map to the preceding pattern.
Next, let’s demonstrate this code in action.
To demonstrate the code depicted in the Building SPA-like pages exercise, follow these steps:
Tip
You will need to have the Network tab opened at all times in order for data to be logged into this tab. Open this tab and then refresh the page to get accurate results as you perform these actions.
The first request, at the top of this list, was initiated by the browser when the user first loaded the page at http://localhost:8000/chapter-8/get-seller/. The second request, /chapter-8/sellers/1/ at the very bottom of this list, shows that the initiator was the site-js.js file, which is the file where we wrote the $gotoSPA_Page() function. The last column shows the time it took to perform each request. All of the files in between are other assets, such as CSS and JavaScript files used by other apps in your project.
Note
There is no need to worry if you do not see these files; it just means they have not been loaded for one reason or another.
Now that we have the client side of our API up and running, let’s explore how to return rendered HTML instead of a string representation of the JSON object that is returned.
Creating our own API endpoints is just as easy as writing another URL pattern. This section will teach us how to write our own API endpoints and practice sending preformatted HTML back to the client. You do not need to create all custom API endpoints to return preformatted HTML but we will practice doing that. Preformatting HTML only works well if the app communicating with your API does not need to restructure or restyle the HTML in any way after it has been received. This means the server/developer needs to know exactly how the client will use the data that it receives. No more JavaScript will be needed other than what was already written in the $gotoSPA_Page() function of the previous exercise. We will reuse that same function and just alter one or two things before we move forward. We will create a new view class and add permission logic to secure that endpoint from unwanted users accessing the API.
Let’s begin working on this exercise in the same order as the previous exercise, starting with the view.
Follow these steps to create your APIView class:
# /becoming_a_django_entdev/chapter_8/views.py
...
from django.shortcuts import render
from ..chapter_3.models import ..., Seller
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
...
# /becoming_a_django_entdev/chapter_8/views.py
...
class GetSellerHTMLView(APIView):
permission_classes = [IsAuthenticated]
template_name = ‘chapter_8/details/seller.html’
def get(self, request, format=None, id=0, *args,
**kwargs):
if request.user.is_authenticated and
request.user.has_perm
(‘chapter_3.view_seller’):
try:
seller = Seller.objects.get(id=id)
except Seller.DoesNotExist:
seller = None
else:
seller = None
context = {‘seller’: seller,}
return render(
request,
self.template_name,
context = context
)
Here, the new GetSellerHTMLView class mimics the GetSellerView class that we created in the previous exercise, except now it uses the APIView class, provided by the Django REST framework. We only need to specify the get() method in this class as well; there is no need for the post() method since we are not working with form objects. We are only creating a view that handles the GET API method at this time, to view/read an object. The template we are mapping to this view is the /chapter_8/templates/chapter_8/details/seller.html file, which we will create in the next subsection. We need to pass id=0 into the get() method, as is highlighted in the preceding code, in anticipation for how we will write the URL pattern for this API endpoint. We have to explicitly set id=0 in the get() method since we are using the APIView class. If you are inheriting the regular View, FormView, CreateView, UpdateView, or DeleteView classes, you would only have to write id without the =0 part. The same applies to the format=None argument, which is only needed when working with APIView classes and not regular View classes.
This approach is reliant on a user logged into your system, by accessing the currently logged-in user with the request.user object. Users outside your organization, who do not have access to the Django admin site, would have to use an authorization token to log in, which will be discussed later in this chapter. Even though we changed the AUTH_USER_MODEL setting in Chapter 3, Models, Relations, and Inheritance, to use the Seller model instead of the Django User model, we can still access the current user in the request object by using request.user. You do not have to use request.seller; in fact, that will result in an error. When using the is_authenticated property of that user, we can determine whether the user is actually logged in with an active session.
The has_perm() method is used to check the permissions of that user. In this case, we are checking whether the user has read/view permissions on a Seller model object using ‘chapter_3.view_seller’. If the user is authenticated and has the correct permissions, we are performing a query to look up the Seller object being searched by using the ID provided. If the user is not authenticated, then we are setting the seller variable to None, which we will use to compare whether or not it has a value within the template file.
That seller variable then gets passed into the context of the template being used, so that we can access its data. Also, we need to wrap the query statement in a try/except block, which is necessary to prevent runtime errors when the user searches for a Seller that does not exist. With the try/except block, we can set the value of seller to None, allowing the program to continue to run without errors. When used in the template, it will indicate that the search returned nothing.
We are using the render() method provided by the django.shortcuts library instead of the TemplateResponse class that we have been using up to now. This is because we want to return only a snippet of HTML and not an entire HTML page, with all of the bells and whistles that a page may have.
Now that the view is created, let’s construct the template that uses that seller object as context.
Follow these steps to prepare your template:
# /becoming_a_django_entdev/chapter_8/templates/chapter_8/details/seller.html
{% load static %}
<h1>Seller Details</h1>
{% if seller %}
<h2>{{ seller.first_name|safe }} {{
seller.last_name|safe }}</h2>
<h3>{{ seller.name|safe }}</h3>
{% if seller.vehicles %}
<ul>
{% for vehicle in seller.vehicles.all %}
<li>{{ vehicle.fullname }}</li>
{% endfor %}
</ul>
{% endif %}
{% else %}
<p>
<b>No Seller to Display</b><br />
<em>or you <b>DO NOT</b> have permission</em>
</p>
{% endif %}
Note that we are not extending any another template in this file. All we are doing is displaying simple text objects as a component of a page and not the page in whole. A conditional statement compares whether or not the seller object has a value, using the {% if seller %} statement. If no seller object exists, text is rendered showing the message No Seller to Display. If a seller does exist, then another conditional statement compares whether or not the seller has any vehicles, using the {% if seller.vehicles %} statement. If vehicles do exist, we iterate through all of the vehicle objects using the {% for vehicle in seller.vehicles.all %} statement. It is important that you add .all to the end of this statement; otherwise, you will receive errors. This is how you access any nested list of objects found in a single object within the Django template language. We use the fullname property method created in Chapter 3, Models, Relations, and Inheritance, to print the full name of the vehicle as an <li> HTML node object.
Now that we have our template, let’s move on to modify the $gotoSPA_Page() function created in the previous exercise.
Change the url variable in the existing $gotoSPA_Page() function to point to the new endpoint, which we will write in the next subsection as `/chapter-8/seller/${id}/`, as in a singular seller in contrast to the plural sellers that we used in the previous exercise.
Take the following step to modify your JavaScript function.
In the $gotoSPA_Page() function, make the following highlighted changes:
# /becoming_a_django_entdev/chapter_8/static/chapter_8/js/site-js.js
function $gotoSPA_Page() {
...
var url = `/chapter-8/seller/${id}/`;
fetch(url, {
method: ‘GET’,
headers: {
‘Content-Type’: ‘application/json’,
}}).then(async(response) => {
return await response.text();
}).then(async(data) => {
container.innerHTML = await data;
});
}
In the preceding code, we are still using the async and await keywords but you are not required to do so. The first three constants of the $gotoSPA_Page() function, container, input, and id, are left untouched and are represented by the preceding three-dot notation.
That’s it; now all we have to do is create the URL pattern that will act as the API endpoint being used.
Take the following step to map your URL pattern.
In your /chapter_8/urls.py file, add the following highlighted pattern, keeping the get-seller path that we previously wrote:
# /becoming_a_django_entdev/chapter_8/urls.py
from .views import (
...,
GetSellerView,
GetSellerHTMLView
)
...
urlpatterns = [
...
path(
‘chapter-8/get-seller/’,
GetSellerView.as_view(),
name = ‘get-seller’
),
path(
‘chapter-8/seller/<int:id>/’,
GetSellerHTMLView.as_view(),
name = ‘seller-detail’
),
]
We will still need that first URL pattern because it is the page that triggers the API request, containing the Get Seller Details button.
That should be it. Now, let’s see what this looks like in action.
To demonstrate this in action, follow these steps:
If you have the Network tab of your browser tools open, you will also see that this action was performed without reloading or redirecting your page. You can style this to look exactly as you need it. For this example, we just used simple HTML nodes with minimal styling and formatting to demonstrate this exercise.
By doing this, we are giving this user the exact permission that we are checking for in the GetSellerHTMLView class. Remember to click the Save button at the bottom of this page before proceeding.
This exercise demonstrated how to use the Django template language to preformat HTML that is being returned in an API GET request. As we discovered working with this exercise, we actually need a user who is already logged into the Django admin site of the site before performing this operation. Without being logged in, this approach will not work. This is controlled by the Staff status checkbox, under the Permissions section when editing a user, which grants that user the ability to access the Django admin site. With the Staff status checkbox left unchecked, a user cannot access your system and thus, will not be able to use any of the permissions in the permission system.
Note
Switch your original superuser, with the username admin, back to its original settings, with the Staff status and Superuser status checkboxes enabled and the individual permissions and group permissions all removed. Make sure this is done and that you are logged in with this user before proceeding to the next exercise.
If you need to build an API that doesn’t grant users access to your Django admin site, then authentication tokens will be needed. In the next exercise, we will use authentication tokens in combination with the Django REST framework to achieve this task.
In this exercise, we will be treating the API that we built earlier in this chapter as if it is now an API provided by a third party. Pretend for a moment that you did not build your API and we will practice authenticating by using a security token. Token security will be used in addition to the individual model permissions as we did in the previous exercise. This will be done whether you grant a user access to the Django admin site or not. That also means we will create a new user/seller for this exercise and then restrict that user’s access to the Django admin site for demonstration purposes.
We will follow the same steps as the previous two exercises next.
This exercise requires a little bit of configuration inside the project’s settings.py file before we can get started with the same steps as before.
Follow these steps to configure your project:
# /becoming_a_django_entdev/settings.py
INSTALLED_APPS = [
...
‘rest_framework’,
‘rest_framework.authtoken’,
]
REST_FRAMEWORK = {
‘DEFAULT_AUTHENTICATION_CLASSES’: (
‘rest_framework.authentication.TokenAuthentication’,
‘rest_framework.authentication.SessionAuthentication’,
),
‘DEFAULT_PERMISSION_CLASSES’: [
‘rest_framework.permissions.
DjangoModelPermissionsOrAnonReadOnly’
],
}
The rest_framework.authtoken app is already installed in your virtual environment, so you do not have to install any additional pip packages. It comes standard when installing the djangorestframework package but does not get enabled in your project with just the basic settings needed for this framework. If we actually intend to use it, we have to add the two authentication classes shown previously to the REST_FRAMEWORK setting, telling the Django REST framework to use token authentication with all of its APIView classes. This means that tokens will be needed for any custom endpoints that we create using that APIView class, as well as all of the endpoints created using the router method from earlier in this chapter.
Endpoints created using the router method are all constructed using the APIView class. Adding the SessionAuthentication class means that we will enable the ability for users to log into the Django admin site to test that endpoint using the Browsable API. Without it, you will see a message indicating you are not authenticated. We will also leave the DjangoModelPermissionsOrAnonReadOnly permission class in the settings shown previously to continue to check for model-level permissions.
Please also make sure you are following proper Python indentation. There is not enough room to display that properly in the code shown previously.
(virtual_env) PS > python3 manage.py migrate
(virtual_env) PS > python manage.py drf_create_token test
Next, we will proceed in the same order as we did for the last two exercises.
Now, we need to create a new view class for this exercise. It will be used for a new endpoint that we will add to the API before we treat it like someone else built it for us. This endpoint will only return standard JSON data and will not return the preformatted HTML that we practiced doing in the previous exercise. JSON is what is traditionally returned in API requests.
Follow these steps to prepare your view class:
# /becoming_a_django_entdev/chapter_8/views.py
...
from django.http import JsonResponse
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
from .serializers import SellerSerializer
from ..chapter_3.models import ..., Seller
...
class GetSellerWithTokenView(APIView):
permission_classes = [IsAuthenticated]
# /becoming_a_django_entdev/chapter_8/views.py
...
class GetSellerWithTokenView(APIView):
...
def get(self, request, format=None, id=0, *args,
**kwargs):
seller = None
req_user = request._user
if req_user.has_perm(‘chapter_3.view_seller’):
perm_granted = True
try:
seller = Seller.objects.get(id=id)
except Seller.DoesNotExist:
pass
else:
perm_granted = False
# /becoming_a_django_entdev/chapter_8/views.py
...
class GetSellerWithTokenView(APIView):
...
def get(self, request, format=None, id=0, *args,
**kwargs):
...
context = {
‘request’: request,
‘seller’: seller,
}
seller = SellerSerializer(
seller,
context = context
)
new_context = {
‘seller’: seller.data,
‘perm_granted’: perm_granted
}
return JsonResponse(new_context)
Note
When using the JsonResponse() object to return data as formatted JSON in your endpoint in this way, your endpoint will not be readily available in the Browsable API tool. If you wish for it to be accessible via that tool, use Response() instead. Keep in mind that it may alter the way developers work with the returned data.
In the preceding class, we are following the same logic format as was used in the GetSellerHTMLView class, written in the previous exercise. We added a property called permission_classes, which uses the IsAuthenticated class. This is needed to work with token authentication. We added an additional query to the get() method. The logic here is that we are using two items added to the request headers when the request is sent, using the fetch() JavaScript function. Those two headers are HTTP_AUTHORIZATION and HTTP_USER, which we will soon add to our JavaScript function.
The request._user item is used to look up the user associated with that request, whether the user is logged into the Django admin site or is passed into the request via the HTTP_USER header, that being the test user created for this exercise, the user we will associate with the API request. We are looking up that user to compare individual model permissions using the same has_perm() method from the previous exercise. If the API request user is found, then we are performing the same logic as before to check whether that user has permission to view a seller object. This time, we removed the is_authenticated property from that conditional statement, as we are now relying on this class’s token authentication. If you granted your test user the ability to view a seller object, the logic continues to look up the seller with the ID provided in that input field, the same as before. If your test user is not granted the ability to view a seller object, then the perm_granted context item will return False, to provide an indicator in the data being returned to us.
The context was broken up into two different items, shown in step 3, because the request is needed in the context when using SellerSerializer. Then, we are removing that request from the final context being returned as JsonResponse().
This exercise does not require a brand-new template. It will be returning only JSON and is not following the preformatted HTML example.
We will be using the same JavaScript example provided in the Modifying the JavaScript subsection under the Writing custom API endpoints section.
Take the following step to modify your JavaScript for this exercise.
In the same JavaScript file, make the following highlighted changes to your existing $gotoSPA_Page() function:
# /becoming_a_django_entdev/chapter_8/static/chapter_8/js/site-js.js
function $gotoSPA_Page() {
...
var url = `/chapter-8/sellertoken/${id}/`;
fetch(url, {
method: ‘GET’,
headers: {
‘Content-Type’: ‘application/json’,
‘Authorization’: ‘Token your_token’,
‘User’: ‘test’
}}).then(async(response) => {
return await response.text();
}).then(async(data) => {
container.innerHTML = await data;
});
}
In this example, we are leaving the first three constants, container, input, and id, the same as they have been written in previous examples and represented by the preceding three-dot notation. We are changing the url variable to point to a new path that we will create shortly, `/chapter-8/sellertoken/${id}/`. The rest of the fetch() function is left the same as before, where we are returning the result as preformatted HTML instead of JSON. The only thing different is that we are adding the ‘Authorization’ and ‘User’ items to the headers of this request. The value of the ‘Authorization’ item is the value of the token that was created, the one you were asked to copy earlier; paste that in place of the preceding your_token shown. The value of the ‘User’ item is the username of the new user/seller that you just created, the one assigned to the token that you are providing.
Note
Tokens should never be kept in a JavaScript file, as is done in the preceding example. An explanation for why this is done in the preceding example is provided in the subsection titled Third demo at the end of this exercise.
We are almost done! We just need to map the endpoint that we are communicating with to our new view class.
Take the following step to map your URL pattern.
In your /chapter_8/urls.py file, add the following path. You can leave the other paths that have already been created, as depicted:
# /becoming_a_django_entdev/chapter_8/urls.py
from .views import ..., GetSellerView, GetSellerHTMLView,
GetSellerWithTokenView
...
urlpatterns = [
...
path(
‘chapter-8/get-seller/’,
GetSellerView.as_view(),
name = ‘get-seller’
),
path(
‘chapter-8/seller/<int:id>/’,
GetSellerHTMLView.as_view(),
name = ‘seller-detail’
),
path(
‘chapter-8/sellertoken/<int:id>/’,
GetSellerWithTokenView.as_view(),
name = ‘seller-token-detail’
),
]
That’s it; let’s demonstrate this code in action next.
Follow these steps to see this in action:
You could also add print() statements to your code to verify if and when each condition is actually met. Additional print() statements and comments providing details have been included with the code of this book.
Note
You will see hyperlinked vehicles in the preceding example if you are inheriting the HyperlinkedModelSerializer class in your serializers. If you are still using the ModelSerializer class, only numeric IDs will be displayed.
Important Note
In a real-world example, the token should never be kept directly in the JavaScript file or even a Python file, if you are using the requests package. Instead, you should consider creating an additional API endpoint that utilizes the built-in token generator called obtain_auth_token, as discussed here: https://www.django-rest-framework.org/api-guide/authentication/#generating-tokens. The token generator works by accepting a username and password that is attached to the headers of the first API request and then receives a newly created token in return. Then, a second API request is used to execute the action desired by attaching the token received from the first request to the second request’s headers. The Django REST framework does the rest of the work to authenticate that request using the credentials provided. The examples provided in this exercise are intended only to demonstrate how to perform requests after the token has already been received. The approach of generating tokens requires the use and knowledge of signals, which goes beyond the scope of this book.
If using the double-request approach as noted in the preceding information box, you can now let developers of third-party apps communicate with your API without needing to create user accounts. However, you can still create a user in your system for that third-party user in order to keep using the granular permission levels of your system as was done throughout this exercise. The path you take is determined by the requirements of your project.
The examples provided throughout this chapter demonstrate a simple way to construct and work with your newly created API, in a variety of ways! If you want to give your app an SPA-like feel, the simplest implementation is to use the vanilla JavaScript fetch() function or the jQuery ajax() function. Instead of writing your own actions with either of these two functions, you could settle upon using a JavaScript-based framework, such as React, AngularJS, or Vue.js, just to name a few. The JavaScript-based frameworks can format and style your HTML on the client side. One of the template-based approaches provided in this chapter also demonstrates how this work can be transferred from the client side onto the server side. This provides you with numerous tools in your toolbox, in regard to building and working with an API.
We also learned how to work with authentication tokens and discovered that we can still work with tokens when formatting HTML on the server side. However, the token approach does require additional, more advanced topics of Django and security measures before being able to fully implement that approach on a live site. The Django REST framework is intended to be the backend of an API and is designed to work with any frontend that a team settles upon.
In the next chapter, we’ll explore how to test our project and make sure what has been written does actually work. To do that, we will explore how to write automated test scripts, and then install a new package that provides even more tools, as well as learning how to use them.
34.231.180.210