Complete Django Web Development Tutorial: From Scratch to Deployment
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.
![]()
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
Postmodel 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 settingsconfig/urls.py: root URL routerconfig/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)|upperis 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).
![]()
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:
- Environment variables
SECRET_KEY(never commit)DEBUG=0ALLOWED_HOSTS(your domain)
- Production settings
SECURE_SSL_REDIRECT=True(if behind HTTPS)SESSION_COOKIE_SECURE=True,CSRF_COOKIE_SECURE=True(with HTTPS)
- Static files
- Add WhiteNoise for simple hosting:
Inpip install whitenoiseMIDDLEWARE(near the top):
Then:"whitenoise.middleware.WhiteNoiseMiddleware",python manage.py collectstatic
- Add WhiteNoise for simple hosting:
- App server
- Use Gunicorn:
Start command example:pip install gunicorngunicorn config.wsgi:application
- Use Gunicorn:
- Database migrations
- Run
python manage.py migrateduring release/build
- Run
- Security sanity check
- Run:
python manage.py check --deploy
- Run:
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.
Rate this tutorial
Sign In to rate this tutorial
More to Explore



Comments (0)
Sign In to join the discussion
Scroll down to load comments and ratings