Django中如何批量生成并打印多个唯一票务PIN码
实现方案
1. 先补全模型层核心约束
你现有模型的pin字段没有加唯一索引,高并发或批量生成场景下极易出现重复PIN,必须先从数据库层面做唯一性兜底,同时补全核销必要的状态字段,修改模型如下:
class Ticket(models.Model): name = models.CharField(max_length=50, verbose_name="票种名称") price = models.PositiveIntegerField(verbose_name="票种价格") pin = models.CharField(max_length=6, unique=True, verbose_name="核销PIN") is_used = models.BooleanField(default=False, verbose_name="是否已核销") create_time = models.DateTimeField(auto_now_add=True, verbose_name="生成时间") def __str__(self): return self.name
如果后续票种规格固定,建议拆分TicketType(存储票种名、基础价格)和Ticket(存储关联票种、PIN、核销状态)两个模型,避免同票种信息重复存储,以下方案先基于你现有模型结构实现。
修改完模型后依次执行python manage.py makemigrations、python manage.py migrate同步表结构。
2. 批量生成唯一PIN核心逻辑
不要循环单条查库、单条插入,性能极差。用bulk_create配合内存差集校验+数据库事务保证原子性,兼顾性能和唯一性:
在ticket应用下新建utils.py写入生成逻辑:
import secrets from django.db import transaction from django.core.exceptions import ValidationError from .models import Ticket def batch_generate_tickets(ticket_name: str, ticket_price: int, generate_count: int) -> int: """ 批量生成指定票种的唯一PIN :param ticket_name: 票种名称 :param ticket_price: 票种价格 :param generate_count: 待生成数量 :return: 实际生成成功数量 """ # 6位纯数字PIN总容量为100万,提前校验剩余容量避免无效运算 total_exist = Ticket.objects.count() if total_exist + generate_count > 1000000: raise ValidationError(f"6位PIN剩余可用量不足,最多还能生成{1000000 - total_exist}个") # 一次性取出所有已存在PIN转集合,内存做差集比循环查库快2个数量级以上 exist_pins = set(Ticket.objects.values_list('pin', flat=True)) available_pins = [] # 生成指定数量的不重复PIN while len(available_pins) < generate_count: # 用secrets替代random生成安全随机数,避免被暴力枚举,自动补前导零凑6位 candidate = f"{secrets.randbelow(1000000):06d}" if candidate not in exist_pins and candidate not in available_pins: available_pins.append(candidate) # 批量组装待插入对象 ticket_objs = [ Ticket(name=ticket_name, price=ticket_price, pin=pin) for pin in available_pins ] # 开事务批量插入,ignore_conflicts兜底极端并发下的唯一键冲突 with transaction.atomic(): Ticket.objects.bulk_create(ticket_objs, ignore_conflicts=True, batch_size=5000) return len(ticket_objs)
如果单次生成量级超过10万,不要一次性加载全量已存在PIN到内存,可以改用号段偏移、分批生成的逻辑;普通活动单次生成几千到几万张票的场景,上述逻辑性能足够。
3. 管理员后台一键生成实现
直接在Django Admin中加自定义入口,不需要额外开发独立前端:
修改ticket应用的admin.py:
from django.contrib import admin from django.shortcuts import render, redirect from django.contrib import messages from django.urls import path from .models import Ticket from .utils import batch_generate_tickets class TicketAdmin(admin.ModelAdmin): list_display = ['name', 'price', 'pin', 'is_used', 'create_time'] list_filter = ['name', 'is_used'] # 注册自定义路由 def get_urls(self): urls = super().get_urls() custom_urls = [ path('batch-generate/', self.admin_site.admin_view(self.batch_generate_view), name='ticket-batch-generate') ] return custom_urls + urls # 批量生成表单视图 def batch_generate_view(self, request): if request.method == 'POST': ticket_name = request.POST.get('ticket_name').strip() ticket_price = int(request.POST.get('ticket_price')) generate_count = int(request.POST.get('generate_count')) try: success_count = batch_generate_tickets(ticket_name, ticket_price, generate_count) messages.success(request, f"生成成功,共创建{success_count}张票") return redirect("admin:tickets_ticket_changelist") except Exception as e: messages.error(request, f"生成失败:{str(e)}") return render(request, 'admin/batch_generate_ticket.html', {'title': '批量生成票券'}) # 列表页注入生成按钮入口 def changelist_view(self, request, extra_context=None): extra_context = extra_context or {} extra_context['batch_generate_url'] = 'admin:ticket-batch-generate' return super().changelist_view(request, extra_context=extra_context) admin.site.register(Ticket, TicketAdmin)
新建模板文件templates/admin/batch_generate_ticket.html,继承admin基础模板写个简单表单:
{% extends "admin/base_site.html" %} {% block content %} <h1>批量生成票券</h1> <form method="post" style="margin-top:20px;"> {% csrf_token %} <div style="margin:15px 0;"> <label style="display:inline-block;width:120px;">票种名称:</label> <input type="text" name="ticket_name" required style="width:320px;padding:6px;"> </div> <div style="margin:15px 0;"> <label style="display:inline-block;width:120px;">票种价格(元):</label> <input type="number" name="ticket_price" min="0" required style="width:320px;padding:6px;"> </div> <div style="margin:15px 0;"> <label style="display:inline-block;width:120px;">生成数量:</label> <input type="number" name="generate_count" min="1" required style="width:320px;padding:6px;"> </div> <button type="submit" class="button" style="padding:8px 20px;cursor:pointer;">确认生成</button> </form> {% endblock %}
最后重写admin的changelist模板,在列表顶部加个跳转到批量生成页的按钮即可,管理员登录后台点按钮填3个参数就能一键生成。
4. 批量打印实现
按需选方案即可:
- 快速落地:安装
django-import-export库,在admin中加导出功能,筛选指定票种的未核销PIN导出为Excel/CSV,直接用Excel排版打印。 - 定制化打印:写独立的打印视图,把筛选出的PIN按A4纸尺寸排版,可搭配
qrcode库为每个PIN生成核销二维码,加@media print样式隐藏页面按钮、导航等无关元素,直接调用浏览器打印功能即可。
核销逻辑注意点
用户提交PIN校验时必须加行锁避免重复核销,参考逻辑:
from django.db import transaction with transaction.atomic(): # 加行锁查询,防止并发下同一个PIN被多次核销 ticket = Ticket.objects.select_for_update().filter(pin=input_pin, is_used=False).first() if not ticket: return "PIN无效或已被使用" ticket.is_used = True ticket.save() # 后续走报名成功逻辑
内容的提问来源于stack exchange,提问作者apollos
相关产品推荐
相关产品推荐

