如何在DRF中通过Postman传入商品名称而非PK创建订单?
解决DRF电商API中通过商品名称+数量创建订单的问题
问题分析
当前订单模型使用ManyToManyField关联商品,但无法存储购买数量,且默认只能通过商品PK创建订单。要实现通过商品名称+数量创建订单,需要调整模型结构并自定义序列化逻辑。
解决方案
1. 调整模型结构
添加中间模型OrderItem来记录订单中每个商品的购买数量,修改PlacedOrder的items字段关联到该中间模型:
from django.db import models from django.contrib.auth.models import User # 保持原Product模型不变 class Product(models.Model): product_tag = models.CharField(max_length=10) name = models.CharField(max_length=100) description = models.CharField(max_length=500, null=True, blank=True) category = models.ForeignKey('Category', on_delete=models.CASCADE) # 假设Category已定义 price = models.IntegerField() stock = models.IntegerField() image = models.ImageField(default="default.png") in_stock = models.BooleanField(default=True) date_created = models.DateField(auto_now_add=True) class Meta: ordering = ["-date_created"] def __str__(self): return self.name # 新增订单条目模型,记录商品和数量 class OrderItem(models.Model): order = models.ForeignKey('PlacedOrder', on_delete=models.CASCADE, related_name='order_items') product = models.ForeignKey(Product, on_delete=models.CASCADE) quantity = models.IntegerField(default=1) class Meta: unique_together = ('order', 'product') # 避免同一订单重复添加同一商品 # 修改PlacedOrder模型,使用through关联OrderItem class PlacedOrder(models.Model): ordered_by = models.ForeignKey(User, on_delete=models.CASCADE) first_name = models.CharField(max_length=100) last_name = models.CharField(max_length=100) phone = models.CharField(max_length=15) address = models.CharField(max_length=100) zipcode = models.CharField(max_length=100) items = models.ManyToManyField(Product, through=OrderItem) total_price = models.IntegerField() created_at = models.DateTimeField(auto_now_add=True) class Meta: ordering = ["-created_at"] def __str__(self): return f"{self.ordered_by}"
2. 自定义序列化器
重写序列化器,实现商品名称+数量的输入验证、库存检查及订单创建逻辑:
from rest_framework import serializers from .models import PlacedOrder, Product, OrderItem class PlacedOrderSerializer(serializers.ModelSerializer): ordered_by = serializers.ReadOnlyField(source="ordered_by.email") # 自定义items字段,接收[{name: 商品名, quantity: 数量}]格式的数据 items = serializers.ListField( child=serializers.DictField( fields={'name': serializers.CharField(), 'quantity': serializers.CharField()} ) ) class Meta: model = PlacedOrder fields = ( "id", "created_at", "ordered_by", "first_name", "last_name", "phone", "address", "zipcode", "items", "total_price", ) # 可选:若要自动计算总价,添加该行并移除输入中的total_price # read_only_fields = ['total_price'] def validate_items(self, value): """验证商品名称、数量及库存""" for item in value: # 检查必填字段 if not item.get('name') or not item.get('quantity'): raise serializers.ValidationError("每个商品必须包含name和quantity字段") # 验证数量为正整数 try: quantity = int(item['quantity']) if quantity <= 0: raise serializers.ValidationError("商品数量必须大于0") except ValueError: raise serializers.ValidationError("quantity必须是有效整数") # 检查商品是否存在 product = Product.objects.filter(name=item['name']).first() if not product: raise serializers.ValidationError(f"商品「{item['name']}」不存在") # 检查库存是否充足 if product.stock < quantity: raise serializers.ValidationError(f"商品「{item['name']}」库存不足,当前库存:{product.stock}") return value def create(self, validated_data): """创建订单及关联的订单条目""" # 取出商品数据,单独处理 items_data = validated_data.pop('items') # 创建订单基本信息 order = PlacedOrder.objects.create(**validated_data) total_price = 0 # 处理每个商品条目 for item_data in items_data: product = Product.objects.get(name=item_data['name']) quantity = int(item_data['quantity']) # 创建订单条目 OrderItem.objects.create(order=order, product=product, quantity=quantity) # 计算总价(若开启自动计算,取消下面注释并设置total_price为只读) # total_price += product.price * quantity # 自动设置总价(可选) # order.total_price = total_price # order.save() return order
3. 视图保持原有逻辑
原视图无需修改,perform_create已处理订单所属用户:
from rest_framework import generics from rest_framework.permissions import IsAuthenticated from .models import PlacedOrder from .serializers import PlacedOrderSerializer class CreateOrder(generics.ListCreateAPIView): permission_classes = [IsAuthenticated] queryset = PlacedOrder.objects.all() serializer_class = PlacedOrderSerializer def perform_create(self, serializer): serializer.save(ordered_by=self.request.user)
4. 测试请求示例
Postman中使用以下格式请求:
{ "first_name": "yes", "last_name": "no", "phone": "0100000000", "address": "whatever address", "zipcode": "254", "items": [ {"name": "商品A", "quantity": 2}, {"name": "商品B", "quantity": 1} ], "total_price": 69 }
若开启自动计算总价,请求中可移除total_price字段,序列化器会自动计算并填充。
内容的提问来源于stack exchange,提问作者Atef Arman Shishir
相关产品推荐
相关产品推荐

