Data Analyse Breda

Deploy Django & React on Azure #2: Change Backend Code for Deployment

In this tutorial I will show you how you can deploy a Django & React application on Microsoft Azure. Over the course of several posts we will go through the following steps:

In the previous post we covered the architecture of the deployment. In this post we will make changes to our Django code so that it is ready for deployment.

Not sure where or how to add certain things to your code? The full code with all the changes is available on GitHub.

We will need to make the following changes to our backend before we can deploy out code:

  • Install additional packages for deployment
  • Create requirements.txt file
  • Change settings for production
  • Add configuration file for deployment
  • Add git ignore file
  • Change hard coded values to environment variables

Installing packages for deployment

On Azure things will work a little bit different then on our local computer. We will use a different database, and will need to change some things to handle static files in the correct manner. To help us with that we install a few packages.

pip install psycopg2
pip install python-dotenv
pip install whitenoise

Creating requirements.txt file

We have installed several packages during our backend development. We need to install the same ones on Azure to make our app work. To let Azure know what to install we generate a requirements.txt file. Go into your backend terminal, and use the command below to generate the file.

pip freeze > requirements.txt

A requirements.txt file specifies the packages and the versions of the packages that are installed during local development.

Change settings for production

I want to be able to run my application both locally as on Azure. Therefore, I will create a different settings file to split settings for development and production. I create a new file called production.py in the same place as the settings.py file.

In the production.py file we only highlight the differences from the settings.py file. We change:

  • DEBUG = False. When we are in production we do not want to see error messages in the browser. These messages sometime contain passwords or other sensitive information.
  • ALLOWED_HOSTS = [os.environ[‘WEBSITE_HOSTNAME’]]. When Azure deploys an application it create a hidden WEBSITE_HOSTNAME variable that specifies the URL name. This line of code specifies that our url is the only allowed host for the app.
  • CSRF_TRUSTED_ORIGINS = [‘https://’+ os.environ[‘WEBSITE_HOSTNAME’]]. Specifies that our app should only trust our site Cross-Site Request Forgery takes place.
  • Add ‘whitenoise.middleware.WhiteNoiseMiddleware‘ to the Middleware below security. WhiteNoise will handle our static files. I also need ‘”corsheaders.middleware.CorsMiddleware” on the top to handle the CORS headers,
  • STATICFILES_STORAGE = ‘whitenoise.storage.CompressedManifestStaticFilesStorage’. Tells the app that whitenoise is handling our files.
  • STATIC_ROOT = os.path.join(BASE_DIR, ‘staticfiles’). Specifies where whitenoise can save the new folder called staticfiles during production.
  • The database must be changed to the settings for PostgreSQL. For this we specify a connection string that will be created by Azure later on (automatically).
import os
from .settings import *  
from .settings import BASE_DIR

# Configure the domain name using the environment variable
# that Azure automatically creates for us.
ALLOWED_HOSTS = [os.environ['WEBSITE_HOSTNAME']] if 'WEBSITE_HOSTNAME' in os.environ else []
CSRF_TRUSTED_ORIGINS = ['https://' + os.environ['WEBSITE_HOSTNAME']] if 'WEBSITE_HOSTNAME' in os.environ else []
DEBUG = False

# WhiteNoise configuration
MIDDLEWARE = [
    "corsheaders.middleware.CorsMiddleware",
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',
    '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',
]

STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')

# Configure Postgres database based on connection string of the libpq Keyword/Value form
# https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING
conn_str = os.environ['AZURE_POSTGRESQL_CONNECTIONSTRING']
conn_str_params = {pair.split('=')[0]: pair.split('=')[1] for pair in conn_str.split(' ')}
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': conn_str_params['dbname'],
        'HOST': conn_str_params['host'],
        'USER': conn_str_params['user'],
        'PASSWORD': conn_str_params['password'],
    }
}

We now know that we want to use the settings in production.py if they are included there, but how will our app know when we are in development and when in production? The answer is in the changed code of our WSGI file….

import os
from django.core.wsgi import get_wsgi_application

# Check for the WEBSITE_HOSTNAME environment variable to see if we are running in Azure Ap Service
# If so, then load the settings from production.py
settings_module = 'projectapi.production' if 'WEBSITE_HOSTNAME' in os.environ else 'projectapi.settings'
os.environ.setdefault('DJANGO_SETTINGS_MODULE', settings_module)

application = get_wsgi_application()

and the Manage.py file. In this file I have added the settings_module variable and the os.environ.setdefault.

import os
import sys


def main():
    """Run administrative tasks."""
    settings_module = "projectapi.production" if 'WEBSITE_HOSTNAME' in os.environ else 'projectapi.settings'
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', settings_module)
   
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


if __name__ == '__main__':
    main()

If WEBSITE_HOSTNAME is present we use production settings. If not, we use our local settings. WEBSITE_HOSTNAME is a standard environment variable from Azure, which helps us identifying whether we are in development or production.

Create production configuration file

We are now going to create a file which sets some variables during our deployment. Create a .production file in the top level of your Django project with the code below.

[config]
SCM_DO_BUILD_DURING_DEPLOYMENT=true

The SCM_DO_BUILD_DURING_DEPLOYMENT makes sure that our application is build when we deploy.

Add .gitignore file

In the next blogpost we are going to add our project to Github with git. However, we do not want to upload all the files, since some of them are not relevant for production. To exclude certain files from uploading we add a .gitignore file in the top level of our Django project.

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class


# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

Example of irrelevant files are the database files, our virtual environment, and the pycache. These ones we only need during local development.

Adding environment variables

The last thing I will be doing is changing my hard coded links and parameters to environment variables. This is useful since urls will be different across development and productions, and displaying sensitive information such as your secret key or passwords is not safe.

In my settings.py I change my SECRET_KEY to an environment variable. To make this work you will also need to set this variable on your local computer and in Azure.

import os
....
SECRET_KEY = os.environ['SECRET_KEY']
....

To do this on your computer, search for environment variables and add a new one. You need to restart your computer for it to work. The variable on Azure we set later.

Backend code is ready!

We have successfully changed our code and we are now ready for deployment. In the next post we will take the first step by pushing our code to GitHub with Git!

Leave a Reply