如何编写支持10万+行数据的Django Admin中间页Action?
处理10万+行数据的Django Admin中间页Action方案
默认的Django Admin中间页Action会把选中对象的ID通过URL参数传递,当数据量达到10万级时,必然会触发浏览器/服务器的URL长度限制,导致功能失效。解决这个问题的核心思路是把大量ID从URL转移到服务器端存储,下面是两种可靠的实现方案:
方案一:利用Session存储选中ID(轻量首选)
Session是服务器端存储,不会受URL长度限制,适合大多数场景(前提是你的Session后端不是基于Cookie的,推荐用Redis或数据库存储Session)。
1. 编写自定义Admin Action
在你的ModelAdmin类中定义Action,将选中ID存入Session后重定向到中间页:
import uuid from django.shortcuts import HttpResponseRedirect, reverse from django.contrib import messages class MyModelAdmin(admin.ModelAdmin): # ... 其他配置 def batch_process_action(self, request, queryset): # 生成唯一标识,避免多标签页操作冲突 action_uuid = str(uuid.uuid4()) # 将选中ID列表存入Session request.session[f"batch_selected_{action_uuid}"] = list(queryset.values_list('pk', flat=True)) # 重定向到中间页,仅传递唯一标识 return HttpResponseRedirect( reverse('admin:myapp_mymodel_batch_process', args=[action_uuid]) ) batch_process_action.short_description = "批量处理选中数据"
2. 编写中间页视图
创建一个视图函数,从Session中读取ID并渲染确认页面:
from django.contrib.admin.views.decorators import staff_member_required from django.shortcuts import render, HttpResponseRedirect, reverse from django.contrib import messages from .models import MyModel @staff_member_required def batch_process_intermediate(request, action_uuid): # 从Session获取选中ID selected_pks = request.session.get(f"batch_selected_{action_uuid}", []) if not selected_pks: messages.error(request, "选中的数据已过期或不存在") return HttpResponseRedirect(reverse('admin:myapp_mymodel_changelist')) # 获取对应的QuerySet(仅统计数量,避免渲染10万条数据) total_count = MyModel.objects.filter(pk__in=selected_pks).count() if request.method == 'POST': # 执行实际的批量操作 MyModel.objects.filter(pk__in=selected_pks).update(status="processed") # 清理Session中的临时数据 del request.session[f"batch_selected_{action_uuid}"] messages.success(request, f"成功处理了{total_count}条数据") return HttpResponseRedirect(reverse('admin:myapp_mymodel_changelist')) # GET请求渲染确认页面 return render(request, 'admin/myapp/mymodel/batch_process_intermediate.html', { 'total_count': total_count, 'opts': MyModel._meta, 'action_uuid': action_uuid })
3. 注册中间页URL
在项目的urls.py中添加中间页的路由:
from django.urls import path from .views import batch_process_intermediate urlpatterns = [ # ... 其他Admin路由 path('admin/myapp/mymodel/batch-process/<str:action_uuid>/', batch_process_intermediate, name='admin:myapp_mymodel_batch_process'), ]
4. 编写中间页模板
创建templates/admin/myapp/mymodel/batch_process_intermediate.html,继承Admin基础模板:
{% extends "admin/base_site.html" %} {% load i18n %} {% block content %} <div class="module"> <h1>{% trans "确认批量处理" %}</h1> <p>{% blocktrans count counter=total_count %}你确定要处理这1条数据吗?{% plural %}你确定要处理这{{ counter }}条数据吗?{% endblocktrans %}</p> <form method="post"> {% csrf_token %} <div class="submit-row"> <input type="submit" value="{% trans '确认执行' %}" class="default"> <a href="{% url 'admin:myapp_mymodel_changelist' %}" class="button cancel-link">{% trans '取消' %}</a> </div> </form> </div> {% endblock %}
方案二:使用临时数据库表存储选中ID(超大数据量场景)
如果Session存储有大小限制,或者需要长期保留选中数据,可以用临时表存储ID:
1. 创建临时存储模型
from django.db import models from django.contrib.auth.models import User class ActionSelection(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) action_name = models.CharField(max_length=100) selected_pks = models.TextField() # 存储逗号分隔的ID字符串 created_at = models.DateTimeField(auto_now_add=True) class Meta: indexes = [ models.Index(fields=['user', 'created_at']), ]
2. 修改Admin Action
def batch_process_action(self, request, queryset): # 将ID拼接为字符串存入临时表 pks_str = ','.join(str(pk) for pk in queryset.values_list('pk', flat=True)) selection = ActionSelection.objects.create( user=request.user, action_name="batch_process", selected_pks=pks_str ) return HttpResponseRedirect( reverse('admin:myapp_mymodel_batch_process', args=[selection.pk]) )
3. 修改中间页视图
@staff_member_required def batch_process_intermediate(request, selection_id): try: selection = ActionSelection.objects.get(pk=selection_id, user=request.user) except ActionSelection.DoesNotExist: messages.error(request, "选中的数据已过期或不存在") return HttpResponseRedirect(reverse('admin:myapp_mymodel_changelist')) selected_pks = list(map(int, selection.selected_pks.split(','))) total_count = MyModel.objects.filter(pk__in=selected_pks).count() if request.method == 'POST': MyModel.objects.filter(pk__in=selected_pks).update(status="processed") selection.delete() # 清理临时数据 messages.success(request, f"成功处理了{total_count}条数据") return HttpResponseRedirect(reverse('admin:myapp_mymodel_changelist')) return render(request, 'admin/myapp/mymodel/batch_process_intermediate.html', { 'total_count': total_count, 'opts': MyModel._meta, })
关键优化点
- 避免渲染大量数据:中间页只显示统计数量,不渲染10万条数据的表格,大幅提升页面加载速度。
- 处理并发冲突:用UUID或临时表主键区分不同操作,避免多标签页操作互相干扰。
- 清理临时数据:操作完成后及时删除Session或临时表中的数据,避免占用资源。
- 过期处理:可以给临时表添加定时清理任务,或者在Session中设置过期时间。
内容的提问来源于stack exchange,提问作者guettli
相关产品推荐
相关产品推荐

