DRF中多对多ManytoMany字段的更新和添加

2023-09-07 01:17:42 浏览数 (1)

背景:

drf的序列化器给模型输出带来了便利但是对于多对多字段网上查询的内容却是很少(也有可能是本人不会搜答案)

经过我多个日夜的摸索,终于实现了我的需求,现将自己的心得记录一下

说下我的需求:定义一个订单模型里面的订单orderId 是自动生成的UUID

订单的区域是外键,下单人也是外键,菜品orderMenu是一个多对多字段(其实通过我查到的方法说的都是外键字段就可以实现但是个人觉得菜品和订单应该是多对多会比较好理解)就这样给自己挖了坑

因为想要在添加订单的同时也要添加对应菜品的数量于是自定义了中间表并且添加了数量字段(噩梦开始~~~)

首先是定义模型类

models.py

代码语言:python 复制
# models.py
import django.utils.timezone as timezone
from django.contrib.auth.models import User
from django.db import models
from serverorders.models import BusinessArea
from menu.models import Menu
import uuid


# Create your models here.
class OrderCenter(models.Model):
    orderId = models.UUIDField(verbose_name='订单编号', default=uuid.uuid4, editable=False)
    orderBusinessArea = models.ForeignKey(to=BusinessArea, verbose_name='订单营业区域', null=True, blank=True,
                                          on_delete=models.CASCADE)
    orderMenu = models.ManyToManyField(to=Menu, verbose_name='多选菜品', null=True, blank=True,
                                       related_name='orderMenu', through='OrderCenterThough')
    orderPerson = models.ForeignKey(to=User, on_delete=models.CASCADE, null=True, blank=True, verbose_name='下单人')
    isCancel = models.BooleanField(verbose_name='是否取消', default=False)
    orderCreatTime = models.DateTimeField("创建日期", default=timezone.now)
    orderModTime = models.DateTimeField('最后修改日期', auto_now=True)

    def __str__(self):
        return str(self.orderId)

    class Meta:
        verbose_name = '订单'
        verbose_name_plural = verbose_name


class OrderCenterThough(models.Model):
    order_id = models.ForeignKey(to='OrderCenter', on_delete=models.CASCADE,verbose_name='订单ID')
    menu_id = models.ForeignKey(to=Menu, on_delete=models.CASCADE, verbose_name='菜品ID')
    menu_number = models.IntegerField(verbose_name="菜品数量")
    isCancel = models.BooleanField(verbose_name='是否取消', default=False)
    orderCreatTime = models.DateTimeField("创建日期", default=timezone.now)
    orderModTime = models.DateTimeField('最后修改日期', auto_now=True)

    class Meta:
        unique_together = ('order_id', 'menu_id')
        verbose_name = '订单中间表'
        verbose_name_plural = verbose_name

然后是定义了一个序列化器

serializer.py

代码语言:javascript复制
# serializer.py
from rest_framework import serializers
# 用于读取OrderCenter
class ReadOrderCenterSerializer(serializers.ModelSerializer):

    # 进一步自定义中间表字段的序列化表示,可以在 to_representation 方法中进行处理
    def to_representation(self, instance):
        representation = super(ReadOrderCenterSerializer, self).to_representation(instance)
        # 定义要显示的orderMenu
        representation['orderMenu'] = []
        # 此时的实例对象是OrderCent,传入实例对象获取orderMenu字段输入many = True 表示多个再使用.data序列化出来
        for i in MenuSerializer(instance.orderMenu, many=True).data:
            # 实例Order的ordercenterthough_set查找外键表传入查找的字段并用data序列话出来
            reason = OrderCenterThoughSerializer(instance.ordercenterthough_set.get(order_id=instance.id, menu_id=i['id'])).data
            # 加入到menu_number字段上
            i['menu_number_detail'] = reason
            # 列表添加,最好返回所有内容
            representation['orderMenu'].append(i)
        # 处理下把订单区域和下单人显示出来
        representation['orderBusinessAreaName']=instance.orderBusinessArea.area
        representation['orderPersonName'] = instance.orderPerson.username
        return representation

    class Meta:
        model = OrderCenter
        fields = "__all__"
        # 查询深度
        depth: 2

# 用于写的OrderCenter
class OrderCenterSerializer(serializers.ModelSerializer):

    class Meta:
        model = OrderCenter
        fields = "__all__"
        depth: 2
        
        
# OrderCenterMenu中间表
class OrderCenterThoughSerializer(serializers.ModelSerializer):

    class Meta:
        model = OrderCenterThough
        fields = "__all__"

这里定义两个序列化器一个用于读取的时候显示就是 list retrieve 方法

之前也是想只用一个序列化器来完成,但是总是会出现字段报错的情况出于无奈正好也要大佬提出这样的方法就试了一下

这里 ReadOrderCenterSerializer主要就是为了自定义读取全部和单个数据需要显示的内容。

接下来是定义视图 views.py

代码语言:python代码运行次数:0复制
from rest_framework import viewsets, status
from .serializers import ReadOrderCenterSerializer, OrderCenterSerializer, OrderCenterThoughSerializer
class OrderCenterViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows groups to be viewed or edited.
    """
    queryset = OrderCenter.objects.all()
    serializer_class = OrderCenterSerializer
    # permission_classes = [permissions.IsAuthenticated] # 权限方面

    # 这里是根据不同的请求调用不同的序列化器
    def get_serializer_class(self):
        print(self.action)
        if self.action == "list" or self.action == "retrieve":
            return ReadOrderCenterSerializer
        else:
            # 当不是list 和 retrieve的时候用下面的序列化器
            return self.serializer_class

    #更新
    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False) # 请求方式为patch的时候partial:True
        instance = self.get_object()
        # 发送的数据传orderBusinessArea_id 这里处理将orderBusinessArea设置为orderBusinessArea_id一样的值
        request.data['orderBusinessArea'] = request.data.get('orderBusinessArea_id')
        # 获取传入过来的多对多信息格式为[{},{}]
        # 我的方法比较笨,理论上是可以传入多个的就是在实例化的时候添加many = True 来标识,但是实在是没心思搞了
        orderMenu = request.data.get('orderMenu')
        for i in orderMenu:
        # 我的思路是既然不能在更新主表的时候更新多对多字段那就单独把多对多字段提出来更新
        # 在传入对多对多字段的时候同步传入需要更新的中间表id
            obj = OrderCenterThough(pk=i.get('id'))
            # 将获取到的id实例 传入序列化器中再把需要更新的字段传入data
            obj_serializer = OrderCenterThoughSerializer(instance=obj, data=i)
            # 校验
            obj_serializer.is_valid()
            保存
            obj_serializer.save()
        request.data['orderPerson'] = request.data.get('orderPerson_id')
        serializer = self.get_serializer(instance, data=request.data, partial=partial)

        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        # for i in serializer.data:
        #     print(i)

        if getattr(instance, '_prefetched_objects_cache', None):
            # If 'prefetch_related' has been applied to a queryset, we need to
            # forcibly invalidate the prefetch cache on the instance.
            instance._prefetched_objects_cache = {}

        return Response(serializer.data)
    # 创建新的订单
    def create(self, request, *args, **kwargs):
        #先取出传入的多对多字段
        orderMenu= request.data.pop('orderMenu',[])
        # 在创建的时候先创建一个centerodrer然后获取centerorder的pk 再创建对应的OrderCenterThough收到更新中间表数据
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        # 上面创建了主订单,这里获取主订单的id
        #print(serializer.instance.pk)
        # 遍历多对多字段
        for i in orderMenu:
            # 找到需要更新的那个中间表对应id
            i['order_id'] = serializer.instance.pk
            # 组建新的中间表数据传入序列化器中
            ojb = OrderCenterThoughSerializer(data=i)
            # 校验
            ojb.is_valid()
            # 保存
            ojb.save()
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

总结:

具体的作用已经写在上面的代码注释中,在写的时候又发现了代码中的几个bug

1、可以更新不是订单人的菜品

2、更新的时候只能更新已经生成的菜品内容,因为无法为订单添加新的菜品,这个涉及到中间表中的对应关系已经确定了。如果解决的话应该还是要加判断或者其他的处理方法

3、针对第二点的解决方法个人认为如果有新的菜品添加的话就要删除当前的订单再重新添加这样的逻辑应该就说的通了,不过具体还要看使用的需求。

其他的bug肯定还有,但是目前已经实现了可以更新已有订单和创建订单的时候添加菜品信息。

主要是一个思路,drf 的ModelSerializer 和 ModelViewSet 封装的太严实了,通过这样的方法来更新和添加多对多字段实属自己技术不成熟。也有可能他也有自己的方法而我不会用,希望对大家有帮助,也欢迎和我多交流。

0 人点赞