订单建模,包含订单基本信息订单商品两张表

### orders.models
from django.db import models

from utils.models import BaseModel
from users.models import UserInfo,Address
from goods.models import SKU

class OrderInfo(BaseModel):
    '''
        - 订单基本信息
        
        - 用到 User 和 Address两个外键
    '''
    PAY_METHODS_ENUM = {
        "CASH": 1,
        "ALIPAY": 2
    }
    PAY_METHOD_CHOICES = (
        (1, "货到付款"),
        (2, "支付宝"),
    )
    ORDER_STATUS_ENUM = {
        "UNPAID": 1,
        "UNSEND": 2,
        "UNRECEIVED": 3,
        "UNCOMMENT": 4,
        "FINISHED": 5
    }
    ORDER_STATUS_CHOICES = (
        (1, "待支付"),
        (2, "待发货"),
        (3, "待收货"),
        (4, "待评价"),
        (5, "已完成"),
        (6, "已取消"),
    )
    # 不使用默认自动生成的ID字段,而是自定义
    order_id = models.CharField(max_length=64, primary_key=True, verbose_name="订单号")
    user = models.ForeignKey(UserInfo, on_delete=models.PROTECT, verbose_name="下单用户")
    address = models.ForeignKey(Address, on_delete=models.PROTECT, verbose_name="收货地址")
    # 正整数
    total_count = models.PositiveIntegerField(default=1, verbose_name="商品总数")
    # 设置了精度的十进制数字(推荐使用)
    total_amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="商品总金额")
    freight = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="运费")
    pay_method = models.SmallIntegerField(choices=PAY_METHOD_CHOICES, default=1, verbose_name="支付方式")
    status = models.SmallIntegerField(choices=ORDER_STATUS_CHOICES, default=1, verbose_name="订单状态")

    class Meta:
        db_table = "tb_order_info"
        verbose_name = '订单基本信息'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.order_id


class OrderGoods(BaseModel):
    """
        - 订单商品
        
        -两个外键: order & sku
    """
    SCORE_CHOICES = (
        (0, '0分'),
        (1, '20分'),
        (2, '40分'),
        (3, '60分'),
        (4, '80分'),
        (5, '100分'),
    )
    order = models.ForeignKey(OrderInfo, related_name='skus', on_delete=models.CASCADE, verbose_name="订单")
    sku = models.ForeignKey(SKU, on_delete=models.PROTECT, verbose_name="订单商品")
    count = models.PositiveIntegerField(default=1, verbose_name="数量")
    price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="单价")
    comment = models.TextField(default="", verbose_name="评价信息")
    score = models.SmallIntegerField(choices=SCORE_CHOICES, default=5, verbose_name='满意度评分')
    is_anonymous = models.BooleanField(default=False, verbose_name='是否匿名评价')
    is_commented = models.BooleanField(default=False, verbose_name='是否评价了')

    class Meta:
        db_table = "tb_order_goods"
        verbose_name = '订单商品'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.sku.name


功能 --- 提交订单展示页

  • 请求方式

    请求方法 请求地址
    GET http://127.0.0.1:8000/orders/settlement/
  • 请求参数

    请求头 key 类型 是否必传 说明
    headers Authorization 'JWT ' + token string JWT字符串一定要加空格(后端以空格进行拆分)
  • 响应成功结果:JSON

    {
        "freight":"100.00",
        "skus":[
            {
                "id":10,
                "name":'华为xxxx手机',
                "default_image_url":"https://www.dfsdfsf.xsdfsdss",
                "price":"3788.00",
                "count":1
            },
            {
                "id":11,
                "name":'苹果xxxx收集',
                "default_image_url":"https://www.xxxx.yyyyyy",
                "price":"6700.00",
                "count":2
            },
        ]
    }
    

  • 后端接口注意事项

    • 用户收货地址(已有)
    • 支付方式('货到付款'/'支付宝')
    • 商品信息数据(不再提供修改功能)
  • 前端要获取两块信息,当网页开始加载的时候,立即向后端发起请求

    • 获取用户的收货地址
    • 获取购物车结算的商品信息
    ......
    mounted: function(){
        // 获取地址信息
        axios.get(this.host + '/addresses/', {
                headers: {
                    'Authorization': 'JWT ' + this.token
                },
                responseType: 'json'
            })
            .then(response => {
                this.addresses = response.data.addresses;
                this.nowsite = response.data.default_address_id;
            })
            ......
    
            // 获取结算商品信息
            axios.get(this.host+'/orders/settlement/', {
                headers: {
                    'Authorization': 'JWT ' + this.token
                },
                responseType: 'json'
            })
            .then(response => {
                this.skus = response.data.skus;
                this.freight = response.data.freight;
                this.total_count = 0;
                this.total_amount = 0;
                for(var i=0; i<this.skus.length; i++){
                    var amount = parseFloat(this.skus[i].price)*this.skus[i].count;
                    this.skus[i].amount = amount.toFixed(2);
                    this.total_count += this.skus[i].count;
                    this.total_amount += amount;
                }
                this.payment_amount = parseFloat(this.freight) + this.total_amount;
                this.payment_amount = this.payment_amount.toFixed(2);
                this.total_amount = this.total_amount.toFixed(2);
            })
            ......
    
    
  • 构造类似下面的数据格式,用两个序列化器来搞定

{
    "freight":"100.00", # 订单序列化器
    "skus":[ # sku序列化器
        {
            ......
        },
        {
            ......
        },
    ]
}

### orders.serializers
from apps.goods.models import SKU
from rest_framework import serializers

class CartSKUSerializer(serializers.ModelSerializer):
    '''订单中,商品数据的序列化(提供给下面的序列化类使用)'''
    
    count = serializers.IntegerField(label='购买数量') # count值从redis取
    class Meta:
        model = SKU
        fields = ['id','name','price','default_image_url','count']


class OrderSettlementSerializer(serializers.Serializer):
    '''订单序列化器'''
    
    # 多条数据,要加上many,skus对应一个查询集
    skus = CartSKUSerializer(many=True)
    # 简单赋值
    freight = serializers.DecimalField(max_digits=10,decimal_places=2,label='运费')
  • 视图: 从redis取出购物车数据,构造数据,然后序列化返回给前端
from decimal import Decimal

from django_redis import get_redis_connection
from rest_framework.views import APIView
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

from .serializers import OrderSettlementSerializer
from apps.goods.models import SKU

class OrderSettlementView(APIView):

    authentication_classes = [JSONWebTokenAuthentication, ]
    permission_classes = [IsAuthenticated, ]

    def get(self,request):
        user = request.user # 代码能进来,说明验证无误,不用再try
        conn = get_redis_connection('cart')
        # 获取 redis 购物车数据,构造dict
        redis_cart_dict = conn.hgetall('cart_{}'.format(user.id))
        selected_ids = conn.smembers('selected_{}'.format(user.id))
        cart_dict = {} # 把sku_id和count丢里面,给后面的queryset使用
        for sku_id in selected_ids:
            cart_dict[int(sku_id)] = int(redis_cart_dict[sku_id]) # 获取count
            
        skus_queryset = SKU.objects.filter(id__in=cart_dict.keys())
        for sku in skus_queryset:
            sku.count = cart_dict[sku.id] # 把count加入queryset对象

        freight = Decimal('10.00')
        data_dict = { # 构造数据并序列化
            'freight':freight,
            'skus':skus_queryset
        }
        serializer = OrderSettlementSerializer(data_dict) # 单个对象,无需再加 many=True

        return Response(serializer.data)

功能 --- 提交订单展示页

  • 请求方式

    请求方法 请求地址
    POST http://127.0.0.1:8000/orders/
  • 请求参数

    请求头 key 类型 是否必传 说明
    headers Authorization 'JWT ' + token string JWT字符串一定要加空格(后端以空格进行拆分)
    请求参数 address 收货地址
    请求参数 pay_method 支付方式
  • 响应成功结果: OrderInfo对象(前端可以提取 order_id,total_amount 渲染页面)

  • 当用户点击```提交订单按钮``时,前端只传两个字段

    • 收货地址
    • 支付方式(货到付款/支付宝)
    • 商品的数据,不要从这里取(虽然用户该页面无法修改,但可以再打开购物车页面,进行编辑)
    ......
    on_order_submit: function(){
        if (this.order_submitting == false){
            this.order_submitting = true;
            axios.post(this.host+'/orders/', {
                    // 提交这两个字段
                    address: this.nowsite,
                    pay_method: this.pay_method
                }, {
                    headers: {
                        'Authorization': 'JWT ' + this.token
                    },
                    responseType: 'json'
                })
                .then(response => {
                    location.href = '/order_success.html?order_id='+response.data.order_id
                        +'&amount='+this.payment_amount
                        +'&pay='+this.pay_method;
                })
                .catch(error => {
                    this.order_submitting = false;
                    alert(error.response.data[0]);
                })
        }
    }
    
    
    

  • 当用户提交订单以后,我们把订单信息保存下来,以便后续用户根据订单信息支付商品费用

    ### serializers
    ......
    # 订单信息序列化器
    class CommitOrderSerializer(serializers.ModelSerializer):
        class Meta:
            model = OrderInfo
            extra_kwargs = {
                'order_id': {'read_only': True},
                'address': {'write_only': True}, # 这两个字段均需校验
                'pay_method': {'write_only', True},
            }
    
        def create(self, validated_data):
            ......
    
    ### views
    ......
    class CommitOrderView(CreateAPIView):
        authentication_classes = [JSONWebTokenAuthentication, ]
        permission_classes = [IsAuthenticated, ]
        
    
    - create
    
    	- 获取 user
    	- order_id:  timezone.localtime().strftime('%Y%m%d%H%M%S') + '0'*9 + str(user.id)
    	- address,pay_method
    	 # 如果是支付宝,就显示未支付;其他显示 未发货
    	- status: (OrderInfo.ORDER_STATUS_ENUM['UNPAID'] # 1
                      if pay_method == OrderInfo.PAY_METHODS_ENUM['ALIPAY'] # 2
                      else OrderInfo.ORDER_STATUS_ENUM['UNSEND']) # 2
                      
        - total_count,total_amount: 0; freight = 10
        
        OrderInfo.create(.....)
    
  • 首先保存订单信息,即生成一条OrderInfo模型记录

    ### serializers
    ......
    class CommitOrderSerializer(serializers.ModelSerializer):
        class Meta:
            ......
        def create(self, validated_data):
            user = self.context['request'].user
            # 手动生成 order_id
            order_id = timezone.localtime().strftime('%Y%m%d%H%M%S') + '0' * 9 + str(user.id)
            address = validated_data.get('address')
            pay_method = validated_data.get('pay_method')
            # 如果是支付宝,就显示-未支付;其他显示-未发货
            status = (OrderInfo.ORDER_STATUS_ENUM['UNPAID']
                      if pay_method == OrderInfo.PAY_METHODS_ENUM['ALIPAY']
                      else OrderInfo.ORDER_STATUS_ENUM['UNSEND'])
    
            total_count = 0 # 先用假数据演练
            total_amount = 0
            freight = 10
    
            OrderInfo.objects.create(
                order_id=order_id,
                user=user,
                address=address,
                total_count=total_count,
                total_amount=total_amount,
                freight=freight,
                pay_method=pay_method,
                status=status
            )
    
    • 从redis获取被勾选的商品信息,更新库存和销量
class CommitOrderSerializer(serializers.ModelSerializer):
    class Meta:
        ......
    def create(self, validated_data):
        ......

        OrderInfo.objects.create(
           ......
        )

        # 修改商品库存,销量
        conn = get_redis_connection('cart')
        cart_dict = conn.hgetall('cart_{}'.format(user.id))
        selected_ids = conn.smembers('selected_{}'.format(user.id))
        for sku_id in selected_ids:
            sku_obj = SKU.objects.get(id=int(sku_id))
            count = int(cart_dict[sku_id])
            if count > sku_obj.stock:
                raise serializers.ValidationError('库存不足')
            sku_obj.stock -= count
            sku_obj.sales += count
            sku_obj.save()
  • 接下来要做的事情
    • 保存订单商品信息
    • 累加商品总数量和总价(之前默认值均为0)
    • 加入邮费并保存订单信息,最后返回订单信息模型
class CommitOrderSerializer(serializers.ModelSerializer):
    class Meta:
       ......

    def create(self, validated_data):
        ......
        order_info_obj = OrderInfo.objects.create( # 接收订单对象,以便后续操作
           ......
        )

        # 修改商品库存,销量
        conn = get_redis_connection('cart')
        ......
        for sku_id in selected_ids:
            sku_obj = SKU.objects.get(id=int(sku_id))
            ......
            sku_obj.save()

            OrderGoods.objects.create(
                order=order_info_obj,
                sku=sku_obj,
                count=count,
                price=sku_obj.price
            )

            order_info_obj.total_count += count
            order_info_obj.total_amount += (sku_obj.price)*count

        order_info_obj.total_amount += order_info_obj.freight
        order_info_obj.save()
        return order_info_obj

  • 上述代码存在的问题
- 触发异常时: raise serializers.ValidationError('库存不足')

- 此时 order_info_obj 记录已经生成,而这笔订单实际是失败的,不能生成订单让用户支付

- 在上述view中,我们同时操作了4张表,应该共同进退,要么一起成功,要么一起失败

- MySQL支持事务,刚好满足需求

    - MySQL 默认并支持开启事务,每次操作一次表会默认拷贝一个副本,在副本上操作数据
      若副本上成功操作数据,则让副本的数据覆盖源数据;若失败,就退回源数据

- 如果把上述同时操作的4张表看成一次事务,就可以满足需求,即4张表共同进退~

django事务API

  • 参考网址
http://47.101.37.192/%E8%AF%BE%E4%BB%B6/%E7%BE%8E%E5%A4%9A%E5%95%86%E5%9F%8E%E8%AF%BE%E4%BB%B6/orders/commit/transaction.html
  • Django 中可以通过django.db.transaction 模块提供的atomic来定义一个事务,atomic提供两种方案实现事务
- 装饰器用法

from django.db import transaction
@transaction.atomic
def viewfunc(request):
  # 这些代码会在一个事务中执行
  ......
- with语句用法

from django.db import transaction
def viewfunc(request):
  # 这部分代码不在事务中,会被 Django 自动提交
  ......
  with transaction.atomic():
      # 这部分代码会在事务中执行
      ......
  • 两种方法取舍
- 装饰器用法:整个视图中所有 MySQL 数据库的操作都看做一个事务,范围太大,不够灵活
  而且无法直接作用于类视图

- with 语句用法(推荐):可以灵活的有选择性的把某些 MySQL 数据库的操作看做一个事务
  而且不用关心视图的类型

  • 事务中的保存点
在 Django 中,还提供了保存点的支持,可以在事务中创建保存点来记录数据的特定状态
数据库出现错误时,可以回滚到数据保存点的状态
from django.db import transaction
# 创建保存点
save_id = transaction.savepoint()  
# 回滚到保存点
transaction.savepoint_rollback(save_id)
# 提交从保存点到当前状态的所有数据库事务操作
transaction.savepoint_commit(save_id)
  • 事务运用到项目,实现共同进退
class CommitOrderSerializer(serializers.ModelSerializer):
    class Meta:
        model = OrderInfo
        extra_kwargs = {
            'order_id': {'read_only': True},
            'address': {'write_only': True},
            'pay_method': {'write_only', True},
        }

    def create(self, validated_data):
        ......
        freight = 10

        with transaction.atomic():
            save_point = transaction.savepoint() # 创建还原点
            try:
                order_info_obj = OrderInfo.objects.create(
                    order_id=order_id,
                    user=user,
                    address=address,
                    total_count=total_count,
                    total_amount=total_amount,
                    freight=freight,
                    pay_method=pay_method,
                    status=status
                )

           
                conn = get_redis_connection('cart')
                cart_dict = conn.hgetall('cart_{}'.format(user.id))
                selected_ids = conn.smembers('selected_{}'.format(user.id))

                for sku_id in selected_ids:
                    sku_obj = SKU.objects.get(id=int(sku_id))
                    count = int(cart_dict[sku_id])
                    if count > sku_obj.stock:
                        raise serializers.ValidationError('库存不足')
                    sku_obj.stock -= count
                    sku_obj.sales += count
                    sku_obj.save()

                    OrderGoods.objects.create(
                        order=order_info_obj,
                        sku=sku_obj,
                        count=count,
                        price=sku_obj.price
                    )

                    order_info_obj.total_count += count
                    order_info_obj.total_amount += (sku_obj.price)*count

                order_info_obj.total_amount += order_info_obj.freight
                order_info_obj.save()

            except Exception:
                transaction.savepoint_rollback(save_point) # 异常就回滚到还原点,触发库存异常
                raise serializers.ValidationError('库存不足')
            else:
                transaction.savepoint_commit(save_point) # 没有异常就提交事务

        return order_info_obj

用户同时下单引发的库存问题

  • A用户B用户同时下单,可能造成库存不足而多个用户却下单成功的场景

    • 比如同时查询了库存,此时库存充足,而A用户网速好,优先修改了库存量;
      由于B用户是同时查询,所以也会查询成功,然后和A用户一样的操作
      最终结果:库存15个,却卖出18个...商家自己填坑!
  • 解决办法:有三种方式,悲观锁,乐观锁,任务队列

- 悲观锁: 当查询某条记录时,即让数据库为该记录加锁,锁住记录后其他人无法操作,使用类似如下语法

    select stock from tb_sku where id=1 for update;
    SKU.objects.select_for_update().get(id=1)

    - 缺点: 类似于我们在多线程资源竞争时添加的互斥锁,容易出现死锁现象(即忘记释放锁)
      而且用户只能等,用户体验并不好

- 乐观锁(推荐): 乐观锁并不是真实存在的锁,而是在更新的时候判断此时的库存是否是之前查询出的库存
  如果相同,表示没人修改,可以更新库存,否则表示别人抢过资源,不再执行库存更新

    update tb_sku set stock=2 where id=1 and stock=7
    SKU.objects.filter(id=1, stock=7).update(stock=2)

- 任务队列: 将下单的逻辑放到任务队列中(如celery),将并行转为串行,所有人排队下单
  比如开启只有一个进程的Celery,一个订单一个订单的处理
  • 项目应用
class CommitOrderSerializer(serializers.ModelSerializer):
    class Meta:
        ......

    def create(self, validated_data):
        ......
        with transaction.atomic():
            ......
                for sku_id in selected_ids:
                    while True: # 让用户拥有无线下单的机会
                        sku_obj = SKU.objects.get(id=int(sku_id))
                        count = int(cart_dict[sku_id])
                        origin_stock = sku_obj.stock

                        if count > sku_obj.stock:
                            raise serializers.ValidationError('库存不足')

                        # sku_obj.stock -= count
                        # sku_obj.sales += count
                        # sku_obj.save()
                        # 先查询是否为原始数据,查询成功则更新库存和销量
                        # 查询不成功,就继续上面的下单流程(若还有库存,用户可以继续下单)
                        update_nums =  SKU.objects.filter(stock=origin_stock,id=int(sku_id)).update(stock=origin_stock-count,sales=sku_obj.sales + count)
                        if update_nums == 0:
                            continue

                        OrderGoods.objects.create(
                           ......
                        )

                        ......
                        order_info_obj.total_amount += (sku_obj.price)*count
                        break # 成功下单就退出循环

                order_info_obj.total_amount += order_info_obj.freight
                order_info_obj.save()

            except Exception:
                transaction.savepoint_rollback(save_point)
                raise serializers.ValidationError('库存不足')
            else:
                transaction.savepoint_commit(save_point)

        return order_info_obj

MySQL事务隔离级别

  • 定义: 指的是在处理同一个数据的多个事务中,一个事务修改数据后
    其他事务何时能看到修改后的结果

  • MySQL数据库事务隔离级别主要有四种

- Serializable:串行化,一个事务一个事务的执行

- Repeatable read(默认设置):可重复读,无论其他事务是否修改并提交了数据,
  在这个事务中看到的数据值始终不受其他事务影响

- Read committed:读取已提交,其他事务提交了对数据的修改后,本事务就能读取到修改后的数据值

- Read uncommitted:读取未提交,其他事务只要修改了数据,即使未提交,本事务也能看到修改后的数据值

  • 使用乐观锁的时候,如果一个事务修改了库存并提交了事务,那其他的事务应该可以读取到修改后的数据值
    所以不能使用可重复读的隔离级别,应该修改为读取已提交(Read committed)
- windows系统下,修改"my.ini",文件末尾增加这句
    transaction-isolation=READ-COMMITTED
  • 最后,清空购物车已结算商品
......
pl = conn.pipeline()
pl.hdel('cart_{}'.format(user.id),*cart_dict.keys())
pl.srem('selected_{}'.format(user.id),*selected_ids)
pl.execute()

        return order_info_obj
  • 完整代码如下
from apps.goods.models import SKU
from .models import OrderInfo, OrderGoods

from django.utils import timezone
from django.db import transaction
from django_redis import get_redis_connection

from rest_framework import serializers

class CommitOrderSerializer(serializers.ModelSerializer):
    class Meta:
        model = OrderInfo
        fields = ['order_id', 'address', 'pay_method']
        extra_kwargs = {
            'order_id': {
                'read_only': True,
            },
            'address': {
                'write_only': True,
            },
            'pay_method': {
                'write_only': True,
            },
        }


    def create(self, validated_data):

        user = self.context['request'].user
        order_id = timezone.localtime().strftime('%Y%m%d%H%M%S') + '0' * 9 + str(user.id)
        address = validated_data.get('address')
        pay_method = validated_data.get('pay_method')

        status = (OrderInfo.ORDER_STATUS_ENUM['UNPAID']
                  if pay_method == OrderInfo.PAY_METHODS_ENUM['ALIPAY']
                  else OrderInfo.ORDER_STATUS_ENUM['UNSEND'])

        total_count = 0
        total_amount = 0
        freight = 10

        with transaction.atomic():
            save_point = transaction.savepoint()
            try:
                order_info_obj = OrderInfo.objects.create(
                    order_id=order_id,
                    user=user,
                    address=address,
                    total_count=total_count,
                    total_amount=total_amount,
                    freight=freight,
                    pay_method=pay_method,
                    status=status
                )

                # 修改商品库存,销量
                conn = get_redis_connection('cart')
                cart_dict = conn.hgetall('cart_{}'.format(user.id))
                selected_ids = conn.smembers('selected_{}'.format(user.id))

                for sku_id in selected_ids:

                    while True:

                        sku_obj = SKU.objects.get(id=int(sku_id))
                        count = int(cart_dict[sku_id])
                        origin_stock = sku_obj.stock

             

                        if count > sku_obj.stock:
                            raise serializers.ValidationError('{}库存不足'.format(sku_obj.name))

                     
                        update_nums = SKU.objects.filter(stock=origin_stock,id=int(sku_id)).update(stock=origin_stock-count,sales=sku_obj.sales + count)
                        if update_nums == 0:
                            continue

                        OrderGoods.objects.create(
                            order=order_info_obj,
                            sku=sku_obj,
                            count=count,
                            price=sku_obj.price
                        )

                        order_info_obj.total_count += count
                        order_info_obj.total_amount += (sku_obj.price)*count
                        break

                order_info_obj.total_amount += order_info_obj.freight
                order_info_obj.save()

            except Exception:
                transaction.savepoint_rollback(save_point)
                raise serializers.ValidationError('{}库存不足'.format(sku_obj.name))
            else:
                transaction.savepoint_commit(save_point)

        pl = conn.pipeline()
        pl.hdel('cart_{}'.format(user.id),*cart_dict.keys())
        pl.srem('selected_{}'.format(user.id),*selected_ids)
        pl.execute()

        return order_info_obj

展示订单信息

  • db获取字段,构造如下数据格式
- 前端传分页字段(query): page,page_size

- 订单号
- 订单创建时间
- sku商品名称,数量,单价,总价,图片
- 付款方式
- 分页


[
	{
		order_id:xxx,
		create_time:xxx,
		total_amount:xxx
		pay_method:xxx,
		skus:[
			{
				sku:{id:xxx,name:xxx,default_image_url:xxx},
				count:xxx,
				price:xxx
			},
			{......}
		],
	},
	{......},
	{......}

]

  • 针对上面的数据格式,我们自定义两个序列化器来搞定它
### orders.serializers
......
### 订单商品序列化器(包含sku商品的相关信息)
class ShowOrderGoodsSerializer(serializers.ModelSerializer):
    sku = serializers.SerializerMethodField()
    class Meta:
        model = OrderGoods
        fields = ['sku', 'count','price']
        read_only_fields = ['sku', 'count','price']

    def get_sku(self,obj):
        sku_dict = {}
        sku_dict['id'] = obj.sku.id
        sku_dict['name'] = obj.sku.name
        sku_dict['default_image_url'] = obj.sku.default_image_url
        return sku_dict

### 订单信息序列化器
class ShowOrderSerializer(serializers.ModelSerializer):
    skus = ShowOrderGoodsSerializer(many=True)
    class Meta:
        model = OrderInfo
        fields = ['order_id', 'pay_method','total_amount','create_time','skus']
        read_only_fields = ['order_id', 'pay_method','total_amount','create_time','skus']

  • views的逻辑如下
### orders.views
......
class ShowOrderView(APIView):
	
	# 必要的用户校验
    authentication_classes = [JSONWebTokenAuthentication, ]
    permission_classes = [IsAuthenticated, ]

    def get(self,request):
        user = request.user
        queryset = OrderInfo.objects.filter(user=user).all()
        serializer = ShowOrderSerializer(queryset,many=True)
        return Response(serializer.data)
        
### orders.urls
from django.conf.urls import url
from . import views

urlpatterns = [
    ......
    url(r"^orders/user/$",views.ShowOrderView.as_view()),
]

  • 测试数据,返回结果如下
[
	{
		"order_id": "2023042616361000000000012",
		"pay_method": 1,
		"total_amount": "30274.06",
		"create_time": "2023-04-26T16:36:10.050187+08:00",
		"skus": [
			{
				"sku": {
					"id": 16,
					"name": "华为 HUAWEI P10 Plus 6GB+128GB 曜石黑 移动联通电信4G手机 双卡双待",
					"default_image_url": "http://192.168.11.39:8888/group1/M00/00/02/CtM3BVrRdPeAXNDMAAYJrpessGQ9777651"
				},
				"count": 1,
				"price": "4099.99"
			},
			{
				"sku": {
					"id": 13,
					"name": "华为 HUAWEI P10 Plus 6GB+64GB 玫瑰金 移动联通电信4G手机 双卡双待",
					"default_image_url": "http://192.168.11.39:8888/group1/M00/00/02/CtM3BVrRdLGARgBAAAVslh9vkK00474545"
				},
				"count": 1,
				"price": "3388.01"
			},
			{
				"sku": {
					"id": 1,
					"name": "Apple MacBook Pro 13.3英寸笔记本 银色",
					"default_image_url": "http://192.168.11.39:8888/group1/M00/00/02/CtM3BVrPB4GAWkTlAAGuN6wB9fU4220429"
				},
				"count": 2,
				"price": "11388.03"
			}
		]
	},
	{
		"order_id": "2023042616391500000000012",
		"pay_method": 1,
		"total_amount": "30274.06",
		"create_time": "2023-04-26T16:39:15.107734+08:00",
		"skus": [
			{
				"sku": {
					"id": 16,
					"name": "华为 HUAWEI P10 Plus 6GB+128GB 曜石黑 移动联通电信4G手机 双卡双待",
					"default_image_url": "http://192.168.11.39:8888/group1/M00/00/02/CtM3BVrRdPeAXNDMAAYJrpessGQ9777651"
				},
				"count": 1,
				"price": "4099.99"
			},
			{
				"sku": {
					"id": 1,
					"name": "Apple MacBook Pro 13.3英寸笔记本 银色",
					"default_image_url": "http://192.168.11.39:8888/group1/M00/00/02/CtM3BVrPB4GAWkTlAAGuN6wB9fU4220429"
				},
				"count": 2,
				"price": "11388.03"
			},
			{
				"sku": {
					"id": 13,
					"name": "华为 HUAWEI P10 Plus 6GB+64GB 玫瑰金 移动联通电信4G手机 双卡双待",
					"default_image_url": "http://192.168.11.39:8888/group1/M00/00/02/CtM3BVrRdLGARgBAAAVslh9vkK00474545"
				},
				"count": 1,
				"price": "3388.01"
			}
		]
	},
	{
		"order_id": "2023042616484000000000012",
		"pay_method": 1,
		"total_amount": "7498.00",
		"create_time": "2023-04-26T16:48:40.313562+08:00",
		"skus": [
			{
				"sku": {
					"id": 13,
					"name": "华为 HUAWEI P10 Plus 6GB+64GB 玫瑰金 移动联通电信4G手机 双卡双待",
					"default_image_url": "http://192.168.11.39:8888/group1/M00/00/02/CtM3BVrRdLGARgBAAAVslh9vkK00474545"
				},
				"count": 1,
				"price": "3388.01"
			},
			{
				"sku": {
					"id": 16,
					"name": "华为 HUAWEI P10 Plus 6GB+128GB 曜石黑 移动联通电信4G手机 双卡双待",
					"default_image_url": "http://192.168.11.39:8888/group1/M00/00/02/CtM3BVrRdPeAXNDMAAYJrpessGQ9777651"
				},
				"count": 1,
				"price": "4099.99"
			}
		]
	},
	{
		"order_id": "2023042715455700000000012",
		"pay_method": 1,
		"total_amount": "7898.00",
		"create_time": "2023-04-27T15:45:57.547184+08:00",
		"skus": [
			{
				"sku": {
					"id": 16,
					"name": "华为 HUAWEI P10 Plus 6GB+128GB 曜石黑 移动联通电信4G手机 双卡双待",
					"default_image_url": "http://192.168.11.39:8888/group1/M00/00/02/CtM3BVrRdPeAXNDMAAYJrpessGQ9777651"
				},
				"count": 1,
				"price": "4099.99"
			},
			{
				"sku": {
					"id": 14,
					"name": "华为 HUAWEI P10 Plus 6GB+128GB 玫瑰金 移动联通电信4G手机 双卡双待",
					"default_image_url": "http://192.168.11.39:8888/group1/M00/00/02/CtM3BVrRdMSAaDUtAAVslh9vkK04466364"
				},
				"count": 1,
				"price": "3788.01"
			}
		]
	}
]
  • 如果把上述逻辑封装成ListAPIView,可以这么搞(代码没省多少)
### views
......
class ShowListOrderView(ListAPIView):

    authentication_classes = [JSONWebTokenAuthentication, ]
    permission_classes = [IsAuthenticated, ]
    serializer_class = ShowOrderSerializer
  
    def get_queryset(self):
        return OrderInfo.objects.filter(user=self.request.user).all()
        
### urls
......
urlpatterns = [
   ......
    # url(r"^orders/user/$",views.ShowOrderView.as_view()),
    url(r"^orders/user/$",views.ShowListOrderView.as_view()),
]