青,取之于蓝而青于蓝;冰,水为之而寒于水。
——《荀子·劝学》

Github和Gitee代码同步更新
https://github.com/PythonWebProject/Django_Fresh_Ecommerce
https://gitee.com/Python_Web_Project/Django_Fresh_Ecommerce

一、首页功能完善

首页待完善的功能包括轮播图、新品尝鲜、系列商品等。

1.轮播图实现

轮播图包括3张图片,链接对应3个商品,先在apps/goods/serializers.py中定义序列化如下:

class BannerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Banner
        fields = '__all__'

再在views.py中定义视图如下:

class BannerViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
    '''
    list:
        轮播图列表
    '''
    queryset = Banner.objects.filter(is_delete=False).order_by('index')
    serializer_class = BannerSerializer

再在urls.py中定义路由如下:

# 配置轮播图路由
router.register(r'banners', BannerViewSet, basename='banners')

测试如下:
django banner api

可以看到,开始没有轮播图数据,经过在管理后台添加数据后,即同步数据。

前端src/views/index/banners.vue如下:

<swiper :options="swiperOption">
    <swiper-slide v-for="item in banners" :key="item.goods">
    <router-link :to="'/app/home/productDetail/'+item.goods" target = _blank> <img :src="item.image" alt="" /></router-link>
    </swiper-slide>
    <div class="swiper-pagination" slot="pagination"></div>
</swiper>

methods:{
    getBanner(){
    bannerGoods()
        .then((response)=> {
        console.log(response)
        //跳转到首页页response.body面
        this.banners = response.data
        })
        .catch(function (error) {
        console.log(error);
        });
    }
},

created(){
    this.getBanner();
}

在初始化时调用getBanner()方法,在调用bannerGoods接口请求数据,请求到的数据再通过for循环展示出来。

api.js修改如下:

//获取轮播图
export const bannerGoods = params => { return axios.get(`${local_host}/banners/`) }

此时再进行测试如下:
django banner vue

显然,请求的图片数据已从本地加载,并且点击轮播图片会调皮转到相应的商品链接。

2.新品功能开发

在定义商品模型时定义了is_new字段表示是否是新品,再实现新品功能时需要用到该字段,直接使用Goods接口并在filters.py过滤器中定义即可,如下:

class GoodsFilter(django_filters.rest_framework.FilterSet):
    '''商品过滤类'''
    name = django_filters.CharFilter(field_name="name", lookup_expr='contains')
    pricemin = django_filters.NumberFilter(field_name="market_price", lookup_expr='gte')
    pricemax = django_filters.NumberFilter(field_name="market_price", lookup_expr='lte')
    top_category = django_filters.NumberFilter(method='top_category_filter')

    def top_category_filter(self, queryset, name, value):
        '''自定义过滤'''
        return queryset.filter(Q(category_id=value)|Q(category__parent_category_id=value)|Q(category__parent_category__parent_category_id=value))

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

演示如下:
django new api

显然,在手动添加新品之后,新品数据即同步,请求的参数中包含is_new=true

前端src/views/index/news.vue如下:

<li class="prolist-cent clearfix have_num" v-for="item in newopro">
    <div class="prolist-l fl">
    <router-link :to="'/app/home/list/'+item.id"  target = _blank> <a  :title="item.name" class="imgBox">
    <img :src="item.goods_front_image" style="height: 158px;width: 158px;" class="zom" :alt="item.name">
    </a></router-link>
    </div>
    <div class="prolist-r fl">
        <h3 class="ft14 c333 bold">
        <router-link :to="'/app/home/list/'+item.id"  :title="item.name" target = _blank>{{item.name}}</router-link>
        </h3>
        <p><em class="c333"></em>{{item.goods_brief}}</p><div>
        <span class="p-price"><em class="fastbuy_price">¥{{item.shop_price}}元</em><del>原价:¥{{item.market_price}}元</del></span>
        <a href="" class="p-buy fr ibg">立即抢购</a>
        <span class="p-time fr">销量:{{item.sold_num}}件</span>
    </div>
    </div>
</li>

methods:{
    getOpro(){
        getGoods({
        "is_new":"true"
        })
        .then((response)=> {
            //跳转到首页页response.body面
            this.newopro = response.data.results
        })
        .catch(function (error) {
            console.log(error);
        });
    }
},

created(){
    this.getOpro();
}

可以看到,在初始化时,调用getOpro()方法,在调用getGoods接口时传入参数is_new,来获取新品,与之前获取商品调用的接口相同,获取到数据后通过for循环显示出来。

访问示意如下:
django new vue

3.商品系列分类展示功能

商品系列分类包括左侧的导航栏和右侧的商品列表,大类对应多个品牌、大类对应多个小类、大类对应多个商品,即包含3个一对多关系,在定义序列化时需要嵌套定义

为了实现嵌套,在定义GoodsCategoryBrand模型时需要指定related_name属性,如下:

class GoodsCategoryBrand(models.Model):
    '''品牌名'''
    category = models.ForeignKey(GoodsCategory, verbose_name='商品类目', related_name='brands', null=True, on_delete=models.SET_NULL)
    name = models.CharField(default='', max_length=30, verbose_name='品牌名', help_text='品牌名')
    desc = models.TextField(default='', max_length=200, verbose_name='品牌描述', help_text='品牌描述')
    image = models.ImageField(max_length=200, upload_to='brands/')
    add_time = models.DateTimeField(default=datetime.now, verbose_name=u'添加时间')
    is_delete = models.BooleanField(default=False, verbose_name='是否删除')

    class Meta:
        verbose_name = '品牌'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name

class IndexAd(models.Model):
    category = models.ForeignKey(GoodsCategory, verbose_name='商品类目', related_name='category', null=True, on_delete=models.SET_NULL)
    goods = models.ForeignKey(Goods, verbose_name='商品', related_name='goods', null=True, on_delete=models.SET_NULL)

    class Meta:
        verbose_name = '首页商品类别广告'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.goods.name

完成后需要进行数据映射。

为了在创建brand时只显示一级类别,在adminx.py中定义GoodsBrandAdmin类时重写了get_context()方法,其中获取到category字段只取category_type为1的数据,如下:

class GoodsBrandAdmin(object):
    list_display = ["category", "image", "name", "desc"]

    def get_context(self):
        context = super(GoodsBrandAdmin, self).get_context()
        if 'form' in context:
            context['form'].fields['category'].queryset = GoodsCategory.objects.filter(category_type=1)
        return context

定义序列化如下:

class BannerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Banner
        fields = '__all__'


class BrandSerializer(serializers.ModelSerializer):
    class Meta:
        model = GoodsCategoryBrand
        fields = '__all__'


class IndexCategorySerializer(serializers.ModelSerializer):
    brands = BrandSerializer(many=True)
    goods = serializers.SerializerMethodField()
    sub_cat = SecCategorySerializer(many=True)
    ad_goods = serializers.SerializerMethodField()

    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)
        return goods_serializer.data

    def get_ad_goods(self, obj):
        goods_json = {}
        ad_goods = IndexAd.objects.filter(category_id=obj.id)
        if ad_goods:
            good_instance = ad_goods[0].goods
            goods_json = GoodsSerializer(good_instance, many=False).data
        return goods_json

    class Meta:
        model = GoodsCategory
        fields = '__all__'

可以看到,定义了多个一对多的关系和一个一对一的关系,视图如下:

class IndexCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
    '''
    list:
        商品分类数据
    '''

    queryset = GoodsCategory.objects.filter(is_delete=False, is_tab=True, name__in=['生鲜食品', '酒水饮料'])
    serializer_class = IndexCategorySerializer

定义路由如下:

# 配置首页商品系列路由
router.register(r'indexgoods', IndexCategoryViewSet, basename='indexgoods')

进行测试:
django serial api

可以看到,再添加数据brands和ad_goods之前这两类数据为空,添加之后数据同步。

但是同时也可以看到,在商品的image属性的值即图片链接中未自动添加域名,这是因为进行嵌套序列化默认不会添加域名,需要给字段设置context属性,配置如下:

class IndexCategorySerializer(serializers.ModelSerializer):
    brands = BrandSerializer(many=True)
    goods = serializers.SerializerMethodField()
    sub_cat = SecCategorySerializer(many=True)
    ad_goods = serializers.SerializerMethodField()

    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

    def get_ad_goods(self, obj):
        goods_json = {}
        ad_goods = IndexAd.objects.filter(category_id=obj.id)
        if ad_goods:
            good_instance = ad_goods[0].goods
            goods_json = GoodsSerializer(good_instance, many=False, context={'request': self.context['request']}).data
        return goods_json

    class Meta:
        model = GoodsCategory
        fields = '__all__'

此时再查看如下:
django serial api image context

显然,已经将域名显示出来。

前端src/views/index/series-list.vue如下:

<div class="series_info">
    <div class="series_name name_hufu">
        <h2>{{items.name}}</h2>
    </div>
    <ul class="brand">

        <li v-for="brand in items.brands">
            <router-link :to="'/app/home/list/'+brand.id" >
            <a :title="brand.name" target="_blank">
                <img :src="brand.image" :alt="brand.name" style="display: inline;">
            </a>
            </router-link>
        </li>
    </ul>
    <div class="brand_cata">
        <router-link  v-for="label in items.sub_cat" :key="label.id" :title="label.name"   :to="'/app/home/list/'+label.id"  >
        {{label.name}}
        </router-link>
    </div>

</div>
    <div class="series_pic">
        <router-link :to="'/app/home/productDetail/'+items.ad_goods.id" target = _blank>
            <img :src="items.ad_goods.goods_front_image" width="340" height="400">
        </router-link>
    </div>

    <div class="pro_list">
        <ul class="cle">
            <li v-for="list in items.goods">
                    <router-link :to="'/app/home/productDetail/'+list.id" target = _blank>
                    <p class="pic">
                        <img :src="list.goods_front_image" style="display: inline;">
                        </p>
                    <h3>{{list.name}}</h3>
                    <p class="price">
                        ¥{{list.shop_price}}元
                    </p>
                </router-link>
            </li>

        </ul>
    </div>
</div>

methods:{
    getList(){
        queryCategorygoods()
        .then((response)=> {
            //跳转到首页页response.body面
            console.log(response)
            this.list = response.data
        })
        .catch(function (error) {
            console.log(error);
        });
    }
},
created(){
    this.getList();
}

在初始化时调用getList()方法,调用queryCategorygoods接口获取到数据后通过便利展示品牌和商品,并将广告商品展示出来。

api.js中接口如下:

//获取商品类别信息
export const queryCategorygoods = params => { return axios.get(`${local_host}/indexgoods/`) }

访问演示如下:
django serial vue

此时可以正常访问。

二、商品相关数量功能实现

1.商品点击数、收藏数功能实现

商品点击数通过在视图GoodsListViewSet中重写RetrieveModelMixin类的retrieve(request, *args, **kwargs)方法实现,每请求一次click_num加1,如下:

class GoodsListViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
    '''
    商品列表页,并实现分页、搜索、过滤、排序
    list:
        商品列表
    retrieve:
        商品详情
    '''

    queryset = Goods.objects.filter(is_delete=False).order_by('id')
    serializer_class = GoodsSerializer
    pagination_class = GoodsPagination
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    filter_class = GoodsFilter
    search_fields = ['name', 'goods_brief', 'goods_desc']
    ordering_fields = ['sold_num', 'shop_price']

    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)

收藏数需要在apps/user_operation/views.py中的GoodsListViewSet视图中重写CreateModelMixin类的perform_create(serializer)方法,如下:

class UserFavViewSet(mixins.CreateModelMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.DestroyModelMixin, viewsets.GenericViewSet):
    '''
    list:
        用户收藏列表
    create:
        创建用户收藏
    retrieve:
        用户收藏详情
    destroy:
        删除用户收藏
    '''
    permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
    serializer_class = UserFavSerializer
    authentication_classes = [JSONWebTokenAuthentication, SessionAuthentication]
    lookup_field = 'goods_id'

    def get_queryset(self):
        return UserFav.objects.filter(user=self.request.user, is_delete=False)

    def get_serializer_class(self):
        '''动态设置序列化'''
        if self.action == 'list':
            return UserFavDetailSerializer
        elif self.action == 'create':
            return UserFavSerializer
        return UserFavSerializer

    def perform_create(self, serializer):
        '''重写实现收藏数'''
        instance = serializer.save()
        goods = instance.goods
        goods.fav_num += 1
        goods.save()

演示如下:
django clicknum favnum api

显然,点击数在刷新页面后即加一,收藏数在用户未收藏的前提下收藏后也会加1。

收藏量除了通过以上方式实现,还可以通过信号量实现,这可以达到增加收藏和减少收藏的效果,注释掉之前在视图中实现增加收藏量的代码,在apps/user_operation下新建signals.py如下:

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

from .models import UserFav


@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()

apps/user_operation/apps.py完善如下:

from django.apps import AppConfig


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

    def ready(self):
        import user_operation.signals

演示如下:
django favnum api add delete

显然,达到了增减收藏量的效果。

2.商品库存量和销量功能实现

引起商品库存量变化的操作一般有3种:

  • 新增商品到购物车
  • 修改购物车数量
  • 删除购物车记录

显然,都与购物车有关,需要完善apps/trade/views.py中的ShoppingCartViewSet视图类,新增商品到购物车重写CreateModelMixin类的perform_create(serializer)方法,修改购物车数量重写UpdateModelMixin类的perform_update(serializer)方法,删除购物车记录重写DestroyModelMixin类的perform_destroy(instance)方法,如下:

class ShoppingCartViewSet(viewsets.ModelViewSet):
    '''
    list:
        购物车列表
    create:
        加入购物车
    update:
        购物车修改
    delete:
        删除购物车
    '''

    permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
    authentication_classes = [JSONWebTokenAuthentication, SessionAuthentication]
    serializer_class = ShoppingCartSerializer
    lookup_field = 'goods_id'

    def get_serializer_class(self):
        if self.action == 'list':
            return ShoppingCartDetailSerializer
        else:
            return ShoppingCartSerializer

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

    def perform_create(self, serializer):
        '''创建购物车更新库存量'''
        shop_cart = serializer.save()
        goods = shop_cart.goods
        goods.goods_num -= shop_cart.nums
        goods.save()

    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.filter(is_delete=False).get(id=serializer.instance.id)
        existed_nums = existed_record.nums
        saved_record = serializer.save()
        nums = saved_record.nums - existed_nums
        goods = saved_record.goods
        goods.goods_num -= nums
        goods.save()

演示如下:
django goodsnum api vue

显然,可以正常更新库存数量。

销售量一般是在支付成功后更新,所以在apps/trade/views.py中的AliPayView中实现:

class AliPayView(APIView):
    '''
    get:
        处理支付宝return_url请求
    post:
        处理支付宝notify_url请求
    '''

    alipay = AliPay(
        appid=ali_app_id,
        app_notify_url=None,
        app_private_key_string=open(app_private_key_path).read(),
        alipay_public_key_string=open(alipay_public_key_path).read(),
        sign_type="RSA2",
        debug=True,
    )

    def get(self, request):

        data = dict(request.GET.items())
        signature = data.pop("sign", None)
        print(data)
        success = self.alipay.verify(data, signature)
        order_sn = data.get('out_trade_no', None)
        print(success)
        trade_status = self.alipay.api_alipay_trade_query(out_trade_no=order_sn).get("trade_status", None)
        print(trade_status)
        if success and trade_status in ("TRADE_SUCCESS", "TRADE_FINISHED"):
            trade_no = data.get('trade_no', None)
            existed_orders = OrderInfo.objects.filter(order_sn=order_sn, is_delete=False)
            if existed_orders:
                for order in existed_orders:
                    order_goods = order.goods.all()
                    for order_good in order_goods:
                        goods = order_good.goods
                        goods.sold_num += order_good.goods_num
                        goods.save()
                    order.pay_status = trade_status
                    order.trade_no = trade_no
                    order.pay_time = datetime.now()
                    order.save()
                response = HttpResponseRedirect('http://127.0.0.1:8080/#/app/home/member/order')
                response.set_cookie('nextPath', 'pay', max_age=2)
                print('cookie', response.cookies)
                return response
        return HttpResponseRedirect('http://127.0.0.1:8080/#/app/shoppingcart/cart')

    def post(self, request):
        data = dict(request.POST.items())
        signature = data.pop("sign", None)
        success = self.alipay.verify(data, signature)
        order_sn = data.get('out_trade_no', None)
        trade_status = self.alipay.api_alipay_trade_query(out_trade_no=order_sn).get("trade_status", None)
        if success and trade_status in ("TRADE_SUCCESS", "TRADE_FINISHED"):
            trade_no = data.get('trade_no', None)
            existed_orders = OrderInfo.objects.filter(order_sn=order_sn, is_delete=False)
            print(len(existed_orders))
            if existed_orders:
                for order in existed_orders:
                    order_goods = order.goods.all()
                    for order_good in order_goods:
                        goods = order_good.goods
                        goods.sold_num += order_good.goods_num
                        goods.save()
                    order.pay_status = trade_status
                    order.trade_no = trade_no
                    order.pay_time = datetime.now()
                    order.save()
                response = HttpResponseRedirect('http://127.0.0.1:8080/#/app/home/member/order')
                response.set_cookie('nextPath', 'pay', max_age=2)
                print('cookie', response.cookies)
                return response
        return HttpResponseRedirect('http://127.0.0.1:8080/#/app/shoppingcart/cart')

演示如下:
django soldnum api vue
显然,已经实现了在提交订单并付款后,销售量更新。

三、DRF缓存设置

1.使用drf-extensions配置缓存

在一般情况下,将一些经常访问的数据放入缓存中,可以加快网页响应的速度。对于变化小的数据,将其保存到缓存中请求时直接获取的成本要源于每次请求再重新计算获取的成本,所以使用缓存是很有必要的。
Django支持的缓存包括Memcached数据库高速缓存文件系统缓存本地内存缓存虚拟缓存等,DRF的缓存机制建立在Django的基础上,并进行了一些优化,这里采用的是已经封装好的drf-extensions(DRF扩展),对DRF进行了很多方面的功能扩展,其中就包括缓存功能,Github地址为https://github.com/chibisov/drf-extensions,缓存caching的文档说明地址为http://chibisov.github.io/drf-extensions/docs/#caching

使用之前需要通过命令pip install drf-extensions -i https://pypi.douban.com/simple安装,我们使用的主要是CacheResponseMixin,主要适用于retrievelist方法,这主要是查询操作,对于新建、修改等操作一般是不能使用缓存的。

对于商品,apps/goods/views.py中的GoodsListViewSet,使之继承自CacheResponseMixin,即可实现缓存,如下:

class GoodsListViewSet(CacheResponseMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
    '''
    商品列表页,并实现分页、搜索、过滤、排序
    list:
        商品列表
    retrieve:
        商品详情
    '''

    queryset = Goods.objects.filter(is_delete=False).order_by('id')
    serializer_class = GoodsSerializer
    pagination_class = GoodsPagination
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    filter_class = GoodsFilter
    search_fields = ['name', 'goods_brief', 'goods_desc']
    ordering_fields = ['sold_num', 'shop_price']

    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)

进行测试如下:
django cache api

可以看出,第一次请求http://127.0.0.1:8000/goods/用时354毫秒,而第二次请求只用了13毫秒,显然,用时大大减少。

在很多时候,我们还需要设置缓存的过期时间,而不是无限制地保留缓存,此时需要在settings.py中进行配置如下:

# drf-extensions配置
REST_FRAMEWORK_EXTENSIONS = {
    'DEFAULT_CACHE_RESPONSE_TIMEOUT': 5
}

再进行测试如下:
django cache api timeout

可以看到,在Django重启后,第1次请求用时865毫秒,第2次请求在5秒内缓存还未过期,因此请求较快,为33毫秒,第3次请求已经超过5秒,缓存过期,所以重新请求,用时576毫秒。

对于一般情况而言,公开数据可以使用缓存,而需要验证才能访问的私有数据则尽量不要使用缓存,可以根据自己的需要对不同的数据进行缓存配置。

2.配置Redis缓存

之前配置的缓存都是使用Local Memory Cache即本地内存进行缓存的,Django重启之后就会消失,Redis作为backend进行了扩展和优化。

缓存应该考虑请求内容的格式是HTML还是json,请求是否包含参数等等问题,这可以根据Redis的键值观察出来,需要使用第三方库django-redis,通过命令pip install django-redis -i https://pypi.douban.com/simple安装即可。

使用前,需要在settings.py中进行配置如下:

# Redis缓存配置
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}

可以看到,需要在本地启动Redis服务。

在配置好Redis缓存后,连接Redis客户端,查询如下:

keys *

打印:

(empty list or set)

显然,此时Redis数据库为空,不存在数据,此时进行访问测试如下:
django cache redis different request

可以看到,相同的请求多次访问,Redis也不会增加数据,只有进行了不同类型的请求才会增加数据,django-redis根据请求的不同类型生成key,以区分不同类型的请求。

四、DRF通过throttling设置api的访问速率

因为爬虫的存在,如果爬虫的速率过快、不考虑网站的承受能力,会对服务器造成很大的压力,甚至影响正常用户的访问,因此需要限制访问速率,对关键数据、对性能要求高的数据进行限速。

DRF自带了限速功能,直接使用throttling进行限速即可实现,throttling与权限类似,它决定是否应授权请求。节流指示临时状态,并用于控制客户端可以向API发出的请求的速率,一般对未经身份验证的请求进行限制,而对于经过身份验证的请求则进行限制较少。

需要在settings.py中进行配置如下:

# DRF配置
REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema',
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ],
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle'
    ],
    'DEFAULT_THROTTLE_RATES': {
        'anon': '2/minute',
        'user': '3/minite'
    }
}

其中,AnonRateThrottle是对未登录用户的限制,通过IP判断;UserRateThrottle是对已登录用户的限制,通过Token或Session判断。

还需要在视图中进行配置,如下:

class GoodsListViewSet(CacheResponseMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
    '''
    商品列表页,并实现分页、搜索、过滤、排序
    list:
        商品列表
    retrieve:
        商品详情
    '''

    throttle_classes = [AnonRateThrottle, UserRateThrottle]
    queryset = Goods.objects.filter(is_delete=False).order_by('id')
    serializer_class = GoodsSerializer
    pagination_class = GoodsPagination
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    filter_class = GoodsFilter
    search_fields = ['name', 'goods_brief', 'goods_desc']
    ordering_fields = ['sold_num', 'shop_price']

    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)

此时进行访问测试如下:
django rate throttling

显然,限速成功,并且对于未登录和已登录的限制不一样。

Logo

前往低代码交流专区

更多推荐