Django Fundamentals
This chapter delves into Django's core concepts, including project setup, application configuration, middleware systems, signal mechanisms, and more, to give you a comprehensive understanding of how the Django framework works.
Django Project Architecture
MVT Architecture Pattern
Django employs the MVT (Model-View-Template) architectural pattern, a variant of the traditional MVC pattern:
┌─────────────────────────────────────────────────────────┐
│ Django MVT Architecture │
├─────────────────────────────────────────────────────────┤
│ HTTP Request → URL Routing → View → Model │
│ ↓ ↓ │
│ Template ← Data Processing │
│ ↓ │
│ HTTP Response → Client │
└─────────────────────────────────────────────────────────┘Model (模型):
- Defines data structures and business logic
- Interacts with the database
- Data validation and processing
View (视图):
- Handles HTTP requests
- Controls business logic
- Calls models and templates
Template (模板):
- Presentation layer logic
- HTML generation
- Data display
Request-Response Flow
# 1. URL routing match
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path('articles/<int:id>/', views.article_detail, name='article_detail'),
]
# 2. View processes the request
# views.py
from django.shortcuts import render, get_object_or_404
from .models import Article
def article_detail(request, id):
article = get_object_or_404(Article, id=id)
return render(request, 'articles/detail.html', {'article': article})
# 3. Model data query
# models.py
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
# 4. Template rendering
# templates/articles/detail.html
<h1>{{ article.title }}</h1>
<p>{{ article.content }}</p>
<small>Published: {{ article.created_at }}</small>Django Settings System
Settings File Structure
# Basic structure of settings.py
import os
from pathlib import Path
# Project root directory
BASE_DIR = Path(__file__).resolve().parent.parent
# Core settings
DEBUG = True
SECRET_KEY = 'your-secret-key'
ALLOWED_HOSTS = []
# Applications
INSTALLED_APPS = [
# Django built-in applications
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Third-party applications
'rest_framework',
'debug_toolbar',
# Local applications
'blog.apps.BlogConfig',
'accounts.apps.AccountsConfig',
]
# Middleware
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
# URL configuration
ROOT_URLCONF = 'mysite.urls'
# Database
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
```##
# Important Settings Explained
#### DEBUG Setting
```python
# Development environment
DEBUG = True
ALLOWED_HOSTS = [] # Empty list means only localhost allowed
# Production environment
DEBUG = False
ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']
# Features when DEBUG=True
- Detailed error pages
- Automatic code reloading
- Static files served automatically
- SQL query debugging information
# Requirements when DEBUG=False
- MUST set ALLOWED_HOSTS
- Need to configure static file serving
- Error messages are hidden
- Performance optimizations enabledSECRET_KEY Setting
# Generate a secure secret key
from django.core.management.utils import get_random_secret_key
SECRET_KEY = get_random_secret_key()
# Read from environment variable
import os
SECRET_KEY = os.environ.get('SECRET_KEY')
# Use python-decouple
from decouple import config
SECRET_KEY = config('SECRET_KEY')
# Uses of SECRET_KEY
- CSRF token generation
- Session signing
- Password reset tokens
- Other cryptographic operationsDatabase Configuration
# SQLite configuration (development environment)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# PostgreSQL configuration (recommended for production)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'mydatabase',
'USER': 'mydatabaseuser',
'PASSWORD': 'mypassword',
'HOST': '127.0.0.1',
'PORT': '5432',
'OPTIONS': {
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
},
}
}
# MySQL configuration
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'mydatabase',
'USER': 'mydatabaseuser',
'PASSWORD': 'mypassword',
'HOST': '127.0.0.1',
'PORT': '3306',
'OPTIONS': {
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
},
}
}
# Multiple database configuration
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'main_db',
'USER': 'postgres',
'PASSWORD': 'password',
'HOST': 'localhost',
'PORT': '5432',
},
'users_db': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'users_db',
'USER': 'mysql_user',
'PASSWORD': 'mysql_password',
'HOST': 'localhost',
'PORT': '3306',
}
}
# Database routing
DATABASE_ROUTERS = ['mysite.routers.DatabaseRouter']Application Configuration System
AppConfig Class
# apps.py
from django.apps import AppConfig
class BlogConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'blog'
verbose_name = 'Blog Management'
def ready(self):
"""Called when the application is ready"""
# Import signal handlers
import blog.signals
# Execute initialization code
self.setup_logging()
def setup_logging(self):
"""Set up logging configuration"""
import logging
logger = logging.getLogger(__name__)
logger.info(f'{self.verbose_name} application loaded')Application Registration
# settings.py
INSTALLED_APPS = [
# Using AppConfig class
'blog.apps.BlogConfig',
# Or simplified form
'blog',
# Third-party applications
'rest_framework',
'corsheaders',
# Django built-in applications
'django.contrib.admin',
'django.contrib.auth',
]Application Discovery and Loading
# Django application loading process
1. Read INSTALLED_APPS setting
2. Import each application's AppConfig
3. Call AppConfig.ready() method
4. Register models, management commands, etc.
# Check installed applications
from django.apps import apps
# Get all applications
all_apps = apps.get_app_configs()
for app in all_apps:
print(f"Application: {app.name}, Label: {app.label}")
# Get specific application
blog_app = apps.get_app_config('blog')
print(f"Application name: {blog_app.verbose_name}")
# Get application's models
blog_models = blog_app.get_models()
for model in blog_models:
print(f"Model: {model.__name__}")Middleware System
Middleware Concept
Middleware is Django's hook framework for request/response processing. It's a lightweight plugin system for globally altering Django's input or output.
# Middleware execution order
Request phase (top to bottom):
SecurityMiddleware
SessionMiddleware
CommonMiddleware
CsrfViewMiddleware
AuthenticationMiddleware
MessageMiddleware
XFrameOptionsMiddleware
↓
View processing
↓
Response phase (bottom to top):
XFrameOptionsMiddleware
MessageMiddleware
AuthenticationMiddleware
CsrfViewMiddleware
CommonMiddleware
SessionMiddleware
SecurityMiddlewareBuilt-in Middleware
# settings.py
MIDDLEWARE = [
# Security middleware - adds security-related HTTP headers
'django.middleware.security.SecurityMiddleware',
# Session middleware - enables session support
'django.contrib.sessions.middleware.SessionMiddleware',
# Common middleware - handles URL rewriting, ETags, etc.
'django.middleware.common.CommonMiddleware',
# CSRF protection middleware
'django.middleware.csrf.CsrfViewMiddleware',
# Authentication middleware - adds user attribute to request
'django.contrib.auth.middleware.AuthenticationMiddleware',
# Message middleware - enables message framework
'django.contrib.messages.middleware.MessageMiddleware',
# Clickjacking protection middleware
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]Custom Middleware
# middleware.py
import time
import logging
from django.utils.deprecation import MiddlewareMixin
class RequestLoggingMiddleware:
"""Request logging middleware"""
def __init__(self, get_response):
self.get_response = get_response
self.logger = logging.getLogger(__name__)
def __call__(self, request):
# Before request processing
start_time = time.time()
self.logger.info(f"Request started: {request.method} {request.path}")
# Call next middleware or view
response = self.get_response(request)
# After response processing
duration = time.time() - start_time
self.logger.info(f"Request completed: {response.status_code} ({duration:.2f}s)")
return response
def process_exception(self, request, exception):
"""Handle exceptions"""
self.logger.error(f"Request exception: {request.path} - {exception}")
return None
class CustomHeaderMiddleware:
"""Custom HTTP header middleware"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
# Add custom headers
response['X-Custom-Header'] = 'MyValue'
response['X-Request-ID'] = getattr(request, 'request_id', 'unknown')
return response
# Class-based middleware (old-style)
class LegacyMiddleware(MiddlewareMixin):
def process_request(self, request):
"""Process request"""
request.custom_attribute = 'custom_value'
return None
def process_response(self, request, response):
"""Process response"""
response['X-Legacy-Header'] = 'legacy_value'
return responseMiddleware Configuration and Usage
# settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'myapp.middleware.RequestLoggingMiddleware', # Custom middleware
'django.contrib.sessions.middleware.SessionMiddleware',
'myapp.middleware.CustomHeaderMiddleware', # Custom middleware
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
# Conditional middleware
if DEBUG:
MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware']Signal System
Django Signal Concept
Django signals are a dispatcher allowing decoupled applications to get notified when certain actions occur elsewhere in the framework.
# Common Django signals
from django.db.models.signals import (
pre_save, post_save, # Before/after saving
pre_delete, post_delete, # Before/after deleting
m2m_changed, # Many-to-many relation changes
)
from django.contrib.auth.signals import (
user_logged_in, # User logged in
user_logged_out, # User logged out
user_login_failed, # Login failed
)
from django.core.signals import (
request_started, # Request started
request_finished, # Request finished
)Signal Handlers
# signals.py
from django.db.models.signals import post_save, pre_delete
from django.contrib.auth.signals import user_logged_in
from django.contrib.auth.models import User
from django.dispatch import receiver
from .models import Profile, Article
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
"""Automatically create user profile when user is created"""
if created:
Profile.objects.create(user=instance)
print(f"Created profile for user {instance.username}")
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
"""Save user profile when user is saved"""
if hasattr(instance, 'profile'):
instance.profile.save()
@receiver(user_logged_in)
def log_user_login(sender, request, user, **kwargs):
"""Log user login"""
import logging
logger = logging.getLogger(__name__)
logger.info(f"User {user.username} logged in from {request.META.get('REMOTE_ADDR')}")
@receiver(pre_delete, sender=Article)
def backup_article_before_delete(sender, instance, **kwargs):
"""Backup article before deletion"""
import json
backup_data = {
'title': instance.title,
'content': instance.content,
'created_at': instance.created_at.isoformat(),
}
# Save to backup file or database
with open(f'backup_article_{instance.id}.json', 'w') as f:
json.dump(backup_data, f)
# Manual signal connection (without decorator)
def article_saved_handler(sender, instance, created, **kwargs):
if created:
print(f"New article created: {instance.title}")
else:
print(f"Article updated: {instance.title}")
post_save.connect(article_saved_handler, sender=Article)Custom Signals
# signals.py
import django.dispatch
# Define custom signals
article_published = django.dispatch.Signal()
user_profile_updated = django.dispatch.Signal()
# Sending signals in models
# models.py
from django.db import models
from .signals import article_published
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
is_published = models.BooleanField(default=False)
def publish(self):
self.is_published = True
self.save()
# Send custom signal
article_published.send(
sender=self.__class__,
instance=self,
user=self.author
)
# Handle custom signals
@receiver(article_published)
def notify_subscribers(sender, instance, user, **kwargs):
"""Notify subscribers when an article is published"""
print(f"Article '{instance.title}' published, author: {user}")
# Send email notification, push messages, etc.Logging System
Logging Configuration
# settings.py
import os
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
'style': '{',
},
'simple': {
'format': '{levelname} {message}',
'style': '{',
},
},
'filters': {
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
},
'handlers': {
'console': {
'level': 'INFO',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'file': {
'level': 'INFO',
'class': 'logging.FileHandler',
'filename': os.path.join(BASE_DIR, 'logs', 'django.log'),
'formatter': 'verbose',
},
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler',
'formatter': 'verbose',
}
},
'root': {
'handlers': ['console'],
},
'loggers': {
'django': {
'handlers': ['console', 'file'],
'level': 'INFO',
},
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},
'myapp': {
'handlers': ['console', 'file'],
'level': 'DEBUG',
'propagate': True,
},
}
}Using Logging
# views.py
import logging
# Get logger
logger = logging.getLogger(__name__)
def my_view(request):
logger.debug('This is debug information')
logger.info('This is info log')
logger.warning('This is warning')
logger.error('This is error')
logger.critical('This is critical error')
try:
# Some code that might fail
result = risky_operation()
except Exception as e:
logger.exception('Operation failed: %s', e)
raise
return render(request, 'template.html')
# Using structured logging
logger.info('User logged in', extra={
'user_id': request.user.id,
'ip_address': request.META.get('REMOTE_ADDR'),
'user_agent': request.META.get('HTTP_USER_AGENT'),
})Cache System
Cache Configuration
# settings.py
# Memory cache (development environment)
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake',
}
}
# Redis cache (recommended for production)
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
# Memcached cache
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': '127.0.0.1:11211',
}
}
# Multi-level cache
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
},
'sessions': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/2',
}
}
# Session cache
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'sessions'Using Cache
# views.py
from django.core.cache import cache
from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator
# Manual caching
def expensive_view(request):
# Try to get from cache
result = cache.get('expensive_calculation')
if result is None:
# Cache miss, perform calculation
result = perform_expensive_calculation()
# Store in cache, expire in 300 seconds
cache.set('expensive_calculation', result, 300)
return render(request, 'result.html', {'result': result})
# View cache decorator
@cache_page(60 * 15) # Cache for 15 minutes
def cached_view(request):
return render(request, 'template.html')
# Class-based view cache
@method_decorator(cache_page(60 * 15), name='dispatch')
class CachedView(View):
def get(self, request):
return render(request, 'template.html')
# Template fragment cache
# template.html
{% load cache %}
{% cache 500 sidebar request.user.username %}
<!-- Complex sidebar content -->
{% endcache %}
# Low-level cache API
from django.core.cache import caches
# Use specific cache
cache = caches['sessions']
cache.set('key', 'value', 300)
value = cache.get('key')
# Batch operations
cache.set_many({'a': 1, 'b': 2, 'c': 3}, 300)
values = cache.get_many(['a', 'b', 'c'])
# Atomic operations
cache.add('key', 'value', 300) # Only set if key doesn't exist
cache.delete('key')
cache.clear() # Clear all cacheChapter Summary
This chapter covered Django's fundamental concepts and core systems in depth:
Key Points:
- MVT Architecture: Model-View-Template design pattern
- Settings System: Project configuration and environment management
- Application Configuration: AppConfig class and application lifecycle
- Middleware System: Request/response processing hooks
- Signal System: Decoupled event notification mechanism
- Logging System: Structured logging
- Cache System: Performance optimization tool
Important Concepts:
- Django's request-response flow
- Middleware execution order and purpose
- Signal sending and receiving mechanism
- Cache strategies and usage scenarios
Best Practices:
- Organize settings files properly
- Use middleware order correctly
- Use signals appropriately for business logic
- Configure structured logging
- Choose appropriate cache strategies
In the next chapter, we will learn about Django views and URL configuration to understand how to handle HTTP requests and responses.