Django & React

React is a popular Javascript library for writing user interfaces. There are lots of websites on how to get started with React and only a few which talk about how to configure React & Django together. It is perfectly possible to keep the user interface and backend as separate projects but for smaller projects this is may not be applicable.

This is my first stab at writing down how to get them working together. Much of this is taken from Valentino Gagliardi’s blog. I’ve had to tweak some bits which seem to have changed since Valentino wrote his articles.

The Problem

Normally, when you want to create a React application you execute the command:

npx create-react-app my-react-app

But if you try and do this inside a Django application you’ll probably encounter the error:

The directory <> contains files that could conflict

One way to get around this is to put the React UI in a subdirectory of your Django application. Another way is to integrate it into your Django app manually.

Getting Started

I’m going to assume you’ve already got Django & npm installed.

As I’ve already written how to get started with Django so I’ll just list the commands to create the bare bones Django application:

$ mkdir PROJECT_NAME
$ cd PROJECT_NAME
$ virtualenv -p python3 virtualenv
$ source virtualenv/bin/activate
$ pip install django
$ django-admin startproject my_project .
$ django-admin startapp main

Then edit the file my_project/settings.py and in the section INSTALLED_APPS add the line:

'main',

Now we’ll start to add React.

First off, we need to create some directories for React in our application:

$ cd main
$ mkdir -p ./src/components
$ mkdir -p ./{static,templates}/frontend

Now we create a package.json file to tell npm what modules we want:

$ cat <<EOF >package.json
{
  "name": "frontend",
  "version": "1.0.0",
  "description": "My React Application",
  "main": "index.js",
  "scripts": {
    "dev": "webpack --mode development",
    "build": "webpack --mode production"
  },
  "keywords": [],
  "author": "John Smith",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.12.3",
    "@babel/preset-env": "^7.12.1",
    "@babel/preset-react": "^7.12.1",
    "babel-loader": "^8.1.0",
    "webpack": "^5.2.0",
    "webpack-cli": "^4.1.0"
  },
  "dependencies": {
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-router": "^5.2.0"
  }
}
EOF

Next we need to create a configuration file for something called Babel. Babel allows Javascript programmers to ignore differences between browser Javascript engines.

$ cat <<EOF >babel.config.json
{
    "presets": [
        "@babel/preset-env",
        "@babel/preset-react",
    ],
    "plugins": [
        "@babel/plugin-proposal-class-properties"
    ]
}
EOF

Up next is a Webpack configuration file. Webpack is used to package Javascript code.

$ cat <<EOF >webpack.config.js
const path = require('path');
 
module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'static/frontend'),
    filename: 'main.js'
  },
  module: {
    rules: [
      {
        loader: 'babel-loader',
        test: /\.js$/,
        exclude: /node_modules/,
      }
    ]
  }
};
EOF

Now we’ve created our configuration files, we can start to write some React.

$ cat <<EOF >src/index.js
import React from "react";
import ReactDOM from 'react-dom';
import App from "./components/App";
 
ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('app')
);
EOF

$ cat <<EOF >src/components/App.js
import React, { Component } from "react";
import { render } from "react-dom";
 
export default class App extends Component {
  constructor(props) {
    super(props);
  }
 
  render() {
    return (
       <p>Hello World from React</p>
    );
  }
}
EOF

Now we can create a view to invoke React:

$ cat <<EOF >templates/frontend/index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Django REST with React</title>
</head>
<body>
<div id="app">
    <!-- React will load here -->
</div>
{% load static %}
<script src="{% static "frontend/main.js" %}"></script>
</body>
</html>
EOF

$ cat <<EOF >>views.py
def index(request):
    return render(request, 'frontend/index.html')
EOF

Finally, we need to update urls.py in the Django Project directory to reference this view:

$ cd ../my_project
$ cat <<EOF >urls.py
from django.urls import path
 
urlpatterns = [
    path('', views.index),
]
EOF

Finally, go back to the application directory and let’s build our Javascript library.

$ cd ../main
$ npm run dev

Once npm has finished, you can start your Django application and fire up a web browser

$ cd ..
$ ./manage.py runserver

Dummies Introduction to Django. Part 4 – Settings

We have covered the basics of creating a Django web application. However, before we can go ahead and deploy our application to a website, there’s an elephant in the room we have to acknowledge. Go and take a look at the project/settings.py file. Here are (some) of the settings in there that we need to take care of:

...
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '...'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []
...
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'djangodb',
        'USER': 'djangouser',
        'PASSWORD': 'secret',
        'HOST': 'localhost',
    }
}
...

These are all either settings that need to vary between our development environment and a live environment, or settings that don’t belong in version control. We need to pull these from somewhere outside our application.

So how do we do this? Unfortunately, there does not appear to be any standard way to do this in Django. The Django website offers half a dozen different solutions. I asked a couple of Django developers in my building and they all had different ways to do this too. There is no “right” way to do it: Just a way that works for you.

My chosen method is to store the settings in an external YAML file. This is easier than you think because the settings.py file is just a plain Python file so you can embed whatever code you want in there. (This is probably also why there are so many ways to tackle this problem: You can code whatever solution you can dream of in settings.py)

First off, we need to add a YAML library to our project.

(Django_Project) $ pip install pyyaml

Now we have to create a YAML configuration file for our application to use. This is the core of what I use:

application:
 - secret_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
   debug: true
   allowed_hosts:
   database_password: secret

Yaml is quite a powerful and flexible file format, so you could structure the data in numerous ways. But this is the way I choose. Make sure to change the “secret_key” and “database_password” value to whatever is currently in your settings.py file.

I called this file “settings.yaml” and put in in the root of our virtual environment:

.
├── bin
├── include
├── lib
├── main
├── manage.py
├── project
└── settings.yaml

Now we have to tell our Django app to read and use this new file. First off, at the top of the project/settings.py file, add the line “import yaml” just below the existing “import os” line”

For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.0/ref/settings/
"""

import os
import yaml   <==== Our new line

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)

Next, at the bottom of the settings.py file, we need to add a chunk of code to read and use settings.yaml. First we need to open the file. But before we can do that, we need to know where to find the file. First, we’ll look for an environment variable called “APPLICATION_CONFIG_FILE”. If it exists, we’ll use it as the full path for our yaml file. If the environment variable is not set, we just assume the file is in the root of our virtual environment. This technique will be very useful later on – trust me!

configuration_file = os.getenv('APPLICATION_CONFIG_FILE')
if configuration_file is None:
  configuration_file = './settings.yaml'

Next we have to load our YAML data into memory. Pyyaml provides several methods for loading yaml files. We’ll start with using the safest one.

yaml_data = yaml.safe_load(open(configuration_file, 'r'))

You can read more about the different loader methods available at Pyyaml’s Github page.

Finally, we can take the parameters from the yaml file and apply them to Django

SECRET_KEY = yaml_data['application'][0]['secret_key']
DEBUG = yaml_data['application'][0]['debug']
DATABASES['default']['PASSWORD'] = yaml_data['application'][0]['database_password']
hosts = yaml_data['application'][0]['allowed_hosts']
if hosts is not None:
  for host in hosts:
    ALLOWED_HOSTS.append(host)

There is no error checking in this code. This is a deliberate decision: If we can’t load any of these settings, there’s no point in carrying on as our application won’t work.

Save all theses files and you should be able to run your Django application as before.

Dummies Introduction to Django. Part 3 – Databases

The next big step our application needs is to talk to a database. For the purposes of this, I’ll assume you’re using Postgres.

Step 1 – Installing Postgres.

If you’re using a Linux system, then you can install Postgres using your distribution’s package manager. e.g.

# apt-get install postgresql

If you’re using Mac there are multiple ways you can install Postgres. Homebrew is one (“brew install postgresql”) or download Postgres.App which is a macOS application which contains Postgres and all the normal command line tools)

Step 2 – Setting up Postgres

In this step I’ll assume we’re starting with a brand new setup. There are multiple ways you could configure this, but this is how I would configure the Postgres environment for Django.

To start with, connect to the Postgres database engine with some kind of administrative privileges. On Linux, you might do this by connecting to the Postgres engine as the unix user “postgres”.

$ sudo - U postgres psql

First, we create a user that Django can connect to Postgres with.

postgres=# CREATE USER djangouser LOGIN UNENCRYPTED PASSWORD 'secret';

Next, we create a database:

postgres=# CREATE DATABASE djangodb WITH OWNER = djangouser;

Then we create a database schema to hold our tables:

postgres=# CREATE SCHEMA djangoschema AUTHORIZATION djangouser;

We give our Django user rights to use this schema:

postgres=# GRANT USAGE ON SCHEMA djangoschema TO djangouser;

And finally we make this schema the default one for our Django user by setting its schema search path:

postgres=# ALTER ROLE djangouser SET search_path TO djangoschema;

We can now test this all works by connecting to Postgres as our Django user and performing a couple of basic operations:

$ psql -W djngodb djangouser
djangodb=> CREATE TABLE test1 (id SERIAL, msg VARCHAR);
djangodb=> INSERT INTO test1 (msg) VALUES ('Hello World');
djangodb=> SELECT * FROM test1;
djangodb=> DROP TABLE test1;

If you get an error message connecting to postgres and you’re on linux, try the following command instead:

$ sudo -U postgres psql djngodb djangouser

Step 3 – Setting up Django connection

Now Postgres is ready for us, we are ready to tell Django about Postgres. First, we need to install a Django driver for Postgres. Within our Python virtual environment:

(Django_Project) $ pip install psycopg2--binary

Now we’ve installed the driver, we need to get Django to use it.

Edit the file project/settings.py and search for the section “DATABASES”. It currently says:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

We have to change this to use our Postgres database. Hopefully, all the settings we need should be self explanatory:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'djangodb',
        'USER': 'djangouser',
        'PASSWORD': 'secret',
        'HOST': 'localhost',
    }
}

Step 4 – Using the database

Now Django knows how to talk to our database, we need to actually use it.

We need to create a model – a mapping or representation of a database table into a python/Django data structure. These are stored in the application/models.py file. Edit the file main/models.py

from django.db import models


class Message(models.Model):
  msg = models.TextField()

  def __str__(self):
    return(msg)

This defines a database table called “message” with just one field/column called “msg” which is a text field. We’ve also defined the __str__ method to return the message field if we ever print out the class.

Note that we aren’t going to create any tables in Postgres ourselves: We’ll get Django to do that for us later on.

Next, we’ll need to create a new view for this feature. In views.py, add the following import line at the top:

from main.models import *

Then add the following function call at the bottom of the file:

def messages(request):
  messages = Message.objects.all()
  context = {
    'messages': messages,
  }
  return render(request, 'messages.html', context)

Add the following entry to the list of urlpatterns in the projects/urls.py file:

    path("messages", views.messages, name="messages"),

Finally, we create our view file in main/templates/messages.html

<!DOCTYPE html>
<html>
  <head>
    <title>List of messages</title>
  </head>
  <body>
    <h2>A list of messages</h2>
    <table border="1">
      <thead>
        <tr>
          <th>Message</th>
          <th>Action</td>
        </tr>
      </thead>
      <tbody>
       {% if messages is not None %}
         {% for message in messages %}
           <tr>
            <td>{{ message.msg }}</td>
            <td></td>
           </tr>
         {% endfor %}
       {% endif %}
      </tbody>
   </table>
  </body>
</html>

Now we’re almost ready to run our Django app. But before we can do that, we need to get Django to update our database schema. The following command should do the trick:

python (Django_Project)$ manage.py migrate

If you don’t get a series of “OK”s, check your database connections. Otherwise, you can go for “python manage.py runserver” and go to http://127.0.0.1:8000/messages and..Blank List of Messages

As we haven’t created any means (yet) to add messages, we’ll quickly add a couple direct into the database:

djangodb=> INSERT INTO main_message (msg) VALUES ('Message 1'), ('Message 2');

And reload the web page:

List of messages 1

Success!

Now we want to be able to add messages through the web page, rather than having to delve into SQL.

First, edit main/views.py and add “, redirect” to the initial import line so it now looks:

from django.shortcuts import render, redirect

Then add the following function to the bottom of the file:

def add_message(request):
   new_message = request.POST.get('newMessage')
   if new_message is not None:
     message = Message(msg=new_message)
     message.save()

   return redirect('messages')

Now, edit main/templates/messages.html and add the following section after the “</table>” tag (But before the “</body>” tag:

<h2>Add Message</h2>
    <form action="addMessage" method="post">
      {% csrf_token %}
      <input type="text" name="newMessage" />
      <input type="submit" value="Add Message" />
    </form>

Finally, edit the project/urls.py and add the following line to the urlpatterns section:

path("addMessage", views.add_message, name="add_message"),

*Phew*. Now, reload the page http://127.0.0.1:8000/messages and you should see the new add message table.

List of messages add button

Put some words of wisdom in the text box and click “Add Message” and you should now see your message in the table:

List of messages Hello World

Let’s add the final basic feature: Deleting a message.

First, edit the messages.html file, and in the blank table cell (which is in the column “Action”) add the following HTML code:

<form method="post" action="deleteMessage">
  {% csrf_token %}
  <input type="hidden" name="messageId" value="{{ message.id }}" />
  <input type="submit" value="Delete" />
</form>

Next, add the following method into views.py:

def delete_message(request):
   messageId = request.POST.get('messageId')
   try:
     msg =  Message.objects.get(**{'id': messageId})
     msg.delete()
   except Message.DoesNotExist:
     # Do something
     return redirect('messages')

   return redirect('messages')

Finally, add an entry to urls.py

 path("deleteMessage", views.delete_message, name="delete_message"),

Reload the /messages page in your browser and you should now see a Delete button next to each message:

List of messages with delete button

Hopefully, you should be able to click “Delete” on a message and it will disappear.

Alternative Python Model Organization

In the above example, we’d store all the database models in one file: models.py. But once you start getting a number of model classes, this file can start to get large and unwieldy. So there’s another way to organize the models.

In the application directory, create a directory called “models”. In that directory, create files containing your models. Finally, create a file called “__init__.py” that just has a series of import statements. e.g.

from .message import Message

Dummies Introduction to Django. Part 2 – Simple Web Forms

Now we’ve got a blank Django app to play with, we can start creating web pages.

Hello World

An introduction in computing wouldn’t be the same without the famous quote.

Open up the file project/urls.py. Ignoring the comment at the start of the file, at the bottom we have:

from django.contrib import admin
from django.urls import path

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

We need to add one import and change that path(…) statement, so the file looks:

from django.contrib import admin
from django.urls import include, path

from main import views

urlpatterns = [
    path("",views.homepage, name="homepage"),
]

Next, we want to edit the file main/views.py. Currently, it looks like:

from django.shortcuts import render

# Create your views here.

Add the following two lines after the comment line:

def homepage(request):
  return render(request, 'index.html')

Next, make a directory called “templates” in the main application folder:

$ mkdir main/templates

And in that new directory, create the file called “index.html” and put some basic HTML in there:

<!DOCTYPE html>
<html>
  <head>
  <title>Hello World</title>
 </head>
 <body>
  <h1>Hello World</h1>
 </body>
</html>

Save this file and run the Django app (“python manage.py runserver”) and go to http://127.0.0.1:8000/ and instead of the pretty picture of a rocket, you should see the exciting output:Hello World

So what have we done here?

  1. We’ve imported the views file from the main application
  2. We’ve told Django that the URL “” (the root or “/” URL) should be passed to the view function “homepage” in the  views file. We’ve also called this mapping “homepage”. Naming mappings is optional but has some benefits further down the line.
  3. In the views.py file, we defined the function “homepage” and told it to render the file “index.html”
  4. In the index.html file we put our basic HTML.

We could have merged steps 3 & 4, but it’s probably more likely you’ll be wanting to render something more complex than “Hello World” which is easier to do in a HTML file than putting the HTML in the middle of python code.

Ask and ye shall…

A web application isn’t much good if it can’t take user input. First, edit the index.html file so that it looks like:

<!DOCTYPE html>
<html>
  <head>
  <title>Hello World</title>
 </head>
 <body>
  <h1>Hello World</h1>
  <form method="get">
    <input type="text" name="question" />
    <input type="submit" value="Speak" />
  </form>
  {%  if answer is not None %}
    <p>{{ answer }}</p>
  {%  endif %}
 </body>
</html>

Next, edit the homepage function in views.py so it looks like:

def homepage(request):
  queryString = request.GET.get('question')
  context = {
        'answer': queryString,
  }

  return render(request, 'index.html', context)

If you reload the page (note, I didn’t say you needed to stop and restart the Django runserver process!) You should see the following:Hello World Blank Form

In the text box, enter some text and then press “Speak”

Hello World Woof

So what have we achieved here?

First, our html file can take more than HTML: It can take code!

Second, we’ve shown the homepage function taking a request parameter and outputting a variable to the template.

These are the first steps to writing web applications in Django. The next stage is to get our application talking to a database.

Dummies Introduction to Django. Part 1 – Installing Django

Being new to Django & Python programming in general, I thought I’d write up some notes on how to get started with a Django web application. I have plans as to where I want to get to with this series – but I’m not going to make any promises yet as I don’t want to make a rod for my own back.

Being a dummy in this area, I do not promise everything I say here is the best/right way to do it. It is just what I’ve discovered. If you think there are better ways to do something, let me know.

Most of the time I’ll assume you’re using a unix-type environment.

Step 0.0 – Python

The first step in working with Python is to ensure you have a reasonably up-to-date version of python installed.

$ python -V
Python 2.7.16

Python 2 isn’t good enough nowadays. We want  python 3.x

Due to incompatibilities between python versions 2 and 3, you tend to find that both may be installed in your environment, with the command “python” linked to one particular version. If python v3 is installed, it may be available under the name “python3”

$ python3 -V
Python 3.7.5

MacOS & Python

If you’re a MacOS user, MacOS comes with python 2 but not version 3 (And Apple have said they’ll remove python altogether from future versions of MacOS). An easy way to install python 3 is to use the Homebrew system:

$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
$ brew install python

Step 0.1 – Virtualenv

Although python has lots of features out of the box, there is a lot that isn’t in the box. A web application framework isn’t one of them. When installing add-ons to Python, there are two options: The first is to install the add-on as part of the python installation on the machine. The advantage is that the package is available for all uses/users of the machine. The downside, is if you run an application on another machine, you have to remember to install the same packages on the other machine.

So python has the option of creating a virtual environment. This allows you to compartmentalize the requirements for a piece of software to make running it elsewhere much simpler. To do this, you need another package called “virtualenv”

$ which virtualenv
/usr/bin/virtualenv

If you get no reponse to the “which” command, you’ll need to install it. It may be available via your package manager. e.g.

$ sudo apt-get install virtualenv

or, you may need to install it via pip:

$ pip3 install virtualenv

Step 1 – Creating a virtual environment

Now we’ve got the core python system installed, we’re ready for our first steps.

First off is to create a virtual environment for our Django application. At a basic level, this is just a directory with some particular files in it.

$ virtualenv -p python3 virtualenv

The “-p python3” is needed if you have both python 2 & 3 installed to ensure your virtual environment gets created to use the correct python version. “Django_Project” is just a directory name.

Once the command has completed, if you look inside the directory, you’ll see three sub directories: bin, include & lib. When we come to commit our Django project to version control, we need to make sure to exclude these.

Now we have a virtual environment, we need to switch it on. Make sure your CWD is Django_Project (Or whatever you named it) and then execute “source bin/activate”

$ source bin/activate
(virtualenv) $

Notice the change to your command prompt.

To leave the virtual environment, you can either exit the shell or run the command “deactivate”

(virtualenv) $ deactivate
$

Notice the command prompt changes back.

We are now ready to install Django into our virtual environment. So, make sure your virtual environment is active, then issue the command “pip install django”

(virtualenv) $ pip install django

This will download and install the latest Django version into our environment.

Step 2 – Creating a Django Application

We’re now ready to create a blank Django project. Again, make sure you’ve activated your virtual environment:

(virtualenv) $ django-admin startproject project .

Note the space & full-stop at the end of that command.

This will create a folder called “project” in your current directory. You should now see four directories and a file:

...
├── manage.py
└── project
    ├── __init__.py
    ├── __pycache__
    │   ├── __init__.cpython-37.pyc
    │   └── settings.cpython-37.pyc
    ├── asgi.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

This has created a Django “project” into which we can create “applications”.

(virtualenv) $ django-admin startapp main

You should now see a directory called “main” alongside all the others.

...
├── main
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── manage.py
└── project
    ├── __init__.py
    ├── __pycache__
    │   ├── __init__.cpython-37.pyc
    │   └── settings.cpython-37.pyc
    ├── asgi.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

Step 3 – Configuring our Django Application

We’re close now to being able to run our Django application for the first time. Before we run it, we have to do one final step: We have to tell the Django “project” about the “application” that we created.

Edit the file projects/settings.py and look for the block:

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

At the end of that list, we need to add in “main” (Or whatever you called your application), so it looks:

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

Now, we can finally run our Django application with the command:

(virtualenv) $ python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 17 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.

January 08, 2020 - 19:39:55
Django version 3.0.2, using settings 'project.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

If you point your browser at http://127.0.0.1:8000/ you should see the following:Django First Launch