59.1 Django Project and App Structure
Right, let’s talk structure. This is where most Django tutorials lose people, not because it’s hard, but because they explain the what and not the why. And the why here is actually pretty brilliant once you get it. Think of it like this: a Django project is your entire website—the container for all its settings and apps. A Django app is a self-contained module that does one specific thing. Your blog is an app. Your user authentication is an app. Your poll system is an app. This isn’t just organization; it’s the entire philosophy of reusable, pluggable components. You’re not building a monolith; you’re building a set of Lego bricks that can form a castle, a spaceship, or, more likely, a slightly janky e-commerce site for artisanal toast.
The Project Skeleton: It’s Alive!
When you run django-admin startproject my_awesome_site, you’re not just getting a folder; you’re getting a meticulously crafted foundation. Here’s what it gives you, and more importantly, why:
my_awesome_site/
manage.py
my_awesome_site/
__init__.py
settings.py
urls.py
asgi.py
wsgi.py
manage.py is your Swiss Army knife. You don’t edit it; you use it. It’s the command-line entry point for every significant thing you’ll do: running the dev server (runserver), creating database tables (migrate), or creating new apps (startapp). It’s what delegates commands to the real Django machinery.
The inner my_awesome_site/ directory is your actual Python package. This is where the magic—and the configuration—lives.
settings.py: This is the brainstem of your entire operation. Database connections, installed apps, middleware, template directories, security keys… it all lives here. We’ll come back to this beast in a minute.urls.py: The bouncer of your website. It looks at the URL a user requests and decides which view gets to handle it. This is your URL dispatcher, and it’s gloriously simple.wsgi.py&asgi.py: These are your deployment hooks. WSGI for the traditional synchronous crowd, ASGI for the cool async kids. You mostly ignore these until it’s time to ship your masterpiece to a real server. For now, just know they exist so yourrunservercommand has something to point at.
The App: Your Feature Factory
An app is meant to be a focused, reusable component. To create one, you run python manage.py startapp blog from the same directory as your manage.py. This scaffolds a new module for you:
blog/
__init__.py
admin.py
apps.py
migrations/
__init__.py
models.py
tests.py
views.py
Let’s demystify this. models.py is where you define your database tables as Python classes. Django’s ORM then translates these into SQL for you, which is one of the framework’s killer features. views.py is where you write the logic that handles a request and returns a response. admin.py is where you register your models to get that slick, auto-generated admin interface for free—a feature so good it feels like you’re cheating.
Now, the most common rookie mistake is to start dumping all your logic into the project root. Don’t. Your project should be a lightweight container; the real work happens inside your apps. If you find yourself creating a views.py in the project folder, you’ve already lost. Create a new app instead.
The Sacred Settings File
Let’s crack open settings.py and look at the two most important sections. First, INSTALLED_APPS. This is a list of all the apps that are active in your Django project. When you startapp, you must add your new app here. Why? Because Django needs to know about it to look for its models, templates, and static files. It’s like adding a new component to the circuit board.
# settings.py
INSTALLED_APPS = [
'django.contrib.admin', # The admin site
'django.contrib.auth', # Authentication system
'django.contrib.contenttypes', # Fancy generic relationships
'django.contrib.sessions', # Session management
'django.contrib.messages', # One-time notification system
'django.contrib.staticfiles', # For managing CSS, JS, images
'blog', # <- YOU ADD YOUR APP HERE. Don't forget this!
]
Second, the DATABASES setting. By default, it gives you SQLite, which is a fantastic choice for development. It’s a single file, there’s no server to run, and it just works. But please, for the love of all that is holy, do not use SQLite in production for a real web application. It will buckle under any real load. The default is a pragmatic choice for getting started, not a recommendation for your live site.
The URL Dispatcher: How Django Knows Where to Go
The project’s main urls.py is the master routing table. Its job is to import the URL patterns from your individual apps and string them all together. This is where the whole “project vs. app” philosophy clicks.
# my_awesome_site/urls.py
from django.contrib import admin
from django.urls import path, include # Note the 'include' import!
urlpatterns = [
path('admin/', admin.site.urls),
path('blog/', include('blog.urls')), # Delegate all URLs starting with /blog/ to the blog app
]
See that? When a request comes in for /blog/my-first-post/, the project-level URLconf chops off the already-matched part (/blog/) and sends the remainder (my-first-post/) down to the blog.urls module to be handled. This means each app can manage its own URL structure without the project needing to know the details. It’s delegation, and it’s beautiful.
You then create a urls.py inside your blog app to handle those routes.
# blog/urls.py
from django.urls import path
from . import views # Import views from the current app
urlpatterns = [
path('my-first-post/', views.post_detail, name='post_detail'),
]
The best practice here is to always use the name argument. This lets you refer to this URL elsewhere in your code by its name ({% url 'post_detail' %}) instead of hard-coding the path. If you change the URL later, everything else updates automatically. It’s a simple trick that saves you from a world of pain.