Getting started
Installation
# Install Django
python -m pip install Django
# Check version
python -m django --version
Create project
# Create new project
django-admin startproject mysite
# Navigate to project
cd mysite
# Run development server
python manage.py runserver
Create app
# Create new app
python manage.py startapp appname
# Add to INSTALLED_APPS in settings.py
INSTALLED_APPS = [
'appname',
# ...
]
Project structure
mysite/
├── manage.py
├── mysite/
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ ├── asgi.py
│ └── wsgi.py
└── appname/
├── migrations/
├── __init__.py
├── admin.py
├── apps.py
├── models.py
├── tests.py
└── views.py
Models
Basic model
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
pub_date = models.DateTimeField()
is_published = models.BooleanField(default=False)
def __str__(self):
return self.title
Field types
| Field | Description |
|---|---|
CharField(max_length) |
Short text |
TextField() |
Long text |
IntegerField() |
Integer number |
FloatField() |
Floating point |
BooleanField() |
True/False |
DateField() |
Date |
DateTimeField() |
Date and time |
EmailField() |
Email address |
URLField() |
URL |
FileField() |
File upload |
ImageField() |
Image upload |
JSONField() |
JSON data |
Relationships
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(
Author,
on_delete=models.CASCADE
)
class Tag(models.Model):
name = models.CharField(max_length=50)
articles = models.ManyToManyField(Article)
class Profile(models.Model):
user = models.OneToOneField(
User,
on_delete=models.CASCADE
)
on_delete options
| Option | Description |
|---|---|
CASCADE |
Delete related objects |
PROTECT |
Prevent deletion |
SET_NULL |
Set to NULL |
SET_DEFAULT |
Set to default |
DO_NOTHING |
Do nothing |
Meta options
class Article(models.Model):
title = models.CharField(max_length=200)
pub_date = models.DateTimeField()
class Meta:
ordering = ['-pub_date']
verbose_name = 'article'
verbose_name_plural = 'articles'
db_table = 'blog_articles'
unique_together = ['title', 'pub_date']
QuerySet / ORM
Creating objects
# Method 1: Create and save
article = Article(title='Hello', content='World')
article.save()
# Method 2: Create in one step
article = Article.objects.create(
title='Hello',
content='World'
)
# Bulk create
Article.objects.bulk_create([
Article(title='One'),
Article(title='Two')
])
Retrieving objects
# Get all objects
articles = Article.objects.all()
# Get single object
article = Article.objects.get(id=1)
# Filter
articles = Article.objects.filter(is_published=True)
# Exclude
articles = Article.objects.exclude(is_published=False)
# Get first/last
first = Article.objects.first()
last = Article.objects.last()
# Check if exists
exists = Article.objects.filter(title='Hello').exists()
Field lookups
# Exact match
Article.objects.filter(title__exact='Hello')
# Case-insensitive exact
Article.objects.filter(title__iexact='hello')
# Contains
Article.objects.filter(title__contains='ell')
# Case-insensitive contains
Article.objects.filter(title__icontains='ELL')
# Starts with
Article.objects.filter(title__startswith='Hel')
# Ends with
Article.objects.filter(title__endswith='lo')
# Greater than
Article.objects.filter(id__gt=5)
# Less than or equal
Article.objects.filter(id__lte=10)
# In list
Article.objects.filter(id__in=[1, 3, 5])
# Is null
Article.objects.filter(pub_date__isnull=True)
# Range
Article.objects.filter(id__range=(1, 10))
# Date
Article.objects.filter(pub_date__year=2024)
Article.objects.filter(pub_date__month=12)
Ordering and slicing
# Order by
Article.objects.order_by('title')
Article.objects.order_by('-pub_date') # Descending
# Multiple ordering
Article.objects.order_by('author', '-pub_date')
# Slicing (LIMIT)
Article.objects.all()[:5]
# Offset and limit
Article.objects.all()[5:10]
# Reverse order
Article.objects.all().reverse()
# Random order
Article.objects.order_by('?')
Updating objects
# Update single object
article = Article.objects.get(id=1)
article.title = 'New Title'
article.save()
# Update multiple objects
Article.objects.filter(is_published=False).update(
is_published=True
)
# Update with F expressions
from django.db.models import F
Article.objects.update(views=F('views') + 1)
Deleting objects
# Delete single object
article = Article.objects.get(id=1)
article.delete()
# Delete multiple objects
Article.objects.filter(is_published=False).delete()
# Delete all
Article.objects.all().delete()
Aggregation
from django.db.models import Count, Avg, Sum, Max, Min
# Count
Article.objects.count()
# Aggregate
Article.objects.aggregate(Avg('views'))
Article.objects.aggregate(total=Sum('views'))
# Annotate
Article.objects.annotate(num_comments=Count('comment'))
Q objects (complex queries)
from django.db.models import Q
# OR condition
Article.objects.filter(
Q(title__contains='Django') | Q(title__contains='Python')
)
# AND condition
Article.objects.filter(
Q(title__contains='Django') & Q(is_published=True)
)
# NOT condition
Article.objects.filter(~Q(title__contains='Test'))
Relationship spanning
# Forward relationship (ForeignKey)
Book.objects.filter(author__name='John')
# Reverse relationship
Author.objects.filter(book__title='Django')
# Multiple levels
Book.objects.filter(
author__profile__country='USA'
)
Views
Function-based views
from django.shortcuts import render, redirect
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world!")
def article_detail(request, id):
article = Article.objects.get(id=id)
return render(request, 'article.html', {
'article': article
})
def article_create(request):
if request.method == 'POST':
# Process form
return redirect('article_list')
return render(request, 'form.html')
Class-based views
from django.views.generic import (
ListView, DetailView, CreateView,
UpdateView, DeleteView
)
class ArticleListView(ListView):
model = Article
template_name = 'article_list.html'
context_object_name = 'articles'
paginate_by = 10
class ArticleDetailView(DetailView):
model = Article
template_name = 'article_detail.html'
class ArticleCreateView(CreateView):
model = Article
fields = ['title', 'content']
success_url = '/articles/'
Shortcuts
from django.shortcuts import (
render, redirect, get_object_or_404
)
# Render template
render(request, 'template.html', context)
# Redirect
redirect('view_name')
redirect('/url/path/')
# Get object or 404
article = get_object_or_404(Article, id=1)
# Get list or 404
from django.shortcuts import get_list_or_404
articles = get_list_or_404(Article, is_published=True)
Request object
def my_view(request):
# Method
request.method # 'GET', 'POST', etc.
# GET parameters
request.GET['key']
request.GET.get('key', 'default')
# POST data
request.POST['key']
# Files
request.FILES['file']
# Session
request.session['key'] = 'value'
# User
request.user
request.user.is_authenticated
URL routing
Basic patterns
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
path('articles/', views.article_list, name='article_list'),
path('articles/<int:id>/', views.article_detail, name='article_detail'),
]
Path converters
| Converter | Description | Example |
|---|---|---|
str |
Any non-empty string | <str:slug> |
int |
Positive integer | <int:id> |
slug |
Slug string | <slug:slug> |
uuid |
UUID | <uuid:uuid> |
path |
Any string with slashes | <path:path> |
Include other URLconfs
# project/urls.py
from django.urls import path, include
urlpatterns = [
path('blog/', include('blog.urls')),
path('admin/', admin.site.urls),
]
URL namespaces
# app/urls.py
app_name = 'blog'
urlpatterns = [
path('', views.index, name='index'),
path('<int:id>/', views.detail, name='detail'),
]
# In templates or views
{% url 'blog:index' %}
reverse('blog:detail', args=[1])
Reverse URLs
from django.urls import reverse
# In views
url = reverse('article_detail', args=[1])
url = reverse('article_detail', kwargs={'id': 1})
# In templates
{% url 'article_detail' 1 %}
{% url 'article_detail' id=1 %}
Regular expressions
from django.urls import re_path
urlpatterns = [
re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
]
Templates
Variables
{# Simple variable #}
{{ variable }}
{# Object attribute #}
{{ article.title }}
{# Dictionary key #}
{{ dict.key }}
{# List index #}
{{ list.0 }}
{# Method call (no parentheses) #}
{{ article.get_title }}
Filters
{# Lower case #}
{{ text|lower }}
{# Upper case #}
{{ text|upper }}
{# Title case #}
{{ text|title }}
{# Length #}
{{ list|length }}
{# Default value #}
{{ value|default:"N/A" }}
{# Date formatting #}
{{ date|date:"Y-m-d" }}
{# Truncate #}
{{ text|truncatewords:30 }}
{# Safe (disable escaping) #}
{{ html|safe }}
{# Join list #}
{{ list|join:", " }}
{# Chain filters #}
{{ text|lower|truncatewords:10 }}
Tags
{# If statement #}
{% if user.is_authenticated %}
<p>Welcome, {{ user.username }}!</p>
{% elif user.is_guest %}
<p>Welcome, guest!</p>
{% else %}
<p>Please log in.</p>
{% endif %}
{# For loop #}
{% for article in articles %}
<h2>{{ article.title }}</h2>
{% empty %}
<p>No articles found.</p>
{% endfor %}
{# With #}
{% with total=articles.count %}
<p>Total: {{ total }}</p>
{% endwith %}
{# URL #}
<a href="{% url 'article_detail' article.id %}">Read</a>
{# Static files #}
{% load static %}
<img src="{% static 'img/logo.png' %}">
{# CSRF token #}
<form method="post">
{% csrf_token %}
...
</form>
Template inheritance
{# base.html #}
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}My Site{% endblock %}</title>
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
{# article.html #}
{% extends "base.html" %}
{% block title %}{{ article.title }}{% endblock %}
{% block content %}
<h1>{{ article.title }}</h1>
<p>{{ article.content }}</p>
{% endblock %}
Includes
{# Include template #}
{% include "header.html" %}
{# Include with variables #}
{% include "article_card.html" with article=article %}
{# Include only if exists #}
{% include "optional.html" only %}
Comments
{# Single line comment #}
{% comment %}
Multi-line comment
Can span multiple lines
{% endcomment %}
For loop variables
{% for item in items %}
{{ forloop.counter }} {# 1-indexed #}
{{ forloop.counter0 }} {# 0-indexed #}
{{ forloop.revcounter }} {# Reverse counter #}
{{ forloop.first }} {# True on first iteration #}
{{ forloop.last }} {# True on last iteration #}
{{ forloop.parentloop }} {# Parent loop #}
{% endfor %}
Forms
Form definition
from django import forms
class ContactForm(forms.Form):
name = forms.CharField(max_length=100)
email = forms.EmailField()
message = forms.CharField(widget=forms.Textarea)
def clean_email(self):
email = self.cleaned_data['email']
if not email.endswith('@example.com'):
raise forms.ValidationError('Must be @example.com')
return email
ModelForm
from django.forms import ModelForm
class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ['title', 'content', 'is_published']
# Or exclude fields
exclude = ['created_at']
widgets = {
'content': forms.Textarea(attrs={'rows': 5}),
}
labels = {
'title': 'Article Title',
}
Using forms in views
def contact(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
name = form.cleaned_data['name']
email = form.cleaned_data['email']
# Process data
return redirect('success')
else:
form = ContactForm()
return render(request, 'contact.html', {'form': form})
Rendering forms
{# Auto-render entire form #}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Submit</button>
</form>
{# Manual field rendering #}
<form method="post">
{% csrf_token %}
{{ form.name.label_tag }}
{{ form.name }}
{{ form.name.errors }}
<button type="submit">Submit</button>
</form>
{# Loop through fields #}
<form method="post">
{% csrf_token %}
{% for field in form %}
{{ field.label_tag }}
{{ field }}
{{ field.errors }}
{% endfor %}
<button type="submit">Submit</button>
</form>
Form rendering options
{{ form.as_p }} {# Wrapped in <p> tags #}
{{ form.as_table }} {# As table rows #}
{{ form.as_ul }} {# As <li> items #}
Field types
| Field | Description |
|---|---|
CharField |
Text input |
EmailField |
Email input |
URLField |
URL input |
IntegerField |
Integer input |
BooleanField |
Checkbox |
ChoiceField |
Select dropdown |
DateField |
Date picker |
FileField |
File upload |
ImageField |
Image upload |
Admin interface
Basic registration
# admin.py
from django.contrib import admin
from .models import Article
admin.site.register(Article)
ModelAdmin customization
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'pub_date', 'is_published']
list_filter = ['is_published', 'pub_date']
search_fields = ['title', 'content']
date_hierarchy = 'pub_date'
ordering = ['-pub_date']
fieldsets = [
('Basic Info', {
'fields': ['title', 'content']
}),
('Meta', {
'fields': ['author', 'pub_date', 'is_published'],
'classes': ['collapse']
}),
]
Inline editing
class CommentInline(admin.TabularInline):
model = Comment
extra = 1
class ArticleAdmin(admin.ModelAdmin):
inlines = [CommentInline]
Custom actions
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
actions = ['make_published']
def make_published(self, request, queryset):
queryset.update(is_published=True)
make_published.short_description = "Mark as published"
List display options
| Option | Description |
|---|---|
list_display |
Columns to display |
list_filter |
Sidebar filters |
search_fields |
Searchable fields |
ordering |
Default ordering |
date_hierarchy |
Date drill-down |
list_per_page |
Items per page |
list_editable |
Editable fields |
Management commands
Database migrations
# Create migrations
python manage.py makemigrations
# Apply migrations
python manage.py migrate
# Show migrations
python manage.py showmigrations
# SQL for migration
python manage.py sqlmigrate app_name 0001
# Fake migration
python manage.py migrate --fake
User management
# Create superuser
python manage.py createsuperuser
# Change password
python manage.py changepassword username
Development server
# Run server
python manage.py runserver
# Custom port
python manage.py runserver 8080
# Custom host and port
python manage.py runserver 0.0.0.0:8000
Shell
# Django shell
python manage.py shell
# Shell with imports
python manage.py shell_plus
Other commands
# Check for problems
python manage.py check
# Collect static files
python manage.py collectstatic
# Create cache table
python manage.py createcachetable
# Clear cache
python manage.py clear_cache
# Load data from fixture
python manage.py loaddata fixture.json
# Dump data to fixture
python manage.py dumpdata app.Model > fixture.json
# Test
python manage.py test
# Show URLs
python manage.py show_urls
Settings
Common settings
# settings.py
# Debug mode
DEBUG = True
# Allowed hosts
ALLOWED_HOSTS = ['localhost', '127.0.0.1']
# Database
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Static files
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_DIRS = [BASE_DIR / 'static']
# Media files
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
# Templates
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
PostgreSQL database
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'mydatabase',
'USER': 'mydatabaseuser',
'PASSWORD': 'mypassword',
'HOST': '127.0.0.1',
'PORT': '5432',
}
}
Internationalization
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
Authentication
Login/Logout views
# urls.py
from django.contrib.auth import views as auth_views
urlpatterns = [
path('login/', auth_views.LoginView.as_view(), name='login'),
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
]
Login required decorator
from django.contrib.auth.decorators import login_required
@login_required
def my_view(request):
return render(request, 'template.html')
# With custom redirect
@login_required(login_url='/accounts/login/')
def my_view(request):
pass
Permission checks
from django.contrib.auth.decorators import permission_required
@permission_required('app.add_article')
def create_article(request):
pass
# In templates
{% if perms.app.add_article %}
<a href="{% url 'create_article' %}">Create</a>
{% endif %}
User model
from django.contrib.auth.models import User
# Create user
user = User.objects.create_user(
username='john',
email='john@example.com',
password='password'
)
# Check authentication
if user.is_authenticated:
print(user.username)
# Check permissions
if user.has_perm('app.add_article'):
pass
Middleware
Custom middleware
# middleware.py
class SimpleMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Code before view
response = self.get_response(request)
# Code after view
return response
Add to settings
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'myapp.middleware.SimpleMiddleware', # Custom
# ...
]
Signals
Built-in signals
from django.db.models.signals import (
pre_save, post_save,
pre_delete, post_delete
)
from django.dispatch import receiver
@receiver(post_save, sender=Article)
def article_saved(sender, instance, created, **kwargs):
if created:
print(f'New article: {instance.title}')
Custom signals
# signals.py
from django.dispatch import Signal
article_published = Signal()
# Send signal
article_published.send(sender=Article, instance=article)
# Receive signal
@receiver(article_published)
def handle_published(sender, instance, **kwargs):
pass
Testing
Test case
from django.test import TestCase
from .models import Article
class ArticleTestCase(TestCase):
def setUp(self):
Article.objects.create(title='Test', content='Content')
def test_article_title(self):
article = Article.objects.get(title='Test')
self.assertEqual(article.title, 'Test')
Test client
from django.test import Client
class ViewTestCase(TestCase):
def test_index(self):
client = Client()
response = client.get('/')
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Welcome')
Run tests
# Run all tests
python manage.py test
# Specific app
python manage.py test myapp
# Specific test
python manage.py test myapp.tests.ArticleTestCase.test_article_title
# Keep database
python manage.py test --keepdb
# Parallel
python manage.py test --parallel
Pagination
In views
from django.core.paginator import Paginator
def article_list(request):
articles = Article.objects.all()
paginator = Paginator(articles, 10) # 10 per page
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
return render(request, 'list.html', {'page_obj': page_obj})
In templates
{% for article in page_obj %}
<h2>{{ article.title }}</h2>
{% endfor %}
<div class="pagination">
{% if page_obj.has_previous %}
<a href="?page=1">First</a>
<a href="?page={{ page_obj.previous_page_number }}">Previous</a>
{% endif %}
<span>Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">Next</a>
<a href="?page={{ page_obj.paginator.num_pages }}">Last</a>
{% endif %}
</div>
Caching
Cache configuration
# settings.py
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379',
}
}
Using cache
from django.core.cache import cache
# Set cache
cache.set('key', 'value', 300) # 300 seconds
# Get cache
value = cache.get('key')
# Delete cache
cache.delete('key')
# Clear all
cache.clear()
View caching
from django.views.decorators.cache import cache_page
@cache_page(60 * 15) # 15 minutes
def my_view(request):
return render(request, 'template.html')
Template fragment caching
{% load cache %}
{% cache 500 sidebar %}
.. expensive sidebar content ..
{% endcache %}
Gotchas
Always await params in Next.js 16
Django views receive resolved params, but remember Django uses synchronous Python functions by default.
Migrations must be committed
Always commit migration files to version control. They define your database schema evolution.
Use select_related and prefetch_related
Avoid N+1 queries by eager loading relationships:
# Bad: N+1 queries
articles = Article.objects.all()
for article in articles:
print(article.author.name) # Query per article
# Good: 2 queries total
articles = Article.objects.select_related('author')
for article in articles:
print(article.author.name)
CSRF protection required
Always include {% csrf_token %} in POST forms or use @csrf_exempt decorator (not recommended).
Static files in production
Run python manage.py collectstatic before deploying. Configure web server to serve static files.
Also see
- Django Documentation - Official documentation
- Django Project - Official website
- Django Girls Tutorial - Beginner-friendly tutorial
- Two Scoops of Django - Best practices book
- Classy Class-Based Views - CBV reference
- Django Packages - Reusable Django apps