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

原文地址:https://www.cnblogs.com/shijieli/p/10745775.html

时间: 2024-12-10 00:46:27

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

第04项目:淘淘商城(SpringMVC+Spring+Mybatis)【第十一天】(购物车+订单)

https://pan.baidu.com/s/1bptYGAb#list/path=%2F&parentPath=%2Fsharelink389619878-229862621083040 第04项目:淘淘商城(SpringMVC+Spring+Mybatis)[第七天](redis缓存) 第04项目:淘淘商城(SpringMVC+Spring+Mybatis)[第八天](solr服务器搭建.搜索功能实现) 第04项目:淘淘商城(SpringMVC+Spring+Mybatis)[第九天](商

【SSH网上商城项目实战20】在线支付平台的介绍

之前已经完成了首页的显示,用户添加购物车,确认订单等功能,下面就是支付功能的开发了.用户确认了订单后会直接跳转到支付页面进行在线支付,在线支付需要第三方的接口,这一节主要介绍一些关于第三方支付的内容,从下一节开始,我们真正开发在线支付模块. 1. 在线支付介绍 在线支付是指卖方与买方通过因特网上的电子商务网站进行交易时,银行为其提供网上资金结算服务的一种业务.它为企业和个人提供了一个安全.快捷.方便的电子商务应用环境和网上资金结算工具.在线支付不仅帮助企业实现了销售款项的快速归集,缩短收款周期,

【SSH网上商城项目实战21】从Demo中看易宝支付的流程

这一节我们先写一个简单点的Demo来测试易宝支付的流程,熟悉这个流程后,再做实际的开发,因为是一个Demo,所以我没有考虑一些设计模式的东西,就是直接实现支付功能.实现支付功能需要易宝给我们提供的API.那么问题来了,使用第三方支付平台最主要的一件事就是获取该平台的API,我们首先得获取他们的API以及开发文档,然后才可以做进一步的开发. 1. 获取易宝的API 获取API的第一步,要在易宝上注册一个账号,这个账号是商家的账号,后面买家付款后,会将钱款存入该账号中,然后商家自己提取到银行卡,易宝

【SSH网上商城项目实战17】购物车基本功能的实现

上一节我们将商品的详细页面做完了,并使用了Hibernate的二级缓存加载详细页面来提高系统的性能.这节我们开始做购物车部分. 1. 添加新的表 首先我们向数据库中添加几张表:用户表.订单状态表.订单表(购物车表)以及购物项表.用户表中存有用户的基本信息,订单状态表中主要存储订单的状态,比如已发货这种,订单表主要存储用户的信息和订单的状态,所以跟用户表和订单状态表关联,购物项表存储某个商品以及所属的订单,所以跟商品表和订单表相关联.具体的表信息见下面的sql语句: /*=============

【SSH网上商城项目实战23】完成在线支付功能

上一节我们做好了支付页面的显示,从上一节支付页面显示的jsp代码中可以看出,当用户点击确认支付时,会跳到${shop}/pay_goBank.action的action,也就是说,提交后我们得在payAction中的goBank方法中处理一些逻辑(即21节demo中的那个流程图的逻辑),即获得明文,将明文加密成签名(密文)然后再去访问易宝服务器,易宝连接银行,完成支付.  但是考虑到MVC设计模式,我们会将上面这些业务逻辑放到Service层中处理,所以下面我们来实现21节中那个demo的支付逻

商城项目购物车的实现

目录 一,新建购物车的实体类Cart 二,在选择商品类型页面进行页面跳转 三,在controller进行添加商品对象并记入session 四,购物车页面接收购物车信息并展示 五,补充说明 @(商城项目购物车的实现) 首先是springboot和thymeleaf整合的demo项目,然后使用session实现购物车 一,新建购物车的实体类Cart public class Cart implements java.io.Serializable{ private Shangpin shangpin

网上商城项目学习の第三方支付流程

1.客户端[点击支付按钮](Browser→Server) ->参数:支付方式.订单SN 2.服务器[处理信息并响应](Server→Browser) ①接收参数,对支付方式.订单SN进行合法性校验 ②根据订单SN查询订单信息,即时计算出订单总价 ③将订单信息(订单SN.订单名称.订单总价...)+商户信息(key.token)+Sign(防伪签名-防止参数被篡改) ④将以上信息拼接成HTML表单.(表单会自动提交action到第三方支付平台接口地址) ->参数:包含订单信息的HTML 3.客

美多商城项目总结

注册 一般来说,注册模块并没有什么难点,但我在注册模块中写了两种验证码(普通验证码,短信验证码),普通验证码没有难度,但手机验证码需要在twilio网获取免费手机号,通过这个手机号给注册用户发短信验证码. 作用: 注册验证逻辑 短信+邮件+验证码 防止机器人重复注册 登录 我的登陆模块写了第三方登录,因为大多数网站都有第三方登陆,并且第三方登录可以省许多时间,比较方便. 关于三方登录的授权机制 在授权过程中大致有三个对象.一个是服务提供方(第三方网站).一个是用户(将资源放在服务提供方存放的对象

我的工作生涯中关于项目的需求和功能分析(商城项目)

时隔一年左右,我又来啦! 这次是一个商城项目,还是照例分为三个栏目,项目需求,需求解析和需求实现. 不具体到技术细节,只谈论如何实现,以及如何以更好的方式实现. 项目需求: 首先,需求就是一个商城,有分类,有商品,有购物车,有优惠券,有拼团和众筹,有订单管理的商城系统. 但是,这个商城有一个不太一般的功能,就是分销商功能. 分销商的功能有很多,也可以管理商品,但是分销商上传的商品需要运营人员审核后才可以上架,筛选购买总店的商品,分类和专题商品, 同时分销商还可以返佣和店铺折扣,也就是在分销商的店