django-framework
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDjango Framework Skill
Django框架技能
progressive_disclosure: entry_point: summary: "Full-featured Python web framework with batteries included (ORM, admin, auth)" when_to_use: - "When building content-heavy web applications" - "When needing built-in admin interface" - "When using Django ORM and migrations" - "When building REST APIs with Django REST Framework" quick_start: - "pip install django" - "django-admin startproject myproject" - "python manage.py runserver" token_estimate: entry: 75-90 full: 4500-5500
progressive_disclosure: entry_point: summary: "功能齐全的Python Web框架,内置丰富组件(ORM、管理后台、认证系统)" when_to_use: - "构建内容密集型Web应用时" - "需要内置管理后台界面时" - "使用Django ORM和数据库迁移时" - "基于Django REST Framework构建REST API时" quick_start: - "pip install django" - "django-admin startproject myproject" - "python manage.py runserver" token_estimate: entry: 75-90 full: 4500-5500
Overview
概述
Django is a high-level Python web framework that encourages rapid development and clean, pragmatic design. Built by experienced developers, it takes care of much of the hassle of web development, enabling focus on writing applications without reinventing the wheel.
Key Philosophy: "Batteries included" - Django comes with extensive built-in features including ORM, authentication, admin interface, forms, and security features.
Django是一个高级Python Web框架,倡导快速开发与简洁务实的设计理念。由经验丰富的开发者打造,它能处理Web开发中的诸多繁琐工作,让开发者专注于业务逻辑实现,无需重复造轮子。
核心理念:"内置丰富组件" - Django包含大量内置功能,如ORM、认证系统、管理后台、表单处理和安全防护等。
Core Concepts
核心概念
MVT Architecture (Model-View-Template)
MVT架构(模型-视图-模板)
Django follows the MVT pattern:
- Model: Data layer (ORM models, database schema)
- View: Business logic (handles requests, returns responses)
- Template: Presentation layer (HTML with Django template language)
Django遵循MVT模式:
- Model(模型):数据层(ORM模型、数据库表结构)
- View(视图):业务逻辑层(处理请求、返回响应)
- Template(模板):表现层(结合Django模板语言的HTML)
Project vs Apps
项目与应用
- Project: The entire Django application (settings, URLs, WSGI config)
- Apps: Modular components (blog, auth, API) that can be reused across projects
bash
undefined- Project(项目):完整的Django应用(包含配置、URL路由、WSGI配置)
- Apps(应用):模块化组件(如博客、认证、API),可在多个项目中复用
bash
undefinedCreate project
创建项目
django-admin startproject myproject
cd myproject
django-admin startproject myproject
cd myproject
Create app
创建应用
python manage.py startapp blog
python manage.py startapp blog
Register app in settings.py
在settings.py中注册应用
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
# ...
'blog',
]
undefinedINSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
# ...
'blog',
]
undefinedModels and ORM
模型与ORM
Model Definition
模型定义
python
undefinedpython
undefinedmodels.py
models.py
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
name = models.CharField(max_length=100, unique=True)
slug = models.SlugField(unique=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name_plural = "categories"
ordering = ['name']
def __str__(self):
return self.nameclass 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()
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
published_at = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-published_at']
indexes = [
models.Index(fields=['-published_at']),
models.Index(fields=['slug']),
]
def __str__(self):
return self.titleundefinedfrom django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
name = models.CharField(max_length=100, unique=True)
slug = models.SlugField(unique=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name_plural = "categories"
ordering = ['name']
def __str__(self):
return self.nameclass 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()
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
published_at = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-published_at']
indexes = [
models.Index(fields=['-published_at']),
models.Index(fields=['slug']),
]
def __str__(self):
return self.titleundefinedCommon Field Types
常用字段类型
python
undefinedpython
undefinedText fields
文本字段
models.CharField(max_length=200)
models.TextField()
models.SlugField()
models.EmailField()
models.URLField()
models.CharField(max_length=200)
models.TextField()
models.SlugField()
models.EmailField()
models.URLField()
Numeric fields
数值字段
models.IntegerField()
models.DecimalField(max_digits=10, decimal_places=2)
models.FloatField()
models.IntegerField()
models.DecimalField(max_digits=10, decimal_places=2)
models.FloatField()
Date/time fields
日期/时间字段
models.DateField()
models.DateTimeField()
models.DurationField()
models.DateField()
models.DateTimeField()
models.DurationField()
Boolean
布尔类型
models.BooleanField(default=False)
models.BooleanField(default=False)
Relationships
关联关系
models.ForeignKey(Model, on_delete=models.CASCADE)
models.ManyToManyField(Model)
models.OneToOneField(Model, on_delete=models.CASCADE)
models.ForeignKey(Model, on_delete=models.CASCADE)
models.ManyToManyField(Model)
models.OneToOneField(Model, on_delete=models.CASCADE)
Files
文件字段
models.FileField(upload_to='uploads/')
models.ImageField(upload_to='images/')
models.FileField(upload_to='uploads/')
models.ImageField(upload_to='images/')
JSON (PostgreSQL)
JSON类型(仅PostgreSQL支持)
models.JSONField()
undefinedmodels.JSONField()
undefinedMigrations
数据库迁移
bash
undefinedbash
undefinedCreate migrations after model changes
模型变更后创建迁移文件
python manage.py makemigrations
python manage.py makemigrations
View SQL that will be executed
查看将执行的SQL语句
python manage.py sqlmigrate blog 0001
python manage.py sqlmigrate blog 0001
Apply migrations
应用迁移
python manage.py migrate
python manage.py migrate
Create empty migration for custom operations
创建空迁移文件用于自定义操作
python manage.py makemigrations --empty blog
python manage.py makemigrations --empty blog
Reverse migration
回滚迁移
python manage.py migrate blog 0001
undefinedpython manage.py migrate blog 0001
undefinedQuerySet API
QuerySet API
python
undefinedpython
undefinedBasic queries
基础查询
Post.objects.all()
Post.objects.filter(status='published')
Post.objects.exclude(status='draft')
Post.objects.get(pk=1) # Returns single object or raises DoesNotExist
Post.objects.all()
Post.objects.filter(status='published')
Post.objects.exclude(status='draft')
Post.objects.get(pk=1) # 返回单个对象,不存在则抛出DoesNotExist异常
Chaining filters
链式过滤
Post.objects.filter(status='published').filter(category__name='Tech')
Post.objects.filter(status='published').filter(category__name='Tech')
Field lookups
字段查询
Post.objects.filter(title__icontains='django') # Case-insensitive contains
Post.objects.filter(published_at__year=2024)
Post.objects.filter(published_at__gte=datetime(2024, 1, 1))
Post.objects.filter(author__username__startswith='john')
Post.objects.filter(title__icontains='django') # 不区分大小写的包含查询
Post.objects.filter(published_at__year=2024)
Post.objects.filter(published_at__gte=datetime(2024, 1, 1))
Post.objects.filter(author__username__startswith='john')
Ordering
排序
Post.objects.order_by('-published_at')
Post.objects.order_by('category', '-created_at')
Post.objects.order_by('-published_at')
Post.objects.order_by('category', '-created_at')
Limiting
结果限制
Post.objects.all()[:5] # First 5
Post.objects.all()[5:10] # Offset pagination
Post.objects.all()[:5] # 前5条
Post.objects.all()[5:10] # 偏移分页
Aggregation
聚合查询
from django.db.models import Count, Avg, Sum
Category.objects.annotate(post_count=Count('post'))
Post.objects.aggregate(avg_length=Avg('content__length'))
from django.db.models import Count, Avg, Sum
Category.objects.annotate(post_count=Count('post'))
Post.objects.aggregate(avg_length=Avg('content__length'))
Q objects for complex queries
Q对象用于复杂查询
from django.db.models import Q
Post.objects.filter(Q(status='published') | Q(author=request.user))
Post.objects.filter(Q(status='published') & ~Q(category=None))
from django.db.models import Q
Post.objects.filter(Q(status='published') | Q(author=request.user))
Post.objects.filter(Q(status='published') & ~Q(category=None))
F expressions for field comparisons
F表达式用于字段间比较
from django.db.models import F
Post.objects.filter(updated_at__gt=F('published_at'))
from django.db.models import F
Post.objects.filter(updated_at__gt=F('published_at'))
Select/Prefetch related (performance optimization)
Select/Prefetch关联(性能优化)
Post.objects.select_related('author', 'category') # SQL JOIN
Post.objects.prefetch_related('tags') # Separate query for M2M
undefinedPost.objects.select_related('author', 'category') # SQL JOIN查询
Post.objects.prefetch_related('tags') # 为多对多关系执行单独查询
undefinedModel Methods and Properties
模型方法与属性
python
class Post(models.Model):
# ... fields ...
@property
def is_published(self):
return self.status == 'published' and self.published_at is not None
def get_absolute_url(self):
from django.urls import reverse
return reverse('post_detail', kwargs={'slug': self.slug})
def save(self, *args, **kwargs):
# Auto-generate slug if not provided
if not self.slug:
from django.utils.text import slugify
self.slug = slugify(self.title)
super().save(*args, **kwargs)
class Meta:
verbose_name = "blog post"
verbose_name_plural = "blog posts"python
class Post(models.Model):
# ... 字段定义 ...
@property
def is_published(self):
return self.status == 'published' and self.published_at is not None
def get_absolute_url(self):
from django.urls import reverse
return reverse('post_detail', kwargs={'slug': self.slug})
def save(self, *args, **kwargs):
# 自动生成slug(如果未提供)
if not self.slug:
from django.utils.text import slugify
self.slug = slugify(self.title)
super().save(*args, **kwargs)
class Meta:
verbose_name = "博客文章"
verbose_name_plural = "博客文章"Views
视图
Function-Based Views (FBV)
基于函数的视图(FBV)
python
undefinedpython
undefinedviews.py
views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.http import JsonResponse, HttpResponse
from django.contrib.auth.decorators import login_required
from .models import Post
from .forms import PostForm
def post_list(request):
posts = Post.objects.filter(status='published').select_related('author', 'category')
context = {'posts': posts}
return render(request, 'blog/post_list.html', context)
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug, status='published')
return render(request, 'blog/post_detail.html', {'post': post})
@login_required
def post_create(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save()
return redirect('post_detail', slug=post.slug)
else:
form = PostForm()
return render(request, 'blog/post_form.html', {'form': form})
def api_posts(request):
posts = Post.objects.filter(status='published').values('title', 'slug', 'published_at')
return JsonResponse(list(posts), safe=False)
undefinedfrom django.shortcuts import render, get_object_or_404, redirect
from django.http import JsonResponse, HttpResponse
from django.contrib.auth.decorators import login_required
from .models import Post
from .forms import PostForm
def post_list(request):
posts = Post.objects.filter(status='published').select_related('author', 'category')
context = {'posts': posts}
return render(request, 'blog/post_list.html', context)
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug, status='published')
return render(request, 'blog/post_detail.html', {'post': post})
@login_required
def post_create(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save()
return redirect('post_detail', slug=post.slug)
else:
form = PostForm()
return render(request, 'blog/post_form.html', {'form': form})
def api_posts(request):
posts = Post.objects.filter(status='published').values('title', 'slug', 'published_at')
return JsonResponse(list(posts), safe=False)
undefinedClass-Based Views (CBV)
基于类的视图(CBV)
python
undefinedpython
undefinedviews.py
views.py
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.urls import reverse_lazy
from .models import Post
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
paginate_by = 10
def get_queryset(self):
return Post.objects.filter(status='published').select_related('author', 'category')class PostDetailView(DetailView):
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'
def get_queryset(self):
return Post.objects.filter(status='published')class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
form_class = PostForm
template_name = 'blog/post_form.html'
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Post
form_class = PostForm
template_name = 'blog/post_form.html'
def test_func(self):
post = self.get_object()
return self.request.user == post.authorclass PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Post
success_url = reverse_lazy('post_list')
def test_func(self):
post = self.get_object()
return self.request.user == post.authorundefinedfrom django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.urls import reverse_lazy
from .models import Post
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
paginate_by = 10
def get_queryset(self):
return Post.objects.filter(status='published').select_related('author', 'category')class PostDetailView(DetailView):
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'
def get_queryset(self):
return Post.objects.filter(status='published')class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
form_class = PostForm
template_name = 'blog/post_form.html'
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Post
form_class = PostForm
template_name = 'blog/post_form.html'
def test_func(self):
post = self.get_object()
return self.request.user == post.authorclass PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Post
success_url = reverse_lazy('post_list')
def test_func(self):
post = self.get_object()
return self.request.user == post.authorundefinedURLs and Routing
URL与路由
python
undefinedpython
undefinedproject/urls.py
project/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('blog/', include('blog.urls')),
path('api/', include('api.urls')),
]
from django.contrib import admin
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
blog/urls.py
from django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
path('', views.PostListView.as_view(), name='post_list'),
path('post/slug:slug/', views.PostDetailView.as_view(), name='post_detail'),
path('post/create/', views.PostCreateView.as_view(), name='post_create'),
path('post/slug:slug/edit/', views.PostUpdateView.as_view(), name='post_update'),
path('post/slug:slug/delete/', views.PostDeleteView.as_view(), name='post_delete'),
# Function-based views
path('api/posts/', views.api_posts, name='api_posts'),]
undefinedfrom django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
path('', views.PostListView.as_view(), name='post_list'),
path('post/slug:slug/', views.PostDetailView.as_view(), name='post_detail'),
path('post/create/', views.PostCreateView.as_view(), name='post_create'),
path('post/slug:slug/edit/', views.PostUpdateView.as_view(), name='post_update'),
path('post/slug:slug/delete/', views.PostDeleteView.as_view(), name='post_delete'),
# 基于函数的视图
path('api/posts/', views.api_posts, name='api_posts'),]
undefinedTemplates
模板
Template Syntax
模板语法
django
{# blog/templates/blog/post_list.html #}
{% extends 'base.html' %}
{% load static %}
{% block title %}Blog Posts{% endblock %}
{% block content %}
<h1>Blog Posts</h1>
{% if posts %}
{% for post in posts %}
<article class="post">
<h2><a href="{% url 'blog:post_detail' post.slug %}">{{ post.title }}</a></h2>
<p class="meta">
By {{ post.author.username }} on {{ post.published_at|date:"F d, Y" }}
in {{ post.category.name }}
</p>
<p>{{ post.content|truncatewords:50 }}</p>
</article>
{% empty %}
<p>No posts found.</p>
{% endfor %}
{# Pagination #}
{% if is_paginated %}
<div class="pagination">
{% if page_obj.has_previous %}
<a href="?page=1">First</a>
<a href="?page={{ page_obj.previous_page_number }}">Previous</a>
{% endif %}
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">Next</a>
<a href="?page={{ page_obj.paginator.num_pages }}">Last</a>
{% endif %}
</div>
{% endif %}
{% else %}
<p>No posts available.</p>
{% endif %}
{% endblock %}django
{# blog/templates/blog/post_list.html #}
{% extends 'base.html' %}
{% load static %}
{% block title %}博客文章{% endblock %}
{% block content %}
<h1>博客文章</h1>
{% if posts %}
{% for post in posts %}
<article class="post">
<h2><a href="{% url 'blog:post_detail' post.slug %}">{{ post.title }}</a></h2>
<p class="meta">
作者:{{ post.author.username }} 发布于 {{ post.published_at|date:"F d, Y" }}
分类:{{ post.category.name }}
</p>
<p>{{ post.content|truncatewords:50 }}</p>
</article>
{% empty %}
<p>暂无文章。</p>
{% endfor %}
{# 分页 #}
{% if is_paginated %}
<div class="pagination">
{% if page_obj.has_previous %}
<a href="?page=1">首页</a>
<a href="?page={{ page_obj.previous_page_number }}">上一页</a>
{% endif %}
第 {{ page_obj.number }} 页,共 {{ page_obj.paginator.num_pages }} 页
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">下一页</a>
<a href="?page={{ page_obj.paginator.num_pages }}">末页</a>
{% endif %}
</div>
{% endif %}
{% else %}
<p>暂无可用文章。</p>
{% endif %}
{% endblock %}Template Filters and Tags
模板过滤器与标签
django
{# Common filters #}
{{ value|lower }}
{{ value|upper }}
{{ value|title }}
{{ value|truncatewords:30 }}
{{ value|date:"Y-m-d" }}
{{ value|default:"N/A" }}
{{ html_content|safe }} {# Disable auto-escaping #}
{{ url|urlencode }}
{# Custom template tag #}
{% load custom_tags %}
{% get_recent_posts 5 as recent %}
{# Include other templates #}
{% include 'blog/partials/post_card.html' with post=post %}
{# Static files #}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<img src="{% static 'images/logo.png' %}" alt="Logo">django
{# 常用过滤器 #}
{{ value|lower }}
{{ value|upper }}
{{ value|title }}
{{ value|truncatewords:30 }}
{{ value|date:"Y-m-d" }}
{{ value|default:"N/A" }}
{{ html_content|safe }} {# 禁用自动转义 #}
{{ url|urlencode }}
{# 自定义模板标签 #}
{% load custom_tags %}
{% get_recent_posts 5 as recent %}
{# 引入其他模板 #}
{% include 'blog/partials/post_card.html' with post=post %}
{# 静态文件 #}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<img src="{% static 'images/logo.png' %}" alt="Logo">Forms
表单
Form Definition
表单定义
python
undefinedpython
undefinedforms.py
forms.py
from django import forms
from .models import Post, Category
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'slug', 'category', 'content', 'status']
widgets = {
'content': forms.Textarea(attrs={'rows': 10}),
'slug': forms.TextInput(attrs={'placeholder': 'auto-generated-if-empty'}),
}
def clean_slug(self):
slug = self.cleaned_data.get('slug')
if slug and Post.objects.filter(slug=slug).exclude(pk=self.instance.pk).exists():
raise forms.ValidationError('This slug is already in use.')
return slugclass ContactForm(forms.Form):
name = forms.CharField(max_length=100)
email = forms.EmailField()
subject = forms.CharField(max_length=200)
message = forms.CharField(widget=forms.Textarea)
def clean_email(self):
email = self.cleaned_data.get('email')
if email and not email.endswith('@example.com'):
raise forms.ValidationError('Please use your company email.')
return email
def send_email(self):
# Send email logic
passundefinedfrom django import forms
from .models import Post, Category
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'slug', 'category', 'content', 'status']
widgets = {
'content': forms.Textarea(attrs={'rows': 10}),
'slug': forms.TextInput(attrs={'placeholder': '未提供则自动生成'}),
}
def clean_slug(self):
slug = self.cleaned_data.get('slug')
if slug and Post.objects.filter(slug=slug).exclude(pk=self.instance.pk).exists():
raise forms.ValidationError('该slug已被使用。')
return slugclass ContactForm(forms.Form):
name = forms.CharField(max_length=100)
email = forms.EmailField()
subject = forms.CharField(max_length=200)
message = forms.CharField(widget=forms.Textarea)
def clean_email(self):
email = self.cleaned_data.get('email')
if email and not email.endswith('@example.com'):
raise forms.ValidationError('请使用公司邮箱。')
return email
def send_email(self):
# 发送邮件逻辑
passundefinedForm Usage in Views
视图中使用表单
python
def contact_view(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
form.send_email()
messages.success(request, 'Message sent successfully!')
return redirect('contact_success')
else:
form = ContactForm()
return render(request, 'contact.html', {'form': form})python
def contact_view(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
form.send_email()
messages.success(request, '消息发送成功!')
return redirect('contact_success')
else:
form = ContactForm()
return render(request, 'contact.html', {'form': form})Form Rendering in Templates
模板中渲染表单
django
<form method="post">
{% csrf_token %}
{# Auto-render all fields #}
{{ form.as_p }}
{# Manual field rendering #}
<div class="form-group">
{{ form.title.label_tag }}
{{ form.title }}
{% if form.title.errors %}
<div class="errors">{{ form.title.errors }}</div>
{% endif %}
</div>
<button type="submit">Submit</button>
</form>django
<form method="post">
{% csrf_token %}
{# 自动渲染所有字段 #}
{{ form.as_p }}
{# 手动渲染字段 #}
<div class="form-group">
{{ form.title.label_tag }}
{{ form.title }}
{% if form.title.errors %}
<div class="errors">{{ form.title.errors }}</div>
{% endif %}
</div>
<button type="submit">提交</button>
</form>Django Admin
Django管理后台
Basic Admin Configuration
基础管理后台配置
python
undefinedpython
undefinedadmin.py
admin.py
from django.contrib import admin
from .models import Post, Category
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ['name', 'slug', 'created_at']
prepopulated_fields = {'slug': ('name',)}
search_fields = ['name']
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'category', 'status', 'published_at']
list_filter = ['status', 'category', 'created_at']
search_fields = ['title', 'content']
prepopulated_fields = {'slug': ('title',)}
date_hierarchy = 'published_at'
ordering = ['-published_at']
fieldsets = (
('Basic Information', {
'fields': ('title', 'slug', 'author', 'category')
}),
('Content', {
'fields': ('content',)
}),
('Publication', {
'fields': ('status', 'published_at')
}),
)
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.select_related('author', 'category')undefinedfrom django.contrib import admin
from .models import Post, Category
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ['name', 'slug', 'created_at']
prepopulated_fields = {'slug': ('name',)}
search_fields = ['name']
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'category', 'status', 'published_at']
list_filter = ['status', 'category', 'created_at']
search_fields = ['title', 'content']
prepopulated_fields = {'slug': ('title',)}
date_hierarchy = 'published_at'
ordering = ['-published_at']
fieldsets = (
('基础信息', {
'fields': ('title', 'slug', 'author', 'category')
}),
('内容', {
'fields': ('content',)
}),
('发布设置', {
'fields': ('status', 'published_at')
}),
)
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.select_related('author', 'category')undefinedAdvanced Admin Features
高级管理后台功能
python
class PostAdmin(admin.ModelAdmin):
# Custom actions
actions = ['make_published', 'make_draft']
def make_published(self, request, queryset):
updated = queryset.update(status='published')
self.message_user(request, f'{updated} posts marked as published.')
make_published.short_description = "Mark selected posts as published"
# Inline editing
class TagInline(admin.TabularInline):
model = Post.tags.through
extra = 1
inlines = [TagInline]
# Custom methods in list_display
def author_email(self, obj):
return obj.author.email
author_email.short_description = 'Author Email'
list_display = ['title', 'author', 'author_email', 'status']python
class PostAdmin(admin.ModelAdmin):
# 自定义操作
actions = ['make_published', 'make_draft']
def make_published(self, request, queryset):
updated = queryset.update(status='published')
self.message_user(request, f'{updated} 篇文章已标记为发布状态。')
make_published.short_description = "将选中文章标记为发布"
# 内联编辑
class TagInline(admin.TabularInline):
model = Post.tags.through
extra = 1
inlines = [TagInline]
# 列表中的自定义方法
def author_email(self, obj):
return obj.author.email
author_email.short_description = '作者邮箱'
list_display = ['title', 'author', 'author_email', 'status']Authentication and Permissions
认证与权限
User Authentication
用户认证
python
undefinedpython
undefinedviews.py
views.py
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.decorators import login_required, permission_required
def login_view(request):
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
return redirect('home')
return render(request, 'login.html')
def logout_view(request):
logout(request)
return redirect('home')
def register_view(request):
if request.method == 'POST':
form = UserCreationForm(request.POST)
if form.is_valid():
user = form.save()
login(request, user)
return redirect('home')
else:
form = UserCreationForm()
return render(request, 'register.html', {'form': form})
@login_required
def profile_view(request):
return render(request, 'profile.html')
@permission_required('blog.add_post')
def create_post_view(request):
# Only users with 'add_post' permission can access
pass
undefinedfrom django.contrib.auth import authenticate, login, logout
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.decorators import login_required, permission_required
def login_view(request):
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
return redirect('home')
return render(request, 'login.html')
def logout_view(request):
logout(request)
return redirect('home')
def register_view(request):
if request.method == 'POST':
form = UserCreationForm(request.POST)
if form.is_valid():
user = form.save()
login(request, user)
return redirect('home')
else:
form = UserCreationForm()
return render(request, 'register.html', {'form': form})
@login_required
def profile_view(request):
return render(request, 'profile.html')
@permission_required('blog.add_post')
def create_post_view(request):
# 仅拥有'add_post'权限的用户可访问
pass
undefinedCustom User Model
自定义用户模型
python
undefinedpython
undefinedmodels.py
models.py
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
bio = models.TextField(blank=True)
avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
website = models.URLField(blank=True)
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
bio = models.TextField(blank=True)
avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
website = models.URLField(blank=True)
settings.py
settings.py
AUTH_USER_MODEL = 'accounts.CustomUser'
undefinedAUTH_USER_MODEL = 'accounts.CustomUser'
undefinedPermissions
权限管理
python
undefinedpython
undefinedCheck permissions in views
在视图中检查权限
if request.user.has_perm('blog.delete_post'):
# User can delete posts
pass
if request.user.has_perm('blog.delete_post'):
# 用户可删除文章
pass
Check in templates
在模板中检查权限
{% if perms.blog.add_post %}
<a href="{% url 'post_create' %}">Create Post</a>
{% endif %}
{% if perms.blog.add_post %}
<a href="{% url 'post_create' %}">创建文章</a>
{% endif %}
Custom permissions
自定义权限
class Post(models.Model):
class Meta:
permissions = [
('can_publish', 'Can publish posts'),
]
undefinedclass Post(models.Model):
class Meta:
permissions = [
('can_publish', '可发布文章'),
]
undefinedDjango REST Framework
Django REST Framework
Installation and Setup
安装与配置
bash
pip install djangorestframeworkpython
undefinedbash
pip install djangorestframeworkpython
undefinedsettings.py
settings.py
INSTALLED_APPS = [
# ...
'rest_framework',
]
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
],
}
undefinedINSTALLED_APPS = [
# ...
'rest_framework',
]
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
],
}
undefinedSerializers
序列化器
python
undefinedpython
undefinedserializers.py
serializers.py
from rest_framework import serializers
from .models import Post, Category
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['id', 'name', 'slug']
class PostSerializer(serializers.ModelSerializer):
author = serializers.ReadOnlyField(source='author.username')
category = CategorySerializer(read_only=True)
category_id = serializers.PrimaryKeyRelatedField(
queryset=Category.objects.all(),
source='category',
write_only=True
)
class Meta:
model = Post
fields = ['id', 'title', 'slug', 'author', 'category', 'category_id',
'content', 'status', 'published_at', 'created_at']
read_only_fields = ['author', 'created_at']
def validate_title(self, value):
if len(value) < 5:
raise serializers.ValidationError("Title must be at least 5 characters.")
return valueundefinedfrom rest_framework import serializers
from .models import Post, Category
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['id', 'name', 'slug']
class PostSerializer(serializers.ModelSerializer):
author = serializers.ReadOnlyField(source='author.username')
category = CategorySerializer(read_only=True)
category_id = serializers.PrimaryKeyRelatedField(
queryset=Category.objects.all(),
source='category',
write_only=True
)
class Meta:
model = Post
fields = ['id', 'title', 'slug', 'author', 'category', 'category_id',
'content', 'status', 'published_at', 'created_at']
read_only_fields = ['author', 'created_at']
def validate_title(self, value):
if len(value) < 5:
raise serializers.ValidationError("标题长度至少为5个字符。")
return valueundefinedAPI Views
API视图
python
undefinedpython
undefinedviews.py
views.py
from rest_framework import viewsets, permissions, status
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import Post
from .serializers import PostSerializer
class IsAuthorOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.author == request.user
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsAuthorOrReadOnly]
lookup_field = 'slug'
def get_queryset(self):
queryset = Post.objects.select_related('author', 'category')
status = self.request.query_params.get('status')
if status:
queryset = queryset.filter(status=status)
return queryset
def perform_create(self, serializer):
serializer.save(author=self.request.user)
@action(detail=True, methods=['post'])
def publish(self, request, slug=None):
post = self.get_object()
post.status = 'published'
post.published_at = timezone.now()
post.save()
return Response({'status': 'post published'})undefinedfrom rest_framework import viewsets, permissions, status
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import Post
from .serializers import PostSerializer
class IsAuthorOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.author == request.user
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsAuthorOrReadOnly]
lookup_field = 'slug'
def get_queryset(self):
queryset = Post.objects.select_related('author', 'category')
status = self.request.query_params.get('status')
if status:
queryset = queryset.filter(status=status)
return queryset
def perform_create(self, serializer):
serializer.save(author=self.request.user)
@action(detail=True, methods=['post'])
def publish(self, request, slug=None):
post = self.get_object()
post.status = 'published'
post.published_at = timezone.now()
post.save()
return Response({'status': '文章已发布'})undefinedAPI URLs
API路由
python
undefinedpython
undefinedapi/urls.py
api/urls.py
from rest_framework.routers import DefaultRouter
from blog.views import PostViewSet
router = DefaultRouter()
router.register(r'posts', PostViewSet)
urlpatterns = router.urls
undefinedfrom rest_framework.routers import DefaultRouter
from blog.views import PostViewSet
router = DefaultRouter()
router.register(r'posts', PostViewSet)
urlpatterns = router.urls
undefinedTesting
测试
Unit Tests with Django TestCase
使用Django TestCase进行单元测试
python
undefinedpython
undefinedtests.py
tests.py
from django.test import TestCase, Client
from django.contrib.auth import get_user_model
from django.urls import reverse
from .models import Post, Category
User = get_user_model()
class PostModelTest(TestCase):
def setUp(self):
self.user = User.objects.create_user(username='testuser', password='12345')
self.category = Category.objects.create(name='Tech', slug='tech')
def test_post_creation(self):
post = Post.objects.create(
title='Test Post',
slug='test-post',
author=self.user,
category=self.category,
content='Test content'
)
self.assertEqual(post.title, 'Test Post')
self.assertEqual(str(post), 'Test Post')
def test_get_absolute_url(self):
post = Post.objects.create(
title='Test Post',
slug='test-post',
author=self.user,
content='Test'
)
self.assertEqual(post.get_absolute_url(), '/blog/post/test-post/')class PostViewTest(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user(username='testuser', password='12345')
self.post = Post.objects.create(
title='Test Post',
slug='test-post',
author=self.user,
content='Test content',
status='published'
)
def test_post_list_view(self):
response = self.client.get(reverse('blog:post_list'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Test Post')
def test_post_detail_view(self):
response = self.client.get(reverse('blog:post_detail', kwargs={'slug': 'test-post'}))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Test Post')
def test_post_create_requires_login(self):
response = self.client.get(reverse('blog:post_create'))
self.assertEqual(response.status_code, 302) # Redirect to login
def test_post_create_authenticated(self):
self.client.login(username='testuser', password='12345')
response = self.client.post(reverse('blog:post_create'), {
'title': 'New Post',
'slug': 'new-post',
'content': 'New content',
'status': 'draft'
})
self.assertEqual(Post.objects.count(), 2)undefinedfrom django.test import TestCase, Client
from django.contrib.auth import get_user_model
from django.urls import reverse
from .models import Post, Category
User = get_user_model()
class PostModelTest(TestCase):
def setUp(self):
self.user = User.objects.create_user(username='testuser', password='12345')
self.category = Category.objects.create(name='Tech', slug='tech')
def test_post_creation(self):
post = Post.objects.create(
title='测试文章',
slug='test-post',
author=self.user,
category=self.category,
content='测试内容'
)
self.assertEqual(post.title, '测试文章')
self.assertEqual(str(post), '测试文章')
def test_get_absolute_url(self):
post = Post.objects.create(
title='测试文章',
slug='test-post',
author=self.user,
content='测试内容'
)
self.assertEqual(post.get_absolute_url(), '/blog/post/test-post/')class PostViewTest(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user(username='testuser', password='12345')
self.post = Post.objects.create(
title='测试文章',
slug='test-post',
author=self.user,
content='测试内容',
status='published'
)
def test_post_list_view(self):
response = self.client.get(reverse('blog:post_list'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, '测试文章')
def test_post_detail_view(self):
response = self.client.get(reverse('blog:post_detail', kwargs={'slug': 'test-post'}))
self.assertEqual(response.status_code, 200)
self.assertContains(response, '测试文章')
def test_post_create_requires_login(self):
response = self.client.get(reverse('blog:post_create'))
self.assertEqual(response.status_code, 302) # 重定向到登录页
def test_post_create_authenticated(self):
self.client.login(username='testuser', password='12345')
response = self.client.post(reverse('blog:post_create'), {
'title': '新文章',
'slug': 'new-post',
'content': '新内容',
'status': 'draft'
})
self.assertEqual(Post.objects.count(), 2)undefinedTesting with pytest-django
使用pytest-django进行测试
bash
pip install pytest-django pytest-covpython
undefinedbash
pip install pytest-django pytest-covpython
undefinedpytest.ini
pytest.ini
[pytest]
DJANGO_SETTINGS_MODULE = myproject.settings
python_files = tests.py test_*.py *_tests.py
[pytest]
DJANGO_SETTINGS_MODULE = myproject.settings
python_files = tests.py test_*.py *_tests.py
conftest.py
conftest.py
import pytest
from django.contrib.auth import get_user_model
User = get_user_model()
@pytest.fixture
def user(db):
return User.objects.create_user(username='testuser', password='12345')
@pytest.fixture
def category(db):
from blog.models import Category
return Category.objects.create(name='Tech', slug='tech')
@pytest.fixture
def post(db, user, category):
from blog.models import Post
return Post.objects.create(
title='Test Post',
slug='test-post',
author=user,
category=category,
content='Test content',
status='published'
)
import pytest
from django.contrib.auth import get_user_model
User = get_user_model()
@pytest.fixture
def user(db):
return User.objects.create_user(username='testuser', password='12345')
@pytest.fixture
def category(db):
from blog.models import Category
return Category.objects.create(name='Tech', slug='tech')
@pytest.fixture
def post(db, user, category):
from blog.models import Post
return Post.objects.create(
title='测试文章',
slug='test-post',
author=user,
category=category,
content='测试内容',
status='published'
)
test_models.py
test_models.py
import pytest
from blog.models import Post
@pytest.mark.django_db
def test_post_creation(user, category):
post = Post.objects.create(
title='Test Post',
slug='test-post',
author=user,
category=category,
content='Test content'
)
assert post.title == 'Test Post'
assert str(post) == 'Test Post'
@pytest.mark.django_db
def test_post_queryset(post):
posts = Post.objects.filter(status='published')
assert posts.count() == 1
assert posts.first() == post
import pytest
from blog.models import Post
@pytest.mark.django_db
def test_post_creation(user, category):
post = Post.objects.create(
title='测试文章',
slug='test-post',
author=user,
category=category,
content='测试内容'
)
assert post.title == '测试文章'
assert str(post) == '测试文章'
@pytest.mark.django_db
def test_post_queryset(post):
posts = Post.objects.filter(status='published')
assert posts.count() == 1
assert posts.first() == post
test_views.py
test_views.py
import pytest
from django.urls import reverse
@pytest.mark.django_db
def test_post_list_view(client, post):
response = client.get(reverse('blog:post_list'))
assert response.status_code == 200
assert 'Test Post' in str(response.content)
@pytest.mark.django_db
def test_post_create_requires_login(client):
response = client.get(reverse('blog:post_create'))
assert response.status_code == 302
@pytest.mark.django_db
def test_post_create_authenticated(client, user):
client.force_login(user)
response = client.post(reverse('blog:post_create'), {
'title': 'New Post',
'slug': 'new-post',
'content': 'New content',
'status': 'draft'
})
assert Post.objects.count() == 1
import pytest
from django.urls import reverse
@pytest.mark.django_db
def test_post_list_view(client, post):
response = client.get(reverse('blog:post_list'))
assert response.status_code == 200
assert '测试文章' in str(response.content)
@pytest.mark.django_db
def test_post_create_requires_login(client):
response = client.get(reverse('blog:post_create'))
assert response.status_code == 302
@pytest.mark.django_db
def test_post_create_authenticated(client, user):
client.force_login(user)
response = client.post(reverse('blog:post_create'), {
'title': '新文章',
'slug': 'new-post',
'content': '新内容',
'status': 'draft'
})
assert Post.objects.count() == 1
Run tests with coverage
运行测试并生成覆盖率报告
pytest --cov=blog --cov-report=html
pytest --cov=blog --cov-report=html
undefinedundefinedDatabase Optimization
数据库优化
Select Related and Prefetch Related
Select Related与Prefetch Related
python
undefinedpython
undefinedN+1 query problem (BAD)
N+1查询问题(不推荐)
posts = Post.objects.all()
for post in posts:
print(post.author.username) # Hits database for each post
posts = Post.objects.all()
for post in posts:
print(post.author.username) # 每篇文章都会触发一次数据库查询
Solution with select_related (for ForeignKey/OneToOne)
解决方案:select_related(适用于ForeignKey/OneToOne关系)
posts = Post.objects.select_related('author', 'category')
for post in posts:
print(post.author.username) # No additional queries
posts = Post.objects.select_related('author', 'category')
for post in posts:
print(post.author.username) # 无额外数据库查询
Solution with prefetch_related (for ManyToMany/Reverse ForeignKey)
解决方案:prefetch_related(适用于ManyToMany/反向ForeignKey关系)
posts = Post.objects.prefetch_related('tags')
for post in posts:
for tag in post.tags.all(): # No additional queries
print(tag.name)
posts = Post.objects.prefetch_related('tags')
for post in posts:
for tag in post.tags.all(): # 无额外数据库查询
print(tag.name)
Advanced prefetch with filtering
高级预取:带过滤条件
from django.db.models import Prefetch
posts = Post.objects.prefetch_related(
Prefetch('comments', queryset=Comment.objects.filter(approved=True))
)
undefinedfrom django.db.models import Prefetch
posts = Post.objects.prefetch_related(
Prefetch('comments', queryset=Comment.objects.filter(approved=True))
)
undefinedDatabase Indexing
数据库索引
python
class Post(models.Model):
title = models.CharField(max_length=200, db_index=True)
class Meta:
indexes = [
models.Index(fields=['status', '-published_at']),
models.Index(fields=['author', 'status']),
]python
class Post(models.Model):
title = models.CharField(max_length=200, db_index=True)
class Meta:
indexes = [
models.Index(fields=['status', '-published_at']),
models.Index(fields=['author', 'status']),
]Bulk Operations
批量操作
python
undefinedpython
undefinedBulk create (single query)
批量创建(单次查询)
posts = [
Post(title=f'Post {i}', content=f'Content {i}', author=user)
for i in range(100)
]
Post.objects.bulk_create(posts)
posts = [
Post(title=f'文章 {i}', content=f'内容 {i}', author=user)
for i in range(100)
]
Post.objects.bulk_create(posts)
Bulk update (single query)
批量更新(单次查询)
Post.objects.filter(status='draft').update(status='published')
Post.objects.filter(status='draft').update(status='published')
Bulk delete
批量删除
Post.objects.filter(created_at__lt=old_date).delete()
undefinedPost.objects.filter(created_at__lt=old_date).delete()
undefinedMiddleware and Signals
中间件与信号
Custom Middleware
自定义中间件
python
undefinedpython
undefinedmiddleware.py
middleware.py
class RequestLoggingMiddleware:
def init(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Code before view
print(f"Request: {request.method} {request.path}")
response = self.get_response(request)
# Code after view
print(f"Response: {response.status_code}")
return responseclass RequestLoggingMiddleware:
def init(self, get_response):
self.get_response = get_response
def __call__(self, request):
# 视图执行前的代码
print(f"请求:{request.method} {request.path}")
response = self.get_response(request)
# 视图执行后的代码
print(f"响应:{response.status_code}")
return responsesettings.py
settings.py
MIDDLEWARE = [
# ...
'myapp.middleware.RequestLoggingMiddleware',
]
undefinedMIDDLEWARE = [
# ...
'myapp.middleware.RequestLoggingMiddleware',
]
undefinedSignals
信号
python
undefinedpython
undefinedsignals.py
signals.py
from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from django.contrib.auth import get_user_model
from .models import Post
User = get_user_model()
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=Post)
def notify_post_published(sender, instance, **kwargs):
if instance.status == 'published' and instance.published_at:
# Send notification
pass
@receiver(pre_delete, sender=Post)
def cleanup_post_files(sender, instance, **kwargs):
# Delete associated files
if instance.image:
instance.image.delete(save=False)
from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from django.contrib.auth import get_user_model
from .models import Post
User = get_user_model()
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=Post)
def notify_post_published(sender, instance, **kwargs):
if instance.status == 'published' and instance.published_at:
# 发送通知逻辑
pass
@receiver(pre_delete, sender=Post)
def cleanup_post_files(sender, instance, **kwargs):
# 删除关联文件
if instance.image:
instance.image.delete(save=False)
apps.py
apps.py
class BlogConfig(AppConfig):
name = 'blog'
def ready(self):
import blog.signalsundefinedclass BlogConfig(AppConfig):
name = 'blog'
def ready(self):
import blog.signalsundefinedSettings and Configuration
设置与配置
Settings Best Practices
配置最佳实践
python
undefinedpython
undefinedsettings/base.py
settings/base.py
import os
from pathlib import Path
BASE_DIR = Path(file).resolve().parent.parent
SECRET_KEY = os.environ.get('SECRET_KEY')
DEBUG = False
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Third-party
'rest_framework',
# Local
'blog',
]
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('DB_NAME'),
'USER': os.environ.get('DB_USER'),
'PASSWORD': os.environ.get('DB_PASSWORD'),
'HOST': os.environ.get('DB_HOST', 'localhost'),
'PORT': os.environ.get('DB_PORT', '5432'),
}
}
import os
from pathlib import Path
BASE_DIR = Path(file).resolve().parent.parent
SECRET_KEY = os.environ.get('SECRET_KEY')
DEBUG = False
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 第三方应用
'rest_framework',
# 本地应用
'blog',
]
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('DB_NAME'),
'USER': os.environ.get('DB_USER'),
'PASSWORD': os.environ.get('DB_PASSWORD'),
'HOST': os.environ.get('DB_HOST', 'localhost'),
'PORT': os.environ.get('DB_PORT', '5432'),
}
}
settings/development.py
settings/development.py
from .base import *
DEBUG = True
ALLOWED_HOSTS = ['localhost', '127.0.0.1']
from .base import *
DEBUG = True
ALLOWED_HOSTS = ['localhost', '127.0.0.1']
settings/production.py
settings/production.py
from .base import *
DEBUG = False
ALLOWED_HOSTS = [os.environ.get('ALLOWED_HOST')]
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
undefinedfrom .base import *
DEBUG = False
ALLOWED_HOSTS = [os.environ.get('ALLOWED_HOST')]
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
undefinedDeployment
部署
Production Checklist
生产环境检查清单
bash
undefinedbash
undefinedCheck deployment readiness
检查部署就绪状态
python manage.py check --deploy
undefinedpython manage.py check --deploy
undefinedDocker Deployment
Docker部署
dockerfile
undefineddockerfile
undefinedDockerfile
Dockerfile
FROM python:3.11-slim
ENV PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN python manage.py collectstatic --noinput
CMD ["gunicorn", "myproject.wsgi:application", "--bind", "0.0.0.0:8000"]
```yamlFROM python:3.11-slim
ENV PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN python manage.py collectstatic --noinput
CMD ["gunicorn", "myproject.wsgi:application", "--bind", "0.0.0.0:8000"]
```yamldocker-compose.yml
docker-compose.yml
version: '3.8'
services:
db:
image: postgres:15
environment:
POSTGRES_DB: mydb
POSTGRES_USER: myuser
POSTGRES_PASSWORD: mypassword
volumes:
- postgres_data:/var/lib/postgresql/data
web:
build: .
command: gunicorn myproject.wsgi:application --bind 0.0.0.0:8000
volumes:
- .:/app
- static_volume:/app/staticfiles
ports:
- "8000:8000"
env_file:
- .env
depends_on:
- db
nginx:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- static_volume:/app/staticfiles
ports:
- "80:80"
depends_on:
- web
volumes:
postgres_data:
static_volume:
undefinedversion: '3.8'
services:
db:
image: postgres:15
environment:
POSTGRES_DB: mydb
POSTGRES_USER: myuser
POSTGRES_PASSWORD: mypassword
volumes:
- postgres_data:/var/lib/postgresql/data
web:
build: .
command: gunicorn myproject.wsgi:application --bind 0.0.0.0:8000
volumes:
- .:/app
- static_volume:/app/staticfiles
ports:
- "8000:8000"
env_file:
- .env
depends_on:
- db
nginx:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- static_volume:/app/staticfiles
ports:
- "80:80"
depends_on:
- web
volumes:
postgres_data:
static_volume:
undefinedGunicorn Configuration
Gunicorn配置
python
undefinedpython
undefinedgunicorn.conf.py
gunicorn.conf.py
bind = "0.0.0.0:8000"
workers = 4
worker_class = "sync"
worker_connections = 1000
timeout = 30
keepalive = 2
accesslog = "-"
errorlog = "-"
loglevel = "info"
undefinedbind = "0.0.0.0:8000"
workers = 4
worker_class = "sync"
worker_connections = 1000
timeout = 30
keepalive = 2
accesslog = "-"
errorlog = "-"
loglevel = "info"
undefinedSecurity Best Practices
安全最佳实践
python
undefinedpython
undefinedsettings.py (production)
settings.py(生产环境)
SECRET_KEY = os.environ.get('SECRET_KEY') # Never hardcode
DEBUG = False
ALLOWED_HOSTS = ['yourdomain.com']
SECRET_KEY = os.environ.get('SECRET_KEY') # 切勿硬编码
DEBUG = False
ALLOWED_HOSTS = ['yourdomain.com']
HTTPS/SSL
HTTPS/SSL配置
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_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
Security headers
安全响应头
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = 'DENY'
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = 'DENY'
Password validation
密码验证
AUTH_PASSWORD_VALIDATORS = [
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'},
{'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
]
AUTH_PASSWORD_VALIDATORS = [
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'},
{'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
]
CSRF protection (automatically enabled)
CSRF防护(默认已启用)
Always use {% csrf_token %} in forms
表单中务必使用 {% csrf_token %}
undefinedundefinedCommon Patterns and Best Practices
常用模式与最佳实践
Environment Variables
环境变量
python
undefinedpython
undefinedUse python-decouple or django-environ
使用python-decouple或django-environ
from decouple import config
SECRET_KEY = config('SECRET_KEY')
DEBUG = config('DEBUG', default=False, cast=bool)
DATABASE_URL = config('DATABASE_URL')
undefinedfrom decouple import config
SECRET_KEY = config('SECRET_KEY')
DEBUG = config('DEBUG', default=False, cast=bool)
DATABASE_URL = config('DATABASE_URL')
undefinedCustom Management Commands
自定义管理命令
python
undefinedpython
undefinedblog/management/commands/cleanup_posts.py
blog/management/commands/cleanup_posts.py
from django.core.management.base import BaseCommand
from blog.models import Post
from datetime import timedelta
from django.utils import timezone
class Command(BaseCommand):
help = 'Delete old draft posts'
def add_arguments(self, parser):
parser.add_argument('--days', type=int, default=30)
def handle(self, *args, **options):
days = options['days']
cutoff_date = timezone.now() - timedelta(days=days)
deleted = Post.objects.filter(
status='draft',
created_at__lt=cutoff_date
).delete()
self.stdout.write(self.style.SUCCESS(f'Deleted {deleted[0]} posts'))from django.core.management.base import BaseCommand
from blog.models import Post
from datetime import timedelta
from django.utils import timezone
class Command(BaseCommand):
help = '删除旧的草稿文章'
def add_arguments(self, parser):
parser.add_argument('--days', type=int, default=30)
def handle(self, *args, **options):
days = options['days']
cutoff_date = timezone.now() - timedelta(days=days)
deleted = Post.objects.filter(
status='draft',
created_at__lt=cutoff_date
).delete()
self.stdout.write(self.style.SUCCESS(f'已删除 {deleted[0]} 篇文章'))Run: python manage.py cleanup_posts --days=60
运行命令:python manage.py cleanup_posts --days=60
undefinedundefinedCaching
缓存
python
undefinedpython
undefinedsettings.py
settings.py
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
}
}
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
}
}
views.py
views.py
from django.views.decorators.cache import cache_page
from django.core.cache import cache
@cache_page(60 * 15) # Cache for 15 minutes
def post_list(request):
posts = Post.objects.filter(status='published')
return render(request, 'blog/post_list.html', {'posts': posts})
from django.views.decorators.cache import cache_page
from django.core.cache import cache
@cache_page(60 * 15) # 缓存15分钟
def post_list(request):
posts = Post.objects.filter(status='published')
return render(request, 'blog/post_list.html', {'posts': posts})
Low-level cache API
底层缓存API
def get_post_count():
count = cache.get('post_count')
if count is None:
count = Post.objects.filter(status='published').count()
cache.set('post_count', count, 60 * 60) # Cache for 1 hour
return count
undefineddef get_post_count():
count = cache.get('post_count')
if count is None:
count = Post.objects.filter(status='published').count()
cache.set('post_count', count, 60 * 60) # 缓存1小时
return count
undefinedQuick Reference
快速参考
Common Commands
常用命令
bash
undefinedbash
undefinedProject management
项目管理
django-admin startproject myproject
python manage.py startapp myapp
python manage.py runserver
python manage.py runserver 0.0.0.0:8000
django-admin startproject myproject
python manage.py startapp myapp
python manage.py runserver
python manage.py runserver 0.0.0.0:8000
Database
数据库
python manage.py makemigrations
python manage.py migrate
python manage.py showmigrations
python manage.py sqlmigrate app_name 0001
python manage.py dbshell
python manage.py makemigrations
python manage.py migrate
python manage.py showmigrations
python manage.py sqlmigrate app_name 0001
python manage.py dbshell
Users
用户管理
python manage.py createsuperuser
python manage.py changepassword username
python manage.py createsuperuser
python manage.py changepassword username
Static files
静态文件
python manage.py collectstatic
python manage.py collectstatic
Testing
测试
python manage.py test
pytest
pytest --cov=app --cov-report=html
python manage.py test
pytest
pytest --cov=app --cov-report=html
Shell
交互式Shell
python manage.py shell
python manage.py shell_plus # django-extensions
python manage.py shell
python manage.py shell_plus # 需要安装django-extensions
Production
生产环境
python manage.py check --deploy
gunicorn myproject.wsgi:application
undefinedpython manage.py check --deploy
gunicorn myproject.wsgi:application
undefinedUseful Packages
实用扩展包
bash
undefinedbash
undefinedDevelopment
开发工具
pip install django-debug-toolbar
pip install django-extensions
pip install django-debug-toolbar
pip install django-extensions
REST API
REST API扩展
pip install djangorestframework
pip install djangorestframework-simplejwt
pip install djangorestframework
pip install djangorestframework-simplejwt
Testing
测试工具
pip install pytest-django
pip install factory-boy
pip install pytest-django
pip install factory-boy
Deployment
部署工具
pip install gunicorn
pip install whitenoise # Static file serving
pip install gunicorn
pip install whitenoise # 静态文件服务
Utilities
实用工具
pip install python-decouple
pip install django-environ
pip install celery # Task queue
---
**Next Steps**: Explore Django documentation at https://docs.djangoproject.com/ and Django REST Framework at https://www.django-rest-framework.org/pip install python-decouple
pip install django-environ
pip install celery # 任务队列
---
**下一步**:访问Django官方文档 https://docs.djangoproject.com/ 和Django REST Framework文档 https://www.django-rest-framework.org/Related Skills
相关技能
When using Django, these skills enhance your workflow:
- sqlalchemy: Alternative ORM for SQLAlchemy-first projects with advanced query capabilities
- test-driven-development: Complete TDD workflow for Django apps (models, views, forms)
- fastapi-local-dev: FastAPI development patterns for building Django + FastAPI hybrid systems
- celery: Asynchronous task processing for Django background jobs and scheduled tasks
[Full documentation available in these skills if deployed in your bundle]
使用Django时,以下技能可提升你的开发效率:
- sqlalchemy:适用于优先使用SQLAlchemy的项目,提供高级查询能力
- test-driven-development:适用于Django应用的完整TDD工作流(模型、视图、表单)
- fastapi-local-dev:适用于构建Django + FastAPI混合系统的FastAPI开发模式
- celery:适用于Django后台任务与定时任务的异步任务处理
[若已部署在你的技能包中,可查看这些技能的完整文档]