What You'll Learn
Introduction to Django
Django is a high-level Python web framework that enables rapid development of secure and maintainable websites. Built by experienced developers, Django takes care of much of the hassle of web development, so you can focus on writing your app without needing to reinvent the wheel.
💡 Why Django?
Batteries Included: Django comes with everything you need - ORM, admin panel, authentication, and more. Used by Instagram, Pinterest, Spotify, and Mozilla!
- Model: Data layer - defines your database structure
- Template: Presentation layer - HTML files with Django template language
- View: Business logic layer - handles requests and returns responses
Installation & Setup
Install Python (if not installed)
Make sure you have Python 3.8+ installed. Check with python --version
Create Virtual Environment
Always use virtual environments to isolate project dependencies
✅ Django Installed!
You're ready to create your first Django project!
Create Project & Apps
Create New Project
🎉 Open http://127.0.0.1:8000/ in your browser - You'll see the Django welcome page!
Create an App
A Django project can contain multiple apps. Each app handles a specific feature (e.g., blog, users, products).
⚠️ Don't Forget!
After creating an app, you must add it to INSTALLED_APPS in settings.py
# Add your app to INSTALLED_APPS INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # Your apps 'blog', # Add this line! ]
URLs & Routing
URLs define the routes in your application. When a user visits a URL, Django uses the URL configuration to determine which view to call.
Project URLs (Main Router)
from django.contrib import admin from django.urls import path, include urlpatterns = [ # Admin panel path('admin/', admin.site.urls), # Include blog app urls path('blog/', include('blog.urls')), # Home page path('', include('blog.urls')), ]
App URLs
Create a new file urls.py inside your app folder:
from django.urls import path from . import views urlpatterns = [ # Home page - / path('', views.home, name='home'), # About page - /about/ path('about/', views.about, name='about'), # Dynamic URL with parameter - /post/1/ path('post/<int:id>/', views.post_detail, name='post_detail'), # String parameter - /category/tech/ path('category/<str:name>/', views.category, name='category'), ]
💡 URL Parameters
<int:id> - Integer parameter
<str:name> - String parameter
<slug:slug> - Slug (letters, numbers, hyphens, underscores)
Views & Functions
Views handle the business logic. They receive HTTP requests and return HTTP responses. Django supports both function-based views (FBV) and class-based views (CBV).
Function-Based Views
from django.shortcuts import render from django.http import HttpResponse # Simple response def home(request): return HttpResponse("<h1>Welcome to My Blog!</h1>") # Render template with context def about(request): context = { 'name': 'Om Pandey', 'skills': ['Python', 'Django', 'React'], } return render(request, 'blog/about.html', context) # View with URL parameter def post_detail(request, id): return HttpResponse(f"<h1>Post ID: {id}</h1>") # View with string parameter def category(request, name): return HttpResponse(f"<h1>Category: {name}</h1>")
Class-Based Views
from django.views import View from django.views.generic import ListView, DetailView, CreateView from .models import Post # Basic Class-Based View class HomeView(View): def get(self, request): return render(request, 'blog/home.html') def post(self, request): # Handle POST request pass # Generic ListView - List all posts class PostListView(ListView): model = Post template_name = 'blog/post_list.html' context_object_name = 'posts' ordering = ['-created_at'] paginate_by = 10 # Generic DetailView - Single post class PostDetailView(DetailView): model = Post template_name = 'blog/post_detail.html'
from django.urls import path from .views import HomeView, PostListView, PostDetailView urlpatterns = [ path('', HomeView.as_view(), name='home'), path('posts/', PostListView.as_view(), name='post_list'), path('post/<int:pk>/', PostDetailView.as_view(), name='post_detail'), ]
Templates & HTML
Templates are HTML files with Django Template Language (DTL) for dynamic content.
Template Structure
Base Template (Layout)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{% block title %}My Blog{% endblock %}</title> <!-- Load static files --> {% load static %} <link rel="stylesheet" href="{% static 'css/style.css' %}"> </head> <body> <nav> <a href="{% url 'home' %}">Home</a> <a href="{% url 'about' %}">About</a> </nav> <main> {% block content %} <!-- Page content goes here --> {% endblock %} </main> <footer> <p>© 2025 My Blog</p> </footer> </body> </html>
Child Template
{% extends 'blog/base.html' %}
{% block title %}Home - My Blog{% endblock %}
{% block content %}
<h1>Welcome, {{ user.username }}!</h1>
<!-- Display variable -->
<p>{{ message }}</p>
<!-- If statement -->
{% if posts %}
<h2>Latest Posts</h2>
<!-- For loop -->
{% for post in posts %}
<article>
<h3>{{ post.title }}</h3>
<p>{{ post.content|truncatewords:30 }}</p>
<small>Posted on {{ post.created_at|date:"M d, Y" }}</small>
<a href="{% url 'post_detail' post.id %}">Read More</a>
</article>
{% empty %}
<p>No posts available.</p>
{% endfor %}
{% else %}
<p>No posts yet.</p>
{% endif %}
{% endblock %}
💡 Template Tags & Filters
{{ variable }} - Output variable
{% tag %} - Template tag (if, for, url, etc.)
{{ text|filter }} - Apply filter (truncate, date, upper, etc.)
Models & Database
Models define your database structure. Django's ORM (Object-Relational Mapping) lets you interact with the database using Python code instead of SQL.
Creating Models
from django.db import models from django.contrib.auth.models import User class Category(models.Model): name = models.CharField(max_length=100) slug = models.SlugField(unique=True) class Meta: verbose_name_plural = 'Categories' def __str__(self): return self.name class Post(models.Model): # Choices for status STATUS_CHOICES = [ ('draft', 'Draft'), ('published', 'Published'), ] # Fields title = models.CharField(max_length=200) slug = models.SlugField(unique=True) content = models.TextField() excerpt = models.TextField(blank=True) image = models.ImageField(upload_to='posts/', blank=True) status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft') # Relationships author = models.ForeignKey(User, on_delete=models.CASCADE) category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True) # Timestamps created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ['-created_at'] def __str__(self): return self.title class Comment(models.Model): post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments') author = models.CharField(max_length=100) email = models.EmailField() body = models.TextField() created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f'Comment by {self.author} on {self.post}'
Migrations
After creating or modifying models, you need to create and apply migrations.
Working with Models (ORM)
from blog.models import Post, Category # CREATE - Add new record post = Post.objects.create( title="My First Post", slug="my-first-post", content="This is my first blog post!", author=user, status='published' ) # READ - Get all posts all_posts = Post.objects.all() # READ - Get single post post = Post.objects.get(id=1) post = Post.objects.get(slug='my-first-post') # FILTER - Get specific posts published_posts = Post.objects.filter(status='published') recent_posts = Post.objects.filter(created_at__year=2025) # ORDER BY latest_posts = Post.objects.order_by('-created_at')[:5] # UPDATE post = Post.objects.get(id=1) post.title = "Updated Title" post.save() # UPDATE - Bulk update Post.objects.filter(status='draft').update(status='published') # DELETE post = Post.objects.get(id=1) post.delete() # DELETE - Bulk delete Post.objects.filter(status='draft').delete() # RELATED OBJECTS post = Post.objects.get(id=1) comments = post.comments.all() # Get all comments for this post # COUNT total_posts = Post.objects.count() # EXISTS has_posts = Post.objects.filter(author=user).exists()
Settings Configuration
Important Settings
import os from pathlib import Path # Build paths BASE_DIR = Path(__file__).resolve().parent.parent # SECURITY - Keep secret in production! SECRET_KEY = 'your-secret-key-here' # Debug mode (False in production) DEBUG = True # Allowed hosts ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'yourdomain.com'] # Installed Apps INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # Third-party apps 'rest_framework', 'corsheaders', # Your apps 'blog', 'users', ] # Database - Default SQLite DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } # Templates TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [BASE_DIR / 'templates'], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] # Static files (CSS, JS, Images) STATIC_URL = 'static/' STATICFILES_DIRS = [BASE_DIR / 'static'] STATIC_ROOT = BASE_DIR / 'staticfiles' # Media files (User uploads) MEDIA_URL = 'media/' MEDIA_ROOT = BASE_DIR / 'media' # Time zone TIME_ZONE = 'Asia/Kathmandu' USE_TZ = True # Language LANGUAGE_CODE = 'en-us' # Login/Logout redirects LOGIN_REDIRECT_URL = 'home' LOGOUT_REDIRECT_URL = 'home'
PostgreSQL Connection
SQLite is great for development, but PostgreSQL is recommended for production.
Install PostgreSQL Driver
Install psycopg2 to connect Django with PostgreSQL
Configure Database Settings
Update your settings.py with PostgreSQL configuration
# PostgreSQL Database Configuration DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'mydatabase', # Database name 'USER': 'myuser', # Database user 'PASSWORD': 'mypassword', # Database password 'HOST': 'localhost', # Database host 'PORT': '5432', # Default PostgreSQL port } } # Better approach - Use environment variables import os DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': os.environ.get('DB_NAME', 'mydatabase'), 'USER': os.environ.get('DB_USER', 'myuser'), 'PASSWORD': os.environ.get('DB_PASSWORD', ''), 'HOST': os.environ.get('DB_HOST', 'localhost'), 'PORT': os.environ.get('DB_PORT', '5432'), } }
Apply Migrations
Run migrations to create tables in PostgreSQL
✅ PostgreSQL Connected!
Your Django project is now using PostgreSQL database!
Forms & User Input
Django forms handle user input validation and HTML rendering.
Creating Forms
from django import forms from .models import Post, Comment # Simple Form class ContactForm(forms.Form): name = forms.CharField(max_length=100) email = forms.EmailField() message = forms.CharField(widget=forms.Textarea) def clean_email(self): email = self.cleaned_data.get('email') if not email.endswith('@gmail.com'): raise forms.ValidationError('Please use Gmail address') return email # ModelForm - Auto-generates from model class PostForm(forms.ModelForm): class Meta: model = Post fields = ['title', 'content', 'category', 'status'] widgets = { 'title': forms.TextInput(attrs={'class': 'form-control'}), 'content': forms.Textarea(attrs={'class': 'form-control', 'rows': 5}), } # Comment Form class CommentForm(forms.ModelForm): class Meta: model = Comment fields = ['author', 'email', 'body']
Handling Forms in Views
from django.shortcuts import render, redirect from django.contrib import messages from .forms import PostForm, ContactForm # Create post with form def create_post(request): if request.method == 'POST': form = PostForm(request.POST, request.FILES) if form.is_valid(): post = form.save(commit=False) post.author = request.user post.save() messages.success(request, 'Post created successfully!') return redirect('post_detail', pk=post.pk) else: form = PostForm() return render(request, 'blog/create_post.html', {'form': form}) # Contact form handling def contact(request): if request.method == 'POST': form = ContactForm(request.POST) if form.is_valid(): # Access cleaned data name = form.cleaned_data['name'] email = form.cleaned_data['email'] message = form.cleaned_data['message'] # Send email or save to database # send_mail(...) messages.success(request, 'Message sent!') return redirect('contact') else: form = ContactForm() return render(request, 'blog/contact.html', {'form': form})
Form in Template
{% extends 'blog/base.html' %}
{% block content %}
<h1>Create New Post</h1>
<!-- Display messages -->
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
{% endfor %}
{% endif %}
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<!-- Render all fields -->
{{ form.as_p }}
<!-- Or render individually -->
<div class="form-group">
<label for="{{ form.title.id_for_label }}">Title</label>
{{ form.title }}
{% if form.title.errors %}
<span class="error">{{ form.title.errors }}</span>
{% endif %}
</div>
<button type="submit">Create Post</button>
</form>
{% endblock %}
⚠️ CSRF Token Required!
Always include {% csrf_token %} in forms to prevent Cross-Site Request Forgery attacks.
Admin Panel
Django's built-in admin panel is a powerful tool for managing your data.
Register Models in Admin
from django.contrib import admin from .models import Post, Category, Comment # Simple registration admin.site.register(Category) # Custom admin with features @admin.register(Post) class PostAdmin(admin.ModelAdmin): list_display = ['title', 'author', 'category', 'status', 'created_at'] list_filter = ['status', 'category', 'created_at'] search_fields = ['title', 'content'] prepopulated_fields = {'slug': ('title',)} date_hierarchy = 'created_at' ordering = ['-created_at'] list_per_page = 20 # Inline editing list_editable = ['status'] @admin.register(Comment) class CommentAdmin(admin.ModelAdmin): list_display = ['author', 'post', 'created_at'] list_filter = ['created_at'] search_fields = ['author', 'body']
Access admin at: http://127.0.0.1:8000/admin/
Advanced Topics
Authentication
from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin # Protect view with decorator @login_required def dashboard(request): return render(request, 'blog/dashboard.html') # Protect class-based view with mixin class CreatePostView(LoginRequiredMixin, CreateView): model = Post form_class = PostForm template_name = 'blog/create_post.html' def form_valid(self, form): form.instance.author = self.request.user return super().form_valid(form)
REST API with Django REST Framework
from rest_framework import serializers from .models import Post class PostSerializer(serializers.ModelSerializer): class Meta: model = Post fields = ['id', 'title', 'content', 'author', 'created_at']
from rest_framework import viewsets from rest_framework.permissions import IsAuthenticatedOrReadOnly from .models import Post from .serializers import PostSerializer class PostViewSet(viewsets.ModelViewSet): queryset = Post.objects.all() serializer_class = PostSerializer permission_classes = [IsAuthenticatedOrReadOnly] def perform_create(self, serializer): serializer.save(author=self.request.user)
Deployment Checklist
# Production settings # Security DEBUG = False SECRET_KEY = os.environ.get('SECRET_KEY') ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com'] # HTTPS settings SECURE_SSL_REDIRECT = True SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True SECURE_HSTS_SECONDS = 31536000 # Static files STATIC_ROOT = BASE_DIR / 'staticfiles' # Email configuration EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST = 'smtp.gmail.com' EMAIL_PORT = 587 EMAIL_USE_TLS = True
🎉 Congratulations!
You've learned Django from zero to hero! You're now ready to build powerful backend applications. Keep practicing and building projects!