C
h
i
L
L
u
.
.
.
Python Web Framework

The Complete Django Guide

Master Django — the high-level Python web framework that encourages rapid development and clean, pragmatic design with "batteries included."

Why Learn Django?

Django was released in 2005 by Adrian Holovaty and Simon Willison. Built with the philosophy of "Don't Repeat Yourself" (DRY) and "Convention over Configuration," Django ships with everything you need for full-stack web development — ORM, admin panel, authentication, forms, templating, and more.

Django's Object-Relational Mapper (ORM) lets you interact with databases using Python instead of SQL. Migrations track schema changes automatically. The built-in admin interface provides a production-ready management UI with zero code.

Instagram, Pinterest, Disqus, Mozilla, and the Washington Post all power their backends with Django — a testament to its scalability and reliability.

Quick start: pip install django && django-admin startproject mysite && python manage.py runserver

1. Setup & Project Structure

Creating a Django Project and App

A Django project contains one or more apps. Each app handles a specific feature. The manage.py script provides commands for running the server, migrations, and creating superusers.

# Create virtual environment and install Django
python -m venv venv
source venv/bin/activate      # Windows: venv\Scripts\activate
pip install django djangorestframework pillow

# Create a new project
django-admin startproject mysite .

# Create an app
python manage.py startapp blog

# Project structure:
# mysite/
#   manage.py
#   mysite/
#     __init__.py
#     settings.py        # configuration
#     urls.py            # root URL config
#     wsgi.py
#   blog/
#     migrations/        # database migrations
#     __init__.py
#     admin.py           # admin interface config
#     apps.py
#     models.py          # database models
#     views.py           # request handlers
#     urls.py            # app URL patterns
#     tests.py

# settings.py — register the app
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',      # DRF
    'blog',                # our app
]

# Run development server
python manage.py runserver    # http://127.0.0.1:8000

2. Models & ORM

Defining Models and Querying with ORM

Models are Python classes that map to database tables. Django's ORM provides a rich query API. Run makemigrations then migrate to apply changes to the database.

# 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, unique=True)
    slug = models.SlugField(unique=True)
    description = models.TextField(blank=True)

    class Meta:
        verbose_name_plural = 'categories'
        ordering = ['name']

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        self.slug = slugify(self.name)
        super().save(*args, **kwargs)

class Post(models.Model):
    STATUS_DRAFT = 'draft'
    STATUS_PUBLISHED = 'published'
    STATUS_CHOICES = [
        (STATUS_DRAFT, 'Draft'),
        (STATUS_PUBLISHED, 'Published'),
    ]

    title = models.CharField(max_length=200)
    slug = models.SlugField(max_length=200, unique=True)
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
    category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, related_name='posts')
    body = models.TextField()
    status = models.CharField(max_length=10, choices=STATUS_CHOICES, default=STATUS_DRAFT)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    published = models.DateTimeField(null=True, blank=True)

    class Meta:
        ordering = ['-created']
        indexes = [models.Index(fields=['status', 'published'])]

    def __str__(self):
        return self.title

# ORM queries
# Post.objects.all()
# Post.objects.filter(status='published').order_by('-published')
# Post.objects.select_related('author', 'category').filter(status='published')
# Post.objects.create(title='Hello', slug='hello', author=user, body='...')
# Post.objects.get(slug='hello-world')
# Post.objects.exclude(status='draft')
# Post.objects.filter(title__icontains='django').count()

3. Views & URLs

Function Views, Class Views, and URL Routing

Views process requests and return responses. Class-Based Views (CBV) reduce boilerplate for common patterns like list/detail/create. URL patterns map paths to views.

# blog/views.py
from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.views.generic import ListView, DetailView, CreateView
from django.urls import reverse_lazy
from .models import Post, Category

# Function-based view
def post_list(request):
    posts = Post.objects.filter(status='published').select_related('author', 'category')
    category_slug = request.GET.get('category')
    if category_slug:
        category = get_object_or_404(Category, slug=category_slug)
        posts = posts.filter(category=category)
    return render(request, 'blog/post_list.html', {'posts': posts})

def post_detail(request, slug):
    post = get_object_or_404(Post, slug=slug, status='published')
    return render(request, 'blog/post_detail.html', {'post': post})

# Class-based views
class PostListView(ListView):
    model = Post
    template_name = 'blog/post_list.html'
    context_object_name = 'posts'
    paginate_by = 10
    queryset = Post.objects.filter(status='published').select_related('author')

class PostCreateView(CreateView):
    model = Post
    fields = ['title', 'body', 'category', 'status']
    success_url = reverse_lazy('blog:post-list')

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)

# blog/urls.py
from django.urls import path
from . import views

app_name = 'blog'
urlpatterns = [
    path('', views.PostListView.as_view(), name='post-list'),
    path('<slug:slug>/', views.post_detail, name='post-detail'),
    path('new/', views.PostCreateView.as_view(), name='post-create'),
]

# mysite/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
    path('admin/', admin.site.urls),
    path('blog/', include('blog.urls')),
]

4. Templates & Forms

Django Template Language and Forms

Django's template language uses tags and filters for dynamic content. Django Forms handle validation, rendering, and CSRF protection automatically.

{# blog/templates/blog/base.html #}
<!DOCTYPE html>
<html>
<head>
  <title>{% block title %}My Blog{% endblock %}</title>
</head>
<body>
  <nav>
    <a href="{% url 'blog:post-list' %}">Home</a>
    {% if user.is_authenticated %}
      <a href="{% url 'blog:post-create' %}">New Post</a>
      <span>{{ user.username }}</span>
    {% else %}
      <a href="{% url 'login' %}">Sign In</a>
    {% endif %}
  </nav>
  {% block content %}{% endblock %}
</body>
</html>

{# blog/templates/blog/post_list.html #}
{% extends 'blog/base.html' %}
{% block content %}
  {% for post in posts %}
    <article>
      <h2><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h2>
      <p>By {{ post.author.get_full_name }} in {{ post.category.name }}</p>
      <p>{{ post.body|truncatewords:30 }}</p>
      <time>{{ post.created|date:"M d, Y" }}</time>
    </article>
  {% empty %}
    <p>No posts yet.</p>
  {% endfor %}
  {% if is_paginated %}
    {% if page_obj.has_previous %}<a href="?page={{ page_obj.previous_page_number }}">Prev</a>{% endif %}
    Page {{ page_obj.number }} of {{ page_obj.num_pages }}
    {% if page_obj.has_next %}<a href="?page={{ page_obj.next_page_number }}">Next</a>{% endif %}
  {% endif %}
{% endblock %}

5. Authentication & Permissions

Django's Built-in Auth System

Django ships with a complete authentication system — users, groups, permissions, sessions, and password hashing. Decorators and mixins protect views from unauthorized access.

# blog/views.py — protecting views
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin

@login_required
def create_post(request):
    # Only authenticated users can access
    pass

@permission_required('blog.add_post', raise_exception=True)
def admin_create(request):
    # Only users with blog.add_post permission
    pass

class PostUpdateView(LoginRequiredMixin, UpdateView):
    model = Post
    fields = ['title', 'body']
    # Redirects to login if not authenticated

# Custom user model (recommended)
# accounts/models.py
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    bio = models.TextField(blank=True)
    avatar = models.ImageField(upload_to='avatars/', blank=True)
    website = models.URLField(blank=True)

# settings.py
AUTH_USER_MODEL = 'accounts.User'

# Registration view
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import login

def register(request):
    if request.method == 'POST':
        form = UserCreationForm(request.POST)
        if form.is_valid():
            user = form.save()
            login(request, user)
            return redirect('blog:post-list')
    else:
        form = UserCreationForm()
    return render(request, 'accounts/register.html', {'form': form})

6. Django REST Framework

Building a REST API with DRF

Django REST Framework (DRF) provides powerful tools for building Web APIs — serializers, viewsets, routers, and authentication classes work together to create production-ready APIs quickly.

# blog/serializers.py
from rest_framework import serializers
from .models import Post, Category

class CategorySerializer(serializers.ModelSerializer):
    post_count = serializers.IntegerField(read_only=True)

    class Meta:
        model = Category
        fields = ['id', 'name', 'slug', 'post_count']

class PostSerializer(serializers.ModelSerializer):
    author_name = serializers.CharField(source='author.get_full_name', read_only=True)
    category = CategorySerializer(read_only=True)
    category_id = serializers.PrimaryKeyRelatedField(
        queryset=Category.objects.all(), source='category', write_only=True
    )

    class Meta:
        model = Post
        fields = ['id', 'title', 'slug', 'author_name', 'category', 'category_id',
                  'body', 'status', 'created', 'updated']
        read_only_fields = ['slug', 'created', 'updated']

# blog/api_views.py
from rest_framework import viewsets, permissions, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from django.db.models import Count

class PostViewSet(viewsets.ModelViewSet):
    serializer_class = PostSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]
    filter_backends = [filters.SearchFilter, filters.OrderingFilter]
    search_fields = ['title', 'body']
    ordering_fields = ['created', 'title']

    def get_queryset(self):
        qs = Post.objects.select_related('author', 'category')
        if self.request.user.is_authenticated:
            return qs.filter(author=self.request.user) | qs.filter(status='published')
        return qs.filter(status='published')

    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

    @action(detail=False, methods=['get'])
    def my_posts(self, request):
        posts = self.get_queryset().filter(author=request.user)
        serializer = self.get_serializer(posts, many=True)
        return Response(serializer.data)

# blog/urls.py — router
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('posts', PostViewSet, basename='post')
urlpatterns = router.urls
# GET /api/posts/         — list
# POST /api/posts/        — create
# GET /api/posts/{id}/    — retrieve
# PUT /api/posts/{id}/    — update
# DELETE /api/posts/{id}/ — destroy
# GET /api/posts/my_posts/ — custom action

Build Production Apps with Django!

Django's batteries-included approach means you can go from zero to a full-featured, production-ready web application incredibly fast. Its robust ecosystem — DRF, Celery, Channels — covers every use case.

Happy Coding with Django!