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

Django ORM多对多关系中无法保存多个对象副本的问题

解决Django多对多关系无法添加同一对象多个副本的问题

你现在的代码里,每次循环调用Tickets.objects.get(id=int(ticket_id))拿到的都是数据库里同一个Ticket实例——哪怕你修改了它的count并保存,它的主键ID还是原来的那个。Django的多对多关系本身是用来关联不同对象实例的,重复添加同一个实例只会保留一条关联记录,这就是最终只存了一个的核心原因。

下面分两种业务场景给出解决方案:

方案一:用中间表记录票的数量(推荐,适合「票种」场景)

如果Tickets模型代表的是票的种类(比如「成人票」「儿童票」),不需要为每张票创建独立实例,只需要记录每种票的购买数量,那应该给多对多关系添加中间表来存储数量字段:

  1. 修改模型结构:
from django.db import models

class Basket(models.Model):
    id = models.TextField(verbose_name='ID Корзины', primary_key=True)
    tickets = models.ManyToManyField(
        'Tickets', 
        verbose_name='Билеты',
        through='BasketTicket'  # 指定关联的中间表
    )
    date = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')

    def to_json(self):
        return {
            'id': self.id,
            'tickets': [
                {
                    'ticket': ticket.to_json(),
                    'quantity': bt.quantity
                } for ticket, bt in self.tickets.through.objects.filter(basket=self).values_list('ticket', 'quantity')
            ],
            'date': self.date
        }

    class Meta:
        verbose_name = 'Корзина'
        verbose_name_plural = 'Корзины'

# 多对多关系的中间表
class BasketTicket(models.Model):
    basket = models.ForeignKey(Basket, on_delete=models.CASCADE)
    ticket = models.ForeignKey('Tickets', on_delete=models.CASCADE)
    quantity = models.IntegerField(default=1, verbose_name='数量')

    class Meta:
        unique_together = ('basket', 'ticket')  # 确保同一购物车同一种票只存一条记录
  1. 更新视图逻辑:
def add_to_basket(request):
    if request.method == 'POST':
        basket_id = request.session.get('BID')
        if not basket_id:
            basket_id = str(uuid.uuid4())
            request.session['BID'] = basket_id
        basket, created = Basket.objects.get_or_create(id=basket_id)
        
        ticket_id = request.POST.get('ticket_id')
        count = int(request.POST.get('count', 1))
        ticket = Tickets.objects.get(id=int(ticket_id))
        
        if ticket.count >= count:
            # 更新原票的库存
            ticket.count -= count
            ticket.save()
            
            # 更新购物车:已有该票则增加数量,没有则创建新记录
            basket_ticket, bt_created = BasketTicket.objects.get_or_create(
                basket=basket,
                ticket=ticket,
                defaults={'quantity': count}
            )
            if not bt_created:
                basket_ticket.quantity += count
                basket_ticket.save()
            
            return JsonResponse({'status': 'ok'})
        else:
            return JsonResponse({'status': 'not enough tickets'})
    else:
        return JsonResponse({'status': 'not get method'})

方案二:创建独立的Ticket实例(适合「单张票」场景)

如果Tickets模型代表的是单张实体票(每张票有唯一编号、座位号等独立属性),那需要为每个要添加的票创建新实例:

修改视图中的循环逻辑:

if ticket.count >= int(count):
    for i in range(int(count)):
        # 复制原票的属性,创建新的Ticket实例(主键自动生成)
        new_ticket = Tickets.objects.create(
            # 这里列出Tickets模型的所有字段,根据你的实际结构调整
            name=ticket.name,
            price=ticket.price,
            seat_number=f"{ticket.seat_prefix}-{i+1}",  # 示例:生成唯一座位号
            count=1  # 单张票的库存设为1
            # 其他字段...
        )
        basket.tickets.add(new_ticket)
    
    # 减少原票种的库存(如果原ticket是库存记录的话)
    ticket.count -= int(count)
    ticket.save()
    
    basket.save()
    return JsonResponse({'status': 'ok'})

这种方式会在数据库中创建多个独立的Ticket记录,适合每张票有专属属性的场景。

内容的提问来源于stack exchange,提问作者fdf228

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 11:33:11