Complete Django Guide

    Learning Path: This guide is structured progressively, starting with fundamental concepts and advancing to expert-level topics. Each section builds upon previous knowledge.

    📚 Table of Contents

    🟢 Part 1: Beginner Level

    1. What is Django?
    2. Setting Up Your Development Environment
    3. Understanding Django Architecture
    4. Your First Django Project
    5. Creating Django Apps
    6. Working with URLs & Views
    7. Introduction to Templates
    8. Building Your First Feature

    🟡 Part 2: Intermediate Level

    1. Models & Database Design
    2. Advanced Template Techniques
    3. Working with Forms
    4. The Django Admin Interface
    5. User Authentication & Authorization
    6. Managing Static Files & Media
    7. Advanced ORM Queries
    8. Pagination & Filtering

    🔴 Part 3: Advanced Level

    1. Class-Based Views
    2. Custom Middleware
    3. Signals & Custom Managers
    4. Building REST APIs
    5. Testing Strategies
    6. Performance Optimization
    7. Security Best Practices
    8. Deployment & Scaling

    🎯 Quick Reference


    🟢 Part 1: Beginner Level

    Goal: Learn Django fundamentals and build your first web application


    1. What is Django? {#introduction}

    🎯 What You’ll Learn

    • What Django is and why it’s popular
    • When to use Django
    • Django’s core philosophy

    Understanding Django

    Django is a high-level Python web framework that helps you build web applications faster and with less code. Think of it as a toolkit that provides pre-built components for common web development tasks.

    🤔 Why Choose Django?

    Perfect for:

    • Blog platforms and content management systems
    • E-commerce websites
    • Social networks
    • Data-driven applications
    • REST APIs
    • Enterprise applications

    Not ideal for:

    • Simple static websites (use a static site generator)
    • Real-time applications like video chat (consider Django Channels or other frameworks)
    • Microservices requiring extreme flexibility (though Django can work)

    Key Features Explained

    FeatureWhat It MeansExample
    Batteries IncludedBuilt-in tools for common tasksUser authentication, admin panel, forms
    ORMWrite Python instead of SQLUser.objects.filter(age__gt=18) instead of raw SQL
    SecurityProtection by defaultCSRF protection, SQL injection prevention
    ScalableGrows with your appUsed by Instagram, Spotify, YouTube
    Fast DevelopmentLess boilerplate codeAdmin interface auto-generated

    Django’s Philosophy (What Makes it Different)

    # ❌ Bad: Repeating code (Violates DRY)
    def get_published_posts():
        return Post.objects.filter(status='published')
    
    def get_published_count():
        return Post.objects.filter(status='published').count()
    
    # ✅ Good: Don't Repeat Yourself (DRY)
    class PostManager(models.Manager):
        def published(self):
            return self.filter(status='published')
    
    # Now use: Post.objects.published() everywhere
    Python

    Core Principles:

    1. DRY (Don’t Repeat Yourself): Write code once, reuse everywhere
    2. Explicit over Implicit: Code should be clear and obvious
    3. Loose Coupling: Components work independently
    4. Convention over Configuration: Sensible defaults reduce setup

    🎓 Real-World Analogy

    Think of Django as a pre-furnished apartment vs building a house from scratch:

    • Furniture (admin panel, auth system) already there
    • Utilities (ORM, template engine) connected
    • Security (locks, alarms) pre-installed
    • You just move in and customize to your needs

    What You’ll Build in This Guide

    Beginner → A blog with posts, comments, and categories
    Intermediate → Add user accounts, media uploads, search
    Advanced → REST API, advanced features, deployment
    Python

    2. Setting Up Your Development Environment {#installation}

    🎯 What You’ll Learn

    • Installing Python and Django
    • Setting up a virtual environment
    • Verifying your installation
    • Installing essential tools

    Step-by-Step Installation

    Prerequisites Checklist

    • Python 3.8+ installed on your computer
    • pip (comes with Python)
    • A code editor (VS Code, PyCharm, or Sublime Text)
    • Basic Python knowledge (variables, functions, classes)

    Installation Steps

    Step 1: Verify Python Installation

    # Check if Python is installed
    python --version  # Should show 3.8 or higher
    
    # Check if pip is available
    pip --version
    Bash

    💡 Troubleshooting: If python doesn’t work, try python3. On Windows, you might need to use py.

    Step 2: Create a Project Directory

    # Create and navigate to your project folder
    mkdir django_projects
    cd django_projects
    Bash

    Step 3: Set Up Virtual Environment

    Virtual environments keep your project dependencies isolated.

    # Create virtual environment
    python -m venv blog_env
    
    # Activate it
    # On Windows:
    blog_env\Scripts\activate
    
    # On macOS/Linux:
    source blog_env/bin/activate
    
    # You should see (blog_env) in your terminal prompt
    Bash

    ⚠️ Important: Always activate your virtual environment before working on your project!

    Step 4: Install Django

    # Install Django (latest version)
    pip install django
    
    # Or install a specific version
    pip install django==4.2
    
    # Verify installation
    django-admin --version
    # Should output something like: 4.2.7
    Bash

    Step 5: Install Essential Tools (Optional but Recommended)

    Creating Your Project

    Step 1: Create the Project

    # Make sure your virtual environment is activated!
    # You should see (blog_env) in your terminal
    
    # Create project named 'myblog'
    django-admin startproject myblog
    
    # Navigate into it
    cd myblog
    Bash

    Step 2: Explore the Structure

    myblog/                     Project root directory
    
    ├── manage.py              Command-line tool (YOU'LL USE THIS A LOT!)
    
    └── myblog/               ← Project package (inner folder)
        ├── __init__.py       ← Makes this a Python package
        ├── settings.py       ← ⚙️ All configuration (IMPORTANT!)
        ├── urls.py           ← 🗺️ URL routing (IMPORTANT!)
        ├── asgi.py           ← For async deployment (ignore for now)
        └── wsgi.py           ← For deployment (ignore for now)
    Bash

    Understanding Key Files

    1. manage.py – Your Command Center

    # You'll use this for everything:
    python manage.py runserver      # Start development server
    python manage.py makemigrations # Create database changes
    python manage.py migrate        # Apply database changes
    python manage.py createsuperuser # Create admin user
    # ... and many more commands
    Bash

    Understanding Apps

    Remember:

    • Project = Your entire website
    • App = One specific feature
    E-commerce Project
    ├── products/     (app - manage products)
    ├── cart/         (app - shopping cart)
    ├── orders/       (app - order processing)
    └── accounts/     (app - user management)
    
    Blog Project
    ├── blog/         (app - posts and articles)
    ├── comments/     (app - comment system)
    └── users/        (app - user profiles)
    Bash

    Creating Your First App

    Step 1: Create the ‘blog’ App

    # From myblog/ directory (where manage.py is)
    python manage.py startapp blog
    Bash

    Step 2: Explore the App Structure

    blog/                           Your new app!
    
    ├── migrations/                Database version control
       └── __init__.py
    
    ├── __init__.py               Makes this a Python package
    ├── admin.py                  🎛️ Register models for admin
    ├── apps.py                   App configuration
    ├── models.py                 📦 Database models (IMPORTANT!)
    ├── tests.py                  Unit tests
    └── views.py                  🎮 View functions (IMPORTANT!)
    Bash

    Understanding App Files

    FilePurposeYou’ll Use It For
    models.pyDefine database structureCreating Post, Comment, Category models
    views.pyWrite view logicHandling requests, rendering pages
    admin.pyCustomize admin panelMaking models editable in admin
    tests.pyWrite testsTesting your code (important!)
    apps.pyApp configurationUsually don’t touch
    migrations/Database change historyAuto-generated, don’t edit

    Registering Your App

    Django needs to know about your app!

    Edit myblog/settings.py:

    INSTALLED_APPS = [
        # Built-in Django apps
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
    
        # Your apps
        'blog',  # ← Add this line!
    ]
    Python

    💡 Pro Tip: Add your apps at the end. Built-in apps should stay at the top.

    Complete Project Structure Now

    myblog/                         Project root
    
    ├── blog/                       Your blog app
       ├── migrations/
       ├── __init__.py
       ├── admin.py
       ├── apps.py
       ├── models.py
       ├── tests.py
       └── views.py
    
    ├── myblog/                     Project settings
       ├── __init__.py
       ├── settings.py
       ├── urls.py
       ├── asgi.py
       └── wsgi.py
    
    ├── db.sqlite3                 Database file
    └── manage.py                  Management commands
    Bash

    Quick Test

    Let’s verify the app is recognized:

    python manage.py check
    Bash

    You should see:

    System check identified no issues (0 silenced).
    Bash

    Success! Your app is ready.

    🎓 Quick Recap

    ✅ Created a ‘blog’ app using startapp
    ✅ Understood the app directory structure
    ✅ Registered the app in settings.py
    ✅ Verified with python manage.py check

    Next: Let’s create views and URLs to display pages!


    6. Working with URLs & Views {#urls-views}

    🎯 What You’ll Learn

    • Creating your first view
    • Setting up URL patterns
    • Rendering responses
    • Understanding the request/response cycle

    The View-URL Connection

    User visits URL  Django matches URL  Calls View  Returns Response
    Bash

    Your First View

    Step 1: Create a Simple View

    Edit blog/views.py:

    from django.http import HttpResponse
    
    def home(request):
        """
        Every view receives a request object
        Every view must return a response
        """
        return HttpResponse("<h1>Welcome to My Blog!</h1>")
    Python

    Let’s break it down:

    def home(request):
        # 'request' contains info about the user's request
        # - request.method: 'GET', 'POST', etc.
        # - request.user: Current user
        # - request.GET: Query parameters
    
        # HttpResponse sends HTML to the browser
        return HttpResponse("<h1>Welcome to My Blog!</h1>")
    Python

    Step 2: Create App URLs

    Create a new file blog/urls.py:

    from django.urls import path
    from . import views  # Import views from current app
    
    # App-specific URLs
    urlpatterns = [
        path('', views.home, name='home'),  # blog/ → home view
    ]
    Python

    Understanding URL Patterns:

    path('', views.home, name='home')
    #    │       │         │
    #    │       │         └─ Name (for reverse lookups)
    #    │       └─────────── Which view to call
    #    └─────────────────── URL path
    Python

    Step 3: Connect to Main URLs

    Edit myblog/urls.py:

    from django.contrib import admin
    from django.urls import path, include  # ← Add 'include'
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('', include('blog.urls')),  # ← Add this!
    ]
    Python

    What this does:

    http://localhost:8000/       → blog.urls → home view
    http://localhost:8000/admin/ → admin site
    Python

    Step 4: Test It!

    # Start server
    python manage.py runserver
    
    # Visit: http://127.0.0.1:8000/
    Python

    You should see: Welcome to My Blog!

    Creating Multiple Views

    Add more views to blog/views.py:

    from django.http import HttpResponse
    
    def home(request):
        return HttpResponse("<h1>Welcome to My Blog!</h1>")
    
    def about(request):
        return HttpResponse("<h1>About Me</h1><p>This is my blog about Django!</p>")
    
    def contact(request):
        return HttpResponse("<h1>Contact</h1><p>Email: myblog@example.com</p>")
    Python

    Update blog/urls.py:

    from django.urls import path
    from . import views
    
    urlpatterns = [
        path('', views.home, name='home'),
        path('about/', views.about, name='about'),
        path('contact/', views.contact, name='contact'),
    ]
    Python

    Test the URLs:

    http://127.0.0.1:8000/         → Home page
    http://127.0.0.1:8000/about/   → About page
    http://127.0.0.1:8000/contact/ → Contact page
    Python

    URL Patterns with Parameters

    Dynamic URLs with parameters:

    # blog/views.py
    def post_detail(request, post_id):
        return HttpResponse(f"<h1>Viewing Post #{post_id}</h1>")
    
    # blog/urls.py
    urlpatterns = [
        path('', views.home, name='home'),
        path('post/<int:post_id>/', views.post_detail, name='post_detail'),
        #          └── URL parameter (must be an integer)
    ]
    Python

    Try it:

    http://127.0.0.1:8000/post/1/   → Viewing Post #1
    http://127.0.0.1:8000/post/42/  → Viewing Post #42
    Python

    Different parameter types:

    path('post/<int:id>/', ...)       # Integer: 1, 2, 3
    path('post/<slug:slug>/', ...)    # Slug: my-first-post
    path('post/<str:title>/', ...)    # String: anything
    path('year/<int:year>/', ...)     # Year: 2024
    Python

    URL Naming and Reverse Lookup

    Why name URLs?

    # ❌ Bad: Hardcoded URL
    return HttpResponse('<a href="/about/">About</a>')
    
    # ✅ Good: Named URL (flexible!)
    from django.urls import reverse
    url = reverse('about')  # Returns '/about/'
    Python

    Using names in templates (you’ll learn this soon):

    <a href="{% url 'home' %}">Home</a>
    <a href="{% url 'about' %}">About</a>
    <a href="{% url 'post_detail' 123 %}">View Post 123</a>
    Python

    Request and Response Objects

    Understanding the request object:

    def my_view(request):
        # HTTP method
        method = request.method  # 'GET', 'POST', etc.
    
        # Query parameters (?search=django)
        search = request.GET.get('search', '')
    
        # Current user
        user = request.user
    
        # Request path
        path = request.path  # '/about/'
    
        return HttpResponse(f"Method: {method}, Search: {search}")
    Python

    Different response types:

    from django.http import HttpResponse, JsonResponse, HttpResponseRedirect
    
    # HTML response
    return HttpResponse("<h1>Hello</h1>")
    
    # JSON response
    return JsonResponse({'message': 'Hello', 'status': 'success'})
    
    # Redirect
    return HttpResponseRedirect('/home/')
    Python

    🎓 Practice Challenge

    Create these views and URLs:

    1. Blog list page at /posts/ showing “All Blog Posts”
    2. Archive page at /archive/2024/ showing the year
    3. Search page at /search/ showing a search term from ?q=django

    💡 Click to see solution

    # blog/views.py
    def post_list(request):
        return HttpResponse("<h1>All Blog Posts</h1>")
    
    def archive(request, year):
        return HttpResponse(f"<h1>Posts from {year}</h1>")
    
    def search(request):
        query = request.GET.get('q', 'No query')
        return HttpResponse(f"<h1>Search Results for: {query}</h1>")
    
    # blog/urls.py
    urlpatterns = [
        path('posts/', views.post_list, name='post_list'),
        path('archive/<int:year>/', views.archive, name='archive'),
        path('search/', views.search, name='search'),
    ]
    Python

    🎓 Key Takeaways

    ✅ Views are Python functions that handle requests
    ✅ Every view receives a request and returns a response
    ✅ URL patterns map URLs to views
    ✅ Use <type:name> for URL parameters
    ✅ Name your URLs for easy reference
    include() organizes URLs per app

    Next: Let’s make our pages look good with templates!


    7. Introduction to Templates {#templates-basic}

    🎯 What You’ll Learn

    • What templates are
    • Creating HTML templates
    • Template syntax basics
    • Rendering templates in views
    • Passing data to templates path(‘admin/’, admin.site.urls), # Admin panel at /admin/ Your URLs will go here ]

    Running Your First Server

    Step 1: Start the Server

    # From myblog/ directory (where manage.py is)
    python manage.py runserver
    Python

    You’ll see:

    Watching for file changes with StatReloader
    Performing system checks...
    
    System check identified no issues (0 silenced).
    
    You have 18 unapplied migration(s)...
    Run 'python manage.py migrate' to apply them.
    
    November 30, 2025 - 12:00:00
    Django version 4.2.7, using settings 'myblog.settings'
    Starting development server at http://127.0.0.1:8000/
    Quit the server with CTRL-BREAK.
    Python

    Step 2: View Your Site

    1. Open browser
    2. Go to: http://127.0.0.1:8000/ or http://localhost:8000/
    3. You should see a rocket ship! 🚀

    ![Django Welcome Page – “The install worked successfully! Congratulations!”]

    Step 3: Access Admin Panel

    Try: http://127.0.0.1:8000/admin/ (you’ll get an error – that’s OK! We’ll fix this soon)

    Common Server Commands

    # Default (runs on http://127.0.0.1:8000/)
    python manage.py runserver
    
    # Custom port
    python manage.py runserver 8080
    # Access at: http://127.0.0.1:8080/
    
    # Accessible from other devices on network
    python manage.py runserver 0.0.0.0:8000
    
    # Stop server: Press CTRL+C
    Python

    Making Your First Change

    Let’s customize the timezone and language!

    Edit myblog/settings.py:

    # Find these lines and change them:
    
    LANGUAGE_CODE = 'en-us'  # Change to your language
    # Examples: 'es' (Spanish), 'fr' (French), 'de' (German)
    
    TIME_ZONE = 'America/New_York'  # Change to your timezone
    # Examples: 'Europe/London', 'Asia/Tokyo', 'Australia/Sydney'
    # Full list: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
    Python

    Save and refresh your browser – changes apply automatically! (server auto-reloads)

    Initial Database Setup

    # Apply built-in migrations
    python manage.py migrate
    Python

    You’ll see:

    Operations to perform:
      Apply all migrations: admin, auth, contenttypes, sessions
    Running migrations:
      Applying contenttypes.0001_initial... OK
      Applying auth.0001_initial... OK
      ...
    Python

    This creates a db.sqlite3 file (your database).

    🎓 What You Just Did

    ✅ Created a Django project
    ✅ Understood the project structure
    ✅ Ran the development server
    ✅ Made configuration changes
    ✅ Set up the initial database

    Next: Let’s create your first app!


    5. Creating Django Apps {#apps}

    🎯 What You’ll Learn

    • Difference between projects and apps
    • Creating your first app
    • Registering apps
    • App structure explaineded
    • Request/response flow
    • Difference between project and app

    The MTV Pattern (Model-Template-View)

    Django uses MTV, which is similar to the popular MVC pattern but with different naming:

    Traditional MVC          Django MTV
    ├── Model               ├── Model        (Data & Business Logic)
    ├── View                ├── Template     (Presentation/HTML)
    └── Controller          └── View         (Logic/Controller)
    Python

    Visual Breakdown

    ┌─────────────────────────────────────────────────────┐
    │  User Types: www.myblog.com/posts/
    └────────────────────┬────────────────────────────────┘
    
    
    ┌────────────────────────────────────────────────────┐
    1. URL Dispatcher (urls.py)
    "Which view should handle this?"
    /posts/ → post_list view                       │
    └────────────────┬───────────────────────────────────┘
    
    
    ┌────────────────────────────────────────────────────┐
    2. View (views.py)
    "Get the data and decide what to show"
    - Fetch posts from database                    │
    - Process business logic                       │
    └────────────────┬───────────────────────────────────┘
    
    
    ┌────────────────────────────────────────────────────┐
    3. Model (models.py)
    "Interact with database"
    │     Post.objects.all() → Returns all posts         │
    └────────────────┬───────────────────────────────────┘
    
    
    ┌────────────────────────────────────────────────────┐
    4. Template (template.html)
    "Display the data as HTML"
    <h1>{{ post.title }}</h1>
    └────────────────┬───────────────────────────────────┘
    
    
    ┌────────────────────────────────────────────────────┐
    │  User Sees: Beautiful blog page with posts         │
    └────────────────────────────────────────────────────┘
    Python

    Component Deep Dive

    📦 Model (M) – Your Data Structure

    What it does: Defines how data is stored and retrieved

    # models.py - Defines a blog post
    class Post(models.Model):
        title = models.CharField(max_length=200)
        content = models.TextField()
        created_at = models.DateTimeField(auto_now_add=True)
    
        # This is your database table structure!
    Python

    Think of it as: A spreadsheet blueprint

    • title column: holds text (max 200 chars)
    • content column: holds long text
    • created_at column: holds date/time

    📄 Template (T) – Your HTML

    What it does: Controls how data appears to users

    <!-- post_list.html -->
    <h1>My Blog</h1>
    {% for post in posts %}
        <article>
            <h2>{{ post.title }}</h2>
            <p>{{ post.content }}</p>
            <small>{{ post.created_at }}</small>
        </article>
    {% endfor %}
    Python

    Think of it as: The visual design/layout

    🎮 View (V) – Your Logic Controller

    What it does: Connects models and templates

    # views.py
    def post_list(request):
        posts = Post.objects.all()  # Get data from Model
        return render(request, 'post_list.html', {'posts': posts})
        # Send data to Template
    Python

    Think of it as: The middleman coordinating everything

    Project vs App: What’s the Difference?

    Project (myblog)              App (blog)
    ├── The entire website        ├── One feature/module
    ├── Contains settings         ├── Posts functionality
    ├── Main URL routing         ├── Has own models, views
    └── Can have many apps       └── Reusable in other projects
    
    Example:
    myblog_project/
        ├── blog/          (app for posts)
        ├── accounts/      (app for users)
        └── comments/      (app for comments)
    Python

    Real-world analogy:

    • Project = A shopping mall
    • Apps = Individual stores (electronics, clothing, food court)

    Complete Request Flow Example

    # 1. URL (urls.py)
    path('posts/', views.post_list, name='post_list')
    
    # 2. View (views.py)
    def post_list(request):
        posts = Post.objects.filter(published=True)  # 3. Model
        return render(request, 'posts.html', {'posts': posts})  # 4. Template
    Python
    <!-- 4. Template (posts.html) -->
    {% for post in posts %}
        <h2>{{ post.title }}</h2>
    {% endfor %}
    Python

    🎓 Key Takeaways

    Model = Database structure (what data to store)
    Template = HTML presentation (how to display)
    View = Business logic (what to show and when)
    URL = Routes traffic to the right view
    Project = Your entire website
    App = A specific feature/module


    4. Your First Django Project {#first-project}

    🎯 What You’ll Learn

    • Creating a Django project
    • Understanding the project structure
    • Running the development server
    • Making your first change

    Django follows the MTV (Model-Template-View) pattern, similar to MVC:

    Model (M):

    • Defines data structure
    • Database schema
    • Business logic
    • ORM for database operations

    Template (T):

    • Presentation layer
    • HTML with Django template language
    • Renders dynamic content

    View (V):

    • Business logic
    • Processes requests
    • Returns responses
    • Interacts with models and templates

    URL Dispatcher:

    • Maps URLs to views
    • Routes incoming requests

    Flow: URL → View → Model → Template → Response


    4. Creating Your First Django Project {#first-project}

    Start a New Project:

    # Create a new Django project
    django-admin startproject myproject
    
    # Project structure:
    myproject/
        manage.py
        myproject/
            __init__.py
            settings.py
            urls.py
            asgi.py
            wsgi.py
    Python

    Key Files:

    • manage.py: Command-line utility for project management
    • settings.py: Configuration settings
    • urls.py: URL declarations
    • wsgi.py: Web Server Gateway Interface entry point
    • asgi.py: Asynchronous Server Gateway Interface entry point

    Run Development Server:

    cd myproject
    python manage.py runserver
    
    # Run on specific port
    python manage.py runserver 8080
    
    # Run on specific IP and port
    python manage.py runserver 0.0.0.0:8000
    Python

    Visit http://127.0.0.1:8000/ to see the welcome page.


    5. Django Apps {#apps}

    Apps are modular components that perform specific functions. A project can have multiple apps.

    Create an App:

    python manage.py startapp blog
    
    # App structure:
    blog/
        __init__.py
        admin.py
        apps.py
        models.py
        tests.py
        views.py
        migrations/
            __init__.py
    Python

    Register the App:

    In myproject/settings.py:

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'blog',  # Add your app
    ]
    Python

    6. Models & Databases {#models}

    What Are Models?

    Models define your database structure using Python classes instead of SQL.

    # Instead of writing SQL:
    CREATE TABLE blog_post (
        id INTEGER PRIMARY KEY,
        title VARCHAR(200),
        content TEXT
    );
    
    # You write Python:
    class Post(models.Model):
        title = models.CharField(max_length=200)
        content = models.TextField()
    Python

    Creating Your Blog Models

    Edit blog/models.py:

    from django.db import models
    from django.contrib.auth.models import User
    from django.utils import timezone
    
    class Category(models.Model):
        """Categories for organizing posts"""
        name = models.CharField(max_length=100)
        slug = models.SlugField(unique=True)
        description = models.TextField(blank=True)
    
        class Meta:
            verbose_name_plural = "Categories"  # Plural form in admin
            ordering = ['name']  # Order alphabetically
    
        def __str__(self):
            return self.name  # How it appears in admin
    
    
    class Post(models.Model):
        """Main blog post model"""
        STATUS_CHOICES = [
            ('draft', 'Draft'),
            ('published', 'Published'),
        ]
    
        # Basic fields
        title = models.CharField(max_length=200)
        slug = models.SlugField(unique=True, max_length=200)
        content = models.TextField()
        excerpt = models.TextField(max_length=300, blank=True)
    
        # Relationships
        author = models.ForeignKey(
            User,
            on_delete=models.CASCADE,  # Delete posts when user deleted
            related_name='posts'  # Access via user.posts.all()
        )
        category = models.ForeignKey(
            Category,
            on_delete=models.SET_NULL,  # Keep post when category deleted
            null=True,
            related_name='posts'
        )
    
        # Dates
        created_at = models.DateTimeField(auto_now_add=True)
        updated_at = models.DateTimeField(auto_now=True)
        published_at = models.DateTimeField(default=timezone.now)
    
        # Other fields
        status = models.CharField(
            max_length=10,
            choices=STATUS_CHOICES,
            default='draft'
        )
        featured_image = models.ImageField(
            upload_to='posts/%Y/%m/%d/',  # Organize by date
            blank=True
        )
        views = models.PositiveIntegerField(default=0)
    
        class Meta:
            ordering = ['-published_at']  # Newest first
            indexes = [
                models.Index(fields=['-published_at']),  # Speed up queries
            ]
    
        def __str__(self):
            return self.title
    
    
    class Comment(models.Model):
        """Comments on blog posts"""
        post = models.ForeignKey(
            Post,
            on_delete=models.CASCADE,  # Delete comments with post
            related_name='comments'
        )
        name = models.CharField(max_length=100)
        email = models.EmailField()
        content = models.TextField()
        created_at = models.DateTimeField(auto_now_add=True)
        active = models.BooleanField(default=True)  # For moderation
    
        class Meta:
            ordering = ['created_at']  # Oldest first
    
        def __str__(self):
            return f'Comment by {self.name} on {self.post}'
    Python

    Understanding Field Types:

    • CharField: Short text
    • TextField: Long text
    • IntegerField: Integers
    • DateField/DateTimeField: Dates
    • EmailField: Email validation
    • URLField: URL validation
    • BooleanField: True/False
    • FileField/ImageField: File uploads
    • ForeignKey: One-to-many relationship
    • ManyToManyField: Many-to-many relationship
    • OneToOneField: One-to-one relationship

    Field Options:

    • null=True: Allow NULL in database
    • blank=True: Allow empty in forms
    • default: Default value
    • unique=True: Unique constraint
    • choices: Limited options
    • max_length: Maximum length

    Make Migrations:

    # Create migration files
    python manage.py makemigrations
    
    # Apply migrations
    python manage.py migrate
    
    # View migration SQL
    python manage.py sqlmigrate blog 0001
    
    # Show migrations
    python manage.py showmigrations
    Python

    7. Views {#views}

    Views handle the logic of your application and return responses.

    Function-Based Views (FBV):

    In blog/views.py:

    from django.shortcuts import render, get_object_or_404, redirect
    from django.http import HttpResponse, JsonResponse, Http404
    from django.core.paginator import Paginator
    from django.db.models import Q
    from .models import Post, Category, Comment
    from .forms import CommentForm
    
    def post_list(request):
        """Display list of published posts"""
        posts = Post.objects.filter(status='published').select_related('author', 'category')
    
        # Pagination
        paginator = Paginator(posts, 10)  # 10 posts per page
        page_number = request.GET.get('page')
        page_obj = paginator.get_page(page_number)
    
        context = {
            'page_obj': page_obj,
            'posts': page_obj,
        }
        return render(request, 'blog/post_list.html', context)
    
    
    def post_detail(request, slug):
        """Display single post"""
        post = get_object_or_404(Post, slug=slug, status='published')
    
        # Increment views
        post.views += 1
        post.save(update_fields=['views'])
    
        # Get comments
        comments = post.comments.filter(active=True)
    
        # Handle comment form
        if request.method == 'POST':
            form = CommentForm(request.POST)
            if form.is_valid():
                comment = form.save(commit=False)
                comment.post = post
                comment.save()
                return redirect('post_detail', slug=post.slug)
        else:
            form = CommentForm()
    
        context = {
            'post': post,
            'comments': comments,
            'form': form,
        }
        return render(request, 'blog/post_detail.html', context)
    
    
    def category_posts(request, slug):
        """Display posts by category"""
        category = get_object_or_404(Category, slug=slug)
        posts = Post.objects.filter(category=category, status='published')
    
        context = {
            'category': category,
            'posts': posts,
        }
        return render(request, 'blog/category_posts.html', context)
    
    
    def search(request):
        """Search posts"""
        query = request.GET.get('q', '')
        posts = Post.objects.none()
    
        if query:
            posts = Post.objects.filter(
                Q(title__icontains=query) | 
                Q(content__icontains=query),
                status='published'
            )
    
        context = {
            'posts': posts,
            'query': query,
        }
        return render(request, 'blog/search.html', context)
    Python

    Common Response Types:

    from django.http import HttpResponse, JsonResponse, HttpResponseRedirect
    from django.shortcuts import render, redirect
    
    # HTML response
    return render(request, 'template.html', context)
    
    # Plain text
    return HttpResponse("Hello, World!")
    
    # JSON response
    return JsonResponse({'key': 'value'})
    
    # Redirect
    return redirect('view_name')
    return redirect('/path/to/page/')
    Python

    8. Templates {#templates}

    Templates render HTML with dynamic content using Django Template Language (DTL).

    Template Structure:

    Create blog/templates/blog/ directory:

    blog/
        templates/
            blog/
                base.html
                post_list.html
                post_detail.html
    Python

    Base Template (base.html):

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>{% block title %}My Blog{% endblock %}</title>
        {% load static %}
        <link rel="stylesheet" href="{% static 'css/style.css' %}">
    </head>
    <body>
        <header>
            <nav>
                <a href="{% url 'post_list' %}">Home</a>
                <a href="{% url 'about' %}">About</a>
                <form method="get" action="{% url 'search' %}">
                    <input type="text" name="q" placeholder="Search...">
                    <button type="submit">Search</button>
                </form>
            </nav>
        </header>
    
        <main>
            {% block content %}
            {% endblock %}
        </main>
    
        <footer>
            <p>© {% now "Y" %} My Blog. All rights reserved.</p>
        </footer>
    
        {% block extra_js %}
        {% endblock %}
    </body>
    </html>
    Python

    Post List Template (post_list.html):

    {% extends 'blog/base.html' %}
    
    {% block title %}Blog Posts{% endblock %}
    
    {% block content %}
    <h1>Latest Posts</h1>
    
    {% if posts %}
        <div class="post-grid">
            {% for post in posts %}
            <article class="post-card">
                {% if post.featured_image %}
                <img src="{{ post.featured_image.url }}" alt="{{ post.title }}">
                {% endif %}
    
                <h2><a href="{% url 'post_detail' post.slug %}">{{ post.title }}</a></h2>
    
                <div class="post-meta">
                    <span>By {{ post.author.username }}</span>
                    <span>{{ post.published_at|date:"F d, Y" }}</span>
                    <span>{{ post.views }} views</span>
                </div>
    
                <p>{{ post.excerpt|truncatewords:30 }}</p>
    
                {% if post.category %}
                <span class="category">{{ post.category.name }}</span>
                {% endif %}
            </article>
            {% endfor %}
        </div>
    
        <!-- Pagination -->
        {% if page_obj.has_other_pages %}
        <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>
        {% endif %}
    {% else %}
        <p>No posts available.</p>
    {% endif %}
    {% endblock %}
    Python

    Post Detail Template (post_detail.html):

    {% extends 'blog/base.html' %}
    
    {% block title %}{{ post.title }}{% endblock %}
    
    {% block content %}
    <article class="post-detail">
        <h1>{{ post.title }}</h1>
    
        <div class="post-meta">
            <span>By {{ post.author.get_full_name|default:post.author.username }}</span>
            <span>{{ post.published_at|date:"F d, Y" }}</span>
            <span>{{ post.views }} views</span>
        </div>
    
        {% if post.featured_image %}
        <img src="{{ post.featured_image.url }}" alt="{{ post.title }}">
        {% endif %}
    
        <div class="post-content">
            {{ post.content|linebreaks }}
        </div>
    
        <!-- Comments Section -->
        <section class="comments">
            <h2>Comments ({{ comments.count }})</h2>
    
            {% for comment in comments %}
            <div class="comment">
                <strong>{{ comment.name }}</strong>
                <span>{{ comment.created_at|timesince }} ago</span>
                <p>{{ comment.content }}</p>
            </div>
            {% empty %}
            <p>No comments yet.</p>
            {% endfor %}
    
            <!-- Comment Form -->
            <h3>Leave a Comment</h3>
            <form method="post">
                {% csrf_token %}
                {{ form.as_p }}
                <button type="submit">Submit</button>
            </form>
        </section>
    </article>
    {% endblock %}
    Python

    Template Tags & Filters:

    {# Variables #}
    {{ variable }}
    {{ user.username }}
    {{ post.title|upper }}
    
    {# Filters #}
    {{ text|lower }}
    {{ text|truncatewords:30 }}
    {{ date|date:"Y-m-d" }}
    {{ value|default:"N/A" }}
    {{ html|safe }}
    
    {# Tags #}
    {% if condition %}
        ...
    {% elif other_condition %}
        ...
    {% else %}
        ...
    {% endif %}
    
    {% for item in items %}
        {{ forloop.counter }}. {{ item }}
    {% empty %}
        No items
    {% endfor %}
    
    {% url 'view_name' arg1 arg2 %}
    {% static 'path/to/file.css' %}
    
    {# Comments #}
    {# This is a comment #}
    {% comment %}
    Multi-line comment
    {% endcomment %}
    
    {# Include other templates #}
    {% include 'partials/header.html' %}
    
    {# Template inheritance #}
    {% extends 'base.html' %}
    {% block content %}...{% endblock %}
    Python

    9. URLs & Routing {#urls}

    URLs map web addresses to views.

    Project URLs (myproject/urls.py):

    from django.contrib import admin
    from django.urls import path, include
    from django.conf import settings
    from django.conf.urls.static import static
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('', include('blog.urls')),
    ]
    
    # Serve media files in development
    if settings.DEBUG:
        urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
        urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
    Python

    App URLs (blog/urls.py):

    from django.urls import path
    from . import views
    
    app_name = 'blog'
    
    urlpatterns = [
        path('', views.post_list, name='post_list'),
        path('post/<slug:slug>/', views.post_detail, name='post_detail'),
        path('category/<slug:slug>/', views.category_posts, name='category_posts'),
        path('search/', views.search, name='search'),
    ]
    Python

    URL Patterns:

    from django.urls import path, re_path
    
    # Basic path
    path('about/', views.about, name='about'),
    
    # With parameters
    path('post/<int:id>/', views.post_detail, name='post_detail'),
    path('post/<slug:slug>/', views.post_detail, name='post_detail'),
    path('year/<int:year>/', views.year_archive, name='year_archive'),
    
    # Regular expressions
    re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
    
    # Path converters:
    # - str: Matches any non-empty string (excluding '/')
    # - int: Matches zero or any positive integer
    # - slug: Matches ASCII letters, numbers, hyphens, and underscores
    # - uuid: Matches a UUID
    # - path: Matches any non-empty string (including '/')
    Python

    10. Forms {#forms}

    Forms handle user input validation and processing.

    Django Forms:

    In blog/forms.py:

    from django import forms
    from django.core.exceptions import ValidationError
    from .models import Comment, Post
    
    class CommentForm(forms.ModelForm):
        class Meta:
            model = Comment
            fields = ['name', 'email', 'content']
            widgets = {
                'name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Your Name'}),
                'email': forms.EmailInput(attrs={'class': 'form-control', 'placeholder': 'Your Email'}),
                'content': forms.Textarea(attrs={'class': 'form-control', 'placeholder': 'Your Comment', 'rows': 4}),
            }
            labels = {
                'name': 'Name',
                'email': 'Email Address',
                'content': 'Comment',
            }
    
        def clean_content(self):
            content = self.cleaned_data.get('content')
            if len(content) < 10:
                raise ValidationError('Comment must be at least 10 characters long.')
            return content
    
    
    class PostForm(forms.ModelForm):
        class Meta:
            model = Post
            fields = ['title', 'slug', 'category', 'content', 'excerpt', 'status', 'featured_image']
            widgets = {
                'content': forms.Textarea(attrs={'rows': 10}),
                'excerpt': forms.Textarea(attrs={'rows': 3}),
            }
    
    
    class ContactForm(forms.Form):
        name = forms.CharField(max_length=100, required=True)
        email = forms.EmailField(required=True)
        subject = forms.CharField(max_length=200, required=True)
        message = forms.CharField(widget=forms.Textarea, required=True)
    
        def clean_email(self):
            email = self.cleaned_data.get('email')
            if not email.endswith('@example.com'):
                raise ValidationError('Please use your company email.')
            return email
    
        def clean(self):
            cleaned_data = super().clean()
            # Cross-field validation
            return cleaned_data
    Python

    Using Forms in Views:

    from django.shortcuts import render, redirect
    from .forms import ContactForm
    
    def contact(request):
        if request.method == 'POST':
            form = ContactForm(request.POST)
            if form.is_valid():
                # Process the data
                name = form.cleaned_data['name']
                email = form.cleaned_data['email']
                subject = form.cleaned_data['subject']
                message = form.cleaned_data['message']
    
                # Send email or save to database
                # send_mail(subject, message, email, ['admin@example.com'])
    
                return redirect('success')
        else:
            form = ContactForm()
    
        return render(request, 'blog/contact.html', {'form': form})
    Python

    Rendering Forms in Templates:

    <form method="post" enctype="multipart/form-data">
        {% csrf_token %}
    
        {# Render entire form #}
        {{ form.as_p }}
    
        {# Or render manually #}
        {% for field in form %}
        <div class="form-group">
            {{ field.label_tag }}
            {{ field }}
            {% if field.errors %}
            <div class="error">{{ field.errors }}</div>
            {% endif %}
        </div>
        {% endfor %}
    
        {# Non-field errors #}
        {% if form.non_field_errors %}
        <div class="error">{{ form.non_field_errors }}</div>
        {% endif %}
    
        <button type="submit">Submit</button>
    </form>
    Python

    11. Admin Interface {#admin}

    Django provides a powerful automatic admin interface.

    Register Models (blog/admin.py):

    from django.contrib import admin
    from .models import Post, Category, Comment
    
    @admin.register(Category)
    class CategoryAdmin(admin.ModelAdmin):
        list_display = ['name', 'slug']
        prepopulated_fields = {'slug': ('name',)}
        search_fields = ['name']
    
    
    @admin.register(Post)
    class PostAdmin(admin.ModelAdmin):
        list_display = ['title', 'author', 'category', 'status', 'published_at', 'views']
        list_filter = ['status', 'created_at', 'published_at', 'category']
        search_fields = ['title', 'content']
        prepopulated_fields = {'slug': ('title',)}
        raw_id_fields = ['author']
        date_hierarchy = 'published_at'
        ordering = ['-published_at']
        list_editable = ['status']
        list_per_page = 20
    
        fieldsets = (
            ('Basic Information', {
                'fields': ('title', 'slug', 'author', 'category')
            }),
            ('Content', {
                'fields': ('content', 'excerpt', 'featured_image')
            }),
            ('Publication', {
                'fields': ('status', 'published_at')
            }),
        )
    
        def get_queryset(self, request):
            qs = super().get_queryset(request)
            return qs.select_related('author', 'category')
    
    
    @admin.register(Comment)
    class CommentAdmin(admin.ModelAdmin):
        list_display = ['name', 'email', 'post', 'created_at', 'active']
        list_filter = ['active', 'created_at']
        search_fields = ['name', 'email', 'content']
        actions = ['approve_comments']
    
        def approve_comments(self, request, queryset):
            queryset.update(active=True)
        approve_comments.short_description = "Approve selected comments"
    Python

    Create Superuser:

    python manage.py createsuperuser
    Python

    Customize Admin Site:

    # In admin.py
    admin.site.site_header = "My Blog Administration"
    admin.site.site_title = "My Blog Admin"
    admin.site.index_title = "Welcome to My Blog Administration"
    Python

    12. Authentication & Authorization {#auth}

    Django provides built-in authentication system.

    Login/Logout Views:

    In blog/views.py:

    from django.contrib.auth import login, logout, authenticate
    from django.contrib.auth.decorators import login_required
    from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
    from django.contrib.auth.models import User
    
    def register(request):
        if request.method == 'POST':
            form = UserCreationForm(request.POST)
            if form.is_valid():
                user = form.save()
                login(request, user)
                return redirect('post_list')
        else:
            form = UserCreationForm()
        return render(request, 'registration/register.html', {'form': form})
    
    
    def user_login(request):
        if request.method == 'POST':
            form = AuthenticationForm(request, data=request.POST)
            if form.is_valid():
                username = form.cleaned_data.get('username')
                password = form.cleaned_data.get('password')
                user = authenticate(username=username, password=password)
                if user is not None:
                    login(request, user)
                    return redirect('post_list')
        else:
            form = AuthenticationForm()
        return render(request, 'registration/login.html', {'form': form})
    
    
    def user_logout(request):
        logout(request)
        return redirect('post_list')
    
    
    @login_required
    def profile(request):
        return render(request, 'registration/profile.html')
    Python

    Custom User Model:

    In myproject/settings.py:

    AUTH_USER_MODEL = 'blog.CustomUser'
    Python

    In blog/models.py:

    from django.contrib.auth.models import AbstractUser
    
    class CustomUser(AbstractUser):
        bio = models.TextField(blank=True)
        avatar = models.ImageField(upload_to='avatars/', blank=True)
        website = models.URLField(blank=True)
    
        def __str__(self):
            return self.username
    Python

    Permission Decorators:

    from django.contrib.auth.decorators import login_required, permission_required, user_passes_test
    
    @login_required
    def my_view(request):
        pass
    
    @permission_required('blog.add_post')
    def create_post(request):
        pass
    
    def is_author(user):
        return user.groups.filter(name='Authors').exists()
    
    @user_passes_test(is_author)
    def author_only_view(request):
        pass
    Python

    URL Configuration:

    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'),
        path('password_change/', auth_views.PasswordChangeView.as_view(), name='password_change'),
        path('password_reset/', auth_views.PasswordResetView.as_view(), name='password_reset'),
    ]
    Python

    13. Static Files & Media {#static}

    Settings Configuration:

    In myproject/settings.py:

    import os
    
    # Static files (CSS, JavaScript, Images)
    STATIC_URL = '/static/'
    STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
    STATICFILES_DIRS = [
        os.path.join(BASE_DIR, 'static'),
    ]
    
    # Media files (User uploads)
    MEDIA_URL = '/media/'
    MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
    Python

    Directory Structure:

    myproject/
        static/
            css/
                style.css
            js/
                script.js
            images/
                logo.png
        media/
            posts/
                2024/01/01/
                    image.jpg
    Python

    Using in Templates:

    {% load static %}
    
    <link rel="stylesheet" href="{% static 'css/style.css' %}">
    <script src="{% static 'js/script.js' %}"></script>
    <img src="{% static 'images/logo.png' %}" alt="Logo">
    
    <!-- Media files -->
    <img src="{{ post.featured_image.url }}" alt="{{ post.title }}">
    Python

    Collect Static Files:

    python manage.py collectstatic
    Python

    14. Django ORM (Object-Relational Mapping) {#orm}

    The ORM allows you to interact with databases using Python code.

    QuerySets:

    from blog.models import Post
    
    # Get all objects
    posts = Post.objects.all()
    
    # Filter
    published_posts = Post.objects.filter(status='published')
    recent_posts = Post.objects.filter(published_at__gte='2024-01-01')
    
    # Exclude
    draft_posts = Post.objects.exclude(status='published')
    
    # Get single object
    post = Post.objects.get(id=1)
    post = Post.objects.get(slug='my-post')
    
    # Get or 404
    from django.shortcuts import get_object_or_404
    post = get_object_or_404(Post, slug='my-post')
    
    # Create
    post = Post.objects.create(
        title='New Post',
        slug='new-post',
        author=request.user,
        content='Content here'
    )
    
    # Update
    Post.objects.filter(id=1).update(views=100)
    post.title = 'Updated Title'
    post.save()
    
    # Delete
    post.delete()
    Post.objects.filter(status='draft').delete()
    
    # Count
    count = Post.objects.count()
    
    # Exists
    exists = Post.objects.filter(slug='my-post').exists()
    
    # Order by
    posts = Post.objects.order_by('-published_at')
    posts = Post.objects.order_by('title', '-created_at')
    
    # Limit
    posts = Post.objects.all()[:5]  # First 5
    posts = Post.objects.all()[5:10]  # Offset and limit
    
    # Distinct
    authors = Post.objects.values('author').distinct()
    Python

    Complex Queries:

    from django.db.models import Q, F, Count, Avg, Sum, Max, Min
    from django.db.models.functions import Lower
    
    # Q objects (OR queries)
    posts = Post.objects.filter(Q(title__icontains='django') | Q(content__icontains='django'))
    
    # F expressions (field comparison)
    posts = Post.objects.filter(views__gt=F('likes') * 2)
    
    # Aggregation
    from django.db.models import Count, Avg
    stats = Post.objects.aggregate(
        total=Count('id'),
        avg_views=Avg('views'),
        max_views=Max('views')
    )
    
    # Annotation
    posts = Post.objects.annotate(comment_count=Count('comments'))
    
    # Select related (for ForeignKey)
    posts = Post.objects.select_related('author', 'category')
    
    # Prefetch related (for ManyToMany and reverse ForeignKey)
    posts = Post.objects.prefetch_related('comments')
    
    # Values and values_list
    posts = Post.objects.values('title', 'slug')
    titles = Post.objects.values_list('title', flat=True)
    
    # Raw SQL
    posts = Post.objects.raw('SELECT * FROM blog_post WHERE status = %s', ['published'])
    Python

    Field Lookups:

    # Exact match
    Post.objects.filter(title__exact='Django')
    
    # Case-insensitive exact
    Post.objects.filter(title__iexact='django')
    
    # Contains
    Post.objects.filter(title__contains='Django')
    Post.objects.filter(title__icontains='django')
    
    # Starts with / Ends with
    Post.objects.filter(title__startswith='Django')
    Post.objects.filter(title__endswith='Guide')
    
    # Greater than / Less than
    Post.objects.filter(views__gt=100)
    Post.objects.filter(views__gte=100)
    Post.objects.filter(views__lt=1000)
    Post.objects.filter(views__lte=1000)
    
    # In
    Post.objects.filter(id__in=[1, 2, 3])
    
    # Range
    Post.objects.filter(views__range=(100, 1000))
    
    # Date lookups
    Post.objects.filter(published_at__year=2024)
    Post.objects.filter(published_at__month=12)
    Post.objects.filter(published_at__day=25)
    
    # Null
    Post.objects.filter(category__isnull=True)
    
    # Relationship lookups
    Post.objects.filter(author__username='john')
    Post.objects.filter(category__name='Technology')
    Python

    15. Middleware {#middleware}

    Middleware is a framework of hooks into Django’s request/response processing.

    Creating Custom Middleware:

    In blog/middleware.py:

    import time
    from django.utils.deprecation import MiddlewareMixin
    
    class RequestTimingMiddleware(MiddlewareMixin):
        def process_request(self, request):
            request.start_time = time.time()
    
        def process_response(self, request, response):
            if hasattr(request, 'start_time'):
                duration = time.time() - request.start_time
                response['X-Request-Duration'] = str(duration)
            return response
    
    
    class CustomHeaderMiddleware:
        def __init__(self, get_response):
            self.get_response = get_response
    
        def __call__(self, request):
            # Code executed before the view
            response = self.get_response(request)
            # Code executed after the view
            response['X-Custom-Header'] = 'My Value'
            return response
    Python

    Register Middleware:

    In myproject/settings.py:

    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
        'blog.middleware.RequestTimingMiddleware',  # Custom middleware
    ]
    Python

    16. Class-Based Views {#cbv}

    Class-based views provide an alternative to function-based views.

    Generic Views:

    In blog/views.py:

    from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
    from django.urls import reverse_lazy
    from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
    from .models import Post
    from .forms import PostForm
    
    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').select_related('author', 'category')
    
        def get_context_data(self, **kwargs):
            context = super().get_context_data(**kwargs)
            context['categories'] = Category.objects.all()
            return context
    
    
    class PostDetailView(DetailView):
        model = Post
        template_name = 'blog/post_detail.html'
        context_object_name = 'post'
        slug_field = 'slug'
    
        def get_queryset(self):
            return Post.objects.filter(status='published')
    
        def get_object(self):
            obj = super().get_object()
            # Increment views
            obj.views += 1
            obj.save(update_fields=['views'])
            return obj
    
    
    class PostCreateView(LoginRequiredMixin, CreateView):
        model = Post
        form_class = PostForm
        template_name = 'blog/post_form.html'
        success_url = reverse_lazy('post_list')
    
        def form_valid(self, form):
            form.instance.author = self.request.user
            return super().form_valid(form)
    
    
    class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
        model = Post
        form_class = PostForm
        template_name = 'blog/post_form.html'
    
        def test_func(self):
            post = self.get_object()
            return self.request.user == post.author
    
        def get_success_url(self):
            return reverse_lazy('post_detail', kwargs={'slug': self.object.slug})
    
    
    class PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
        model = Post
        template_name = 'blog/post_confirm_delete.html'
        success_url = reverse_lazy('post_list')
    
        def test_func(self):
            post = self.get_object()
            return self.request.user == post.author
    Python

    URLs for CBVs:

    from django.urls import path
    from .views import PostListView, PostDetailView, PostCreateView, PostUpdateView, PostDeleteView
    
    urlpatterns = [
        path('', PostListView.as_view(), name='post_list'),
        path('post/<slug:slug>/', PostDetailView.as_view(), name='post_detail'),
        path('post/new/', PostCreateView.as_view(), name='post_create'),
        path('post/<slug:slug>/edit/', PostUpdateView.as_view(), name='post_update'),
        path('post/<slug:slug>/delete/', PostDeleteView.as_view(), name='post_delete'),
    ]
    Python

    17. Building REST API Endpoints {#rest}

    🎯 What You’ll Learn

    • What REST APIs are and why they’re important
    • Building API endpoints without frameworks (Django only)
    • Using Django REST Framework for advanced APIs
    • Different types of API endpoints
    • Authentication and permissions
    • API documentation

    Understanding REST APIs

    REST API = A way for applications to communicate using HTTP requests

    Mobile App  ←→  REST API  ←→  Django Backend  ←→  Database
    Web App     ←→  REST API  ←→
    Third Party ←→  REST API  ←→
    Python

    Why Build APIs?

    • Mobile apps can use your backend
    • JavaScript frameworks (React, Vue) can fetch data
    • Third-party integrations
    • Microservices architecture

    API Endpoints Explained

    An endpoint is a URL that returns data (usually JSON) instead of HTML:

    Traditional Web:
    GET /posts/ → Returns HTML page
    
    API Endpoint:
    GET /api/posts/ → Returns JSON data
    Python

    Method 1: Simple JSON API (No Framework)

    🎯 Basic JSON Endpoint

    Perfect for simple APIs without extra dependencies.

    Create a simple API view:

    # blog/views.py
    from django.http import JsonResponse
    from django.views.decorators.http import require_http_methods
    from django.views.decorators.csrf import csrf_exempt
    from .models import Post, Category
    import json
    
    # GET endpoint - List all posts
    def api_posts_list(request):
        """
        GET /api/posts/
        Returns list of all published posts
        """
        posts = Post.objects.filter(status='published').values(
            'id', 'title', 'slug', 'content', 'published_at', 'views'
        )
    
        # Convert QuerySet to list for JSON serialization
        posts_list = list(posts)
    
        return JsonResponse({
            'success': True,
            'count': len(posts_list),
            'data': posts_list
        }, safe=False)
    
    
    # GET endpoint - Single post detail
    def api_post_detail(request, post_id):
        """
        GET /api/posts/1/
        Returns single post details
        """
        try:
            post = Post.objects.get(id=post_id, status='published')
    
            data = {
                'id': post.id,
                'title': post.title,
                'slug': post.slug,
                'content': post.content,
                'author': post.author.username,
                'category': post.category.name if post.category else None,
                'published_at': post.published_at,
                'views': post.views,
            }
    
            return JsonResponse({
                'success': True,
                'data': data
            })
    
        except Post.DoesNotExist:
            return JsonResponse({
                'success': False,
                'error': 'Post not found'
            }, status=404)
    
    
    # POST endpoint - Create new post
    @csrf_exempt  # Only for testing! Use CSRF protection in production
    @require_http_methods(["POST"])
    def api_post_create(request):
        """
        POST /api/posts/create/
        Creates a new post
        """
        try:
            # Parse JSON data from request body
            data = json.loads(request.body)
    
            # Create post
            post = Post.objects.create(
                title=data.get('title'),
                slug=data.get('slug'),
                content=data.get('content'),
                author=request.user,
                status='draft'
            )
    
            return JsonResponse({
                'success': True,
                'message': 'Post created successfully',
                'data': {
                    'id': post.id,
                    'title': post.title,
                    'slug': post.slug,
                }
            }, status=201)
    
        except Exception as e:
            return JsonResponse({
                'success': False,
                'error': str(e)
            }, status=400)
    
    
    # PUT/PATCH endpoint - Update post
    @csrf_exempt
    @require_http_methods(["PUT", "PATCH"])
    def api_post_update(request, post_id):
        """
        PUT/PATCH /api/posts/1/update/
        Updates existing post
        """
        try:
            post = Post.objects.get(id=post_id)
            data = json.loads(request.body)
    
            # Update fields
            if 'title' in data:
                post.title = data['title']
            if 'content' in data:
                post.content = data['content']
            if 'status' in data:
                post.status = data['status']
    
            post.save()
    
            return JsonResponse({
                'success': True,
                'message': 'Post updated successfully',
                'data': {
                    'id': post.id,
                    'title': post.title,
                }
            })
    
        except Post.DoesNotExist:
            return JsonResponse({
                'success': False,
                'error': 'Post not found'
            }, status=404)
    
    
    # DELETE endpoint - Delete post
    @csrf_exempt
    @require_http_methods(["DELETE"])
    def api_post_delete(request, post_id):
        """
        DELETE /api/posts/1/delete/
        Deletes a post
        """
        try:
            post = Post.objects.get(id=post_id)
            post.delete()
    
            return JsonResponse({
                'success': True,
                'message': 'Post deleted successfully'
            })
    
        except Post.DoesNotExist:
            return JsonResponse({
                'success': False,
                'error': 'Post not found'
            }, status=404)
    
    
    # Search endpoint
    def api_posts_search(request):
        """
        GET /api/posts/search/?q=django
        Search posts by query
        """
        query = request.GET.get('q', '')
    
        if not query:
            return JsonResponse({
                'success': False,
                'error': 'Query parameter "q" is required'
            }, status=400)
    
        posts = Post.objects.filter(
            title__icontains=query,
            status='published'
        ).values('id', 'title', 'slug', 'excerpt')
    
        return JsonResponse({
            'success': True,
            'query': query,
            'count': len(posts),
            'data': list(posts)
        })
    
    
    # Statistics endpoint
    def api_stats(request):
        """
        GET /api/stats/
        Returns blog statistics
        """
        from django.db.models import Count, Sum
    
        total_posts = Post.objects.filter(status='published').count()
        total_views = Post.objects.aggregate(Sum('views'))['views__sum'] or 0
        total_categories = Category.objects.count()
    
        # Posts by category
        posts_by_category = Category.objects.annotate(
            post_count=Count('posts')
        ).values('name', 'post_count')
    
        return JsonResponse({
            'success': True,
            'data': {
                'total_posts': total_posts,
                'total_views': total_views,
                'total_categories': total_categories,
                'posts_by_category': list(posts_by_category),
            }
        })
    Python

    URL Configuration:

    # blog/urls.py
    from django.urls import path
    from . import views
    
    urlpatterns = [
        # ... existing URLs ...
    
        # API Endpoints
        path('api/posts/', views.api_posts_list, name='api_posts_list'),
        path('api/posts/<int:post_id>/', views.api_post_detail, name='api_post_detail'),
        path('api/posts/create/', views.api_post_create, name='api_post_create'),
        path('api/posts/<int:post_id>/update/', views.api_post_update, name='api_post_update'),
        path('api/posts/<int:post_id>/delete/', views.api_post_delete, name='api_post_delete'),
        path('api/posts/search/', views.api_posts_search, name='api_posts_search'),
        path('api/stats/', views.api_stats, name='api_stats'),
    ]
    Python

    Testing Your API:

    # Using curl
    curl http://127.0.0.1:8000/api/posts/
    
    # Using Python requests
    import requests
    response = requests.get('http://127.0.0.1:8000/api/posts/')
    print(response.json())
    
    # Using browser (GET requests only)
    http://127.0.0.1:8000/api/posts/
    Python

    Example JSON Response:

    {
      "success": true,
      "count": 3,
      "data": [
        {
          "id": 1,
          "title": "First Post",
          "slug": "first-post",
          "content": "Content here...",
          "published_at": "2024-11-30T10:30:00Z",
          "views": 42
        },
        {
          "id": 2,
          "title": "Second Post",
          "slug": "second-post",
          "content": "More content...",
          "published_at": "2024-11-29T15:20:00Z",
          "views": 28
        }
      ]
    }
    Python

    Method 2: Django REST Framework (Advanced)

    🎯 Professional API with DRF

    Django REST Framework provides powerful features for building production-ready APIs.

    Installation:

    pip install djangorestframework
    pip install django-filter  # For filtering
    pip install drf-yasg      # For API documentation
    Python

    Settings Configuration:

    # myproject/settings.py
    
    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
    
        # Third-party apps
        'rest_framework',
        'django_filters',
        'drf_yasg',  # Swagger documentation
    
        # Your apps
        'blog',
    ]
    
    # REST Framework Configuration
    REST_FRAMEWORK = {
        # Default authentication
        'DEFAULT_AUTHENTICATION_CLASSES': [
            'rest_framework.authentication.SessionAuthentication',
            'rest_framework.authentication.TokenAuthentication',
        ],
    
        # Default permissions
        'DEFAULT_PERMISSION_CLASSES': [
            'rest_framework.permissions.IsAuthenticatedOrReadOnly',
        ],
    
        # Pagination
        'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
        'PAGE_SIZE': 10,
    
        # Filtering
        'DEFAULT_FILTER_BACKENDS': [
            'django_filters.rest_framework.DjangoFilterBackend',
            'rest_framework.filters.SearchFilter',
            'rest_framework.filters.OrderingFilter',
        ],
    
        # Throttling (rate limiting)
        'DEFAULT_THROTTLE_CLASSES': [
            'rest_framework.throttling.AnonRateThrottle',
            'rest_framework.throttling.UserRateThrottle'
        ],
        'DEFAULT_THROTTLE_RATES': {
            'anon': '100/day',
            'user': '1000/day'
        }
    }
    Python

    Create Serializers:

    # blog/serializers.py
    from rest_framework import serializers
    from .models import Post, Category, Comment
    from django.contrib.auth.models import User
    
    
    class UserSerializer(serializers.ModelSerializer):
        """Serializer for User model"""
        class Meta:
            model = User
            fields = ['id', 'username', 'email', 'first_name', 'last_name']
            read_only_fields = ['id']
    
    
    class CategorySerializer(serializers.ModelSerializer):
        """Serializer for Category model"""
        post_count = serializers.SerializerMethodField()
    
        class Meta:
            model = Category
            fields = ['id', 'name', 'slug', 'description', 'post_count']
            read_only_fields = ['id']
    
        def get_post_count(self, obj):
            return obj.posts.filter(status='published').count()
    
    
    class CommentSerializer(serializers.ModelSerializer):
        """Serializer for Comment model"""
        class Meta:
            model = Comment
            fields = ['id', 'post', 'name', 'email', 'content', 'created_at', 'active']
            read_only_fields = ['id', 'created_at']
    
    
    class PostListSerializer(serializers.ModelSerializer):
        """Lightweight serializer for post lists"""
        author = serializers.StringRelatedField()
        category = serializers.StringRelatedField()
        comment_count = serializers.SerializerMethodField()
    
        class Meta:
            model = Post
            fields = [
                'id', 'title', 'slug', 'author', 'category',
                'excerpt', 'published_at', 'views', 'comment_count'
            ]
            read_only_fields = ['id', 'published_at', 'views']
    
        def get_comment_count(self, obj):
            return obj.comments.filter(active=True).count()
    
    
    class PostDetailSerializer(serializers.ModelSerializer):
        """Detailed serializer for single post"""
        author = UserSerializer(read_only=True)
        category = CategorySerializer(read_only=True)
        comments = CommentSerializer(many=True, read_only=True)
    
        class Meta:
            model = Post
            fields = [
                'id', 'title', 'slug', 'author', 'category', 'content',
                'excerpt', 'created_at', 'updated_at', 'published_at',
                'status', 'featured_image', 'views', 'comments'
            ]
            read_only_fields = ['id', 'created_at', 'updated_at', 'views']
    
        def validate_slug(self, value):
            """Custom validation for slug"""
            if Post.objects.filter(slug=value).exists():
                raise serializers.ValidationError("Post with this slug already exists.")
            return value
    
    
    class PostCreateUpdateSerializer(serializers.ModelSerializer):
        """Serializer for creating/updating posts"""
        class Meta:
            model = Post
            fields = [
                'title', 'slug', 'category', 'content', 'excerpt',
                'status', 'featured_image'
            ]
    
        def create(self, validated_data):
            # Author is set from request.user in the view
            return Post.objects.create(**validated_data)
    Python

    Create API Views:

    # blog/api_views.py
    from rest_framework import viewsets, status, filters
    from rest_framework.decorators import action, api_view, permission_classes
    from rest_framework.response import Response
    from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly, AllowAny
    from django_filters.rest_framework import DjangoFilterBackend
    from django.db.models import Q
    
    from .models import Post, Category, Comment
    from .serializers import (
        PostListSerializer, PostDetailSerializer, PostCreateUpdateSerializer,
        CategorySerializer, CommentSerializer
    )
    
    
    class PostViewSet(viewsets.ModelViewSet):
        """
        ViewSet for Post model
    
        Provides:
        - list: GET /api/posts/
        - create: POST /api/posts/
        - retrieve: GET /api/posts/{id}/
        - update: PUT /api/posts/{id}/
        - partial_update: PATCH /api/posts/{id}/
        - destroy: DELETE /api/posts/{id}/
        """
        queryset = Post.objects.filter(status='published')
        permission_classes = [IsAuthenticatedOrReadOnly]
        filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
        filterset_fields = ['category', 'author', 'status']
        search_fields = ['title', 'content', 'excerpt']
        ordering_fields = ['published_at', 'views', 'created_at']
        ordering = ['-published_at']
        lookup_field = 'slug'
    
        def get_serializer_class(self):
            """Use different serializers for different actions"""
            if self.action == 'list':
                return PostListSerializer
            elif self.action in ['create', 'update', 'partial_update']:
                return PostCreateUpdateSerializer
            return PostDetailSerializer
    
        def get_queryset(self):
            """Custom queryset with optimizations"""
            queryset = super().get_queryset()
    
            # Optimize queries
            queryset = queryset.select_related('author', 'category')
            queryset = queryset.prefetch_related('comments')
    
            # Show all posts to staff
            if self.request.user.is_staff:
                queryset = Post.objects.all()
    
            return queryset
    
        def perform_create(self, serializer):
            """Set author when creating post"""
            serializer.save(author=self.request.user)
    
        # Custom action: Increment views
        @action(detail=True, methods=['post'], permission_classes=[AllowAny])
        def increment_views(self, request, slug=None):
            """
            POST /api/posts/{slug}/increment_views/
            Increment post view count
            """
            post = self.get_object()
            post.views += 1
            post.save(update_fields=['views'])
    
            return Response({
                'success': True,
                'views': post.views
            })
    
        # Custom action: Get post comments
        @action(detail=True, methods=['get'])
        def comments(self, request, slug=None):
            """
            GET /api/posts/{slug}/comments/
            Get all comments for a post
            """
            post = self.get_object()
            comments = post.comments.filter(active=True)
            serializer = CommentSerializer(comments, many=True)
    
            return Response({
                'success': True,
                'count': comments.count(),
                'data': serializer.data
            })
    
        # Custom action: Publish post
        @action(detail=True, methods=['post'], permission_classes=[IsAuthenticated])
        def publish(self, request, slug=None):
            """
            POST /api/posts/{slug}/publish/
            Publish a draft post
            """
            post = self.get_object()
    
            # Only author can publish
            if post.author != request.user:
                return Response(
                    {'error': 'You can only publish your own posts'},
                    status=status.HTTP_403_FORBIDDEN
                )
    
            post.status = 'published'
            post.save(update_fields=['status'])
    
            return Response({
                'success': True,
                'message': 'Post published successfully',
                'status': post.status
            })
    
    
    class CategoryViewSet(viewsets.ModelViewSet):
        """
        ViewSet for Category model
        """
        queryset = Category.objects.all()
        serializer_class = CategorySerializer
        permission_classes = [IsAuthenticatedOrReadOnly]
        lookup_field = 'slug'
    
        @action(detail=True, methods=['get'])
        def posts(self, request, slug=None):
            """
            GET /api/categories/{slug}/posts/
            Get all posts in a category
            """
            category = self.get_object()
            posts = category.posts.filter(status='published')
            serializer = PostListSerializer(posts, many=True)
    
            return Response({
                'success': True,
                'category': category.name,
                'count': posts.count(),
                'data': serializer.data
            })
    
    
    class CommentViewSet(viewsets.ModelViewSet):
        """
        ViewSet for Comment model
        """
        queryset = Comment.objects.filter(active=True)
        serializer_class = CommentSerializer
        permission_classes = [IsAuthenticatedOrReadOnly]
        filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
        filterset_fields = ['post', 'active']
        ordering = ['-created_at']
    
    
    # Function-based API views
    @api_view(['GET'])
    @permission_classes([AllowAny])
    def api_stats(request):
        """
        GET /api/stats/
        Get blog statistics
        """
        from django.db.models import Count, Sum, Avg
    
        stats = {
            'total_posts': Post.objects.filter(status='published').count(),
            'total_categories': Category.objects.count(),
            'total_comments': Comment.objects.filter(active=True).count(),
            'total_views': Post.objects.aggregate(Sum('views'))['views__sum'] or 0,
            'avg_views_per_post': Post.objects.aggregate(Avg('views'))['views__avg'] or 0,
            'posts_by_category': list(
                Category.objects.annotate(
                    post_count=Count('posts')
                ).values('name', 'post_count')
            )
        }
    
        return Response({
            'success': True,
            'data': stats
        })
    
    
    @api_view(['GET'])
    @permission_classes([AllowAny])
    def api_search(request):
        """
        GET /api/search/?q=django&type=posts
        Global search endpoint
        """
        query = request.GET.get('q', '')
        search_type = request.GET.get('type', 'all')
    
        if not query:
            return Response(
                {'error': 'Query parameter "q" is required'},
                status=status.HTTP_400_BAD_REQUEST
            )
    
        results = {}
    
        if search_type in ['all', 'posts']:
            posts = Post.objects.filter(
                Q(title__icontains=query) | Q(content__icontains=query),
                status='published'
            )
            results['posts'] = PostListSerializer(posts, many=True).data
    
        if search_type in ['all', 'categories']:
            categories = Category.objects.filter(
                Q(name__icontains=query) | Q(description__icontains=query)
            )
            results['categories'] = CategorySerializer(categories, many=True).data
    
        return Response({
            'success': True,
            'query': query,
            'results': results
        })
    Python

    URL Configuration with Router:

    # blog/urls.py
    from django.urls import path, include
    from rest_framework.routers import DefaultRouter
    from . import views, api_views
    
    # DRF Router for ViewSets
    router = DefaultRouter()
    router.register(r'posts', api_views.PostViewSet, basename='post')
    router.register(r'categories', api_views.CategoryViewSet, basename='category')
    router.register(r'comments', api_views.CommentViewSet, basename='comment')
    
    urlpatterns = [
        # ... existing URLs ...
    
        # API endpoints
        path('api/', include(router.urls)),
        path('api/stats/', api_views.api_stats, name='api_stats'),
        path('api/search/', api_views.api_search, name='api_search'),
    
        # API authentication
        path('api-auth/', include('rest_framework.urls')),
    ]
    Python

    API Documentation with Swagger:

    # myproject/urls.py
    from django.contrib import admin
    from django.urls import path, include
    from rest_framework import permissions
    from drf_yasg.views import get_schema_view
    from drf_yasg import openapi
    
    # Swagger documentation
    schema_view = get_schema_view(
        openapi.Info(
            title="Blog API",
            default_version='v1',
            description="REST API for Blog application",
            terms_of_service="https://www.example.com/terms/",
            contact=openapi.Contact(email="contact@blog.local"),
            license=openapi.License(name="BSD License"),
        ),
        public=True,
        permission_classes=(permissions.AllowAny,),
    )
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('', include('blog.urls')),
    
        # API documentation
        path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
        path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
    ]
    Python

    Available Endpoints:

    # Posts
    GET    /api/posts/                      - List all posts
    POST   /api/posts/                      - Create new post
    GET    /api/posts/{slug}/               - Get single post
    PUT    /api/posts/{slug}/               - Update post
    PATCH  /api/posts/{slug}/               - Partial update
    DELETE /api/posts/{slug}/               - Delete post
    POST   /api/posts/{slug}/increment_views/ - Increment views
    GET    /api/posts/{slug}/comments/      - Get post comments
    POST   /api/posts/{slug}/publish/       - Publish post
    
    # Categories
    GET    /api/categories/                 - List all categories
    POST   /api/categories/                 - Create category
    GET    /api/categories/{slug}/          - Get single category
    GET    /api/categories/{slug}/posts/    - Get category posts
    
    # Comments
    GET    /api/comments/                   - List all comments
    POST   /api/comments/                   - Create comment
    
    # Utilities
    GET    /api/stats/                      - Get statistics
    GET    /api/search/?q=django            - Search
    
    # Documentation
    GET    /swagger/                        - Swagger UI
    GET    /redoc/                          - ReDoc UI
    Python

    Testing the API:

    # test_api.py
    import requests
    
    BASE_URL = 'http://127.0.0.1:8000/api'
    
    # Get all posts
    response = requests.get(f'{BASE_URL}/posts/')
    print(response.json())
    
    # Get single post
    response = requests.get(f'{BASE_URL}/posts/my-first-post/')
    print(response.json())
    
    # Create post (requires authentication)
    response = requests.post(
        f'{BASE_URL}/posts/',
        json={
            'title': 'New Post',
            'slug': 'new-post',
            'content': 'Post content here',
            'status': 'published'
        },
        headers={'Authorization': 'Token your-token-here'}
    )
    
    # Search
    response = requests.get(f'{BASE_URL}/search/?q=django')
    print(response.json())
    
    # Get stats
    response = requests.get(f'{BASE_URL}/stats/')
    print(response.json())
    Python

    🎓 Key Takeaways

    ✅ Simple JSON endpoints with JsonResponse for basic APIs
    ✅ Django REST Framework for production-ready APIs
    ✅ Serializers convert models to JSON
    ✅ ViewSets provide CRUD operations automatically
    ✅ Use @action decorator for custom endpoints
    ✅ Router auto-generates URL patterns
    ✅ Swagger/ReDoc for API documentation
    ✅ Permissions control who can access what

    Next: Let’s add comprehensive testing!


    18. Testing {#testing}

    Unit Tests:

    In blog/tests.py:

    from django.test import TestCase, Client
    from django.contrib.auth.models import User
    from django.urls import reverse
    from .models import Post, Category
    
    class PostModelTest(TestCase):
        @classmethod
        def setUpTestData(cls):
            # Set up non-modified objects used by all test methods
            user = User.objects.create_user(username='testuser', password='12345')
            Category.objects.create(name='Tech', slug='tech')
            Post.objects.create(
                title='Test Post',
                slug='test-post',
                author=user,
                content='Test content',
                status='published'
            )
    
        def test_post_content(self):
            post = Post.objects.get(id=1)
            self.assertEqual(post.title, 'Test Post')
            self.assertEqual(post.slug, 'test-post')
    
        def test_post_str(self):
            post = Post.objects.get(id=1)
            self.assertEqual(str(post), 'Test Post')
    
        def test_post_absolute_url(self):
            post = Post.objects.get(id=1)
            # Assuming you have get_absolute_url method
            # self.assertEqual(post.get_absolute_url(), '/post/test-post/')
    
    
    class PostViewTest(TestCase):
        def setUp(self):
            self.client = Client()
            self.user = User.objects.create_user(username='testuser', password='12345')
            self.post = Post.objects.create(
                title='Test Post',
                slug='test-post',
                author=self.user,
                content='Test content',
                status='published'
            )
    
        def test_post_list_view(self):
            response = self.client.get(reverse('post_list'))
            self.assertEqual(response.status_code, 200)
            self.assertContains(response, 'Test Post')
            self.assertTemplateUsed(response, 'blog/post_list.html')
    
        def test_post_detail_view(self):
            response = self.client.get(reverse('post_detail', args=['test-post']))
            self.assertEqual(response.status_code, 200)
            self.assertContains(response, 'Test Post')
    
        def test_post_create_view_authenticated(self):
            self.client.login(username='testuser', password='12345')
            response = self.client.post(reverse('post_create'), {
                'title': 'New Post',
                'slug': 'new-post',
                'content': 'New content',
                'status': 'draft'
            })
            self.assertEqual(Post.objects.count(), 2)
    Python

    Run Tests:

    # Run all tests
    python manage.py test
    
    # Run specific app tests
    python manage.py test blog
    
    # Run specific test class
    python manage.py test blog.tests.PostModelTest
    
    # Run with verbosity
    python manage.py test --verbosity=2
    
    # Keep test database
    python manage.py test --keepdb
    Python

    19. Deployment {#deployment}

    Preparation:

    1. Update settings for production:
    # settings.py
    DEBUG = False
    ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']
    
    # Security settings
    SECURE_SSL_REDIRECT = True
    SESSION_COOKIE_SECURE = True
    CSRF_COOKIE_SECURE = True
    SECURE_BROWSER_XSS_FILTER = True
    SECURE_CONTENT_TYPE_NOSNIFF = True
    X_FRAME_OPTIONS = 'DENY'
    
    # Database (use environment variables)
    import os
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql',
            'NAME': os.environ.get('DB_NAME'),
            'USER': os.environ.get('DB_USER'),
            'PASSWORD': os.environ.get('DB_PASSWORD'),
            'HOST': os.environ.get('DB_HOST'),
            'PORT': '5432',
        }
    }
    
    # Static and media files
    STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
    MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
    Python
    1. Create requirements.txt:
    pip freeze > requirements.txt
    Python
    1. Collect static files:
    python manage.py collectstatic
    Python

    Deployment Options:

    Heroku:

    # Install Heroku CLI and login
    heroku login
    
    # Create Heroku app
    heroku create myapp
    
    # Install required packages
    pip install gunicorn dj-database-url whitenoise
    
    # Create Procfile
    echo "web: gunicorn myproject.wsgi" > Procfile
    
    # Create runtime.txt
    echo "python-3.11.0" > runtime.txt
    
    # Deploy
    git push heroku main
    
    # Run migrations
    heroku run python manage.py migrate
    
    # Create superuser
    heroku run python manage.py createsuperuser
    Python

    Docker:

    # Dockerfile
    FROM python:3.11-slim
    
    ENV PYTHONUNBUFFERED=1
    ENV PYTHONDONTWRITEBYTECODE=1
    
    WORKDIR /app
    
    COPY requirements.txt /app/
    RUN pip install --no-cache-dir -r requirements.txt
    
    COPY . /app/
    
    RUN python manage.py collectstatic --noinput
    
    EXPOSE 8000
    
    CMD ["gunicorn", "--bind", "0.0.0.0:8000", "myproject.wsgi:application"]
    Python
    # docker-compose.yml
    version: '3.8'
    
    services:
      db:
        image: postgres:14
        volumes:
          - postgres_data:/var/lib/postgresql/data
        environment:
          - POSTGRES_DB=mydb
          - POSTGRES_USER=myuser
          - POSTGRES_PASSWORD=mypassword
    
      web:
        build: .
        command: gunicorn myproject.wsgi:application --bind 0.0.0.0:8000
        volumes:
          - .:/app
        ports:
          - "8000:8000"
        depends_on:
          - db
        environment:
          - DATABASE_URL=postgresql://myuser:mypassword@db:5432/mydb
    
    volumes:
      postgres_data:
    Python

    20. Best Practices {#best-practices}

    Code Organization:

    1. Use apps for modular code: Each app should have a single responsibility
    2. Keep views thin: Move business logic to models or separate services
    3. Use managers for common queries: Create custom model managers
    4. Separate settings: Use different settings files for dev/staging/production

    Security:

    1. Never commit sensitive data: Use environment variables
    2. Use HTTPS in production: Set SECURE_SSL_REDIRECT
    3. Keep Django updated: Regular security patches
    4. Validate user input: Use forms and serializers
    5. Use CSRF protection: Always include {% csrf_token %}
    6. Sanitize output: Use {{ variable }} not {{ variable|safe }}

    Performance:

    1. Use select_related and prefetch_related: Reduce database queries
    2. Add database indexes: On frequently queried fields
    3. Use caching: Django’s cache framework
    4. Optimize queries: Use django-debug-toolbar
    5. Paginate large querysets: Don’t load everything at once
    6. Use database-level operations: bulk_create, bulk_update

    Database Optimization:

    # Use select_related for foreign keys
    posts = Post.objects.select_related('author', 'category')
    
    # Use prefetch_related for many-to-many
    posts = Post.objects.prefetch_related('comments')
    
    # Bulk operations
    Post.objects.bulk_create([
        Post(title='Post 1', ...),
        Post(title='Post 2', ...),
    ])
    
    # Only fetch needed fields
    posts = Post.objects.only('title', 'slug')
    posts = Post.objects.defer('content')
    
    # Database indexes
    class Post(models.Model):
        title = models.CharField(max_length=200, db_index=True)
    Python

    Project Structure:

    myproject/
    ├── myproject/
    │   ├── __init__.py
    │   ├── settings/
    │   │   ├── __init__.py
    │   │   ├── base.py
    │   │   ├── development.py
    │   │   └── production.py
    │   ├── urls.py
    │   └── wsgi.py
    ├── apps/
    │   ├── blog/
    │   ├── users/
    │   └── core/
    ├── static/
    ├── media/
    ├── templates/
    ├── requirements/
    │   ├── base.txt
    │   ├── development.txt
    │   └── production.txt
    ├── manage.py
    └── README.md
    Python

    Environment Variables:

    # Use python-decouple
    from decouple import config
    
    SECRET_KEY = config('SECRET_KEY')
    DEBUG = config('DEBUG', default=False, cast=bool)
    DATABASE_URL = config('DATABASE_URL')
    Python

    Additional Resources

    Official Documentation:

    Useful Packages:

    • django-debug-toolbar: Debugging tool
    • django-extensions: Management command extensions
    • django-crispy-forms: Better form rendering
    • django-filter: Advanced filtering
    • celery: Asynchronous task queue
    • django-allauth: Authentication & social login
    • django-cors-headers: CORS handling
    • django-storages: Cloud storage backends

    Commands Reference:

    # Project management
    django-admin startproject myproject
    python manage.py startapp myapp
    python manage.py runserver
    python manage.py shell
    
    # Database
    python manage.py makemigrations
    python manage.py migrate
    python manage.py dbshell
    python manage.py dumpdata > backup.json
    python manage.py loaddata backup.json
    
    # User management
    python manage.py createsuperuser
    python manage.py changepassword username
    
    # Static files
    python manage.py collectstatic
    python manage.py findstatic filename
    
    # Testing
    python manage.py test
    python manage.py test --keepdb
    
    # Other
    python manage.py check
    python manage.py showmigrations
    python manage.py sqlmigrate app_name migration_name
    Python

    Conclusion

    Django is a powerful, batteries-included web framework that enables rapid development of secure and scalable web applications. This guide covers the essential concepts from basic setup to advanced features like REST APIs, authentication, and deployment.

    Key Takeaways:

    • Django follows the MTV (Model-Template-View) pattern
    • The ORM provides powerful database abstraction
    • Built-in admin interface saves development time
    • Security features are built-in by default
    • Class-based views offer reusable components
    • Django REST Framework makes API development easy
    • Testing is integrated into the framework
    • Deployment can be done on various platforms

    Continue learning by building real projects, reading the official documentation, and exploring the vibrant Django ecosystem!


    Discover more from Altgr Blog

    Subscribe to get the latest posts sent to your email.

    Leave a Reply

    Your email address will not be published. Required fields are marked *