DRF中无法关联多个订单项至订单,无法获取嵌套数据
问题
开发餐厅API时,用户购物车下单需将商品复制至OrderItem模型,同时创建Order模型存储总价、订单状态等信息。目前添加商品到购物车功能正常,但调用订单接口时返回结果缺失items字段,无法获取嵌套的订单项数据。
相关代码
models.py
class Category(models.Model): slug = models.SlugField() title = models.CharField(max_length=255,db_index=True) def __str__(self) -> str: return self.title class MenuItem(models.Model): title = models.CharField(max_length=255,db_index=True) price = models.DecimalField(max_digits=4,decimal_places=2) featured = models.BooleanField(db_index=True,null=True) category = models.ForeignKey(Category,on_delete=models.PROTECT) def __str__(self) -> str: return self.title class Cart(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) menuitem = models.ForeignKey(MenuItem, on_delete=models.CASCADE) quantity = models.IntegerField() unit_price = models.DecimalField(max_digits=8,decimal_places=1) price = models.DecimalField(max_digits=6,decimal_places=1) class Meta: unique_together = ['menuitem','user'] class OrderList(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) menuitem = models.ForeignKey(MenuItem, on_delete=models.CASCADE) quantity = models.SmallIntegerField() unit_price = models.DecimalField(max_digits=6,decimal_places=1) price = models.DecimalField(max_digits=6,decimal_places=1) class Test(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) class Order(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) delivery_crew = models.ForeignKey(User, related_name='delivery_crew', on_delete=models.CASCADE,null=True) total = models.DecimalField(max_digits=6,decimal_places=1) status = models.BooleanField(default=0) date = models.DateField(db_index=True,auto_now_add=True) class OrderItem(models.Model): order = models.ForeignKey(Order, on_delete=models.CASCADE) menuitem = models.ForeignKey(MenuItem, on_delete=models.CASCADE) quantity = models.SmallIntegerField() unit_price = models.DecimalField(max_digits=6,decimal_places=1) price = models.DecimalField(max_digits=6,decimal_places=1) class Meta: unique_together = ['menuitem','order']
views.py
class OrderView(views.APIView): def post(self,request): endpoint = request.POST.get('endpoint', False) if endpoint == 'http://127.0.0.1:8000/api/cart/menu-items': total = 0 user = self.request.user cart_items = Cart.objects.filter(user=user) for item in cart_items: total += item.price if Order.objects.filter(user=user): o = Order.objects.get(user=user) o.total = total o.save() else: Order.objects.create(user=user,total=total) order = Order.objects.filter(user=user).first() for item in cart_items: menuitem = item.menuitem price_per_unit = float(item.unit_price) price = float(item.price) quantity = item.quantity OrderItem.objects.create(order=order,menuitem=menuitem,unit_price=price_per_unit,price=price,quantity=quantity) cart_items.delete() return Response({'message':'order placed successfully'}) return Response({'message':'invalid endpoint'}) def get(self, request): user = self.request.user queryset = Order.objects.filter(user=user) serialized_item = OrderSerializer(queryset,many=True) return Response(serialized_item.data)
serializer.py
class OrderItemSerializer(serializers.ModelSerializer): class Meta: model = OrderItem fields = '__all__' class OrderSerializer(serializers.ModelSerializer): items = OrderItemSerializer(many=True,read_only=True) class Meta: model = Order fields = '__all__'
urls.py
urlpatterns = [ path('cart', views.CartView.as_view()), path('orders',views.OrderView.as_view()), path('orders/<int:id>',views.single_order_item), ]
当前返回结果
[ { "delivery_crew": null, "total": "16500.0", "status": false, "date": "2023-07-19" } ]
期望返回格式
{ "user": "A", "delivery_crew": "B", "items": [ { "id": 5, "name": "Item 5" }, { "id": 6, "name": "Item 6" } ], "total" : 123.00 }
解决方案
1. 修复Order与OrderItem的反向关联映射
Django中外键的反向关联默认使用模型名_set格式(如orderitem_set),序列化器中items字段未指定来源导致无法识别,可通过两种方式修复:
方式一:修改OrderItem模型的外键
给OrderItem的order外键添加related_name='items',明确反向关联名称:
class OrderItem(models.Model): order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='items') # 其他字段保持不变
方式二:在序列化器中指定source
若不想修改模型,在OrderSerializer的items字段指定source为默认反向关联名:
class OrderSerializer(serializers.ModelSerializer): items = OrderItemSerializer(many=True, read_only=True, source='orderitem_set') # 其他代码不变
2. 优化查询,避免N+1数据库问题
获取Order查询集时,使用prefetch_related预加载关联的OrderItem数据,提升接口性能:
def get(self, request): user = self.request.user # 预加载关联的订单项 queryset = Order.objects.filter(user=user).prefetch_related('items') serialized_item = OrderSerializer(queryset, many=True) return Response(serialized_item.data)
3. 调整序列化器字段,匹配期望格式
- 让
OrderSerializer显示user字段,可选择显示用户名或ID - 修改
OrderItemSerializer,将menuitem的title映射为name字段,精简返回内容:
class OrderItemSerializer(serializers.ModelSerializer): name = serializers.CharField(source='menuitem.title') class Meta: model = OrderItem fields = ['id', 'name', 'quantity', 'unit_price', 'price'] class OrderSerializer(serializers.ModelSerializer): items = OrderItemSerializer(many=True, read_only=True) # 显示用户名而非ID,若需ID可移除该字段 user = serializers.CharField(source='user.username') delivery_crew = serializers.CharField(source='delivery_crew.username', allow_null=True) class Meta: model = Order fields = ['user', 'delivery_crew', 'items', 'total', 'status', 'date']
4. 修复下单逻辑:每次下单创建新订单
当前代码会更新用户已有订单,不符合“一个用户可拥有多个订单”的业务逻辑,修改post方法:
def post(self,request): endpoint = request.POST.get('endpoint', False) if endpoint == 'http://127.0.0.1:8000/api/cart/menu-items': user = self.request.user cart_items = Cart.objects.filter(user=user) if not cart_items.exists(): return Response({'message': 'cart is empty'}, status=status.HTTP_400_BAD_REQUEST) total = sum(item.price for item in cart_items) # 每次下单创建新订单 order = Order.objects.create(user=user, total=total) for item in cart_items: # 保留Decimal类型,避免精度丢失 OrderItem.objects.create( order=order, menuitem=item.menuitem, unit_price=item.unit_price, price=item.price, quantity=item.quantity ) cart_items.delete() return Response({'message':'order placed successfully'}) return Response({'message':'invalid endpoint'}, status=status.HTTP_400_BAD_REQUEST)
最终效果
调整后调用订单接口,会返回包含嵌套订单项的完整数据,格式如下:
[ { "user": "user1", "delivery_crew": null, "items": [ { "id": 1, "name": "Burger", "quantity": 2, "unit_price": "10.0", "price": "20.0" } ], "total": "20.0", "status": false, "date": "2023-07-19" } ]
内容的提问来源于stack exchange,提问作者user693150
相关产品推荐
相关产品推荐

