您现在的位置是: 网站首页 >Django >Vue+Django REST framework前后端分离生鲜电商 Django

【Vue+DRF生鲜电商】32.商品操作后计数更改,热搜榜关键字功能实现

admin2019年8月14日 13:58 Django | Html | JavaScript | Vue 1263人已围观

Vue+Django REST framework前后端分离生鲜电商简介 Vue+Django REST framework 打造前后端分离的生鲜电商项目(慕课网视频)。 Github地址:https://github.com/xyliurui/DjangoOnlineFreshSupermarket ; Django版本:2.2、djangorestframework:3.9.2。 前端Vue模板可以直接联系我拿。

## 商品操作数值更改 ### 商品点击数、收藏数修改 #### 商品点击数修改 当访问商品详情时,将点击数+1 在 apps/goods/views.py 中的`GoodsListViewSet` ```python class GoodsListViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet): """ list: 显示商品列表,分页、过滤、搜索、排序 retrieve: 显示商品详情 """ queryset = Goods.objects.all() # 使用get_queryset函数,依赖queryset的值 serializer_class = GoodsSerializer pagination_class = GoodsPagination filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter,) # 将过滤器后端添加到单个视图或视图集 filterset_class = GoodsFilter # authentication_classes = (TokenAuthentication, ) # 只在本视图中验证Token search_fields = ('name', 'goods_desc', 'category__name') # 搜索字段 ordering_fields = ('click_num', 'sold_num', 'shop_price') # 排序 ``` 因为显示详情时继承了`mixins.RetrieveModelMixin` 按住Ctrl点击它,然后可以复制它的`retrieve(self, request, *args, **kwargs)`方法 ```python from rest_framework.response import Response class GoodsListViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet): """ list: 显示商品列表,分页、过滤、搜索、排序 retrieve: 显示商品详情 """ queryset = Goods.objects.all() # 使用get_queryset函数,依赖queryset的值 serializer_class = GoodsSerializer pagination_class = GoodsPagination filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter,) # 将过滤器后端添加到单个视图或视图集 filterset_class = GoodsFilter # authentication_classes = (TokenAuthentication, ) # 只在本视图中验证Token search_fields = ('name', 'goods_desc', 'category__name') # 搜索字段 ordering_fields = ('click_num', '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) ``` ![BLOG_20190814_140437_18](/media/blog/images/2019/08/BLOG_20190814_140437_18.png "博客图集BLOG_20190814_140437_18.png") 进入商品详情时,增加点击数。 #### 商品收藏数修改,用信号修改 用户点击收藏+1,取消收藏-1 在 apps/user_operation/views.py 中`UserFavViewSet`继承了`mixins.CreateModelMixin`和`mixins.DestroyModelMixin` 按住Ctrl点击进去查看其中的方法,可以重写`perform_create(self, serializer)`方法,将收藏数+1;重写`perform_create(self, serializer)`方法,将收藏数-1。 ```python class UserFavViewSet(mixins.CreateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet): """ create: 用户收藏商品 destroy: 取消收藏商品 list: 显示收藏商品列表 retrieve: 根据商品id显示收藏详情 """ queryset = UserFav.objects.all() # serializer_class = UserFavSerializer def get_serializer_class(self): """ 不同的action使用不同的序列化 :return: """ if self.action == 'list': return UserFavListSerializer # 显示用户收藏列表序列化 else: return UserFavSerializer permission_classes = (IsAuthenticated, IsOwnerOrReadOnly) authentication_classes = (JWTAuthentication, SessionAuthentication) # 配置登录认证:支持JWT认证和DRF基本认证 lookup_field = 'goods_id' def get_queryset(self): # 过滤当前用户的收藏记录 return self.queryset.filter(user=self.request.user) def perform_create(self, serializer): # 添加收藏商品,商品收藏数+1 # 序列化保存,然后将它赋值给一个实例,也就是UserFav(models.Model)对象 instance = serializer.save() # 获取其中的商品 goods = instance.goods # 商品收藏数+1 goods.fav_num += 1 goods.save() def perform_destroy(self, instance): # 删除收藏商品,商品收藏数-1 goods = instance.goods # 商品收藏数-1 goods.fav_num -= 1 if goods.fav_num < 0: goods.fav_num = 0 goods.save() instance.delete() ``` ![BLOG_20190814_140427_72](/media/blog/images/2019/08/BLOG_20190814_140427_72.png "博客图集BLOG_20190814_140427_72.png") 进入调试模式,在这两个方法中添加断点,可以在API中添加收藏和删除收藏,当添加收藏时,收藏数+1,删除该收藏时,收藏数-1。 下面可以用Django的信号来实现,收藏注释掉上方的`perform_create(self, serializer)`和`perform_destroy(self, instance)` 在 apps/user_operation/ 目录下创建 signals.py 文件,增加信号相关的代码 ```python from django.db.models.signals import post_save, pre_delete from django.dispatch import receiver from .models import UserFav @receiver(post_save, sender=UserFav) def userfav_post_save_handler(sender, instance=None, created=False, **kwargs): # 首次创建时收藏数+1 if created: goods = instance.goods goods.fav_num += 1 goods.save() @receiver(pre_delete, sender=UserFav) def userfav_pre_delete_handler(sender, instance, **kwargs): # 删除前发送信号 goods = instance.goods # 商品收藏数-1 goods.fav_num -= 1 if goods.fav_num < 0: goods.fav_num = 0 goods.save() ``` 修改 apps/user_operation/__init__.py 增加以下代码 ```python default_app_config = 'user_operation.apps.UserOperationConfig' ``` 修改 apps/user_operation/apps.py 引入信号配置 ```python from django.apps import AppConfig class UserOperationConfig(AppConfig): name = 'user_operation' verbose_name = '操作' def ready(self): """ 在子类中重写此方法,以便在Django启动时运行代码。 :return: """ from .signals import userfav_post_save_handler, userfav_pre_delete_handler ``` 接下来访问 http://127.0.0.1:8000/userfavs/ 进行收藏,取消收藏接口的测试,可以在信号相关代码上打上断点,看程序是否正常进入该位置。 ![BLOG_20190814_140415_62](/media/blog/images/2019/08/BLOG_20190814_140415_62.png "博客图集BLOG_20190814_140415_62.png") 收藏数已经从0变为1 ![BLOG_20190814_140408_31](/media/blog/images/2019/08/BLOG_20190814_140408_31.png "博客图集BLOG_20190814_140408_31.png") 进入收藏详情,删除收藏 ![BLOG_20190814_140403_13](/media/blog/images/2019/08/BLOG_20190814_140403_13.png "博客图集BLOG_20190814_140403_13.png") 刷新数据表,此时收藏数变为0 ![BLOG_20190814_140359_11](/media/blog/images/2019/08/BLOG_20190814_140359_11.png "博客图集BLOG_20190814_140359_11.png") ### 商品库存和销量修改 #### 商品库存修改 会引起商品库存数变化的请况有: - 新增商品到购物车 - 修改购物车商品数量 - 删除购物车记录 - 订单取消,库存增加 也就是与购物车相关功能有关 在 apps/trade/views.py 中有关于购物车相关的类`ShoppingCartViewSet(viewsets.ModelViewSet)`,可以重写继承类的方法,来完成库存数修改,相较于信号来说更加灵活。 添加到购物车,重写方法 ```python def perform_create(self, serializer): serializer.save() ``` 删除购物车,重写方法 ```python def perform_destroy(self, instance): instance.delete() ``` 更新购物车,重写方法 ```python def perform_update(self, serializer): serializer.save() ``` 先来打上断点,观察下`serializer`的值,可以获取到更新前的库存量 ![BLOG_20190814_140348_77](/media/blog/images/2019/08/BLOG_20190814_140348_77.png "博客图集BLOG_20190814_140348_77.png") 修改`ShoppingCartViewSet`,重写相关方法 ```python class ShoppingCartViewSet(viewsets.ModelViewSet): """ 购物车功能实现 list: 获取购物车列表 create: 添加商品到购物车 update: 更新购物车商品数量 delete: 从购物车中删除商品 """ # 权限问题:购物车和用户权限关联,这儿和用户操作差不多 permission_classes = (IsAuthenticated, IsOwnerOrReadOnly) # 用户必须登录才能访问 authentication_classes = (JWTAuthentication, SessionAuthentication) # 配置登录认证:支持JWT认证和DRF基本认证 # serializer_class = ShoppingCartSerializer # 使用get_serializer_class(),这个就不需要了 queryset = ShoppingCart.objects.all() lookup_field = 'goods' def get_serializer_class(self): if self.action == 'list': # 当获取购物车列表时,使用ModelSerializer,可以显示购物车商品详情 return ShoppingCartListSerializer else: return ShoppingCartSerializer def get_queryset(self): # 只能显示当前用户的购物车列表 return self.queryset.filter(user=self.request.user) def perform_create(self, serializer): # 添加到购物车,库存数减少 shop_cart = serializer.save() goods = shop_cart.goods # 商品的库存量goods_num,减去购物车中的数量 goods.goods_num -= shop_cart.nums goods.save() def perform_destroy(self, instance): # 从购物车中删除,库存量减少 goods = instance.goods # 商品的库存量goods_num,加上删除的数量 goods.goods_num += instance.nums goods.save() instance.delete() def perform_update(self, serializer): # 更新购物车中数量,先获取原来的数量,再进行更新 cart_goods = serializer.instance old_cart_goods_num = cart_goods.nums # 获取购物车中该商品原来的数量 update_cart_goods = serializer.save() diff_nums = update_cart_goods.nums - old_cart_goods_num # 现在的数量减去以前的数量 goods = cart_goods.goods # 得到商品对象,更改库存量 goods.goods_num -= diff_nums goods.save() ``` 测试,首先将`goods_num`初始值给10 选择该商品添加3个到购物车 ![BLOG_20190814_140334_79](/media/blog/images/2019/08/BLOG_20190814_140334_79.png "博客图集BLOG_20190814_140334_79.png") 库存量减少到7 ![BLOG_20190814_140326_49](/media/blog/images/2019/08/BLOG_20190814_140326_49.png "博客图集BLOG_20190814_140326_49.png") 修改购物车中的数量 ![BLOG_20190814_140321_92](/media/blog/images/2019/08/BLOG_20190814_140321_92.png "博客图集BLOG_20190814_140321_92.png") 库存量减少到5 ![BLOG_20190814_140314_98](/media/blog/images/2019/08/BLOG_20190814_140314_98.png "博客图集BLOG_20190814_140314_98.png") 从购物车删除该商品 ![BLOG_20190814_140310_65](/media/blog/images/2019/08/BLOG_20190814_140310_65.png "博客图集BLOG_20190814_140310_65.png") 库存量恢复到10 ![BLOG_20190814_140258_15](/media/blog/images/2019/08/BLOG_20190814_140258_15.png "博客图集BLOG_20190814_140258_15.png") 用户下单后,将购物车中的商品放到订单商品中。订单一旦取消,商品并未返回购物车,所以,需要将库存数量修改。 修改 apps/trade/views.py 中的`OrderInfoViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.DestroyModelMixin, viewsets.GenericViewSet)`,重写删除订单的方法 ```python def perform_destroy(self, instance): instance.delete() ``` 复制到`OrderInfoViewSet`中 ```python class OrderInfoViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.DestroyModelMixin, viewsets.GenericViewSet): """ 订单管理 list: 获取个人订单 create: 新建订单 delete: 删除订单 detail: 订单详情 """ permission_classes = (IsAuthenticated, IsOwnerOrReadOnly) # 用户必须登录才能访问 authentication_classes = (JWTAuthentication, SessionAuthentication) # 配置登录认证:支持JWT认证和DRF基本认证 queryset = OrderInfo.objects.all() # serializer_class = OrderInfoSerializer # 添加序列化 def get_queryset(self): return self.queryset.filter(user=self.request.user) def get_serializer_class(self): # 动态序列化,当显示订单详情,用另一个Serializer if self.action == 'retrieve': return OrderInfoDetailSerializer else: return OrderInfoSerializer def perform_create(self, serializer): # 完成创建后保存到数据库,可以拿到保存的值 order = serializer.save() shopping_carts = ShoppingCart.objects.filter(user=self.request.user) # 将该用户购物车所有商品都取出来放在订单商品中 for shopping_cart in shopping_carts: OrderGoods.objects.create( order=order, goods=shopping_cart.goods, goods_nums=shopping_cart.nums ) # 然后清空该用户购物车 shopping_carts.delete() def perform_destroy(self, instance): # 取消(删除)商品库存量增加 for order_goods in instance.order_goods.all(): goods = order_goods.goods # 获取订单商品的数量,修改库存量 goods.goods_num += order_goods.goods_nums goods.save() instance.delete() ``` 测试下单,取消订单 访问 http://127.0.0.1:8080/#/app/home/productDetail/105 直接在前端操作吧 添加1个到购物车 ![BLOG_20190814_140247_48](/media/blog/images/2019/08/BLOG_20190814_140247_48.png "博客图集BLOG_20190814_140247_48.png") 商品库存量减少 ![BLOG_20190814_140241_28](/media/blog/images/2019/08/BLOG_20190814_140241_28.png "博客图集BLOG_20190814_140241_28.png") 但不进入支付,进入我的订单,点击取消 ![BLOG_20190814_140231_95](/media/blog/images/2019/08/BLOG_20190814_140231_95.png "博客图集BLOG_20190814_140231_95.png") 商品库存恢复10 ![BLOG_20190814_140220_53](/media/blog/images/2019/08/BLOG_20190814_140220_53.png "博客图集BLOG_20190814_140220_53.png") #### 商品销量修改 一般销量变化发生在订单支付成功后。 修改 apps/trade/views.py 中的`AliPayView(APIView)` ```python class AliPayView(APIView): def get(self, request): # ...... def post(self, request): """ 处理支付宝notify_url异步通知 :param request: :return: """ processed_dict = {} for key, value in request.POST.items(): processed_dict[key] = value print('request.POST的值:', processed_dict) sign = processed_dict.pop('sign', None) # 直接就是字符串了 server_ip = get_server_ip() alipay = AliPay( app_id=app_id, # 自己支付宝沙箱 APP ID notify_url="http://{}:8000/alipay/return/".format(server_ip), app_private_key_path=app_private_key_path, # 可以使用相对路径那个 alipay_public_key_path=alipay_public_key_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, debug=alipay_debug, # 默认False, return_url="http://{}:8000/alipay/return/".format(server_ip) ) verify_result = alipay.verify(processed_dict, sign) # 验证签名,如果成功返回True if verify_result: order_sn = processed_dict.get('out_trade_no') # 原支付请求的商户订单号 trade_no = processed_dict.get('trade_no') # 支付宝交易凭证号 trade_status = processed_dict.get('trade_status') # 交易目前所处的状态 # 更新数据库订单状态 """ OrderInfo.objects.filter(order_sn=order_sn).update( trade_no=trade_no, # 更改交易号 pay_status=trade_status, # 更改支付状态 pay_time=timezone.now() # 更改支付时间 ) """ orderinfos = OrderInfo.objects.filter(order_sn=order_sn) for orderinfo in orderinfos: orderinfo.trade_no = trade_no, # 更改交易号 orderinfo.pay_status = trade_status, # 更改支付状态 orderinfo.pay_time = timezone.now() # 更改支付时间 # 更改商品的销量 order_goods = orderinfo.order_goods.all() for item in order_goods: # 获取订单中商品和商品数量,然后将商品的销量进行增加 goods = item.goods goods.sold_num += item.goods_nums goods.save() orderinfo.save() # 给支付宝返回一个消息,证明已收到异步通知 # 当商户收到服务器异步通知并打印出 success 时,服务器异步通知参数 notify_id 才会失效。 # 也就是说在支付宝发送同一条异步通知时(包含商户并未成功打印出 success 导致支付宝重发数次通知),服务器异步通知参数 notify_id 是不变的。 return Response('success') ``` 现在下单1个商品,并支付完成。**确保Django运行到服务器,支付宝可以通过公网POST订单支付信息** 刷新数据库,可以看到库存减1,销量加1 ![BLOG_20190814_140209_61](/media/blog/images/2019/08/BLOG_20190814_140209_61.png "博客图集BLOG_20190814_140209_61.png") 商品详情页数据也发生了变化 ![BLOG_20190814_140202_55](/media/blog/images/2019/08/BLOG_20190814_140202_55.png "博客图集BLOG_20190814_140202_55.png") ## 结合Redis实现热搜关键字显示 采用redis的有序集合实现,每次搜索的关键字保存在redis,重复时将对应的分数+1 ### 获取参数中的关键字 修改 apps/goods/views.py `GoodsListViewSet`在商品过滤时提取集中的搜索关键字参数 ```python class GoodsListViewSet(CacheResponseMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet): """ list: 显示商品列表,分页、过滤、搜索、排序 retrieve: 显示商品详情 """ queryset = Goods.objects.all() # 使用get_queryset函数,依赖queryset的值 serializer_class = GoodsSerializer pagination_class = GoodsPagination filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter,) # 将过滤器后端添加到单个视图或视图集 filterset_class = GoodsFilter # authentication_classes = (TokenAuthentication, ) # 只在本视图中验证Token search_fields = ('name', 'goods_desc', 'category__name') # 搜索字段 ordering_fields = ('click_num', 'sold_num', 'shop_price') # 排序 # throttle_classes = [UserRateThrottle, AnonRateThrottle] # DRF默认限速类,可以仿照写自己的限速类 throttle_scope = 'goods_list' 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) def get_queryset(self): keyword = self.request.query_params.get('search') if keyword: from utils.hotsearch import HotSearch hot_search = HotSearch() hot_search.save_keyword(keyword) return self.queryset ``` ### 热搜关键字保存和查询 在 utils/ 目录下创建 hotsearch.py ,用于关键字的保存和热搜排行 ```python # 使用redis来记录热搜,并根据分数进行排序 import redis # 首先pip install redis安装好 class HotSearch(object): def __init__(self): pool = redis.ConnectionPool(host='localhost', port=6379, db=6, decode_responses=True) self.r_conn = redis.Redis(connection_pool=pool) # 创建连接池,并进行连接 self.name = 'keyword:hot:search' def save_keyword(self, keyword): # 如果关键字已存在,分数+1 if self.r_conn.zscore(self.name, keyword): self.r_conn.zincrby(self.name, amount=1, value=keyword) else: self.r_conn.zadd(self.name, {keyword: 1}) # print(self.r_conn.zrevrange(self.name, 0, 5, withscores=True)) def get_hotsearch(self): hot_5 = self.r_conn.zrevrange(self.name, 0, 5) # 得到一个关键字的列表 return hot_5 ``` ### 热搜榜数据序列化显示 当然这个只能自己写api来序列化查询结果了,修改 apps/user_operation/views.py 添加`HotSearchView` ```python class HotSearchView(APIView): def get(self, request): from utils.hotsearch import HotSearch from django.http import JsonResponse from rest_framework.response import Response from rest_framework import exceptions, status import json hot_search = HotSearch() result = [] for keyword in hot_search.get_hotsearch(): tmp = dict() tmp['keyword'] = keyword result.append(tmp) # return JsonResponse(result, safe=False) return Response(result, status=status.HTTP_200_OK) ``` 修改 DjangoOnlineFreshSupermarket/urls.py 添加路由 ```python from user_operation.views import HotSearchView urlpatterns = [ # ...... # 获取热搜 path('hotsearchs/', HotSearchView.as_view(), name='hotsearchs') ] ``` 访问 http://127.0.0.1:8000/hotsearchs/ 可以查看热搜磅 ![BLOG_20190814_140143_50](/media/blog/images/2019/08/BLOG_20190814_140143_50.png "博客图集BLOG_20190814_140143_50.png") 在Vue前端页面中多搜索几个关键字 ![BLOG_20190814_140133_40](/media/blog/images/2019/08/BLOG_20190814_140133_40.png "博客图集BLOG_20190814_140133_40.png") 然后访问 http://127.0.0.1:8000/hotsearchs/ 可以查看结果 ![BLOG_20190814_140128_47](/media/blog/images/2019/08/BLOG_20190814_140128_47.png "博客图集BLOG_20190814_140128_47.png") ### Vue和热搜接口联调 在 src/views/head/head.vue 获取热搜榜的函数是 ```JavaScript getHotSearch() { //获取热搜 getHotSearch() .then((response) => { console.log('获取热搜榜:'); console.log(response.data); this.hotSearch = response.data }) .catch(function (error) { console.log(error); }); } ``` 组件创建时,就请求 src/api/api.js 中的接口 ```JavaScript //获取热门搜索关键词 export const getHotSearch = params => { return axios.get(`${local_host}/hotsearchs/`) }; ``` 然后遍历显示到页面中 ```html <div class="head_search_hot"> <span>热搜榜:</span> <!--router-link :to="'/app/home/search/'+'牛肉'">牛肉<手动增加></router-link--> <router-link v-for="item in hotSearch" :to="'/app/home/search/'+item.keyword" :key="item.keyword"> {{item.keyword}} </router-link> </div> ``` ![BLOG_20190814_140115_37](/media/blog/images/2019/08/BLOG_20190814_140115_37.png "博客图集BLOG_20190814_140115_37.png")

很赞哦! (1)

文章交流

  • emoji
0人参与,0条评论

当前用户

未登录,点击   登录

站点信息

  • 建站时间:网站已运行2074天
  • 系统信息:Linux
  • 后台程序:Python: 3.8.10
  • 网站框架:Django: 3.2.6
  • 文章统计:256 篇
  • 文章评论:60 条
  • 腾讯分析网站概况-腾讯分析
  • 百度统计网站概况-百度统计
  • 公众号:微信扫描二维码,关注我们
  • QQ群:QQ加群,下载网站的学习源码
返回
顶部
标题 换行 登录
网站