django-framework

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Django 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
undefined

Create 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', ]
undefined
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', # ... 'blog', ]
undefined

Models and ORM

模型与ORM

Model Definition

模型定义

python
undefined
python
undefined

models.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.name
class Post(models.Model): STATUS_CHOICES = [ ('draft', 'Draft'), ('published', 'Published'), ]
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
content = models.TextField()
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.title
undefined
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.name
class Post(models.Model): STATUS_CHOICES = [ ('draft', 'Draft'), ('published', 'Published'), ]
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
content = models.TextField()
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.title
undefined

Common Field Types

常用字段类型

python
undefined
python
undefined

Text 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()
undefined
models.JSONField()
undefined

Migrations

数据库迁移

bash
undefined
bash
undefined

Create 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
undefined
python manage.py migrate blog 0001
undefined

QuerySet API

QuerySet API

python
undefined
python
undefined

Basic 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
undefined
Post.objects.select_related('author', 'category') # SQL JOIN查询 Post.objects.prefetch_related('tags') # 为多对多关系执行单独查询
undefined

Model 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
undefined
python
undefined

views.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)
undefined
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)
undefined

Class-Based Views (CBV)

基于类的视图(CBV)

python
undefined
python
undefined

views.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.author
class 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.author
undefined
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.author
class 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.author
undefined

URLs and Routing

URL与路由

python
undefined
python
undefined

project/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'),
]
undefined
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'),
# 基于函数的视图
path('api/posts/', views.api_posts, name='api_posts'),
]
undefined

Templates

模板

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
undefined
python
undefined

forms.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 slug
class 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
    pass
undefined
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': '未提供则自动生成'}), }
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 slug
class 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):
    # 发送邮件逻辑
    pass
undefined

Form 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
undefined
python
undefined

admin.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')
undefined
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 = (
    ('基础信息', {
        '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')
undefined

Advanced 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
undefined
python
undefined

views.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
undefined
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): # 仅拥有'add_post'权限的用户可访问 pass
undefined

Custom User Model

自定义用户模型

python
undefined
python
undefined

models.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'
undefined
AUTH_USER_MODEL = 'accounts.CustomUser'
undefined

Permissions

权限管理

python
undefined
python
undefined

Check 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'), ]
undefined
class Post(models.Model): class Meta: permissions = [ ('can_publish', '可发布文章'), ]
undefined

Django REST Framework

Django REST Framework

Installation and Setup

安装与配置

bash
pip install djangorestframework
python
undefined
bash
pip install djangorestframework
python
undefined

settings.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', ], }
undefined
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', ], }
undefined

Serializers

序列化器

python
undefined
python
undefined

serializers.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 value
undefined
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("标题长度至少为5个字符。")
    return value
undefined

API Views

API视图

python
undefined
python
undefined

views.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'})
undefined
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': '文章已发布'})
undefined

API URLs

API路由

python
undefined
python
undefined

api/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
undefined
from rest_framework.routers import DefaultRouter from blog.views import PostViewSet
router = DefaultRouter() router.register(r'posts', PostViewSet)
urlpatterns = router.urls
undefined

Testing

测试

Unit Tests with Django TestCase

使用Django TestCase进行单元测试

python
undefined
python
undefined

tests.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)
undefined
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='测试文章',
        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)
undefined

Testing with pytest-django

使用pytest-django进行测试

bash
pip install pytest-django pytest-cov
python
undefined
bash
pip install pytest-django pytest-cov
python
undefined

pytest.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

undefined
undefined

Database Optimization

数据库优化

Select Related and Prefetch Related

Select Related与Prefetch Related

python
undefined
python
undefined

N+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)) )
undefined
from django.db.models import Prefetch posts = Post.objects.prefetch_related( Prefetch('comments', queryset=Comment.objects.filter(approved=True)) )
undefined

Database 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
undefined
python
undefined

Bulk 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()
undefined
Post.objects.filter(created_at__lt=old_date).delete()
undefined

Middleware and Signals

中间件与信号

Custom Middleware

自定义中间件

python
undefined
python
undefined

middleware.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 response
class 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 response

settings.py

settings.py

MIDDLEWARE = [ # ... 'myapp.middleware.RequestLoggingMiddleware', ]
undefined
MIDDLEWARE = [ # ... 'myapp.middleware.RequestLoggingMiddleware', ]
undefined

Signals

信号

python
undefined
python
undefined

signals.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.signals
undefined
class BlogConfig(AppConfig): name = 'blog'
def ready(self):
    import blog.signals
undefined

Settings and Configuration

设置与配置

Settings Best Practices

配置最佳实践

python
undefined
python
undefined

settings/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
undefined
from .base import *
DEBUG = False ALLOWED_HOSTS = [os.environ.get('ALLOWED_HOST')] SECURE_SSL_REDIRECT = True SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True
undefined

Deployment

部署

Production Checklist

生产环境检查清单

bash
undefined
bash
undefined

Check deployment readiness

检查部署就绪状态

python manage.py check --deploy
undefined
python manage.py check --deploy
undefined

Docker Deployment

Docker部署

dockerfile
undefined
dockerfile
undefined

Dockerfile

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"]

```yaml
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"]

```yaml

docker-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:
undefined
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:
undefined

Gunicorn Configuration

Gunicorn配置

python
undefined
python
undefined

gunicorn.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"
undefined
bind = "0.0.0.0:8000" workers = 4 worker_class = "sync" worker_connections = 1000 timeout = 30 keepalive = 2 accesslog = "-" errorlog = "-" loglevel = "info"
undefined

Security Best Practices

安全最佳实践

python
undefined
python
undefined

settings.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 %}

undefined
undefined

Common Patterns and Best Practices

常用模式与最佳实践

Environment Variables

环境变量

python
undefined
python
undefined

Use 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')
undefined
from decouple import config
SECRET_KEY = config('SECRET_KEY') DEBUG = config('DEBUG', default=False, cast=bool) DATABASE_URL = config('DATABASE_URL')
undefined

Custom Management Commands

自定义管理命令

python
undefined
python
undefined

blog/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

undefined
undefined

Caching

缓存

python
undefined
python
undefined

settings.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
undefined
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) # 缓存1小时 return count
undefined

Quick Reference

快速参考

Common Commands

常用命令

bash
undefined
bash
undefined

Project 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
undefined
python manage.py check --deploy gunicorn myproject.wsgi:application
undefined

Useful Packages

实用扩展包

bash
undefined
bash
undefined

Development

开发工具

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后台任务与定时任务的异步任务处理
[若已部署在你的技能包中,可查看这些技能的完整文档]