入门
安装与创建项目
通过 pip 安装 Django。项目包含设置和 URL 配置;应用是包含模型和视图的可复用模块。
pip install django
django-admin startproject myproject
cd myproject
python manage.py startapp myapp运行开发服务器
启动内置开发服务器并自动重载。切勿在生产环境中使用。
python manage.py runserver
# Default: http://127.0.0.1:8000
python manage.py runserver 8080
python manage.py runserver 0.0.0.0:8000迁移与超级用户
makemigrations 从模型变更生成迁移文件。migrate 将其应用到数据库。createsuperuser 创建管理员账户。
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser项目结构
manage.py 是命令行工具。内部包存放项目配置;应用目录存放应用级代码。
myproject/
manage.py # CLI utility
myproject/
settings.py # Configuration
urls.py # Root URL dispatcher
wsgi.py # WSGI entry point
asgi.py # ASGI entry point
myapp/
models.py # Database models
views.py # View functions
admin.py # Admin registration设置基础
INSTALLED_APPS 列出已激活的应用。在生产中设置 DEBUG=False 并配置 ALLOWED_HOSTS。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'myapp',
]
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
DEBUG = True
ALLOWED_HOSTS = []模型字段
定义模型
每个模型类映射到数据库表。每个字段映射到列。__str__ 定义显示名称。
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=200)
body = models.TextField()
published = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title常见字段类型
CharField 需要 max_length。auto_now_add 在创建时设置;auto_now 在每次保存时更新。EmailField/URLField 添加验证。
name = models.CharField(max_length=100)
description = models.TextField(blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
quantity = models.IntegerField(default=0)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
email = models.EmailField(unique=True)
url = models.URLField(blank=True)字段选项
null 是数据库级别;blank 是验证级别。choices 在表单和管理后台创建下拉选择。
title = models.CharField(
max_length=200,
null=True, # DB allows NULL
blank=True, # Form allows empty
default='',
choices=[('draft', 'Draft'), ('pub', 'Published')],
help_text='Article title',
verbose_name='Article Title',
)Meta 选项
ordering 设置默认排序。db_table 覆盖自动生成的表名。constraints 强制数据库级规则。
class Meta:
ordering = ['-created_at']
db_table = 'articles'
verbose_name_plural = 'Articles'
constraints = [
models.UniqueConstraint(
fields=['title', 'author'],
name='unique_title_per_author',
)
]模型方法与属性
重写 save() 添加保存前逻辑。@property 添加计算属性。务必调用 super().save()。
class Article(models.Model):
title = models.CharField(max_length=200)
views = models.IntegerField(default=0)
def __str__(self):
return self.title
def save(self, *args, **kwargs):
self.title = self.title.strip()
super().save(*args, **kwargs)
@property
def is_popular(self):
return self.views > 1000模型关系
ForeignKey(多对一)
ForeignKey 创建多对一关系。related_name 启用反向查询:author.books.all()。
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
author = models.ForeignKey(
Author,
on_delete=models.CASCADE,
related_name='books',
)
title = models.CharField(max_length=200)OneToOneField
OneToOneField 创建一对一链接。通过 user.profile 或 profile.user 访问。
class Profile(models.Model):
user = models.OneToOneField(
User,
on_delete=models.CASCADE,
)
bio = models.TextField()ManyToManyField
ManyToManyField 自动创建中间表。添加/移除:course.students.add(student)。
class Student(models.Model):
name = models.CharField(max_length=100)
class Course(models.Model):
students = models.ManyToManyField(Student, blank=True)
title = models.CharField(max_length=200)on_delete 选项
CASCADE 在父对象删除时删除子对象。SET_NULL 需要 null=True。PROTECT 抛出 ProtectedError。
# CASCADE - delete related objects
author = models.ForeignKey(Author, on_delete=models.CASCADE)
# SET_NULL - set to NULL (field must be nullable)
author = models.ForeignKey(Author, on_delete=models.SET_NULL, null=True)
# PROTECT - prevent deletion
author = models.ForeignKey(Author, on_delete=models.PROTECT)
# SET_DEFAULT - set to default value
author = models.ForeignKey(Author, on_delete=models.SET_DEFAULT, default=1)关联对象查询
正向访问使用字段名。反向访问使用 related_name(默认为 modelname_set)。
# Forward query
book.author.name
# Reverse query (uses related_name)
author.books.all()
author.books.filter(title__contains='Django')
# Add/remove in M2M
course.students.add(student1, student2)
course.students.remove(student1)
course.students.clear()
# Check membership
student in course.students.all()ORM 查询
创建与保存对象
create() 立即保存。get_or_create 返回 (对象, 是否创建布尔值) — 原子性获取或创建。
# Method 1: Create and save separately
article = Article(title='Hello', body='World')
article.save()
# Method 2: Create in one step
article = Article.objects.create(title='Hello', body='World')
# Method 3: get_or_create
obj, created = Article.objects.get_or_create(
title='Hello', defaults={'body': 'World'}
)检索单个对象
get() 未找到时抛出 DoesNotExist,多个时抛出 MultipleObjectsReturned。使用 first() 安全获取。
# Get by primary key
article = Article.objects.get(pk=1)
# Get by field
article = Article.objects.get(title='Hello')
# First/last (returns None if empty)
first = Article.objects.first()
last = Article.objects.last()
# Latest/earliest by field
latest = Article.objects.latest('created_at')过滤与排除
filter() 返回 QuerySet(惰性)。链式调用应用 AND 逻辑。QuerySet 仅在迭代时求值。
# Filter
Article.objects.filter(title__contains='Django')
Article.objects.filter(published=True)
# Exclude
Article.objects.exclude(views__lt=100)
# Chain filters (AND)
Article.objects.filter(author=1).exclude(draft=True)字段查找
查找使用双下划线语法。常用:exact, contains, icontains, gt, gte, lt, lte, in, startswith, range。
Article.objects.filter(title__icontains='django') # case-insensitive
Article.objects.filter(views__gte=100) # >=
Article.objects.filter(views__in=[100, 200, 300]) # IN
Article.objects.filter(created_at__year=2024) # date lookup
Article.objects.filter(title__startswith='Hello') # LIKE 'Hello%'
Article.objects.filter(title__isnull=True) # IS NULL排序与切片
切片在数据库层面应用 LIMIT。QuerySet 不支持负索引。
# Order by
Article.objects.order_by('-created_at') # descending
Article.objects.order_by('author', '-views')
# Slice (LIMIT/OFFSET)
Article.objects.all()[:5] # first 5
Article.objects.all()[5:10] # offset 5, limit 5
Article.objects.order_by('-views')[:3] # top 3
# Distinct
Article.objects.values('author').distinct()更新与删除
update() 适用于批量更改。save(update_fields=[...]) 避免竞态条件。delete() 默认级联删除。
# Bulk update
Article.objects.filter(draft=True).update(draft=False)
# Single update (only save this field)
article.title = 'New Title'
article.save(update_fields=['title'])
# Delete
article.delete() # returns (count, {model: count})
Article.objects.filter(views=0).delete() # bulk deleteORM 进阶
F 表达式
F() 引用数据库列值,无需将对象加载到 Python 即可实现原子更新。
from django.db.models import F
# Atomic increment (no race condition)
Article.objects.filter(pk=1).update(views=F('views') + 1)
# Compare two fields
Article.objects.filter(views__gt=F('min_views'))
# Arithmetic on fields
Article.objects.annotate(ratio=F('views') / F('likes'))Q 对象复杂查询
Q 对象支持 OR (|)、AND (&) 和 NOT (~) 逻辑。不用 Q 时,多个 filter() 调用仅支持 AND。
from django.db.models import Q
# OR
Article.objects.filter(Q(title__contains='Django') | Q(title__contains='Flask'))
# AND with NOT
Article.objects.filter(Q(published=True) & ~Q(views=0))
# OR + AND
Article.objects.filter(
(Q(author=1) | Q(author=2)) & Q(published=True)
)聚合
aggregate() 返回总计字典。annotate() 为每个对象添加计算值。
from django.db.models import Count, Sum, Avg, Max, Min
# Single aggregate
total = Article.objects.aggregate(total=Count('id'))
avg_views = Article.objects.aggregate(Avg('views'))
# Per-group aggregate
Author.objects.annotate(
article_count=Count('article'),
total_views=Sum('article__views'),
)注解
annotate() 为每个对象添加计算字段。可以对注解字段使用 filter() 和 order_by()。
from django.db.models import Count, Avg
# Add computed field to each object
authors = Author.objects.annotate(article_count=Count('book'))
# Filter on annotated field
Author.objects.annotate(
avg_rating=Avg('book__rating')
).filter(avg_rating__gte=4.0)select_related 与 prefetch_related
select_related 使用 SQL JOIN(正向 FK)。prefetch_related 使用第二个查询(反向/M2M)。两者都防止 N+1 查询。
# select_related (FK/OneToOne - single query with JOIN)
books = Book.objects.select_related('author').all()
# book.author.name -> no extra query
# prefetch_related (M2M/reverse - second query)
authors = Author.objects.prefetch_related('books').all()
# author.books.all() -> no extra query
# Deep prefetch
Author.objects.prefetch_related('books', 'books__reviews').all()函数视图
基本函数视图
视图接收 HttpRequest 并返回 HttpResponse。render() 加载模板并用上下文填充。
from django.shortcuts import render
from django.http import HttpResponse
def hello(request):
return HttpResponse("Hello, World!")
def home(request):
context = {'title': 'Home', 'items': [1, 2, 3]}
return render(request, 'home.html', context)HttpRequest 对象
request.GET/POST 是 QueryDict(使用 .get() 更安全)。request.user 是已认证用户或 AnonymousUser。
def my_view(request):
method = request.method # 'GET' or 'POST'
get_data = request.GET.get('q') # query param
post_data = request.POST.get('name')
path = request.path # '/articles/1/'
user = request.user # current user
files = request.FILES # uploaded files
meta = request.META.get('HTTP_HOST')HttpResponse 类型
JsonResponse 序列化为 JSON。redirect() 接受 URL 路径或带参数的命名 URL。
from django.http import HttpResponse, JsonResponse
from django.shortcuts import redirect
def json_view(request):
return JsonResponse({'status': 'ok', 'count': 42})
def redirect_view(request):
return redirect('/home/')
return redirect('article_detail', pk=1)
def csv_view(request):
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="data.csv"'
return response视图装饰器
login_required 将未认证用户重定向到 LOGIN_URL。require_POST/GET 限制 HTTP 方法。
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_POST, require_GET
@login_required
def dashboard(request):
return render(request, 'dashboard.html')
@require_POST
def submit(request):
pass # Only accepts POST
@require_GET
def search(request):
pass # Only accepts GET文件上传视图
request.FILES 包含上传的文件。对大文件使用 .chunks() 避免将整个文件加载到内存。
def upload_file(request):
if request.method == 'POST':
uploaded = request.FILES['file']
Document.objects.create(file=uploaded)
# Or save manually
with open(f'media/{uploaded.name}', 'wb') as f:
for chunk in uploaded.chunks():
f.write(chunk)
return redirect('success')
return render(request, 'upload.html')类视图
TemplateView
TemplateView 渲染静态模板。重写 get_context_data() 添加额外上下文。
from django.views.generic import TemplateView
class AboutView(TemplateView):
template_name = 'about.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = 'About Us'
return contextListView
ListView 自动分页并在上下文中提供 object_list 和 page_obj。重写 get_queryset() 进行过滤。
from django.views.generic import ListView
class ArticleListView(ListView):
model = Article
template_name = 'article_list.html'
context_object_name = 'articles'
paginate_by = 10
def get_queryset(self):
return Article.objects.filter(published=True)DetailView
DetailView 通过 URL 中的 pk 或 slug 获取单个对象。重写 get_object() 自定义获取逻辑。
from django.views.generic import DetailView
class ArticleDetailView(DetailView):
model = Article
template_name = 'article_detail.html'
context_object_name = 'article'
def get_object(self):
return Article.objects.get(
slug=self.kwargs['slug'], published=True
)CreateView 与 UpdateView
CreateView/UpdateView 处理表单显示、验证和保存。重写 form_valid() 在保存前添加逻辑。
from django.views.generic.edit import CreateView, UpdateView
class ArticleCreateView(CreateView):
model = Article
fields = ['title', 'body', 'author']
template_name = 'article_form.html'
success_url = '/articles/'
class ArticleUpdateView(UpdateView):
model = Article
fields = ['title', 'body']
template_name = 'article_form.html'Mixins
Mixins 为类视图添加可复用行为。Django 提供 LoginRequiredMixin、PermissionRequiredMixin。
from django.contrib.auth.mixins import LoginRequiredMixin
class OwnerRequiredMixin:
def get_queryset(self):
return super().get_queryset().filter(owner=self.request.user)
class ArticleListView(LoginRequiredMixin, OwnerRequiredMixin, ListView):
model = Article
paginate_by = 10URL 路由
基本 URL 配置
path() 将 URL 模式映射到视图。name= 启用反向 URL 查找。<int:pk> 捕 获整数。
from django.urls import path
from myapp import views
urlpatterns = [
path('', views.home, name='home'),
path('about/', views.about, name='about'),
path('article/<int:pk>/', views.article_detail, name='article_detail'),
]包含应用 URL
include() 将 URL 匹配委托给应用的 urls.py。app_name 为 URL 反向解析创建命名空间。
# myproject/urls.py
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('blog/', include('blog.urls')),
path('api/', include('api.urls')),
]
# blog/urls.py
app_name = 'blog'
urlpatterns = [
path('', views.list, name='list'),
path('<int:pk>/', views.detail, name='detail'),
]路径转换器
路径转换器自动类型转换 URL 片段。复杂正则模式使用 re_path。
path('article/<int:pk>/') # integer
path('article/<slug:slug>/') # slug string
path('article/<str:title>/') # non-slash string
path('user/<uuid:user_id>/') # UUID
path('file/<path:filepath>/') # any string including /
# Regex path (re_path)
from django.urls import re_path
re_path(r'^article/(?P<year>[0-9]{4})/$', views.year_archive)命名 URL 与 reverse()
命名 URL 允许通过名称引用路由而非硬编码路径。reverse() 在 Python 代码中生成 URL。
# urls.py
path('article/<int:pk>/', views.detail, name='article_detail')
# In views.py
from django.urls import reverse
url = reverse('article_detail', kwargs={'pk': 42})
# Returns '/article/42/'
# In templates
<a href="{% url 'article_detail' pk=article.pk %}">Read</a>URL 命名空间
命名空间防止应用间名称冲突。namespace 必须与应用的 app_name 匹配。
# myproject/urls.py
path('blog/', include(('blog.urls', 'blog'), namespace='blog'))
# blog/urls.py
app_name = 'blog'
urlpatterns = [
path('<int:pk>/', views.detail, name='detail'),
]
# Usage
reverse('blog:detail', kwargs={'pk': 1})
# Template: {% url 'blog:detail' pk=1 %}模板 (DTL)
模板继承
extends 继承基础模板。block 定义可覆盖区域。一个模板只能继承一个父模板。
{# base.html #}
<html>
<body>
{% block content %}{% endblock %}
{% block sidebar %}{% endblock %}
</body>
</html>
{# article.html #}
{% extends 'base.html' %}
{% block content %}
<h1>{{ article.title }}</h1>
{% endblock %}变量与标签
{{ }} 输出变量。{% %} 执行标签。forloop.counter 从 1 开始;forloop.counter0 从 0 开始。
{# Variables #}
{{ article.title }}
{{ user.get_full_name }}
{{ article.tags.0 }}
{# Tags #}
{% if article.published %}
Published
{% elif article.draft %}
Draft
{% else %}
Archived
{% endif %}
{% for item in items %}
{{ forloop.counter }}: {{ item }}
{% empty %}
No items
{% endfor %}模板过滤器
过滤器使用管道 | 语法修改 变量输出。有些在冒号后接受参数。过滤器可以链式使用。
{{ name|lower }}
{{ text|truncatewords:30 }}
{{ date|date:"Y-m-d" }}
{{ value|default:"N/A" }}
{{ list|length }}
{{ text|striptags }}
{{ price|floatformat:2 }}
{{ value|add:5 }}内置模 板标签
csrf_token 在所有 POST 表单中必需。include 渲染子模板。load 导入自定义标签库。
{% url 'article_detail' pk=1 %}
{% csrf_token %}
{% load static %}
{% include 'header.html' %}
{% with total=items|length %}
{{ total }}
{% endwith %}
{% now "Y-m-d" %}
{% blocktrans %}Hello{% endblocktrans %}自定义模板标签
标签位于 app/templatetags/ 目录。@register.simple_tag 用于函数;@register.filter 用于值修饰器。
# templatetags/my_tags.py
from django import template
register = template.Library()
@register.simple_tag
def multiply(a, b):
return a * b
@register.filter
def cut(value, arg):
return value.replace(arg, '')
# In template:
{% load my_tags %}
{% multiply 3 4 %}
{{ "hello world"|cut:" " }}表单
定义表单
forms.Form 创建独立表单。每个字段自动生成 HTML 控件。字段类型提供验证。
from django import forms
class ContactForm(forms.Form):
name = forms.CharField(max_length=100)
email = forms.EmailField()
message = forms.CharField(widget=forms.Textarea)
subject = forms.ChoiceField(
choices=[('general', 'General'), ('bug', 'Bug Report')],
)ModelForm
ModelForm 从模型自动构建表单。fields/exclude 控制显示哪些字段。widgets 自定义 HTML。
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ['title', 'body', 'author']
# Or: fields = '__all__'
# Or: exclude = ['created_at']
widgets = {
'body': forms.Textarea(attrs={'rows': 10}),
}
labels = {'title': 'Article Title'}表单验证
clean_<field>() 验证单个字段。clean() 验证整个表单。验证失败时抛出 ValidationError。
class SignupForm(forms.Form):
username = forms.CharField(max_length=50)
password = forms.CharField(widget=forms.PasswordInput)
def clean_username(self):
username = self.cleaned_data['username']
if User.objects.filter(username=username).exists():
raise forms.ValidationError("Username taken.")
return username
def clean(self):
cleaned = super().clean()
# Cross-field validation
return cleaned在模板中渲染表单
as_p/as_table/as_ul 自动渲染所有字段。自定义布局时逐个渲染字段。POST 需要 csrf_token。
<form method="post">
{% csrf_token %}
{{ form.as_p }}
{{ form.as_table }}
{{ form.as_ul }}
{# Manual rendering #}
{{ form.title.label_tag }}
{{ form.title }}
{{ form.title.errors }}
<button type="submit">Submit</button>
</form>在视图中处理表单
传入 request.POST 绑定表单。is_valid() 运行验证。save(commit=False) 返回未保存的对象。
def article_create(request):
if request.method == 'POST':
form = ArticleForm(request.POST)
if form.is_valid():
article = form.save(commit=False)
article.author = request.user
article.save()
form.save_m2m()
return redirect('article_detail', pk=article.pk)
else:
form = ArticleForm()
return render(request, 'form.html', {'form': form})表单控件
widgets 控制 HTML 渲染。传入 attrs 字典设置 CSS 类、占位符或 HTML5 输入类型。
class UploadForm(forms.Form):
title = forms.CharField(
widget=forms.TextInput(attrs={'class': 'form-control'})
)
file = forms.FileField(widget=forms.ClearableFileInput)
date = forms.DateField(widget=forms.DateInput(attrs={'type': 'date'}))
tags = forms.MultipleChoiceField(
choices=TAG_CHOICES,
widget=forms.CheckboxSelectMultiple,
)Admin 管理站点
在 Admin 中注册模型
@admin.register 优于 admin.site.register。模型必须注册才能在管理界面中显示。
from django.contrib import admin
from .models import Article
# Simple registration
admin.site.register(Article)
# With custom ModelAdmin
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ('title', 'author', 'published')ModelAdmin 自定义
list_display 在列表视图中显示列。list_filter 添加侧边栏过滤器。search_fields 启用搜索框。
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ('title', 'author', 'status')
list_filter = ('status', 'author', 'created_at')
search_fields = ('title', 'body')
ordering = ('-created_at',)
list_editable = ('status',)
list_per_page = 25
date_hierarchy = 'created_at'
readonly_fields = ('views', 'created_at')内联 Admin
内联允许在同一页面编辑关联模型。extra=1 默认显示一个空行。
class ReviewInline(admin.TabularInline):
model = Review
extra = 1
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
inlines = [ReviewInline]
# TabularInline: compact table format
# StackedInline: full form fields stacked自定义 Admin 操作
操作出现在列表页的下拉菜单中。它们接收 request 和 queryset。message_user 显示闪现消息。
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
actions = ['publish_selected']
@admin.action(description='Publish selected')
def publish_selected(self, request, queryset):
updated = queryset.update(published=True)
self.message_user(request, f'{updated} articles published.')Admin 站点配置
覆盖 site_header/site_title 进行品牌定制。如需多个管理站点,创建自定义 AdminSite 实例。
admin.site.site_header = 'My Site Admin'
admin.site.site_title = 'Admin Portal'
admin.site.index_title = 'Dashboard'
# Custom admin site
from django.contrib.admin import AdminSite
my_site = AdminSite(name='myadmin')
my_site.register(Article, ArticleAdmin)
# urls.py
path('myadmin/', my_site.urls)认证
内置 User 模型
Django 的 User 模型包含 username、password(哈希)、email、first_name、last_name 和权限标志。
from django.contrib.auth.models import User
user = User.objects.get(username='admin')
user.is_staff # can access admin
user.is_superuser # has all permissions
user.is_active # account enabled
user.set_password('newpass') # hash password
user.save()
user.check_password('secret') # returns True/False登录与登出
authenticate() 验证凭据(返回 User 或 None)。login() 创建会话。logout() 清除会话。
from django.contrib.auth import authenticate, login, logout
def login_view(request):
user = authenticate(request, username='admin', password='pass')
if user is not None:
login(request, user)
return redirect('dashboard')
return render(request, 'login.html')
def logout_view(request):
logout(request)
return redirect('home')用户注册
UserCreationForm 处理用户名、密码和密码确认,内置验证。
from django.contrib.auth.forms import UserCreationForm
def register(request):
if request.method == 'POST':
form = UserCreationForm(request.POST)
if form.is_valid():
user = form.save()
login(request, user) # auto-login after register
return redirect('home')
else:
form = UserCreationForm()
return render(request, 'register.html', {'form': form})login_required 装饰器
login_required 重定向到 LOGIN_URL(默认 /accounts/login/)。类视图使用 LoginRequiredMixin。
from django.contrib.auth.decorators import login_required
@login_required
def profile(request):
return render(request, 'profile.html')
@login_required(login_url='/custom-login/')
def settings(request):
return render(request, 'settings.html')
# Class-based view mixin
from django.contrib.auth.mixins import LoginRequiredMixin
class DashboardView(LoginRequiredMixin, ListView):
login_url = '/login/'
model = Article密码管理
update_session_auth_hash 防止修改密码后登出。密码验证器全站强制规则。
from django.contrib.auth import update_session_auth_hash
# Change password
user.set_password('newpass123')
user.save()
update_session_auth_hash(request, user) # keep user logged in
# Using form
from django.contrib.auth.forms import PasswordChangeForm
form = PasswordChangeForm(user, request.POST)
if form.is_valid():
form.save()自定义 User 模型
继承 AbstractUser 添加字段。尽早设置 AUTH_USER_MODEL — 后期修改很困难。
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
bio = models.TextField(blank=True)
avatar = models.ImageField(upload_to='avatars/', blank=True)
phone = models.CharField(max_length=20, blank=True)
# settings.py
AUTH_USER_MODEL = 'myapp.CustomUser'
# Admin
from django.contrib.auth.admin import UserAdmin
@admin.register(CustomUser)
class CustomUserAdmin(UserAdmin):
pass中间件
内置中间件
顺序很重要!SecurityMiddleware 在最前,然后是 sessions,再是 auth。CSRF 检查在视图处理前运行。
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',
]自定义函数中间件
函数中间件使用新式工厂模式。get_response 调用下一个中间件或视图。
def simple_middleware(get_response):
def middleware(request):
# Code before view (request phase)
response = get_response(request)
# Code after view (response phase)
return response
return middleware
# Example: timing middleware
import time
def timing_middleware(get_response):
def middleware(request):
start = time.time()
response = get_response(request)
response['X-Duration'] = str(time.time() - start)
return response
return middleware类中间件
__init__ 在启动时运行一次。__call__ 每次请求运行。process_view 可返回响应以绕过视图。
class MyMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Request phase (before view)
response = self.get_response(request)
# Response phase (after view)
return response
def process_view(self, request, view_func, args, kwargs):
return None # or HttpResponse to short-circuit中间件顺序与流程
请求从上到下穿过中间件;响应从下到上冒泡。安全中间件放在最前。
# Request phase: top to bottom
# Response phase: bottom to top
MIDDLEWARE = [
'A', # req: 1st -> resp: last
'B', # req: 2nd -> resp: 2nd
'C', # req: 3rd -> resp: 1st
]
# Request flows: A -> B -> C -> view
# Response flows: view -> C -> B -> A中间件用例
中间件可短路(提前返回 HttpResponse)或将数据附加到 request 供视图使用。
# IP restriction
class IPBlockMiddleware:
BLOCKED_IPS = ['192.168.1.100']
def __call__(self, request):
if request.META['REMOTE_ADDR'] in self.BLOCKED_IPS:
return HttpResponseForbidden('Blocked')
return self.get_response(request)
# Add context to request
class UserPrefMiddleware:
def __call__(self, request):
request.is_mobile = 'Mobile' in request.META.get('HTTP_USER_AGENT', '')
return self.get_response(request)静态与媒体文件
静态文件配置
STATIC_URL 是 URL 前缀。STATICFILES_DIRS 是源位置(开发)。STATIC_ROOT 是生产收集目标。
# settings.py
STATIC_URL = '/static/'
STATICFILES_DIRS = [BASE_DIR / 'static'] # dev location
STATIC_ROOT = BASE_DIR / 'staticfiles' # collectstatic output
# Project structure:
# static/
# css/style.css
# js/app.js
# img/logo.png开发中提供静态文件
Django 仅在 DEBUG=True 时自动提供静态文件。生产中使用 Web 服务器(nginx)或 WhiteNoise。
# urls.py (dev only)
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
# Only active when DEBUG=True在模板中使用静态文件
需要 {% load static %}。{% static %} 在生产中解析为带哈希的文件名(使用 ManifestStaticFilesStorage)。
{% load static %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<img src="{% static 'img/logo.png' %}" alt="Logo">
<script src="{% static 'js/app.js' %}"></script>
{# Dynamic static path #}
{% get_static_prefix as STATIC_URL %}
<img src="{{ STATIC_URL }}img/{{ theme }}.png">媒体文件配置
MEDIA_ROOT 是文件系统路径;MEDIA_URL 是 URL 前缀。upload_to 可以是路径字符串或可调用对象。
# settings.py
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
# urls.py (dev)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# Model with file upload
class Document(models.Model):
file = models.FileField(upload_to='documents/')
image = models.ImageField(upload_to='images/')
# Access
doc.file.url # '/media/documents/file.pdf'
doc.file.path # '/full/path/to/file.pdf'collectstatic 命令
部署前运行 collectstatic。WhiteNoise 让 Django 在生产中无需 nginx 即可提供静态文件。
# Collect all static files into STATIC_ROOT
python manage.py collectstatic
# With clearing
python manage.py collectstatic --clear
# Production with WhiteNoise
# pip install whitenoise
MIDDLEWARE = [..., 'whitenoise.middleware.WhiteNoiseMiddleware', ...]
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'信号
内置信号
信号允许解耦的应用响应操作。post_save 在 save() 完成后触发;pre_save 在之前触发。
from django.db.models.signals import (
post_save, pre_save, post_delete, pre_delete,
m2m_changed,
)
from django.contrib.auth.signals import (
user_logged_in, user_logged_out,
)
# Common signals:
# pre_save / post_save - before/after model save
# pre_delete / post_delete - before/after model delete
# m2m_changed - M2M relationship changes
# user_logged_in / user_logged_out连接信号接收器
@receiver 将函数连接到信号。sender 限定到特定模型。created 仅在新对象时为 True。
from django.db.models.signals import post_save
from django.dispatch import receiver
@receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_profile(sender, instance, **kwargs):
instance.profile.save()断开信号
disconnect() 在测试中或需要阻止级联操作时很有用。务必同时提供 receiver 和 sender。
from django.db.models.signals import post_save
# Disconnect
post_save.disconnect(
receiver=create_profile,
sender=User,
)
# Temporarily disconnect in tests
post_save.disconnect(handler, sender=Article)
# ... do stuff without signal ...
post_save.connect(handler, sender=Article)自定义信号
在模块级别定义信号。send() 通知所有接收器。kwargs 传递给所有已连接的接收器。
from django.dispatch import Signal, receiver
# Define a custom signal
order_created = Signal()
# Send the signal
order_created.send(
sender=Order,
order=order_instance,
user=request.user,
)
# Receive
@receiver(order_created, sender=Order)
def send_notification(sender, order, user, **kwargs):
pass # Send email notification信号用例与注意事项
在 AppConfig.ready() 中导入信号处理器以确保启动时连接。避免慢操作 — 异步工作使用 Celery。
# Good: Profile auto-creation
@receiver(post_save, sender=User)
def ensure_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.get_or_create(user=instance)
# Register in AppConfig.ready()
# apps.py
class MyAppConfig(AppConfig):
name = 'myapp'
def ready(self):
from . import signals # noqa缓存
缓存配置
生产环境推荐使用 Redis。LocMemCache 是进程本地的(worker 间不共享)。
# settings.py - Memcached
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': '127.0.0.1:11211',
}
}
# Redis
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379',
}
}
# Local memory (dev)
CACHES = {
'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'}
}低级缓存 API
cache.get() 对缺失的键返回 None。使用 get_or_set 实现原子性的不存在则设置。
from django.core.cache import cache
# Set
cache.set('key', 'value', timeout=300) # 5 minutes
cache.set_many({'a': 1, 'b': 2}, timeout=60)
# Get
value = cache.get('key') # None if missing
value = cache.get('key', 'default') # with default
data = cache.get_many(['a', 'b']) # dict
# Delete
cache.delete('key')
cache.clear() # delete all
# get_or_set (atomic)
value = cache.get_or_set('key', 'value', 300)模板缓存
{% cache %} 存储渲染的 HTML 片段。添加变化参数(用户 ID、页面)避免提供错误的缓存内容。
{% load cache %}
{# Cache block for 500 seconds #}
{% cache 500 sidebar %}
<div class="sidebar">
{{ expensive_data }}
</div>
{% endcache %}
{# Cache with varying key #}
{% cache 500 user_sidebar request.user.id %}
Welcome, {{ request.user.username }}
{% endcache %}视图级缓存
cache_page 缓存整个 HttpResponse。缓存键包含 URL,因此每个 URL 有独立的缓存条目。
from django.views.decorators.cache import cache_page
@cache_page(60 * 15)
def article_list(request):
return render(request, 'list.html', {'articles': Article.objects.all()})
# With cache key prefix
@cache_page(60 * 15, key_prefix='articles')
def article_detail(request, pk):
...
# Class-based view
from django.utils.decorators import method_decorator
class ArticleListView(ListView):
@method_decorator(cache_page(60 * 15))
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)缓存策略
vary_on_headers 为每个头值创建独立缓存条目。API 应基于 Accept-Language 或 Authorization 变化。
# Per-view cache with Vary headers
from django.views.decorators.vary import vary_on_headers
@cache_page(60 * 15)
@vary_on_headers('User-Agent')
def api_data(request):
...
# Model-level caching
def get_articles():
articles = cache.get('all_articles')
if articles is None:
articles = list(Article.objects.all())
cache.set('all_articles', articles, 300)
return articles测试
编写测试
TestCase 将测试包装在数据库事务中(回滚)。SimpleTestCase 用于无数据库访问的测试(更快)。
from django.test import TestCase, SimpleTestCase
from .models import Article
class ArticleModelTest(TestCase):
def setUp(self):
self.article = Article.objects.create(
title='Test', body='Content'
)
def test_str(self):
self.assertEqual(str(self.article), 'Test')
def test_field_default(self):
self.assertEqual(self.article.views, 0)
# SimpleTestCase: no DB access
class MathTest(SimpleTestCase):
def test_addition(self):
self.assertEqual(1 + 1, 2)使用测试客户端
self.client 模拟 HTTP 请求而无需启动服务器。.login() 为后续请求进行认证。
class ViewTest(TestCase):
def setUp(self):
self.user = User.objects.create_user('test', '[email protected]', 'pass')
def test_login(self):
response = self.client.post('/login/', {
'username': 'test', 'password': 'pass'
})
self.assertEqual(response.status_code, 302)
def test_protected_view(self):
self.client.login(username='test', password='pass')
response = self.client.get('/dashboard/')
self.assertEqual(response.status_code, 200)模型与 QuerySet 测试
测试数据在 setUp() 中创建并在每个测试后回滚。使用 .count() 和索引验证结果。
class ArticleQuerySetTest(TestCase):
def setUp(self):
Article.objects.create(title='A', views=100, published=True)
Article.objects.create(title='B', views=50, published=False)
def test_published_filter(self):
published = Article.objects.filter(published=True)
self.assertEqual(published.count(), 1)
def test_ordering(self):
articles = Article.objects.order_by('-views')
self.assertEqual(articles[0].title, 'A')URL 与表单测试
reverse() 从名称构建 URL。resolve() 检查 URL 映射到哪个视图。传入 data= 字典测试表单。
from django.urls import reverse, resolve
class URLTest(TestCase):
def test_article_list_url(self):
url = reverse('article_list')
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
def test_resolve(self):
url = reverse('article_detail', kwargs={'pk': 1})
self.assertEqual(resolve(url).func, views.article_detail)
class FormTest(TestCase):
def test_valid_form(self):
form = ArticleForm(data={'title': 'Hi', 'body': '...'})
self.assertTrue(form.is_valid())Fixtures 与测试命令
Fixtures 从 JSON 文件加载测试数据。通过应用、类或方法路径运行特定测试。
# Create fixture
python manage.py dumpdata myapp.Article --indent=2 > fixtures/articles.json
# Use in test
class FixtureTest(TestCase):
fixtures = ['articles.json']
def test_data_loaded(self):
self.assertEqual(Article.objects.count(), 10)
# Run tests
python manage.py test
python manage.py test myapp
python manage.py test myapp.tests.ArticleModelTestDjango REST Framework
安装与配置 DRF
将 rest_framework 添加到 INSTALLED_APPS。在 REST_FRAMEWORK 设置字典中配置默认认证/权限。
pip install djangorestframework
# settings.py
INSTALLED_APPS = [..., 'rest_framework', 'rest_framework.authtoken']
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}序列化器
ModelSerializer 从模型自动生成字段。source= 从关联对象拉取数据。validate_<field>() 添加自定义验证。
from rest_framework import serializers
class ArticleSerializer(serializers.ModelSerializer):
author_name = serializers.CharField(source='author.username', read_only=True)
class Meta:
model = Article
fields = ['id', 'title', 'body', 'author', 'author_name']
read_only_fields = ['id', 'author_name']
def validate_title(self, value):
if len(value) < 5:
raise serializers.ValidationError("Title too short.")
return valueViewSets
ModelViewSet 提供 CRUD(list, create, retrieve, update, destroy)。重写 get_queryset() 进行过滤。
from rest_framework import viewsets, permissions
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def get_queryset(self):
qs = super().get_queryset()
author = self.request.query_params.get('author')
if author:
qs = qs.filter(author__username=author)
return qs
def perform_create(self, serializer):
serializer.save(author=self.request.user)路由器
DefaultRouter 自动生成 URL 模式和 API 根视图。queryset 动态时使用 basename 进行 URL 命名。
from rest_framework.routers import DefaultRouter
from .views import ArticleViewSet
router = DefaultRouter()
router.register(r'articles', ArticleViewSet, basename='article')
# router.register(r'users', UserViewSet)
# urls.py
urlpatterns = [
path('api/', include(router.urls)),
]
# Generates: /api/articles/, /api/articles/{id}/认证
Token 认证使用单个静态令牌。JWT 签发 access + refresh 令牌。SimpleJWT 是最流行的 JWT 库。
# Token Authentication
from rest_framework.authtoken.views import obtain_auth_token
urlpatterns = [
path('api/token/', obtain_auth_token),
]
# JWT Authentication (pip install djangorestframework-simplejwt)
from rest_framework_simplejwt.views import (
TokenObtainPairView, TokenRefreshView,
)
urlpatterns = [
path('api/token/', TokenObtainPairView.as_view()),
path('api/token/refresh/', TokenRefreshView.as_view()),
]权限
SAFE_METHODS 为 GET/HEAD/OPTIONS。has_object_permission 检查每对象访问。get_permissions() 允许按操作规则。
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True # GET, HEAD, OPTIONS
return obj.author == request.user
# Built-in: IsAuthenticated, IsAdminUser,
# IsAuthenticatedOrReadOnly, AllowAny
# Per-action
class ArticleViewSet(viewsets.ModelViewSet):
permission_classes = [IsOwnerOrReadOnly]
def get_permissions(self):
if self.action == 'create':
return [permissions.IsAuthenticated()]
return super().get_permissions()部署与安全
生产环境设置
切勿将 SECRET_KEY 提交到版本控制。使用环境变量。DEBUG=False 防止错误页面中的信息泄露。
# settings.py
DEBUG = False
ALLOWED_HOSTS = ['example.com', 'www.example.com']
SECRET_KEY = os.environ['SECRET_KEY']
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'mydb',
'USER': os.environ['DB_USER'],
'PASSWORD': os.environ['DB_PASS'],
'HOST': 'localhost',
'PORT': '5432',
}
}生产中的静态文件
collectstatic 将所有静态文件收集到 STATIC_ROOT。WhiteNoise 从 Django 提供 gzip 压缩的哈希文件。
# Collect static files
python manage.py collectstatic --noinput
# WhiteNoise for serving static
MIDDLEWARE = [..., 'whitenoise.middleware.WhiteNoiseMiddleware', ...]
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
# Or with nginx:
# location /static/ { alias /path/to/staticfiles/; }安全设置
SSL_REDIRECT 强制 HTTPS。HSTS 告诉浏览器始终使用 HTTPS。X_FRAME_OPTIONS 防止点击劫持。
# settings.py
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
CSRF_TRUSTED_ORIGINS = ['https://example.com']WSGI/ASGI 部署
Gunicorn 是同步的(传统 Django)。Uvicorn 支持 ASGI(异步视图、WebSocket)。使用进程管理 器。
# Gunicorn (WSGI)
# pip install gunicorn
gunicorn myproject.wsgi:application --bind 0.0.0.0:8000 --workers 3
# Uvicorn (ASGI, for async/websockets)
# pip install uvicorn
uvicorn myproject.asgi:application --host 0.0.0.0 --port 8000 --workers 4
# Supervisor config
[program:django]
command=gunicorn myproject.wsgi:application
directory=/app
autostart=true
autorestart=true生产检查清单
check --deploy 运行 Django 的部署检查。它标记缺失的安全设置和生产就绪问题。
# Run security check
python manage.py check --deploy
# Key items:
# - DEBUG = False
# - ALLOWED_HOSTS set
# - SECRET_KEY from env
# - HTTPS enabled (SECURE_SSL_REDIRECT)
# - HSTS configured
# - collectstatic run
# - Database (PostgreSQL/MySQL)
# - Caching (Redis/Memcached)
# - Error monitoring (Sentry)相关 Django 代码片段
Copy-paste ready code for common tasks.
Model Definition
Define a Django model with field types and meta options.
ORM QuerySet
Filter, exclude, annotate and chain QuerySets.
Class-Based Views
Use generic class-based views for common CRUD flows.
URL Routing
Wire URLs to views with path converters and includes.
ModelForms
Build forms from models with validation and widgets.
Template Tags
Use built-in tags and filters in Django templates.
Admin Customization
Customize the Django admin with list_display and actions.
Authentication
Login, logout, and protect views with auth decorators.
这篇内容对您有帮助吗?