1. 程式人生 > >DRF 商城項目 - 購物( 購物車, 訂單, 支付 )邏輯梳理

DRF 商城項目 - 購物( 購物車, 訂單, 支付 )邏輯梳理

troy 訂單號 和數 serialize user object read 數據 sta

購物車

購物車模型

購物車中的數據不應該重復. 即對相同商品的增加應該是對購買數量的處理而不是增加一條記錄

因此對此進行聯合唯一索引, 但是也因此存在一些問題

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 商城項目 - 購物( 購物車, 訂單, 支付 )邏輯梳理