Django tutorial written like I’m sitting next to you explaining it step by step (as of early 2026). We’ll build a classic polling application (very similar to the official one but explained more conversationally, with extra tips, common mistakes, and modern touches).
Current stable version: Django 6.0.1 (released January 2026). It supports Python 3.12–3.14.
Let’s go!
0. Before we start – Prerequisites (5–10 minutes)
Make sure you have:
- Python 3.12 / 3.13 / 3.14 installed Check: python –version or python3 –version
- pip is up to date python -m pip install –upgrade pip
- A code editor (VS Code + Python extension is great)
- Basic terminal comfort (cd, mkdir, etc.)
If something is missing → Google “install Python 3.13 Windows/macOS/Linux” for your OS.
1. Create virtual environment (very important!)
Never install packages globally.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# Create project folder mkdir mypolls && cd mypolls # Create virtual env (2026 style – venv is still king) python -m venv venv # Activate it # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate |
You should now see (venv) in your prompt.
2. Install Django 6.0.1
|
0 1 2 3 4 5 6 |
pip install Django==6.0.1 |
Check it worked:
|
0 1 2 3 4 5 6 7 |
python -m django --version # Should print: 6.0.1 |
3. Create the Django project
|
0 1 2 3 4 5 6 |
django-admin startproject mysite . |
Note the dot (.) at the end → it creates the project in the current folder (cleaner structure).
Now your folder looks like:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
mypolls/ ├── manage.py ├── mysite/ │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── venv/ |
4. Run the development server (first “Hello World”)
|
0 1 2 3 4 5 6 |
python manage.py runserver |
Open http://127.0.0.1:8000/
You should see the beautiful “The install worked successfully!” rocket page.
→ Press Ctrl+C to stop it when done.
5. Create your first app – polls
Django projects are made of apps. One project → many reusable apps.
|
0 1 2 3 4 5 6 |
python manage.py startapp polls |
New folder appears: polls/
Typical app structure:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
polls/ ├── __init__.py ├── admin.py ├── apps.py ├── migrations/ ├── models.py ├── tests.py └── views.py |
6. Tell Django about your new app
Open mysite/settings.py → find INSTALLED_APPS list and add:
|
0 1 2 3 4 5 6 7 8 9 |
INSTALLED_APPS = [ # ... other default apps ... 'polls.apps.PollsConfig', # ← add this line ] |
(Using PollsConfig is the modern / recommended way since Django 1.9+)
7. Write your first view (very simple)
Open polls/views.py
|
0 1 2 3 4 5 6 7 8 9 |
from django.http import HttpResponse def index(request): return HttpResponse("Hello, world. You're at the polls index.") |
This is the most basic view possible.
8. Connect view → URL
Create polls/urls.py (new file!)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
from django.urls import path from . import views urlpatterns = [ path("", views.index, name="index"), ] |
Now tell the main project to include this URLconf.
Open mysite/urls.py and change it to:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
from django.contrib import admin from django.urls import include, path urlpatterns = [ path("polls/", include("polls.urls")), # ← important! path("admin/", admin.site.urls), ] |
9. Test it!
|
0 1 2 3 4 5 6 |
python manage.py runserver |
Go to: http://127.0.0.1:8000/polls/
→ You should see: Hello, world. You’re at the polls index.
🎉 First working page!
10. Models – Database time (Question + Choice)
Open polls/models.py
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
from django.db import models from django.utils import timezone import datetime class Question(models.Model): question_text = models.CharField(max_length=200) pub_date = models.DateTimeField("date published") def __str__(self): return self.question_text def was_published_recently(self): return self.pub_date >= timezone.now() - datetime.timedelta(days=1) class Choice(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE) choice_text = models.CharField(max_length=200) votes = models.IntegerField(default=0) def __str__(self): return self.choice_text |
Important notes:
- ForeignKey = many-to-one relationship
- on_delete=models.CASCADE = if question deleted → delete all choices
- __str__ = better display in admin & shell
11. Make & run migrations
|
0 1 2 3 4 5 6 7 8 9 10 |
# Create migration files (like git diff for DB) python manage.py makemigrations polls # Actually apply them to database (creates tables) python manage.py migrate |
You’ll see something like:
|
0 1 2 3 4 5 6 7 8 9 |
Migrations for 'polls': polls/migrations/0001_initial.py - Create model Question - Create model Choice |
Django auto-creates id primary key for every model.
12. Play with the API in shell (very useful for learning)
|
0 1 2 3 4 5 6 |
python manage.py shell |
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
from polls.models import Question, Choice from django.utils import timezone # Create object (method 1) q = Question(question_text="What's your favorite color?", pub_date=timezone.now()) q.save() # Or shorter (method 2 – recommended) Question.objects.create(question_text="Best football team?", pub_date=timezone.now()) # All questions Question.objects.all() # Filter Question.objects.filter(id=1) # Get one (raises exception if not found) Question.objects.get(pk=1) # Update q = Question.objects.get(pk=1) q.question_text = "What's new?" q.save() # Delete q.delete() |
Exit with exit()
13. Activate the killer feature – Django Admin
Create superuser:
|
0 1 2 3 4 5 6 |
python manage.py createsuperuser |
Follow prompts (username, email, password).
Start server again → http://127.0.0.1:8000/admin/
Login → but you see nothing about polls yet.
Fix: open polls/admin.py
|
0 1 2 3 4 5 6 7 8 9 10 11 |
from django.contrib import admin from .models import Question, Choice admin.site.register(Question) admin.site.register(Choice) |
Refresh admin → now you can create/edit Questions and Choices!
Pro tip 2026: make it nicer
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
@admin.register(Question) class QuestionAdmin(admin.ModelAdmin): list_display = ("question_text", "pub_date", "was_published_recently") list_filter = ["pub_date"] search_fields = ["question_text"] fieldsets = [ (None, {"fields": ["question_text"]}), ("Date information", {"fields": ["pub_date"], "classes": ["collapse"]}), ] class ChoiceInline(admin.TabularInline): model = Choice extra = 3 @admin.register(Question) class QuestionAdmin(admin.ModelAdmin): # ... previous stuff ... inlines = [ChoiceInline] |
Much better UX!
14. Next steps – Template + Detail view (brief roadmap)
Create polls/templates/polls/index.html
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{% if latest_question_list %} <ul> {% for question in latest_question_list %} <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li> {% endfor %} </ul> {% else %} <p>No polls are available.</p> {% endif %} |
Update view:
|
0 1 2 3 4 5 6 7 8 9 10 11 |
from django.shortcuts import render from .models import Question def index(request): latest_question_list = Question.objects.order_by("-pub_date")[:5] return render(request, "polls/index.html", {"latest_question_list": latest_question_list}) |
And continue to:
- Detail view
- Results view
- Voting (POST handling + F() expressions)
- Generic class-based views
- Forms
- Static files & templates inheritance
- Testing
- Deployment (Railway.app, Render, Fly.io are popular in 2026)
