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:
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
Whitenoise handles static files for us. Psycopg2 the database.
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.
Azure automatically sets the WEBSITE_HOSTNAME variable upon deployment, so if that is present we know we are in deployment.
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
Make sure that you are in the top backend folder, and have your virtual environment activated.
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/
Some examples of files that we do not need are sqlite3 or pycache files.