DRF实操学习——购物车及订单生成


目标:实现购物车功能及订单生成功能

分析:
技术点:事务控制
模型:
购物车表
订单表
订单商品表
在这里插入图片描述

1.基础配置

1. 创建模型

在shopping的models文件下创建模型

# 购物车表
class ShoppingCart(DateTimeModelMixin):
    number = models.IntegerField(default=1,verbose_name='数量')
    community = models.ForeignKey(Commodity,on_delete=models.CASCADE,verbose_name='商品')
    user = models.ForeignKey(User,on_delete=models.CASCADE,verbose_name='用户')

    class Meta:
        ordering=['-update_time']
        db_table = 'shopping_cart'
        verbose_name = '购物车'
        verbose_name_plural = verbose_name

# 订单表
class Order(DateTimeModelMixin):
    PAY_METHOD_CHOICES = (
        (1,'支付宝'),
        (2,'货到付款'),
    )
    STATUS_CHOICES = (
        (1,'待支付'),
        (2,'待发货'),
        (3,'待收货'),
        (4,'待评价'),
        (5,'已完成'),
        (6,'已取消'),
        (7,'申请退款'),
        (8,'退款成功'),
    )
    # 将订单号定义为主键字段
    order_id = models.CharField(max_length=64,primary_key=True,verbose_name='订单号')
    total_count = models.IntegerField(default=1,verbose_name='商品数')
    total_amount = models.DecimalField(max_digits=10,decimal_places=2,verbose_name='商品总金额')
    pay_method = models.IntegerField(verbose_name='支付方式',default=1,choices=PAY_METHOD_CHOICES)
    status = models.IntegerField(verbose_name='订单状态',default=1,choices=STATUS_CHOICES)
    address = models.TextField(verbose_name='收货地址')

    user = models.ForeignKey(User,on_delete=models.PROTECT,verbose_name='用户')

    class Meta:
        ordering=['-create_time']
        db_table = 'order'
        verbose_name = '订单表'
        verbose_name_plural = verbose_name


# 订单商品表
class OrderGoods(DateTimeModelMixin):
    SCORE_CHOICES = (
        (0,'零星'),
        (1,'一星'),
        (2,'二星'),
        (3,'三星'),
        (4,'四星'),
        (5,'五星'),
    )
    number = models.IntegerField(default=1,verbose_name='商品数')
    price = models.DecimalField(max_digits=10,decimal_places=2,verbose_name='单价')
    comment = models.TextField(verbose_name='评价',null=True,blank=True)
    score = models.IntegerField(null=True,blank=True,verbose_name='评分',choices=SCORE_CHOICES)

    is_anonymous = models.BooleanField(default=False,verbose_name='匿名评价')

    order = models.ForeignKey(Order,on_delete=models.CASCADE,verbose_name='订单')
    commodity = models.ForeignKey(Commodity,on_delete=models.SET_NULL,null=True,blank=True,verbose_name='商品')


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

2. 创建序列化器

class ShoppingCartSerializer(ModelSerializer):
    # 调用get_commodityDetail
    commodityDetail = SerializerMethodField() 

    class Meta:
        model = ShoppingCart
        fields = '__all__'
    
    # 使用对象级别的额外校验:校验加入购物车的商品数量不能大于商品库存数量
    def validate(self,data):
        if data['number'] <=0:
            raise serializers.ValidationError('要购买的商品数量错误')
        elif data['number'] > data['commodity'].stock:
            raise serializers.ValidationError('商品数量超出库存')
        else:
            return data

    def get_commodityDetail(self,shoppingcart):
        return CommodityImgSerializer(shoppingcart.community).data

class OrderCartSerializer(ModelSerializer):
    class Meta:
        model = Order
        fields = '__all__'

3. 创建视图

class ShoppingCartViewSet(ModelViewSet):
    serializer_class = ShoppingCartSerializer
    permission_classes = [IsAuthenticated]
    
    # 返回当前登录用户的购物车数据
    def get_queryset(self):
        # 使用管理器:源模型类小写_set
        return self.request.user.shoppingcart_set.all()
    
    @auto_user #自动将当前登录的用户作为创建者
    def create(self, request, *args, **kwargs):
        return ModelViewSet.create(self, request, *args, **kwargs)

    @update_auto_user
    def update(self, request, *args, **kwargs):
        return ModelViewSet.create(self, request, *args, **kwargs)

    @destory_auto_user
    def destroy(self, request, *args, **kwargs):
        return ModelViewSet.create(self, request, *args, **kwargs)

class OrderCartViewSet(ReadOnlyModelViewSet,CreateModelMixin):
    serializer_class = OrderCartSerializer

    # 返回当前登录用户的数据
    def get_queryset(self):
        # 使用管理器:源模型类小写_set
        return self.request.user.order_set.all()

4. 创建路由


from django.urls import path
from rest_framework.routers import DefaultRouter
from rest_framework_jwt.views import obtain_jwt_token
from .views import *

urlpatterns = []
router = DefaultRouter()
router.register('classifications', ClassificationViewSet)
router.register('commoditys', CommodityViewSet)
router.register('commodity_img', CommodityImgViewSet)
router.register('cart', ShoppingCartViewSet,basename='shoppingcart')
router.register('order', OrderCartViewSet,basename='order')
urlpatterns += router.urls

2. 接口的编写

知识补充:事务

使用场景,保证代码在一个事务中执行,比如购买商品时进行结算,代码执行到一半时突然断电或宕机,所有的事务都要撤销,不能让金额或购物信息出现错误,保证数据的一致性。
在这里插入图片描述

1. 在事务开始时创建保存点

在这里插入图片描述

2. 在事务出现问题时回滚到保存点

在这里插入图片描述

3. 执行成功,提交从保存点到当前的所有事务操作

在这里插入图片描述

使用方法
1. 装饰器用法

在这里插入图片描述##### 2. with语句用法
在这里插入图片描述

1. 重写订单创建接口:事务的实操

在序列化器重写创建,完成自定义的创建功能

class OrderCartSerializer(ModelSerializer):
    # 得到前端传入过来要结算的购物车商品id列表
    cart = serializers.ListField(write_only=True)
    class Meta:
        model = Order
        fields = '__all__'
        # 定义只读字段,不允许前端传入,前端传入有直接通过页面检查修改的风险
        read_only_fields = ['order_id','total_count','total_amount','status','user']


    #在序列化器重写创建,完成自定义的创建功能
    # validated_data保存前端传入的所有数据
    def create(self,validated_data):
        """
        自定义创建订单功能
        """
        #生成订单编号:唯一性,标识性。 日期+时间+六位数的用户id+两位随机数  22位
        #在视图集中self.request参数保存请求对象,在序列化器中是self.context['request']
        user = self.context['request'].user 
        # '%06d%02d'格式化 d表示整数,6表示6位,不够补0
        order_id = datetime.now().strftime('%Y%m%d%H%M%S') + '%06d%02d'%(user.id,randint(1,99))
        # 获取前端传入的支付方式
        pay_method = validated_data.get('pay_method')
        # 如果没有传入id,只传入了地址,说明传入的是假地址
        try:
            address = Address.objects.get(id=validated_data.get('address'))
        except Address.DoesNotExist:
            raise serializers.ValidationError('address error')
        # 格式化收货地址信息
        adress_s = f'{address.province.name}{address.city.name}{address.district.name}{address.place}[{address.receive} 收]{address.mobile}'
        with transaction.atomic():
            #开启事务,在with缩进里面的代码执行完毕之后自动关闭事务,with上下文协议
            # 里面的代码都是在一个原子,事务中
            save_point = transaction.savepoint()#创建保存点
            try:
                #创建订单信息
                order = Order.objects.create(
                    order_id = order_id,
                    total_count=0,
                    total_amount=Decimal('0.00'),
                    pay_method = pay_method,
                    # 支付宝支付,状态待支付。货到付款,状态为代发货 三目运算
                    status = 1 if pay_method == 1 else 2 ,
                    user =user,
                    address =adress_s,

                )

                #获取购物车中要结算的商品
                carts = validated_data.get('cart') #要结算的商品id列表
                for cart_id in carts:
                    try:
                        cart = ShoppingCart.objects.get(id=cart_id)
                    except:
                        transaction.savepoint_rollback(save_point) #回滚到指定保存点
                        raise serializers.ValidationError('购物信息不存在')
                    # 得到购物车中的商品信息,库存数量,销量
                    commodity = cart.commodity #关联查询
                    # 原本的库存与销量
                    origin_stock = commodity.stock
                    origin_sales = commodity.sales
                    #核算 购买的数量是否超过库存
                    if cart.number>origin_stock:
                        raise serializers.ValidationError('库存不足')

                    # 库存操作:减少库存,销量增加
                    new_stock = origin_stock-cart.number
                    new_sales = origin_sales+cart.number
                    # 更新商品信息:模型对象是修改属性调用save方法保存,查询集才是用update方法做修改
                    # 所以这里不用get,用filter
                    Commodity.objects.filter(id=commodity.id).update(stock=new_stock,sales=new_sales)

                    # 创建订单对应的商品数据
                    OrderGoods.objects.create(
                        number=cart.number,
                        price = commodity.price,
                        order=order,
                        commodity=commodity
                    )
                    # 更新订单表
                    order.total_count += cart.number
                    order.total_amount +=(cart.number * commodity.price)
                #保存订单信息
                order.save()
                #清除购物车
                # id__in=carts 查询出id在carts中的数据
                ShoppingCart.objects.filter(id__in=carts).delete()
            except Exception as e:
                transaction.savepoint_rollback(save_point) #回滚到指定保存点
                raise serializers.ValidationError(e) #结束方法 响应给客户端错误信息
            else:
                transaction.savepoint_commit(save_point) #提交保存点的所有信息
        return order

知识补充:悲观锁和乐观锁

并发问题:资源抢夺问题
问题的描述:A商品库存为6,张三加入购物车A商品为6,李四加入购物车A商品为6,两人同时结算支付,因为购买的数量小于等于库存数量,所以都可以支付成功
解决办法: 加锁、队列

1. 悲观锁

悲观锁是mysql级别的锁,需要配合事务进行使用。
BEGIN:
SELECT ‘stock’ FROM ‘commodity’ where ‘id’=1 for UPDATE;
UPDATE ‘commodity’ SET ‘stock’=0 WHERE ‘id’=1;

django实现这个sql语句:
Commodity.objects.select_for_update().get(id=1)

不推荐使用悲观锁,容易死锁。

在这里插入图片描述

2. 乐观锁

.乐观锁为逻辑锁,并不是真实存在的锁
UPDATE ‘commodity’ SET ‘stock’=0 WHERE ‘id’=1 AND ‘stock’=6;

django实现这个sql语句:
Commodity.objects.filter(id=1,stock=6).update(stock=0)
在这里插入图片描述
使用乐观锁,修改代码如下:

class OrderCartSerializer(ModelSerializer):
    # 得到前端传入过来要结算的购物车商品id列表
    cart = serializers.ListField(write_only=True)
    class Meta:
        model = Order
        fields = '__all__'
        # 定义只读字段,不允许前端传入,前端传入有直接通过页面检查修改的风险
        read_only_fields = ['order_id','total_count','total_amount','status','user']


    #在序列化器重写创建,完成自定义的创建功能
    # validated_data保存前端传入的所有数据
    def create(self,validated_data):
        """
        自定义创建订单功能
        """
        #生成订单编号:唯一性,标识性。 日期+时间+六位数的用户id+两位随机数  22位
        #在视图集中self.request参数保存请求对象,在序列化器中是self.context['request']
        user = self.context['request'].user 
        # '%06d%02d'格式化 d表示整数,6表示6位,不够补0
        order_id = datetime.now().strftime('%Y%m%d%H%M%S') + '%06d%02d'%(user.id,randint(1,99))
        # 获取前端传入的支付方式
        pay_method = validated_data.get('pay_method')
        # 如果没有传入id,只传入了地址,说明传入的是假地址
        try:
            address = Address.objects.get(id=validated_data.get('address'))
        except Address.DoesNotExist:
            raise serializers.ValidationError('address error')
        # 格式化收货地址信息
        adress_s = f'{address.province.name}{address.city.name}{address.district.name}{address.place}[{address.receive} 收]{address.mobile}'
        with transaction.atomic():
            #开启事务,在with缩进里面的代码执行完毕之后自动关闭事务,with上下文协议
            # 里面的代码都是在一个原子,事务中
            save_point = transaction.savepoint()#创建保存点
            try:
                #创建订单信息
                order = Order.objects.create(
                    order_id = order_id,
                    total_count=0,
                    total_amount=Decimal('0.00'),
                    pay_method = pay_method,
                    # 支付宝支付,状态待支付。货到付款,状态为代发货 三目运算
                    status = 1 if pay_method == 1 else 2 ,
                    user =user,
                    address =adress_s,

                )

                #获取购物车中要结算的商品
                carts = validated_data.get('cart') #要结算的商品id列表
                for cart_id in carts:
                    while True:
                        try:
                            cart = ShoppingCart.objects.get(id=cart_id)
                        except:
                            transaction.savepoint_rollback(save_point) #回滚到指定保存点
                            raise serializers.ValidationError('购物信息不存在')
                        # 得到购物车中的商品信息,库存数量,销量
                        commodity = cart.commodity #关联查询
                        # 原本的库存与销量
                        origin_stock = commodity.stock
                        origin_sales = commodity.sales
                        #核算 购买的数量是否超过库存
                        if cart.number>origin_stock:
                            raise serializers.ValidationError('库存不足')

                        # 库存操作:减少库存,销量增加
                        new_stock = origin_stock-cart.number
                        new_sales = origin_sales+cart.number
                        # 更新商品信息:模型对象是修改属性调用save方法保存,查询集才是用update方法做修改
                        # 所以这里不用get,用filter
                        
                        res = Commodity.objects.filter(id=commodity.id, stock=origin_stock).update(stock=new_stock,sales=new_sales)
                        # 增加乐观锁,在循环开始用while True:
                        if not res:
                            continue
                        # 创建订单对应的商品数据
                        OrderGoods.objects.create(
                            number=cart.number,
                            price = commodity.price,
                            order=order,
                            commodity=commodity
                        )
                        # 更新订单表
                        order.total_count += cart.number
                        order.total_amount +=(cart.number * commodity.price)

                        break
                #保存订单信息
                order.save()
                #清除购物车
                # id__in=carts 查询出id在carts中的数据
                ShoppingCart.objects.filter(id__in=carts).delete()
            except Exception as e:
                transaction.savepoint_rollback(save_point) #回滚到指定保存点
                raise serializers.ValidationError(e) #结束方法 响应给客户端错误信息
            else:
                transaction.savepoint_commit(save_point) #提交保存点的所有信息
        return order




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值