Manage Static Files for Django Website on A2 Hosting with cPanel

After you deployed your first Django website on a2hosting.com (as I showed in the tutorial How to Deploy a Django Website to A2 Hosting) you will soon notice, that your website looks weird because no static files like CSS, JavaScript or images are shown. This is due to the reason that Django does not serve static files in a production environment.

According to the Django how to the method of serving static during development (e.g. when you start your Django website local with the runserver command) is grossly inefficient, insecure and unstable for production environments.

In this tutorial I will show you how you have to configure your Django website in order to serve static files on A2 Hosting.

Current website setup

In order to show you how to configure your Django website correctly first lets look at the current setup.

file structure

stocktimemachine.com/                 
 ├── app_name/
 |   ├── migrations/
 |   ├── static/
 |   |   ├── app_name/
 |   |   |   ├── style.css
 |   ├── templates/
 |   |   ├── app_name/
 |   |   |   ├── index.html
 |   ├── admin.py
 |   ├── apps.py
 |   ├── models.py
 |   ├── tests.py
 |   ├── admin.py
 |   ├── urls.py
 |   └── views.py            
 ├── project_name/ 
 |   ├── settings.py
 |   ├── urls.py
 |   └── wsgi.py 
 ├── public/ 
 ├── tmp/ 
 ├── passenger_wsgy.py 
 ├── db.sqllite3       
 └── manage.py

settings.py

# ...
DEBUG = False
# ...

urls.py

from django.urls import path, re_path, include
from . import views

urlpatterns = [
    re_path(r'^$', views.index, name='index'),
]

views.py

from django.shortcuts import render

def index(request):   
    context = {}
    context['string'] = 'Test'    
    return render(request, 'app_test/index.html', context)

index.html

{% load static %}

<!DOCTYPE html>
<html lang="en">
  <head>
   <link rel="stylesheet" type="text/css" href="{% static 'app_test/style.css' %}" />
  </head>
  <body>
   {{string}}
  </body>
</html>

style.css

body {
    font-family: Arial,sans-serif;
    font-size: 90px;
}

When you go tho the site now the only thing you will see is the word Test in the default font. This is because Django does not serve the static files in production mode. To serve static files we will use the WhiteNoise middleware.

Serving Static files with WhiteNoise

WhiteNoise is a middleware that allows web applications to serve their own static files. The following steps will walk you through how to setup your Django app wit WhiteNoise.

Configure staticfiles correctly

At first we need to tell Django where to find the static files. Therefore we need to define some variables in the settings.py file as shown below.

settings.py

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'public') #after collect static is run all the static files will be put in this folder

STATICFILES_DIRS = ( #is where Django will additionally look for static files

)

The STATIC_ROOT variable defines where absolute path where the command collectstatic (is explained further below) will copy all the static files for deployment. We will set this to the public folder which was created when we started the Python application on A2 Hosting. The STATIC_URL sets the URL which is used to reference to static files. The STATICFILES_DIR defines additional locations where Django can look for static files. We will leave this empty since we only have one location for the static files.

Install WhiteNoise

After that we need to install the WhiteNoise middleware in your Python environment. To do this go to the python application in your cPanel and install WhiteNoise. A detailed explanation on how to do this can be found here. Your modules should look like this now.

WhiteNoise installation in cPanel of a2hosting.com
WhiteNoise installation in cPanel of a2hosting.com

Next add the the WhiteNoise middleware to the MIDDLEWARE classes in your settings.py file below the SecurityMiddleware but above all others. This is also explained in the WhiteNoise documentation.

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',
    # ...
]

Update passenger_wsgi file

In order to enable WhiteNoise you need to update your passenger_wsgi.py file. The code below wraps the existing WSGI application into a WhiteNoise instance. In line 9 we first import WhiteNoise. Line 29 configures the WhiteNoise application and tells it where to find the static files. Edit lines 5 and 29 according to your project configuration.

import os
import sys
# Set up paths and environment variables
sys.path.append(os.getcwd())
os.environ['DJANGO_SETTINGS_MODULE'] = 'project_name.settings'

import django.core.handlers.wsgi
from django.core.wsgi import get_wsgi_application
from whitenoise import WhiteNoise

SCRIPT_NAME = os.getcwd()
SCRIPT_NAME = '' #solution for damn link problem

class PassengerPathInfoFix(object):
    def __init__(self, app):
        self.app = app

    def __call__(self, environ, start_response):
        from urllib.parse import unquote
        environ['SCRIPT_NAME'] = SCRIPT_NAME

        request_uri = unquote(environ['REQUEST_URI'])
        script_name = unquote(environ.get('SCRIPT_NAME', ''))
        offset = request_uri.startswith(script_name) and len(environ['SCRIPT_NAME']) or 0
        environ['PATH_INFO'] = request_uri[offset:].split('?', 1)[0]
        return self.app(environ, start_response)

application = get_wsgi_application()
application = PassengerPathInfoFix(application)
application = WhiteNoise(application, root='/home/stocktim/public_html/stocktimemachine.com/public')

Collectstatic after updating static files

Above you configured WhiteNoise to look for static files in the public folder. However this folder is currently empty. The last step is to run the collectstatic command. This will tell Django to copy all static files into the STATIC_ROOT folder which you configured in your settings.py file.

To run the command navigate to your Python application in the A2 Hosting cPanel. Below the modules you will find a column named execute command.

Execute collectstatic command in cPanel
Execute collectstatic command in cPanel

Past the collect static command (you probably need to adjust the path) into the input box and click Run. This will copy all the static files into the public folder.

python /home/stocktim/public_html/xxx.stocktimemachine.com/manage.py collectstatic --noinput

That’s it. You will now have all your static files in your public folder. Restart the Python application and you should now have access to the static files.

Every time you upload some new static files you need to run the collectstatic command. I hope you like this tutorial. For questions use the comment section below. You can find more information on serving static files in a production environment on the official Django documentation.