DRF 商城项目 - 购物( 购物车, 订单, 支付 )逻辑梳理

Posted shijieli

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DRF 商城项目 - 购物( 购物车, 订单, 支付 )逻辑梳理相关的知识,希望对你有一定的参考价值。

购物车

购物车模型

购物车中的数据不应该重复. 即对相同商品的增加应该是对购买数量的处理而不是增加一条记录

因此对此进行联合唯一索引, 但是也因此存在一些问题

class ShoppingCart(models.Model):
    user = models.ForeignKey(User, verbose_name=u"用户")
    goods = models.ForeignKey(Goods, verbose_name=u"商品")
    nums = models.IntegerField(default=0, verbose_name="购买数量")

    add_time = models.DateTimeField(default=datetime.now, verbose_name=u"添加时间")

    class Meta:
        verbose_name = 购物车
        verbose_name_plural = verbose_name
        unique_together = ("user", "goods")  # 一个商品不应该在购物车中重复

    def __str__(self):
        return "%s(%d)".format(self.goods.name, self.nums)

购物车序列化组件

选择序列化方式

数据库中设定联合唯一索引之后. 如果对某一商品重复提添加数据, 会导致记录重复.因此会触发报错,

报错后就无法进入视图逻辑, 而我们想要实现的操作是重复记录的提交处理成购买数量的增加.而不是给与前端一个报错信息

,因此在序列化组件的时候需要绕过此报错, 对验证处理进行重写,所以使用更灵活的 serializers.Serializer 方式

class ShopCartSerializer(serializers.Serializer):

外键字段处理

Serializer 的外键处理需要用 PrimaryKeyRelatedField 字段, 如果是 ModelSerializer 也可以使用此字段, 但是无需指定 queryset 即可

详情使用见 官网文档  ( ModelSerializer  本来就和数据库有映射, 因此可以自动识别到外联表)

goods = serializers.PrimaryKeyRelatedField(required=True, queryset=Goods.objects.all())

ModelSerializer 对外键的处理还可以使用 序列化组价的嵌套来处理, 也可以实现相同的效果,

class CategorySerializer(serializers.ModelSerializer):
    sub_cat = CategorySerializer2(many=True)

    class Meta:
        model = GoodsCategory
        fields = "__all__"


class GoodsSerializer(serializers.ModelSerializer):
    category = CategorySerializer()
    images = GoodsImageSerializer(many=True)

    class Meta:
        model = Goods
        fields = "__all__"

重写create

为了处理重复记录的问题, 视图类中我们继承的是 viewsets.ModelViewSet ,但是底层的处理方法是 mixin.CreateModelMixin 中的 create 方法

因此我们需要重写此方法, 当然不能再视图类中重写, 前面分析过了, 在序列化组件验证的时候就会被报错拦截下来. 根本进不去视图类, 重写也没用

因此我们在 序列化组件中重写 create 方法

    def create(self, validated_data):
        user = self.context["request"].user
        nums = validated_data["nums"]
        goods = validated_data["goods"]

        existed = ShoppingCart.objects.filter(user=user, goods=goods)

        # 判断当前是否已有记录
        if existed:
            existed = existed[0]
            existed.nums += nums
            existed.save()
        else:
            existed = ShoppingCart.objects.create(**validated_data)
        # 需要返回保存数据
        return existed

重写 update 方法

Serializer 本身是继承自  BaseSerializer , 而  BaseSerializer 中有一个 update 方法

此 update 方法中仅仅是抛出了一个异常

技术图片

而 Serializer 内部也没有对 update 方法进行重写. 因此导致无法进行更新操作

因此我们需要重写此方法

按照正常的购物流程来说

修改商品应该是先加入购物车才可以进行选择

此处的修改只允许修改商品数量

因此进行如下重写即可

    def update(self, instance, validated_data):
        # 修改商品数量
        instance.nums = validated_data["nums"]
        instance.save()
        return instance

ps: 对比 ModelSerializer  

ModelSerializer  中就有对 update 的重写. 因此不需要额外操作

技术图片

ps: DELETE 处理

删除操作不需要重写的. BaseSerializer  里面没有对 delete 的操作, 因此也不会有什么奇怪的报错,

这部分的详细问题就进 rest_framework.serializers.py 文件中查看即可

购物商品详情

购物商品详情序列化组件

引用 商品序列化组件来获取商品所有信息

# 购物车商品详情
class ShopCartDetailSerializer(serializers.ModelSerializer):
    goods = GoodsSerializer(many=False, read_only=True)

    class Meta:
        model = ShoppingCart
        fields = ("goods", "nums")

 购物车商品详情视图分流

# 分流 序列化组件
    def get_serializer_class(self):
        if self.action == list:
            return ShopCartDetailSerializer
        else:
            return ShopCartSerializer

购物车全部代码

购物车序列化组件

# 购物车商品详情
class ShopCartDetailSerializer(serializers.ModelSerializer):
    goods = GoodsSerializer(many=False, read_only=True)

    class Meta:
        model = ShoppingCart
        fields = ("goods", "nums")


# 购物车
class ShopCartSerializer(serializers.Serializer):
    user = serializers.HiddenField(
        default=serializers.CurrentUserDefault()
    )
    nums = serializers.IntegerField(required=True, label="数量", min_value=1,
                                    error_messages={
                                        "min_value": "商品数量不能小于一",
                                        "required": "请选择购买数量"
                                    })
    # Serializer 的外键处理需要用此字段, 如果是 ModelSerializer 也可以使用此字段, 但是无需指定 queryset 即可
    goods = serializers.PrimaryKeyRelatedField(required=True, queryset=Goods.objects.all())

    def create(self, validated_data):
        user = self.context["request"].user
        nums = validated_data["nums"]
        goods = validated_data["goods"]

        existed = ShoppingCart.objects.filter(user=user, goods=goods)

        # 判断当前是否已有记录
        if existed:
            existed = existed[0]
            existed.nums += nums
            existed.save()
        else:
            existed = ShoppingCart.objects.create(**validated_data)
        # 需要返回保存数据
        return existed

    def update(self, instance, validated_data):
        # 修改商品数量
        instance.nums = validated_data["nums"]
        instance.save()
        return instance

购物车视图

# 购物车
class ShoppingCartViewset(viewsets.ModelViewSet):
    """
    list:
        获取购物车详情
    create:
        加入购物车
    delete:
        删除购物记录
    """
    permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)
    authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)
    serializer_class = ShopCartSerializer
    # 我们修改的是要的是 goods 的id 而不是这条记录本身的 id
    lookup_field = "goods_id"

    # 分流 序列化组件
    def get_serializer_class(self):
        if self.action == list:
            return ShopCartDetailSerializer
        else:
            return ShopCartSerializer

    def get_queryset(self):
        return ShoppingCart.objects.filter(user=self.request.user)

订单

模型表

订单信息模型表

class OrderInfo(models.Model):
    ORDER_STATUS = (
        ("TRADE_SUCCESS", "成功"),
        ("TRADE_CLOSED", "超时关闭"),
        ("WAIT_BUYER_PAY", "交易创建"),
        ("TRADE_FINISHED", "交易结束"),
        ("paying", "待支付"),
    )

    user = models.ForeignKey(User, verbose_name="用户")
    order_sn = models.CharField(max_length=30, null=True, blank=True, unique=True, verbose_name="订单号")
    trade_no = models.CharField(max_length=100, unique=True, null=True, blank=True, verbose_name=u"交易号")
    pay_status = models.CharField(choices=ORDER_STATUS, default="paying", max_length=30, verbose_name="订单状态")
    post_script = models.CharField(max_length=200, verbose_name="订单留言")
    order_mount = models.FloatField(default=0.0, verbose_name="订单金额")
    pay_time = models.DateTimeField(null=True, blank=True, verbose_name="支付时间")

    # 用户信息
    address = models.CharField(max_length=100, default="", verbose_name="收货地址")
    signer_name = models.CharField(max_length=20, default="", verbose_name="签收人")
    singer_mobile = models.CharField(max_length=11, verbose_name="联系电话")

    add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间")

    class Meta:
        verbose_name = u"订单"
        verbose_name_plural = verbose_name

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

order_sn 

特别说明一下 order_sn 订单号字段, 订单号是必须要有的, 但是是需要在后端进行生成的

且订单号也应该是唯一的, 因此不能使用默认值的方式来处理, 用户在前端生成订单的时候必然是不知道订单号的

但是在 create 的时候会进行所有字段验证, 如果此字段不存在就会报错

为了避免报错, 这里姑且设置为空方便后续操作

trade_no

支付宝提供的交易号

用户信息

用户信息的相关的字段是不能使用外键来处理的, 因为订单的信息是不能随便改动的

如果下订单后, 用户又在用户中心操作了相关的属性也会因为是外键的关系导致订单中的信息也发送变化

因此此处的用户信息需要进行额外的字段来保存

订单商品详情模型表

订单和商品之间是多对多关系, 因此需要第三张表来建立, 同时在此表中需要额外字段商品数量以及添加时间

不能简单的直接通过ORM 的属性来创建

# 订单的商品详情
class OrderGoods(models.Model):
    order = models.ForeignKey(OrderInfo, verbose_name="订单信息", related_name="goods")
    goods = models.ForeignKey(Goods, verbose_name="商品")
    goods_num = models.IntegerField(default=0, verbose_name="商品数量")

    add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间")

    class Meta:
        verbose_name = "订单商品"
        verbose_name_plural = verbose_name

    def __str__(self):
        return str(self.order.order_sn)

订单序列化组件

订单

# 订单
class OrderSerializer(serializers.ModelSerializer):
    user = serializers.HiddenField(
        default=serializers.CurrentUserDefault()
    )

    # 订单的某些信息是不能自己修改的
    pay_status = serializers.CharField(read_only=True)
    trade_no = serializers.CharField(read_only=True)
    order_sn = serializers.CharField(read_only=True)
    pay_time = serializers.DateTimeField(read_only=True)


    # 生成订单号函数
    def generate_order_sn(self):
        # 当前时间+userid+随机数
        from time import strftime
        from random import Random
        random_ins = Random()
        order_sn = "{time_str}{userid}{ranstr}".format(time_str=strftime("%Y%m%d%H%M%S"),
                                                       userid=self.context["request"].user.id,
                                                       ranstr=random_ins.randint(10, 99))
        return order_sn

    # 对订单号进行生成
    def validate(self, attrs):
        attrs["order_sn"] = self.generate_order_sn()
        return attrs

    class Meta:
        model = OrderInfo
        fields = "__all__"

订单详情

# 订单详情
class OrderDetailSerializer(serializers.ModelSerializer):
    goods = OrderGoodsSerialzier(many=True)


    class Meta:
        model = OrderInfo
        fields = "__all__"

订单商品

# 订单商品详情
class OrderGoodsSerialzier(serializers.ModelSerializer):
    goods = GoodsSerializer(many=False)

    class Meta:
        model = OrderGoods
        fields = "__all__"

订单视图

源码剖析

订单视图需要生成订单号, 订单号的生成需要在 保存操作之前,

通过源码翻找可以找到, 在mixin.CreateModelMixin中的 perform_create 方法处理相关的保存操作

技术图片

订单号生成

所以在保存操作前进行订单号的生成即可, 订单号的生成可以在视图进行完成也可以在序列化组件进行完成, 

这里采用的是在序列化组件中进行生成了. ( 注意序列化组件中和视图中取当前用户对象的方式是不同的 )

完成订单号的生成后然后对 perform_create  进行重写 

重写创建函数

重写  perform_create  方法, 手动进行订单表的创建添加操作

    def perform_create(self, serializer):
        order = serializer.save()
        shop_carts = ShoppingCart.objects.filter(user=self.request.user)
        for shop_cart in shop_carts:
            order_goods = OrderGoods()
            order_goods.goods = shop_cart.goods
            order_goods.goods_num = shop_cart.nums
            order_goods.order = order
            order_goods.save()
            shop_cart.delete()
        return order

订单全部代码

订单序列化组件

# 订单商品详情
class OrderGoodsSerialzier(serializers.ModelSerializer):
    goods = GoodsSerializer(many=False)

    class Meta:
        model = OrderGoods
        fields = "__all__"


# 订单详情 class OrderDetailSerializer(serializers.ModelSerializer): goods = OrderGoodsSerialzier(many=True) class Meta: model = OrderInfo fields = "__all__" # 订单 class OrderSerializer(serializers.ModelSerializer): user = serializers.HiddenField( default=serializers.CurrentUserDefault() ) # 订单的某些信息是不能自己修改的 pay_status = serializers.CharField(read_only=True) trade_no = serializers.CharField(read_only=True) order_sn = serializers.CharField(read_only=True) pay_time = serializers.DateTimeField(read_only=True) # 生成订单号函数 def generate_order_sn(self): # 当前时间+userid+随机数 from time import strftime from random import Random random_ins = Random() order_sn = "{time_str}{userid}{ranstr}".format(time_str=strftime("%Y%m%d%H%M%S"), userid=self.context["request"].user.id, ranstr=random_ins.randint(10, 99)) return order_sn # 对订单号进行生成 def validate(self, attrs): attrs["order_sn"] = self.generate_order_sn() return attrs class Meta: model = OrderInfo fields = "__all__"

订单视图全部代码

# 订单
class OrderViewset(mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin, mixins.DestroyModelMixin,
                   viewsets.GenericViewSet):
    """
    list:
        获取个人订单
    delete:
        删除订单
    create:
        新增订单
    """
    permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)
    authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)
    serializer_class = OrderSerializer

    def get_queryset(self):
        return OrderInfo.objects.filter(user=self.request.user)

    def get_serializer_class(self):
        if self.action == "retrieve":
            return OrderDetailSerializer
        return OrderSerializer

    def perform_create(self, serializer):
        order = serializer.save()
        shop_carts = ShoppingCart.objects.filter(user=self.request.user)
        for shop_cart in shop_carts:
            order_goods = OrderGoods()
            order_goods.goods = shop_cart.goods
            order_goods.goods_num = shop_cart.nums
            order_goods.order = order
            order_goods.save()

            shop_cart.delete()
        return order

 

以上是关于DRF 商城项目 - 购物( 购物车, 订单, 支付 )逻辑梳理的主要内容,如果未能解决你的问题,请参考以下文章

基于SSH的网上购物商城系统-JavaWeb项目-有源码

web day24 小项目练习图书商城, 用户,模块(注册,激活,登陆,退出),分类/图书模块

小程序项目实战—购物商城微信小程序

小程序项目实战—购物商城微信小程序

JavaWeb SSM SpringBoot+Vue+Redis实现的前后端分类版网上商城项目3(源码+sql可运行《精品毕设》)主要功能用户登录注册商城浏览购买订单购物车退货用户角色

《精品毕设》java ssm springboot二手物品商城项目(完整源码+sql+论文)主要功能:登录注册商品浏览分类模糊查找轮播图热销商品购物车订单订单流程控制用户管理