(项目)生鲜超市(十一)

十四、首页、商品数量、缓存和限速功能开发

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)

  然后在登录或者未登录状态下访问该接口,超出次数如下:

posted @ 2018-11-29 17:07  Sweltering  阅读(432)  评论(0编辑  收藏  举报