如何按规则自定义校验并实现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
相关产品推荐
相关产品推荐

