Neatly organising Django apps


For some reason, I’m deeply unsatisfied with how Django scaffolding sets up apps within a project. By default, apps are created as top level directories within the project. This is the directory structure for a new Django project my_project with a single app my_app:

.
|-- manage.py
|-- my_app
|   |-- __init__.py
|   |-- admin.py
|   |-- apps.py
|   |-- migrations
|   |   `-- __init__.py
|   |-- models.py
|   |-- tests.py
|   `-- views.py
`-- my_project
    |-- __init__.py
    |-- asgi.py
    |-- settings.py
    |-- urls.py
    `-- wsgi.py

I’m not really a fan of the apps being crammed inside the same directory as the project module. As you start adding more apps, they continue to crowd the same directory which annoys me.

My solution is to add an apps directory that contains all apps. This would result in a directory structure like so:

.
|-- apps
|   `-- my_app
|       |-- __init__.py
|       |-- admin.py
|       |-- apps.py
|       |-- migrations
|       |   `-- __init__.py
|       |-- models.py
|       |-- tests.py
|       `-- views.py
|-- manage.py
`-- my_project
    |-- __init__.py
    |-- asgi.py
    |-- settings.py
    |-- urls.py
    `-- wsgi.py

Now, all future apps go inside the apps directory and don’t clutter up the project base directory.

With this structure, you lose the ability to import apps using just their names because apps isn’t part of PYTHONPATH. To fix this, I add the following lines to settings.py to modify the path whenever project settings are loaded:

import sys
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent
APPS_DIR = BASE_DIR / "apps"
sys.path.insert(0, str(APPS_DIR))

Now, you can import apps using their name without any problems (so they are technically reuseable code chunks again). The final issue is that Django’s default test runner won’t discover tests from modules within the apps directory now. There are two solutions here.

My preferred approach is to ensure all tests are confined to the app modules themselves so you can point the apps directory as the start directory for the unittest runner which would execute the tests fine.

$ ./manage.py test . apps

I know a lot of this is just subjective elegance, but as I mentioned this is my preferred way to setup projects with apps that I find scales great as more apps and groups of apps are added to the project over time.