Giving Django Project a “Two Scoops” Configuration

Years back I bought a physical copy of Two Scoops of Django 1.11 after seeing it highly recommended in a book called Hello Web App by Tracy Osborn. Little did I know it was way over my head at the time.

With the release of Two Scoops of Django 3.X, and with a lot of recent practice, the book is now only slightly over my head, so I’ve been trying to implement more of the practices recommended in the book.

I’ve also been continuing to use Docker and Docker Compose after learning the basics in Django for Professionals by Will Vincent.

Fixing Problems?

Early on in Two Scoops, the authors discuss how the standard project layout from django-admin startproject leaves a lot to be desired.

I definitely have NOT experienced that pain because I don’t have the skills to make any complex projects. I do, however, see the value in getting comfortable with a new layout. It teaches you how some of the module inheritance in Django works. Python import statements are one of the most challenging things for me, personally, so this is a good way to train your coding skills.

Let’s talk about two different project layouts.

Standard layout

Here’s the base layout you get from running django-admin startproject <PROJECT_NAME> <DIRECTORY_LOCATION>:

django-admin startproject basic_startproject .
tree.
.
├── basic_startproject/
   ├── asgi.py
   ├── __init__.py
   ├── settings.py
   ├── urls.py
   └── wsgi.py
└── manage.py

Two Scoops layout

Here’s the directory layout recommended in Two Scoops:

<repository_root>
├── <configuration_root>
└── <django_project_root>

Here’s what’s stored in the <configuration_root> directory

<configuration_root>
├── <settings>
├── asgi.py
├── urls.py
└── wsgi.py

The <settings> folder holds onto your multiple settings files. This is another notable difference. See the Two Scoops book for more info on how to set that up; I found it helpful. Their guidance left me with the following project structure:

.
├── config/
   ├── asgi.py
   ├── settings/
      ├── base.py
      ├── __init__.py
      ├── local.py
      └── production.py
   ├── urls.py
   └── wsgi.py
├── <django_project_root>/
   └── <project_name>/
       └── __init__.py
├── docker-compose.yml
├── Dockerfile
├── docs/
├── manage.py
├── Pipfile
└── Pipfile.lock

Note that I’m using pipenv, Docker, and docker-compose as well.

Moving files breaks things

Now it’s easy enough to move files around, but if you move things around, import statements break.

Where to put docker-compose.yml

I decided to place the Dockerfile and docker-compose.yml alongside the manage.py file. This meant I did not need to change these files from a familiar setup:

# Dockerfile
# pull base image
FROM python:3.8

# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# set work directory
WORKDIR /code

# install dependenceies
COPY Pipfile Pipfile.lock /code/
RUN pip install pipenv && pipenv install --system

# copy project
COPY . /code/
# docker-compose.yml
version: "3.7"

services:
  web:
    build: .
    command: python /code/manage.py runserver 0.0.0.0:8000
    env_file:
      - .env
    volumes:
      - .:/code
    ports:
      - 8000:8000
    depends_on:
      - db
  db:
    image: postgres:12
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - "POSTGRES_HOST_AUTH_METHOD=trust"

volumes:
  postgres_data:

Finding the project app

The first major problem was that I ran into a ModuleNotFoundError when starting the development server. The module with the name of my Django project could not be found.

I found a nice Stack Overflow question addressing this. manage.py needs to know where to look for the apps. Since we moved things around, we’ll have to tell the system where to find that Django project folder.

In my base settings file, base.py:

# config/settings/base.py
import sys
from pathlib import Path

# Project root directory
BASE_DIR = Path(__file__).resolve().parent.parent.parent
sys.path.append(str(BASE_DIR / '<django_project_root>'))  # replace

Finding ROOT_URLCONF

Running the development server again left me with this error: ModuleNotFoundError: No module named '<project_name>.urls'

Now is a good time to compare our directories again. With the standard django-admin startproject command, our settings.py file is in this relative path: <django_project_root>/<project_name>/settings.py. The standard ROOT_URLCONF value is: '<project_name>.urls'. This works because the project urls.py file is in the same directory as the settings.py file.

With our new structure, we’ve separated these files and put them in a new folder named config/, so we need to update this setting.

.
├── config/
   ├── settings/
      ├── base.py
      ├── __init__.py
      ├── local.py
      └── production.py
   └── urls.py
└── <django_project_root>/
# config/settings/base.py
ROOT_URLCONF = 'config.urls'

Finding WSGI_APPLICATION

Same goes for finding the WSGI application.

# config/settings/base.py
WSGI_APPLICATION = 'config.wsgi.application'

Success!

Proof that our Django project is running locally

It seems simple, but it took me an entire morning to figure this out! Hopefully it speeds up your progress.

Be sure to check out Two Scoops of Django 3.X for more best practices in Django development.

Get Notified of New Posts

Sign up for the newsletter and I'll send you an email when there's a new post.