Django automatically generates SQL from model definitions.
# blog/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)
Field types define how data is stored in the database.
# blog/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)
CharField represents short strings
TextField represents long text
DateTimeField(auto_now_add=True) represents timestamp when created
Applying Migrations
After modifying models, create migration scripts:
python3 manage.py makemigrations
Apply them and update the database schema:
python3 manage.py migrate
Django now creates the corresponding SQL tables.
Common Django Model Fields
Field Type
Description
Example
CharField
Short strings
CharField(max_length=100)
TextField
Large text
TextField()
IntegerField
Integers
IntegerField()
DateTimeField
Date and time values
DateTimeField(auto_now_add=True)
BooleanField
True/False
BooleanField(default=False)
ForeignKey
Many-to-one relationship
ForeignKey(User, on_delete=models.CASCADE)
FileField
File uploads
FileField(upload_to="uploads/")
Adding Relationships Between Models
Django supports three common relational patterns:
One-to-many → ForeignKey
Many-to-many → ManyToManyField
One-to-one → OneToOneField
# blog/models.py
from django.db import models
from django.contrib.auth.models import User
class Comment(models.Model):
post = models.ForeignKey("Post", on_delete=models.CASCADE)
author = models.ForeignKey(User, on_delete=models.CASCADE)
text = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
ForeignKey links each comment to one post.
on_delete=models.CASCADE ensures comments disappear when a post is deleted.
Useful Model Methods
You can add custom methods to models:
class Post(models.Model):
title = models.CharField(max_length=200)
body = models.TextField()
def __str__(self):
return self.title
def preview(self):
return self.body[:50] + "..."
__str__ improves admin display.
preview returns a short snippet of the body.
Querying Models Using the ORM
Use the Django shell for experimenting:
python3 manage.py shell
Example operations:
from blog.models import Post
# Create
Post.objects.create(title="Hello", body="First post!")
# Read
Post.objects.all()
# Filter
Post.objects.filter(title__icontains="hello")
# Order
Post.objects.order_by("-created_at")
Meta Options (Model Configuration)
The Meta class customizes model behavior.
class Post(models.Model):
title = models.CharField(max_length=200)
class Meta:
ordering = ["-id"]
verbose_name = "Blog Post"
verbose_name_plural = "Blog Posts"
ordering → default sort order
verbose_name → human-friendly name in admin
Registering Models in the Admin
To manage models via Django Admin, you must register them:
# blog/admin.py
from django.contrib import admin
from .models import Post, Comment
admin.site.register(Post)
admin.site.register(Comment)
Object–Relational Mapper (ORM) in Django
What Is the Django ORM?
The Django ORM is a powerful system that allows you to interact with databases using Python classes instead of writing SQL manually.
ORM converts Python model operations into SQL queries automatically.
python3 -m django startproject mysite
cd mysite
python3 -m django startapp blog
Models and Tables
Define a model (maps to a database table):
# blog/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)
Each attribute becomes a database column.
Django creates the SQL table automatically through migrations.
Migrations: Create the Database Schema
Generate migration scripts:
python3 manage.py makemigrations
Apply migrations:
python3 manage.py migrate
Your database now contains a Post table.
Creating Objects (INSERT)
Use the model's manager objects to create records:
from blog.models import Post
Post.objects.create(title="Hello", body="First post")
This generates SQL like:
INSERT INTO blog_post (title, body, created_at) VALUES (...);
But you do not write SQL — Django handles it.
Querying Objects (SELECT)
Get all objects:
Post.objects.all()
Filter using conditions:
Post.objects.filter(title__icontains="hello")
Get a single object:
Post.objects.get(id=1)
If no object is found, DoesNotExist error is raised.
Common ORM Lookups
Lookup
Example
Description
exact
title__exact="Hello"
Exact match
icontains
title__icontains="hello"
Case-insensitive substring match
gt
id__gt=3
Greater than
lt
id__lt=10
Less than
in
id__in=[1, 2, 3]
Filter within a list
startswith
title__startswith="Django"
Prefix match
Updating Objects (UPDATE)
Fetch an object, modify it, then save:
post = Post.objects.get(id=1)
post.title = "Updated title"
post.save()
# list of (title, rating)
Book.objects.values_list("title", "rating")
# flat list of titles
Book.objects.values_list("title", flat=True)
only() & defer() controls which fields are loaded (useful for large models):
# Only load title and rating, other fields deferred until accessed
qs = Book.objects.only("title", "rating")
# Load everything except big_text_field
qs = Book.objects.defer("big_text_field")
Relations: ForeignKey & Reverse Relations
Forward relation: from Book to Author:
book = Book.objects.get(pk=1)
author = book.author # follow ForeignKey
Reverse relation: from Author to Book:
author = Author.objects.get(pk=1)
# default is author.book_set, but we used related_name="books"
books = author.books.all()
Filter across relations using double underscores:
# Books with author name "Alice"
Book.objects.filter(author__name="Alice")
# Authors who have at least one book with rating >= 4
Author.objects.filter(books__rating__gte=4).distinct()
Problem: N+1 queries when accessing related objects in a loop.
select_related() performs JOIN
and loads related single-valued objects in one query. Used for ForeignKey, OneToOneField.
# Without select_related: 1 query for books, +1 per book for author
books = Book.objects.all()
# With select_related: 1 query total
books = Book.objects.select_related("author")
for book in books:
print(book.title, book.author.name)
prefetch_related() runs additional queries and joins results in Python. Used for many-valued relations: ManyToManyField, reverse ForeignKey, etc.
authors = Author.objects.prefetch_related("books")
for author in authors:
# does not hit DB per loop; books prefetched
print(author.name, [b.title for b in author.books.all()])
Aggregations & Annotations: aggregate(), annotate(), F
# Number of books per author
authors = Author.objects.annotate(book_count=Count("books"))
for a in authors:
print(a.name, a.book_count)
F expressions let you refer to field values in updates or filters:
from django.db.models import F
# Books where rating is greater than the author id (nonsense, but as example)
Book.objects.filter(rating__gt=F("author__id"))
# Increase rating by 1 for all books
Book.objects.update(rating=F("rating") + 1)
In this context, "Models Research" means searching through your Django model data.
You often search when users type something into a search bar:
search blog posts
filter products
find users
search comments
Example Model for Demonstration
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
Basic Searching With filter()
The simplest form of searching uses filter():
Post.objects.filter(title="Hello World")
Django translates this into an SQL WHERE query automatically.
class Post(models.Model):
...
objects = PostManager()
Now you can chain methods:
Post.objects.published().recent()
This is much more powerful than plain Managers.
Replacing the Default Manager
Sometimes you want the default QuerySet to always be filtered.
Example: hide soft-deleted items by default.
class ActiveManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_deleted=False)
class User(models.Model):
...
objects = ActiveManager() # replaces default manager
Now User.objects.all() hides deleted entries.
Performing Raw SQL Queries in Django Models
Why Use Raw SQL in Django?
Django’s ORM is very powerful, but sometimes you need direct SQL because:
a query is too complex for ORM
you want database-specific optimization
you want to call database functions or stored procedures
you need full manual control over SQL
Django allows raw SQL while keeping models safe and integrated.
Using .raw() for SELECT Queries
Every model gets Model.objects.raw() for raw SELECT statements.
Django will map the result rows into model instances.
# blog/models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
body = models.TextField()
posts = Post.objects.raw("SELECT * FROM blog_post")
for p in posts:
print(p.title)
The SQL must return all fields that the model requires.
You can also select additional fields — Django will ignore them.
Using Parameters (Avoid SQL Injection!)
Always use placeholders instead of string concatenation.
Django uses the database's native param syntax (usually %s).
title = "Hello"
posts = Post.objects.raw(
"SELECT * FROM blog_post WHERE title = %s",
[title]
)
This is safe and recommended.
Executing Non-SELECT Queries with connection.cursor()
To execute INSERT, UPDATE, DELETE, or custom SQL, use cursor().
You import it from:
from django.db import connection
Example: Manual INSERT
from django.db import connection
def insert_post(title, body):
with connection.cursor() as cursor:
cursor.execute(
"INSERT INTO blog_post (title, body) VALUES (%s, %s)",
[title, body]
)
Example: Manual UPDATE
with connection.cursor() as cursor:
cursor.execute(
"UPDATE blog_post SET title = %s WHERE id = %s",
["New Title", 1]
)
Example: Manual DELETE
with connection.cursor() as cursor:
cursor.execute("DELETE FROM blog_post WHERE id = %s", [1])
Fetching Results Manually
cursor can fetch rows manually:
with connection.cursor() as cursor:
cursor.execute("SELECT id, title FROM blog_post")
rows = cursor.fetchall()
for row in rows:
print(row) # (id, title)
Using dictfetchall() for Dictionary Results
Django doesn’t ship one, but the docs recommend writing your own helper.
def dictfetchall(cursor):
columns = [col[0] for col in cursor.description]
return [
dict(zip(columns, row))
for row in cursor.fetchall()
]
with connection.cursor() as cursor:
cursor.execute("SELECT id, title FROM blog_post")
results = dictfetchall(cursor)
for item in results:
print(item["id"], item["title"])
Raw Query With Model Mapping + Extra Fields
Django ignores extra fields unless you alias one of them as pk (primary key) with AS.
posts = Post.objects.raw(
"SELECT id, title, body, NOW() as retrieved_at FROM blog_post"
)
for p in posts:
print(p.title) # model field
print(p.retrieved_at) # extra attribute
Django attaches unknown columns as attributes.
Running Raw SQL on a Specific Database (Multiple DBs)
from django.db import connections
with connections["analytics"].cursor() as cursor:
cursor.execute("SELECT COUNT(*) FROM stats_table")
result = cursor.fetchone()
Using Model.objects.raw() With Complex Joins
posts = Post.objects.raw("""
SELECT p.*, COUNT(c.id) AS comment_count
FROM blog_post p
LEFT JOIN blog_comment c ON p.id = c.post_id
GROUP BY p.id
""")
You get model instances + an extra attribute comment_count.
Warnings When Using Raw SQL
NEVER manually concatenate SQL strings — always use placeholders.
Raw SQL bypasses type checking.
Raw SQL bypasses database abstraction (not portable across databases).
Raw UPDATE / DELETE do NOT trigger model signals (pre_save, post_delete, etc.).
Raw queries do NOT use Django’s model validation.
Retrieving model instances from .raw() bypasses QuerySet optimizations.
Summary
Django supports raw SQL through .raw() and connection.cursor().
.raw() is used for SELECT queries that map to model instances.
cursor() is used for INSERT, UPDATE, DELETE, and custom SQL.
Query parameters should always use placeholders to avoid SQL injection.
Raw SQL is powerful but should be used sparingly — prefer ORM when possible.
Database Transactions in Django Models
What Are Database Transactions?
A transaction is a group of database operations that behave like a single unit.
If any operation fails, the entire group is rolled back.
Transactions guarantee the famous ACID properties:
Atomicity – all or nothing
Consistency – DB stays valid
Isolation – transactions don’t interfere
Durability – committed data is saved
Django’s Transaction API Overview
Provided by django.db.transaction:
from django.db import transaction
Tools include:
transaction.atomic
transaction.on_commit
transaction.set_rollback()
transaction.set_autocommit()
Using transaction.atomic (The Most Important Tool)
atomic makes a block of code run inside a single transaction.
If an exception occurs, everything is rolled back.
from django.db import transaction
def create_order(user, product, qty):
with transaction.atomic():
order = Order.objects.create(user=user)
OrderItem.objects.create(order=order, product=product, quantity=qty)
product.stock -= qty
product.save()
If product.save() fails, Order and OrderItem are rolled back.
The key ("analytics") becomes the database alias used throughout Django.
Running Migrations on a Specific Database
python3 manage.py migrate --database=analytics
By default, migrations run against the default database.
You choose where each model's tables should reside.
Saving an Object to a Specific Database
You can specify which DB to write to using using().
post = Post(title="Hello", body="World")
post.save(using="analytics")
Querying a Specific Database
Post.objects.using("analytics").all()
Queries do not mix records across databases.
Deleting From a Specific Database
post.delete(using="analytics")
Using Multiple Databases with Raw SQL
from django.db import connections
with connections["analytics"].cursor() as cursor:
cursor.execute("SELECT COUNT(*) FROM blog_post")
count = cursor.fetchone()
Database Routers (Core of Multi-DB Architecture)
A database router decides:
which DB to read from
which DB to write to
where to migrate models
whether relations between databases are allowed
You implement routers in a separate Python file.
# mysite/dbrouters.py
class AnalyticsRouter:
app_label = "analytics_app"
def db_for_read(self, model, **hints):
if model._meta.app_label == self.app_label:
return "analytics"
return None
def db_for_write(self, model, **hints):
if model._meta.app_label == self.app_label:
return "analytics"
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
if app_label == self.app_label:
return db == "analytics"
return None
This router sends all models of analytics_app to the "analytics" DB.
Important: transactions do not span multiple databases automatically!
You must explicitly use atomic() per database:
from django.db import transaction
with transaction.atomic(using="default"):
# write to default DB
...
with transaction.atomic(using="analytics"):
# write to analytics DB
...
No automatic distributed transactions (i.e., no two-phase commit).
Cross-Database Foreign Keys Are Not Allowed
Django does not allow foreign keys between databases.
If needed, you must handle relations manually (using integer fields, UUIDs, etc.).
Routers can be configured to reject cross-database relationships:
CREATE TABLE app_post (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title VARCHAR(200) NOT NULL,
body TEXT NOT NULL
);
The mapping is:
Python class → SQL table
Model fields → SQL columns
Model instances → rows
Automatic Primary Key Field (id)
If you do not define a primary key, Django adds one automatically:
id = models.AutoField(primary_key=True)
This auto-id increments for each row.
You may override it:
class User(models.Model):
user_id = models.UUIDField(primary_key=True)
username = models.CharField(max_length=50)
Defining Fields (Columns)
All fields are defined using models.<FieldType>.
Examples:
class Product(models.Model):
name = models.CharField(max_length=100)
price = models.DecimalField(max_digits=8, decimal_places=2)
available = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
Each field tells Django:
What SQL type to use (VARCHAR, TEXT, INTEGER, DATE, etc.)
Whether it is nullable, unique, indexed
Default values
Constraints
Model Instances = Rows
You create a row by instantiating a model:
p = Product(name="Laptop", price=999.99)
p.save() # INSERT into database
Saving calls an INSERT statement.
Updating an instance calls an UPDATE.
p.price = 899.99
p.save() # UPDATE in database
ORM tracks the row by primary key (p.id).
Deleting Rows
p = Product.objects.get(id=1)
p.delete() # DELETE FROM table
Meta Class: Controlling Table Options
Each model can define a nested Meta class.
This controls Django’s behavior for the table:
class Product(models.Model):
name = models.CharField(max_length=100)
price = models.DecimalField(max_digits=8, decimal_places=2)
class Meta:
db_table = "store_products"
ordering = ["name"]
verbose_name = "Product"
verbose_name_plural = "Products"
indexes = [
models.Index(fields=["name"]),
]
class Category(models.Model):
name = models.CharField(max_length=50)
class Product(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
ORM automatically creates reverse relationships:
cat = Category.objects.get(id=1)
cat.product_set.all() # All products in the category
Model Managers
Every model has at least one manager named objects.
The manager is the entry point for creating queries:
products = Product.objects.filter(price__lt=50)
You can define custom managers to override query defaults.
Full Lifecycle of a Model Row
Create:
obj = Product.objects.create(name="Pen", price=2)
Retrieve:
obj = Product.objects.get(id=1)
Update:
obj.price = 3
obj.save()
Delete:
obj.delete()
Understanding the class Meta Options in Django Models
What Is theMetaClass?
The inner class Meta inside a Django model is a special configuration class.
It tells Django how to behave when generating:
database table names
sorting rules
indexes
constraints
permissions
model names
relationships between apps
Meta does not affect Python logic — it affects Django ORM behavior, database schema, and admin interface.
Basic Structure of Meta
class Product(models.Model):
name = models.CharField(max_length=50)
class Meta:
ordering = ["name"]
The Meta class must be inside the model and indented.
All its attributes are optional.
Full List of Common Meta Options
Option
Purpose
db_table
Custom SQL table name
ordering
Default ordering for QuerySets
verbose_name
Human-readable singular name
verbose_name_plural
Plural human-readable name
indexes
Custom database indexes
unique_together
Multi-field uniqueness constraint (legacy)
constraints
Advanced database constraints
permissions
Custom permission definitions
default_related_name
Reverse relationship name
app_label
Assign model to a different app
managed
If False, Django will NOT create or modify the table
get_latest_by
Field used by QuerySet.latest()
ordering
Set default ordering (ASC/DESC)
abstract
Create abstract base classes
proxy
Create proxy models
db_table — Custom Table Name
By default Django names tables like appname_modelname.
You can override this:
class Product(models.Model):
name = models.CharField(max_length=50)
class Meta:
db_table = "ecommerce_products"
The database table will now be called ecommerce_products.
ordering — Default QuerySet Order
This determines how QuerySets are sorted unless you override it.
class Post(models.Model):
created_at = models.DateTimeField()
class Meta:
ordering = ["-created_at"]
Post.objects.all() will always be newest → oldest.
Supports multiple fields.
verbose_name & verbose_name_plural
Used by Django Admin for labels.
class Product(models.Model):
class Meta:
verbose_name = "Product Item"
verbose_name_plural = "Product Items"
indexes — Creating Database Indexes
class User(models.Model):
username = models.CharField(max_length=50)
email = models.EmailField()
class Meta:
indexes = [
models.Index(fields=["username"]),
models.Index(fields=["email"]),
]
Django will generate SQL for index creation.
This improves query performance.
unique_together (Deprecated in Favor of UniqueConstraint)
class Enrollment(models.Model):
student = models.ForeignKey("Student", on_delete=models.CASCADE)
course = models.ForeignKey("Course", on_delete=models.CASCADE)
class Meta:
unique_together = ["student", "course"]
Ensures that one student cannot enroll in the same course twice.
Use constraints instead in modern Django.
constraints — Advanced SQL Constraints
from django.db import models
class Product(models.Model):
price = models.DecimalField(max_digits=10, decimal_places=2)
class Meta:
constraints = [
models.CheckConstraint(check=models.Q(price__gte=0), name="price_positive")
]
Types of constraints:
CheckConstraint
UniqueConstraint
ForeignKey constraints are handled automatically
permissions — Custom Permissions
class Report(models.Model):
class Meta:
permissions = [
("view_sensitive_data", "Can view sensitive data"),
("export_reports", "Can export reports"),
]
Django creates these permissions in the database.
Often used with the Admin interface.
default_related_name — Reverse Relation Name
class Author(models.Model):
class Meta:
default_related_name = "authors"
This affects reverse relationships used by ForeignKeys.
managed — Should Django Create This Table?
class LegacyUser(models.Model):
legacy_id = models.IntegerField()
class Meta:
managed = False
db_table = "legacy_user_table"
Use when working with a table that Django should NOT create or modify.
Common for legacy databases.
get_latest_by — Field Used by latest()
class Event(models.Model):
timestamp = models.DateTimeField()
class Meta:
get_latest_by = "timestamp"
Now Event.objects.latest() works.
abstract — Create an Abstract Base Class
class BaseTimestamp(models.Model):
created = models.DateTimeField(auto_now_add=True)
class Meta:
abstract = True
No table is created for abstract models.
They serve as base classes for other models.
proxy — Create a Proxy Model
class OrderedUser(User):
class Meta:
proxy = True
ordering = ["username"]
No new table created — same table, different Python class.
Used to add methods or change default ordering.
app_label — Force a Model into a Different App
Normally, Django determines an app by the folder name.
You can override this:
class Report(models.Model):
class Meta:
app_label = "analytics"
Do this only when necessary (e.g., models in a shared folder).
Understanding Tablespaces in Django Models
What Are Tablespaces?
A tablespace is a storage location inside a database where tables and indexes can be placed.
They are supported by some database systems such as:
PostgreSQL
Oracle
SQLite and MySQL do not support them in the same way, so tablespace features may be limited.
In Django, you can tell the ORM where to store the:
table itself
indexes for the table
This is primarily useful in enterprise environments where DBAs organize storage across disks for:
performance optimization
backup strategies
clustered or distributed setups
Basic Syntax for Using Tablespaces
You define tablespaces inside the model’s Meta class:
class Report(models.Model):
name = models.CharField(max_length=100)
class Meta:
db_tablespace = "report_tablespace"
default_related_name = "reports"
db_tablespace tells Django where to store this model's table.
How to Set Tablespaces for Indexes
Django also allows placing indexes in separate tablespaces:
class Report(models.Model):
title = models.CharField(max_length=200)
class Meta:
db_tablespace = "ts_reports"
indexes = [
models.Index(fields=["title"], db_tablespace="ts_indexes"),
]
Now:
The table goes into ts_reports
The index goes into ts_indexes
Setting Tablespaces for All Models in an App
You can define tablespaces globally inside settings.py:
Saves you from setting tablespaces on each model manually.
Model-level configuration overrides global defaults.
Full Example: Table + Index Tablespaces
class Document(models.Model):
title = models.CharField(max_length=255)
uploaded_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_tablespace = "docs_ts"
indexes = [
models.Index(
fields=["uploaded_at"],
name="upload_time_idx",
db_tablespace="docs_idx_ts",
),
]
This is how you split data storage vs index storage.
Checking Tablespaces in Generated SQL
You can inspect SQL using:
python3 manage.py sqlmigrate appname 0001
Example SQL (PostgreSQL):
CREATE TABLE "app_document" (
"id" serial NOT NULL PRIMARY KEY,
"title" varchar(255) NOT NULL,
"uploaded_at" timestamp with time zone NOT NULL
) TABLESPACE docs_ts;
CREATE INDEX upload_time_idx
ON "app_document" ("uploaded_at")
TABLESPACE docs_idx_ts;
This confirms Django applied your tablespace configuration.
What Happens if Your Database Doesn’t Support Tablespaces?
Django will silently ignore db_tablespace for databases that don't support it.
This means:
SQLite → ignored
MySQL → mostly ignored
PostgreSQL → fully used
Oracle → fully used
Django ensures migrations remain portable.
Database Access Optimization in Django
Why Database Optimization Matters
Django’s ORM is powerful, but databases are often the slowest part of your application.
Optimization prevents:
N+1 query problems
excessive round-trips to the database
slow pages during heavy load
unnecessary joins
full-table scans and index misses
Django Debug Toolbar (Essential Tool)
Before optimizing, you must first measure.
Install:
python3 -m pip install django-debug-toolbar
This tool shows:
number of queries
execution time
duplicated queries
stack traces of query sources
Always profile before optimizing.
Useselect_related()for ForeignKey and OneToOne
By default, Django loads related objects lazily:
for book in Book.objects.all():
print(book.author.name)
This causes an N+1 query problem:
1 query to fetch books
N queries to fetch each book’s author
Solve it using select_related():
books = Book.objects.select_related("author")
for book in books:
print(book.author.name)
Now Django performs ONE JOIN query only
Useprefetch_related()for Many-to-Many and Reverse Relations
ForeignKey = use select_related
ManyToMany or related sets = use prefetch_related
authors = Author.objects.prefetch_related("books")
for author in authors:
print(author.books.all())
Saves massive numbers of DB hits.
Django performs 2 queries total:
Authors
Books
Then combines data in Python memory.
Avoid Loading Unneeded Fields (values() & only())
Use .values() for lightweight dict-style queries:
products = Product.objects.values("id", "name")
Prevents Django from loading big fields like TextField or JSONField.
Use .only() to load partial model fields:
users = User.objects.only("id", "username")
Django fetches missing fields lazily only when accessed.
Avoid.count()on QuerySets Without Filtering
total = Product.objects.count() # efficient
But avoid:
len(Product.objects.all()) # loads EVERYTHING into memory → slow!
Use count() instead.
Use Indexes for Fast Filtering
Indexes greatly speed up WHERE queries.
You can define indexes in Meta:
class Product(models.Model):
name = models.CharField(max_length=200)
class Meta:
indexes = [
models.Index(fields=["name"]),
]
Also remember:
unique=True automatically creates an index.
Useexists()Instead of Fetching Objects
if User.objects.filter(email=email).exists():
...
This generates:
SELECT (1) AS a FROM user WHERE email=... LIMIT 1;
Much faster than doing .get() or loading a full queryset.
Avoid Query Inside a Loop
Do NOT do:
for user in User.objects.all():
print(Order.objects.filter(user=user).count())
Fixtures serialize data for loading and exporting database content.
Located in fixtures/ directories or FIXTURE_DIRS.
Loaded using loaddata, created using dumpdata.
Excellent for:
initial data
demos
development
test environments
Support JSON, YAML, and XML.
Work seamlessly with Django’s ORM, including signals.
Best used for stable and predictable data structures.
URL Dispatcher in Django
What Is the URL Dispatcher?
The URL dispatcher is the core mechanism in Django that decides which view should handle an incoming HTTP request.
When the browser requests a URL (for example /blog/hello/), Django:
reads the path part of the URL
matches it against urlpatterns in one or more urls.py files
calls the first matching view function or class-based view
The dispatcher is configured by:
ROOT_URLCONF in settings.py
top-level urls.py in the project
app-level urls.py files, included via include()
Request Flow: From Browser to View
A high-level flow for a request to /blog/hello/:
Browser --> Django (WSGI/ASGI) --> ROOT_URLCONF (mysite/urls.py)
|
V
urlpatterns list
|
V
include("blog.urls") for "blog/"
|
V
blog/urls.py: path("hello/", views.hello)
|
V
views.hello(request)
Django:
reads ROOT_URLCONF (usually "mysite.urls") from settings.py
imports mysite/urls.py
iterates over urlpatterns in order until one pattern matches
calls the associated view with request and any captured arguments
if nothing matches, returns a 404 response
Basic URL Patterns withpath()
Most routes are defined via the path() helper.
Project-level urls.py:
# mysite/urls.py
from django.contrib import admin
from django.urls import path
from blog import views
urlpatterns = [
path("admin/", admin.site.urls),
path("hello/", views.hello), # maps /hello/ to views.hello
]
View example:
# blog/views.py
from django.http import HttpResponse
def hello(request):
return HttpResponse("Hello from the URL dispatcher!")
Now http://127.0.0.1:8000/hello/ invokes hello().
Regular Expressions withre_path()
For complex patterns, Django offers re_path(), which uses regular expressions.
from django.urls import re_path
from blog import views
urlpatterns = [
re_path(r"^archive/(?P<year>[0-9]{4})/$", views.year_archive),
]
Example view:
def year_archive(request, year):
return HttpResponse(f"Archive for year {year}")
Now /archive/2024/ calls year_archive(request, year="2024").
path() is simpler and recommended for most cases; re_path() is for advanced patterns.
Splitting Routes by App withinclude()
For better organization, each app usually defines its own urls.py.
App-level urls.py:
# blog/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("hello/", views.hello),
path("about/", views.about),
]
Project-level urls.py includes the app URLs:
# mysite/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("blog/", include("blog.urls")), # mounts blog.urls under /blog/
]
Resulting URLs:
/blog/hello/
/blog/about/
include() lets you build a tree of URLconfs.
Dynamic Segments with Path Converters
With path() you can capture parts of the URL as parameters.
Syntax: <converter:name>
# blog/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("post/<int:post_id>/", views.post_detail, name="post-detail"),
]
View receives the argument:
# blog/views.py
def post_detail(request, post_id):
return HttpResponse(f"Post id is {post_id}")
Built-in converters:
int → integer
str → non-empty string (no slash)
slug → letters, numbers, hyphen, underscore
uuid → UUID string
path → string including slashes
Custom Path Converters (Advanced)
You can define your own converters when built-ins are not enough.
Example: only accept lowercase letters.
# blog/converters.py
class LowercaseConverter:
regex = "[a-z]+"
def to_python(self, value):
return value # can transform the value if needed
def to_url(self, value):
return value.lower()
Register the converter:
# mysite/urls.py
from django.contrib import admin
from django.urls import path, include, register_converter
from blog.converters import LowercaseConverter
register_converter(LowercaseConverter, "lower")
urlpatterns = [
path("admin/", admin.site.urls),
path("blog/", include("blog.urls")),
]
Use it in your app URLs:
# blog/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("user/<lower:username>/", views.profile),
]
If nothing matches, Django returns a 404 Not Found page.
Trailing slash behavior is influenced by APPEND_SLASH: If APPEND_SLASH = True (default), Django may redirect /hello to /hello/.
Named URLs and Reverse Resolution
Giving a name to your URL pattern lets you refer to it without hardcoding the path.
# blog/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("hello/", views.hello, name="hello"),
]
Use reverse() in Python code:
from django.urls import reverse
url = reverse("hello") # returns "/hello/"
Use {% url %} in templates:
<a href="{% url 'hello' %}">Say hello</a>
Advantages:
changing the actual path does not break all links
safe refactoring
Class-Based Views and as_view()
Django encourages class-based views (CBVs) for reusable logic.
# blog/views.py
from django.views import View
from django.http import HttpResponse
class HelloView(View):
def get(self, request):
return HttpResponse("Hello from class-based view!")
URL pattern uses .as_view():
# blog/urls.py
from django.urls import path
from .views import HelloView
urlpatterns = [
path("hello/", HelloView.as_view(), name="hello-class"),
]
For generic views (like ListView, DetailView), you also use as_view() in the dispatcher.
Static Files, Media, and Other Helpers
During development, you can serve static and media files via URL dispatcher helpers.
Example (development only):
# mysite/urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path("admin/", admin.site.urls),
path("blog/", include("blog.urls")),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
The admin itself (/admin/) is just another URL pattern registered in the dispatcher.
Beginner's Guide to Views in Django
What Is a View?
A view is simply a Python function (or class) that Django calls when someone visits a URL in your website.
You can think of a view as:
input: an HTTP request from the browser
logic: your Python code runs
output: an HTTP response (text, HTML, JSON, etc.)
Your First View
Go to blog/views.py and create a simple function:
from django.http import HttpResponse
def hello(request):
return HttpResponse("Hello, Django beginner!")
This view:
receives the request
returns the text "Hello, Django beginner!" to the browser
Then map it to a URL:
# blog/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("hello/", views.hello),
]
Now visit http://127.0.0.1:8000/blog/hello/.
Views Should Return HttpResponse
Every view must return some kind of HttpResponse.
You can return simple text:
def hi(request):
return HttpResponse("Hi from Django!")
You can also return HTML as a string:
def html_example(request):
return HttpResponse("<h1>Welcome</h1><p>This is HTML.</p>")
Views That Use Templates
In real web apps, we don't want to write HTML in Python.
We use templates for cleaner separation.
Example view using render():
from django.shortcuts import render
def home(request):
return render(request, "blog/home.html")
Template example:
<!-- blog/templates/blog/home.html -->
<h1>Welcome to my blog</h1>
<p>This page is rendered using a Django template.</p>
<h1>Hello {{ name }}!</h1>
<p>Your hobby is {{ hobby }}.</p>
Views with URL Parameters
Sometimes URLs include variable parts like IDs.
URL route:
path("post/<int:id>/", views.show_post)
View receives the variable:
def show_post(request, id):
return HttpResponse(f"You requested post {id}")
Handling GET and POST Requests
Basic check for request method:
def contact(request):
if request.method == "GET":
return HttpResponse("This is the contact form page.")
if request.method == "POST":
return HttpResponse("Form submitted!")
Later, you can process forms using Django forms or request.POST.
Redirecting from a View
Use redirect() to send users to a different page:
from django.shortcuts import redirect
def go_home(request):
return redirect("/blog/hello/")
Class-Based Views (Very Beginner-Friendly)
Instead of writing functions, Django lets you create views as classes.
A very simple class-based view:
from django.views import View
from django.http import HttpResponse
class HelloView(View):
def get(self, request):
return HttpResponse("Hello from a class-based view!")
URL route:
path("hello-class/", HelloView.as_view())
Beginners can ignore CBVs at first, but they become useful later.
What Should a Beginner Focus On?
As a beginner, you should mainly understand:
How to create a view function
How to map a URL to a view
How to return text or HTML
How to render templates
How to pass data to templates
Ignore advanced stuff like mixins, decorators, or class inheritance until you're comfortable with basics.
Views in Django
What Is a View?
A view in Django is a Python function or class that receives a request and returns a response.
Views contain the business logic of your application:
querying the database
rendering templates
processing forms
returning JSON or HTML
redirecting users
Every view must return some kind of HttpResponse object.
Simple Function-Based View (FBV)
A basic function-based view looks like this:
# blog/views.py
from django.http import HttpResponse
def hello(request):
return HttpResponse("Hello from a Django view!")
Mapped in urls.py:
# blog/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("hello/", views.hello),
]
Request path → /hello/ executes hello().
Returning HTML with HttpResponse
You can return raw HTML as a string:
def home(request):
return HttpResponse("<h1>Welcome!</h1><p>This is raw HTML.</p>")
This is very common in modern REST or AJAX applications.
Handling URL Parameters
URL patterns can pass arguments to views.
# blog/views.py
def show_post(request, post_id):
return HttpResponse(f"Post ID is {post_id}")
URL route:
path("post/<int:post_id>/", views.show_post)
Class-Based Views (CBV)
Django offers class-based views for reusable, structured logic.
A simple CBV:
from django.views import View
from django.http import HttpResponse
class HelloView(View):
def get(self, request):
return HttpResponse("Hello from a class-based view!")
URL mapping:
path("hello/", HelloView.as_view())
Advantages of Class-Based Views
CBVs provide:
built-in methods (get, post, put, etc.)
clean separation of HTTP methods
mixins for reusable behavior
generic views for CRUD operations
For example, ListView, DetailView, CreateView, etc. help build apps faster.
Generic Class-Based Views (GCBV)
Generic Class-Based Views are ready-made view classes that perform common tasks for you.
You do not need to manually query the database or write boilerplate logic.
Django already knows how to list objects, show details, create forms, update records, delete records, etc.
Here is a simple example: displaying a list of all Post objects.
from django.views.generic import ListView
from .models import Post
class PostListView(ListView):
model = Post # Tell Django which model you want to list
template_name = "blog/posts.html"
context_object_name = "posts"
How this works:
Django automatically runs Post.objects.all() for you.
The result is stored in a context variable called object_list (or post_list).
The view then renders your template blog/posts.html and provides the list of posts as data.
Connecting the View to a URL:
path("posts/", PostListView.as_view())
Django will now serve /posts/ using your PostListView.
Whenever a user visits that page, Django automatically fetches the posts and renders the template.
from django.http import StreamingHttpResponse
def stream_numbers(request):
def generator():
for i in range(1, 6):
yield f"Number: {i}\n"
return StreamingHttpResponse(generator())
FileResponse (Efficient File Downloads)
Django recommends FileResponse for serving files:
from django.http import FileResponse
def download(request):
return FileResponse(open("report.pdf", "rb"), as_attachment=True)
Streams file data instead of loading the entire file into memory.
Writing Views in Django
What Does It Mean to “Write a View”?
Writing a view means creating Python code that decides what should happen when a user opens a certain URL.
Every view:
receives a request object
runs some logic (optional)
returns an HttpResponse (or render() template)
Views live inside the views.py file of your app.
Start With a Simple View
Open blog/views.py and write:
from django.http import HttpResponse
def hello(request):
return HttpResponse("Hello! This is my first view.")
Now create a URL route:
# blog/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("hello/", views.hello),
]
Visit /blog/hello/ and the text will appear.
Views That Return HTML
You can write HTML inside the response:
def welcome(request):
return HttpResponse("<h1>Welcome</h1><p>This is a simple HTML page.</p>")
Django will send this HTML to the browser.
Rendering Templates (Recommended)
For anything more than simple text, use templates.
Example view:
from django.shortcuts import render
def home(request):
return render(request, "blog/home.html")
Create the template:
<!-- blog/templates/blog/home.html -->
<h1>Blog Home</h1>
<p>This page was rendered using a template!</p>
This separates Python logic from HTML layout.
Passing Data to a Template
Views can send variables to templates using a dictionary (called a "context").
def about(request):
info = {
"title": "About This Blog",
"author": "Junzhe",
}
return render(request, "blog/about.html", info)
<!-- blog/templates/blog/about.html -->
<h1>{{ title }}</h1>
<p>Created by {{ author }}.</p>
def show_post(request, id):
return HttpResponse(f"You asked for post {id}")
Visiting /blog/post/7/ will return You asked for post 7.
Views That Handle GET and POST
Django views can check request methods.
def contact(request):
if request.method == "GET":
return HttpResponse("This is the contact page.")
elif request.method == "POST":
return HttpResponse("You submitted the form!")
This is the basis for form handling.
Redirects in Views
You can redirect to another URL easily.
from django.shortcuts import redirect
def go_to_home(request):
return redirect("/blog/home/")
This tells the browser to open a different page.
Writing Class-Based Views (Beginner Level)
Instead of using a function, Django allows writing views as classes.
A simple class-based view:
from django.views import View
from django.http import HttpResponse
class HelloView(View):
def get(self, request):
return HttpResponse("Hello from a class-based view!")
path("hello-class/", HelloView.as_view())
For now, it’s fine if you focus only on function-based views.
Where Should You Write Views?
Each app has its own views.py file:
blog/
├── admin.py
├── apps.py
├── models.py
├── views.py <-- write your views here
├── tests.py
└── migrations/
You write your project’s logic here.
A Good Beginner Workflow for Writing Views
Whenever you need a new page in your website:
step 1 → write the view in views.py
step 2 → connect it to a URL in urls.py
step 3 → (optional) create a template
step 4 → add logic or pass data as needed
This flow becomes second nature once you practice it a few times.
Beginner's Guide to View Decorators in Django
What Is a View Decorator?
A decorator in Django is something you place on top of a view function to give that view extra features or behavior.
You can think of it like:
“this view needs login first”
“this view should only accept certain HTTP methods”
“this view should run extra checks before executing”
The syntax always looks like this:
@decorator_name
def my_view(request):
...
The @decorator line wraps your view and adds extra rules.
Why Do We Use Decorators?
They help you avoid repeating the same logic in many views.
They make your code cleaner and more readable.
Django provides many useful built-in decorators.
Decorator #1: login_required
This is the most common decorator.
It makes sure the user is logged in before accessing the view.
If not logged in, Django automatically redirects to the login page.
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
@login_required
def dashboard(request):
return HttpResponse("Welcome to your dashboard!")
If the user is not logged in:
Django redirects them to /accounts/login/ (default)
Decorator #2: require_http_methods
This decorator restricts which HTTP methods are allowed for a view.
Example: allow only GET and POST:
from django.views.decorators.http import require_http_methods
from django.http import HttpResponse
@require_http_methods(["GET", "POST"])
def contact(request):
return HttpResponse("This view accepts only GET and POST")
If someone sends a PUT request, Django returns 405 (Method Not Allowed).
Decorator #3: require_GET and require_POST
These shortcuts restrict views to a single method.
from django.views.decorators.http import require_GET
@require_GET
def homepage(request):
return HttpResponse("Only GET requests are allowed here!")
from django.views.decorators.http import require_POST
@require_POST
def save_data(request):
return HttpResponse("Only POST requests accepted.")
Decorator #4: csrf_exempt
This decorator disables Django's CSRF protection for a specific view.
Beginners should use this carefully (only when you know why).
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def webhook(request):
return HttpResponse("CSRF check skipped for this view.")
Useful for external services posting data (GitHub webhooks, Stripe, etc.)
Decorator #5: cache_page
This caches (saves) the output of a view for a certain number of seconds.
from django.views.decorators.cache import cache_page
@cache_page(60) # cache for 60 seconds
def news(request):
return HttpResponse("This page is cached for 1 minute.")
Meaning: the view won't run again until the cache expires.
How Decorators Work (beginner explanation)
When you add @something above a view, Django replaces your view with a “wrapped” version.
You don’t need to fully understand decorators in Python yet, but the idea is:
@my_decorator
def my_view(request):
...
This is the same as:
my_view = my_decorator(my_view)
So the decorator can add rules before or after your view runs.
Decorators With Arguments
Some decorators need extra information.
Example: restrict to certain HTTP methods:
@require_http_methods(["GET", "POST"])
def example(request):
return HttpResponse("This view accepts only GET and POST")
The decorator receives your list ["GET", "POST"] as a parameter.
Using Multiple Decorators
You can “stack” decorators on top of each other.
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_GET
@login_required
@require_GET
def profile(request):
return HttpResponse("Your profile")
Order matters: the top-most decorator runs first
Decorators and Class-Based Views (Beginner Note)
Decorators can be used on CBVs using method_decorator, but beginners can skip this for now.
Just know that decorators do work with CBVs too.
Which Decorators Should Beginners Learn First?
If you are new, learn these 3:
login_required
require_GET / require_POST
csrf_exempt (use sparingly)
The others are useful but not needed for your first 2–3 weeks of Django learning.
File Uploads in Django (Beginner's Guide)
What Does “File Upload” Mean in Django?
File upload means allowing users to send files (images, PDFs, documents, videos) from their browser to your server.
Django handles uploads using:
request.FILES → uploaded file objects
HTML forms with enctype="multipart/form-data"
settings MEDIA_ROOT and MEDIA_URL to store and serve files
Step 1: Configure MEDIA_ROOT and MEDIA_URL
In mysite/settings.py add (or check) these lines:
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
MEDIA_ROOT = BASE_DIR / "media" # folder on disk for uploaded files
MEDIA_URL = "/media/" # URL prefix used in the browser
MEDIA_ROOT: real folder on the server (e.g. /home/user/project/media).
MEDIA_URL: how the browser reaches files in that folder (e.g. /media/...).
Step 2: Serve Media Files in Development
In development you let Django serve uploaded files for you.
In mysite/urls.py:
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path("admin/", admin.site.urls),
path("blog/", include("blog.urls")),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Now any file saved under MEDIA_ROOT can be accessed via URLs that start with MEDIA_URL.
enctype="multipart/form-data" is required for file uploads.
{% csrf_token %} is needed for POST forms in Django.
Step 4: View That Saves the Uploaded File (Manual Saving)
In blog/views.py:
from django.shortcuts import render
from django.http import HttpResponse
from django.conf import settings
import os
def upload_file(request):
if request.method == "POST":
uploaded = request.FILES.get("file")
if not uploaded:
return HttpResponse("No file selected.")
save_path = settings.MEDIA_ROOT / uploaded.name
with open(save_path, "wb") as f:
for chunk in uploaded.chunks():
f.write(chunk)
return HttpResponse(f"Uploaded: {uploaded.name}")
return render(request, "blog/upload.html")
Here we:
read the file from request.FILES
build a path using settings.MEDIA_ROOT (not a hardcoded "media/")
write the file in chunks (safe for big files)
Step 5: URL for the Upload View
In blog/urls.py:
from django.urls import path
from . import views
urlpatterns = [
path("upload/", views.upload_file, name="upload"),
]
Now /blog/upload/ shows the upload form.
Where Files Actually Go
After uploading picture.jpg, your project folder looks like:
<!-- blog/templates/blog/files.html -->
<h2>Uploaded Files</h2>
{% for f in files %}
<p>
<a href="{{ f.file.url }}">{{ f.file.name }}</a>
</p>
{% endfor %}
{{ f.file.url }} automatically uses MEDIA_URL, so you don't hardcode /media/....
Serving Media Files: Development vs Production
In development, Django can serve uploaded files for you using a helper in urls.py:
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
# your normal routes here...
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
This means:
When DEBUG = True, any URL starting with MEDIA_URL (for example /media/)
is served directly from files in MEDIA_ROOT (for example BASE_DIR / "media").
This helper is only meant for local development, not for production.
In production:
Django still saves files into MEDIA_ROOT.
But a real web server (like Nginx or Apache) should serve those files for requests that start with MEDIA_URL.
You normally do not use the static(...) helper when DEBUG = False.
A very simplified Nginx example (just to show the idea, not full config):
location /media/ {
alias /var/www/myproject/media/;
}
As a beginner you can remember:
Development: Django serves media using static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT).
Production: your web server (or a storage service like S3 plus CDN) serves files from MEDIA_ROOT for MEDIA_URL requests.
Common Beginner Mistakes (Revisited)
Forgetting enctype="multipart/form-data" in the form.
Trying to read files from request.POST instead of request.FILES.
Hardcoding paths like "media/" instead of using MEDIA_ROOT.
Hardcoding URLs like "/media/..." instead of using {{ file.url }}.
Not adding the static() helper for media in urls.py during development.
Summary
MEDIA_ROOT is where files live on disk, MEDIA_URL is how the browser reaches them.
Use request.FILES to access uploaded files and save them under MEDIA_ROOT.
Using FileField on a model is the cleanest way to manage uploads.
Use {{ some_file_field.url }} in templates instead of hardcoding /media/....
Hardcoded /media paths & manual "media/" folders are okay for quick demos, but not ideal for real projects.
Django Shortcut HTTP Functions (Beginner's Guide)
What Are Django Shortcut HTTP Functions?
Django provides several shortcut functions that make writing views easier.
These shortcuts help you:
render templates
redirect users
display 404 errors
handle permissions
render() — Return an HTML Template
Most common shortcut.
It loads a template, fills it with data, and returns an HttpResponse.
Equivalent long version (you don't need to write this!):
from django.template import loader
from django.http import HttpResponse
def home(request):
template = loader.get_template("blog/home.html")
html = template.render({"title": "Homepage"}, request)
return HttpResponse(html)
Use render() for any page that returns HTML.
redirect() — Redirect the User to Another URL
Redirects the browser to a different page.
You can redirect to:
a URL path ("/blog/")
a view name
a full external URL
from django.shortcuts import redirect
def go_home(request):
return redirect("/blog/home/")
Recommended way: redirect by view name.
def go_home(request):
return redirect("home") # the URL pattern named "home"
This avoids hardcoding URL strings.
get_object_or_404() — Retrieve an Object or Return 404
This is the best way to fetch a single object from the database.
If not found → Django automatically shows a 404 page.
from django.shortcuts import get_object_or_404
from .models import Post
def show_post(request, id):
post = get_object_or_404(Post, pk=id)
return render(request, "blog/post.html", {"post": post})
Without it, you would need:
try:
post = Post.objects.get(pk=id)
except Post.DoesNotExist:
raise Http404()
Use get_object_or_404() whenever you expect exactly one object.
4. get_list_or_404() — Get a List or Return 404
Same as get_object_or_404(), but for lists of objects.
If the list is empty → 404 error.
from django.shortcuts import get_list_or_404
from .models import Post
def posts_by_author(request, name):
posts = get_list_or_404(Post, author=name)
return render(request, "blog/list.html", {"posts": posts})
Often used when a "page must show something".
HttpResponse() — Return Plain Text or Custom Content
Django's basic HTTP response object.
Used when you do not need a template.
from django.http import HttpResponse
def hello(request):
return HttpResponse("Hello world!")
You can set status codes or headers:
return HttpResponse("Not allowed", status=405)
HttpResponseRedirect() — Redirect Using a URL
This is the low-level version of redirect().
You rarely need it directly.
from django.http import HttpResponseRedirect
def go_somewhere(request):
return HttpResponseRedirect("/blog/home/")
redirect() is easier, so use that instead.
JsonResponse() — Return JSON Data
Perfect for APIs or AJAX calls.
Accepts a Python dict or list and converts it to JSON automatically.
HttpResponseNotFound(), HttpResponseForbidden(), etc.
Django provides shortcuts for common HTTP errors:
from django.http import HttpResponseNotFound
def missing(request):
return HttpResponseNotFound("This page does not exist")
Other shortcuts:
HttpResponseBadRequest (400)
HttpResponseForbidden (403)
HttpResponseNotAllowed (405)
HttpResponseServerError (500)
Beginner's Guide to Django Generic Views
What Are Generic Views?
Django comes with many built-in views called generic views.
A generic view is a reusable view class that already knows how to do common tasks such as:
showing a list of database objects
showing details of a single object
creating, editing, deleting objects
rendering templates
This means you don’t have to write the logic yourself. Django handles it for you.
You only specify:
which model should be used
which template to render
Generic views help avoid writing repetitive CRUD code.
The Two Most Common Generic Views
Django’s generic views include many types, but beginners should start with these:
ListView: displays a list of objects
DetailView: displays one object
ListView — Display a List of Objects
Suppose you have a model:
# blog/models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
body = models.TextField()
def __str__(self):
return self.title
You can create a view to list all posts:
# blog/views.py
from django.views.generic import ListView
from .models import Post
class PostListView(ListView):
model = Post
template_name = "blog/post_list.html"
Add a URL pattern:
# blog/urls.py
from django.urls import path
from .views import PostListView
urlpatterns = [
path("posts/", PostListView.as_view(), name="post-list"),
]
Create the template:
<!-- blog/templates/blog/post_list.html -->
<h1>All Posts</h1>
{% for post in object_list %}
<h3>{{ post.title }}</h3>
<p>{{ post.body|truncatechars:80 }}</p>
{% endfor %}
object_list is provided automatically by Django.
DetailView — Show One Object
To show a single post:
# blog/views.py
from django.views.generic import DetailView
class PostDetailView(DetailView):
model = Post
template_name = "blog/post_detail.html"
object is automatically passed into the template (the Post instance).
Understanding `pk` in the URL
DetailView automatically looks for a parameter called pk.
So the URL must use <int:pk> unless you customize it.
This pk is used to fetch a single object from the database.
Other Useful Generic Views (Beginner Overview)
Generic views for creating, editing, and deleting objects:
CreateView: create a new object
UpdateView: edit an existing object
DeleteView: delete an object
FormView: display and process forms
For example, a simple CreateView:
from django.views.generic import CreateView
from .models import Post
class PostCreateView(CreateView):
model = Post
fields = ["title", "body"]
template_name = "blog/post_form.html"
success_url = "/blog/posts/"
This automatically:
generates a form
saves the data
redirects after success
How Generic Views Know What to Do
You must provide at least two things:
model → which database model to use
template_name → which HTML file to load
Optional:
context variables
ordering
fields (for CreateView/UpdateView)
success_url
Customizing Context Data
You can add your own variables by overriding get_context_data().
class PostListView(ListView):
model = Post
template_name = "blog/post_list.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["extra"] = "Hello from context!"
return context
Then in the template:
{{ extra }}
Beginner's Guide to Django Middleware
What Is Middleware?
Middleware in Django is like a series of filters that every request and response passes through.
Every time a user opens a page:
the request enters Django
it travels through the middleware chain
reaches your view
the response travels back through middleware
finally reaches the user
Django comes with many built-in middleware, but you can also write your own.
from django import forms
class ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = '__all__'
class ProductAdmin(admin.ModelAdmin):
form = ProductForm
This enables custom validation, widgets, or logic in forms.
Use strong passwords and enable two-factor authentication.
Consider changing the default admin URL to reduce bot scans:
path('secret-admin/', admin.site.urls)
Use HTTPS and enable CSRF protection (enabled by default).
Django Admin Create User
Creating Users via Django Admin
Django’s built-in User model can be managed directly via the admin interface.
To access it, make sure:
django.contrib.auth is in INSTALLED_APPS
You’ve run python3 manage.py migrate
Login to /admin as a superuser, and go to:
Users under the “Authentication and Authorization” section
Click + Add to create a new user
You'll first be prompted for:
Username
Password
Fields in the Admin Create User Form
After the initial password setup, you can set additional fields:
First name, Last name
Email address
Is staff: can access the admin interface
Is superuser: full permissions
Is active: determines if the account is active
Groups: role-based permission bundles
User permissions: fine-grained permission settings
Last login and Date joined
Click Save to create the user.
Customizing the User Creation Form
If you are using a custom user model, the default form may not be sufficient.
Create a custom admin class in admin.py:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth import get_user_model
class CustomUserAdmin(UserAdmin):
model = get_user_model()
list_display = ('username', 'email', 'is_staff')
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('username', 'email', 'password1', 'password2', 'is_staff')}
),
)
admin.site.register(get_user_model(), CustomUserAdmin)
Now the admin panel uses the custom logic and layout for creating users.
Creating Users Programmatically (Optional)
Users can also be created via Django shell:
python3 manage.py shell
from django.contrib.auth.models import User
user = User.objects.create_user('junzhe', password='secure123')
user.email = 'junzhe@example.com'
user.is_staff = True
user.save()
Always use create_user() or create_superuser() when creating users programmatically. They handle password hashing and validation.
Don’t forget to assign is_staff=True for users that should access the admin panel.
By default, is_active=True — set it to False to temporarily disable a user.
Assigning users to groups and permissions helps enforce access control.
Django Admin Include Models
What Does "Including Models in Django Admin" Mean?
By default, Django admin only shows models that you explicitly register.
“Including a model” means registering it in the admin.py file of your app so it appears in the admin panel.
Once registered, you can create, edit, filter, and delete records from the admin dashboard.
Basic Model Registration
To make a model appear in the admin interface:
# admin.py
from django.contrib import admin
from .models import Product
admin.site.register(Product)
Now Product will show up in the admin panel automatically.
Registering Multiple Models
You can register multiple models in the same file:
from django.contrib import admin
from .models import Product, Category, Order
admin.site.register(Product)
admin.site.register(Category)
admin.site.register(Order)
Alternatively, use a loop (not commonly used):
models = [Product, Category, Order]
for model in models:
admin.site.register(model)
Using ModelAdmin for Custom Admin Behavior
You can pass a custom admin class with display/editing configuration:
from django import forms
class ProductForm(forms.ModelForm):
def clean_price(self):
price = self.cleaned_data['price']
if price < 0:
raise forms.ValidationError("Price cannot be negative")
return price
class ProductAdmin(admin.ModelAdmin):
form = ProductForm
Admin forms fully support Django’s validation system.