- Introduction to Django
- Project Setup & Configuration
- Models & Database
- Views & URL Routing
- Templates & Static Files
- Django Admin Interface
- User Authentication & Authorization
- Forms & File Uploads
- Best Practices & Workflows
Django is a high-level Python web framework that follows the MTV (Model-Template-View) pattern, enabling rapid development of secure and maintainable websites.
- DRY (Don't Repeat Yourself): Minimize code repetition
- Convention over Configuration: Sensible defaults reduce boilerplate
- Security by Default: Built-in protection against common vulnerabilities
- Scalability: Designed to handle high traffic efficiently
# Create virtual environment
python -m venv env_site
source env_site/bin/activate # On Mac/Linux
env_site\Scripts\activate # On Windows
# Install Django
pip install django
# Create project
django-admin startproject myproject
cd myproject
# Run development server
python manage.py runservermyproject/
├── myproject/ # Project configuration
│ ├── __init__.py
│ ├── settings.py # Main configuration
│ ├── urls.py # Root URL configuration
│ ├── asgi.py
│ └── wsgi.py
├── manage.py # Command-line utility
└── db.sqlite3 # Database (created after first migration)
python manage.py startapp posts
python manage.py startapp usersAlways register apps in settings.py:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'posts', # Your apps
'users',
]# settings.py
STATIC_URL = 'static/'
STATICFILES_DIRS = [
BASE_DIR / 'static'
]# settings.py
MEDIA_URL = 'media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')# Main urls.py
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
# ... your patterns
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)Models define your database structure. Each model is a Python class that represents a database table.
# posts/models.py
from django.db import models
from django.contrib.auth.models import User
class Post(models.Model):
title = models.CharField(max_length=75)
body = models.TextField()
slug = models.SlugField()
date = models.DateTimeField(auto_now_add=True)
banner = models.ImageField(default='fallback.png', blank=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, default=None)
def __str__(self):
return self.title| Field Type | Use Case | Parameters |
|---|---|---|
CharField |
Short text | max_length (required) |
TextField |
Long text | No length limit |
SlugField |
URL-friendly text | Lowercase, hyphens |
DateTimeField |
Date/time | auto_now_add=True for creation |
ImageField |
Images | Requires Pillow: pip install Pillow |
ForeignKey |
Relationships | Links to another model |
BooleanField |
True/False | Default values |
EmailField |
Email addresses | Built-in validation |
max_length: Character limit (required for CharField)blank=True: Allows empty values in formsnull=True: Allows NULL in databasedefault: Default valueunique=True: Ensures uniquenessauto_now_add=True: Set on creationauto_now=True: Update on every save
ForeignKey (Many-to-One):
author = models.ForeignKey(User, on_delete=models.CASCADE)on_delete options:
CASCADE: Delete related objectsPROTECT: Prevent deletionSET_NULL: Set to NULLSET_DEFAULT: Set to default value
# 1. Create migrations after model changes
python manage.py makemigrations
# 2. Apply migrations to database
python manage.py migrate
# Useful commands:
python manage.py showmigrations # View migration status
python manage.py sqlmigrate posts 0001 # View SQL for migrationMigration Best Practices:
- Always create migrations after model changes
- Review migration files before applying
- Never edit applied migrations—create new ones
- Keep migrations in version control
- Test migrations on development database first
Django Shell:
python manage.py shellBasic Operations:
# Import model
from posts.models import Post
# Create object
p = Post()
p.title = "My First Post"
p.save()
# Query all objects
Post.objects.all()
# Get specific object
post = Post.objects.get(slug='my-slug')
# Filter objects
posts = Post.objects.filter(author=request.user)
# Order objects
posts = Post.objects.all().order_by('-date') # Descending
# Count objects
Post.objects.count()Views process HTTP requests and return HTTP responses.
Basic View:
# posts/views.py
from django.shortcuts import render, redirect
from .models import Post
def posts_list(request):
posts = Post.objects.all().order_by('-date')
return render(request, 'posts/posts_list.html', {'posts': posts})
def post_page(request, slug):
post = Post.objects.get(slug=slug)
return render(request, 'posts/post_page.html', {'post': post})App URLs (posts/urls.py):
from django.urls import path
from . import views
app_name = 'posts' # Namespace
urlpatterns = [
path('', views.posts_list, name="list"),
path('new-post/', views.post_new, name="new-post"),
path('<slug:slug>', views.post_page, name="page"),
]Project URLs (myproject/urls.py):
from django.contrib import admin
from django.urls import path, include
from . import views
urlpatterns = [
path('admin/', admin.site.urls),
path('posts/', include('posts.urls')),
path('users/', include('users.urls')),
path('', views.Homepage),
path('about/', views.about),
]<int:id>: Matches integers<str:name>: Matches strings<slug:slug>: Matches slugs (letters, numbers, hyphens, underscores)<path:path>: Matches paths with slashes
In templates:
<a href="{% url 'posts:list' %}">All Posts</a>
<a href="{% url 'posts:page' slug=post.slug %}">{{ post.title }}</a>Benefits:
- Change URL patterns without updating templates
- Single source of truth
- Django validates URL names at runtime
Place specific URLs before general ones:
urlpatterns = [
path('new-post/', views.post_new, name="new-post"), # Specific
path('<slug:slug>', views.post_page, name="page"), # General
]myproject/
├── templates/ # Project-level templates
│ ├── layout.html # Base template
│ ├── home.html
│ └── about.html
└── posts/
└── templates/
└── posts/ # App-level templates
├── posts_list.html
├── post_page.html
└── post_new.html
<!DOCTYPE html>
{% load static %}
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
{% block title %}
Django App
{% endblock %}
</title>
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<script src="{% static 'js/main.js' %}" defer></script>
</head>
<body>
<nav>
<a href="/"><span role="img" aria-label="Home" title="Home">🏠</span></a> |
<a href="/about"><span role="img" aria-label="About" title="About">😊</span></a> |
<a href="{% url 'posts:list' %}"><span role="img" aria-label="Posts" title="Posts">📰</span></a> |
{% if user.is_authenticated %}
<a href="{% url 'posts:new_post' %}"><span role="img" aria-label="New Post" title="New Post">🆕</span></a> |
<form class="logout" action="{% url 'users:logout' %}" method="post">
{% csrf_token %}
<button class="logout-button" aria-label="User Logout" title="User Logout">👋</button>
</form>
{% else %}
<a href="{% url 'users:register' %}"><span role="img" aria-label="User Registration" title="User Registration">🚀</span></a> |
<a href="{% url 'users:login' %}"><span role="img" aria-label="User Login" title="User Login">🔐</span></a> |
{% endif %}
</nav>
<main>
{% block content %}
{% endblock %}
</main>
</body>
</html>{% extends 'layout.html' %}
{% block title %}
Posts
{% endblock %}
{% block content %}
<h1>Posts</h1>
{% for post in posts %}
<article class="post">
<h2>
<a href="{% url 'posts:page' slug=post.slug %}">
{{ post.title }}
</a>
</h2>
<p>{{ post.date }} by {{ post.author }}</p>
<p>{{ post.body }}</p>
</article>
{% endfor %}
{% endblock %}Common Tags:
{% extends 'base.html' %}: Template inheritance{% load static %}: Load static files{% block name %}: Define replaceable blocks{% for item in items %}: Loop{% if condition %}: Conditional{% url 'name' %}: Reverse URL{% csrf_token %}: CSRF protection{% comment %}: Comments
Common Filters:
{{ value|length }}: Length of string/list{{ text|lower }}: Lowercase{{ date|date:"Y-m-d" }}: Format date{{ text|truncatewords:10 }}: Truncate text
{% if user.is_authenticated %}
<p>Welcome, {{ user.username }}!</p>
{% else %}
<p>Please log in.</p>
{% endif %}<img
class="banner"
src="{{ post.banner.url }}"
alt="{{ post.title }}"
/>python manage.py createsuperuser# posts/admin.py
from django.contrib import admin
from .models import Post
admin.site.register(Post)- Automatic CRUD interface: Create, Read, Update, Delete
- Form generation: Based on model fields
- Search and filtering: Built-in functionality
- Bulk actions: Operate on multiple records
- User management: Permissions and groups
- Content management: Non-technical users can manage data
URL: http://127.0.0.1:8000/admin/
Authentication (Who you are):
- Login/logout processes
- User identity verification
- Session management
Authorization (What you can do):
- Access control
- Permissions
- Conditional content
Form:
# users/views.py
from django.shortcuts import render, redirect
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import login
def register_view(request):
if request.method == 'POST':
form = UserCreationForm(request.POST)
if form.is_valid():
login(request, form.save()) # Auto-login
return redirect("posts:list")
else:
form = UserCreationForm()
return render(request, 'users/register.html', {"form": form})Template:
{% extends 'layout.html' %}
{% block content %}
<h1>Register a New User</h1>
<form action="/users/register/" method="post">
{% csrf_token %}
{{ form }}
<button type="submit">Submit</button>
</form>
{% endblock %}# users/views.py
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth import login
def login_view(request):
if request.method == "POST":
form = AuthenticationForm(data=request.POST)
if form.is_valid():
login(request, form.get_user())
if "next" in request.POST:
return redirect(request.POST.get('next'))
else:
return redirect("posts:list")
else:
form = AuthenticationForm()
return render(request, 'users/login.html', {"form": form})from django.contrib.auth import logout
def logout_view(request):
if request.method == "POST":
logout(request)
return redirect("posts:list")from django.contrib.auth.decorators import login_required
@login_required(login_url="/users/login/")
def post_new(request):
# Only authenticated users can access
return render(request, 'posts/post_new.html')Redirects users back to their intended page after login:
if "next" in request.POST:
return redirect(request.POST.get('next'))def my_view(request):
if request.user.is_authenticated:
user = request.user
username = user.username
# ...{% if user.is_authenticated %}
<p>Welcome, {{ user.username }}!</p>
{{ user.email }}
{{ user.first_name }}
{% endif %}ModelForm automatically generates forms from models.
# posts/forms.py
from django import forms
from . import models
class CreatePost(forms.ModelForm):
class Meta:
model = models.Post
fields = ['title', 'body', 'slug', 'banner']# posts/views.py
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from . import forms
@login_required(login_url="/users/login/")
def post_new(request):
if request.method == 'POST':
form = forms.CreatePost(request.POST, request.FILES)
if form.is_valid():
newpost = form.save(commit=False)
newpost.author = request.user
newpost.save()
return redirect('posts:list')
else:
form = forms.CreatePost()
return render(request, 'posts/post_new.html', {'form': form}){% extends 'layout.html' %}
{% block content %}
<h1>New Post</h1>
<form action="{% url 'posts:new-post' %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form }}
<button type="submit">Add Post</button>
</form>
{% endblock %}Key Points:
enctype="multipart/form-data": Required for file uploadsrequest.FILES: Handles uploaded files{% csrf_token %}: Security protection (required for all POST forms)
Allows modification before saving:
newpost = form.save(commit=False) # Create object in memory
newpost.author = request.user # Set additional fields
newpost.save() # Commit to databaseWhy use commit=False:
- Set fields not in the form
- Additional processing before saving
- Ensure data integrity
- Required relationship fields
What it is: Cross-Site Request Forgery protection prevents malicious websites from submitting forms.
How it works:
- Django generates unique token for session
- Token embedded in form as hidden field
- Django validates token on submission
- Request processed if valid
Usage:
<form method="post">
{% csrf_token %}
<!-- form fields -->
</form>Django automatically validates:
- Field types
- Required fields
- Unique constraints
- Model constraints
- Custom validation methods
Error handling:
if form.is_valid():
# Process form
else:
# Errors automatically displayed in template- Plan models → Define database structure
- Create migrations →
makemigrations→migrate - Create views → Process requests
- Configure URLs → Route to views
- Create templates → Display data
- Test functionality → Verify features
- Iterate → Refine and improve
- Always use CSRF tokens in POST forms
- Use
@login_requiredfor protected views - Validate user input through forms
- Use POST for state-changing operations (never GET)
- Hash passwords (Django does this automatically)
- Use ForeignKey relationships to link data properly
- Implement proper permissions for different user roles
Models:
- One model per table
- Use descriptive field names
- Include
__str__()method - Add helpful comments
Views:
- Keep logic simple
- Use decorators for common functionality
- Return appropriate HTTP responses
- Handle errors gracefully
Templates:
- Use inheritance for consistency
- Keep logic minimal
- Use semantic HTML
- Ensure accessibility
URLs:
- Use descriptive names
- Organize with app namespaces
- Place specific before general patterns
- Always create migrations after model changes
- Review migrations before applying
- Never edit applied migrations
- Test on development database first
- Keep migrations in version control
- Use
order_by()for consistent ordering - Optimize queries with
select_related()andprefetch_related()
- Use template inheritance for consistency
- Load static files with
{% load static %} - Use named URLs instead of hard-coding
- Add accessibility attributes (aria-labels, alt text)
- Keep templates DRY (Don't Repeat Yourself)
- Use semantic HTML5 elements
- Use ModelForm when possible
- Validate on backend (never trust client-side only)
- Provide clear error messages
- Use appropriate field types
- Add help text for complex fields
- Test validation thoroughly
- Forgetting to add app to INSTALLED_APPS
- Not running migrations after model changes
- Forgetting CSRF token in forms
- Hard-coding URLs instead of using
{% url %} - Not using
commit=Falsewhen setting additional fields - Forgetting
enctype="multipart/form-data"for file uploads - Placing general URL patterns before specific ones
- Not using POST method for logout
# Import models
from posts.models import Post
from django.contrib.auth.models import User
# Create objects
p = Post(title="Title", body="Body", slug="slug")
p.save()
# Query
Post.objects.all()
Post.objects.filter(author__username="john")
Post.objects.get(id=1)
Post.objects.order_by('-date')
# Update
p.title = "New Title"
p.save()
# Delete
p.delete()
# Count
Post.objects.count()
# Relationships
user.post_set.all() # Get all posts by userBy the end of this guide, you've learned to build a complete Django blog with:
- ✅ Project setup and configuration
- ✅ Multiple Django apps (posts, users)
- ✅ Database models with relationships
- ✅ Migrations workflow
- ✅ Django ORM for database operations
- ✅ Views and URL routing
- ✅ Template inheritance and static files
- ✅ Django admin interface
- ✅ User registration and authentication
- ✅ Login/logout functionality
- ✅ Authorization with decorators
- ✅ Custom forms with ModelForm
- ✅ File uploads (images)
- ✅ CSRF protection
- ✅ Conditional navigation
- ✅ Author attribution for posts
You've now completed a comprehensive Django learning journey, building a fully functional blog application with user authentication, post creation, image uploads, and proper security measures. This foundation prepares you for more advanced Django topics like:
- Class-based views
- REST APIs with Django REST Framework
- Testing and debugging
- Deployment to production
- Performance optimization
- Advanced authentication (OAuth, social auth)
- Real-time features with WebSockets
- Caching strategies
Continue practicing by building additional features and exploring Django's extensive ecosystem. The official Django documentation (docs.djangoproject.com) is an excellent resource for deepening your knowledge.