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 complexity of web development, letting you focus on writing your app.
Why Django?
Django's philosophy is "batteries included" — it comes with:
- ✅ ORM (Object-Relational Mapper) for database interaction
- ✅ Automatic admin interface
- ✅ Built-in authentication system
- ✅ Form handling and validation
- ✅ Security features (CSRF, XSS protection, SQL injection prevention)
- ✅ Template engine
- ✅ URL routing
- ✅ Caching framework
- ✅ Internationalization support
Django vs Flask
| Feature | Django | Flask |
|---|
| Size | Full-stack framework | Micro-framework |
| ORM | Built-in | External (SQLAlchemy) |
| Admin | Automatic | Manual |
| Learning curve | Steeper | Gentler |
| Flexibility | More opinionated | More flexible |
| Best for | Large apps, CMS, APIs | Small apps, microservices |
Installation
# Create virtual environment
python -m venv venv
# Activate
# Windows:
venv\Scripts\activate
# macOS/Linux:
source venv/bin/activate
# Install Django
pip install django
# Verify installation
python -m django --version
# Output: 5.x.x
# Full requirements.txt
pip install django pillow python-dotenv
Creating a Django Project
# Create a new project
django-admin startproject mysite
# Project structure created:
# mysite/
# ├── manage.py ← Command-line utility
# └── mysite/
# ├── __init__.py
# ├── settings.py ← Project settings
# ├── urls.py ← URL declarations
# ├── asgi.py ← ASGI config (async)
# └── wsgi.py ← WSGI config (sync)
# Run the development server
cd mysite
python manage.py runserver
# Output:
# Watching for file changes with StatReloader
# Performing system checks...
# System check identified no issues (0 silenced).
# Starting development server at http://127.0.0.1:8000/
Visit http://127.0.0.1:8000/ to see the Django welcome page!
Django Project Structure
mysite/
├── manage.py # CLI utility for project management
├── mysite/
│ ├── __init__.py
│ ├── settings.py # Global project settings
│ ├── urls.py # Root URL configuration
│ └── wsgi.py # WSGI deployment config
├── blog/ # An app (created with startapp)
│ ├── __init__.py
│ ├── admin.py # Admin interface registration
│ ├── apps.py # App configuration
│ ├── models.py # Database models
│ ├── views.py # View functions/classes
│ ├── urls.py # App-level URL patterns
│ ├── forms.py # Forms (optional)
│ ├── tests.py # Unit tests
│ ├── migrations/ # Database migration files
│ │ └── __init__.py
│ └── templates/
│ └── blog/
│ ├── index.html
│ └── detail.html
└── templates/ # Project-level templates
└── base.html
Creating a Django App
Django projects are made up of apps — reusable components:
# Create a new app called 'blog'
python manage.py startapp blog
Register the app in settings.py:
# mysite/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog', # ← Add your app here
]
Django Settings
# mysite/settings.py — Key settings explained
import os
from pathlib import Path
from dotenv import load_dotenv
load_dotenv()
# Build paths inside the project
BASE_DIR = Path(__file__).resolve().parent.parent
# Security key — keep this SECRET in production
SECRET_KEY = os.environ.get('SECRET_KEY', 'django-insecure-dev-key')
# Debug mode — set to False in production
DEBUG = os.environ.get('DEBUG', 'True') == 'True'
# Allowed hosts — important for production
ALLOWED_HOSTS = ['localhost', '127.0.0.1']
# In production: ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']
# Database settings
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# For PostgreSQL:
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.postgresql',
# 'NAME': 'mydb',
# 'USER': 'myuser',
# 'PASSWORD': os.environ.get('DB_PASSWORD'),
# 'HOST': 'localhost',
# 'PORT': '5432',
# }
# }
# Static files (CSS, JS, Images)
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles' # For production (collectstatic)
STATICFILES_DIRS = [BASE_DIR / 'static'] # Development static files
# Media files (uploaded by users)
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
# Templates
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'], # Project-level templates
'APP_DIRS': True, # Look for templates in app's templates/ folder
'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',
],
},
},
]
# Default auto field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# Internationalization
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
URL Configuration
Root URL Configuration
# 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('', include('blog.urls')), # Blog app URLs
path('api/', include('api.urls')), # API app URLs
path('accounts/', include('accounts.urls')), # Auth URLs
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
App-Level URLs
# blog/urls.py
from django.urls import path
from . import views
app_name = 'blog' # Namespace for URL reversing
urlpatterns = [
path('', views.post_list, name='post-list'),
path('post/<int:pk>/', views.post_detail, name='post-detail'),
path('post/new/', views.post_create, name='post-create'),
path('post/<int:pk>/edit/', views.post_edit, name='post-edit'),
path('post/<int:pk>/delete/', views.post_delete, name='post-delete'),
path('category/<slug:slug>/', views.category_posts, name='category'),
path('search/', views.search, name='search'),
]
Django MVT Architecture
Django follows the Model-View-Template (MVT) pattern:
Browser Request
↓
URL Router (urls.py)
↓
View (views.py) ←→ Model (models.py) ←→ Database
↓
Template (templates/)
↓
HTTP Response → Browser
Model — Data Layer
# blog/models.py
from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse
class Category(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
def __str__(self):
return self.name
class Meta:
verbose_name_plural = 'categories'
class Post(models.Model):
STATUS_CHOICES = [
('draft', 'Draft'),
('published', 'Published'),
]
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
content = models.TextField()
excerpt = models.TextField(blank=True)
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
published_at = models.DateTimeField(null=True, blank=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('blog:post-detail', kwargs={'pk': self.pk})
class Meta:
ordering = ['-created_at']
View — Logic Layer
# blog/views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib import messages
from .models import Post, Category
def post_list(request):
posts = Post.objects.filter(status='published').select_related('author', 'category')
categories = Category.objects.all()
# Search
query = request.GET.get('q')
if query:
posts = posts.filter(title__icontains=query)
context = {
'posts': posts,
'categories': categories,
'query': query,
}
return render(request, 'blog/post_list.html', context)
def post_detail(request, pk):
post = get_object_or_404(Post, pk=pk, status='published')
related_posts = Post.objects.filter(
category=post.category, status='published'
).exclude(pk=pk)[:3]
context = {
'post': post,
'related_posts': related_posts,
}
return render(request, 'blog/post_detail.html', context)
Template — Presentation Layer
<!-- blog/templates/blog/post_list.html -->
{% extends "base.html" %}
{% block title %}Blog Posts{% endblock %}
{% block content %}
<h1>Latest Posts</h1>
{% for post in posts %}
<article>
<h2><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h2>
<p>By {{ post.author.username }} | {{ post.created_at|date:"F d, Y" }}</p>
<p>{{ post.excerpt }}</p>
</article>
{% empty %}
<p>No posts yet.</p>
{% endfor %}
{% endblock %}
Django Admin
Django's admin is automatically generated:
# blog/admin.py
from django.contrib import admin
from .models import Post, Category
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'status', 'created_at']
list_filter = ['status', 'category', 'author']
search_fields = ['title', 'content']
prepopulated_fields = {'slug': ('title',)}
date_hierarchy = 'created_at'
ordering = ['-created_at']
fieldsets = (
('Post Details', {
'fields': ('title', 'slug', 'author', 'category', 'status')
}),
('Content', {
'fields': ('content', 'excerpt')
}),
('Dates', {
'fields': ('published_at',),
'classes': ('collapse',)
}),
)
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ['name', 'slug']
prepopulated_fields = {'slug': ('name',)}
Create a superuser:
python manage.py createsuperuser
# Enter: username, email, password
# Visit: http://127.0.0.1:8000/admin/
Django manage.py Commands
# Database migrations
python manage.py makemigrations # Create migration files
python manage.py migrate # Apply migrations to database
python manage.py showmigrations # List all migrations
# Development
python manage.py runserver # Start dev server
python manage.py runserver 0.0.0.0:8080 # Different host/port
python manage.py shell # Interactive Python shell
python manage.py dbshell # Database shell
# Admin
python manage.py createsuperuser # Create admin user
python manage.py changepassword username # Change a user's password
# Static files (production)
python manage.py collectstatic # Collect static files
# Testing
python manage.py test # Run all tests
python manage.py test blog # Run tests for 'blog' app
# Database
python manage.py dumpdata # Export data
python manage.py loaddata fixture.json # Import data
python manage.py inspectdb # Generate models from existing DB
Running Your First Django App
Here's the complete flow to get a Django app running:
# 1. Create project
django-admin startproject myblog
cd myblog
# 2. Create app
python manage.py startapp blog
# 3. Add app to INSTALLED_APPS in settings.py
# 4. Create models in blog/models.py
# 5. Create and apply migrations
python manage.py makemigrations
python manage.py migrate
# 6. Create superuser
python manage.py createsuperuser
# 7. Register models in blog/admin.py
# 8. Create views in blog/views.py
# 9. Create URL patterns in blog/urls.py
# 10. Include blog URLs in mysite/urls.py
# 11. Create templates
# 12. Run server
python manage.py runserver
Summary
In this lesson, you learned:
- ✅ What Django is and its "batteries included" philosophy
- ✅ Installing Django and creating a project
- ✅ Django's MVT architecture
- ✅ Project structure and app organization
- ✅ Configuring
settings.py - ✅ URL routing (root and app-level)
- ✅ Introduction to Models, Views, and Templates
- ✅ Django's automatic admin interface
- ✅ Essential
manage.py commands
*Next Lesson: Django Models →*