PYTHON / DJANGO
Apps, settings, and project layout
Organize a Django codebase: register apps through AppConfig and INSTALLED_APPS, control app labels, and lay out settings so each environment loads one module.
What you will learn
- Register an app by its dotted path in INSTALLED_APPS; a folder alone does nothing
- Set label/verbose_name on an AppConfig to control app identity and table prefixes
- Resolve settings via DJANGO_SETTINGS_MODULE, or settings.configure() in plain scripts
- Inspect loaded apps at runtime with django.apps.apps.get_app_config and get_model
Understanding Apps, settings, and project layout
The directory that startapp creates is inert Python until its dotted path appears in INSTALLED_APPS. At startup Django imports each entry, builds one AppConfig object per app, and stores it in a registry keyed by the app's label. That label defaults to the last component of the package name, and it is the identity used everywhere afterwards: Model._meta.app_label, the default table prefix, the argument to makemigrations, and the key for apps.get_app_config(). If two installed packages end in the same word, their labels collide and Django refuses to start until you set label explicitly on one AppConfig.
Settings are just a Python module whose import path lives in the DJANGO_SETTINGS_MODULE environment variable. Django imports that module exactly once, copies its UPPERCASE module-level names into the lazy django.conf.settings object, and fills every name you did not define from django.conf.global_settings. That is why application code must always do `from django.conf import settings` rather than importing the project module: the lazy object is what environment-specific settings modules, --settings, and override_settings in tests actually swap out. In a script with no manage.py, settings.configure() followed by django.setup() plays the same role.
Because settings are only a module path, layout is a free choice. A common one is to turn settings.py into a settings/ package containing base.py, dev.py, and prod.py, where each environment module starts with `from .base import *` and overrides a handful of names; DJANGO_SETTINGS_MODULE then picks one. The same freedom applies to apps: nesting them under an apps/ package is fine, but then the app's name becomes apps.billing while its label stays billing, and BASE_DIR in settings must still point at the directory you build paths from.
import django
from django.conf import settings
settings.configure(
INSTALLED_APPS=[
"django.contrib.contenttypes",
"django.contrib.auth",
"django.contrib.sessions",
],
)
django.setup()
from django.apps import apps
for config in apps.get_app_configs():
print(f"{config.label:<14}{config.name:<30}{config.verbose_name}")
print()
print("auth models:", [m.__name__ for m in apps.get_app_config("auth").get_models()])
print("User label:", apps.get_model("auth.User")._meta.label)
print("sessions module:", apps.get_app_config("sessions").models_module.__name__)A Django project is not a folder shape: it is one settings module plus the app labels Django loads from INSTALLED_APPS into its app registry.
Worked examples
Settings are lazy and uppercase-only
Shows when django.conf.settings becomes usable, what it rejects, and where unspecified values come from.
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
print("configured?", settings.configured)
try:
settings.DEBUG
except ImproperlyConfigured:
print("reading DEBUG too early raised ImproperlyConfigured")
try:
settings.configure(debug=True)
except TypeError:
print("lowercase names are rejected by configure()")
settings.configure(DEBUG=True, PAGE_SIZE=25)
print("configured?", settings.configured)
print("DEBUG:", settings.DEBUG)
print("PAGE_SIZE:", settings.PAGE_SIZE)
print("INSTALLED_APPS default:", settings.INSTALLED_APPS)Example explained
Line 1settings.configured is False until DJANGO_SETTINGS_MODULE is read or configure() runs, so module-level settings access in your own code can explode.
Line 2Django never guesses a settings module: touching settings.DEBUG with nothing configured raises ImproperlyConfigured instead of using a default.
Line 3configure() refuses a lowercase name, mirroring the rule that only UPPERCASE module-level names in settings.py count as settings.
Line 4PAGE_SIZE works because any uppercase name you invent becomes a setting, while INSTALLED_APPS falls back to global_settings rather than being missing.
A custom AppConfig label changes the model's identity
Builds a throwaway app package to show that label, not the package name, drives app_label and the default table name.
import os, sys, tempfile
import django
from django.conf import settings
root = tempfile.mkdtemp()
pkg = os.path.join(root, "store")
os.makedirs(pkg)
open(os.path.join(pkg, "__init__.py"), "w").close()
with open(os.path.join(pkg, "apps.py"), "w") as f:
f.write(
"from django.apps import AppConfig\n"
"class StoreConfig(AppConfig):\n"
" name = 'store'\n"
" label = 'catalog'\n"
" verbose_name = 'Product catalogue'\n"
" default_auto_field = 'django.db.models.SmallAutoField'\n"
)
with open(os.path.join(pkg, "models.py"), "w") as f:
f.write(
"from django.db import models\n"
"class Product(models.Model):\n"
" name = models.CharField(max_length=50)\n"
)
sys.path.insert(0, root)
settings.configure(INSTALLED_APPS=["store.apps.StoreConfig"])
django.setup()
from django.apps import apps
cfg = apps.get_app_config("catalog")
print("label:", cfg.label)
print("name:", cfg.name)
print("verbose_name:", cfg.verbose_name)
Product = apps.get_model("catalog.Product")
print("model label:", Product._meta.label)
print("pk field:", Product._meta.pk.__class__.__name__)
print("table:", Product._meta.db_table)Example explained
Line 1INSTALLED_APPS names the AppConfig class directly, so Django uses StoreConfig instead of building a default config for the store package.
Line 2label = 'catalog' replaces the default label derived from the package name, and every registry lookup now needs 'catalog', not 'store'.
Line 3Product._meta.label is catalog.Product because a model's identity is app label plus class name, independent of where the module lives.
Line 4default_auto_field on the AppConfig beats settings.DEFAULT_AUTO_FIELD, and db_table is catalog_product — which is why relabelling a migrated app breaks its tables.
Important notes
manage.py uses os.environ.setdefault, so an exported DJANGO_SETTINGS_MODULE wins over the line in manage.py — convenient, but a stale export is the usual reason a dev machine suddenly loads prod settings.
The settings module body runs once per process, so anything read from os.environ there is frozen for the life of the server; changing the environment later has no effect until restart.
Common mistakes
Creating or copying an app folder but never adding it to INSTALLED_APPS: makemigrations answers 'No changes detected', and templates or static files inside that app are invisible to the app-directories loaders.
Writing `from myproject import settings` or `from myproject.settings import DEBUG` in app code: the value is bound to one specific module at import time, so per-environment settings, --settings, and override_settings in tests silently have no effect.
Installing two packages whose last path component matches, such as `blog` and `vendor.blog`: startup fails with "Application labels aren't unique, duplicates: blog", and the fix is `label = 'vendor_blog'` on one AppConfig, not renaming the folder.
Try it yourself
Change, predict, then run
In a browser editor, call settings.configure() with INSTALLED_APPS listing django.contrib.contenttypes, django.contrib.auth and django.contrib.sessions plus a custom PAGE_SIZE=10, run django.setup(), then print every app label and `apps.get_model('auth.Group')._meta.db_table`. Reorder INSTALLED_APPS and confirm the printed label order follows it.
Open the Python workspaceCheck your understanding
You copy a colleague's `payments` app folder into your project next to manage.py, then run `python manage.py makemigrations`. Django prints 'No changes detected'. What is the most likely cause?
- makemigrations only inspects apps that already contain a migrations/ directory
- The app's models.py must be imported by the project's urls.py before Django can see it
- The app's dotted path is missing from INSTALLED_APPS, so no AppConfig was built and its models were never registered
- Copied apps need startapp rerun so Django records the folder in the django_migrations table
Show answer
makemigrations walks the app registry, which is built solely from INSTALLED_APPS; an unlisted package is never imported, so its models do not exist as far as Django is concerned. Option 1 is tempting but wrong: makemigrations happily creates migrations/0001_initial.py for a registered app that has no migrations directory yet, and django_migrations only records applied migrations, never app folders.