Complete Django Web Development Tutorial: From Scratch to Deployment

Technology
Jun 2, 2026
8 min read

Django is a “batteries-included” Python web framework that helps you build secure, maintainable web apps fast—without reinventing common features like routing, forms, authentication, or an admin dashboard. In this tutorial you’ll build a small (but realistic) project step-by-step, learning Django’s core workflow from local setup to deployment-ready configuration.

Django request/response flow (MVT overview)

Goal (what you’ll have by the end)

You’ll create a simple “Posts” web app with:

  • Pages powered by URLs + views
  • HTML templates with inheritance and static CSS
  • A database-backed Post model with CRUD operations
  • Admin management
  • Forms (including validation)
  • User login/logout (and a path to registration)
  • Deployment checklist + production settings basics

Prerequisites

  • Intermediate Python comfort (functions, modules, virtual environments)
  • Basic HTML/CSS familiarity helps
  • Python 3.11+ recommended (3.10+ works)

Module 1: Introduction to Django

What is Django and why use it?

Django is designed for building database-driven websites quickly and safely. It’s popular because it includes:

  • An ORM (Pythonic database access)
  • Built-in security defaults (CSRF protection, password hashing)
  • A powerful admin interface
  • A clear project/app structure that scales well

Understanding MVT (Model–View–Template)

Django uses MVT, which maps closely to MVC:

  • Model: your data schema and database logic (ORM classes)
  • View: request-handling logic (what data to fetch, what response to return)
  • Template: presentation layer (HTML with Django Template Language)

Django’s URL routing decides which view handles a request; the view renders a template with context data, or returns JSON/redirects/etc.

Set up environment (Python, Django, virtualenv)

Create a project folder, then:

mkdir django-tutorial && cd django-tutorial
python -m venv .venv
# macOS/Linux
source .venv/bin/activate
# Windows PowerShell
.venv\Scripts\Activate.ps1

python -m pip install --upgrade pip
pip install django
django-admin --version

Tip: Keep dependencies pinned for deployment later:

pip freeze > requirements.txt

Create your first project + directory structure

django-admin startproject config .
python manage.py runserver

Key files:

  • manage.py: command runner (migrations, server, admin tasks)
  • config/settings.py: installed apps, middleware, database, static settings
  • config/urls.py: root URL router
  • config/wsgi.py / asgi.py: production/server entrypoints

Open http://127.0.0.1:8000/ to confirm it works.

Module 2: Apps, URLs, and Views

Project vs App (what is a Django app?)

A project is the whole site configuration; an app is a focused feature unit (blog, accounts, payments). Apps should be reusable and modular.

Create an app and register it

python manage.py startapp posts

Add it to INSTALLED_APPS in config/settings.py:

INSTALLED_APPS = [
    # ...
    "posts",
]

URL routing (root + app URLs)

Create posts/urls.py:

from django.urls import path
from . import views

urlpatterns = [
    path("", views.home, name="home"),
]

Include it in config/urls.py:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("", include("posts.urls")),
]

Function-Based Views (FBVs)

In posts/views.py:

from django.http import HttpResponse

def home(request):
    return HttpResponse("Hello from Django!")

Why this matters: a view is just a callable that takes a request and returns a response. Starting with HttpResponse keeps the mental model simple before templates and databases.

Module 3: Templates and Static Files

Set up templates

Create posts/templates/posts/home.html (Django convention: app/templates/app/...):

<!doctype html>
<html>
  <head><title>Home</title></head>
  <body>
    <h1>Home</h1>
  </body>
</html>

Update the view to render a template:

from django.shortcuts import render

def home(request):
    return render(request, "posts/home.html")

Django Template Language (DTL): variables, tags, filters

Pass context data:

def home(request):
    return render(request, "posts/home.html", {"name": "Ada"})

In template:

<h1>Hello, {{ name|upper }}</h1>
{% if name %}
  <p>Welcome back.</p>
{% endif %}
  • {{ ... }} outputs variables
  • {% ... %} runs template logic (loops, conditions, includes)
  • |upper is a filter (formatting/transforms)

Template inheritance (base.html)

Create templates/base.html at the project level. In config/settings.py, set a global templates dir:

from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent

TEMPLATES[0]["DIRS"] = [BASE_DIR / "templates"]

templates/base.html:

<!doctype html>
<html>
  <head>
    <title>{% block title %}My Site{% endblock %}</title>
  </head>
  <body>
    <nav><a href="{% url 'home' %}">Home</a></nav>
    <main>
      {% block content %}{% endblock %}
    </main>
  </body>
</html>

Update posts/home.html:

{% extends "base.html" %}
{% block title %}Home{% endblock %}
{% block content %}
  <h1>Home</h1>
{% endblock %}

Static files (CSS/JS/images)

In config/settings.py:

STATIC_URL = "static/"

Create posts/static/posts/styles.css:

body { font-family: system-ui, sans-serif; }

In base.html:

{% load static %}
<link rel="stylesheet" href="{% static 'posts/styles.css' %}">

Common mistake: forgetting {% load static %} or placing static files outside static/.

Module 4: Models and Databases

SQLite by default

Django uses SQLite locally by default, which is perfect for learning and small projects. You’ll later switch to Postgres/MySQL for production.

Create a model

In posts/models.py:

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=200)
    body = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title

Migrations (makemigrations + migrate)

python manage.py makemigrations
python manage.py migrate

Why migrations matter: Django tracks schema changes as code, so your database structure stays reproducible across machines and environments.

Django ORM CRUD basics

Open the shell:

python manage.py shell
from posts.models import Post

# Create
p = Post.objects.create(title="First Post", body="Hello ORM")

# Read
Post.objects.all()
Post.objects.filter(title__icontains="first")

# Update
p.body = "Updated"
p.save()

# Delete
p.delete()

Admin panel

Create an admin user:

python manage.py createsuperuser

Register the model in posts/admin.py:

from django.contrib import admin
from .models import Post

admin.site.register(Post)

Now you can manage posts at /admin/—a big productivity win for internal tools and content-heavy sites.

Module 5: Forms and User Authentication

ModelForms for creating posts

Create posts/forms.py:

from django import forms
from .models import Post

class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ["title", "body"]

Add a create view in posts/views.py:

from django.shortcuts import render, redirect
from .forms import PostForm

def post_create(request):
    if request.method == "POST":
        form = PostForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect("home")
    else:
        form = PostForm()
    return render(request, "posts/post_form.html", {"form": form})

Add URL:

path("posts/new/", views.post_create, name="post_create"),

Template posts/templates/posts/post_form.html:

{% extends "base.html" %}
{% block content %}
  <h1>New Post</h1>
  <form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Save</button>
  </form>
{% endblock %}

Key concepts:

  • POST handling: validate, save, redirect (PRG pattern avoids double-submit)
  • CSRF token: required for form security

Built-in authentication (login/logout)

Add Django’s auth URLs in config/urls.py:

urlpatterns = [
    path("admin/", admin.site.urls),
    path("accounts/", include("django.contrib.auth.urls")),
    path("", include("posts.urls")),
]

Create templates/registration/login.html:

{% extends "base.html" %}
{% block content %}
  <h1>Login</h1>
  <form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Log in</button>
  </form>
{% endblock %}

In config/settings.py, set redirects:

LOGIN_REDIRECT_URL = "home"
LOGOUT_REDIRECT_URL = "home"

To protect a view:

from django.contrib.auth.decorators import login_required

@login_required
def post_create(request):
    ...

Registration note: Django doesn’t ship a “signup” view by default, but you can add one using UserCreationForm (a common next step).

Local dev to deployment pipeline (settings, DB, static, server)

Module 6: Advanced Concepts & Deployment

Intro to Class-Based Views (CBVs)

CBVs help you reuse patterns like “list objects” or “create object.” For example, a ListView for posts:

from django.views.generic import ListView
from .models import Post

class PostListView(ListView):
    model = Post
    template_name = "posts/post_list.html"
    context_object_name = "posts"

CBVs shine when you want consistent behavior (pagination, mixins, permission logic) without rewriting boilerplate.

Using PostgreSQL/MySQL

For production, Postgres is a common default.

Install driver:

pip install psycopg[binary]

Set DATABASES via environment variables (don’t hardcode credentials):

import os

DATABASES = {
  "default": {
    "ENGINE": "django.db.backends.postgresql",
    "NAME": os.environ["DB_NAME"],
    "USER": os.environ["DB_USER"],
    "PASSWORD": os.environ["DB_PASSWORD"],
    "HOST": os.environ["DB_HOST"],
    "PORT": os.environ.get("DB_PORT", "5432"),
  }
}

Tip: Use python-decouple or django-environ for cleaner settings management.

Deployment best practices (Render / Heroku-style checklist)

Most Django deployment issues come down to configuration. Use this checklist:

  1. Environment variables
    • SECRET_KEY (never commit)
    • DEBUG=0
    • ALLOWED_HOSTS (your domain)
  2. Production settings
    • SECURE_SSL_REDIRECT=True (if behind HTTPS)
    • SESSION_COOKIE_SECURE=True, CSRF_COOKIE_SECURE=True (with HTTPS)
  3. Static files
    • Add WhiteNoise for simple hosting:
      pip install whitenoise
      In MIDDLEWARE (near the top):
      "whitenoise.middleware.WhiteNoiseMiddleware",
      Then:
      python manage.py collectstatic
  4. App server
    • Use Gunicorn:
      pip install gunicorn
      Start command example:
      gunicorn config.wsgi:application
  5. Database migrations
    • Run python manage.py migrate during release/build
  6. Security sanity check
    • Run: python manage.py check --deploy

Render/Heroku/Vercel differ in UI and wiring, but the core ideas are the same: environment variables, production settings, static handling, and an app server.

Next steps

You now have the core Django building blocks: apps, routing, templates, models, forms, auth, and deployment-ready configuration. A strong next upgrade is adding full CRUD pages (list/detail/update/delete), switching your views to CBVs with authentication mixins, and adding tests (pytest-django) so refactors stay safe.