python5 min read

Django Tutorial: Build Web Apps with Python from Scratch (2026)

Django Tutorial: Build Web Apps with Python from Scratch (2026)

Published:  |  Category: Python  |  Reading time: ~15 min
Django Tutorial: Build Web Apps with Python from Scratch (2026)

I built my first Django site for a university department back when Django 1.4 was current. What struck me then — and still holds — is how much mileage you get from the framework's batteries-included philosophy. You get an ORM, an admin panel, authentication, URL routing, a template engine, and a testing client without reaching for a single third-party package. That's not bloat; it's a coherent set of decisions that let you ship a full-featured web application in days, not weeks.

In this walkthrough we'll build a simple book review site. Users register, log in, browse books, and leave reviews. We'll use Django's class-based views for the common CRUD patterns, the ORM for queries, and the admin to manage content. By the end you'll understand why Django is the default choice for Python teams that need to move fast without sacrificing structure.

Project Structure and the Startproject Command

Django organizes code into projects (the whole site) and apps (reusable modules). Running django-admin startproject creates the manage.py entry point, settings, URLs, and WSGI configuration. I tend to create a single app per domain concept. For the book review site we'll create a reviews app. The structure keeps concerns separated and makes it easy to extract apps into reusable packages later.

django-admin startproject bookreview
cd bookreview
python manage.py startapp reviews

# Register in settings.py INSTALLED_APPS:
# 'reviews',

Models: Defining Your Data Schema

Django's ORM lets you define database tables as Python classes. Each field type — CharField, IntegerField, ForeignKey — maps to a database column. Migrations auto-generate the SQL to create and alter tables without writing raw DDL. The trick I learned early: always add a __str__ method to every model so the admin and querysets display readable names.

from django.db import models
from django.contrib.auth.models import User

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.CharField(max_length=100)
    published = models.DateField()

    def __str__(self):
        return self.title

class Review(models.Model):
    book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name='reviews')
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    rating = models.IntegerField(choices=[(i, i) for i in range(1, 6)])
    body = models.TextField()
    created = models.DateTimeField(auto_now_add=True)

Views and URL Routing

Class-based views (CBVs) handle common patterns like listing objects or creating records with less boilerplate than function views. ListView, DetailView, and CreateView pair with Django's URL dispatcher that maps URL patterns to view methods. I like CBVs for standard operations and write function views when the flow is custom enough that CBVs fight me.

from django.views.generic import ListView, DetailView
from .models import Book

class BookListView(ListView):
    model = Book
    template_name = 'reviews/book_list.html'
    context_object_name = 'books'
    paginate_by = 20

class BookDetailView(DetailView):
    model = Book

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

urlpatterns = [
    path('', views.BookListView.as_view(), name='book-list'),
    path('/', views.BookDetailView.as_view(), name='book-detail'),
]

Templates and the Django Template Language

Django's template engine is intentionally restrictive — it doesn't allow arbitrary Python execution, which forces logic into views and filters. Templates use {{ variables }} and {% tags %}. I use template inheritance heavily: a base.html with the site chrome, and child blocks for page-specific content. This keeps HTML DRY and makes global changes (like a new stylesheet) a single edit.


{% extends 'base.html' %}

{% block content %}
  

Books

    {% for book in books %}
  • {{ book.title }}
  • {% empty %}
  • No books yet.
  • {% endfor %}
{% endblock %}

Authentication and User Registration

Django ships with a full authentication system: users, sessions, login/logout views, password reset, and decorators like @login_required. For registration I extend the built-in UserCreationForm and add an email field. The auth system integrates seamlessly with CBVs through mixins like LoginRequiredMixin, which redirects anonymous users to the login page.

from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView
from django.contrib.auth.forms import UserCreationForm
from django.urls import reverse_lazy

class SignUpView(CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('login')
    template_name = 'registration/signup.html'

# Protect a view:
class ReviewCreateView(LoginRequiredMixin, CreateView):
    model = Review
    fields = ['book', 'rating', 'body']

The Admin Panel and Production Considerations

Django's admin interface is one of its killer features — register your models and you get a full CRUD UI for free. I use it as an internal tool for support staff to manage data without writing SQL. For production, collect static files, switch DEBUG=False, set ALLOWED_HOSTS, use a real database (PostgreSQL), and serve with Gunicorn behind nginx. The Django deployment checklist covers the rest.

from django.contrib import admin
from .models import Book, Review

@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
    list_display = ('title', 'author', 'published')
    search_fields = ('title', 'author')

@admin.register(Review)
class ReviewAdmin(admin.ModelAdmin):
    list_display = ('book', 'user', 'rating', 'created')
    list_filter = ('rating', 'created')

Frequently Asked Questions

Should I use Django REST Framework with Django?

If you need a REST API alongside your site, yes. DRF integrates naturally with Django models, adds serializers and viewsets, and provides browsable API docs. I add it on every project that serves a mobile app or SPA frontend.

How do I handle database migrations in a team?

Commit migration files to version control. Each team member runs python manage.py migrate after pulling. If two branches introduce conflicting migrations, you can merge them with python manage.py makemigrations --merge and resolve the dependency order.

Is Django too heavy for small projects?

Django's 'heavy' perception comes from the startproject boilerplate, but you can trim it. Remove apps you don't need (sites, flatpages, redirects). For truly tiny APIs, Flask or FastAPI may be lighter, but Django is still a strong choice because you can grow into its features without switching frameworks.

How do I debug performance issues in Django?

Start with django-debug-toolbar to see SQL queries on each page. Use n+1 query detection. Then profile views with silk. For slow pages, add select_related() and prefetch_related() to your querysets. Cache expensive template fragments with Django's cache framework.

Originally published on Ayodhyyya. Last updated June 1, 2026.