Chapter 11: Django Models
perfect timing to talk about Django Models, which are truly the backbone of any real Django application.
You’ve already got:
- Virtual env + Django 6.0.1 installed
- Project (mysite)
- App (polls)
- Views, URLs, templates basics
Now models let you define your data structure — like “What is a Question? What is a Choice?” — and Django automatically handles creating database tables, relationships, queries, validation, and even the admin interface for free.
Think of models as smart Python classes that represent database rows/tables. One model class = one database table (usually). Django’s ORM (Object-Relational Mapper) lets you work with database data using pure Python — no raw SQL needed for 90% of cases.
We’ll build the classic polls models step-by-step (exactly like the official tutorial but explained more conversationally, with extra tips, common mistakes, and 2026 notes).
Step 1: Where Do Models Live?
In your app folder:
|
0 1 2 3 4 5 6 7 8 |
polls/ ├── models.py ← this is the file! ├── ... |
Open polls/models.py (it’s almost empty by default — perfect).
Step 2: Import What We Need
At the top:
|
0 1 2 3 4 5 6 7 8 |
from django.db import models from django.utils import timezone # helpful for dates import datetime # for timedelta |
Step 3: Define the Two Main Models – Question & Choice
We’ll create:
- Question: A poll question (e.g. “What’s your favorite color?”)
- Choice: An option for that question (e.g. “Red”, “Blue”) with vote count
|
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
class Question(models.Model): """ Represents a poll question. Each question has text and a publication date. """ question_text = models.CharField( max_length=200, verbose_name="Question text", # shows nicely in admin/forms ) pub_date = models.DateTimeField( verbose_name="Date published", default=timezone.now, # auto-set to now when created ) def __str__(self): return self.question_text # better display in admin/shell def was_published_recently(self): """ Custom method: True if published in last 24 hours. Useful in templates or admin. """ now = timezone.now() return now - datetime.timedelta(days=1) <= self.pub_date <= now class Meta: verbose_name = "Poll Question" verbose_name_plural = "Poll Questions" ordering = ["-pub_date"] # newest first in lists class Choice(models.Model): """ An answer option for a Question. Many choices belong to one question (ForeignKey). """ question = models.ForeignKey( Question, on_delete=models.CASCADE, # if question deleted → delete choices related_name="choices", # access via question.choices.all() ) choice_text = models.CharField( max_length=200, verbose_name="Choice text", ) votes = models.IntegerField( default=0, verbose_name="Number of votes", ) def __str__(self): return self.choice_text class Meta: verbose_name = "Choice" verbose_name_plural = "Choices" |
Key concepts explained like a teacher:
- Every model inherits from models.Model → gives ORM magic
- Fields = class attributes (like question_text = models.CharField(…))
- Common field types:
- CharField → short text (always need max_length)
- DateTimeField → date + time
- IntegerField → numbers
- ForeignKey → many-to-one relationship (many choices → one question)
- on_delete=models.CASCADE → “if parent gone, children gone” (most common)
- __str__() → what shows in admin, shell, debug (never leave default!)
- class Meta → model options (ordering, names, permissions…)
- verbose_name → human-friendly names in admin/forms
- Custom method (was_published_recently) → call in templates: {{ question.was_published_recently }}
Step 4: Make & Apply Migrations (Create Tables!)
Django doesn’t touch the database until you tell it to.
-
Detect changes & create migration files (like git commits for DB)
Bash0123456python manage.py makemigrations pollsOutput something like:
text0123456789Migrations for 'polls':polls/migrations/0001_initial.py- Create model Question- Create model ChoiceThese files are Python code — safe to commit to git!
-
Apply migrations (actually run SQL to create tables)
Bash0123456python manage.py migrate- First time → also creates Django’s built-in tables (auth, sessions, admin…)
- Output: “Applying polls.0001_initial… OK”
Now your SQLite database (db.sqlite3) has real tables!
Step 5: Play with Models in the Shell (Super Useful for Learning!)
|
0 1 2 3 4 5 6 |
python manage.py shell |
Inside Python 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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
from polls.models import Question, Choice from django.utils import timezone # Create a question (method 1 - verbose) q = Question( question_text="What's the best biryani in Hyderabad?", pub_date=timezone.now() ) q.save() # actually inserts into DB # Shorter & recommended (create + save in one) Question.objects.create( question_text="Favorite Python web framework?", pub_date=timezone.now() ) # All questions Question.objects.all() # → <QuerySet [<Question: What's the best...>]> # Filter Question.objects.filter(question_text__contains="biryani") # Get one (raises DoesNotExist if missing) q = Question.objects.get(pk=1) # pk = primary key (auto id) # Update q.question_text = "Best biryani spot in Gachibowli?" q.save() # Create related choice Choice.objects.create( question=q, choice_text="Paradise", votes=0 ) # Access reverse relation (thanks to related_name) q.choices.all() # → all choices for this question # Delete q.delete() # cascades to choices too |
Exit with exit()
Step 6: Register Models in Admin (Instant CRUD UI!)
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 # Simple version admin.site.register(Question) admin.site.register(Choice) |
Better version (recommended – makes admin much nicer):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
@admin.register(Question) class QuestionAdmin(admin.ModelAdmin): list_display = ("question_text", "pub_date", "was_published_recently") list_filter = ["pub_date"] search_fields = ["question_text"] # Show choices inline (add/edit right on question page) class ChoiceInline(admin.TabularInline): model = Choice extra = 3 # show 3 empty slots inlines = [ChoiceInline] @admin.register(Choice) class ChoiceAdmin(admin.ModelAdmin): list_display = ("choice_text", "question", "votes") list_filter = ["question"] |
Run server → http://127.0.0.1:8000/admin/ Login (create superuser if not done: python manage.py createsuperuser) → You can now add/edit questions & choices via beautiful UI!
Common Beginner Mistakes & Fixes
- No changes detected in makemigrations → forgot to save models.py or typo
- OperationalError: no such table → forgot migrate after makemigrations
- FieldError → wrong field name or missing max_length on CharField
- RelatedObjectDoesNotExist → trying q.choices before saving q
- Migrations conflict → deleted old migration files manually → use –fake carefully or reset DB
Quick Recap Table – Core Model Ideas
| Concept | Example in Code | Why Important |
|---|---|---|
| Model class | class Question(models.Model): | Defines table structure |
| Field | question_text = models.CharField(…) | Columns in DB |
| ForeignKey | question = models.ForeignKey(Question, …) | Relationships (many-to-one) |
| __str__() | return self.question_text | Human-readable representation |
| Custom method | def was_published_recently(self): | Business logic, usable in templates/admin |
| Meta class | ordering = [“-pub_date”] | Default sort, names, permissions |
| ORM Manager | Question.objects.all() | Query interface (filter, get, create…) |
What’s Next? Your Call!
You’ve now:
- Defined models
- Created & applied migrations
- Played in shell
- Got admin working
Tell me:
- “Let’s add more fields / relationships (ManyToMany, OneToOne)”
- “How to query models in views (filter, order_by, count…)”
- “Explain migrations deeper (squash, fake, rollback)”
- “Show me how to use models in templates (with for loops)”
- Or “I got this error after migrate — help!” (paste it)
You’re building a full app now — models are the foundation, and yours is solid! 💪🚀 Let’s keep going!
