vue+django2.0.2-rest-framework 生鲜项目
一、首页相关-细节完善
1、首页轮播图
1)goods/serializers.py:新增轮播图序列化
class BannerSerializer(serializers.ModelSerializer): ''' 轮播图 ''' class Meta: model = Banner fields = "__all__"
2)goods/views.py:新增首页轮播图view
class BannerViewset(mixins.ListModelMixin, viewsets.GenericViewSet): """ 首页轮播图 """ queryset = Banner.objects.all().order_by("index") serializer_class = BannerSerializer
3)url配置:
# 配置首页轮播图的url router.register(r'banners', BannerViewset, base_name="banners")
2、首页-新品模块
使用的是url是goods(商品)下的url路由,所以只需要在filter(过滤器)中加上字段:is_new ,就可以了
class GoodsFilter(django_filters.rest_framework.FilterSet): ''' 自定义过滤器,实现区间过滤 商品过滤的类 ''' …… class Meta: model = Goods fields = ['pricemin', 'pricemax','top_category','is_hot','is_new'] # 新增‘is_new’字段
3、首页商品分类显示功能
首先是大类,然后里面有
- 商品商标(多个)
- 大类下的二级类
- 广告商品
- 所有商品
1)goods/models.py中新增表:
class IndexAd(models.Model): """ 首页类别标签右边展示的七个商品广告 """ category = models.ForeignKey(GoodsCategory, on_delete=models.CASCADE, related_name='category',verbose_name="商品类目") goods =models.ForeignKey(Goods, on_delete=models.CASCADE, related_name='goods') class Meta: verbose_name = '首页广告' verbose_name_plural = verbose_name def __str__(self): return self.goods.name
2)goods/xadmin.py:注册IndexAd
class IndexAdAdmin(object): list_display = ["category", "goods"] xadmin.site.register(IndexAd, IndexAdAdmin)
3)goods/serializers.py:
class BrandSerializer(serializers.ModelSerializer): ''' 大类下面的宣传商标 ''' class Meta: model = GoodsCategoryBrand fields = "__all__" class IndexCategorySerializer(serializers.ModelSerializer): #某个大类的商标,可以有多个商标,一对多的关系 brands = BrandSerializer(many=True) # good有一个外键category,但这个外键指向的是三级类,直接反向通过外键category(三级类),取某个大类下面的商品是取不出来的 goods = serializers.SerializerMethodField() # 在parent_category字段中定义的related_name="sub_cat" # 取二级商品分类 sub_cat = CategorySerializer2(many=True) # 广告商品 ad_goods = serializers.SerializerMethodField() def get_ad_goods(self, obj): goods_json = {} ad_goods = IndexAd.objects.filter(category_id=obj.id, ) if ad_goods: #取到这个商品Queryset[0] good_ins = ad_goods[0].goods #在serializer里面调用serializer的话,就要添加一个参数context(上下文request),嵌套serializer必须加 # serializer返回的时候一定要加 “.data” ,这样才是json数据 goods_json = GoodsSerializer(good_ins, many=False, context={'request': self.context['request']}).data return goods_json #自定义获取方法 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 class Meta: model = GoodsCategory fields = "__all__"
4)goods/views.py:
class IndexCategoryViewset(mixins.ListModelMixin, viewsets.GenericViewSet): """ 首页商品分类数据 """ # 获取is_tab=True(导航栏)里面的分类下的商品数据 queryset = GoodsCategory.objects.filter(is_tab=True, name__in=["生鲜食品", "酒水饮料"]) serializer_class = IndexCategorySerializer
5)urls.py配置:
# 首页系列商品展示url router.register(r'indexgoods', IndexCategoryViewset, base_name="indexgoods")
4、商品点击数跟收藏数
1)点击数
GoodsListViewSet其中继承了mixins.RetrieveModelMixin(获取商品详情),当用户访问某个商品详情时,让商品点击数加1
重写retrieve()方法
#商品点击数 + 1 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)
完整代码:
goods/views.py/GoodsListViewSet:
class GoodsListViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin,viewsets.GenericViewSet): ''' list: 商品列表,分页,搜索,过滤,排序 retrieve: 获取商品详情 ''' # authentication_classes = (TokenAuthentication,) #这里必须要定义一个默认的排序,否则会报错 queryset = Goods.objects.all().order_by('id') # 分页 pagination_class = GoodsPagination #序列化 serializer_class = GoodsSerializer filter_backends = (DjangoFilterBackend,filters.SearchFilter,filters.OrderingFilter) # 设置filter的类为我们自定义的类 #过滤 filter_class = GoodsFilter #搜索 search_fields = ('name', 'goods_brief', 'goods_desc') #排序 ordering_fields = ('sold_num', 'shop_price') #商品点击数 + 1 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)
2)商品收藏数
两种方法处理,第一种是重写源码相应方法,如:添加收藏,用户点击收藏时,相当于创建了收藏表数据,重写mixins.CreateModelMixin的perform_create方法就可以了:
user_operation/view.py下的UserFavViewset:
# 用户收藏的商品数量+1 def perform_create(self, serializer): instance = serializer.save() # 拿到的当前对象是收藏对象,封装着user、goods goods = instance.goods goods.fav_num += 1 goods.save()
完整代码:
user_operation/view.py/UserFavViewset
class UserFavViewset(viewsets.GenericViewSet, mixins.ListModelMixin, mixins.CreateModelMixin, mixins.DestroyModelMixin): ''' 用户收藏 ''' #permission是用来做权限判断的 # IsAuthenticated:必须登录用户;IsOwnerOrReadOnly:必须是当前登录的用户 permission_classes = (IsAuthenticated,IsOwnerOrReadOnly) #auth使用来做用户认证的 authentication_classes = (JSONWebTokenAuthentication,SessionAuthentication) #搜索的字段 lookup_field = 'goods_id' #动态选择serializer def get_serializer_class(self): if self.action == "list": return UserFavDetailSerializer elif self.action == "create": return UserFavSerializer return UserFavSerializer def get_queryset(self): #只能查看当前登录用户的收藏,不会获取所有用户的收藏 return UserFav.objects.filter(user=self.request.user) # 用户收藏的商品数量+1 def perform_create(self, serializer): instance = serializer.save() # 这里instance相当于UserFav model,通过它找到goods goods = instance.goods goods.fav_num += 1 goods.save()
用户取消收藏,商品收藏数减1,重写mixins.DestroyModelMixin下的perform_destroy(self, instance)方法,也是可以实现收藏数减1,具体实现可以自己尝试。
第二种方法是用信号量处理,代码分离性比较好,不需要在views逻辑页面中修改代码,比较推荐这种。
首先,在user_operation下新建py文件:signal.py,代码如下:
post_save:前面章节有介绍过,跟下面的一样,都是用来接收信号的方式。当数据被create或update时会触发此方法
post_delete:当数据被删除时会触发此方法
# users_operation/signals.py from django.db.models.signals import post_save,post_delete from django.dispatch import receiver from user_operation.models import UserFav # post_save:接收信号的方式 #sender: 接收信号的model @receiver(post_save, sender=UserFav) def create_UserFav(sender, instance=None, created=False, **kwargs): # 是否新建收藏,因为update的时候也会进行post_save,新建才让goods收藏+1 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()
然后,在user_operation/apps.py中添加代码:
def ready(self): import user_operation.signals
from django.apps import AppConfig class UserOperationConfig(AppConfig): name = 'user_operation' verbose_name = "操作管理" def ready(self): # 新增 import user_operation.signals
这样便完成了收藏 ± 1 操作
5、商品库存和销量修改
1)商品库存数量的行为:
- 新增商品到购物车
- 修改购物车数量
- 删除购物车记录
以重新方法的方式:
- CreateModelMixin/perform_create() 方法
- UpdateModelMixin/perform_update() 方法
- DestroyModelMixin/perform_destroy() 方法
trade/views.py/ShoppingCartViewset:
# 库存数-1 def perform_create(self, serializer): shop_cart = serializer.save() goods = shop_cart.goods goods.goods_num -= shop_cart.nums goods.save() # 库存数+1 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.get(id=serializer.instance.id) existed_nums = existed_record.nums # 先保存之前的数据existed_nums saved_record = serializer.save() #变化的数量 nums = saved_record.nums-existed_nums goods = saved_record.goods goods.goods_num -= nums goods.save()
完整代码:
class ShoppingCartViewset(viewsets.ModelViewSet): """ 购物车功能 list: 获取购物车详情 create: 加入购物车 delete: 删除购物记录 """ permission_classes = (IsAuthenticated, IsOwnerOrReadOnly) authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication) def get_serializer_class(self): if self.action == 'list': return ShopCartDetailSerializer else: return ShopCartSerializer lookup_field = "goods_id" def get_queryset(self): return ShoppingCart.objects.filter(user=self.request.user) # 库存数-1 def perform_create(self, serializer): shop_cart = serializer.save() goods = shop_cart.goods goods.goods_num -= shop_cart.nums goods.save() # 库存数+1 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.get(id=serializer.instance.id) existed_nums = existed_record.nums # 先保存之前的数据existed_nums saved_record = serializer.save() #变化的数量 nums = saved_record.nums-existed_nums goods = saved_record.goods goods.goods_num -= nums goods.save()
2)销量修改
商品的销量只有在支付成功后才会 +1
trade/views.py:
for order_good in order_goods: goods = order_good.goods goods.sold_num += order_good.goods_num goods.save()
完整代码:
class AlipayView(APIView): def get(self, request): """ 处理支付宝的return_url返回 """ processed_dict = {} # 1. 获取GET中参数 for key, value in request.GET.items(): processed_dict[key] = value # 2. 取出sign sign = processed_dict.pop("sign", None) # 3. 生成ALipay对象 alipay = AliPay( appid="2016091500517456", app_notify_url="http://47.93.198.159:8000/alipay/return/", app_private_key_path=private_key_path, alipay_public_key_path=ali_pub_key_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, debug=True, # 默认False, return_url="http://47.93.198.159:8000/alipay/return/" ) verify_re = alipay.verify(processed_dict, sign) # 这里可以不做操作。因为不管发不发return url。notify url都会修改订单状态。 if verify_re is True: order_sn = processed_dict.get('out_trade_no', None) trade_no = processed_dict.get('trade_no', None) trade_status = processed_dict.get('trade_status', None) existed_orders = OrderInfo.objects.filter(order_sn=order_sn) for existed_order in existed_orders: existed_order.pay_status = trade_status existed_order.trade_no = trade_no existed_order.pay_time = datetime.now() existed_order.save() response = redirect("/index/#/app/home/member/order") return response else: response = redirect("index") return response def post(self, request): """ 处理支付宝的notify_url """ #存放post里面所有的数据 processed_dict = {} #取出post里面的数据 for key, value in request.POST.items(): processed_dict[key] = value #把signpop掉,文档有说明 sign = processed_dict.pop("sign", None) #生成一个Alipay对象 alipay = AliPay( appid="2016091500517456", app_notify_url="http://47.93.198.159:8000/alipay/return/", app_private_key_path=private_key_path, alipay_public_key_path=ali_pub_key_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, debug=True, # 默认False, return_url="http://47.93.198.159:8000/alipay/return/" ) #进行验证 verify_re = alipay.verify(processed_dict, sign) # 如果验签成功 if verify_re is True: #商户网站唯一订单号 order_sn = processed_dict.get('out_trade_no', None) #支付宝系统交易流水号 trade_no = processed_dict.get('trade_no', None) #交易状态 trade_status = processed_dict.get('trade_status', None) # 查询数据库中订单记录 existed_orders = OrderInfo.objects.filter(order_sn=order_sn) for existed_order in existed_orders: # 订单商品项 order_goods = existed_order.goods.all() # 商品销量增加订单中数值 for order_good in order_goods: goods = order_good.goods goods.sold_num += order_good.goods_num goods.save() # 更新订单状态 existed_order.pay_status = trade_status existed_order.trade_no = trade_no existed_order.pay_time = datetime.now() existed_order.save() #需要返回一个'success'给支付宝,如果不返回,支付宝会一直发送订单支付成功的消息 return Response("success")
6、drf的缓存设置
为了加速网站的访问速度,将一些数据放到缓存当中,取数据的时候首先去缓存中去,然后再去数据库中取
我们用drf的一个扩展来实现缓存,github上面的使用说明:http://chibisov.github.io/drf-extensions/docs/#caching
一般在retrieve、list,有这两种方法需求的情况下需要缓存设置
使用:
1)安装:
pip install drf-extensions
2)view中使用:
导入模块:
from rest_framework_extensions.cache.mixins import CacheResponseMixin
在GoodsListViewSet中添加缓存功能:
#CacheResponseMixin一定要放在第一个位置 class GoodsListViewSet(CacheResponseMixin,mixins.ListModelMixin, mixins.RetrieveModelMixin,viewsets.GenericViewSet):
3)设置过期时间,settings里面:
#缓存配置 REST_FRAMEWORK_EXTENSIONS = { 'DEFAULT_CACHE_RESPONSE_TIMEOUT': 5 # 5秒过期 # 'DEFAULT_CACHE_RESPONSE_TIMEOUT': 60 *5 #5分钟过期 }
注意:上述缓存是存于内存中的,未写入磁盘,如果系统断电或者重启,缓存就不存在了。
7、drf配置redis缓存
使用django-redis第三方库
中文文档:http://django-redis-chs.readthedocs.io/zh_CN/latest/#id8
使用:
1)安装:
pip install django-redis
2)setting中配置:
# redis缓存 CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", } } }
8、drf的throttle设置api的访问速率
为了防止爬虫对服务器造成的重大压力,对数据进行访问速率限制就显得非常的重要了。
官方文档:http://www.django-rest-framework.org/api-guide/throttling/
throttle是drf自带的模块,因此不需要安装第三方包
使用:
1)setting配置:
REST_FRAMEWORK = { #限速设置 'DEFAULT_THROTTLE_CLASSES': ( 'rest_framework.throttling.AnonRateThrottle', #未登陆用户 'rest_framework.throttling.UserRateThrottle' #登陆用户 ), 'DEFAULT_THROTTLE_RATES': { 'anon': '18/minute', #每分钟可以请求18次 'user': '30/minute' #每分钟可以请求30次 } }
2)goods/views.py/GoodsListViewSet中使用 :
from rest_framework.throttling import UserRateThrottle,AnonRateThrottle class GoodsListViewSet(CacheResponseMixin,mixins.ListModelMixin, mixins.RetrieveModelMixin,viewsets.GenericViewSet): . . throttle_classes = (UserRateThrottle, AnonRateThrottle)