Django and Modern JS Libraries - Backend (1)

canburaks

Can Burak Sofyalioglu

Posted on June 22, 2020

Django and Modern JS Libraries - Backend (1)

Django and Modern JS Libraries - Backend

What is this tutorial about?

Django and Modern JS Frameworks will be a tutorial series that integrates Django and contemporary frontend solutions such as React and Svelte. Other framework/library integrations are also planning in the future.

The Project Description

  • We will setup one Django server and make two simple single page applications. Each of them will use different Javascript libraries and both of them will communicate with Django server.

  • React application will be written from scratch with webpack in the second part. Also note that create-react-app will not be used.

  • The latest and third part of this tutorial will be the Svelte integration.

There are 2 projects and 3 articles in this series:

  • Django server and GraphQL API setup

  • React application setup with webpack and integrating it with our back-end.

  • Svelte application setup with webpack and integrating it with our back-end.

What are the requirements to follow?

  • Basic level of knowledge about Python and Django framework

  • Basic level of Javascript and React is a must.

Motivation and Preliminary Information

Python is my first programming language. When we were making a movie recommendation engine, we must integrate it with Facebook’s React library because we want it to be a single page application. My level of knowledge about Javascript was at introduction level. Proficient in an unfamiliar programming language takes some time. Also, I like Python ecosystem because of the excellent data science libraries and giving up from Python was never a choice. To sum up, it really took some time to integrate Django and React. When I recently published my development blog and Istanbul travel guide, I edited and update all my old articles. During this time, another front-end library was released, and it excited me a lot: Svelte. I also added an integration article with Svelte and Django. I hope that this article series will help newcomers a bit to to solve their problems.

There will be only one server on each project which is running in a production environment.

INTRODUCTION

Single Page App Architecture

What is a Single Page Application?

In classic web pages, all HTML, CSS and JS code are arranged and transferred by the server in a render-ready form. When a browser receives the code, it immediately render elements on a screen. If a user clicks a link, then the browser makes another request to the server. The server will make all the logical operations and respond with another render-ready code.

In modern client-side apps, some logical operations are handled by Javascript code which is executed in the browser of users. Because of this, servers send all the website code in the first request. Thus, browsers need extra time for the first contentful painting.

Except the first loading, client side apps works faster and feels more native because some actions are done immediately on browser and I/O operations can be done via asynchronous behavior of Javascript. Therefore, users still see your app rather than blank white page.

Browsers are amazing and capable of many impressive things. Because of this capability, handling resource heavy operations in the user’s browser can be a suitable alternative. Otherwise, those operations make our server busy and can increase the bill.

Anyone who slaps a ‘this page is best viewed with Browser X’ label on a Web page appears to be yearning for the bad old days, before the Web, when you had very little chance of reading a document written on another computer, another word processor, or another network. ~ Tim Berners-Lee

Create The Backend with Django

Step-1: Create a Django project from scratch

Setup Django

Let's create a virtual environment for clean setup.

This virtual environment will be an active environment for all three articles.

python3 -m venv tutorial-env

# activate
source ./tutorial-env/bin/activate
Enter fullscreen mode Exit fullscreen mode

Install Django and dependencies

# install our dependencies
pip install ipython django django_extensions django-cors-headers "graphene-django>=2.0"

#create a django project
django-admin startproject djangoproject

# change directory
cd djangoproject

# create templates directory
mkdir templates

# create static folder
mkdir static

# create utils folder for initial data
mkdir utils

Enter fullscreen mode Exit fullscreen mode

2- Configuring and running

Update your ***'djangoproject/djangoproject/settings.py'*** file. Extra settings are labeled as *'New ...'.*

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    "django_extensions",# New! (useful extension)
    'graphene_django',  # New! (for graphql communication)
    'corsheaders',      # New! (for cors request in dev env)
]

# New (for improved interactive shell)
SHELL_PLUS = "ipython"

# New (it allows webpack development server to make cross origin request)
CORS_ORIGIN_WHITELIST = (
    'http://localhost:8080',
)


MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'corsheaders.middleware.CorsMiddleware', # New Add this
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': (os.path.join(BASE_DIR, 'templates'),), # New
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

#New 
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'static'),
)
Enter fullscreen mode Exit fullscreen mode

Before starting our project, we should first make database migration. After, we will run our server and will see that is working.

# create migration for django-orm
python manage.py migrate
Enter fullscreen mode Exit fullscreen mode

Now, if everything goes well, Django server will start. You can open your browser and check the address 127.0.0.1:8000 You will see a screen like that:

Django running screen

Step-3: Creating a movie app

We will create a movie model with basic fields that a movie should have.

Before that, we should give some information about the field choices.

*Why there is URL field for poster rather than image field?*

Because serving static files in production is not recommended, we use only the URL field. Fetching the image from remote and then saving it to our production storage is a topic of another post. Because of this, we will save only the poster’s URL, not the poster’s itself as an image file. Also, sending static files like images is not a good approach. We will send the exact URL of an image to the user. Then, the *user’s browser fetches the image* from this.

*What is a slug and why it should be unique?*

Let me explain with an example: I published the original article on cbsofyalioglu[com]/post/django-and-modern-js-libraries-backend

The last part of the URL , django-and-modern-js-libraries-backend, is the slug of the post and also it is an identifier which makes the URL distinctive from other post pages.

In the GraphQL part of the tutorial, you will see that we will use this slug as a query parameter meaning that we will do database queries according to slug. Therefore, it should be unique.

We can also choose another identifier as the as URL identifier, but it’s clear that the URL will not be human-readable address.

Search engine indexing and ranking is a vital part of any website targeting new users. Readable URL address’ are good for users themselves and also suggested by search engine guides. Also, Google webmaster guidelines recommends using clean and concise URL structures.

Let’s make our model and define its properties and methods. In the next step, we will populate our database with initial records. Therefore, I added a class method responsible for database population.

Let's create a Django app. This app will includes our model. The database tables will be done according to this. Also API requests will be based on this.

# create new Django app
python manage.py startapp items
Enter fullscreen mode Exit fullscreen mode

Update settings .py


INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    "django_extensions",
    'graphene_django', 
    'corsheaders',
    "items"             # New! (make our app will active)
]
Enter fullscreen mode Exit fullscreen mode

Open ***'djangoproject/items/models.py'*** file and copy the below code.


# items.models

from django.db import models

class  Movie(models.Model):

    id  = models.IntegerField(primary_key=True)
    name = models.CharField(max_length=100)
    year = models.IntegerField(null=True)
    summary = models.TextField(max_length=5000,null=True)
    poster_url = models.URLField(blank=True, null=True)
    slug = models.SlugField(max_length=50, null=True,blank  =True, unique=True)
    # order items in descending order
    class  Meta:
        ordering = ["-year"]

    # the method which defines string output of class
    def  __str__(self):
        return  self.name

    # the method which loads initial data
    @classmethod
    def  import_records(cls, record_list):
        for record in record_list:
            # create record if id is not exist
            if  not  cls.objects.filter(id=record.get("id")).exists():
                new_movie =  cls.objects.create(**record)
            else:
                print(f"Id:{record.get('id')} is already exist.")
        print("Import operation done successfully")

Enter fullscreen mode Exit fullscreen mode
# make database migrations
python manage.py makemigrations
python manage.py migrate
Enter fullscreen mode Exit fullscreen mode

Step-4: Populating database with initial data

There is no movie record currently in our database. We will provide a small initial data to create some movie records. All the data is provided by the community built The Movie Database (TMDb). We will use those records in our app.

First, create a *"initial_data.py"* file in ***"djangoproject/utils"*** folder. After, you can copy and paste below data to this new file.

initial_data = [{
    'id': 503919,
    'name': 'The Lighthouse',
    'year': 2019,
    'summary': 'The hypnotic and hallucinatory tale of two lighthouse keepers on a remote and mysterious New England island in the 1890s.',
    'slug': 'the-lighthouse-2019',
    'poster_url': 'https://image.tmdb.org/t/p/w185/3nk9UoepYmv1G9oP18q6JJCeYwN.jpg'
    },{
    'id': 475557,
    'name': 'Joker',
    'year': 2019,
    'summary': 'During the 1980s, a failed stand-up comedian is driven insane and turns to a life of crime and chaos in Gotham City while becoming an infamous psychopathic crime figure.',
    'slug': 'joker-2019',
    'poster_url': 'https://image.tmdb.org/t/p/w185/udDclJoHjfjb8Ekgsd4FDteOkCU.jpg'
    },{
    'id': 530385,
    'name': 'Midsommar',
    'year': 2019,
    'summary': "A couple travels to Sweden to visit a rural hometown's fabled mid-summer festival. What begins as an idyllic retreat quickly devolves into an increasingly violent and bizarre competition at the hands of a pagan cult.",
    'slug': 'midsommar-2019',
    'poster_url': 'https://image.tmdb.org/t/p/w185/rXsh4MI6uyVgZBSSzXCfitJnVPy.jpg'
  },{
    'id': 531428,
    'name': 'Portrait of a Lady on Fire',
    'year': 2019,
    'summary': 'On an isolated island in Bretagne at the end of the eighteenth century, a female painter is obliged to paint a wedding portrait of a young woman.',
    'slug': 'portrait-of-a-lady-on-fire-2019',
    'poster_url': 'https://image.tmdb.org/t/p/w185/3NTEMlG5mQdIAlKDl3AJG0rX29Z.jpg'
  }]
Enter fullscreen mode Exit fullscreen mode

Now, we will import and create new records at database level. Normally we should have open Django shell. However, *shell_plus* command which is provided by *django_extensions* is more functional, so we will use this. It automatically imports all apps we created.

# open interactive shell
python manage.py shell_plus
Enter fullscreen mode Exit fullscreen mode
# let's check database and verify it's empty
Movie.objects.all()
# prints: <QuerySet []>

# import the records which we took it from github repo
from utils.initial_data import initial_data

# create records in the database
Movie.import_records(initial_data)

# prints 'Import operation done successfully'

# query database and verify it is not empty
Movie.objects.all()
Enter fullscreen mode Exit fullscreen mode

Our model and database are ready. You can close the shell with *quit* command.

The next section will be creating a GraphQL API.


GraphQL API

Setup GraphQL API

In this section we will make our app’s API part with Graphene which is a GraphQL framework implementation of Python.

What we do in this section is:

  1. Creating another Django app: We will put all API configurations in there.

  2. Creating an API Schema that has three parts: API-model, Resolvers and Queries.

  3. Creating a URL endpoint:The client-side application will requests all information to this URL address.

Step 1 - Creating another Django app for API configurations

Actually, there is not any obligation to make another app because this app will not create or update any database table. However, to put all API-related configurations in one place, I chose this way.

Let’s create the second backend app. The name of the app should not have to be *‘gql’* , but if you set another name, you should also change the name of the schema in *settings .py* later.

Open your terminal at the root level of your project.

# create app with the name gql
python manage.py startapp gql

# change directory
cd gql


# create schema.py file
touch schema.py
Enter fullscreen mode Exit fullscreen mode

Step 2 - Creating an API Schema: API-model, Queries and Resolvers

API-schema will have three parts considering the scope of the article.

Those are as follows:

  1. *API-Model-Type:* A class which is a mapped version of movie model. You can send responses based on this, if the response is not a primitive type.

  2. *Queries:* The client-side app will use these queries for distinct requests.

  3. *Resolvers:* Those are response functions of fields. When the client side request matched with a query, the resolvers come into play and make all the logical parts, then send information back to the client.

*A )* *API-Model-Type and Resolvers*

A class which is a mapped version of an existing Django model. It is the intermediary layer between Django model (or database) and API response. The fields of ModelType will be the same fields of the corresponding model. We can also create custom fields that are not belong to the corresponding model.

Compare Django model and corresponding GraphQL model

You can check other scalar types from the Graphene Python documentations..

We will step by step write the schema .py file. You can copy and paste it.

import graphene
from items.models import Movie
from graphene_django.types import DjangoObjectType

# api-movie-model
class MovieType(DjangoObjectType):
    id = graphene.Int()
    name = graphene.String()
    year = graphene.Int()
    summary = graphene.String()
    poster_url = graphene.String()
    slug = graphene.String()

    # define which model will be the base
    class Meta:
        model = Movie

    # 'self' corresponds to the item of Django model 
    # like The Lighthouse or Joker
    def resolve_id(self, info):
        return self.id

    def resolve_name(self, info):
        return self.name

    def resolve_year(self, info):
        return self.year

    def resolve_summary(self, info):
        return self.summary

    def resolve_poster_url(self, info):
        return self.poster_url

    def resolve_slug(self, info):
        return self.slug
Enter fullscreen mode Exit fullscreen mode

Let me explain the above code.

The 'MovieType’ class is a mapped version of Movie model. You may notice that all the fields are the same. We defined the base model in class Meta, so the movie model will be the base model.

It is important to say that resolver names are written in snake case like ‘resolve_poster_url’. However, when we write client-side queries, those will be pascalCase such as ‘posterUrl’. You see that later.

*B ) Queries and Resolvers*

The client-side app will use these queries for distinct requests. We will also write client-side queries in its part. A client side query should match with server-side query. Therefore, this part also defines the allowable requests of the frontend part.

For the sake of simplicity, we will define only two queries.

  • The movie_list query (*resolve_movie_list*) returns to all the movies in the database

  • The movie query (*resolve_movie) returns only specific movie if the parameter (slug*) is matched.

Let add this code below MovieType class.


class Query(graphene.ObjectType):
    movie_list = graphene.List(MovieType)
    movie = graphene.Field(MovieType, slug=graphene.String())

    def resolve_movie_list(self, info, *_):
        # for large lists only query what you need
        return Movie.objects.all().only("name", "poster_url", "slug")

    def resolve_movie(self, info, slug):
        movie_queryset = Movie.objects.filter(slug=slug)
        if movie_queryset.exists():
            return movie_queryset.first()

schema = graphene.Schema(query=Query)
Enter fullscreen mode Exit fullscreen mode

In the last row, you will see a schema object. This is the root node of the API. We should tell the Django server to use this as our API schema. To do so, update the *settings. py*.

# djangoproject/djangoproject/settings.py

# New - Add this part
GRAPHENE= {'SCHEMA': 'gql.schema.schema'}

# MIDDLEWARE = [..]

Enter fullscreen mode Exit fullscreen mode

Step 3 - Create URL endpoints

In REST API, we define different URL Endpoints for different requests. The one of the good part of GraphQL is that we will only define one endpoint. All the requests will be done through that.

Copy the below code and paste it to djangoproject/djangoproject/urls .py**** file.

from django.contrib import admin
from django.urls import path
from graphene_django.views import GraphQLView
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import TemplateView

urlpatterns = [
    path('admin/', admin.site.urls),

    # apiclient on client-side will request this adress later
    path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True))),


    # index.html file will be our root template. When a user opens our webste,
    # this file will be sent by server at first. After then, api requests
    # will directed above address.

    # (it points to ~/Blog/djr/templates/index.html)
    # (currently there is no file, webpack production bundle will come here )
    path("", TemplateView.as_view(template_name="index.html")),
    ]
Enter fullscreen mode Exit fullscreen mode

You noticed that we set *graphiql=True.* This is GraphQL interactive panel. We can make a query like a client app through this panel. You will also see the details of all queries.

Now, please run the server in the root folder : 'djangoproject/'


python manage.py runserver

Enter fullscreen mode Exit fullscreen mode

Open *127.0.0.1:8000/graphql* address from your browser. We will query the movie with specific identifier (slug). On the left panel, paste this and press the *Execute Query* button.

Please note that, we request fields with pascalCase. (*posterUrl*)


query {

movie(slug:"the-lighthouse-2019"){

id, name, posterUrl

}

}

Enter fullscreen mode Exit fullscreen mode

and the response will be in JSON format like this.


{

"data": {

"movie": {

"id": 503919,

"name": "The Lighthouse",

"posterUrl": "https://image.tmdb.org/t/p/w185/3nk9UoepYmv1G9oP18q6JJCeYwN.jpg"

}

}

}

Enter fullscreen mode Exit fullscreen mode

Our API are ready to response the requests. This part of the tutorial is finished.

Now, we will make two different client-side apps. Please choose one of them to continue.

💖 💪 🙅 🚩
canburaks
Can Burak Sofyalioglu

Posted on June 22, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related