Django Tutorial
Learn Django from scratch: install the framework, create models, views, templates, run migrations, and launch a working web app step by step.
Django is a high-level Python web framework that lets you build secure, scalable web applications quickly. It follows the Model-Template-View (MTV) architectural pattern — a close relative of MVC — and comes with an ORM, an admin interface, URL routing, authentication, and much more built in. Django's "batteries-included" philosophy means you spend your time on your application logic rather than assembling infrastructure.
This tutorial walks through the essential steps: installing Django inside a virtual environment, creating a project and an app, defining models, writing views, building templates, running database migrations, and verifying everything in the browser.
Why Use Django
Batteries Included
Django ships with a built-in admin panel, user authentication, form handling, an ORM, caching support, and internationalization utilities. There is no need to hunt for third-party packages to cover these basics.
Security by Default
Django guards against SQL injection by using parameterized queries through its ORM. It also provides built-in protection against Cross-Site Scripting (XSS), Cross-Site Request Forgery (CSRF), and clickjacking. The secret key, DEBUG mode, and ALLOWED_HOSTS settings further reduce the attack surface.
Scalability
Django powers high-traffic sites including Instagram and Disqus. Its caching framework integrates with Memcached and Redis, and the ORM supports database connection pooling. You can scale vertically or horizontally without changing your application code.
Large Ecosystem
Django's package index lists thousands of third-party apps — REST APIs (djangorestframework), image handling, payment gateways, and more. The framework's stability and long release cycle mean these packages are actively maintained.
How Django's MTV Pattern Works
| Layer | Django term | Responsibility |
|---|---|---|
| Data | Model | Python class that maps to a database table |
| Presentation | Template | HTML file with {{ variable }} placeholders |
| Logic | View | Python function (or class) that reads data and returns a response |
The URL dispatcher (urls.py) maps incoming request paths to the right view. The view queries models, then passes data to a template. The rendered HTML is returned to the browser.
Setting Up a Virtual Environment
Always isolate Django projects in a virtual environment so that dependencies do not conflict across projects.
# Create and activate a virtual environment
python -m venv venv
source venv/bin/activate # macOS / Linux
venv\Scripts\activate # WindowsAfter activation your prompt shows (venv). Every pip install from this point affects only this environment.
Installing Django
With the virtual environment active, install Django using pip:
pip install djangoVerify the installation:
python -m django --versionDjango prints a version string such as 5.1.4.
Creating a Django Project
A project is the top-level container for your entire site. Create one with:
django-admin startproject mysite
cd mysiteThis generates the following layout:
mysite/
├── manage.py # Command-line utility for this project
└── mysite/
├── __init__.py
├── settings.py # Project configuration (database, installed apps, …)
├── urls.py # Root URL dispatcher
├── asgi.py # ASGI entry point
└── wsgi.py # WSGI entry pointmanage.py is your main tool for running commands such as starting the dev server, making migrations, and creating a superuser. The inner mysite/ folder is the Python package for the project itself.
Creating a Django App
A project can contain multiple apps — self-contained modules, each responsible for one feature area. Create a blog app:
python manage.py startapp blogRegister it so Django can find its models, templates, and static files. Open mysite/settings.py and add 'blog' to INSTALLED_APPS:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog', # <-- add this line
]The app directory looks like this after creation:
blog/
├── __init__.py
├── admin.py # Register models with the admin interface
├── apps.py # App configuration
├── migrations/ # Auto-generated migration files
│ └── __init__.py
├── models.py # Database models
├── tests.py
└── views.py # View functionsDefining Models
A model is a Python class that subclasses django.db.models.Model. Each class attribute maps to a database column. Django's ORM translates your Python code into SQL and handles all database interactions.
Open blog/models.py and define a Post model:
from django.db import models
from django.contrib.auth.models import User
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
date_posted = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
class Meta:
ordering = ['-date_posted'] # Newest posts firstKey field types used here:
| Field | Purpose |
|---|---|
CharField(max_length=N) | Short text with a maximum length |
TextField() | Unlimited-length text |
ForeignKey(…, on_delete=CASCADE) | Many-to-one relationship; deletes posts when their author is deleted |
DateTimeField(auto_now_add=True) | Set once when the record is created |
DateTimeField(auto_now=True) | Updated every time the record is saved |
The __str__ method controls how the object displays in the admin panel and in the Python shell.
Running Migrations
After defining or changing models you must create and apply migrations — version-controlled instructions that update the database schema.
# Generate a new migration file from your model changes
python manage.py makemigrations
# Apply all pending migrations to the database
python manage.py migrateYou will see output like:
Migrations for 'blog':
blog/migrations/0001_initial.py
- Create model Post
Operations to perform:
Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
Applying blog.0001_initial... OKRun migrate any time you add, remove, or modify model fields. Never edit migration files by hand unless you fully understand their SQL.
Using the Django Admin Panel
The built-in admin interface lets you create, read, update, and delete records through a browser — no custom UI required. Register the Post model in blog/admin.py:
from django.contrib import admin
from .models import Post
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'author', 'date_posted')
list_filter = ('date_posted', 'author')
search_fields = ('title', 'content')Create a superuser account so you can log in:
python manage.py createsuperuserDjango prompts for a username, email, and password. Then start the dev server and visit http://127.0.0.1:8000/admin/:
python manage.py runserverLog in with the superuser credentials and you will see the Posts model listed under the "Blog" section.
Writing Views
A view is a Python function (or class) that receives an HTTP request and returns an HTTP response. Open blog/views.py:
from django.shortcuts import render, get_object_or_404
from .models import Post
def post_list(request):
"""Display all published posts, newest first."""
posts = Post.objects.all()
return render(request, 'blog/post_list.html', {'posts': posts})
def post_detail(request, pk):
"""Display a single post by primary key."""
post = get_object_or_404(Post, pk=pk)
return render(request, 'blog/post_detail.html', {'post': post})get_object_or_404 is a helpful shortcut: it fetches the object from the database or automatically returns a 404 response if it does not exist. The third argument to render is a context dictionary — its keys become template variables.
Creating Templates
Templates are HTML files that contain Django Template Language (DTL) tags. Create the directory structure:
blog/
└── templates/
└── blog/
├── base.html
├── post_list.html
└── post_detail.htmlblog/templates/blog/base.html — a shared layout:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}My Blog{% endblock %}</title>
</head>
<body>
<header><h1>My Blog</h1></header>
<main>
{% block content %}{% endblock %}
</main>
</body>
</html>
blog/templates/blog/post_list.html — the list page:
{% extends "blog/base.html" %}
{% block title %}All Posts{% endblock %}
{% block content %}
{% for post in posts %}
<article>
<h2><a href="/blog/{{ post.pk }}/">{{ post.title }}</a></h2>
<p>By {{ post.author }} on {{ post.date_posted|date:"N j, Y" }}</p>
<p>{{ post.content|truncatewords:30 }}</p>
</article>
{% empty %}
<p>No posts yet.</p>
{% endfor %}
{% endblock %}
blog/templates/blog/post_detail.html — the detail page:
{% extends "blog/base.html" %}
{% block title %}{{ post.title }}{% endblock %}
{% block content %}
<h2>{{ post.title }}</h2>
<p>By {{ post.author }} on {{ post.date_posted|date:"N j, Y" }}</p>
<div>{{ post.content }}</div>
<a href="/">Back to all posts</a>
{% endblock %}
Key DTL features used above:
| Tag / Filter | What it does |
|---|---|
{% extends "…" %} | Inherit from a base template |
{% block name %} | Define a replaceable section |
{{ variable }} | Output a value (HTML-escaped automatically) |
{% for … %} / {% empty %} | Loop with a fallback for empty lists |
|date:"N j, Y" | Format a datetime value |
|truncatewords:30 | Shorten text to 30 words |
Configuring URLs
URL configuration maps request paths to view functions.
blog/urls.py — create this file in the blog directory:
from django.urls import path
from . import views
app_name = 'blog' # Enables namespaced URL reversing
urlpatterns = [
path('', views.post_list, name='post_list'),
path('<int:pk>/', views.post_detail, name='post_detail'),
]mysite/urls.py — include the app URLs in the project root:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('blog/', include('blog.urls', namespace='blog')),
path('', include('blog.urls', namespace='blog_root')),
]The <int:pk> path converter captures an integer from the URL and passes it as the pk keyword argument to post_detail.
Verifying the Full Flow
Start the development server:
python manage.py runserverThen open http://127.0.0.1:8000/ in your browser. If no posts exist yet, the template shows "No posts yet." Log in to http://127.0.0.1:8000/admin/ and add a post. Refresh the home page — the post appears immediately.
The request lifecycle for the home page is:
- Browser sends
GET / - Django's URL dispatcher matches
''and callspost_list post_listqueriesPost.objects.all()(translates toSELECT * FROM blog_post ORDER BY date_posted DESC)- Django renders
post_list.html, inserting the posts into the template - The rendered HTML is returned to the browser
Django ORM Query Cheatsheet
The ORM lets you build SQL queries using Python method chains. Understanding the most common lookups saves significant time:
# All posts
Post.objects.all()
# Posts by a specific author
Post.objects.filter(author__username='alice')
# Posts containing a word in the title (case-insensitive)
Post.objects.filter(title__icontains='django')
# The five most recent posts
Post.objects.order_by('-date_posted')[:5]
# Count posts
Post.objects.count()
# Get one object (raises DoesNotExist if not found)
Post.objects.get(pk=1)
# Exclude posts by a specific author
Post.objects.exclude(author__username='alice')Each of these generates a SQL query only when the queryset is evaluated (iterated, sliced, or converted to a list). This is called lazy evaluation and keeps unnecessary database calls to a minimum.
Project Structure Summary
After completing this tutorial, the project structure looks like this:
mysite/
├── manage.py
├── mysite/
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── blog/
├── admin.py
├── migrations/
│ └── 0001_initial.py
├── models.py
├── templates/
│ └── blog/
│ ├── base.html
│ ├── post_list.html
│ └── post_detail.html
├── urls.py
└── views.pyCommon Gotchas
Forgetting to register the app. If 'blog' is missing from INSTALLED_APPS, Django will not find its models or templates and makemigrations will produce no output for that app.
Skipping migrate after makemigrations. Running only makemigrations creates the migration file but does not touch the database. Always follow up with migrate.
Template not found errors. Django looks for templates inside each app's templates/ directory. The extra blog/ subfolder inside templates/ (templates/blog/post_list.html) is a convention that prevents name collisions between apps — do not skip it.
DEBUG = True in production. The development server and DEBUG = True expose tracebacks to anyone. Set DEBUG = False and configure ALLOWED_HOSTS before deploying.
Next Steps
- Python Modules — understand how Python's module system underpins Django's import structure.
- Python PIP — manage Django and its dependencies.
- Python Virtual Environments — keep each Django project isolated.
- MySQL with Python — swap SQLite for a production-grade database.
- MongoDB with Python — use a NoSQL backend with Django.
Django's official documentation at docs.djangoproject.com covers class-based views, forms, authentication, REST APIs with Django REST Framework, and deployment to production — all natural next steps once you are comfortable with the basics covered here.