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.
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.
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
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'
Same goes for finding the WSGI application.
# config/settings/base.py WSGI_APPLICATION = 'config.wsgi.application'
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.