Filter concepts by levelShowing all levels.

Django · Project Structure

Project Structure

What every generated file and directory is for, and how they refer to each other

  • manage.py = pre-wired django-adminDJANGO_SETTINGS_MODULE already set
  • One models.py per appthe single source makemigrations reads
  • Root urls.py stays thinmostly include() per app
  • migrations/ is a change lognever delete an applied one

1.manage.py

manage.py wraps django-admin, generated once by startproject. It auto-sets DJANGO_SETTINGS_MODULE, so `python manage.py runserver` needs no flags but bare `django-admin runserver` does.

Key features

  • Generated once by `django-admin startproject`, at the project root
  • Automatically sets DJANGO_SETTINGS_MODULE — no --settings flag needed
  • Runs the exact same commands as django-admin (runserver, migrate, ...)
  • `python manage.py test` and `python manage.py shell` are the other two used daily
  • Also usable as `python -m django`, with the same settings-detection behavior

manage.py vs. plain django-admin

manage.py vs. plain django-admin
Propertymanage.pydjango-admin
Settingsauto-set to this projectmust pass --settings or set env var
Scopeone specific projectany project, system-wide install
Typical useday-to-day project commandsswitching between several projects
manage.py as a pre-configured django-admin

django-admin

generic, needs --settings

startproject

generates manage.py

manage.py

settings already wired in

  1. django-admin — generic, needs --settings
  2. startproject — generates manage.py
  3. manage.py — settings already wired in

Command

bash
python manage.py runserver
python manage.py migrate
python manage.py shell
python manage.py test

manage.py = django-admin with DJANGO_SETTINGS_MODULE pre-set to this project — same commands, no --settings flag needed.

2.App models.py

models.py is generated empty by startapp, one per app. Every model class goes here — the single file makemigrations reads, and the file every other module (views.py, admin.py) imports from.

Key features

  • Generated empty by startapp — `from django.db import models` is the only starting line
  • One models.py per app; large apps sometimes split into a models/ package instead
  • makemigrations scans this file (per app) to detect schema changes
  • views.py, admin.py, and serializers all import model classes FROM here
  • See `django.fundamentals.models` for how a model class itself works

What imports from models.py, and why

What imports from models.py, and why
FileImports models.py for
views.pyquerying and creating rows to build a response
admin.pyregistering a model with admin.site.register()
tests.pycreating test fixtures with Model.objects.create()
migrations/not imported directly — makemigrations reads this file to generate them
models.py as the app's single source of data shape

models.py

defines Invoice

views.py

imports and queries

admin.py

imports and registers

  1. models.py — defines Invoice
  2. views.py — imports and queries
  3. admin.py — imports and registers

Command

python
# <app>/models.py
from django.db import models

class MyModel(models.Model):
    name = models.CharField(max_length=100)

One models.py per app, generated empty — every model class an app owns goes here; makemigrations reads this file per app to detect schema changes.

3.App views.py

views.py is generated empty by startapp. It holds every view the app defines, imports models.py for data, and is referenced by that app's own urls.py.

Key features

  • Generated empty by startapp — no starting boilerplate beyond the file existing
  • Holds every function-based or class-based view the app defines
  • Imports models from `.models`, referenced by name from that app's urls.py
  • A large app can split views.py into a views/ package, mirroring models/
  • See `django.fundamentals.views` for the HttpRequest/HttpResponse contract itself

views.py's position between models.py and urls.py

views.py's position between models.py and urls.py
RelationshipDirection
views.py imports frommodels.py (this app's own, or another app's)
urls.py imports fromviews.py (by name, per path())
templates renderwhatever context a view in views.py passes them
views.py sits between models.py and urls.py

models.py

Invoice

views.py

invoice_list(request)

urls.py

path("", views.invoice_list)

  • models.py
    • Invoice
  • views.py
    • invoice_list(request)
  • urls.py
    • path("", views.invoice_list)

Command

python
# <app>/views.py
from django.shortcuts import render
from .models import MyModel

def my_view(request):
    return render(request, "app/template.html", {})

views.py is generated empty per app — every view lives here, imports models from .models, and is referenced by name from that app's urls.py.

4.migrations/

migrations/ is generated by startapp, one per app, holding numbered files — each a Migration class describing a schema change. makemigrations creates them; migrate applies them; commit both.

Key features

  • Generated by startapp, one migrations/ directory per app, with an __init__.py
  • Files are numbered sequentially: 0001_initial.py, 0002_add_field.py, ...
  • makemigrations generates a new file by diffing models.py against the last one
  • migrate applies pending migrations, tracked in Django's own migration history table
  • Meant to be committed to version control — the schema history is part of the codebase

makemigrations vs. migrate

makemigrations vs. migrate
CommandDoes
makemigrationscompares models.py to the last migration, writes a new file
migrateruns pending migration files against the actual database
makemigrations --dry-runshows what WOULD be generated, writes nothing
migrate <app> <number>migrates one app to a specific migration, forward or back
From a model change to an applied migration

models.py

field added

makemigrations

writes 0002_....py

migrate

applies to the real table

  1. models.py — field added
  2. makemigrations — writes 0002_....py
  3. migrate — applies to the real table

Command

bash
python manage.py makemigrations
python manage.py migrate

migrations/ holds numbered files generated by makemigrations from models.py, applied by migrate — commit them to version control, never delete an applied one.

5.settings.py

settings.py lives inside the project's inner package (mysite/mysite/settings.py), generated once by startproject. It is the one file every other part of the project — models, middleware, templates — ultimately configures itself against.

Key features

  • Lives at mysite/mysite/settings.py — inside the inner package, not the project root
  • Generated once by startproject, then hand-edited as the project grows
  • INSTALLED_APPS, MIDDLEWARE, DATABASES, and ROOT_URLCONF all live here
  • A large project often splits this into a settings/ package (base/dev/prod) — see below
  • See `django.fundamentals.settings` for how DJANGO_SETTINGS_MODULE picks this file

Where settings.py sits, project vs. large-project layout

Where settings.py sits, project vs. large-project layout
LayoutLocation
startproject defaultmysite/mysite/settings.py — one file
Large-project splitconfig/settings/base.py + development.py/production.py

Command

text
mysite/
    manage.py
    mysite/
        __init__.py
        settings.py   # <- here
        urls.py
        asgi.py
        wsgi.py

settings.py sits at mysite/mysite/settings.py — INSTALLED_APPS, MIDDLEWARE, and DATABASES all live here; large projects split it into base/dev/production files.

6.urls.py (project)

The project's root urls.py lives beside settings.py and is what ROOT_URLCONF points at. In a well-structured project it stays short — mostly include() calls delegating to each app's own urls.py, not a flat list of every route in the site.

Key features

  • Lives at mysite/mysite/urls.py, alongside settings.py
  • ROOT_URLCONF (a setting) points at this module by dotted path
  • Best practice: mostly include() calls, one per app, not inline app routes
  • admin.site.urls is almost always included here directly, unlike app routes
  • See `django.fundamentals.url-configuration` for path()/include() mechanics

What typically lives in the root urls.py vs. an app's own

What typically lives in the root urls.py vs. an app's own
PropertyProject urls.pyApp urls.py
Holdsinclude() calls + adminthe app's actual path() patterns
Changes whena new app is addedthat app's routes change
ROOT_URLCONF points atthis file(not directly — reached via include())

Command

python
# mysite/urls.py
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path("admin/", admin.site.urls),
    path("billing/", include("billing.urls")),
]

The root urls.py (ROOT_URLCONF) stays short — mostly include() per app — while each app's own urls.py holds its actual routes.

7.App apps.py

apps.py is generated by startapp inside every app. It defines one AppConfig subclass carrying the app's metadata (name, verbose_name) and its ready() hook.

Key features

  • Generated by `python manage.py startapp <name>`, one AppConfig subclass per app
  • name is required — the full Python path to the app (e.g. "billing")
  • verbose_name gives a human-readable label, shown in the admin site
  • ready() is the hook for signal registration — see `django.fundamentals.app-registry`
  • INSTALLED_APPS usually just lists the app name; Django finds this config automatically

AppConfig attributes commonly set in apps.py

AppConfig attributes commonly set in apps.py
AttributePurpose
namethe app's full Python path — required
verbose_namehuman-readable label, shown in the admin
default_auto_fieldprimary key type for this app's models
ready()method — runs once, after the full registry is populated

Command

python
# <app>/apps.py
from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = "myapp"

apps.py holds one AppConfig subclass per app — name is required and must match the real import path; ready() is where signal registration belongs.

8.App admin.py

admin.py is generated with one commented-out import by startapp. It is the only place a model needs to be registered for the admin site to manage it — an app with no admin.py content is simply invisible in /admin/, nothing else changes.

Key features

  • Generated by startapp with `from django.contrib import admin` and nothing else
  • admin.site.register(Model) is the minimum needed to manage a model there
  • A model NOT registered here simply does not appear in /admin/ — no error
  • ModelAdmin subclasses (list_display, etc.) also live in this file
  • See `django.fundamentals.admin` for what a ModelAdmin can customize

admin.py before and after registering a model

admin.py before and after registering a model
StateEffect
Freshly generated (empty)model exists, invisible in /admin/
admin.site.register(Model) addedbasic CRUD interface appears in /admin/
@admin.register(Model) + ModelAdmincustomized list_display, filters, search

Command

python
# <app>/admin.py
from django.contrib import admin
from .models import MyModel

admin.site.register(MyModel)

admin.py starts as one commented import; a model is invisible in /admin/ until admin.site.register(Model) explicitly lists it — no error if forgotten.

9.App tests.py

tests.py is generated by startapp as the home for an app's tests. TestCase wraps each test in a transaction that rolls back afterward, so tests never leak data into each other.

Key features

  • Generated by startapp with `from django.test import TestCase` and nothing else
  • django.test.TestCase wraps each test in a transaction, rolled back after
  • Always subclass TestCase, not plain unittest.TestCase, for database-touching tests
  • `python manage.py test` discovers every test*.py file and TestCase subclass automatically
  • A large app can split tests.py into a tests/ package (test_models.py, test_views.py, ...)

manage.py test invocation forms

manage.py test invocation forms
CommandRuns
python manage.py testevery discovered test in the project
python manage.py test billingevery test in the billing app
python manage.py test billing.tests.InvoiceTestCaseone test class

Command

python
# <app>/tests.py
from django.test import TestCase

class MyTestCase(TestCase):
    def test_something(self):
        self.assertTrue(True)

Always subclass django.test.TestCase, not plain unittest.TestCase, for database-touching tests — each test runs in its own rolled-back transaction.

10.App urls.py

An app's own urls.py is not generated by startapp — added by hand so its routes can be included() from the root urls.py. app_name sets a namespace so {% url %} reverses correctly even if two apps reuse a name.

Key features

  • Not generated by startapp — added by convention, unlike models.py/views.py/admin.py
  • app_name sets a namespace, so {% url 'billing:list' %} is unambiguous
  • Included from the project's root urls.py via include("app.urls")
  • Its own urlpatterns only need patterns relative to whatever prefix include() gave it
  • See `django.project-structure.urls-py` for the root file that includes this one

Namespacing two apps that reuse the same URL name

Namespacing two apps that reuse the same URL name
Without app_nameWith app_name
{% url 'list' %} — ambiguous if two apps both define "list"{% url 'billing:list' %} — unambiguous
reverse("list")reverse("billing:list")

Command

python
# <app>/urls.py
from django.urls import path
from . import views

app_name = "myapp"
urlpatterns = [
    path("", views.index, name="index"),
]

An app's urls.py is hand-added, not generated — app_name namespaces its route names so {% url 'app:name' %} stays unambiguous across apps.

11.templates/

Django looks for templates in each app's own templates/ (APP_DIRS, the default) plus any TEMPLATES DIRS for project-wide ones. Convention nests app templates deeper, templates/<app_name>/, to avoid collisions.

Key features

  • APP_DIRS = True (the default) makes Django look in each app's own templates/
  • TEMPLATES[0]["DIRS"] lists extra, project-wide template directories
  • Convention: templates/<app_name>/file.html, not templates/file.html directly
  • {% extends %} and {% include %} both resolve against this same combined search path
  • render(request, "billing/detail.html", ...) — the app-name prefix is part of the path

Where Django looks for a template, in order

Where Django looks for a template, in order
SourceExample path
TEMPLATES[0]["DIRS"] (project-wide)BASE_DIR / "templates" / "base.html"
Each app's own templates/ (APP_DIRS)billing/templates/billing/detail.html

Command

text
billing/
    templates/
        billing/
            list.html
            detail.html

APP_DIRS (default True) searches each app's own templates/; nest under templates/<app_name>/ to avoid two apps' files colliding in the shared search path.

12.static/

Like templates/, static/ is discovered per-app automatically (via staticfiles) plus STATICFILES_DIRS for project-wide assets. Same namespacing convention: static/<app_name>/file.css.

Key features

  • Each app's own static/ is discovered automatically via django.contrib.staticfiles
  • STATICFILES_DIRS lists extra, project-wide static directories
  • Convention: static/<app_name>/file.css, mirroring templates/<app_name>/
  • collectstatic merges every source into one STATIC_ROOT for production
  • See `django.fundamentals.static-files` for STATIC_URL and the {% static %} tag itself

templates/ and static/ discovery, side by side

templates/ and static/ discovery, side by side
Propertytemplates/static/
Per-app, automaticAPP_DIRS = Truedjango.contrib.staticfiles (always on)
Project-wide settingTEMPLATES[0]["DIRS"]STATICFILES_DIRS
Namespacing conventiontemplates/<app_name>/static/<app_name>/
Merged for production by(not applicable — templates render live)collectstatic → STATIC_ROOT

Command

text
billing/
    static/
        billing/
            invoice.css
            invoice.js

static/ discovers per-app automatically (like templates/), plus STATICFILES_DIRS project-wide — namespace under static/<app_name>/, same as templates.

Remember

  • manage.py is django-admin with DJANGO_SETTINGS_MODULE already set — same commands, no --settings flag.
  • One models.py per app — every other file imports FROM it, never the other way around.
  • The root urls.py stays short: mostly include() per app; each app's own urls.py holds its actual routes.
  • templates/ and static/ both discover per-app automatically, namespaced under templates/<app>/ and static/<app>/.

Pro tip

  • A model is invisible in /admin/ until admin.site.register(Model) explicitly lists it — no error if forgotten.
  • Always subclass django.test.TestCase, not plain unittest.TestCase — each test runs in a rolled-back transaction.
  • management/commands/ needs __init__.py at BOTH the management/ and commands/ levels, or the command silently fails to appear.
  • A large app can split models.py/views.py into a models/ or views/ package — Django only cares what's importable, not the file count.

Next up

  • Request/response lifecycle in depth
  • Models + ORM + PostgreSQL
  • Views + Forms + Templates

Sources

  • django-admin and manage.py — docs.djangoproject.com/en/5.2/ref/django-admin/
  • Migrations — docs.djangoproject.com/en/5.2/topics/migrations/
  • Every claim checked against Django 5.2 LTS docs · checked 2026-08-20

Spotted a mistake on this page? Tell us.