Overview
This guide is not about how to use the Django framework. Rather, it describes the Princeton CS environment and how one might set up a Django site in "project space" at https://mywebsite.cs.princeton.edu
that has reasonable portability to other hosting services when/if the need should arise.
These instructions roughly parallel the initial steps from these Django website documents: Quick install guide, Writing your first Django app, part 1, and, because this is a deployment, Deployment checklist.
Initial Setup
Request Resources and Set up the Python/WSGI Environment
Follow the steps in the Requesting Resources / Initial Set-up section of the CS Guide document Python-based Websites Using WSGI. Now, when visiting https://mywebsite.cs.princeton.edu
, you should see a "Hello, World" page. Since we will be editing the site, put the site into maintenance mode:
- Run:
touch /n/fs/myproject/mywebsitefiles/www-approot/tmp/maint.txt
- Run:
touch /n/fs/myproject/mywebsitefiles/www-approot/tmp/restart.txt
- Reload the site a few times until it shows the maintenance page. This can take up to 10 seconds.
The remainder of this document uses the same naming conventions and project layout as Python-based Websites Using WSGI.
Activate your Python Virtual Environment
The shell commands in this document assume that you are in your Python virtual environment:
source /n/fs/myproject/mywebsitefiles/venv/bin/activate
Install Django and Create your Django Project
Install Django, create mysite
project, and make directories for secrets and logs:
# in your venv:
pip install Django
cd /n/fs/myproject/mywebsitefiles
django-admin startproject mysite
mkdir -p /n/fs/myproject/mywebsitefiles/log
chmod go= /n/fs/myproject/mywebsitefiles/log
mkdir -p /n/fs/myproject/mywebsitefiles/secrets
chmod go= /n/fs/myproject/mywebsitefiles/secrets
touch /n/fs/myproject/mywebsitefiles/secrets/credentials.env
chmod go= /n/fs/myproject/mywebsitefiles/secrets/credentials.env
Your /n/fs/myproject/mywebsitefiles
should look roughly like this:
/n/fs/myproject
+-- python-3.12.0/
| +-- ...
+-- mywebsitefiles/
+-- log/
+-- mysite/
| +-- manage.py
| +-- mysite/
| +-- __init__.py
| +-- asgi.py
| +-- settings.py
| +-- urls.py
| +-- wsgi.py
+-- secrets/
| +-- credentials.env
+-- venv/
| +-- ...
+-- www-docroot/
+-- www-approot/
+-- config.toml
+-- passenger_wsgi.py
+-- tmp/
+-- maint.txt
+-- restart.txt
"Productionize"
The initial settings.py
file is meant for local development. We need to make some changes to it so that the site suitable for production. In addition, it is good practice to keep hard-wired values out of the settings.py
file.
Parameterize ALLOWED_HOSTS
In your settings.py
file, Just above the from pathlib import Path
line, add:
import os
Replace the ALLOWED_HOSTS
line with:
ALLOWED_HOSTS = [os.environ['MYSITEURL']]
We will add MYSITEURL
to the environment when we edit the config.toml
file, below.
Generate and Protect the SECRET_KEY
Install the python-dotenv
package and create a credentials.env
file defining DJANGO_SECRET_KEY
:
# in your venv:
pip install python-dotenv
cd /n/fs/myproject/mywebsitefiles/secrets
python -c 'from django.core.management import utils; print(f"DJANGO_SECRET_KEY={utils.get_random_secret_key()}")' >>credentials.env
In your settings.py
file, Just below the from pathlib import Path
line, add:
from dotenv import dotenv_values
In your settings.py
file, Replace the SECRET_KEY
line with:
mysecrets = dotenv_values(os.environ['MYSITESECRETS'])
SECRET_KEY = mysecrets['DJANGO_SECRET_KEY']
We will add MYSITESECRETS
to the environment when we edit the config.toml
file, below.
Configure Logging
In your settings.py
file, append this to the end of the file:
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'default': {
'format': '%(asctime)s %(levelname)s %(name)s '
'%(threadName)s : %(message)s',
},
},
'handlers': {
'file': {
'level': 'DEBUG',
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.environ['LOG_FILENAME'],
'maxBytes': 40000,
'backupCount': 2,
},
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'DEBUG',
'propagate': True,
},
},
}
We will add LOG_FILENAME
to the environment when we edit the config.toml
file, below.
Edit the uwsgi-shim config.toml file
The startproject
also creates mysite/mysite/wsgi.py
with an application
callable. Update the [wsgi]
section of your /n/fs/myproject/mywebsitefiles/www-approot/config.toml
to chdir to the top-level Django directory (a requirement of Django) and use the app
setting to point to the application
callable relative to the Django directory:
chdir = "/n/fs/myproject/mywebsitefiles/mysite"
app = "mysite.wsgi.application"
In your config.toml
file, add these lines to the [environment]
section:
MYSITEURL = "mywebsite.cs.princeton.edu"
LOG_FILENAME = "/n/fs/myproject/mywebsitefiles/log/logfile"
DJANGO_SETTINGS_MODULE = "mysite.settings"
In your config.toml
file, add this line to the [secret_files]
section:
MYSITESECRETS = "/n/fs/myproject/mywebsitefiles/secrets/credentials.env"
Test the Site and disable DEBUG
As initially created, the settings.py
file enables DEBUG mode. Before we disable it, let's test the site:
Take your site out of Maintenance Mode and restart the server:
- Run:
rm /n/fs/myproject/mywebsitefiles/www-approot/tmp/maint.txt
- Run:
touch /n/fs/myproject/mywebsitefiles/www-approot/tmp/restart.txt
- Reload the site a few times until it shows the Django page. This can take up to 10 seconds.
You should see the Django page, "The install worked successfully! Congratulations!"
Warning: you are running your site in DEBUG mode on the open Internet. Let's take care of that now.
- Run:
touch /n/fs/myproject/mywebsitefiles/www-approot/tmp/maint.txt
- Run:
touch /n/fs/myproject/mywebsitefiles/www-approot/tmp/restart.txt
- Reload the site a few times until it shows the Maintenance page. This can take up to 10 seconds.
- Edit
settings.py
and change theDEBUG
setting to:DEBUG = False
.
At this point, you could re-enable your site. However, since you have not created any Django views, re-loading the site would only show the generic-looking "Not Found" page.
Start coding!
While the above steps set up a bare-bones Django application, this was directly in a production environment. Typically, one would develop code in a local environment (e.g., a laptop) and check it into a repository on a service like GitLab or GitHub. When ready for deployment, the repository would be checked out to a directory in /n/fs/myproject/mywebsitefiles
with suitable adjustments to the [wsgi]
section of your config.toml
file.
Database
Assuming your Django project needs a database (most will), the databases officially supported by Django that are also available in the CS Infrastructure are SQLite (lightweight, disk-based, suitable for development) and MariaDB (suitable for production).
For production websites strictly using the CS Infrastructure, we recommend MariaDB over SQLite.1 The remainder of this document assumes you are using MariaDB and specific configuration suggestions are given in Database Configuration, below.
Here are more references on using MariaDB / MySQL with Django:
- https://blog.ionelmc.ro/2014/12/28/terrible-choices-mysql/
- https://docs.djangoproject.com/en/5.0/ref/databases/#collation-settings
- https://stackoverflow.com/questions/46441487/django-mysql-strict-mode-with-database-url-in-settings
- https://github.com/jazzband/dj-database-url/issues/71
Requesting a MariaDB Database
To request a MariaDB database, use the "Request Forms" block on the CS Guide and click on "Project Database". The key fields are:
Use: Collaborative
Database Name: mydatabasename
Brief description of use: Django database for mywebsite.cs.princeton.edu
Change DB Password and Character Set
When the database is set up, you will get an email from CS Staff with instructions on changing the database password. While you are changing the password, also change the character set and collation as follows:
mysql> ALTER DATABASE `mydatabasename` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
Managing Database Credentials
For this example, we define database-related variables in your secrets/credentials.env
file:
DB_NAME=mydatabasename
DB_USER=mynetid
DB_PASS=mychangeddatabasepassword
DB_HOST=publicdb.cs.princeton.edu
DB_PORT=3306
Database Configuration
With the database variables in our secrets/credentials.env
file, we need to extract them for the DATABASE
dictionary in the Django settings.py
file.
In your settings.py
file, replace the DATABASES
definition with:
= {
DATABASES 'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': mysecrets['DB_NAME'],
'USER': mysecrets['DB_USER'],
'PASSWORD': mysecrets['DB_PASS'],
'HOST': mysecrets['DB_HOST'],
'PORT': mysecrets['DB_PORT'],
'CONN_MAX_AGE': 600,
'CONN_HEALTH_CHECKS': True,
'OPTIONS': {
# escalate several InnoDB-specific warnings to errors
'init_command': 'SET innodb_strict_mode=1',
# Tell MySQLdb to connect with 'utf8mb4' character set
'charset': 'utf8mb4',
},# Tell Django to build the test database with the 'utf8mb4' character set
'TEST': {
'CHARSET': 'utf8mb4',
'COLLATION': 'utf8mb4_unicode_ci',
},
}, }
Install the Database Driver
# in your venv:
pip install mysqlclient
Static Files
If you have already visited the /admin
pages, you'll notice that the layout looks lousy. This is because, in production, one must explicitly manage static files. Because the CS webserver will first search for files from the DocRoot and only when they are not found connect to your Django application, one can put static files in the www-docroot
directory. This is described in the Django documentation at Serving the site and your static files from the same server.
A simpler approach that does not require you to remember to open the world-read permissions on the files in the DocRoot is to use WhiteNoise.
Install with:
# in your venv:
pip install whitenoise
From Using WhiteNoise with Django:
Add this to the bottom of your settings.py
file
STATIC_ROOT = BASE_DIR / 'staticfiles'
And edit your settings.py
file and add WhiteNoise to the MIDDLEWARE list2, above all other middleware apart from Django’s SecurityMiddleware:
MIDDLEWARE = [
# ...
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
# ...
]
Use collectstatic
to copy static files from various Django apps to the STATIC_ROOT
directory:
# in your venv:
cd /n/fs/myproject/mywebsitefiles/mysite
export MYSITESECRETS="/n/fs/myproject/mywebsitefiles/secrets/credentials.env"
export MYSITEURL="mywebsite.cs.princeton.edu"
export LOG_FILENAME="/n/fs/myproject/mywebsitefiles/log/logfile"
python manage.py collectstatic
Take your site out of Maintenance Mode and restart the server:
- Run:
rm /n/fs/myproject/mywebsitefiles/www-approot/tmp/maint.txt
- Run:
touch /n/fs/myproject/mywebsitefiles/www-approot/tmp/restart.txt
- Visit the admin page at:
https://mywebsite.cs.princeton.edu/admin
If you get the maintenance page, reload the site a few times until it shows the Django admin login.
Next Steps
Obviously, your next steps depend on the requirements of your site. Typically, you will want to run your first database migration and then create an admin user. These are described below.
Your First Migration
If you are starting a brand-new project, consider the Django recommendation Using a custom user model when starting a project before running your first migration. In the simplest case, this entails creating a Django app called user
:
# in your venv:
cd /n/fs/myproject/mywebsitefiles/mysite
python manage.py startapp user
Edit user/models.py
to have the following contents:
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
pass
Edit user/admin.py
to have the following contents:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User
admin.site.register(User, UserAdmin)
Now make two edits to your settings.py
file. First, in the INSTALLED_APPS
list, add 'user.app.UserConfig',
after the django.*
entries:
INSTALLED_APPS = [
...
'django.contrib.staticfiles',
'user.apps.UserConfig',
]
Second, at the end of the file add this line:
AUTH_USER_MODEL = 'user.User'
Now, perform the migration:
# in your venv:
cd /n/fs/myproject/mywebsitefiles/mysite
export MYSITESECRETS="/n/fs/myproject/mywebsitefiles/secrets/credentials.env"
export MYSITEURL="mywebsite.cs.princeton.edu"
export LOG_FILENAME="/n/fs/myproject/mywebsitefiles/log/logfile"
python manage.py makemigrations
python manage.py migrate
Creating an admin user
With the first migration complete, you can follow the steps on the Django page: Creating an admin user.
...and continue with that tutorial to learn more about Django.
While way beyond the scope of this document, it is also possible to use a database from an outside service (e.g., PostgreSQL in AWS RDS).↩︎
For local development, also see: Using WhiteNoise in development.↩︎