(项目)生鲜超市(十一)
十四、首页、商品数量、缓存和限速功能开发
1、首页轮播图
为了方便测试,还是将pycharm的环境改成本地的,vue中的host也改成本地地址,注意要修改数据库的地址。
然后在goods/serializers.py中加入轮播图字段的序列化:
1 class bannerserializer(serializers.modelserializer): 2 class meta: 3 model = banner 4 fields = '__all__'
在goods/views.py中编写轮播图的接口:
1 class bannerviewset(mixins.listmodelmixin, viewsets.genericviewset): 2 """首页轮播图""" 3 4 queryset = banner.objects.all().order_by('index') 5 serializer_class = bannerserializer
注册url:
1 router.register(r'banners', bannerviewset, base_name='banners') # 首页轮播图
在后台添加轮播图片,然后访问首页即可看到轮播图:
2、首页新品
我们之前在设计goods的model的时候,有设计过一个字段is_new:is_new = models.booleanfield("是否新品", default=false)。
实现这个功能只需要在goods/filter/goodsfilter中添加过滤即可:
1 class goodsfilter(django_filters.rest_framework.filterset): 2 """商品过滤""" 3 4 # name是要过滤的字段,lookup是执行的行为 5 pricemin = django_filters.numberfilter(field_name="shop_price", lookup_expr='gte') 6 pricemax = django_filters.numberfilter(field_name="shop_price", lookup_expr='lte') 7 top_category = django_filters.numberfilter(field_name="category", method='top_category_filter') 8 9 def top_category_filter(self, queryset, name, value): 10 # 不管当前点击的是一级分类二级分类还是三级分类,都能找到 11 return queryset.filter(q(category_id=value) | q(category__parent_category_id=value) | q( 12 category__parent_category__parent_category_id=value)) 13 14 class meta: 15 model = goods 16 fields = ['pricemin', 'pricemax', 'is_hot', 'is_new']
然后在后台添加几个新品即可:
3、首页商品分类
首先将商品广告位的字段进行序列化,还需要对商品大类下的分类及品牌进行序列化:
1 class brandserializer(serializers.modelserializer): 2 class meta: 3 model = goodscategorybrand 4 fields = '__all__' 5 6 7 class indexcategoryserializer(serializers.modelserializer): 8 # 一个大类下可以有多个商标 9 brands = brandserializer(many=true) 10 # good有一个外键category指向三级类,直接反向通过外键category(三级类),取某个大类下面的商品是取不出来的 11 goods = serializers.serializermethodfield() 12 # 取二级商品分类 13 sub_cat = categoryserializer2(many=true) 14 # 广告商品 15 ad_goods = serializers.serializermethodfield() 16 17 def get_ad_goods(self, obj): 18 goods_json = {} 19 ad_goods = indexad.objects.filter(category_id=obj.id) 20 if ad_goods: 21 good_ins = ad_goods[0].goods 22 # 在serializer里面调用serializer,就要添加一个参数context(上下文request),嵌套serializer必须加 23 # serializer返回的时候一定要加'data',这样才是json数据 24 goods_json = goodsserializer(good_ins, many=false, context={'request': self.context['request']}).data 25 return goods_json 26 27 def get_goods(self, obj): 28 # 让这个商品相关父类子类等都可以进行匹配 29 all_goods = goods.objects.filter(q(category_id=obj.id) | q(category__parent_category_id=obj.id) | 30 q(category__parent_category__parent_category_id=obj.id)) 31 goods_serializer = goodsserializer(all_goods, many=true, context={'request': self.context['request']}) 32 return goods_serializer.data 33 34 class meta: 35 model = goodscategory 36 fields = '__all__'
然后编写首页商品分类的接口:
1 class indexcategoryviewset(mixins.listmodelmixin, viewsets.genericviewset): 2 """首页商品分类""" 3 4 queryset = goodscategory.objects.filter(is_tab=true, name__in=["生鲜食品", "酒水饮料"]) 5 serializer_class = indexcategoryserializer
注册url:
1 router.register(r'indexgoods', indexcategoryviewset, base_name='indexgoods') # 首页系列商品分类
在后台添加宣传品牌和首页广告:
4、商品点击数和收藏数
4.1 点击数
goodslistviewset中继承了mixins.retrievemodelmixin(获取商品详情),我们只需要重写这个类的retrieve方法即可:
1 class goodslistviewset(mixins.listmodelmixin, mixins.retrievemodelmixin, viewsets.genericviewset): 2 """ 3 list: 4 商品列表,分页,搜索,过滤,排序 5 retrieve: 6 获取商品详情 7 """ 8 9 pagination_class = goodspagination 10 queryset = goods.objects.all().order_by('id') # 必须定义一个默认的排序,否则会报错 11 serializer_class = goodsserializer 12 filter_backends = (djangofilterbackend, filters.searchfilter, filters.orderingfilter) 13 14 # 自定义过滤类 15 filter_class = goodsfilter 16 17 # 搜索,=name表示精确搜索,也可以使用正则 18 search_fields = ('name', 'goods_brief', 'goods_desc') 19 20 # 排序 21 ordering_fields = ('sold_num', 'shop_price') 22 23 # 重写retrieve方法,商品点击数加1 24 def retrieve(self, request, *args, **kwargs): 25 instance = self.get_object() 26 instance.click_num += 1 27 instance.save() 28 serializer = self.get_serializer(instance) 29 return response(serializer.data)
点击某一个商品,在数据库中可以看到click_num加了1:
4.2 收藏数
在userfavviewset接口中继承了mixins.createmodelmixin,添加收藏实际就是创建数据库,这里重写它的perform_create方法即可:
1 class userfavviewset(mixins.listmodelmixin, mixins.createmodelmixin, mixins.destroymodelmixin, 2 mixins.retrievemodelmixin, viewsets.genericviewset): 3 """用户收藏""" 4 5 # permission是权限验证 isauthenticated必须登录用户 isownerorreadonly必须是当前登录的用户 6 permission_classes = (isauthenticated, isownerorreadonly) 7 8 # authentication是用户认证 9 authentication_classes = (jsonwebtokenauthentication, sessionauthentication) 10 11 # 搜索的字段 12 lookup_field = 'goods_id' 13 14 # 动态选择serializer 15 def get_serializer_class(self): 16 if self.action == "list": 17 return userfavdetailserializer 18 elif self.action == "create": 19 return userfavserializer 20 return userfavserializer 21 22 # 只能查看当前登录用户的收藏,不会获取所有用户的收藏 23 def get_queryset(self): 24 return userfav.objects.filter(user=self.request.user) 25 26 # 收藏数加1 27 def perform_create(self, serializer): 28 instance = serializer.save() 29 # 这里instance相当于userfav的model,通过它找到goods 30 goods = instance.goods 31 goods.fav_num += 1 32 goods.save()
登录点击收藏之后,收藏数会增加:
4.3 信号量实现收藏数
用户在delete和create的时候django model都会发送一个信号量出来,可以通过信号量的方式修改收藏数,在user_operation下新建signal.py:
1 from django.dispatch import receiver 2 from django.db.models.signals import post_save, post_delete 3 from user_operation.models import userfav 4 5 @receiver(post_save, sender=userfav) 6 def create_userfav(sender, instance=none, created=false, **kwargs): 7 # update的时候也会进行post_save 8 if created: 9 goods = instance.goods 10 goods.fav_num += 1 11 goods.save() 12 13 @receiver(post_delete, sender=userfav) 14 def delete_userfav(sender, instance=none, created=false, **kwargs): 15 goods = instance.goods 16 goods.fav_num -= 1 17 goods.save()
然后在apps.py中重写ready方法:
1 from django.apps import appconfig 2 3 4 class useroperationconfig(appconfig): 5 name = 'user_operation' 6 verbose_name = '用户操作管理' 7 8 def ready(self): 9 import user_operation.signals
现在添加收藏,删除收藏:
5、商品库存和销量修改
5.1 库存数
我们在新增商品到购物车,修改购物车中的数量,删除购物车记录都会对商品库存数产生影响,在trade/views.py中对购物车接口中的相关操作做库存数修改的逻辑:
1 class shoppingcartviewset(viewsets.modelviewset): 2 """ 3 购物车功能 4 list: 5 获取购物车详情 6 create: 7 加入购物车 8 delete: 9 删除购物记录 10 """ 11 12 permission_classes = (isauthenticated, isownerorreadonly) 13 authentication_classes = (jsonwebtokenauthentication, sessionauthentication) 14 15 # 把商品id传回去 16 lookup_field = 'goods_id' 17 18 def get_serializer_class(self): 19 if self.action == 'list': 20 return shopcartdetailserializer 21 else: 22 return shopcartserializer 23 24 def get_queryset(self): 25 return shoppingcart.objects.filter(user=self.request.user) 26 27 # 创建购物车,库存数减少 28 def perform_create(self, serializer): 29 shop_cart = serializer.save() 30 goods = shop_cart.goods 31 goods.goods_num -= shop_cart.nums 32 goods.save() 33 34 # 移除购物车,库存数增加 35 def perform_destroy(self, instance): 36 goods = instance.goods() 37 goods.goods_num += instance.nums 38 goods.save() 39 instance.delete() 40 41 # 跟新购物车,可能是增加也可能是减少 42 def perform_update(self, serializer): 43 # 先获取修改之前的库存数量 44 existed_record = shoppingcart.objects.get(id=serializer.instance.id) 45 existed_nums = existed_record.nums 46 47 # 先保存之前的数据existed_nums 48 save_record = serializer.save() 49 50 # 做更新操作 51 nums = save_record.nums - existed_nums 52 goods = save_record.goods 53 goods.goods_num -= nums 54 goods.save()
5.2 销量
商品的销量只有在支付成功之后才能增加,也就是在alipayview接口中订单支付成功加入增加销量的逻辑:
1 class alipayview(apiview): 2 """支付宝接口""" 3 4 # 处理支付宝的return_url返回 5 def get(self, request): 6 processed_dict = {} 7 8 # 获取get中的参数 9 for key, value in request.get.items(): 10 processed_dict[key] = value 11 12 # 从processed_dict中取出sign 13 sign = processed_dict.pop("sign", none) 14 15 # 生成alipay对象 16 alipay = alipay( 17 appid="2016092000557473", 18 app_notify_url="http://148.70.2.75:8000/alipay/return/", 19 app_private_key_path=private_key_path, 20 alipay_public_key_path=ali_pub_key_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, 21 debug=true, # 默认false, 22 return_url="http://148.70.2.75:8000/alipay/return/" 23 ) 24 25 # 验证签名 26 verify_re = alipay.verify(processed_dict, sign) 27 28 # 这里可以不做操作。因为不管发不发return url。notify url都会修改订单状态。 29 if verify_re is true: 30 order_sn = processed_dict.get('out_trade_no', none) 31 trade_no = processed_dict.get('trade_no', none) 32 trade_status = processed_dict.get('trade_status', none) 33 34 existed_orders = orderinfo.objects.filter(order_sn=order_sn) 35 for existed_order in existed_orders: 36 existed_order.pay_status = trade_status 37 existed_order.trade_no = trade_no 38 existed_order.pay_time = datetime.now() 39 existed_order.save() 40 41 # 支付完成跳转到首页 42 response = redirect("index") 43 response.set_cookie("nextpath", "pay", max_age=2) 44 return response 45 else: 46 response = redirect("index") 47 return response 48 49 # 处理支付宝的notify_url 50 def post(self, request): 51 processed_dict = {} 52 53 # 取出post里面的数据 54 for key, value in request.post.items(): 55 processed_dict[key] = value 56 57 # 去掉sign 58 sign = processed_dict.pop("sign", none) 59 60 # 生成一个alipay对象 61 alipay = alipay( 62 appid="2016092000557473", 63 app_notify_url="http://148.70.2.75:8000/alipay/return/", 64 app_private_key_path=private_key_path, 65 alipay_public_key_path=ali_pub_key_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, 66 debug=true, # 默认false, 67 return_url="http://148.70.2.75:8000/alipay/return/" 68 ) 69 70 # 进行验证 71 verify_re = alipay.verify(processed_dict, sign) 72 73 if verify_re is true: 74 # 商户网站唯一订单号 75 order_sn = processed_dict.get('out_trade_no', none) 76 # 支付宝系统交易流水号 77 trade_no = processed_dict.get('trade_no', none) 78 # 交易状态 79 trade_status = processed_dict.get('trade_status', none) 80 81 # 查询数据库中订单记录 82 existed_orders = orderinfo.objects.filter(order_sn=order_sn) 83 for existed_order in existed_orders: 84 # 订单商品项 85 order_goods = existed_order.goods.all() 86 # 商品销量增加 87 for order_good in order_goods: 88 goods = order_good.goods 89 goods.sold_num += order_good.goods_num 90 goods.save() 91 92 # 更新订单状态 93 existed_order.pay_status = trade_status 94 existed_order.trade_no = trade_no 95 existed_order.pay_time = datetime.now() 96 existed_order.save() 97 # 需要返回一个'success'给支付宝,如果不返回,支付宝会一直发送订单支付成功的消息 98 return response("success")
6、drf的缓存设置
缓存的作用是为了加速用户访问某一资源的速度,将用户经常访问的数据加入缓存中,取数据的时候直接到缓存中去取,没有的话再去数据库,速度肯定会快很多。我们通过drf的一个扩展来实现缓存,github上有官方使用说明:http://chibisov.github.io/drf-extensions/docs/#caching。
首先安装drf-extensions库:pip install drf-extensions
然后在需要访问的数据的接口中加上缓存即可,我们在商品列表的接口中加入缓存,直接继承cacheresponsemixin即可:
1 class goodslistviewset(cacheresponsemixin, mixins.listmodelmixin, mixins.retrievemodelmixin, viewsets.genericviewset): 2 """ 3 list: 4 商品列表,分页,搜索,过滤,排序 5 retrieve: 6 获取商品详情 7 """ 8 9 pagination_class = goodspagination 10 queryset = goods.objects.all().order_by('id') # 必须定义一个默认的排序,否则会报错 11 serializer_class = goodsserializer 12 filter_backends = (djangofilterbackend, filters.searchfilter, filters.orderingfilter) 13 14 # 自定义过滤类 15 filter_class = goodsfilter 16 17 # 搜索,=name表示精确搜索,也可以使用正则 18 search_fields = ('name', 'goods_brief', 'goods_desc') 19 20 # 排序 21 ordering_fields = ('sold_num', 'shop_price') 22 23 # 重写retrieve方法,商品点击数加1 24 def retrieve(self, request, *args, **kwargs): 25 instance = self.get_object() 26 instance.click_num += 1 27 instance.save() 28 serializer = self.get_serializer(instance) 29 return response(serializer.data)
在settings中设置过期时间:
1 # 缓存配置 2 rest_framework_extensions = { 3 'default_cache_response_timeout': 5 # 5s过期 4 }
这个缓存使用的是内存,每次重启都会失效。
现在我们配置redis缓存,文档说明:http://django-redis-chs.readthedocs.io/zh_cn/latest/#id8
首先安装包:pip install django-redis
然后在settings中配置redis缓存:
1 # redis缓存 2 caches = { 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才能使用。
7、def的throttle设置api的访问速率
为了防止爬虫或者黑客恶意攻击,对api的访问速率进行限制就显得非常重要的,官方文档说明:http://www.django-rest-framework.org/api-guide/throttling/
首先在settings中进行配置:
1 rest_framework = { 2 'default_authentication_classes': ( 3 'rest_framework.authentication.basicauthentication', 4 'rest_framework.authentication.sessionauthentication', 5 # 'rest_framework.authentication.tokenauthentication' 6 ), 7 'default_throttle_classes': ( 8 'rest_framework.throttling.anonratethrottle', # 未登陆用户 9 'rest_framework.throttling.userratethrottle' # 登陆用户 10 ), 11 'default_throttle_rates': { 12 'anon': '3/minute', # 每分钟可以请求两次 13 'user': '5/minute' # 每分钟可以请求五次 14 } 15 }
然后在对应的api接口中加入访问速率限制即可,这里对商品列表接口进行配置:
1 class goodslistviewset(cacheresponsemixin, mixins.listmodelmixin, mixins.retrievemodelmixin, viewsets.genericviewset): 2 """ 3 list: 4 商品列表,分页,搜索,过滤,排序 5 retrieve: 6 获取商品详情 7 """ 8 9 pagination_class = goodspagination 10 queryset = goods.objects.all().order_by('id') # 必须定义一个默认的排序,否则会报错 11 serializer_class = goodsserializer 12 filter_backends = (djangofilterbackend, filters.searchfilter, filters.orderingfilter) 13 14 # 接口访问速率限制 15 throttle_classes = (userratethrottle, anonratethrottle) 16 17 # 自定义过滤类 18 filter_class = goodsfilter 19 20 # 搜索,=name表示精确搜索,也可以使用正则 21 search_fields = ('name', 'goods_brief', 'goods_desc') 22 23 # 排序 24 ordering_fields = ('sold_num', 'shop_price') 25 26 # 重写retrieve方法,商品点击数加1 27 def retrieve(self, request, *args, **kwargs): 28 instance = self.get_object() 29 instance.click_num += 1 30 instance.save() 31 serializer = self.get_serializer(instance) 32 return response(serializer.data)
然后在登录或者未登录状态下访问该接口,超出次数如下:
上一篇: Python3 笔记01:求两数之和