Data Analyse Breda

Deploying Django & React on Microsoft Azure #2: Changing our Django & React Code

In this brand new tutorial series we are deploying a full stack web application using Python Django in the Backend and React JS in the Frontend. This series is split in 6 parts:

  • #1: Architecture and application overview
  • #2 TODAY: Changing our Django & React code for Deployment
  • #3: Pushing our code to GitHub using Git
  • #4: Deploying our Django Backend on Microsoft Azure
  • #5: Deploying our React JS Frontend on Microsoft Azure
  • #6: Costs of Deployment and Troubleshouting

In todays post we are changing our local Django & React code so it is ready for Deployment. The full project code is available on GitHub. You can find a link to the Youtube video on the bottom of the post.

What do we need to do?

To make our code ready for deployment we need to make some changes to our Django backend code. We need to:

  • Install some additional packages
  • Creating a new settings file for deployment
  • Tell our app when to use what settings file
  • Create a requirements.txt file
  • Adding a .config and .gitignore file

Installing additional packages

During deployment we will use another database (PostgreSQL versus SQLite) and there will be another way of handling static files (Whitenoise). To make that work, we need some additional packages.

pip install psycopg2 whitenoise

Creating a new settings file

Our current settings file has our local settings, but we need some different ones on Microsoft Azure. Therefore, we are going to define the variables that are different in another file.

  • Create a new file in the same folder as your settings.py file called deployment.py. In there import everything from the settings.py file, and overwrite the settings that need to be changed.
import os 
from .settings import *
from .settings import BASE_DIR

ALLOWED_HOSTS = [os.environ['WEBSITE_HOSTNAME']]
CSRF_TRUSTED_ORIGINS = ['https://'+os.environ['WEBSITE_HOSTNAME']]
DEBUG = False
SECRET_KEY = os.environ['MY_SECRET_KEY']

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',
]

#CORS_ALLOWED_ORIGINS = []


STORAGES = {
    "default": {
        "BACKEND": "django.core.files.storage.FileSystemStorage",
    },
    "staticfiles": {
        "BACKEND": "whitenoise.storage.CompressedStaticFilesStorage",
    },
}

CONNECTION = os.environ['AZURE_POSTGRESQL_CONNECTIONSTRING']
CONNECTION_STR = {pair.split('=')[0]:pair.split('=')[1] for pair in CONNECTION.split(' ')}

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": CONNECTION_STR['dbname'],
        "HOST": CONNECTION_STR['host'],
        "USER": CONNECTION_STR['user'],
        "PASSWORD": CONNECTION_STR['password'],
    }
}

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'mail_admins': {
            'level': 'ERROR',
            'class': 'django.utils.log.AdminEmailHandler',
        },
    },
    'loggers': {
        'django': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        },
    },
}



ADMINS = [("Nick", "YOURMAIL.com")]

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = os.environ.get('EMAIL_USER')
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_PASSWORD')
DEFAULT_FROM_EMAIL = 'default from email'



STATIC_ROOT = BASE_DIR/'staticfiles'

But what do these things mean?

  • ALLOWED_HOSTS = Needs to be equal to the backend domain on Azure. We use an environment variable that Azure sets as the default when deploying.
  • CSRF_TRUSTED_ORIGINS = The same as ALLOWED_HOSTS but with the extension of https. Specifies the domains that can make unsafe requests to the database (such as post requests).
  • DEBUG = False, because we do not want to see error logs in production. These display sensitive information.
  • SECRET_KEY = Changed to an environment variable to make sure it remains a secret. We set this variable later on Azure.
  • MIDDLEWARE = Copied from settings.py file, and added the ‘whitenoise.middleware.WhiteNoiseMiddleware’, underneath the security middleware. This is for static file handling using WhiteNoise.
  • STORAGES = Added the WhiteNoise link in the staticfiles settings. If you use a Django version earlier than 4.2 you need the variable STATICFILES_STORAGE. You can find all information in the WhiteNoise documentation.
  • DATABASES = Configuration of PostgreSQL database. It uses the CONNECTION string environment variable which will be provided by Azure. We slightly transform that in the CONNECTION_STR variable. This is also included in the Microsoft example. We pass the CONNECTION_STR variable to the relevant attributes of the database to get the credentials.
  • STATIC_ROOT = Location where we want to store static files in production. This needs to be in the bottom of the file.
  • LOGGING + EMAIL: For the logging. Not required, but handy for troubleshooting. You need to set up sending emails with Django to get this to work. And specify your email in the Admins list.

Tel our app what settings to use when

We now added a new settings file, but we still need to tell Django what settings file to use when…We want to use our settings.py locally, and deployment.py on Azure.

To realize that we will change the wsgi.py and manage.py file. The wsgi file is specified below..

import os

from django.core.wsgi import get_wsgi_application
settings_module = 'crud.deployment' if 'WEBSITE_HOSTNAME' in os.environ else 'crud.settings'
os.environ.setdefault('DJANGO_SETTINGS_MODULE', settings_module)

application = get_wsgi_application()

We add the variable settings_module. This states if the WEBSITE_HOSTNAME is in the environment variables, then we use the deployment.py settings. Otherwise the local settings. Next we pass that variable in the os.environ.setdefault variable.

In the manage.py file we do exactly the same..

#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys


def main():
    """Run administrative tasks."""
    settings_module = 'crud.deployment' if 'WEBSITE_HOSTNAME' in os.environ else 'crud.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()

Create requirements.txt file

The requirements.txt file is a list of all packages used in development including the version. In your project terminal, use the following command to generate this file.

python -m pip freeze > requirements.txt

This will generate the requirements.txt file in the highest level of your project.

Add a config and gitignore file

We need to add two additional files with some configuration settings. The first file is a .production file, which is going to set an environment variable on Azure when we deploy our code.

The environment variable SCM_DO_BUILD_DURING_DEPLOYMENT is crucial, since it tells Azure to build our deployment.

[config]
SCM_DO_BUILD_DURING_DEPLOYMENT=true

We also add a .gitignore file. This excludes some of the files from out local project when we push our code to GitHub using git. This file comes straight from the Microsoft example.

# 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/

Also available on YouTube!

Leave a Reply