Python
CRUD、分页、搜索与消息框架
发布于 2026年7月22日
CRUD、分页、搜索与消息框架
CRUD 指创建(Create)、读取(Read)、更新(Update)和删除(Delete)。本篇把前面分散的模型、表单、视图和模板组合成一个完整业务模块,并加入分页、搜索和操作反馈。
一、路由设计
notes/urls.py:
from django.urls import path
from . import views
app_name = "notes"
urlpatterns = [
path("", views.message_list, name="list"),
path("create/", views.message_create, name="create"),
path("<int:pk>/", views.message_detail, name="detail"),
path("<int:pk>/edit/", views.message_update, name="update"),
path("<int:pk>/delete/", views.message_delete, name="delete"),
]
把 create/ 放在 <int:pk>/ 前面可读性更好。虽然 int 不会匹配 create,但明确的静态路由优先是一个好习惯。
二、列表与搜索
from django.core.paginator import Paginator
from django.db.models import Q
from django.shortcuts import render
from .forms import MessageSearchForm
from .models import Message
def message_list(request):
queryset = Message.objects.select_related("owner").prefetch_related("tags")
form = MessageSearchForm(request.GET)
if form.is_valid():
keyword = form.cleaned_data["q"]
status = form.cleaned_data["status"]
if keyword:
queryset = queryset.filter(
Q(name__icontains=keyword) | Q(content__icontains=keyword)
)
if status:
queryset = queryset.filter(status=status)
paginator = Paginator(queryset, 10)
page_obj = paginator.get_page(request.GET.get("page"))
return render(request, "notes/message_list.html", {
"form": form,
"page_obj": page_obj,
})
get_page() 会容错非法页码;若你希望非法页码直接报错,可以使用 page() 并自行处理异常。
三、详情页
from django.shortcuts import get_object_or_404, render
def message_detail(request, pk):
message = get_object_or_404(
Message.objects.select_related("owner").prefetch_related("tags"),
pk=pk,
)
return render(request, "notes/message_detail.html", {"message": message})
四、创建与更新
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404, redirect, render
from .forms import MessageForm
@login_required
def message_create(request):
form = MessageForm(request.POST or None)
if request.method == "POST" and form.is_valid():
message = form.save(commit=False)
message.owner = request.user
message.save()
form.save_m2m()
messages.success(request, "留言创建成功。")
return redirect("notes:detail", pk=message.pk)
return render(request, "notes/message_form.html", {
"form": form,
"page_title": "新建留言",
})
@login_required
def message_update(request, pk):
message = get_object_or_404(Message, pk=pk)
form = MessageForm(request.POST or None, instance=message)
if request.method == "POST" and form.is_valid():
message = form.save()
messages.success(request, "留言更新成功。")
return redirect("notes:detail", pk=message.pk)
return render(request, "notes/message_form.html", {
"form": form,
"page_title": "编辑留言",
})
这里暂未加入登录和对象归属校验,下一篇会补上。真实项目不能只要知道主键就允许编辑。
五、删除确认
删除通常分两步:GET 展示确认页面,POST 真正删除。
@login_required
def message_delete(request, pk):
message = get_object_or_404(Message, pk=pk)
if request.method == "POST":
message.delete()
messages.success(request, "留言已删除。")
return redirect("notes:list")
return render(request, "notes/message_confirm_delete.html", {
"message": message,
})
确认模板:
{% extends "base.html" %}
{% block content %}
<h1>确认删除</h1>
<p>确定删除“{{ message.name }}”吗?此操作无法撤销。</p>
<form method="post">
{% csrf_token %}
<button type="submit">确认删除</button>
<a href="{% url 'notes:detail' message.pk %}">取消</a>
</form>
{% endblock %}
六、分页模板
<nav aria-label="分页">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}&q={{ request.GET.q|urlencode }}">上一页</a>
{% endif %}
<span>第 {{ page_obj.number }} / {{ page_obj.paginator.num_pages }} 页</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}&q={{ request.GET.q|urlencode }}">下一页</a>
{% endif %}
</nav>
筛选条件较多时,建议编写模板标签或在 View 中构造保留参数,避免分页链接丢失当前筛选。
七、消息框架
base.html:
{% if messages %}
<div aria-live="polite">
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
{% endfor %}
</div>
{% endif %}
常用级别:messages.success、messages.info、messages.warning、messages.error。
八、业务规则放在哪里
建议按职责划分:
- 输入格式校验:Form。
- 单个模型自身规则:Model 的验证或方法。
- 跨模型业务流程:Service 函数或领域服务模块。
- 请求和响应编排:View。
- 展示:Template。
不要让 View 同时承担复杂计算、多个模型写入、第三方接口调用和页面渲染,否则很难测试与复用。
九、常见问题
- 搜索后翻页条件丢失:分页链接没有保留查询字符串。
- 更新变成新增:ModelForm 缺少
instance=message。 - 删除可被链接触发:真正删除逻辑没有限制为 POST。
- 成功消息不显示:模板未遍历
messages,或相关中间件/上下文处理器被移除。 - 列表查询过多:模板访问关联对象,View 没有使用
select_related或prefetch_related。
十、本篇检查清单
- 能完成模型的增删改查页面。
- 能实现搜索、状态筛选和分页。
- 删除只在 POST 请求中执行。
- 成功写操作采用 PRG 模式并显示反馈。
- 能说清 Form、View、Model 和 Service 的职责边界。