You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 00:48:19