Learn Django from
Zero to Production
The high-level Python web framework for perfectionists with deadlines. Build fast, secure, and scalable web applications with this comprehensive step-by-step guide packed with real code, explanations, and best practices.
1. What is Django?
Django is a free, open-source, high-level Python web framework. It was created in 2003 by Adrian Holovaty and Simon Willison at a Kansas newspaper, then publicly released in 2005. The name is inspired by the jazz guitarist Django Reinhardt.
Django follows the MVT (ModelβViewβTemplate) architectural pattern and promotes the philosophy of "Don't Repeat Yourself" (DRY) and "Convention over Configuration".
Why Django is Special
Admin panel, auth system, ORM, forms, sessions β all built-in, no extra installs needed.
Protects against SQL injection, CSRF, XSS, clickjacking by default with minimal configuration.
Powers Instagram (1B+ users), Pinterest, Disqus, Mozilla, NASA, and many Fortune 500 companies.
Write Python to query databases β no raw SQL required. Supports PostgreSQL, MySQL, SQLite, Oracle.
One of the most popular web frameworks globally with extensive documentation and packages (PyPI).
Reusable components and templates. Write once, use everywhere. Eliminates code duplication.
MVT Architecture Explained
| Layer | Role | Django Component |
|---|---|---|
| Model | Data & business logic, database structure | models.py |
| View | Request handling, response logic | views.py |
| Template | HTML presentation layer, UI | templates/*.html |
| URL Dispatcher | Routes URLs to correct views | urls.py |
Real-World Sites Built with Django
The most popular photo-sharing app. Django handles billions of requests daily.
Visual discovery engine serving hundreds of millions of users worldwide.
Comment platform for millions of websites. Highly scalable Django deployment.
Firefox browser's website and add-ons portal built entirely on Django.
Official NASA website and several internal tools use Django as the backend.
Git hosting service (Atlassian) originally built with Django.
2. Installation Guide (Windows, Mac, Linux)
Install Python 3.10+
Download from python.org. On Windows, check "Add Python to PATH" during installation.
# Check Python version python --version # Windows python3 --version # Mac/Linux # Should output Python 3.10 or higher
Create a Virtual Environment
# Create virtual environment python -m venv myenv # Activate β Windows myenv\Scripts\activate # Activate β Mac/Linux source myenv/bin/activate # You'll see (myenv) prefix in terminal
Install Django
pip install django # Install specific version pip install django==5.0.6 # Verify installation django-admin --version python -c "import django; print(django.get_version())"
Install Useful Dev Tools
pip install django-debug-toolbar # Dev debugging pip install djangorestframework # REST APIs pip install pillow # Image handling pip install python-decouple # .env config pip install psycopg2-binary # PostgreSQL
Save Dependencies
# Save all installed packages pip freeze > requirements.txt # Later, restore on new machine pip install -r requirements.txt
3. Creating Your First Django Project
# Create project django-admin startproject myproject # Enter project folder cd myproject # Run development server python manage.py runserver # Run on custom port python manage.py runserver 8080
Project Structure Explained
myproject/ β βββ manage.py # CLI utility: run server, migrate, shell β βββ myproject/ # Project package (same name) βββ __init__.py # Makes it a Python package βββ settings.py # ALL configuration: DB, apps, timezone βββ urls.py # Root URL declarations βββ asgi.py # Async server gateway (Django Channels) βββ wsgi.py # WSGI server entry (Gunicorn, uWSGI)
Key settings.py Settings to Know
# myproject/settings.py DEBUG = True # False in production! ALLOWED_HOSTS = ['*'] # Allowed domains SECRET_KEY = 'your-secret-key-here' # Change this! Never share! INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'pages', # Your own apps go here ] DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } TIME_ZONE = 'Asia/Karachi' # Set your timezone LANGUAGE_CODE = 'en-us'
SECRET_KEY to Git! Use python-decouple or environment variables to keep it safe in production.4. Django Apps & Project Structure
A Django project is a collection of apps. Each app is a self-contained module that handles a specific feature (e.g., blog, store, users).
# Create an app called "blog"
python manage.py startapp blogblog/ βββ __init__.py βββ admin.py # Register models for admin panel βββ apps.py # App configuration βββ migrations/ # Database migration files β βββ __init__.py βββ models.py # Database models (classes) βββ tests.py # Unit tests βββ views.py # View functions or classes
settings.py β INSTALLED_APPS. Without this, Django ignores it!Recommended Folder Structure for Larger Projects
myproject/ βββ apps/ β βββ blog/ β βββ users/ β βββ store/ βββ config/ # settings.py, urls.py, wsgi.py βββ static/ # CSS, JS, images βββ templates/ # Shared HTML templates βββ media/ # User-uploaded files βββ requirements.txt βββ manage.py
5. Views & URL Routing
Views are Python functions (or classes) that receive an HTTP request and return an HTTP response. URL routing maps URL patterns to views.
Function-Based Views (FBV)
# blog/views.py from django.shortcuts import render, get_object_or_404 from django.http import HttpResponse, JsonResponse from .models import Post def home(request): return HttpResponse("<h1>Welcome!</h1>") def post_list(request): posts = Post.objects.all().order_by('-created_at') return render(request, 'blog/post_list.html', {'posts': posts}) def post_detail(request, pk): post = get_object_or_404(Post, pk=pk) return render(request, 'blog/post_detail.html', {'post': post}) def api_posts(request): data = list(Post.objects.values('id', 'title', 'created_at')) return JsonResponse({'posts': data})
URL Configuration
# blog/urls.py (App-level) from django.urls import path from . import views app_name = 'blog' urlpatterns = [ path('', views.post_list, name='list'), path('<int:pk>/', views.post_detail, name='detail'), path('api/', views.api_posts, name='api'), ]
# myproject/urls.py (Project-level) from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('blog.urls')), path('users/', include('users.urls')), ]
URL Patterns Cheatsheet
| Pattern | Example URL | Captures |
|---|---|---|
| <int:pk> | /posts/5/ | Integer as pk=5 |
| <str:slug> | /posts/my-title/ | String as slug |
| <uuid:id> | /items/abc123.../ | UUID as id |
| <path:filepath> | /files/a/b/c.txt | Full path with slashes |
{% url 'blog:detail' pk=post.pk %} in templates instead of hardcoding URLs. If you change your URL pattern, templates update automatically!6. Templates & Django Template Language (DTL)
Django's template language lets you create dynamic HTML pages. It's intentionally simple β no arbitrary Python in templates.
Base Template (Inheritance)
<!-- templates/base.html -->
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}My Site{% endblock %}</title>
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
<nav>
<a href="{% url 'blog:list' %}">Blog</a>
</nav>
<main>
{% block content %}{% endblock %}
</main>
<footer>Β© 2025 My Site</footer>
</body>
</html><!-- templates/blog/post_list.html -->
{% extends 'base.html' %}
{% block title %}All Posts{% endblock %}
{% block content %}
<h1>All Posts</h1>
{% for post in posts %}
<div class="post-card">
<h2>{{ post.title }}</h2>
<p>{{ post.content|truncatewords:25 }}</p>
<small>{{ post.created_at|date:"F j, Y" }}</small>
<a href="{% url 'blog:detail' pk=post.pk %}">Read More</a>
</div>
{% empty %}
<p>No posts found.</p>
{% endfor %}
{% endblock %}Template Tags & Filters Cheatsheet
| Tag/Filter | Purpose | Example |
|---|---|---|
| {{ var }} | Print variable | {{ user.name }} |
| {% if %} | Conditionals | {% if user.is_authenticated %} |
| {% for %} | Loops | {% for item in list %} |
| {% url %} | Reverse URL | {% url 'blog:list' %} |
| {% static %} | Static file URL | {% static 'img/logo.png' %} |
| {% include %} | Include template | {% include 'nav.html' %} |
| |lower | Lowercase filter | {{ name|lower }} |
| |truncatewords:N | Trim text | {{ text|truncatewords:20 }} |
| |date:"Y-m-d" | Format date | {{ post.date|date:"F j, Y" }} |
| |default:"β" | Fallback value | {{ val|default:"None" }} |
| |safe | Mark HTML safe | {{ html_content|safe }} |
| |length | Count items | {{ list|length }} |
7. Django Models & ORM
Models define your database structure in Python classes. Django's ORM translates them to SQL automatically.
# blog/models.py from django.db import models from django.contrib.auth.models import User from django.utils.text import slugify class Category(models.Model): name = models.CharField(max_length=100) slug = models.SlugField(unique=True) class Meta: verbose_name_plural = 'Categories' ordering = ['name'] def __str__(self): return self.name class Post(models.Model): STATUS_DRAFT = 'draft' STATUS_PUBLISHED = 'published' STATUS_CHOICES = [ (STATUS_DRAFT, 'Draft'), (STATUS_PUBLISHED, 'Published'), ] author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts') category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True) title = models.CharField(max_length=200) slug = models.SlugField(unique=True) content = models.TextField() image = models.ImageField(upload_to='posts/', blank=True) status = models.CharField(max_length=10, choices=STATUS_CHOICES, default=STATUS_DRAFT) views = models.PositiveIntegerField(default=0) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ['-created_at'] def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.title) super().save(*args, **kwargs) def __str__(self): return self.title
Common Model Fields
| Field | Use Case | Options |
|---|---|---|
| CharField | Short text, names | max_length required |
| TextField | Long content, articles | blank=True |
| IntegerField | Numbers | default=0 |
| FloatField / DecimalField | Prices, coordinates | max_digits, decimal_places |
| BooleanField | True/False flags | default=False |
| DateField / DateTimeField | Dates, timestamps | auto_now, auto_now_add |
| ImageField / FileField | Uploads | upload_to='folder/' |
| EmailField | Email validation | max_length=254 |
| URLField | URL validation | |
| SlugField | SEO-friendly URLs | unique=True |
| ForeignKey | Many-to-one relationship | on_delete=CASCADE |
| ManyToManyField | Many-to-many relationship | through='...' |
| OneToOneField | Profile for User | on_delete=CASCADE |
ORM Query Examples
# Open shell: python manage.py shell from blog.models import Post, Category # CREATE post = Post.objects.create(title="Hello", content="World", ...) # READ ALL posts = Post.objects.all() # FILTER (WHERE clause) published = Post.objects.filter(status='published') recent = Post.objects.filter(created_at__year=2025) # GET single record (raises error if not found) post = Post.objects.get(pk=1) # EXCLUDE drafts = Post.objects.exclude(status='published') # ORDER posts = Post.objects.order_by('-created_at', 'title') # LIMIT (slicing) top5 = Post.objects.all()[:5] # UPDATE Post.objects.filter(status='draft').update(status='published') # DELETE Post.objects.get(pk=5).delete() # COUNT total = Post.objects.count() # SEARCH (case insensitive contains) results = Post.objects.filter(title__icontains='django') # RELATED objects (ForeignKey) user_posts = user.posts.all() # related_name='posts'
8. Migrations β Database Schema Management
Migrations track changes to your models and apply them to the database. Think of them as version control for your database schema.
# 1. After changing models.py, create migration files python manage.py makemigrations # 2. Apply migrations to the database python manage.py migrate # Check migration status python manage.py showmigrations # Show the SQL that a migration would run python manage.py sqlmigrate blog 0001 # Rollback to a specific migration python manage.py migrate blog 0002
makemigrations after changing models.py.9. Django Forms
Django Forms handle user input, validation, and rendering. ModelForm auto-generates forms from your models.
# blog/forms.py from django import forms from .models import Post class PostForm(forms.ModelForm): class Meta: model = Post fields = ['title', 'content', 'category', 'status'] widgets = { 'title': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Post title'}), 'content': forms.Textarea(attrs={'class': 'form-control', 'rows': 8}), } def clean_title(self): title = self.cleaned_data.get('title') if len(title) < 5: raise forms.ValidationError("Title must be at least 5 characters.") return title
# blog/views.py β Handling form submission from .forms import PostForm def create_post(request): if request.method == 'POST': form = PostForm(request.POST, request.FILES) if form.is_valid(): post = form.save(commit=False) post.author = request.user post.save() return redirect('blog:detail', pk=post.pk) else: form = PostForm() return render(request, 'blog/create_post.html', {'form': form})
<!-- templates/blog/create_post.html -->
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Publish Post</button>
</form>{% csrf_token %} in forms! Django rejects POST requests without it to prevent Cross-Site Request Forgery attacks.10. Django Admin Panel
Django's admin is a powerful built-in CMS. With just a few lines, you get a full CRUD interface for all your models.
# Create admin superuser python manage.py createsuperuser # Visit: http://127.0.0.1:8000/admin
# blog/admin.py from django.contrib import admin from .models import Post, Category @admin.register(Category) class CategoryAdmin(admin.ModelAdmin): prepopulated_fields = {'slug': ('name',)} @admin.register(Post) class PostAdmin(admin.ModelAdmin): list_display = ('title', 'author', 'status', 'views', 'created_at') list_filter = ('status', 'category') search_fields = ('title', 'content') prepopulated_fields = {'slug': ('title',)} date_hierarchy = 'created_at' ordering = ('-created_at',) raw_id_fields = ('author',) # Better for large user tables readonly_fields = ('created_at', 'updated_at')
list_editable to edit inline, actions to add bulk actions, inlines to nest related models on the same page.11. User Authentication (Login, Register, Logout)
Django provides a complete authentication system. Use Django's built-in views or build your own.
Using Django's Built-in Auth Views
# urls.py from django.contrib.auth import views as auth_views urlpatterns = [ path('login/', auth_views.LoginView.as_view(template_name='auth/login.html'), name='login'), path('logout/', auth_views.LogoutView.as_view(), name='logout'), path('password-reset/', auth_views.PasswordResetView.as_view(), name='password_reset'), ]
Custom Registration View
# users/views.py from django.contrib.auth.forms import UserCreationForm from django.contrib.auth import login from django.shortcuts import render, redirect def register(request): if request.method == 'POST': form = UserCreationForm(request.POST) if form.is_valid(): user = form.save() login(request, user) return redirect('home') else: form = UserCreationForm() return render(request, 'auth/register.html', {'form': form})
Protecting Views with @login_required
from django.contrib.auth.decorators import login_required @login_required(login_url='/login/') def dashboard(request): return render(request, 'dashboard.html') # In templates, check authentication # {% if user.is_authenticated %} ... {% endif %}
LOGIN_REDIRECT_URL = '/dashboard/' and LOGOUT_REDIRECT_URL = '/' in settings.py to control where users go after login/logout.12. Class-Based Views (CBV)
CBVs reduce boilerplate for common patterns like list, detail, create, update, delete.
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView from django.contrib.auth.mixins import LoginRequiredMixin from django.urls import reverse_lazy class PostListView(ListView): model = Post template_name = 'blog/post_list.html' context_object_name = 'posts' paginate_by = 10 def get_queryset(self): return Post.objects.filter(status='published').order_by('-created_at') class PostDetailView(DetailView): model = Post template_name = 'blog/post_detail.html' context_object_name = 'post' class PostCreateView(LoginRequiredMixin, CreateView): model = Post fields = ['title', 'content', 'category'] template_name = 'blog/post_form.html' def form_valid(self, form): form.instance.author = self.request.user return super().form_valid(form) class PostDeleteView(LoginRequiredMixin, DeleteView): model = Post success_url = reverse_lazy('blog:list')
LoginRequiredMixin to protect CBVs (instead of @login_required).13. Static & Media Files
Static files are CSS/JS/images bundled with your app. Media files are user-uploaded content.
# settings.py STATIC_URL = '/static/' STATICFILES_DIRS = [BASE_DIR / 'static'] STATIC_ROOT = BASE_DIR / 'staticfiles' # collectstatic output MEDIA_URL = '/media/' MEDIA_ROOT = BASE_DIR / 'media'
# urls.py β Serve media in development only from django.conf import settings from django.conf.urls.static import static urlpatterns = [ ... ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# Collect all static files for production
python manage.py collectstatic14. Middleware & Security
Middleware is a layer of processing that sits between the request and the response. Django's built-in middleware handles sessions, authentication, CSRF, and more.
# settings.py β Default middleware stack MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', # HTTPS, headers 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', # CSRF protection 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]
Production Security Settings
# settings.py β Production security DEBUG = False ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com'] SECURE_SSL_REDIRECT = True # Redirect HTTP to HTTPS SESSION_COOKIE_SECURE = True # Only send cookies over HTTPS CSRF_COOKIE_SECURE = True SECURE_HSTS_SECONDS = 31536000 SECURE_CONTENT_TYPE_NOSNIFF = True X_FRAME_OPTIONS = 'DENY'
Write a Custom Middleware
# myapp/middleware.py class LogRequestMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): print(f"Request: {request.method} {request.path}") response = self.get_response(request) print(f"Response: {response.status_code}") return response
15. REST API with Django REST Framework (DRF)
DRF is the gold standard for building REST APIs in Django. It provides serializers, viewsets, authentication, and browsable API.
pip install djangorestframework
# blog/serializers.py from rest_framework import serializers from .models import Post class PostSerializer(serializers.ModelSerializer): class Meta: model = Post fields = ['id', 'title', 'content', 'status', 'created_at'] read_only_fields = ['id', 'created_at']
# blog/views.py β DRF Viewsets from rest_framework import viewsets, permissions from .serializers import PostSerializer class PostViewSet(viewsets.ModelViewSet): queryset = Post.objects.all() serializer_class = PostSerializer permission_classes = [permissions.IsAuthenticatedOrReadOnly] def perform_create(self, serializer): serializer.save(author=self.request.user)
# urls.py β Register viewsets with router from rest_framework.routers import DefaultRouter from blog.views import PostViewSet router = DefaultRouter() router.register(r'posts', PostViewSet) urlpatterns = [ path('api/', include(router.urls)), path('api-auth/', include('rest_framework.urls')), ] # Endpoints: GET /api/posts/, POST /api/posts/, GET/PUT/DELETE /api/posts/1/
16. Caching & Performance
Caching stores expensive results so they don't need to be recalculated. Django supports per-view, template fragment, and low-level caching.
# settings.py β Use Redis for caching CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.redis.RedisCache', 'LOCATION': 'redis://127.0.0.1:6379/1', } }
# Per-view cache (15 minutes) from django.views.decorators.cache import cache_page @cache_page(60 * 15) def post_list(request): ... # Low-level cache API from django.core.cache import cache cache.set('popular_posts', posts, timeout=300) # Cache 5 mins data = cache.get('popular_posts') # Returns None if expired
Database Query Optimization
# Use select_related for ForeignKey (reduces SQL JOINs) posts = Post.objects.select_related('author', 'category').all() # Use prefetch_related for ManyToMany posts = Post.objects.prefetch_related('tags').all() # Use only() to load specific fields posts = Post.objects.only('title', 'created_at') # Use values() for dicts (no model overhead) data = Post.objects.values('id', 'title')
17. Celery β Background Tasks & Scheduling
Celery handles long-running tasks (email sending, image processing, report generation) in the background without blocking the HTTP request.
pip install celery redis
# myproject/celery.py import os from celery import Celery os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings') app = Celery('myproject') app.config_from_object('django.conf:settings', namespace='CELERY') app.autodiscover_tasks()
# blog/tasks.py from celery import shared_task from django.core.mail import send_mail @shared_task def send_welcome_email(user_email): send_mail( subject='Welcome to Our Site!', message='Thanks for joining us.', from_email='no-reply@mysite.com', recipient_list=[user_email], ) # In view β call task asynchronously send_welcome_email.delay(user.email)
celery -A myproject worker --loglevel=info. Use celery beat for periodic scheduled tasks (cron-like).18. Deployment to Production
Deploy Django to a Linux server (Ubuntu) with Gunicorn + Nginx + PostgreSQL.
Step-by-Step Deployment
Install dependencies on server
sudo apt update && sudo apt install python3-pip python3-venv nginx postgresql
Configure PostgreSQL
sudo -u postgres psql CREATE DATABASE mydb; CREATE USER myuser WITH PASSWORD 'password'; GRANT ALL PRIVILEGES ON DATABASE mydb TO myuser;
Update settings.py for production
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'mydb', 'USER': 'myuser',
'PASSWORD': 'password', 'HOST': 'localhost',
}
}Install Gunicorn & run
pip install gunicorn gunicorn --workers 3 myproject.wsgi:application
Configure Nginx
server {
listen 80;
server_name yourdomain.com;
location /static/ { root /var/www/myproject; }
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
}
}Quick Deployment to Railway / Render (Easiest)
pip install gunicorn whitenoise dj-database-url # Procfile (create in root) web: gunicorn myproject.wsgi # runtime.txt python-3.11.0
19. Summary, Tips & Next Steps
What You've Learned in This Guide
- β Django's MVT architecture and why it's used by millions
- β Setting up Python, virtual environments, and installing Django
- β Creating projects, apps, models, views, URLs, templates
- β Django ORM β querying, creating, updating, deleting records
- β Migrations for database schema management
- β Forms and form validation with ModelForm
- β Admin panel customization
- β User authentication β login, register, logout, decorators
- β Class-Based Views for clean, reusable code
- β Static and media file handling
- β Middleware and production security settings
- β REST API with Django REST Framework
- β Caching with Redis and query optimization
- β Background tasks with Celery
- β Production deployment with Gunicorn + Nginx + PostgreSQL
Essential Packages to Know
Suggested Projects to Build
CRUD posts, categories, tags, comments, user auth, admin, pagination, search.
Products, cart, orders, Stripe payments, inventory management, dashboard.
User profiles, follow system, posts, likes, notifications, real-time chat.
Multi-tenant, subscription billing, analytics, API backend for a React frontend.
π docs.djangoproject.com β Official docs (the best!)
π django-rest-framework.org β DRF documentation
π MDN Web Docs, Real Python, TestDriven.io for tutorials
Congratulations!
You now have a complete foundation in Django. Start building real projects β that's the fastest way to mastery. Every great app begins with a single python manage.py startproject.