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

如何按规则自定义校验并实现Django订单商品接收转移功能?

订单收货功能实现方案

1. 完善Orders模型

首先补充received_products字段,和products一样采用多对多关联Products,并允许默认空值:

from django.db import models
from .models import Customers, Products

class Orders(models.Model):
    delivery_model_choices = (("Pickup", "Pickup"),
                              ("Delivery", "Delivery"))
    order_status_choices = (("Draft", "Draft"),
                            ("Open", "Open"),
                            ("Partially Received", "Partially Received"),
                            ("Received", "Received"),
                            ("Cancelled", "Cancelled"))
    customer = models.ManyToManyField(Customers)
    products = models.ManyToManyField(Products, related_name='orders_pending')
    # 补充收货商品字段,关联Products,默认空
    received_products = models.ManyToManyField(Products, related_name='orders_received', blank=True)
    date_create = models.DateTimeField(auto_now_add=True)
    delivery = models.CharField(max_length=40, choices=delivery_model_choices)
    delivery_date = models.DateField()
    order_status = models.CharField(max_length=40, choices=order_status_choices)
    total_price = models.CharField(max_length=10)

注:设置related_name是为了避免两个多对多字段反向关联时的命名冲突。


2. 方案一:使用PATCH请求处理收货逻辑

2.1 自定义序列化器

通过序列化器验证收货商品的合法性,并实现转移逻辑:

from rest_framework import serializers
from .models import Orders
from rest_framework.exceptions import ValidationError

class OrderReceiveSerializer(serializers.ModelSerializer):
    # 接收前端传入的待收货商品ID列表
    receive_product_ids = serializers.ListField(child=serializers.IntegerField())

    class Meta:
        model = Orders
        fields = ['receive_product_ids']

    def update(self, instance, validated_data):
        receive_ids = validated_data.get('receive_product_ids')
        # 验证所有待收货商品都属于当前订单的待收货列表
        valid_products = instance.products.filter(id__in=receive_ids)
        if valid_products.count() != len(receive_ids):
            raise ValidationError("部分商品不属于当前订单待收货范围")
        
        # 执行商品转移:从products移除,加入received_products
        instance.received_products.add(*valid_products)
        instance.products.remove(*valid_products)

        # 自动更新订单状态
        if instance.products.count() == 0:
            instance.order_status = "Received"
        else:
            instance.order_status = "Partially Received"
        instance.save()

        return instance

2.2 视图层实现

在ViewSet中添加自定义PATCH动作:

from rest_framework import viewsets, permissions, status
from rest_framework.response import Response
from .models import Orders
from .serializers import OrderReceiveSerializer

class OrderViewSet(viewsets.ModelViewSet):
    queryset = Orders.objects.all()
    permission_classes = [permissions.IsAuthenticated]

    # 自定义收货动作,仅允许PATCH请求
    @action(detail=True, methods=['patch'], serializer_class=OrderReceiveSerializer)
    def receive_products(self, request, pk=None):
        order = self.get_object()
        # 验证订单状态是否允许收货
        if order.order_status not in ["Open", "Partially Received"]:
            return Response({"error": "当前订单状态无法执行收货操作"}, status=status.HTTP_400_BAD_REQUEST)
        
        serializer = self.get_serializer(order, data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response({
            "message": "收货成功",
            "remaining_products": instance.products.count(),
            "order_status": instance.order_status
        }, status=status.HTTP_200_OK)

前端调用示例:发送PATCH请求到/api/orders/{订单ID}/receive_products/,请求体为{"receive_product_ids": [1, 3, 5]}。


3. 方案二:封装独立模型方法

将收货逻辑封装在模型层,提升代码复用性,视图层仅做参数校验和调用:

3.1 模型新增收货方法

from django.db import models, transaction
from .models import Customers, Products

class Orders(models.Model):
    # ... 原有字段 ...

    @transaction.atomic  # 事务装饰器,保证操作原子性
    def receive_products(self, product_ids):
        # 验证订单状态合法性
        if self.order_status not in ["Open", "Partially Received"]:
            raise ValueError("当前订单状态无法执行收货")
        
        # 验证商品归属
        valid_products = self.products.filter(id__in=product_ids)
        if valid_products.count() != len(product_ids):
            raise ValueError("部分商品不属于当前订单待收货范围")
        
        # 执行商品转移
        self.received_products.add(*valid_products)
        self.products.remove(*valid_products)

        # 更新订单状态
        if self.products.count() == 0:
            self.order_status = "Received"
        else:
            self.order_status = "Partially Received"
        self.save()

3.2 视图层调用方法

from rest_framework import viewsets, permissions, status
from rest_framework.response import Response
from .models import Orders

class OrderViewSet(viewsets.ModelViewSet):
    queryset = Orders.objects.all()
    permission_classes = [permissions.IsAuthenticated]

    @action(detail=True, methods=['post'])
    def receive_products(self, request, pk=None):
        order = self.get_object()
        product_ids = request.data.get('product_ids', [])
        
        # 参数格式校验
        if not isinstance(product_ids, list) or not all(isinstance(pid, int) for pid in product_ids):
            return Response({"error": "product_ids必须为整数列表"}, status=status.HTTP_400_BAD_REQUEST)
        
        try:
            order.receive_products(product_ids)
            return Response({
                "message": "收货成功",
                "order_status": order.order_status
            }, status=status.HTTP_200_OK)
        except ValueError as e:
            return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)

4. 关键注意事项

  • 事务原子性:使用@transaction.atomic确保商品转移和状态更新要么全部成功,要么全部回滚,避免数据不一致。
  • 权限控制:根据业务需求调整permission_classes,比如限制仅订单创建者或管理员操作。
  • 状态校验:必须拦截Draft、Cancelled等不允许收货的订单状态,避免非法操作。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 17:15:17