青,取之于蓝而青于蓝;冰,水为之而寒于水。 ——《荀子·劝学》
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中定义序列化如下:
1class BannerSerializer(serializers.ModelSerializer): 2 class Meta: 3 model = Banner 4 fields = '__all__'
再在views.py中定义视图如下:
1class BannerViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): 2 ''' 3 list: 4 轮播图列表 5 ''' 6 queryset = Banner.objects.filter(is_delete=False).order_by('index') 7 serializer_class = BannerSerializer
再在urls.py中定义路由如下:
1# 配置轮播图路由 2router.register(r'banners', BannerViewSet, basename='banners')
测试如下:

可以看到,开始没有轮播图数据,经过在管理后台添加数据后,即同步数据。
前端src/views/index/banners.vue如下:
1<swiper :options="swiperOption"> 2 <swiper-slide v-for="item in banners" :key="item.goods"> 3 <router-link :to="'/app/home/productDetail/'+item.goods" target = _blank> <img :src="item.image" alt="" /></router-link> 4 </swiper-slide> 5 <div class="swiper-pagination" slot="pagination"></div> 6</swiper> 7 8methods:{ 9 getBanner(){ 10 bannerGoods() 11 .then((response)=> { 12 console.log(response) 13 //跳转到首页页response.body面 14 this.banners = response.data 15 }) 16 .catch(function (error) { 17 console.log(error); 18 }); 19 } 20}, 21 22created(){ 23 this.getBanner(); 24}
在初始化时调用getBanner()方法,在调用bannerGoods接口请求数据,请求到的数据再通过for循环展示出来。
api.js修改如下:
1//获取轮播图 2export const bannerGoods = params => { return axios.get(`${local_host}/banners/`) }
此时再进行测试如下:

显然,请求的图片数据已从本地加载,并且点击轮播图片会调皮转到相应的商品链接。
2.新品功能开发
在定义商品模型时定义了is_new字段表示是否是新品,再实现新品功能时需要用到该字段,直接使用Goods接口并在filters.py过滤器中定义即可,如下:
1class GoodsFilter(django_filters.rest_framework.FilterSet): 2 '''商品过滤类''' 3 name = django_filters.CharFilter(field_name="name", lookup_expr='contains') 4 pricemin = django_filters.NumberFilter(field_name="market_price", lookup_expr='gte') 5 pricemax = django_filters.NumberFilter(field_name="market_price", lookup_expr='lte') 6 top_category = django_filters.NumberFilter(method='top_category_filter') 7 8 def top_category_filter(self, queryset, name, value): 9 '''自定义过滤''' 10 return queryset.filter(Q(category_id=value)|Q(category__parent_category_id=value)|Q(category__parent_category__parent_category_id=value)) 11 12 class Meta: 13 model = Goods 14 fields = ['name', 'pricemin', 'pricemax', 'is_hot', 'is_new']
演示如下:

显然,在手动添加新品之后,新品数据即同步,请求的参数中包含is_new=true。
前端src/views/index/news.vue如下:
1<li class="prolist-cent clearfix have_num" v-for="item in newopro"> 2 <div class="prolist-l fl"> 3 <router-link :to="'/app/home/list/'+item.id" target = _blank> <a :title="item.name" class="imgBox"> 4 <img :src="item.goods_front_image" style="height: 158px;width: 158px;" class="zom" :alt="item.name"> 5 </a></router-link> 6 </div> 7 <div class="prolist-r fl"> 8 <h3 class="ft14 c333 bold"> 9 <router-link :to="'/app/home/list/'+item.id" :title="item.name" target = _blank>{{item.name}}</router-link> 10 </h3> 11 <p><em class="c333"></em>{{item.goods_brief}}</p><div> 12 <span class="p-price"><em class="fastbuy_price">¥{{item.shop_price}}元</em><del>原价:¥{{item.market_price}}元</del></span> 13 <a href="" class="p-buy fr ibg">立即抢购</a> 14 <span class="p-time fr">销量:{{item.sold_num}}件</span> 15 </div> 16 </div> 17</li> 18 19methods:{ 20 getOpro(){ 21 getGoods({ 22 "is_new":"true" 23 }) 24 .then((response)=> { 25 //跳转到首页页response.body面 26 this.newopro = response.data.results 27 }) 28 .catch(function (error) { 29 console.log(error); 30 }); 31 } 32}, 33 34created(){ 35 this.getOpro(); 36}
可以看到,在初始化时,调用getOpro()方法,在调用getGoods接口时传入参数is_new,来获取新品,与之前获取商品调用的接口相同,获取到数据后通过for循环显示出来。
访问示意如下:

3.商品系列分类展示功能
商品系列分类包括左侧的导航栏和右侧的商品列表,大类对应多个品牌、大类对应多个小类、大类对应多个商品,即包含3个一对多关系,在定义序列化时需要嵌套定义。
为了实现嵌套,在定义GoodsCategoryBrand模型时需要指定related_name属性,如下:
1class GoodsCategoryBrand(models.Model): 2 '''品牌名''' 3 category = models.ForeignKey(GoodsCategory, verbose_name='商品类目', related_name='brands', null=True, on_delete=models.SET_NULL) 4 name = models.CharField(default='', max_length=30, verbose_name='品牌名', help_text='品牌名') 5 desc = models.TextField(default='', max_length=200, verbose_name='品牌描述', help_text='品牌描述') 6 image = models.ImageField(max_length=200, upload_to='brands/') 7 add_time = models.DateTimeField(default=datetime.now, verbose_name=u'添加时间') 8 is_delete = models.BooleanField(default=False, verbose_name='是否删除') 9 10 class Meta: 11 verbose_name = '品牌' 12 verbose_name_plural = verbose_name 13 14 def __str__(self): 15 return self.name 16 17class IndexAd(models.Model): 18 category = models.ForeignKey(GoodsCategory, verbose_name='商品类目', related_name='category', null=True, on_delete=models.SET_NULL) 19 goods = models.ForeignKey(Goods, verbose_name='商品', related_name='goods', null=True, on_delete=models.SET_NULL) 20 21 class Meta: 22 verbose_name = '首页商品类别广告' 23 verbose_name_plural = verbose_name 24 25 def __str__(self): 26 return self.goods.name
完成后需要进行数据映射。
为了在创建brand时只显示一级类别,在adminx.py中定义GoodsBrandAdmin类时重写了get_context()方法,其中获取到category字段只取category_type为1的数据,如下:
1class GoodsBrandAdmin(object): 2 list_display = ["category", "image", "name", "desc"] 3 4 def get_context(self): 5 context = super(GoodsBrandAdmin, self).get_context() 6 if 'form' in context: 7 context['form'].fields['category'].queryset = GoodsCategory.objects.filter(category_type=1) 8 return context
定义序列化如下:
1class BannerSerializer(serializers.ModelSerializer): 2 class Meta: 3 model = Banner 4 fields = '__all__' 5 6 7class BrandSerializer(serializers.ModelSerializer): 8 class Meta: 9 model = GoodsCategoryBrand 10 fields = '__all__' 11 12 13class IndexCategorySerializer(serializers.ModelSerializer): 14 brands = BrandSerializer(many=True) 15 goods = serializers.SerializerMethodField() 16 sub_cat = SecCategorySerializer(many=True) 17 ad_goods = serializers.SerializerMethodField() 18 19 def get_goods(self, obj): 20 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)) 21 goods_serializer = GoodsSerializer(all_goods, many=True) 22 return goods_serializer.data 23 24 def get_ad_goods(self, obj): 25 goods_json = {} 26 ad_goods = IndexAd.objects.filter(category_id=obj.id) 27 if ad_goods: 28 good_instance = ad_goods[0].goods 29 goods_json = GoodsSerializer(good_instance, many=False).data 30 return goods_json 31 32 class Meta: 33 model = GoodsCategory 34 fields = '__all__'
可以看到,定义了多个一对多的关系和一个一对一的关系,视图如下:
1class IndexCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): 2 ''' 3 list: 4 商品分类数据 5 ''' 6 7 queryset = GoodsCategory.objects.filter(is_delete=False, is_tab=True, name__in=['生鲜食品', '酒水饮料']) 8 serializer_class = IndexCategorySerializer
定义路由如下:
1# 配置首页商品系列路由 2router.register(r'indexgoods', IndexCategoryViewSet, basename='indexgoods')
进行测试:

可以看到,再添加数据brands和ad_goods之前这两类数据为空,添加之后数据同步。
但是同时也可以看到,在商品的image属性的值即图片链接中未自动添加域名,这是因为进行嵌套序列化默认不会添加域名,需要给字段设置context属性,配置如下:
1class IndexCategorySerializer(serializers.ModelSerializer): 2 brands = BrandSerializer(many=True) 3 goods = serializers.SerializerMethodField() 4 sub_cat = SecCategorySerializer(many=True) 5 ad_goods = serializers.SerializerMethodField() 6 7 def get_goods(self, obj): 8 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)) 9 goods_serializer = GoodsSerializer(all_goods, many=True, context={'request': self.context['request']}) 10 return goods_serializer.data 11 12 def get_ad_goods(self, obj): 13 goods_json = {} 14 ad_goods = IndexAd.objects.filter(category_id=obj.id) 15 if ad_goods: 16 good_instance = ad_goods[0].goods 17 goods_json = GoodsSerializer(good_instance, many=False, context={'request': self.context['request']}).data 18 return goods_json 19 20 class Meta: 21 model = GoodsCategory 22 fields = '__all__'
此时再查看如下:

显然,已经将域名显示出来。
前端src/views/index/series-list.vue如下:
1<div class="series_info"> 2 <div class="series_name name_hufu"> 3 <h2>{{items.name}}</h2> 4 </div> 5 <ul class="brand"> 6 7 <li v-for="brand in items.brands"> 8 <router-link :to="'/app/home/list/'+brand.id" > 9 <a :title="brand.name" target="_blank"> 10 <img :src="brand.image" :alt="brand.name" style="display: inline;"> 11 </a> 12 </router-link> 13 </li> 14 </ul> 15 <div class="brand_cata"> 16 <router-link v-for="label in items.sub_cat" :key="label.id" :title="label.name" :to="'/app/home/list/'+label.id" > 17 {{label.name}} 18 </router-link> 19 </div> 20 21</div> 22 <div class="series_pic"> 23 <router-link :to="'/app/home/productDetail/'+items.ad_goods.id" target = _blank> 24 <img :src="items.ad_goods.goods_front_image" width="340" height="400"> 25 </router-link> 26 </div> 27 28 <div class="pro_list"> 29 <ul class="cle"> 30 <li v-for="list in items.goods"> 31 <router-link :to="'/app/home/productDetail/'+list.id" target = _blank> 32 <p class="pic"> 33 <img :src="list.goods_front_image" style="display: inline;"> 34 </p> 35 <h3>{{list.name}}</h3> 36 <p class="price"> 37 ¥{{list.shop_price}}元 38 </p> 39 </router-link> 40 </li> 41 42 </ul> 43 </div> 44</div> 45 46methods:{ 47 getList(){ 48 queryCategorygoods() 49 .then((response)=> { 50 //跳转到首页页response.body面 51 console.log(response) 52 this.list = response.data 53 }) 54 .catch(function (error) { 55 console.log(error); 56 }); 57 } 58}, 59created(){ 60 this.getList(); 61}
在初始化时调用getList()方法,调用queryCategorygoods接口获取到数据后通过便利展示品牌和商品,并将广告商品展示出来。
api.js中接口如下:
1//获取商品类别信息 2export const queryCategorygoods = params => { return axios.get(`${local_host}/indexgoods/`) }
访问演示如下:

此时可以正常访问。
二、商品相关数量功能实现
1.商品点击数、收藏数功能实现
商品点击数通过在视图GoodsListViewSet中重写RetrieveModelMixin类的retrieve(request, *args, **kwargs)方法实现,每请求一次click_num加1,如下:
1class GoodsListViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet): 2 ''' 3 商品列表页,并实现分页、搜索、过滤、排序 4 list: 5 商品列表 6 retrieve: 7 商品详情 8 ''' 9 10 queryset = Goods.objects.filter(is_delete=False).order_by('id') 11 serializer_class = GoodsSerializer 12 pagination_class = GoodsPagination 13 filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] 14 filter_class = GoodsFilter 15 search_fields = ['name', 'goods_brief', 'goods_desc'] 16 ordering_fields = ['sold_num', 'shop_price'] 17 18 def retrieve(self, request, *args, **kwargs): 19 '''重写实现点击数''' 20 instance = self.get_object() 21 instance.click_num += 1 22 instance.save() 23 serializer = self.get_serializer(instance) 24 return Response(serializer.data)
收藏数需要在apps/user_operation/views.py中的GoodsListViewSet视图中重写CreateModelMixin类的perform_create(serializer)方法,如下:
1class UserFavViewSet(mixins.CreateModelMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.DestroyModelMixin, viewsets.GenericViewSet): 2 ''' 3 list: 4 用户收藏列表 5 create: 6 创建用户收藏 7 retrieve: 8 用户收藏详情 9 destroy: 10 删除用户收藏 11 ''' 12 permission_classes = [IsAuthenticated, IsOwnerOrReadOnly] 13 serializer_class = UserFavSerializer 14 authentication_classes = [JSONWebTokenAuthentication, SessionAuthentication] 15 lookup_field = 'goods_id' 16 17 def get_queryset(self): 18 return UserFav.objects.filter(user=self.request.user, is_delete=False) 19 20 def get_serializer_class(self): 21 '''动态设置序列化''' 22 if self.action == 'list': 23 return UserFavDetailSerializer 24 elif self.action == 'create': 25 return UserFavSerializer 26 return UserFavSerializer 27 28 def perform_create(self, serializer): 29 '''重写实现收藏数''' 30 instance = serializer.save() 31 goods = instance.goods 32 goods.fav_num += 1 33 goods.save()
演示如下:

显然,点击数在刷新页面后即加一,收藏数在用户未收藏的前提下收藏后也会加1。
收藏量除了通过以上方式实现,还可以通过信号量实现,这可以达到增加收藏和减少收藏的效果,注释掉之前在视图中实现增加收藏量的代码,在apps/user_operation下新建signals.py如下:
1from django.db.models.signals import post_save, post_delete 2from django.dispatch import receiver 3from rest_framework.authtoken.models import Token 4 5from .models import UserFav 6 7 8@receiver(post_save, sender=UserFav) 9def create_userfav(sender, instance=None, created=False, **kwargs): 10 if created: 11 goods = instance.goods 12 goods.fav_num += 1 13 goods.save() 14 15 16@receiver(post_delete, sender=UserFav) 17def delete_userfav(sender, instance=None, created=False, **kwargs): 18 goods = instance.goods 19 goods.fav_num -= 1 20 goods.save()
apps/user_operation/apps.py完善如下:
1from django.apps import AppConfig 2 3 4class UserOperationConfig(AppConfig): 5 name = 'user_operation' 6 verbose_name = '用户操作管理' 7 8 def ready(self): 9 import user_operation.signals 10
演示如下:

显然,达到了增减收藏量的效果。
2.商品库存量和销量功能实现
引起商品库存量变化的操作一般有3种:
- 新增商品到购物车
- 修改购物车数量
- 删除购物车记录
显然,都与购物车有关,需要完善apps/trade/views.py中的ShoppingCartViewSet视图类,新增商品到购物车重写CreateModelMixin类的perform_create(serializer)方法,修改购物车数量重写UpdateModelMixin类的perform_update(serializer)方法,删除购物车记录重写DestroyModelMixin类的perform_destroy(instance)方法,如下:
1class ShoppingCartViewSet(viewsets.ModelViewSet): 2 ''' 3 list: 4 购物车列表 5 create: 6 加入购物车 7 update: 8 购物车修改 9 delete: 10 删除购物车 11 ''' 12 13 permission_classes = [IsAuthenticated, IsOwnerOrReadOnly] 14 authentication_classes = [JSONWebTokenAuthentication, SessionAuthentication] 15 serializer_class = ShoppingCartSerializer 16 lookup_field = 'goods_id' 17 18 def get_serializer_class(self): 19 if self.action == 'list': 20 return ShoppingCartDetailSerializer 21 else: 22 return ShoppingCartSerializer 23 24 def get_queryset(self): 25 return ShoppingCart.objects.filter(user=self.request.user, is_delete=False) 26 27 def perform_create(self, serializer): 28 '''创建购物车更新库存量''' 29 shop_cart = serializer.save() 30 goods = shop_cart.goods 31 goods.goods_num -= shop_cart.nums 32 goods.save() 33 34 def perform_destroy(self, instance): 35 '''删除购物车更新库存量''' 36 goods = instance.goods 37 goods.goods_num += instance.nums 38 goods.save() 39 instance.delete() 40 41 def perform_update(self, serializer): 42 '''修改购物车更新库存量''' 43 existed_record = ShoppingCart.objects.filter(is_delete=False).get(id=serializer.instance.id) 44 existed_nums = existed_record.nums 45 saved_record = serializer.save() 46 nums = saved_record.nums - existed_nums 47 goods = saved_record.goods 48 goods.goods_num -= nums 49 goods.save()
演示如下:

显然,可以正常更新库存数量。
销售量一般是在支付成功后更新,所以在apps/trade/views.py中的AliPayView中实现:
1class AliPayView(APIView): 2 ''' 3 get: 4 处理支付宝return_url请求 5 post: 6 处理支付宝notify_url请求 7 ''' 8 9 alipay = AliPay( 10 appid=ali_app_id, 11 app_notify_url=None, 12 app_private_key_string=open(app_private_key_path).read(), 13 alipay_public_key_string=open(alipay_public_key_path).read(), 14 sign_type="RSA2", 15 debug=True, 16 ) 17 18 def get(self, request): 19 20 data = dict(request.GET.items()) 21 signature = data.pop("sign", None) 22 print(data) 23 success = self.alipay.verify(data, signature) 24 order_sn = data.get('out_trade_no', None) 25 print(success) 26 trade_status = self.alipay.api_alipay_trade_query(out_trade_no=order_sn).get("trade_status", None) 27 print(trade_status) 28 if success and trade_status in ("TRADE_SUCCESS", "TRADE_FINISHED"): 29 trade_no = data.get('trade_no', None) 30 existed_orders = OrderInfo.objects.filter(order_sn=order_sn, is_delete=False) 31 if existed_orders: 32 for order in existed_orders: 33 order_goods = order.goods.all() 34 for order_good in order_goods: 35 goods = order_good.goods 36 goods.sold_num += order_good.goods_num 37 goods.save() 38 order.pay_status = trade_status 39 order.trade_no = trade_no 40 order.pay_time = datetime.now() 41 order.save() 42 response = HttpResponseRedirect('http://127.0.0.1:8080/#/app/home/member/order') 43 response.set_cookie('nextPath', 'pay', max_age=2) 44 print('cookie', response.cookies) 45 return response 46 return HttpResponseRedirect('http://127.0.0.1:8080/#/app/shoppingcart/cart') 47 48 def post(self, request): 49 data = dict(request.POST.items()) 50 signature = data.pop("sign", None) 51 success = self.alipay.verify(data, signature) 52 order_sn = data.get('out_trade_no', None) 53 trade_status = self.alipay.api_alipay_trade_query(out_trade_no=order_sn).get("trade_status", None) 54 if success and trade_status in ("TRADE_SUCCESS", "TRADE_FINISHED"): 55 trade_no = data.get('trade_no', None) 56 existed_orders = OrderInfo.objects.filter(order_sn=order_sn, is_delete=False) 57 print(len(existed_orders)) 58 if existed_orders: 59 for order in existed_orders: 60 order_goods = order.goods.all() 61 for order_good in order_goods: 62 goods = order_good.goods 63 goods.sold_num += order_good.goods_num 64 goods.save() 65 order.pay_status = trade_status 66 order.trade_no = trade_no 67 order.pay_time = datetime.now() 68 order.save() 69 response = HttpResponseRedirect('http://127.0.0.1:8080/#/app/home/member/order') 70 response.set_cookie('nextPath', 'pay', max_age=2) 71 print('cookie', response.cookies) 72 return response 73 return HttpResponseRedirect('http://127.0.0.1:8080/#/app/shoppingcart/cart')
演示如下:
显然,已经实现了在提交订单并付款后,销售量更新。
三、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,主要适用于retrieve和list方法,这主要是查询操作,对于新建、修改等操作一般是不能使用缓存的。
对于商品,apps/goods/views.py中的GoodsListViewSet,使之继承自CacheResponseMixin,即可实现缓存,如下:
1class GoodsListViewSet(CacheResponseMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet): 2 ''' 3 商品列表页,并实现分页、搜索、过滤、排序 4 list: 5 商品列表 6 retrieve: 7 商品详情 8 ''' 9 10 queryset = Goods.objects.filter(is_delete=False).order_by('id') 11 serializer_class = GoodsSerializer 12 pagination_class = GoodsPagination 13 filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] 14 filter_class = GoodsFilter 15 search_fields = ['name', 'goods_brief', 'goods_desc'] 16 ordering_fields = ['sold_num', 'shop_price'] 17 18 def retrieve(self, request, *args, **kwargs): 19 '''重写实现点击数''' 20 instance = self.get_object() 21 instance.click_num += 1 22 instance.save() 23 serializer = self.get_serializer(instance) 24 return Response(serializer.data)
进行测试如下:

可以看出,第一次请求http://127.0.0.1:8000/goods/用时354毫秒,而第二次请求只用了13毫秒,显然,用时大大减少。
在很多时候,我们还需要设置缓存的过期时间,而不是无限制地保留缓存,此时需要在settings.py中进行配置如下:
1# drf-extensions配置 2REST_FRAMEWORK_EXTENSIONS = { 3 'DEFAULT_CACHE_RESPONSE_TIMEOUT': 5 4}
再进行测试如下:

可以看到,在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中进行配置如下:
1# Redis缓存配置 2CACHES = { 3 "default": { 4 "BACKEND": "django_redis.cache.RedisCache", 5 "LOCATION": "redis://127.0.0.1:6379", 6 "OPTIONS": { 7 "CLIENT_CLASS": "django_redis.client.DefaultClient", 8 } 9 } 10}
可以看到,需要在本地启动Redis服务。
在配置好Redis缓存后,连接Redis客户端,查询如下:
1keys *
打印:
1(empty list or set)
显然,此时Redis数据库为空,不存在数据,此时进行访问测试如下:

可以看到,相同的请求多次访问,Redis也不会增加数据,只有进行了不同类型的请求才会增加数据,django-redis根据请求的不同类型生成key,以区分不同类型的请求。
四、DRF通过throttling设置api的访问速率
因为爬虫的存在,如果爬虫的速率过快、不考虑网站的承受能力,会对服务器造成很大的压力,甚至影响正常用户的访问,因此需要限制访问速率,对关键数据、对性能要求高的数据进行限速。
DRF自带了限速功能,直接使用throttling进行限速即可实现,throttling与权限类似,它决定是否应授权请求。节流指示临时状态,并用于控制客户端可以向API发出的请求的速率,一般对未经身份验证的请求进行限制,而对于经过身份验证的请求则进行限制较少。
需要在settings.py中进行配置如下:
1# DRF配置 2REST_FRAMEWORK = { 3 'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'], 4 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema', 5 'DEFAULT_AUTHENTICATION_CLASSES': [ 6 'rest_framework.authentication.BasicAuthentication', 7 'rest_framework.authentication.SessionAuthentication', 8 ], 9 'DEFAULT_THROTTLE_CLASSES': [ 10 'rest_framework.throttling.AnonRateThrottle', 11 'rest_framework.throttling.UserRateThrottle' 12 ], 13 'DEFAULT_THROTTLE_RATES': { 14 'anon': '2/minute', 15 'user': '3/minite' 16 } 17}
其中,AnonRateThrottle是对未登录用户的限制,通过IP判断;UserRateThrottle是对已登录用户的限制,通过Token或Session判断。
还需要在视图中进行配置,如下:
1class GoodsListViewSet(CacheResponseMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet): 2 ''' 3 商品列表页,并实现分页、搜索、过滤、排序 4 list: 5 商品列表 6 retrieve: 7 商品详情 8 ''' 9 10 throttle_classes = [AnonRateThrottle, UserRateThrottle] 11 queryset = Goods.objects.filter(is_delete=False).order_by('id') 12 serializer_class = GoodsSerializer 13 pagination_class = GoodsPagination 14 filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] 15 filter_class = GoodsFilter 16 search_fields = ['name', 'goods_brief', 'goods_desc'] 17 ordering_fields = ['sold_num', 'shop_price'] 18 19 def retrieve(self, request, *args, **kwargs): 20 '''重写实现点击数''' 21 instance = self.get_object() 22 instance.click_num += 1 23 instance.save() 24 serializer = self.get_serializer(instance) 25 return Response(serializer.data)
此时进行访问测试如下:

显然,限速成功,并且对于未登录和已登录的限制不一样。
本文原文首发来自博客专栏Python Web开发实战,由本人转发至https://www.helloworld.net/p/b330cmKtj2ij5,其他平台均属侵权,可点击https://blog.csdn.net/CUFEECR/article/details/107797490查看原文,也可点击https://blog.csdn.net/CUFEECR浏览更多优质原创内容。
