1. 程式人生 > >Django+Vue打造購物網站(十)

Django+Vue打造購物網站(十)

首頁、商品數量、快取和限速功能開發

將環境切換為本地,vue也切換為本地

輪播圖

goods/serializers.py

class BannerSerializer(serializers.ModelSerializer):
    '''
    輪播圖
    '''

    class Meta:
        model = Banner
        fields = "__all__"

goods/views.py

class BannerViewset(mixins.ListModelMixin, viewsets.GenericViewSet):
    """
    首頁輪播圖
    """
    queryset = Banner.objects.all().order_by("index")
    serializer_class = BannerSerializer

urls.py

# 配置首頁輪播圖的url
router.register(r'banners', BannerViewset, base_name="banners")

新品推薦功能

在設計Goods model時候有一個欄位is_new

    is_new = models.BooleanField(default=False, verbose_name="是否新品")

實現這個介面只要在goods/filters/GoodsFilter裡面新增一個過濾就可以了

    class Meta:
        model = Goods
        fields = ['pricemin', 'pricemax','is_hot','is_new']

首頁商品分類顯示功能

goods/serializers.py

class BrandSerializer(serializers.ModelSerializer):
    '''
    大類下面的宣傳商標
    '''

    class Meta:
        model = GoodsCategoryBrand
        fields = "__all__"


class IndexCategorySerializer(serializers.ModelSerializer):
    # 某個大類的商標,可以有多個商標,一對多的關係
    brands = BrandSerializer(many=True)
    # good有一個外來鍵category,但這個外來鍵指向的是三級類,直接反向通過外來鍵category(三級類),取某個大類下面的商品是取不出來的
    goods = serializers.SerializerMethodField()
    # 在parent_category欄位中定義的related_name="sub_cat"
    # 取二級商品分類
    sub_cat = CategorySerializer2(many=True)
    # 廣告商品
    ad_goods = serializers.SerializerMethodField()

    def get_ad_goods(self, obj):
        goods_json = {}
        ad_goods = IndexAd.objects.filter(category_id=obj.id, )
        if ad_goods:
            # 取到這個商品Queryset[0]
            good_ins = ad_goods[0].goods
            # 在serializer裡面呼叫serializer的話,就要新增一個引數context(上下文request),
            # 否則圖片連結是不完整的
            # 巢狀serializer必須加
            # serializer返回的時候一定要加 “.data” ,這樣才是json資料
            goods_json = GoodsSerializer(good_ins, many=False, context={'request': self.context['request']}).data
        return goods_json

    # 自定義獲取方法
    def get_goods(self, obj):
        # 將這個商品相關父類子類等都可以進行匹配
        all_goods = Goods.objects.filter(Q(category_id=obj.id) | Q(category__parent_category_id=obj.id) | Q(
            category__parent_category__parent_category_id=obj.id))
        goods_serializer = GoodsSerializer(all_goods, many=True, context={'request': self.context['request']})
        return goods_serializer.data

    class Meta:
        model = GoodsCategory
        fields = "__all__"

goods/views.py

class IndexCategoryViewset(mixins.ListModelMixin, viewsets.GenericViewSet):
    """
    首頁商品分類資料
    """
    # 獲取is_tab=True(導航欄)裡面的分類下的商品資料
    queryset = GoodsCategory.objects.filter(is_tab=True, name__in=["生鮮食品", "酒水飲料"])
    serializer_class = IndexCategorySerializer

urls.py

# 首頁系列商品展示url
router.register(r'indexgoods', IndexCategoryViewset, base_name="indexgoods")

熱搜詞

goods/serializers.py

class HotWordsSerializer(serializers.ModelSerializer):
    class Meta:
        model = HotSearchWords
        fields = "__all__"

goods/views.py

class HotSearchsViewset(mixins.ListModelMixin, viewsets.GenericViewSet):
    """
    獲取熱搜詞列表
    """
    queryset = HotSearchWords.objects.all().order_by("-index")
    serializer_class = HotWordsSerializer

urls.py

# 首頁熱搜詞
router.register(r'hotsearchs', HotSearchsViewset, base_name="hotsearchs")

商品點選數和收藏數

GoodsListViewSet其中繼承了mixins.RetrieveModelMixin
只需要重寫retrieve方法即可

class GoodsListViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
    '''
    商品列表頁, 分頁, 過濾, 排序
    '''
    queryset = Goods.objects.all().order_by('id')
    serializer_class = GoodsSerializer
    pagination_class = GoodsPagination
    filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter)

    # authentication_classes = (TokenAuthentication,)
    # 自定義過濾器
    filter_class = GoodsFilter
    # 搜尋,預設模糊查詢
    search_fields = ('name', 'goods_brief')
    # 排序
    ordering_fields = ('sold_num', 'shop_price')

    # 商品點選數 + 1
    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        instance.click_num += 1
        instance.save()
        serializer = self.get_serializer(instance)
        return Response(serializer.data)

UserFavViewset繼承了mixins.CreateModelMixin
重寫perform_create方法即可

class UserFavViewset(viewsets.GenericViewSet, mixins.ListModelMixin, mixins.CreateModelMixin, mixins.DestroyModelMixin):
    '''
    list:
        獲取使用者的所有收藏
    create:
        新增收藏
    destroy:
        取消收藏
    '''
    # permission是用來做許可權判斷的
    # IsAuthenticated:必須登入使用者;IsOwnerOrReadOnly:必須是當前登入的使用者
    permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)
    # auth使用來做使用者認證的
    authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)
    # 搜尋的欄位
    lookup_field = 'goods_id'

    def get_queryset(self):
        # 只能檢視當前登入使用者的收藏,不會獲取所有使用者的收藏
        return UserFav.objects.filter(user=self.request.user)

    # 動態選擇serializer
    def get_serializer_class(self):
        if self.action == "list":
            return UserFavDetailSerializer
        elif self.action == "create":
            return UserFavSerializer
        return UserFavSerializer

    # 使用者收藏的商品數量+1
    def perform_create(self, serializer):
        instance = serializer.save()
        # 這裡instance相當於UserFav model,通過它找到goods
        goods = instance.goods
        goods.fav_num += 1
        goods.save()

用訊號量實現收藏數變化
delete和create的時候django model都會發送一個訊號量出來,用訊號量的方式程式碼分離性更好

user_operation/signals.py

from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver

from user_operation.models import UserFav


# post_save : model變化方式
# sender : 變動的model
@receiver(post_save, sender=UserFav)
def create_userfav(sender, instance=None, created=False, **kwargs):
    if created:
        goods = instance.goods
        goods.fav_num += 1
        goods.save()


@receiver(post_delete, sender=UserFav)
def delete_userfav(sender, instance=None, created=False, **kwargs):
    goods = instance.goods
    goods.fav_num -= 1
    goods.save()

user_operation/apps.py

from django.apps import AppConfig


class UserOperationConfig(AppConfig):
    name = 'user_operation'
    verbose_name = "使用者操作管理"

    def ready(self):
        import user_operation.signals

商品庫存和銷量修改

引起商品庫存數量變化的行為:

  • 新增商品到購物車
  • 修改購物車數量
  • 刪除購物車記錄

trade/views.py

class ShoppingCartViewset(viewsets.ModelViewSet):
    """
    購物車功能
    list:
        獲取購物車詳情
    create:
        加入購物車
    delete:
        刪除購物記錄
    """
    permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)
    authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)

    # serializer_class = ShopCartSerializer
    lookup_field = "goods_id"

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

    def get_serializer_class(self):
        if self.action == 'list':
            return ShopCartDetailSerializer
        else:
            return ShopCartSerializer

    # 庫存數-n
    def perform_create(self, serializer):
        shop_cart = serializer.save()
        goods = shop_cart.goods
        goods.goods_num -= shop_cart.nums
        goods.save()

    # 庫存數+n
    def perform_destroy(self, instance):
        goods = instance.goods
        goods.goods_num += instance.nums
        goods.save()
        instance.delete()

    # 更新庫存,修改可能是增加頁可能是減少
    def perform_update(self, serializer):
        # 首先獲取修改之前的庫存數量
        existed_record = ShoppingCart.objects.get(id=serializer.instance.id)
        existed_nums = existed_record.nums
        # 先儲存之前的資料existed_nums
        saved_record = serializer.save()
        # 變化的數量
        nums = saved_record.nums - existed_nums
        goods = saved_record.goods
        goods.goods_num -= nums
        goods.save()

商品的銷量只有在支付成功後才會 +n
trade/views.py
AlipayView/post方法

            # 查詢資料庫中訂單記錄
            existed_orders = OrderInfo.objects.filter(order_sn=order_sn)
            for existed_order in existed_orders:
                # 訂單商品項
                order_goods = existed_order.goods.all()
                # 商品銷量增加訂單中數值
                for order_good in order_goods:
                    goods = order_good.goods
                    goods.sold_num += order_good.goods_num
                    goods.save()

                # 更新訂單狀態
                existed_order.pay_status = trade_status
                existed_order.trade_no = trade_no
                existed_order.pay_time = datetime.now()
                existed_order.save()
            # 需要返回一個'success'給支付寶,如果不返回,支付寶會一直髮送訂單支付成功的訊息
            return Response("success")

drf快取

http://chibisov.github.io/drf-extensions/docs/#caching

pip install drf-extensions

簡單使用
在GoodsListViewSet中新增快取功能

from rest_framework_extensions.cache.mixins import CacheResponseMixin

# CacheResponseMixin一定要放在第一個位置

class GoodsListViewSet(CacheResponseMixin,mixins.ListModelMixin, mixins.RetrieveModelMixin,viewsets.GenericViewSet):

設定過期時間,settings裡面

# 快取配置
REST_FRAMEWORK_EXTENSIONS = {
    'DEFAULT_CACHE_RESPONSE_TIMEOUT': 60*60*8   # 多少秒過期,時間自己可以隨便設定
}

這個快取使用的是記憶體,每次重啟之後就會失效

drf配置redis快取

https://django-redis-chs.readthedocs.io/zh_CN/latest/

drf的throttle設定api的訪問速率

針對爬蟲
http://www.django-rest-framework.org/api-guide/throttling/

settings中配置

REST_FRAMEWORK = {
    # 限速設定
    'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.AnonRateThrottle',  # 未登陸使用者
        'rest_framework.throttling.UserRateThrottle'  # 登陸使用者
    ),
    'DEFAULT_THROTTLE_RATES': {
        'anon': '10/minute',  # 每分鐘可以請求n次
        'user': '30/minute'  # 每分鐘可以請求n次
    }
}

goods/views.py中使用

from rest_framework.throttling import UserRateThrottle,AnonRateThrottle

class GoodsListViewSet(CacheResponseMixin,mixins.ListModelMixin, mixins.RetrieveModelMixin,viewsets.GenericViewSet):
  .
  .
  throttle_classes = (UserRateThrottle, AnonRateThrottle)