第一章、Django序列化操作
1.django的view實現商品列表頁(基於View類)
# 通過json來序列化,但手寫字典key代碼量較大,容易出錯;還有遇到時間,圖片序列化會報錯
from goods.base_views import Goodslistview url(r'^goods/$',Goodslistview.as_view(),name='goods_list'),
from datetime import datetime from django.db import models from DjangoUeditor.models import UEditorField class Goods(models.Model): """ 商品 """ category = models.ForeignKey(GoodsCategory, verbose_name="商品類目") goods_sn = models.CharField(max_length=50, default="", verbose_name="商品唯一貨號") name = models.CharField(max_length=100, verbose_name="商品名") click_num = models.IntegerField(default=0, verbose_name="點擊數") sold_num = models.IntegerField(default=0, verbose_name="商品銷售量") fav_num = models.IntegerField(default=0, verbose_name="收藏數") goods_num = models.IntegerField(default=0, verbose_name="庫存數") market_price = models.FloatField(default=0, verbose_name="市場價格") shop_price = models.FloatField(default=0, verbose_name="本店價格") goods_brief = models.TextField(max_length=500, verbose_name="商品簡短描述") goods_desc = UEditorField(verbose_name=u"內容", imagePath="goods/images/", width=1000, height=300, filePath="goods/files/", default='') ship_free = models.BooleanField(default=True, verbose_name="是否承擔運費") goods_front_image = models.ImageField(upload_to="goods/images/", null=True, blank=True, verbose_name="封面圖") is_new = models.BooleanField(default=False, verbose_name="是否新品") is_hot = models.BooleanField(default=False, verbose_name="是否熱銷") add_time = models.DateTimeField(default=datetime.now, verbose_name="添加時間") class Meta: verbose_name = '商品' verbose_name_plural = verbose_name def __str__(self): return self.name
from django.views.generic import View
from goods import models
from django.http import HttpResponse
class Goodslistview(View):
def get(self,requset):
"""
通過Django的views實現商品列表頁
:param requset:
:return:
"""
json_list = []
goods = models.Goods.objects.all()[0:10]
for good in goods:
json_dict = {}
json_dict['name'] = good.name
json_dict['category'] = good.category.name
json_dict['goods_sn'] = good.goods_sn
json_list.append(json_dict)
import json # 時間,圖片序列化會出錯
return HttpResponse(json.dumps(json_list),content_type="application/json")
# 還是通過json來序列化,model_to_dict方法不需要手寫字典key,可以將所有字段提取出來,但遇時間,圖片序列化會報錯
from django.views.generic import View
from goods import models
from django.http import HttpResponse
class Goodslistview(View):
def get(self,requset):
"""
通過Django的views實現商品列表頁
:param requset:
:return:
"""
json_list = []
goods = models.Goods.objects.all()[0:10]
from django.forms.models import model_to_dict
for good in goods:
json_dict = model_to_dict(good)
json_list.append(json_dict)
import json
return HttpResponse(json.dumps(json_list),content_type="application/json")
2. Django的serializers序列化(基於View類,可以序列化時間,圖片)
from django.views.generic import View
from goods import models
from django.http import HttpResponse
class Goodslistview(View):
def get(self,requset):
"""
通過Django的views實現商品列表頁
:param requset:
:return:
"""
goods = models.Goods.objects.all()[0:10]
from django.core import serializers
json_data = serializers.serialize('json',goods)
return HttpResponse(json_data,content_type="application/json")
# 下面一種也可以
# import json
# json_data = json.loads(json_data)
# return JsonResponse(json_data,safe=False) #JsonResponse內部有dumps
注:django的序列化這么好用了,為啥還要用Django ESRT framework?圖片保存的是相對路徑,第三方端需要手動加域名;文檔生成等問題。
二、Django REST framework操作
一、商品詳情頁功能
1.配置及安裝模塊
# 安裝模塊
pip install djangorestframework -i https://pypi.doubanio.com/simple # 豆瓣鏡像 pip install markdown # Markdown support for the browsable API. pip install django-filter pip install django-guardian # 對象級別權限控制 pip install coreapi # 支持Django Rest Framework文檔
# 配置settings
'rest_framework',
# url權限
from goods.views import GoodsList
url(r'^goods/$',GoodsList.as_view(),name='goods_list'),
# url添加,用於生成Django Rest Framework自動文檔
from rest_framework.documentation import include_docs_urls
url(r'docs/', include_docs_urls(title="商城")),
# 設置Django Rest Framework登錄url,方便調試api
from django.conf.urls import include
url(r'^api-auth/', include('rest_framework.urls'))
# modes文件
from datetime import datetime from django.db import models from DjangoUeditor.models import UEditorField # Create your models here. class GoodsCategory(models.Model): """ 商品類別 """ CATEGORY_TYPE = ( (1, "一級類目"), (2, "二級類目"), (3, "三級類目"), ) name = models.CharField(default="", max_length=30, verbose_name="類別名", help_text="類別名") code = models.CharField(default="", max_length=30, verbose_name="類別code", help_text="類別code") desc = models.TextField(default="", verbose_name="類別描述", help_text="類別描述") category_type = models.IntegerField(choices=CATEGORY_TYPE, verbose_name="類目級別", help_text="類目級別") parent_category = models.ForeignKey("self", null=True, blank=True, verbose_name="父類目級別", help_text="父目錄", related_name="sub_cat") is_tab = models.BooleanField(default=False, verbose_name="是否導航", help_text="是否導航") add_time = models.DateTimeField(default=datetime.now, verbose_name="添加時間") class Meta: verbose_name = "商品類別" verbose_name_plural = verbose_name def __str__(self): return self.name class GoodsCategoryBrand(models.Model): """ 品牌名 """ category = models.ForeignKey(GoodsCategory, related_name='brands', null=True, blank=True, verbose_name="商品類目") name = models.CharField(default="", max_length=30, verbose_name="品牌名", help_text="品牌名") desc = models.TextField(default="", max_length=200, verbose_name="品牌描述", help_text="品牌描述") image = models.ImageField(max_length=200, upload_to="brands/") add_time = models.DateTimeField(default=datetime.now, verbose_name="添加時間") class Meta: verbose_name = "品牌" verbose_name_plural = verbose_name db_table = "goods_goodsbrand" def __str__(self): return self.name class Goods(models.Model): """ 商品 """ category = models.ForeignKey(GoodsCategory, verbose_name="商品類目") goods_sn = models.CharField(max_length=50, default="", verbose_name="商品唯一貨號") name = models.CharField(max_length=100, verbose_name="商品名") click_num = models.IntegerField(default=0, verbose_name="點擊數") sold_num = models.IntegerField(default=0, verbose_name="商品銷售量") fav_num = models.IntegerField(default=0, verbose_name="收藏數") goods_num = models.IntegerField(default=0, verbose_name="庫存數") market_price = models.FloatField(default=0, verbose_name="市場價格") shop_price = models.FloatField(default=0, verbose_name="本店價格") goods_brief = models.TextField(max_length=500, verbose_name="商品簡短描述") goods_desc = UEditorField(verbose_name=u"內容", imagePath="goods/images/", width=1000, height=300, filePath="goods/files/", default='') ship_free = models.BooleanField(default=True, verbose_name="是否承擔運費") goods_front_image = models.ImageField(upload_to="goods/images/", null=True, blank=True, verbose_name="封面圖") is_new = models.BooleanField(default=False, verbose_name="是否新品") is_hot = models.BooleanField(default=False, verbose_name="是否熱銷") add_time = models.DateTimeField(default=datetime.now, verbose_name="添加時間") class Meta: verbose_name = '商品' verbose_name_plural = verbose_name def __str__(self): return self.name class IndexAd(models.Model): category = models.ForeignKey(GoodsCategory, related_name='category', verbose_name="商品類目") goods = models.ForeignKey(Goods, related_name='goods') class Meta: verbose_name = '首頁商品類別廣告' verbose_name_plural = verbose_name def __str__(self): return self.goods.name class GoodsImage(models.Model): """ 商品輪播圖 """ goods = models.ForeignKey(Goods, verbose_name="商品", related_name="images") image = models.ImageField(upload_to="", verbose_name="圖片", null=True, blank=True) add_time = models.DateTimeField(default=datetime.now, verbose_name="添加時間") class Meta: verbose_name = '商品圖片' verbose_name_plural = verbose_name def __str__(self): return self.goods.name class Banner(models.Model): """ 輪播的商品 """ goods = models.ForeignKey(Goods, verbose_name="商品") image = models.ImageField(upload_to='banner', verbose_name="輪播圖片") index = models.IntegerField(default=0, verbose_name="輪播順序") add_time = models.DateTimeField(default=datetime.now, verbose_name="添加時間") class Meta: verbose_name = '輪播商品' verbose_name_plural = verbose_name def __str__(self): return self.goods.name class HotSearchWords(models.Model): """ 熱搜詞 """ keywords = models.CharField(default="", max_length=20, verbose_name="熱搜詞") index = models.IntegerField(default=0, verbose_name="排序") add_time = models.DateTimeField(default=datetime.now, verbose_name="添加時間") class Meta: verbose_name = '熱搜詞' verbose_name_plural = verbose_name def __str__(self): return self.keywords
from datetime import datetime from django.db import models from users.models import UserProfile from goods.models import Goods # User = get_user_model() # Create your models here. class ShoppingCart(models.Model): """ 購物車 """ user = models.ForeignKey(UserProfile, verbose_name=u"用戶") goods = models.ForeignKey(Goods, verbose_name=u"商品") nums = models.IntegerField(default=0, verbose_name="購買數量") add_time = models.DateTimeField(default=datetime.now, verbose_name=u"添加時間") class Meta: verbose_name = '購物車' verbose_name_plural = verbose_name unique_together = ("user", "goods") def __str__(self): return "%s(%d)".format(self.goods.name, self.nums) class OrderInfo(models.Model): """ 訂單 """ ORDER_STATUS = ( ("TRADE_SUCCESS", "成功"), ("TRADE_CLOSED", "超時關閉"), ("WAIT_BUYER_PAY", "交易創建"), ("TRADE_FINISHED", "交易結束"), ("paying", "待支付"), ) user = models.ForeignKey(UserProfile, verbose_name="用戶") order_sn = models.CharField(max_length=30, null=True, blank=True, unique=True, verbose_name="訂單號") trade_no = models.CharField(max_length=100, unique=True, null=True, blank=True, verbose_name=u"交易號") pay_status = models.CharField(choices=ORDER_STATUS, default="paying", max_length=30, verbose_name="訂單狀態") post_script = models.CharField(max_length=200, verbose_name="訂單留言") order_mount = models.FloatField(default=0.0, verbose_name="訂單金額") pay_time = models.DateTimeField(null=True, blank=True, verbose_name="支付時間") # 用戶信息 address = models.CharField(max_length=100, default="", verbose_name="收貨地址") signer_name = models.CharField(max_length=20, default="", verbose_name="簽收人") singer_mobile = models.CharField(max_length=11, verbose_name="聯系電話") add_time = models.DateTimeField(default=datetime.now, verbose_name="添加時間") class Meta: verbose_name = u"訂單" verbose_name_plural = verbose_name def __str__(self): return str(self.order_sn) class OrderGoods(models.Model): """ 訂單的商品詳情 """ order = models.ForeignKey(OrderInfo, verbose_name="訂單信息", related_name="goods") goods = models.ForeignKey(Goods, verbose_name="商品") goods_num = models.IntegerField(default=0, verbose_name="商品數量") add_time = models.DateTimeField(default=datetime.now, verbose_name="添加時間") class Meta: verbose_name = "訂單商品" verbose_name_plural = verbose_name def __str__(self): return str(self.order.order_sn)
from datetime import datetime from django.db import models from users.models import UserProfile # from django.contrib.auth import get_user_model # settings中設置了AUTH_USER_MODEL # User = get_user_model() from goods.models import Goods class UserFav(models.Model): """ 用戶收藏 """ user = models.ForeignKey(UserProfile, verbose_name="用戶") goods = models.ForeignKey(Goods, verbose_name="商品", help_text="商品id") add_time = models.DateTimeField(default=datetime.now, verbose_name=u"添加時間") class Meta: verbose_name = '用戶收藏' verbose_name_plural = verbose_name unique_together = ("user", "goods") def __str__(self): return self.user.username class UserLeavingMessage(models.Model): """ 用戶留言 """ MESSAGE_CHOICES = ( (1, "留言"), (2, "投訴"), (3, "詢問"), (4, "售后"), (5, "求購") ) user = models.ForeignKey(UserProfile, verbose_name="用戶") message_type = models.IntegerField(default=1, choices=MESSAGE_CHOICES, verbose_name="留言類型", help_text=u"留言類型: 1(留言),2(投訴),3(詢問),4(售后),5(求購)") subject = models.CharField(max_length=100, default="", verbose_name="主題") message = models.TextField(default="", verbose_name="留言內容", help_text="留言內容") file = models.FileField(upload_to="message/images/", verbose_name="上傳的文件", help_text="上傳的文件") add_time = models.DateTimeField(default=datetime.now, verbose_name="添加時間") class Meta: verbose_name = "用戶留言" verbose_name_plural = verbose_name def __str__(self): return self.subject class UserAddress(models.Model): """ 用戶收貨地址 """ user = models.ForeignKey(UserProfile, verbose_name="用戶") province = models.CharField(max_length=100, default="", verbose_name="省份") city = models.CharField(max_length=100, default="", verbose_name="城市") district = models.CharField(max_length=100, default="", verbose_name="區域") address = models.CharField(max_length=100, default="", verbose_name="詳細地址") signer_name = models.CharField(max_length=100, default="", verbose_name="簽收人") signer_mobile = models.CharField(max_length=11, default="", verbose_name="電話") add_time = models.DateTimeField(default=datetime.now, verbose_name="添加時間") class Meta: verbose_name = "收貨地址" verbose_name_plural = verbose_name def __str__(self): return self.address
from datetime import datetime from django.db import models from django.contrib.auth.models import AbstractUser # Create your models here. class UserProfile(AbstractUser): """ 用戶 """ name = models.CharField(max_length=30, null=True, blank=True, verbose_name="姓名") birthday = models.DateField(null=True, blank=True, verbose_name="出生年月") gender = models.CharField(max_length=6, choices=(("male", u"男"), ("female", "女")), default="female", verbose_name="性別") mobile = models.CharField(null=True, blank=True, max_length=11, verbose_name="電話") email = models.EmailField(max_length=100, null=True, blank=True, verbose_name="郵箱") class Meta: verbose_name = "用戶" verbose_name_plural = verbose_name def __str__(self): return self.username class VerifyCode(models.Model): """ 短信驗證碼 """ code = models.CharField(max_length=10, verbose_name="驗證碼") mobile = models.CharField(max_length=11, verbose_name="電話") add_time = models.DateTimeField(default=datetime.now, verbose_name="添加時間") class Meta: verbose_name = "短信驗證碼" verbose_name_plural = verbose_name def __str__(self): return self.code
2.APIView方式實現商品列表頁(基於APIView類)
# 通過一個簡單的Class-based Views實例,serializers序列化指定字段,官方文檔https://www.django-rest-framework.org/tutorial/3-class-based-views/
# url.py
from goods.views import GoodsList urlpatterns = [ url(r'^goods/$',GoodsList.as_view(),name='goods_list'), ]
# goods/views.py
from goods.serializers import GoodsSerializer
from rest_framework.views import APIView
from rest_framework.response import Response
from goods.models import Goods
class GoodsList(APIView):
"""
List all goods
"""
def get(self, request, format=None):
goods = Goods.objects.all()[:10]
goods_serializer = GoodsSerializer(goods, many=True)
return Response(goods_serializer.data)
# goods/serializers.py
from rest_framework import serializers
class GoodsSerializer(serializers.Serializer):
name = serializers.CharField(required=True,max_length=100)
click_num = serializers.IntegerField(default=0)

# 接受前端提交數據並保存數據庫(基於APIView類)
# goods/views.py(新增post方法)
from goods.models import Goods from goods.serializers import GoodsSerializer from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status class GoodsList(APIView): """ List all goods """ def get(self, request, format=None): goods = Goods.objects.all()[:10] goods_serializer = GoodsSerializer(goods, many=True) return Response(goods_serializer.data) def post(self, request, format=None): serializer = GoodsSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# goods/serializers.py(重載create方法)
from rest_framework import serializers from goods.models import Goods class GoodsSerializer(serializers.Serializer): name = serializers.CharField(required=True,max_length=100) click_num = serializers.IntegerField(default=0) goods_front_image = serializers.ImageField() # 創建並返回一個新的“user”對象 , 給予驗證數據。 def create(self, validated_data): """ Create and return a new `Goods` instance, given the validated data. """ return Goods.objects.create(**validated_data)

3.drf的model serializer實現商品的列表頁(基於APIView類):
1).serializer可以序列化所有字段 | 自定制字段
# url.py
from goods.views import GoodsList
urlpatterns = [
url(r'^goods/$',GoodsList.as_view(),name='goods_list'),
]
# goods/views.py
from goods.models import Goods from goods.serializers import GoodsSerializer from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status class GoodsList(APIView): """ List all goods """ def get(self, request, format=None): goods = Goods.objects.all()[:10] goods_serializer = GoodsSerializer(goods, many=True) return Response(goods_serializer.data) def post(self, request, format=None): serializer = GoodsSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# goods/serializers.py
from rest_framework import serializers
from goods.models import Goods
class GoodsSerializer(serializers.ModelSerializer):
class Meta:
model = Goods
# 自定制字段
# fields = ('id', 'category', 'goods_sn', 'name', 'goods_front_image', 'add_time')
# 所有字段
fields = "__all__"

2).外鍵category只打印了ID,沒有打印里面內容,Serialzer還可以嵌套使用,覆蓋外鍵字段
# url.py
from goods.views import GoodsList
urlpatterns = [
url(r'^goods/$',GoodsList.as_view(),name='goods_list'),
]
# goods/views.py
from goods.models import Goods from goods.serializers import GoodsSerializer from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status class GoodsList(APIView): """ List all goods """ def get(self, request, format=None): goods = Goods.objects.all()[:10] goods_serializer = GoodsSerializer(goods, many=True) return Response(goods_serializer.data) def post(self, request, format=None): serializer = GoodsSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# goods/serializers.py
from rest_framework import serializers
from goods.models import Goods,GoodsCategory
# ModelSerializer實現商品分類
class GoodsCategorySerializer(serializers.ModelSerializer):
class Meta:
model = GoodsCategory
fields = "__all__"
# ModelSerializer實現商品列表頁
class GoodsSerializer(serializers.ModelSerializer):
# 覆蓋外鍵字段,category為外鍵字段,對商品分類實例化
category = GoodsCategorySerializer()
class Meta:
model = Goods
# fields = ('id', 'category', 'goods_sn', 'name', 'goods_front_image', 'add_time')
fields = "__all__"

出現問題;
1.如果安裝coreapi出現utf_8錯誤:
將虛擬環境-->lib-->site-pakeages-->pip-->compat-->__init__.py 文件中的75行utf_8修改成gbk,
修改后卸載pip uninstall coreapi MarkupSafe再重新安裝pip install coreapi
2.報錯:rest_framework.request.WrappedAttributeError: 'CSRFCheck' object has no attribute 'process_request'
1.打開rest_framework的settings,D:\daly\PycharmProjects\VueShop\Lib\site-packages\rest_framework\settings.py,注釋掉43,44行
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication'
2.設置Django的settings
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
]
}
3.報錯'AutoSchema' object has no attribute 'get_link'
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'
}
4.報錯__str__ returned non-string(type NoneType)
# 原因分析:調用__str__方法返回了一個None值,是因為model中的name字段設置了參數為null=True, blank=True # 解決辦法:選擇一個不為空參數的字段 def __str__(self): return self.username
4.GenericAPIView實現商品列表頁(基於GenericAPIView類)
1).mixins.ListModelMixin + generics.GenericAPIView
- mixins.ListModelMixin里面list方法做好了分頁和序列化的工作,但是需要我們自己重寫get方法
- GenericAPIView繼承APIView,封裝了很多方法,比APIView功能更強大
# url.py
from goods.views import GoodsList urlpatterns = [ url(r'^goods/$',GoodsList.as_view(),name='goods_list'), ]
# goods/views.py
from goods.serializers import GoodsSerializer from rest_framework import mixins from rest_framework import generics from goods.models import Goods class GoodsList(mixins.ListModelMixin,generics.GenericAPIView): queryset = Goods.objects.all()[:10] # queryset不可以更改 serializer_class = GoodsSerializer # 使用mixins.ListModelMixin,需要重寫get方法 def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs)
# goods/serializers.py
from rest_framework import serializers
from goods.models import Goods,GoodsCategory
class GoodsCategorySerializer(serializers.ModelSerializer):
class Meta:
model = GoodsCategory
fields = "__all__"
class GoodsSerializer(serializers.ModelSerializer):
category = GoodsCategorySerializer() # category為外鍵字段,實例化操作
class Meta:
model = Goods
# fields = ('id', 'category', 'goods_sn', 'name', 'goods_front_image', 'add_time')
fields = "__all__"

2).generics.ListAPIView(上面的升級版)
- 繼承了mixins.ListModelMixin, GenericAPIView,
- 里面還內置get方法
# url.py
urlpatterns = [ url(r'^goods/$',GoodsList.as_view(),name='goods_list'), ]
# settings配置每頁展示數據10個
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'PAGE_SIZE': 10, # 每頁展示數據10個
'DEFAULT_PERMISSION_CLASSES': [
]
}
# views.py文件
from goods.serializers import GoodsSerializer
from rest_framework import generics
from goods.models import Goods
class GoodsList(generics.ListAPIView):
queryset = Goods.objects.all().order_by('goods__category_id') # queryset不可以更改
serializer_class = GoodsSerializer
# serializers.py文件
from rest_framework import serializers
from goods.models import Goods,GoodsCategory
class GoodsCategorySerializer(serializers.ModelSerializer):
class Meta:
model = GoodsCategory
fields = "__all__"
class GoodsSerializer(serializers.ModelSerializer):
category = GoodsCategorySerializer() # category為外鍵字段,實例化操作
class Meta:
model = Goods
# fields = ('id', 'category', 'goods_sn', 'name', 'goods_front_image', 'add_time')
fields = "__all__"

generics.py(繼承mixins)
class GenericAPIView(views.APIView): # 有查詢filter,分頁pagination,序列化數據等 class CreateAPIView(mixins.CreateModelMixin, GenericAPIView): # 內置post創建方法 class ListAPIView(mixins.ListModelMixin, GenericAPIView): # 內置get列表方法 class RetrieveAPIView(mixins.RetrieveModelMixin, GenericAPIView): # 內置get詳情方法 class DestroyAPIView(mixins.DestroyModelMixin, GenericAPIView): # 內置delete刪除方法 class UpdateAPIView(mixins.UpdateModelMixin, GenericAPIView): # 內置put更新,patch部分更新方法 class ListCreateAPIView(mixins.ListModelMixin, mixins.CreateModelMixin, GenericAPIView): # 內置get列表方法,post創建方法 class RetrieveUpdateAPIView(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, GenericAPIView): # 內置get詳情方法,put更新,patch部分更新方法 class RetrieveDestroyAPIView(mixins.RetrieveModelMixin, mixins.DestroyModelMixin, GenericAPIView): # 內置get詳情方法,delete刪除方法 class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, GenericAPIView): # 內置get詳情方法,put更新,patch部分更新方法,delete刪除
出現問題:
1.settings.py設置了'PAGE_SIZE': 10后出現下面出警告:
System check identified some issues:
WARNINGS:
?: (rest_framework.W001) You have specified a default PAGE_SIZE pagination rest_framework setting,without specifying also a DEFAULT_PAGINATION_CLASS.
HINT: The default for DEFAULT_PAGINATION_CLASS is None. In previous versions this was PageNumberPagination. If you wish to define PAGE_SIZE globally whilst defining pagination_class on a per-view basis you may silence this check.
解決方案:將D:\daly\PycharmProjects\VueShop\Lib\site-packages\rest_framework\settings.py文件中的55代碼'DEFAULT_PAGINATION_CLASS': None,修改成
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
2.UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list:
解決方案:給views.py視圖添加order_by()方法排序即可
class GoodsList(generics.ListAPIView):
queryset = Goods.objects.all().order_by('goods__category_id') # queryset不可以更改
serializer_class = GoodsSerializer
3).自定制顯示頁碼
# goods/views.py
from goods.serializers import GoodsSerializer
from rest_framework import generics
from goods.models import Goods
from rest_framework.pagination import PageNumberPagination
class GooodsPagination(PageNumberPagination):
"""
商品列表自定義分頁
"""
page_size = 10 # 每頁顯示條數
page_size_query_param = 'page_size' # 頁顯示條數名字
page_query_param = 'p' # 頁碼名字
max_page_size = 10000 # 每頁最大顯示條數
class GoodsList(generics.ListAPIView):
queryset = Goods.objects.all().order_by('goods__category_id') # queryset變量名為固定格式
serializer_class = GoodsSerializer
pagination_class = GooodsPagination
# stttings.py,注釋掉之前的頁碼設置
# 'PAGE_SIZE': 10,
# goods/serializers.py
from rest_framework import serializers
from goods.models import Goods,GoodsCategory
class GoodsCategorySerializer(serializers.ModelSerializer):
class Meta:
model = GoodsCategory
fields = "__all__"
class GoodsSerializer(serializers.ModelSerializer):
category = GoodsCategorySerializer() # category為外鍵字段,實例化操作
class Meta:
model = Goods
# fields = ('id', 'category', 'goods_sn', 'name', 'goods_front_image', 'add_time')
fields = "__all__"

5.ViewSets & Routers完成商品列表頁(基於ViewSet類+Routers)
官方文檔地址:https://www.django-rest-framework.org/tutorial/6-viewsets-and-routers/
1).ViewSets (單獨使用)
- mixins.ListModelMixin + viewsets.GenericViewSet ,視圖函數中不需要重寫get方法,是因為url中綁定了'get': 'list'
- 每寫一個類,單獨使用ViewSets都需要綁定,多的情況下比較麻煩,后面的Routers配合ViewSets可以實現自動綁定各種方法
# url.py
from django.conf.urls import url
from goods.views import GoodsListViewSet
goods_list = GoodsListViewSet.as_view({
'get': 'list',
})
urlpatterns = [
# 商品展示url
url(r'^goods/$',goods_list,name='goods_list'),
]
# goods/views.py
from goods.serializers import GoodsSerializer
from goods.models import Goods
from rest_framework.pagination import PageNumberPagination
from rest_framework import viewsets
from rest_framework import mixins
class GooodsPagination(PageNumberPagination):
page_size = 10 # 每頁顯示條數
page_size_query_param = 'page_size'
page_query_param = 'p' # 頁碼名字
max_page_size = 10000 # 每頁最大顯示條數
class GoodsListViewSet(mixins.ListModelMixin,viewsets.GenericViewSet): # 繼承ViewSet類
queryset = Goods.objects.all().order_by('goods__category_id') # queryset不可以更改
serializer_class = GoodsSerializer
pagination_class = GooodsPagination
# goods/serializers.py
from rest_framework import serializers
from goods.models import Goods,GoodsCategory
class GoodsCategorySerializer(serializers.ModelSerializer):
class Meta:
model = GoodsCategory
fields = "__all__"
class GoodsSerializer(serializers.ModelSerializer):
category = GoodsCategorySerializer() # category為外鍵字段,實例化操作
class Meta:
model = Goods
# fields = ('id', 'category', 'goods_sn', 'name', 'goods_front_image', 'add_time')
fields = "__all__"
# 綁定的方式共有以下好幾種,上面只用了'get': 'list'一種綁定方法
goods_detail = GoodsListViewSet.as_view({
'get': 'list',
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
})
2).Routers(配合ViewSets一起使用)
- register會自動將我們的默認的操作做綁定,如get轉到list上去
# url.py
# url.py
from django.conf.urls import url,include
from goods.views import GoodsListViewSet
from rest_framework.routers import DefaultRouter
# goods_list = GoodsListViewSet.as_view({
# 'get': 'list',
# })
# 生成router對象
router = DefaultRouter()
# 配置goods的url
router.register(r'goods', GoodsListViewSet)
urlpatterns = [
# router調用url時,自動將我們注冊的東西全部轉化為url配置
url(r'^', include(router.urls)),
# 商品展示url
# url(r'^goods/$',goods_list,name='goods_list'),
]
# goods/views.py
from goods.serializers import GoodsSerializer
from goods.models import Goods
from rest_framework.pagination import PageNumberPagination
from rest_framework import viewsets
from rest_framework import mixins
class GooodsPagination(PageNumberPagination):
page_size = 10 # 每頁顯示條數
page_size_query_param = 'page_size'
page_query_param = 'p' # 頁碼名字
max_page_size = 10000 # 每頁最大顯示條數
class GoodsListViewSet(mixins.ListModelMixin,viewsets.GenericViewSet): # 繼承ViewSet類
queryset = Goods.objects.all().order_by('goods__category_id') # queryset不可以更改
serializer_class = GoodsSerializer
pagination_class = GooodsPagination
# goods/serializers.py
from rest_framework import serializers
from goods.models import Goods,GoodsCategory
class GoodsCategorySerializer(serializers.ModelSerializer):
class Meta:
model = GoodsCategory
fields = "__all__"
class GoodsSerializer(serializers.ModelSerializer):
category = GoodsCategorySerializer() # category為外鍵字段,實例化操作
class Meta:
model = Goods
# fields = ('id', 'category', 'goods_sn', 'name', 'goods_front_image', 'add_time')
fields = "__all__"
6.drf的APIView、GenericView、viewsets和router的原理分析
viewsets.py(繼承mixins,generics) # 1.結合Routers使用 2.initialize_request中的action方便后期的動態serializers操作
class ViewSetMixin(object): # as_view,initialize_request方法,initialize_request中的action方便后期的動態serializers操作
class ViewSet(ViewSetMixin, views.APIView):pass
class GenericViewSet(ViewSetMixin, generics.GenericAPIView):pass
class ReadOnlyModelViewSet(mixins.RetrieveModelMixin, mixins.ListModelMixin, GenericViewSet):pass
class ModelViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, GenericViewSet):pass
mixins.py
class CreateModelMixin(object): # 里面有create方法,試圖函數需要重寫post方法
class ListModelMixin(object): # 里面有list方法,過濾,分頁,試圖函數需要重寫get方法(列表頁)
class RetrieveModelMixin(object): # 里面有retrieve方法,獲取某個具體商品信息,試圖函數需要重寫get方法(詳情頁)
class UpdateModelMixin(object): # 里面有update,partial_update方法,部分更新,全部更新,試圖函數需要重寫put方法,
class DestroyModelMixin(object): # 里面有destroy方法,試圖函數需要重寫delete方法
generics.py(繼承mixins)
class GenericAPIView(views.APIView): # 有過濾filter,分頁pagination,序列化數據等
class CreateAPIView(mixins.CreateModelMixin, GenericAPIView): # 內置post創建方法
class ListAPIView(mixins.ListModelMixin, GenericAPIView): # 內置get列表方法
class RetrieveAPIView(mixins.RetrieveModelMixin, GenericAPIView): # 內置get詳情方法
class DestroyAPIView(mixins.DestroyModelMixin, GenericAPIView): # 內置delete刪除方法
class UpdateAPIView(mixins.UpdateModelMixin, GenericAPIView): # 內置put更新,patch部分更新方法
class ListCreateAPIView(mixins.ListModelMixin, mixins.CreateModelMixin, GenericAPIView): # 內置get列表方法,post創建方法
class RetrieveUpdateAPIView(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, GenericAPIView): # 內置get詳情方法,put更新,patch部分更新方法
class RetrieveDestroyAPIView(mixins.RetrieveModelMixin, mixins.DestroyModelMixin, GenericAPIView): # 內置get詳情方法,delete刪除方法
class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, GenericAPIView): # 內置get詳情方法,put更新,patch部分更新方法,delete刪除
# 小結
使用 mixins.ListModelMixin + GenericAPIViewView時,視圖函數需要寫get方法
使用 ListAPIView時,繼承了(mixins.ListModelMixin, GenericAPIView)兩個類,ListAPIView類本身有get方法,View視圖函數不用寫get方法
使用 ViewSetMixin + mixins.ListModelMixin時,視圖函數不需要寫get方法,但時url里需要配置goods_list = GoodsListViewSet.as_view({'get': 'list',},)每一個類都需要寫綁定方法
使用 router+ViewSet,router中register會自動將我們的默認的操作做綁定,如get轉到list上去
# 單獨使用ViewSets就需要綁定對應方法
goods_detail = GoodsListViewSet.as_view({
'get': 'list',
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
})
# GenericViewSet,GenericAPIView,APIView,View之間的關系
class GenericViewSet(ViewSetMixin, generics.GenericAPIView): --drf
class GenericAPIView(views.APIView): --drf
class APIView(View): --drf
7.drf的過濾,搜索,排序
# 官方文檔:https://www.django-rest-framework.org/api-guide/filtering/(過濾,搜索,排序)
# django-filter官方文檔:https://django-filter.readthedocs.io/en/master/ (過濾)
為什么有drf的過濾還要用django-filter的過濾?django-filter可以定制過濾功能,如模糊匹配,區間匹配,drf的過濾只能精確匹配
- DjangoFilterBackend:定制過濾功能
- SearchFilter;定制搜索功能
- OrderingFilter:定制排序功能
# settings apps添加:
'django_filters',
# goods/views.py
from goods.serializers import GoodsSerializer
from rest_framework import generics
from goods.models import Goods
from rest_framework.pagination import PageNumberPagination
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters
from goods.filters import GoodsFilter
class GooodsPagination(PageNumberPagination):
"""
商品列表自定義分頁
"""
page_size = 10 # 每頁顯示條數
page_size_query_param = 'page_size' # 修改"每頁顯示條數"
page_query_param = 'page' # 頁碼參數名字
max_page_size = 10000 # 最多顯示頁碼數
class GoodsListViewSet(mixins.ListModelMixin,viewsets.GenericViewSet):
"""
商品列表頁,分頁,過濾器,搜索,排序
"""
queryset = Goods.objects.all().order_by('goods__category_id') # queryset不可以更改
serializer_class = GoodsSerializer # 引用序列化相應的對象
pagination_class = GooodsPagination # 引用商品列表自定義分頁
filter_backends = (DjangoFilterBackend,filters.SearchFilter,filters.OrderingFilter) # 過濾器,搜索,排序
# filter_fields = ('name', 'market_price') # 過濾精確字段
filter_class = GoodsFilter # 過濾
search_fields = ('name', 'goods_brief','goods_desc') # 搜索字段
ordering_fields = ('click_num', 'add_time') # 排序字段
# goods/filters.py
import django_filters from goods.models import Goods class GoodsFilter(django_filters.rest_framework.FilterSet): """ 商品的過濾類 """ pricemin = django_filters.NumberFilter(field_name="shop_price",help_text ='最低價格',lookup_expr='gte') # 新版已改成field_name,使用name會報錯 pricemax = django_filters.NumberFilter(field_name="shop_price",help_text ='最高價格',lookup_expr='lte') name = django_filters.CharFilter(field_name='name',lookup_expr='icontains') class Meta: model = Goods fields = ['pricemin','pricemax','name']
# goods/serializers.py
from rest_framework import serializers
from goods.models import Goods,GoodsCategory
class GoodsCategorySerializer(serializers.ModelSerializer):
class Meta:
model = GoodsCategory
fields = "__all__"
class GoodsSerializer(serializers.ModelSerializer):
category = GoodsCategorySerializer() # category為外鍵字段,實例化操作
class Meta:
model = Goods
# fields = ('id', 'category', 'goods_sn', 'name', 'goods_front_image', 'add_time')
fields = "__all__"

問題點:
1.出現REST framework TypeError: __init__() got an unexpected keyword argument 'name'錯誤
解決方法:
最新版本的django-filter 參數名字已經由name 更改為 field_name
price_min = django_filters.NumberFilter(field_name='shop_price', lookup_expr='gte')
二、商品類別數據和vue展示
1.創建商品類別數據接口
- 全部商品分類:一級二級三級 (ListModelMixin商品類別列表頁)
- 商品某一級大類詳情 實現商品分類列表+商品總件數+商品列表頁 (RetrieveModelMixin商品類別詳情頁,url末尾添加大類的ID即可查詢,restFul規范)


# urls.py
from goods.views import GoodsListViewSet,CategoryViewSet # 配置categorys的url router.register(r'categorys', CategoryViewSet,base_name='categorys')
# goods/views.py
from goods.serializers import GoodsSerializer,CategorySerializer from goods.models import Goods,GoodsCategory class CategoryViewSet(mixins.ListModelMixin,mixins.RetrieveModelMixin,viewsets.GenericViewSet): """ list: 商品分類列表數據 retrieve: 獲取商品分類詳情 """ queryset = GoodsCategory.objects.filter(category_type=1) # 取出所有對象 serializer_class = CategorySerializer # 序列化相應的對象
# goods/serializers.py
from rest_framework import serializers from goods.models import Goods,GoodsCategory class CategorySerializer3(serializers.ModelSerializer): class Meta: model = GoodsCategory fields = "__all__" class CategorySerializer2(serializers.ModelSerializer): sub_cat = CategorySerializer3(many=True) class Meta: model = GoodsCategory fields = "__all__" class CategorySerializer(serializers.ModelSerializer): sub_cat = CategorySerializer2(many=True) class Meta: model = GoodsCategory fields = "__all__"
我們需要遵循restful api 對於某一個商品詳情獲取的推薦,也就是GET /category/ID:獲取某個指定分類的信息
其實這個工作 viewset 已經幫我們做了,一但我們進行了register的注冊。只要我們繼承了 RetrieveModelMixin 就可以直接通過id進行獲取。

問題點:
1.Got AttributeError when attempting to get a value for field `sub_cat` on serializer `CategorySerializer2`.
The serializer field might be named incorrectly and not match any attribute or key on the `RelatedManager` instance.
Original exception text was: 'RelatedManager' object has no attribute 'sub_cat'.
解決方法:
sub_cat = CategorySerializer2(many=True)
2.跨域
# 前端項目,為了方便調試,把src/api/api.js文件上線IP更改為本地IP
// let host = 'http://47.107.36.249:8001'; let localhost = 'http://127.0.0.1:8000';
問題點:
1.GET http://127.0.0.1:8000/categorys/ net::ERR_CONNECTION_REFUSED
解決方法:一般是后台沒有開啟服務,開啟后台即可
2.Access to XMLHttpRequest at 'http://127.0.0.1:8000/categorys/' from origin 'http://localhost:8080'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
解決方法:跨域防護機制,8080轉8000域名
vue是在本地8080運行,怎么接受后端http://127.0.0.1:8000/的數據呢?
如何讓解決跨域問題:
- 1.前端代理
- 2.服務器設置 (我們使用此方法)
GitHub:django cors headers,鏈接地址:https://github.com/adamchainz/django-cors-headers
# 安裝django-cors-headers跨域模塊
pip install django-cors-headers
# setting配置
# app設置到settings當中 'corsheaders', # 添加中間件,放在CsrfViewMiddleware前面,可以放在第一個 'corsheaders.middleware.CorsMiddleware', # 直接允許所有主機跨域,寫在中間件下面即可 CORS_ORIGIN_ALLOW_ALL = True # 默認為False
3.Vue展示商品列表頁數據
后端傳給前端字段需要保持一致
# 前端 src/views/list/list.vue文件
if(this.pageType=='search'){
getGoods({
search: this.searchWord, //搜索關鍵詞
}).then((response)=> {
this.listData = response.data.results;
this.proNum = response.data.count;
}).catch(function (error) {
console.log(error);
});
}else {
getGoods({
page: this.curPage, //當前頁碼
top_category: this.top_category, //商品類型
ordering: this.ordering, //排序類型
pricemin: this.pricemin, //價格最低 默認為‘’ 即為不選價格區間
pricemax: this.pricemax // 價格最高 默認為‘’
}).then((response)=> {
this.listData = response.data.results;
this.proNum = response.data.count;
}).catch(function (error) {
console.log(error);
});
}
# goods/views.py
from goods.serializers import GoodsSerializer,CategorySerializer
from rest_framework import generics
from goods.models import Goods,GoodsCategory
from rest_framework.pagination import PageNumberPagination
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets
from rest_framework import filters
from rest_framework import mixins
from goods.filters import GoodsFilter
class GooodsPagination(PageNumberPagination):
"""
商品列表自定義分頁
"""
page_size = 12 # 每頁顯示條數
page_size_query_param = 'page_size' # 修改"每頁顯示條數"
page_query_param = 'page' # 頁碼參數名字
max_page_size = 10000 # 最多顯示頁碼數
class GoodsListViewSet(mixins.ListModelMixin,viewsets.GenericViewSet):
"""
商品列表頁,分頁,過濾器,搜索,排序
"""
queryset = Goods.objects.all().order_by('goods__category_id') # queryset不可以更改
serializer_class = GoodsSerializer # 引用序列化相應的對象
pagination_class = GooodsPagination # 引用商品列表自定義分頁
filter_backends = (DjangoFilterBackend,filters.SearchFilter,filters.OrderingFilter) # 過濾器,搜索,排序
# filter_fields = ('name', 'market_price')
filter_class = GoodsFilter #過濾精確字段
search_fields = ('name', 'goods_brief','goods_desc') # 搜索字段
ordering_fields = ('sold_num', 'shop_price') # 排序字段
class CategoryViewSet(mixins.ListModelMixin,mixins.RetrieveModelMixin,viewsets.GenericViewSet):
"""
list:商品分類列表數據
retrieve:獲取商品分類詳情
"""
queryset = GoodsCategory.objects.filter(category_type=1) # 取出所有對象
serializer_class = CategorySerializer # 序列化相應的對象
# goods/filter.py
import django_filters from goods.models import Goods from django.db.models import Q #增加Q方法用來構造復雜or查詢 class GoodsFilter(django_filters.rest_framework.FilterSet): """ 商品的過濾類 """ pricemin = django_filters.NumberFilter(field_name="shop_price",help_text ='最低價格',lookup_expr='gte') # 新版已改成field_name,使用name會報錯 pricemax = django_filters.NumberFilter(field_name="shop_price",help_text ='最高價格',lookup_expr='lte') # name = django_filters.CharFilter(field_name='name',lookup_expr='icontains') # 模糊匹配 # 不管當前點擊的是一級分類二級分類還是三級分類,都能找到 top_category = django_filters.NumberFilter(method='top_catagory_filter') def top_catagory_filter(self,queryset,name,value): queryset = queryset.filter(Q(category_id = value)|Q(category__parent_category_id = value)|Q(category__parent_category__parent_category_id=value)) return queryset class Meta: model = Goods fields = ['pricemin','pricemax']
# goods/serializers.py
from rest_framework import serializers
from goods.models import Goods,GoodsCategory
class CategorySerializer3(serializers.ModelSerializer):
"""
商品三級類別序列化
"""
class Meta:
model = GoodsCategory
fields = "__all__"
class CategorySerializer2(serializers.ModelSerializer):
"""
商品二級類別序列化
"""
sub_cat = CategorySerializer3(many=True)
class Meta:
model = GoodsCategory
fields = "__all__"
class CategorySerializer(serializers.ModelSerializer):
"""
商品一級類別序列化
"""
sub_cat = CategorySerializer2(many=True)
class Meta:
model = GoodsCategory
fields = "__all__"
class GoodsSerializer(serializers.ModelSerializer):
category = CategorySerializer() # category為外鍵字段,實例化操作
class Meta:
model = Goods
# fields = ('id', 'category', 'goods_sn', 'name', 'goods_front_image', 'add_time')
fields = "__all__"


三、用戶登陸和手機注冊
1.用戶登錄
1).使用drf自帶Token認證
- setting里面設置token驗證為全局變量時,當token值填錯時訪問公共數據報錯就會讓人覺得怪異,需要把token驗證拿到的局部的view視圖中
# 配置文件:
# settings配置文件中增加REST_FRAMEWORK設置(AUTH模塊是用來驗證用戶登陸信息):
之前rest_framework的settings中,也就是D:\daly\PycharmProjects\VueShop\Lib\site-packages\rest_framework\settings.py文件中的43,44行被注釋注釋掉了,現在刪除注釋即可:
'DEFAULT_AUTHENTICATION_CLASSES': (
# 'rest_framework.authentication.BasicAuthentication', # rest_framework\settings.py已經設置
# 'rest_framework.authentication.SessionAuthentication', # rest_framework\settings.py已經設置
'rest_framework.authentication.TokenAuthentication',
)
# settings中apps設置:
'rest_framework.authtoken',
# 生成表authtoken_token
python manage.py makemigrations
python manage.py migrate
# urls.py
from rest_framework.authtoken import views
urlpatterns += [
url(r'^api-token-auth/', views.obtain_auth_token)
]
數據庫新增加表:

下載Google插件Postman,打開后以post方式提交用戶和密碼獲取其token值
Postman下載及配置鏈接:https://www.cnblogs.com/mafly/p/postman.html

數據庫展示(user_id是外鍵):

通過token獲取其用戶goods信息,如果token的值不正確就會提示“認證令牌無效”:

drf的token存在問題:
- 1.token值保存在一個系統數據庫authtoken_token表中,分布式需要把token同步過去
- 2.token沒有時間限制,可以一直使用
總結事項:
1.settings.py中設置的token是全局的,當token值填錯時訪問公共數據報錯就會讓人覺得怪異,需要把token驗證拿到的局部的view視圖中
2.django驗證(SessionMiddleware,AuthenticationMiddleware)
- django的sessions(中間件)會截取cookie,獲取SESSION_COOKIE_NAME取到session_key,在通過session_key取到request中的session
- django的auth(中間件)會調用session和一些不斷嵌套繼承的方法找到user
3.drf驗證(BasicAuthentication,SessionAuthentication,TokenAuthentication)
- 用戶在登陸的時候,如果出現沒有token值,url中的(drf的Token認證接口)obtain_auth_token.ObtainAuthToken.post方法會create一個token值
- token驗證,rest_framework/authentication.py會去數據庫表authtoken_token中取key(token值)
2).jwt認證
github文檔:http://getblimp.github.io/django-rest-framework-jwt/
# 安裝模塊
pip install djangorestframework-jwt -i https://pypi.doubanio.com/simple
# settings.py配置
增加JSONWebTokenAuthentication,注釋掉原來drf自帶TokenAuthentication 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', # 'rest_framework.authentication.BasicAuthentication', # 'rest_framework.authentication.SessionAuthentication', # 'rest_framework.authentication.TokenAuthentication', )
# urls.py 添加
from rest_framework_jwt.views import obtain_jwt_token urlpatterns = [ # jwt的認證接口 url(r'^jwt-auth/', obtain_jwt_token), ]
# post方式提交用戶和密碼獲取其token值:

# 拿到其toke對goods url進行get請求:

3).用戶登陸操作
# 前端vue登錄接口是login
//登錄
export const login = params => {
return axios.post(`${local_host}/login/`, params)
}
# urls.py,修改jwt接口替換成login與前端保持一致
url(r'^login/', obtain_jwt_token), # jwt的認證接口
# 前端views/login/login.vue
// 將name跟token設置到Cookie當中,過期日期7天
login({
username:this.userName, //當前頁碼
password:this.parseWord
}).then((response)=> {
console.log(response);
//本地存儲用戶信息
cookie.setCookie('name',this.userName,7);
cookie.setCookie('token',response.data.token,7)
//存儲在store
// 更新store數據
that.$store.dispatch('setInfo');
//跳轉到首頁頁面
this.$router.push({ name: 'index'})
})
# 前端src/store/mutations.js
// 后面所有請求都要帶上token,通過vuex的SET_INFO將name跟token數據取出放入state當中,其余組件會從state取出來
export default {
[types.SET_INFO] (state) {
state.userInfo = {
name:cookie.getCookie('name'),
token:cookie.getCookie('token')
}
console.log(state.userInfo);
},
jwt是調用django的auth認證方法,去與數據庫中username和password做比較,如果是手機號碼登陸的話會失敗,意思就是說auth默認為username加password登陸,我們需要自定制用戶認證函數。
# settings.py設置:
AUTHENTICATION_BACKENDS = ( # 自定制用戶認證函數 'users.views.CustomBackend', )
# users/views.py
from django.contrib.auth.backends import ModelBackend from django.db.models import Q from django.contrib.auth import get_user_model # settings中設置了AUTH_USER_MODEL User = get_user_model() # from users.models import UserProfile class CustomBackend(ModelBackend): """ 自定義用戶驗證 """ def authenticate(self, username=None, password=None, **kwargs): try: user = User.objects.get(Q(username=username)|Q(mobile=username)) if user.check_password(password): return user except Exception as e: return None
# 將用戶名和密碼放入body向login網頁發起POST請求,獲取其JWT token值

# 攜帶JTW token值向goods網頁發起GET請求

# 頁面展示

# JWT附加功能
settings.py設置
import datetime
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), # 過期時間
'JWT_AUTH_HEADER_PREFIX': 'JWT', # 請求頭
}
2.雲片網發送短信驗證碼(雲片)
# 注冊
國內短信-->簽名/模板管理(完成簽名/模板審核)
# API文檔
API文檔-->使用說明-->國內短信-->單條發送接口,鏈接地址 :https://www.yunpian.com/doc/zh_CN/domestic/single_send.html
# 新建apps/utils/yunpian.py,發送驗證碼功能
import requests
import json
class Yunpian(object):
def __init__(self,apikey):
self.apikey = apikey
self.url = 'https://sms.yunpian.com/v2/sms/single_send.json'
def send_meg(self,mobile,code):
data = {
'apikey':self.apikey,
'mobile':mobile,
'text':"【搞笑的】您的驗證碼是{code}。如非本人操作,請忽略本短信".format(code=code),
}
response = requests.post(self.url, data=data).text
re_dic = json.loads(response)
print(re_dic)
if __name__ == '__main__':
yunpian = Yunpian('雲片網APIKEY')
yunpian.send_meg('手機號碼','2345')
# Debugger模式下可以看到data數據

# 設置IP白名單(設置-->系統設置-->IP白名單)


3.drf實現發送短信驗證碼接口
# settings.py添加
# 手機號碼正則表達式
REGEX_MOBILE = "^1[358]\d{9}$|^147\d{8}$|^176\d{8}$"
# 雲片網APIKEY
APIKEY = "ade265174862ce8794f02576a9fc6a4b"
# users/serializers,對手機號驗證
import re
from Shop.settings import REGEX_MOBILE
from datetime import datetime,timedelta
from rest_framework import serializers
from users.models import VerifyCode
from django.contrib.auth import get_user_model # settings中設置了AUTH_USER_MODEL
User = get_user_model()
# from users.models import UserProfile
class MesSerializer(serializers.Serializer):
mobile = serializers.CharField(max_length=11)
# 函數名必須:validate + 驗證字段名
def validate_mobile(self,mobile):
"""
驗證手機號碼
:param mobile:
:return:
"""
# 手機是否注冊
if User.objects.filter(mobile=mobile).count():
raise serializers.ValidationError('用戶已經存在')
# 驗證手機號碼是否合法
if not re.match(REGEX_MOBILE,mobile):
raise serializers.ValidationError("手機號碼非法")
# 驗證碼發送頻率
# one_mintes_ago是60秒之前的時間點
one_mintes_ago = datetime.now()-timedelta(hours=0,minutes=1,seconds=0)
# add_time如果大於one_mintes_ago,說明時間點往后,也就是在60秒之間
if VerifyCode.objects.filter(add_time__gt=one_mintes_ago,mobile=mobile).count():
raise serializers.ValidationError("距離上一次發送未超過60s")
return mobile
# 發送短信驗證碼,視圖重寫CreateModelMixin的create方法
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
from django.contrib.auth import get_user_model # settings中設置了AUTH_USER_MODEL
User = get_user_model()
# from users.models import UserProfile
from rest_framework.mixins import CreateModelMixin
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework import status
from random import choice
from users.serializers import MesSerializer
from Shop.settings import APIKEY
from users.models import VerifyCode
from utils.yunpian import Yunpian
class MsgCodeViewset(CreateModelMixin, viewsets.GenericViewSet):
"""
發送短信驗證碼
"""
serializer_class = MesSerializer
def generate_code(self):
"""
生成四位數的驗證碼
:return:
"""
seeds = "1234567890" # 種子
random_str = []
for i in range(4):
random_str.append(choice(seeds))
# 空字符串把結果join起來
return "".join(random_str)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
mobile = serializer.validated_data["mobile"] # 是一個字典
yun_pian = Yunpian(APIKEY) # settings中設置了APIKEY
code = self.generate_code() # 調用上面 生成四位數的驗證碼方法
msg_status = yun_pian.send_msg(mobile=mobile,code=code)
if msg_status["code"] != 0: # 不為0返回400錯誤提示,為0時返回201正確提示
return Response({
"mobile":msg_status["msg"] # mobile字段 響應信息
},status=status.HTTP_400_BAD_REQUEST)
else:
code_record = VerifyCode(mobile=mobile,code=code) # 為0時正確,並把mobile跟code錄入數據庫
code_record.save()
return Response({
"mobile":mobile
},status=status.HTTP_201_CREATED)
# url注冊
from users.views import MsgCodeViewset router.register(r'codes', MsgCodeViewset,base_name='codes') # 配置codes的url

問題點:
1.type object 'UserProfile' has no attribute 'object'
源碼:
# 手機是否注冊
if User.object.filter(mobile=mobile).count():
raise serializers.ValidationError('用戶已經存在')
決解方法:
object修改為objects
2.AttributeError: 'str' object has no attribute 'read'
re_dict = json.loads(response.text)
決解方法:
load修改為loads
4.完成用戶注冊的接口,user serializer 和validator驗證
from rest_framework.validators import UniqueValidator class UserRegSerializer(serializers.ModelSerializer): code = serializers.CharField(required=True,max_length=4,min_length=4, error_messages={ "blank":"該字段不能為空", "required":"請輸入驗證碼", # required針對字段名稱都沒有,此處post空數據需要用blank提醒 "max_length":"驗證碼格式錯誤", "min_length":"驗證碼格式錯誤", },help_text="驗證碼",) username = serializers.CharField(required=True,allow_blank=False, validators=[UniqueValidator(queryset=User.objects.all(),message="用戶已存在")], ) def validate_code(self, code): """ 驗證碼錯誤: 1.驗證碼不存在,輸入錯誤 2.自帶驗證,最大長度最小長度為4 3.驗證碼過期 4.兩次驗證碼以最后面為准 :param code: :return: """ # try: # 使用get會拋異常錯誤,需要提前斷言 # verify_records = VerifyCode.objects.get(mobile=self.initial_data["username"], code=code) # except VerifyCode.DoesNotExist as e: # pass # except VerifyCode.MultipleObjectsReturned as e: # pass # 用戶注冊,已post方式提交注冊信息,post的數據都保存在initial_data里面 # username就是用戶注冊的手機號,驗證碼按添加時間倒序排序,為了后面驗證過期,錯誤等 verify_records = VerifyCode.objects.filter(mobile=self.initial_data["username"]).order_by("-add_time") if verify_records: last_records = verify_records[0] # 獲取最后一條數據 five_mintes_ago = datetime.now() - timedelta(hours=0, minutes=1, seconds=0) if five_mintes_ago > last_records.add_time: raise serializers.ValidationError("驗證碼過期") if last_records != code: raise serializers.ValidationError("驗證碼錯誤") else: # 記錄不存在 raise serializers.ValidationError("驗證碼錯誤") def validate(self, attrs): # 所有字段,attrs是字段驗證合法之后返回的總的dict # 前端沒有傳mobile值到后端,這里添加進來 attrs["mobile"] = attrs["username"] # code是自己添加的,數據庫中沒有這個字段,驗證完就刪除掉 del attrs["code"] return attrs class Meta: model = User # UserProfile繼承的是django自帶的User,username為必填字段 fields = ("username","code","mobile")
from users.serializers import UserRegSerializer class UserViewset(CreateModelMixin, viewsets.GenericViewSet): """ 用戶 """ serializer_class = UserRegSerializer
# url注冊
from users.views import UserViewset router.register(r'users', UserViewset,base_name='users') # 配置users的url

5.django信號量實現用戶密碼修改
# 完善用戶注冊
class UserViewset(CreateModelMixin, viewsets.GenericViewSet): """ 用戶 """ serializer_class = UserRegSerializer queryset = User.objects.all()
# user/serializer.py中添加password字段
fields = ("username","code","mobile","password")

# 設置password不能明文顯示和加密保存
# 設置password不能明文顯示和加密保存 password = serializers.CharField( style={'input_type': 'password'}, label="密碼", write_only=True, )

上面的serializer序列化中,password字段添加了write_only=True;如果不添加時,序列化后返回回來,密碼會被別人截獲

# 密碼加密保存(密碼存入數據庫時為明文,沒有加密)
# 密碼加密保存 def create(self, validated_data): user = super(UserRegSerializer, self).create(validated_data=validated_data) user.set_password(validated_data["password"]) user.save() return user
# 使用信號量,users下面創建signals.py(使用信號量的話前面的create方法注釋掉,也就前步驟密碼加密保存)
from django.db.models.signals import post_save from django.dispatch import receiver from django.contrib.auth import get_user_model User = get_user_model() # post_save:接收信號的方式 #sender: 接收信號的model @receiver(post_save, sender=User) def create_user(sender, instance=None, created=False, **kwargs): # 是否新建,因為update的時候也會進行post_save if created: password = instance.password #instance相當於user instance.set_password(password) instance.save()
# 加載配置
from django.apps import AppConfig class UsersConfig(AppConfig): name = 'users' verbose_name = '用戶管理' def ready(self): import users.signals
問題點:
1.Original exception text text was:'UserProfile' objects has no attribute 'code'
原因:code字段被刪除,無法序列化
解決方法:在字段里面添加read_only=True參數,序列化的時候不會序列此字段
code = serializers.CharField(required=True,read_only=True,max_length=4,min_length=4,label="驗證碼",error_messages={"blank":"該字段不能為空",},help_text="驗證碼",)
6.vue和注冊功能聯調
生成token的兩個重要步驟,一是payload,二是encode
# jwt源碼位置
D:/Program Files/python36/Lib/site-packages/rest_framework_jwt/serializers.py中57行
payload = jwt_payload_handler(user)
return {
'token': jwt_encode_handler(payload),
'user': user
}
# 數據定制化,返回JWT(token)
class UserViewset(CreateModelMixin, viewsets.GenericViewSet): """ 用戶 """ serializer_class = UserRegSerializer queryset = User.objects.all() # 注冊完成后實現登錄,把token返回回來 def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) user = self.perform_create(serializer) # 通過User生成jwt token # 數據放在data中,先序列化,在取出返回給用戶 re_dict = serializer.data payload = jwt_payload_handler(user) re_dict["token"] = jwt_encode_handler(payload) re_dict["name"] = user.name if user.name else user.username headers = self.get_success_headers(serializer.data) return Response(re_dict, status=status.HTTP_201_CREATED, headers=headers) def perform_create(self, serializer): # 返回的是UserRegSerializer中model的對象(也就是User) return serializer.save()


四、商品詳情頁功能
1.viewsets實現商品詳情頁接口
# goods/views.py,商品詳情頁只需要多繼承一個類(mixins.RetrieveModelMixin)
class GoodsListViewSet(mixins.ListModelMixin,mixins.RetrieveModelMixin,viewsets.GenericViewSet):
# 商品輪播圖,他是一個外鍵,序列化外鍵用嵌套的方法來實現
class GoodsImageSerializer(serializers.ModelSerializer): class Meta: model = GoodsImage fields = ("image",) class GoodsSerializer(serializers.ModelSerializer): # 覆蓋外鍵字段 category = CategorySerializer() # category為外鍵字段,實例化操作 images = GoodsImageSerializer(many=True) # 變量名為外鍵的related_name class Meta: model = Goods # fields = ('id', 'category', 'goods_sn', 'name', 'goods_front_image', 'add_time') fields = "__all__"


2.熱賣商品接口實現
# fields里面添加is_hot字段
class GoodsFilter(django_filters.rest_framework.FilterSet): class Meta: model = Goods fields = ['pricemin','pricemax','is_hot']
# 后台管理,是否熱銷打勾

3.用戶收藏接口實現
設置當前用戶字段drf文檔:https://www.django-rest-framework.org/api-guide/validators/#currentuserdefault
聯合唯一驗證drf文檔:https://www.django-rest-framework.org/api-guide/validators/#uniquetogethervalidator
# 獲取當前用戶,一個商品只允許收藏一次
from rest_framework import serializers from user_operation.models import UserFav from rest_framework.validators import UniqueTogetherValidator class UserFavSerializer(serializers.ModelSerializer): # 獲取當前登錄的用戶 user = serializers.HiddenField(default=serializers.CurrentUserDefault()) class Meta: model = UserFav # validate實現唯一聯合,一個商品只能收藏一次 validators = [ UniqueTogetherValidator( queryset=UserFav.objects.all(), fields=('user', 'goods'), # message的信息可以自定義 message="已經收藏", ) ] # 收藏的時候需要返回商品的id,因為取消收藏的時候必須知道商品的id是多少 fields = ("user","goods","id")
# 增加mixins.ListModelMixin類,獲取商品列表
from rest_framework import viewsets from rest_framework import mixins from user_operation.models import UserFav from user_operation.serializers import UserFavSerializer class UserFavViewset(viewsets.GenericViewSet,mixins.ListModelMixin,mixins.CreateModelMixin,mixins.DestroyModelMixin): """ 用戶收藏功能列表 """ queryset = UserFav.objects.all() serializer_class = UserFavSerializer
# 說明:繼承的類
mixins.CreateModelMixin 添加收藏(相當於創建數據庫) mixins.DestroyModelMixin 取消刪除(相當於數據庫刪除) mixins.ListModelMixin 獲取已收藏的商品列表
# 收藏三個不同商品

# 重復收藏同一個商品提示:“已經收藏”

# 使用postman軟件,刪除商品收藏

# 再次點擊刪除同樣商品的ID會提示:"未找到"

出現問題:
# user_operation\serializers.py # 獲取當前登錄的用戶 user = serializers.HiddenField(default=serializers.CurrentUserDefault()) # 提交數據會報以下錯誤信息 ValueError at /userfavs/ Cannot assign "<django.contrib.auth.models.AnonymousUser object at 0x000002284AEA4E48>": "UserFav.user" must be a "UserProfile" instance. 造成原因:django版本過低造成 解決方法:升級django,至少1.11.6版本,python -m pip install --upgrade django==1.11.6
4.drf的權限驗證
# utils文件夾下創建permissions文件,把owner更改為user
官方文檔:https://www.django-rest-framework.org/api-guide/permissions/#examples
from rest_framework import permissions class IsOwnerOrReadOnly(permissions.BasePermission): """ Object-level permission to only allow owners of an object to edit it. Assumes the model instance has an `owner` attribute. """ def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request, # so we'll always allow GET, HEAD or OPTIONS requests. if request.method in permissions.SAFE_METHODS: return True # Instance must have an attribute named `owner`. # obj為數據庫的model,此處需要把owner修改為user return obj.user == request.user
# 引入用戶驗證和權限
from rest_framework import viewsets from rest_framework import mixins from user_operation.models import UserFav from user_operation.serializers import UserFavSerializer from rest_framework.permissions import IsAuthenticated from utils.permissions import IsOwnerOrReadOnly from rest_framework_jwt.authentication import JSONWebTokenAuthentication class UserFavViewset(viewsets.GenericViewSet,mixins.ListModelMixin,mixins.CreateModelMixin,mixins.DestroyModelMixin): """ 用戶收藏功能列表 """ serializer_class = UserFavSerializer # permission是用來做權限判斷的 # IsAuthenticated為必須登錄用戶,IsOwnerOrReadOnly:必須是當前登錄用戶 permission_classes = (IsAuthenticated,IsOwnerOrReadOnly) # auth用戶驗證 authentication_classes = (JSONWebTokenAuthentication,) # 只能查看當前登錄用戶的收藏,不會獲取所有用戶的收藏 def get_queryset(self): return UserFav.objects.filter(user=self.request.user)
# 后台數據

# 拿到user_id=1的token

# 刪除id=17的收藏數據(此收藏收據屬於另一用戶)

# 刪除id=14的收藏數據

# 再次查看后台數據

# 未加入SessionAuthentication直接訪問http://127.0.0.1:8000/userfavs/會出現:"身份認證信息未提供".

# 加入SessionAuthentication與搜索的字段
from rest_framework import viewsets from rest_framework import mixins from user_operation.models import UserFav from user_operation.serializers import UserFavSerializer from rest_framework.permissions import IsAuthenticated from utils.permissions import IsOwnerOrReadOnly from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework.authentication import SessionAuthentication class UserFavViewset(viewsets.GenericViewSet,mixins.ListModelMixin,mixins.CreateModelMixin,mixins.DestroyModelMixin): """ 用戶收藏功能列表 """ # permission是用來做權限判斷的 # IsAuthenticated為必須登錄用戶,IsOwnerOrReadOnly:必須是當前登錄用戶 permission_classes = (IsAuthenticated,IsOwnerOrReadOnly) serializer_class = UserFavSerializer # auth用戶驗證 authentication_classes = (JSONWebTokenAuthentication,SessionAuthentication) # 搜索的字段 lookup_field = "goods_id" # 只能查看當前登錄用戶的收藏,不會獲取所有用戶的收藏 def get_queryset(self): return UserFav.objects.filter(user=self.request.user)
# view視圖添加了lookup_field = "goods_id",后續直接搜索商品的id就可以查找商品,之前設置的是數據庫id

# 出現問題:
1.'CSRFCheck' object has no attribute 'process_request' 造成原因:django版本過低造成 解決方法:升級django,至少1.11.6版本,python -m pip install --upgrade django==1.11.6
5.用戶收藏功能與vue關聯
# 全部替換成localhost
//收藏......
export const addFav = params => { return axios.post(`${localhost}/userfavs/`, params) }
//取消收藏.......
export const delFav = goodsId => { return axios.delete(`${localhost}/userfavs/`+goodsId+'/') }
export const getAllFavs = () => { return axios.get(`${localhost}/userfavs/`) }
//判斷是否收藏......
export const getFav = goodsId => { return axios.get(`${localhost}/userfavs/`+goodsId+'/') }
# 已收藏的商品顯示“已收藏”,沒有收藏就顯示“收藏”

五、個人中心功能開發
1.drf的api文檔自動生成和功能詳解
# url配置,用於生成Django Rest Framework自動文檔,title可以自己定義
urlpatterns = [
url(r'docs/', include_docs_urls(title="商城")),
]
# 輸入http://127.0.0.1:8000/docs/即可訪問

# 官方文檔,ViewSet注釋格式,https://www.django-rest-framework.org/topics/documenting-your-api/
class UserViewSet(viewsets.ModelViewSet):
"""
retrieve:
Return the given user.
list:
Return a list of all the existing users.
create:
Create a new user instance.
"""
# drf文檔的優點:
- 自動生成
- 文檔里可以做交互和測試
- 可以生成js,shel和python代碼段
2.動態設置serializer和permission獲取用戶信息
# 用戶個人信息修改,因為手機號是驗證過的,不能隨便改

# 用戶詳情的序列化
class UserDetailSerializer(serializers.ModelSerializer): """ 用戶詳情序列化類 """ class Meta: model = User fields = ("name","birthday","gender","email","mobile")
# 用戶視圖函數
from rest_framework import permissions from rest_framework.authentication import SessionAuthentication from rest_framework_jwt.authentication import JSONWebTokenAuthentication from users.serializers import UserRegSerializer,UserDetailSerializer class UserViewset(CreateModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet): """ 用戶 """ serializer_class = UserRegSerializer queryset = User.objects.all() authentication_classes = (SessionAuthentication,JSONWebTokenAuthentication) # 瀏覽器里面添加session或者head里面添加token # permission_classes = (permissions.IsAuthenticated,) # 出現彈窗,需要登陸用戶和密碼 # 序列化的選擇 # 1.用戶注冊(UserRegSerializer),只返回username和mobile;會員中心(UserDetailSerializer)需要更多字段 # 2.如果注冊的使用userdetailSerializer,又會導致驗證失敗,所以需要動態的使用serializer,重構get_serializer_class方法 def get_serializer_class(self): if self.action == "retrieve": return UserDetailSerializer elif self.action == "create": return UserRegSerializer return UserDetailSerializer # 動態權限配置 # 1.用戶注冊的時候不應該有權限限制 # 2.用戶在獲取用戶詳情信息的時候,需要登陸才行 def get_permissions(self): if self.action == "retrieve": return [permissions.IsAuthenticated()] elif self.action == "create": return [] return [] # 注冊完成后實現登錄,把token返回回來 def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) user = self.perform_create(serializer) # 通過User生成jwt token # 數據放在data中,先序列化,在取出返回給用戶 re_dict = serializer.data payload = jwt_payload_handler(user) re_dict["token"] = jwt_encode_handler(payload) re_dict["name"] = user.name if user.name else user.username headers = self.get_success_headers(serializer.data) return Response(re_dict, status=status.HTTP_201_CREATED, headers=headers) # 雖然繼承了Retrieve可以獲取用戶詳情,但是並不知道用戶的id,所以要重寫get_object方法 # 重寫get_object方法,就知道是哪個用戶了 def get_object(self): return self.request.user def perform_create(self, serializer): # 返回的是UserRegSerializer中model的對象(也就是User) return serializer.save()
# 主要添加的內容:
- 繼承mixins.RetrieveModelMixin -->>獲取用戶信息
- 重寫get_object -->>獲取登錄的用戶
- get_permissions -->>動態權限分配
- get_serializer_class -->>動態序列化分配
# 輸入id獲取用戶個人信息

# 修改用戶個人信息,添加繼承mixins.UpdateModelMixin類
class UserViewset(CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin,viewsets.GenericViewSet):

# 查看當前用戶登陸信息

出現問題:
1.修改用戶個人信息時提示:"detail": "CSRF Failed: CSRF token missing or incorrect." 造成原因:此用戶在其他drf頁面登陸 決解方法:退出其他drf當前登陸角色 2.出現錯誤提示:"AttributeError at /users/1/'AnonymousUser' object has no attribute '_meta' 造成原因:drf文檔刷新后JWT消失 決解方法:重新添加JTW
3.用戶收藏功能
# 新增用戶收藏詳情類(UserFavDetailSerializer)
from goods.serializers import GoodsSerializer class UserFavDetailSerializer(serializers.ModelSerializer): """ 用戶收藏詳情 """ # 通過商品id獲取收藏的商品,需要嵌套商品的序列化 goods = GoodsSerializer() class Meta: model = UserFav fields = ("goods","id")
# 動態選擇serializer
from rest_framework import viewsets from rest_framework import mixins from user_operation.models import UserFav from user_operation.serializers import UserFavSerializer,UserFavDetailSerializer from rest_framework.permissions import IsAuthenticated from utils.permissions import IsOwnerOrReadOnly from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework.authentication import SessionAuthentication class UserFavViewset(viewsets.GenericViewSet,mixins.ListModelMixin,mixins.CreateModelMixin,mixins.RetrieveModelMixin,mixins.DestroyModelMixin): """ list: 用戶收藏功能列表 create: 收藏商品 Retrieve: 判斷某個商品是否已經收藏 delete: 刪除收藏商品 """ # 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)
# 會員中心-->我的收藏

4.用戶留言功能
# 序列化
from user_operation.models import UserLeavingMessage class LeavingMessageSerializer(serializers.ModelSerializer): # 獲取當前登錄的用戶 user = serializers.HiddenField(default=serializers.CurrentUserDefault()) # 添加時間,read_only只返回,不提交 add_time = serializers.DateTimeField(read_only=True, format="%Y-%m-%d %H:%M") class Meta: model = UserLeavingMessage fields = ("user","message_type","subject","message","file","id","add_time")
# 留言視圖函數
from rest_framework import viewsets from rest_framework import mixins from user_operation.models import UserLeavingMessage from user_operation.serializers import LeavingMessageSerializer from rest_framework.permissions import IsAuthenticated from utils.permissions import IsOwnerOrReadOnly from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework.authentication import SessionAuthentication class LeavingMessageViewset(mixins.ListModelMixin, mixins.DestroyModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet): """ List: 獲取用戶留言 Create: 添加留言 Delete: 刪除留言功能 """ permission_classes = (IsAuthenticated, IsOwnerOrReadOnly) authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication) serializer_class = LeavingMessageSerializer # 只能看到自己的留言 def get_queryset(self): return UserLeavingMessage.objects.filter(user=self.request.user)
# 配置用戶留言的url
from user_operation.views import LeavingMessageViewset router.register(r'messages', LeavingMessageViewset, base_name='messages')
# drf如何解析圖片並保存到數據庫
官方文檔:https://www.django-rest-framework.org/api-guide/parsers/#multipartparser
# 可以獲取、刪除留言等功能

5.用戶收貨地址功能
# 序列化
from user_operation.models import UserAddress class AddressSerializer(serializers.ModelSerializer): # 獲取當前登錄的用戶 user = serializers.HiddenField(default=serializers.CurrentUserDefault()) # 添加時間,read_only只返回,不提交 add_time = serializers.DateTimeField(read_only=True, format="%Y-%m-%d %H:%M") class Meta: model = UserAddress fields = ("id","user", "province", "city", "district", "address", "signer_name","signer_mobile", "add_time")
# 收貨地址函數,ModelViewSet類包含了增刪改查功能
from rest_framework import viewsets from rest_framework import mixins from user_operation.models import UserAddress from user_operation.serializers import AddressSerializer from rest_framework.permissions import IsAuthenticated from utils.permissions import IsOwnerOrReadOnly from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework.authentication import SessionAuthentication class AddressViewset(viewsets.ModelViewSet): """ 收貨地址管理 List: 獲取收貨地址 create: 添加收貨地址 update: 更新收貨地址 delete: 刪除收貨地址 """ permission_classes = (IsAuthenticated, IsOwnerOrReadOnly) authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication) serializer_class = AddressSerializer # 只能看到自己的留言 def get_queryset(self): return UserAddress.objects.filter(user=self.request.user)
# 配置用戶收貨地址的url
from user_operation.views import AddressViewset router.register(r'address', AddressViewset, base_name='address')
# 收貨地址詳情

六、購物車、訂單管理和支付功能
1.添加商品到購物車
# 序列化
from rest_framework import serializers from goods.models import Goods from trade.models import ShoppingCart class ShopCartSerializer(serializers.Serializer): # 獲取當前登錄的用戶 user = serializers.HiddenField(default=serializers.CurrentUserDefault()) nums = serializers.IntegerField(required=True,label="數量",min_value=1,max_value=None, error_messages={ "required":"請選擇購買數量", "min_value":"商品數量不能小於1", }) goods = serializers.PrimaryKeyRelatedField(required=True,queryset=Goods.objects.all()) def create(self, validated_data): # validated_data處理后的數據 # 獲取當前用戶 user = self.context["request"].user nums = validated_data["nums"] goods = validated_data["goods"] existed = ShoppingCart.objects.filter(user=user, goods=goods) # 如果購物車中有記錄,現有數量+原來數量 # 如果購物車車沒有記錄,就創建 if existed: existed = existed[0] existed.nums += nums existed.save() else: existed = ShoppingCart.objects.create(**validated_data) return existed
# 購物車功能視圖函數
from rest_framework import viewsets from rest_framework.permissions import IsAuthenticated from rest_framework.authentication import SessionAuthentication from rest_framework_jwt.authentication import JSONWebTokenAuthentication from trade.serializers import ShopCartSerializer from trade.models import ShoppingCart from utils.permissions import IsOwnerOrReadOnly class ShoppingCartViewset(viewsets.ModelViewSet): """ 購物車功能 list: 獲取購物車詳情 create: 加入購物車 delete: 刪除購物車記錄 """ permission_classes = (IsAuthenticated, IsOwnerOrReadOnly) authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication) serializer_class = ShopCartSerializer # 返回當前用戶購物車列表頁 def get_queryset(self): return ShoppingCart.objects.filter(user=self.request.user)
# 配置購物車的url
from trade.views import ShoppingCartViewset router.register(r'shopcarts', ShoppingCartViewset, base_name="shopcarts")
2.修改購物車商品的數量(通過goods_id搜索出來的具體商品,需要重構update方法)
# ShoppingCartViewset類中添加商品id為搜索的字段
lookup_field = "goods_id"
Serializer繼承BaseSerializer,但是Seriazer中並沒有更新update方法,所有添加一個update方法;ModelSerializer有update方法
# ShopCartSerializer類中添加update方法
def update(self, instance, validated_data): # 修改商品數量 instance.nums = validated_data["nums"] instance.save() return instance

3.購物車商品列表詳情頁
# 嵌入goods字段成為詳情列表頁
from rest_framework import serializers from trade.models import ShoppingCart from goods.serializers import GoodsSerializer class ShopCartDetailSerializer(serializers.ModelSerializer): """ 購物車詳情信息 """ # 一個購物車對應一個商品 goods = GoodsSerializer(many=False) class Meta: model = ShoppingCart fields = "__all__"
# 動態選擇serializer,ShoppingCartViewset類加入get_serializer_class方法
def get_serializer_class(self): if self.action == "list": return ShopCartDetailSerializer else: return ShopCartSerializer

4.訂單管理接口
# 訂單序列化
from rest_framework import serializers from goods.models import Goods from trade.models import OrderInfo,OrderGoods from goods.serializers import GoodsSerializer class OrderGoodsSerializer(serializers.ModelSerializer): # 訂單詳情中的商品信息 goods = GoodsSerializer(many=False) class Meta: model = OrderGoods fields = "__all__" class OrderDetailSerializer(serializers.ModelSerializer): # 訂單詳情 goods = OrderGoodsSerializer(many=True) class Meta: model = OrderInfo fields = "__all__" class OrderSerializer(serializers.ModelSerializer): # 獲取當前登錄的用戶,界面不顯示user,相當於read_only user = serializers.HiddenField(default=serializers.CurrentUserDefault()) # 生成訂單的時候這些不用POST pay_status = serializers.CharField(read_only=True) trade_no = serializers.CharField(read_only=True) order_sn = serializers.CharField(read_only=True) pay_time = serializers.DateTimeField(read_only=True) def generate_order_sn(self): # 生成訂單號,當前時間 + userid + 兩位隨機數 import time from random import Random random_ins = Random() order_sn = "{time_str}{user_id}{random_str}".format(time_str=time.strftime("%Y%m%d%H%M%S"), user_id=self.context["request"].user.id, random_str=random_ins.randint(10,99)) return order_sn def validate(self, attrs): # 實例化上面方法,validate中添加order_sn,在view中perform_create方法里save attrs["order_sn"]=self.generate_order_sn() return attrs class Meta: model = OrderInfo fields = "__all__"
# 上面的訂單詳情序列化里面嵌套了兩層序列化
OrderDetailSerializer(訂單詳情序列化)
--OrderGoodsSerializer(訂單的商品序列化)
--GoodsSerializer(商品序列化)
# 訂單視圖函數
from rest_framework import viewsets from rest_framework.permissions import IsAuthenticated from rest_framework.authentication import SessionAuthentication from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework import mixins from trade.serializers import OrderSerializer,OrderDetailSerializer from trade.models import OrderInfo,OrderGoods from utils.permissions import IsOwnerOrReadOnly class OrderViewset(mixins.ListModelMixin, mixins.CreateModelMixin, mixins.RetrieveModelMixin,mixins.DestroyModelMixin, viewsets.GenericViewSet): # 此處不使用ModelViewSet,因為訂單不能修改,不能使用UpdateModelMixin """ 訂單詳情 List: 獲取個人訂單 delete: 刪除訂單 create: 新增訂單 Retrieve: 訂單詳情 """ permission_classes = (IsAuthenticated, IsOwnerOrReadOnly) authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication) serializer_class = OrderSerializer # 獲取當前用戶訂單 def get_queryset(self): return OrderInfo.objects.filter(user= self.request.user) # 動態獲取 def get_serializer_class(self): if self.action == "retrieve": return OrderDetailSerializer return OrderSerializer # 在訂單提交保存之前還需要多兩步步驟,所以這里自定義perform_create方法 # 1.將購物車中的商品保存到OrderGoods中 # 2.清空購物車 def perform_create(self, serializer): # save之前生成訂單號 order = serializer.save() shop_carts = ShoppingCart.objects.filter(user=self.request.user) for shop_cart in shop_carts: order_goods = OrderGoods() order_goods.goods = shop_cart.goods order_goods.goods_num = shop_cart.nums order_goods.order = order order_goods.save() # 清空購物車 shop_cart.delete() return order
# 配置訂單的url
from trade.views import OrderViewset router.register(r'orders', OrderViewset, base_name="orders")



5.pycharm遠程代碼調試
https://www.cnblogs.com/dalyday/p/10991180.html
6.支付功能
創建應用
# 進入螞蟻金服開放平台(https://open.alipay.com/platform/home.htm),登錄后進入管理中心-->>網頁&移動應用列表


# 創建應用

# 輸入“應用名稱”,上傳“應用圖標”,點擊“確認創建”,在“我的應用列表”中可以查看已經創建好的應用

沙箱環境
# 沙箱應用地址: https://openhome.alipay.com/platform/appDaily.htm?tab=info
# 開發中心-->研發服務-->沙箱環境-->沙箱應用

# 應用公鑰和私鑰的生成方法,地址:https://docs.open.alipay.com/291/105971,選擇對應系統版本

# 解壓后打開RSA簽名驗簽工具.bat文件

# 點擊“打開密鑰文件路徑”,把文件“應用公鑰2048.txt”內容拷貝“應用公鑰”中

# 把生成的應用公鑰和私鑰文件拷貝到trade/keys下面-->對文件重命名-->內容也需要修改下

修改的內容
-----BEGIN PRIVATE KEY----- -----END PRIVATE KEY-----
# 把支付寶公鑰也拷貝到trade/keys下面


官方文檔說明
- 電腦網站支付,網址:https://docs.open.alipay.com/270/105898/
- 統一收單下單並支付頁面接口網址:https://docs.open.alipay.com/api_1/alipay.trade.page.pay/
- 如何簽名,網址:https://docs.open.alipay.com/291/105974
編寫代碼
# 把環境改成本地的,安裝加密模塊
pip install pycryptodome -i https://pypi.doubanio.com/simple
# utils中新建alipay.py,實例測試
from datetime import datetime from Crypto.PublicKey import RSA from Crypto.Signature import PKCS1_v1_5 from Crypto.Hash import SHA256 from base64 import b64encode, b64decode from urllib.parse import quote_plus from urllib.parse import urlparse, parse_qs from urllib.request import urlopen from base64 import decodebytes, encodebytes import json class AliPay(object): """ 支付寶支付接口 """ def __init__(self, appid, app_notify_url, app_private_key_path, alipay_public_key_path, return_url, debug=False): self.appid = appid self.app_notify_url = app_notify_url # 應用私鑰 self.app_private_key_path = app_private_key_path self.app_private_key = None self.return_url = return_url with open(self.app_private_key_path) as fp: self.app_private_key = RSA.importKey(fp.read()) # 阿里公鑰 self.alipay_public_key_path = alipay_public_key_path with open(self.alipay_public_key_path) as fp: self.alipay_public_key = RSA.import_key(fp.read()) if debug is True: self.__gateway = "https://openapi.alipaydev.com/gateway.do" else: self.__gateway = "https://openapi.alipay.com/gateway.do" def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs): # 請求參數 biz_content = { "subject": subject, "out_trade_no": out_trade_no, "total_amount": total_amount, "product_code": "FAST_INSTANT_TRADE_PAY", # "qr_pay_mode":4 } # 允許傳遞更多參數,放到biz_content biz_content.update(kwargs) data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url) return self.sign_data(data) def build_body(self, method, biz_content, return_url=None): # 公共請求參數 data = { "app_id": self.appid, "method": method, "charset": "utf-8", "sign_type": "RSA2", "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "version": "1.0", "biz_content": biz_content } if return_url is not None: data["notify_url"] = self.app_notify_url data["return_url"] = self.return_url return data def sign_data(self, data): # 簽名 data.pop("sign", None) # 排序后的字符串 unsigned_items = self.ordered_data(data) # 排完序后拼接起來 unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items) # 得到簽名的字符串 sign = self.sign(unsigned_string.encode("utf-8")) # 對url進行處理 quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items) # 獲得最終的訂單信息字符串 signed_string = quoted_string + "&sign=" + quote_plus(sign) return signed_string def ordered_data(self, data): # 參數傳進來一定要排序 complex_keys = [] for key, value in data.items(): if isinstance(value, dict): complex_keys.append(key) # 將字典類型的數據dump出來 for key in complex_keys: data[key] = json.dumps(data[key], separators=(',', ':')) return sorted([(k, v) for k, v in data.items()]) def sign(self, unsigned_string): # 開始計算簽名 key = self.app_private_key # 簽名對象 signer = PKCS1_v1_5.new(key) # 生成簽名 signature = signer.sign(SHA256.new(unsigned_string)) # base64 編碼,轉換為unicode表示並移除回車 sign = encodebytes(signature).decode("utf8").replace("\n", "") return sign def _verify(self, raw_content, signature): # 開始計算簽名 key = self.alipay_public_key signer = PKCS1_v1_5.new(key) digest = SHA256.new() digest.update(raw_content.encode("utf8")) if signer.verify(digest, decodebytes(signature.encode("utf8"))): return True return False def verify(self, data, signature): if "sign_type" in data: sign_type = data.pop("sign_type") # 排序后的字符串 unsigned_items = self.ordered_data(data) message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items) return self._verify(message, signature) if __name__ == "__main__": return_url = 'http://127.0.0.1:8001/?charset=utf-8&out_trade_no=201702021224&method=alipay.trade.page.pay.return&total_amount=0.01&sign=R01hGeanJ6GUdufKFUQhRzYbv2h%2F8PwvhFykotT7E6UDebrarj9xSt8YgPkWxq%2F3yYlGg1aWtc88yuwL2rI40s6R1AYpv8FFTwHn0%2FRcQZAvq1D8hKo8JXXpyMaKbEzTqPsUtAA8jWOfRZiUqAJxBeLM0G4hawK3qu6x4MyKH%2BLg3uFuxhm9smlSgxwKrHQ84WfdQw6WUEDVG56FX8CG2hWVgO5X7CXM6L0ZkrLYJij254%2BcA9cMHuTacpl35otUbYggoDZHpm5dt2fEKnmRwkzY0Cja30kYcc6w%2FAWCQfetIx0W0psnLKTIhnV9MDQbtqbHGbWWI0I8GOHTNkS9Zg%3D%3D&trade_no=2019062022001459821000056107&auth_app_id=2016092900626681&version=1.0&app_id=2016092900626681&sign_type=RSA2&seller_id=2088102177859523×tamp=2019-06-20+10%3A36%3A58' o = urlparse(return_url) query = parse_qs(o.query) processed_query = {} ali_sign = query.pop("sign")[0] # 測試用例 alipay = AliPay( # 沙箱里面的appid值 appid="2016092900626681", # notify_url是異步的url app_notify_url="http://47.107.36.249:8001/alipay/return/", # 我們自己商戶的密鑰 app_private_key_path="../trade/keys/private_2048.txt", # 支付寶的公鑰 alipay_public_key_path="../trade/keys/alipay_key_2048.txt", # 支付寶的公鑰,驗證支付寶回傳消息使用,不是你自己的公鑰, # debug為true時使用沙箱的url。如果不是用正式環境的url debug=True, # 默認False, return_url="http://47.107.36.249:8001/alipay/return/" ) for key, value in query.items(): processed_query[key] = value[0] print (alipay.verify(processed_query, ali_sign)) url = alipay.direct_pay( # 訂單標題 subject="測試訂單", # 我們商戶自行生成的訂單號 out_trade_no="201702021224", # 訂單金額 total_amount=0.01, # 成功付款后跳轉到的頁面,return_url同步的url return_url="http://47.107.36.249:8001/alipay/return/", ) re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url) print(re_url)
# 點擊打印出的url,會跳轉到阿里支付界面,可以使用沙箱版錢包掃碼支付,或者登陸沙箱賬號買家網頁支付

# 登陸沙箱賬號

django集成支付寶notify_url和return_url
# setting設置支付寶相關的key
private_key_path = os.path.join(BASE_DIR, 'apps/trade/keys/private_2048.txt') ali_pub_key_path = os.path.join(BASE_DIR, 'apps/trade/keys/alipay_key_2048.txt')
# 配置支付寶支付相關接口的url
from trade.views import AlipayView url(r'^alipay/return/', AlipayView.as_view(), name="alipay"),
# apps/utils.py
把return_url和notify_url都改成遠程服務器的地址 return_url="http://47.107.36.249:8001/alipay/return/" app_notify_url="http://47.107.36.249:8001/alipay/return/"
import json from datetime import datetime from Crypto.PublicKey import RSA from Crypto.Signature import PKCS1_v1_5 from Crypto.Hash import SHA256 from base64 import b64encode, b64decode from urllib.parse import quote_plus from urllib.parse import urlparse, parse_qs from urllib.request import urlopen from base64 import decodebytes, encodebytes class AliPay(object): """ 支付寶支付接口 """ def __init__(self, appid, app_notify_url, app_private_key_path, alipay_public_key_path, return_url, debug=False): self.appid = appid self.app_notify_url = app_notify_url # 應用私鑰 self.app_private_key_path = app_private_key_path self.app_private_key = None self.return_url = return_url with open(self.app_private_key_path) as fp: self.app_private_key = RSA.importKey(fp.read()) # 阿里公鑰 self.alipay_public_key_path = alipay_public_key_path with open(self.alipay_public_key_path) as fp: self.alipay_public_key = RSA.import_key(fp.read()) if debug is True: self.__gateway = "https://openapi.alipaydev.com/gateway.do" else: self.__gateway = "https://openapi.alipay.com/gateway.do" def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs): # 請求參數 biz_content = { "subject": subject, "out_trade_no": out_trade_no, "total_amount": total_amount, "product_code": "FAST_INSTANT_TRADE_PAY", # "qr_pay_mode":4 } # 允許傳遞更多參數,放到biz_content biz_content.update(kwargs) data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url) return self.sign_data(data) def build_body(self, method, biz_content, return_url=None): # 公共請求參數 data = { "app_id": self.appid, "method": method, "charset": "utf-8", "sign_type": "RSA2", "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "version": "1.0", "biz_content": biz_content } if return_url is not None: data["notify_url"] = self.app_notify_url data["return_url"] = self.return_url return data def sign_data(self, data): # 簽名 data.pop("sign", None) # 排序后的字符串 unsigned_items = self.ordered_data(data) # 排完序后拼接起來 unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items) # 得到簽名的字符串 sign = self.sign(unsigned_string.encode("utf-8")) # 對url進行處理 quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items) # 獲得最終的訂單信息字符串 signed_string = quoted_string + "&sign=" + quote_plus(sign) return signed_string def ordered_data(self, data): # 參數傳進來一定要排序 complex_keys = [] for key, value in data.items(): if isinstance(value, dict): complex_keys.append(key) # 將字典類型的數據dump出來 for key in complex_keys: data[key] = json.dumps(data[key], separators=(',', ':')) return sorted([(k, v) for k, v in data.items()]) def sign(self, unsigned_string): # 開始計算簽名 key = self.app_private_key # 簽名對象 signer = PKCS1_v1_5.new(key) # 生成簽名 signature = signer.sign(SHA256.new(unsigned_string)) # base64 編碼,轉換為unicode表示並移除回車 sign = encodebytes(signature).decode("utf8").replace("\n", "") return sign def _verify(self, raw_content, signature): # 開始計算簽名 key = self.alipay_public_key signer = PKCS1_v1_5.new(key) digest = SHA256.new() digest.update(raw_content.encode("utf8")) if signer.verify(digest, decodebytes(signature.encode("utf8"))): return True return False def verify(self, data, signature): if "sign_type" in data: sign_type = data.pop("sign_type") # 排序后的字符串 unsigned_items = self.ordered_data(data) message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items) return self._verify(message, signature) if __name__ == "__main__": return_url = 'http://47.107.36.249:8001/alipay/return/?charset=utf-8&out_trade_no=201702021224&method=alipay.trade.page.pay.return&total_amount=0.01&sign=R01hGeanJ6GUdufKFUQhRzYbv2h%2F8PwvhFykotT7E6UDebrarj9xSt8YgPkWxq%2F3yYlGg1aWtc88yuwL2rI40s6R1AYpv8FFTwHn0%2FRcQZAvq1D8hKo8JXXpyMaKbEzTqPsUtAA8jWOfRZiUqAJxBeLM0G4hawK3qu6x4MyKH%2BLg3uFuxhm9smlSgxwKrHQ84WfdQw6WUEDVG56FX8CG2hWVgO5X7CXM6L0ZkrLYJij254%2BcA9cMHuTacpl35otUbYggoDZHpm5dt2fEKnmRwkzY0Cja30kYcc6w%2FAWCQfetIx0W0psnLKTIhnV9MDQbtqbHGbWWI0I8GOHTNkS9Zg%3D%3D&trade_no=2019062022001459821000056107&auth_app_id=2016092900626681&version=1.0&app_id=2016092900626681&sign_type=RSA2&seller_id=2088102177859523×tamp=2019-06-20+10%3A36%3A58' o = urlparse(return_url) query = parse_qs(o.query) processed_query = {} ali_sign = query.pop("sign")[0] # 測試用例 alipay = AliPay( # 沙箱里面的appid值 appid="2016092900626681", # notify_url是異步的url app_notify_url="http://47.107.36.249:8001/alipay/return/", # 我們自己商戶的密鑰 app_private_key_path="../trade/keys/private_2048.txt", # 支付寶的公鑰 alipay_public_key_path="../trade/keys/alipay_key_2048.txt", # 支付寶的公鑰,驗證支付寶回傳消息使用,不是你自己的公鑰, # debug為true時使用沙箱的url。如果不是用正式環境的url debug=True, # 默認False, return_url="http://47.107.36.249:8001/alipay/return/" ) for key, value in query.items(): processed_query[key] = value[0] print (alipay.verify(processed_query, ali_sign)) url = alipay.direct_pay( # 訂單標題 subject="測試訂單", # 我們商戶自行生成的訂單號 out_trade_no="201702021226", # 訂單金額 total_amount=0.01, # 成功付款后跳轉到的頁面,return_url同步的url return_url="http://47.107.36.249:8001/alipay/return/", ) re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url) print(re_url)
# get方法處理支付寶的return_url返回,post方法處理支付寶的notify_url
from rest_framework.views import APIView from utils.alipay import AliPay from Shop.settings import private_key_path,ali_pub_key_path from datetime import datetime from rest_framework.response import Response class AlipayView(APIView): def get(self,request): """ 處理支付寶的return_url返回 :param requeat: :return: """ processed_dict = { } # 獲取GET中的數據 for key, value in request.GET.items(): processed_dict[key] = value # 取出sign和空數據 sign =processed_dict.pop("sign",None) # 測試用例 alipay = AliPay( # 沙箱里面的appid值 appid="2016092900626681", # notify_url是異步的url app_notify_url="http://47.107.36.249:8001/alipay/return/", # 我們自己商戶的密鑰 app_private_key_path=private_key_path, # 支付寶的公鑰 alipay_public_key_path=ali_pub_key_path, # 支付寶的公鑰,驗證支付寶回傳消息使用,不是你自己的公鑰, # debug為true時使用沙箱的url。如果不是用正式環境的url debug=True, # 默認False, return_url="http://47.107.36.249:8001/alipay/return/" ) # 進行驗證,是否為支付寶請求過來的數據 verify_re = alipay.verify(processed_dict, sign) # 這里可以不做操作.因為不管發不發return url, notify url都會修改訂單狀態. if verify_re == 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() return Response('success') def post(self,request): """ 處理支付寶的notify_url :param request: :return: """ # 存放post里面所有的數據 processed_dict = { } # 取出post里面的數據,數據在request.POST里面 for key, value in request.POST.items(): processed_dict[key] = value # 把sign和空數據都需要pop掉,阿里文檔有說明 sign =processed_dict.pop("sign",None) # 測試用例 alipay = AliPay( # 沙箱里面的appid值 appid="2016092900626681", # notify_url是異步的url app_notify_url="http://47.107.36.249:8001/alipay/return/", # 我們自己商戶的密鑰 app_private_key_path=private_key_path, # 支付寶的公鑰 alipay_public_key_path=ali_pub_key_path, # 支付寶的公鑰,驗證支付寶回傳消息使用,不是你自己的公鑰, # debug為true時使用沙箱的url。如果不是用正式環境的url debug=True, # 默認False, return_url="http://47.107.36.249:8001/alipay/return/" ) # 進行驗證,是否為支付寶請求過來的數據 verify_re = alipay.verify(processed_dict, sign) if verify_re == 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() # 需要返回一個'success'給支付寶,如果不返回,支付寶會一直發送訂單支付成功的消息 return Response('success')
# 完成支付訂單的url
# 創建訂單的時候生成一個支付的url,這個邏輯OderSerializer和OrderDetailSerializer中都添加
# 靈活字段,可以自己寫函數邏輯,不用依賴數據表的字段,drf參考網址:https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield
alipay_url = serializers.SerializerMethodField(read_only=True)
def get_alipay_url(self, obj):
# 測試用例
alipay = AliPay(
# 沙箱里面的appid值
appid="2016092900626681",
# notify_url是異步的url
app_notify_url="http://47.107.36.249:8001/alipay/return/",
# 我們自己商戶的密鑰
app_private_key_path = private_key_path,
# 支付寶的公鑰
alipay_public_key_path = ali_pub_key_path, # 支付寶的公鑰,驗證支付寶回傳消息使用,不是你自己的公鑰,
# debug為true時使用沙箱的url。如果不是用正式環境的url
debug=True, # 默認False,
return_url="http://47.107.36.249:8001/alipay/return/"
)
url = alipay.direct_pay(
# 訂單標題
subject= obj.order_sn ,
# 我們商戶自行生成的訂單號
out_trade_no= obj.order_sn ,
# 訂單金額
total_amount=obj.order_mount,
)
re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url)
return re_url
from rest_framework import serializers from goods.models import Goods from trade.models import ShoppingCart,OrderInfo,OrderGoods from goods.serializers import GoodsSerializer from utils.alipay import AliPay from Shop.settings import private_key_path,ali_pub_key_path class ShopCartDetailSerializer(serializers.ModelSerializer): """ 購物車詳情信息 """ # 一個購物車對應一個商品 goods = GoodsSerializer(many=False) class Meta: model = ShoppingCart fields = "__all__" class ShopCartSerializer(serializers.Serializer): # 獲取當前登錄的用戶 user = serializers.HiddenField(default=serializers.CurrentUserDefault()) nums = serializers.IntegerField(required=True,label="數量",min_value=1,max_value=None, error_messages={ "required":"請選擇購買數量", "min_value":"商品數量不能小於1", }) goods = serializers.PrimaryKeyRelatedField(required=True,queryset=Goods.objects.all()) def create(self, validated_data): # validated_data處理后的數據 # 獲取當前用戶 user = self.context["request"].user nums = validated_data["nums"] goods = validated_data["goods"] existed = ShoppingCart.objects.filter(user=user, goods=goods) # 如果購物車中有記錄,數量+1 # 如果購物車車沒有記錄,就創建 if existed: existed = existed[0] existed.nums += nums existed.save() else: existed = ShoppingCart.objects.create(**validated_data) return existed def update(self, instance, validated_data): # 修改商品數量 instance.nums = validated_data["nums"] instance.save() return instance class OrderGoodsSerializer(serializers.ModelSerializer): # 訂單詳情中的商品信息 goods = GoodsSerializer(many=False) class Meta: model = OrderGoods fields = "__all__" class OrderDetailSerializer(serializers.ModelSerializer): # 訂單詳情 goods = OrderGoodsSerializer(many=True) # 支付訂單的url # 參考網址:https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield alipay_url = serializers.SerializerMethodField(read_only=True) def get_alipay_url(self, obj): # 測試用例 alipay = AliPay( # 沙箱里面的appid值 appid="2016092900626681", # notify_url是異步的url app_notify_url="http://47.107.36.249:8001/alipay/return/", # 我們自己商戶的密鑰 app_private_key_path = private_key_path, # 支付寶的公鑰 alipay_public_key_path = ali_pub_key_path, # 支付寶的公鑰,驗證支付寶回傳消息使用,不是你自己的公鑰, # debug為true時使用沙箱的url。如果不是用正式環境的url debug=True, # 默認False, return_url="http://47.107.36.249:8001/alipay/return/" ) url = alipay.direct_pay( # 訂單標題 subject= obj.order_sn , # 我們商戶自行生成的訂單號 out_trade_no= obj.order_sn , # 訂單金額 total_amount=obj.order_mount, ) re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url) return re_url class Meta: model = OrderInfo fields = "__all__" class OrderSerializer(serializers.ModelSerializer): # 獲取當前登錄的用戶,界面不顯示user,相當於read_only user = serializers.HiddenField(default=serializers.CurrentUserDefault()) # 生成訂單的時候這些不用POST(不提交,服務器生成返回給用戶) pay_status = serializers.CharField(read_only=True) trade_no = serializers.CharField(read_only=True) order_sn = serializers.CharField(read_only=True) pay_time = serializers.DateTimeField(read_only=True) # 支付訂單的url # 靈活字段,可以自己寫函數邏輯,不用依賴數據表的字段,drf參考網址:https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield alipay_url = serializers.SerializerMethodField(read_only=True) def get_alipay_url(self, obj): # 測試用例 alipay = AliPay( # 沙箱里面的appid值 appid="2016092900626681", # notify_url是異步的url app_notify_url="http://47.107.36.249:8001/alipay/return/", # 我們自己商戶的密鑰 app_private_key_path = private_key_path, # 支付寶的公鑰 alipay_public_key_path = ali_pub_key_path, # 支付寶的公鑰,驗證支付寶回傳消息使用,不是你自己的公鑰, # debug為true時使用沙箱的url。如果不是用正式環境的url debug=True, # 默認False, return_url="http://47.107.36.249:8001/alipay/return/" ) url = alipay.direct_pay( # 訂單標題 subject= obj.order_sn , # 我們商戶自行生成的訂單號 out_trade_no= obj.order_sn , # 訂單金額 total_amount=obj.order_mount, ) re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url) return re_url def generate_order_sn(self): # 生成訂單號,當前時間 + userid + 兩位隨機數 import time from random import Random random_ins = Random() order_sn = "{time_str}{user_id}{random_str}".format(time_str=time.strftime("%Y%m%d%H%M%S"), user_id=self.context["request"].user.id, random_str=random_ins.randint(10,99)) return order_sn def validate(self, attrs): # 實例化上面方法,validate中添加order_sn,在view中perform_create方法里save attrs["order_sn"]=self.generate_order_sn() return attrs class Meta: model = OrderInfo fields = "__all__"
# 測試代碼改為服務器,記得上傳本地修改代碼,創建訂單-->生成訂單(訂單生成時里面包含支付url)

vue靜態文件放到django中
# vue使用build生成的靜態文件(dist文件夾)
cnpm run build

# 從dist文件夾中把index.html拷貝到templates目錄下

# 修改index.html中靜態文件路徑
<script type="text/javascript" src="/static/index.entry.js"></script>
# django中創建static目錄
把index.entry.js考到django的static目錄下面 把dist/static下的兩個文件夾拷貝到django static目錄下

# setting設置static和templates路徑
1.static路徑 STATIC_URL = '/static/' STATICFILES_DIRS = ( os.path.join(BASE_DIR, "static"), ) 2.templates模板路徑 'DIRS': [os.path.join(BASE_DIR, 'templates')],
# 配置index的url
from django.views.generic import TemplateView url(r'^index/', TemplateView.as_view(template_name="index.html"),name="index"),
# 配置支付成功return的地址
response = redirect("index") response.set_cookie("nextPath", "pay", max_age=2) return response else: response = redirect("index") return response
from rest_framework import viewsets from rest_framework.permissions import IsAuthenticated from rest_framework.authentication import SessionAuthentication from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework import mixins from trade.serializers import ShopCartSerializer,ShopCartDetailSerializer,OrderSerializer,OrderDetailSerializer from trade.models import ShoppingCart,OrderInfo,OrderGoods from utils.permissions import IsOwnerOrReadOnly class ShoppingCartViewset(viewsets.ModelViewSet): """ 購物車功能 list: 獲取購物車詳情 create: 加入購物車 delete: 刪除購物車記錄 """ permission_classes = (IsAuthenticated, IsOwnerOrReadOnly) authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication) serializer_class = ShopCartSerializer # 搜索的字段 lookup_field = "goods_id" def get_serializer_class(self): if self.action == "list": return ShopCartDetailSerializer else: return ShopCartSerializer # 返回當前用戶購物車列表頁 def get_queryset(self): return ShoppingCart.objects.filter(user=self.request.user) class OrderViewset(mixins.ListModelMixin, mixins.CreateModelMixin, mixins.RetrieveModelMixin,mixins.DestroyModelMixin, viewsets.GenericViewSet): # 此處不使用ModelViewSet,因為訂單不能修改,不能使用UpdateModelMixin """ 訂單詳情 List: 獲取個人訂單 delete: 刪除訂單 create: 新增訂單 Retrieve: 訂單詳情 """ permission_classes = (IsAuthenticated, IsOwnerOrReadOnly) authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication) serializer_class = OrderSerializer # 獲取當前用戶訂單 def get_queryset(self): return OrderInfo.objects.filter(user= self.request.user) # 動態獲取 def get_serializer_class(self): if self.action == "retrieve": return OrderDetailSerializer return OrderSerializer # 在訂單提交保存之前還需要多兩步步驟,所以這里自定義perform_create方法 # 1.將購物車中的商品保存到OrderGoods中 # 2.清空購物車 def perform_create(self, serializer): # save之前生成訂單號 order = serializer.save() shop_carts = ShoppingCart.objects.filter(user=self.request.user) for shop_cart in shop_carts: order_goods = OrderGoods() order_goods.goods = shop_cart.goods order_goods.goods_num = shop_cart.nums order_goods.order = order order_goods.save() # 清空購物車 shop_cart.delete() return order from rest_framework.views import APIView from utils.alipay import AliPay from Shop.settings import private_key_path,ali_pub_key_path from datetime import datetime from rest_framework.response import Response from django.shortcuts import redirect class AlipayView(APIView): def get(self,request): """ 處理支付寶的return_url返回 :param requeat: :return: """ processed_dict = { } # 獲取GET中的數據 for key, value in request.GET.items(): processed_dict[key] = value # 取出sign和空數據 sign =processed_dict.pop("sign",None) # 測試用例 alipay = AliPay( # 沙箱里面的appid值 appid="2016092900626681", # notify_url是異步的url app_notify_url="http://47.107.36.249:8001/alipay/return/", # 我們自己商戶的密鑰 app_private_key_path=private_key_path, # 支付寶的公鑰 alipay_public_key_path=ali_pub_key_path, # 支付寶的公鑰,驗證支付寶回傳消息使用,不是你自己的公鑰, # debug為true時使用沙箱的url。如果不是用正式環境的url debug=True, # 默認False, return_url="http://47.107.36.249:8001/alipay/return/", ) # 進行驗證,是否為支付寶請求過來的數據 verify_re = alipay.verify(processed_dict, sign) # 這里可以不做操作.因為不管發不發return url, notify url都會修改訂單狀態. if verify_re == 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") # response = redirect("index") # response.set_cookie("nextPath", "pay", max_age=2) return response else: response = redirect("index") return response def post(self,request): """ 處理支付寶的notify_url :param request: :return: """ # 存放post里面所有的數據 processed_dict = { } # 取出post里面的數據,數據在request.POST里面 for key, value in request.POST.items(): processed_dict[key] = value # 把sign和空數據都需要pop掉,阿里文檔有說明 sign =processed_dict.pop("sign",None) # 測試用例 alipay = AliPay( # 沙箱里面的appid值 appid="2016092900626681", # notify_url是異步的url app_notify_url="http://47.107.36.249:8001/alipay/return/", # 我們自己商戶的密鑰 app_private_key_path=private_key_path, # 支付寶的公鑰 alipay_public_key_path=ali_pub_key_path, # 支付寶的公鑰,驗證支付寶回傳消息使用,不是你自己的公鑰, # debug為true時使用沙箱的url。如果不是用正式環境的url debug=True, # 默認False, return_url="http://47.107.36.249:8001/alipay/return/" ) # 進行驗證,是否為支付寶請求過來的數據 verify_re = alipay.verify(processed_dict, sign) if verify_re == 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() # 需要返回一個'success'給支付寶,如果不返回,支付寶會一直發送訂單支付成功的消息 return Response('success')
# 訪問http://47.107.36.249:8001/index/
七、首頁、商品數量、緩存、限速功能開發
1.輪播圖接口實現和Vue調試
# 首先把pycharm環境改成本地的,vue中local_host也改成本地
# goods/serializer
class BannerSerializer(serializers.ModelSerializer):
'''
輪播圖
'''
class Meta:
model = Banner
fields = "__all__"
# goods/views.py
class BannerViewset(mixins.ListModelMixin, viewsets.GenericViewSet):
"""
首頁輪播圖
"""
queryset = Banner.objects.all().order_by("index")
serializer_class = BannerSerializer
# url設置,配置首頁輪播圖的url
router.register(r'banners', BannerViewset, base_name="banners")
# 在xadmin后台添加首頁輪播圖圖片

2.新品接口功能開發
# 在表設計Goods的model有一個字段is_new
is_new = models.BooleanField("是否新品",default=False)
# goods/filters,實現這個接口只要在GoodsFilter里面添加一個過濾就可以了
class Meta: model = Goods fields = ['pricemin','pricemax','is_hot','is_new']
# 在后台xadmin設置幾個商品 is_new

3.首頁商品分類顯示功能
實現四個功能(1.商品商標(多個),2.大類下的二級類,3.廣告商品,4.所有商品)
# goods/serializers.py
from rest_framework import serializers
from goods.models import Goods,GoodsCategory,GoodsCategoryBrand,IndexAd
from django.db.models import Q
class BrandSerializer(serializers.ModelSerializer):
"""
大類下面品牌名商標
"""
class Meta:
model = GoodsCategoryBrand
fields = "__all__"
class IndexCatagorySerializer(serializers.ModelSerializer):
# GoodsCategoryBrand這張表有個外鍵指向category,一個category有多個brand,所以使用many=Ture
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),嵌套必須加,不加的話image序列化后不會添加前面的域名
# 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_serialiser = GoodsSerializer(all_goods,many=True,context={'request':self.context['request']})
return goods_serialiser.data
class Meta:
model = GoodsCategory
fields = "__all__"
# goods/views.py
class IndexCategoryViewset(mixins.ListModelMixin, viewsets.GenericViewSet):
"""
首頁商品分類數據
"""
queryset = GoodsCategory.objects.filter(is_tab=True, name__in=["生鮮食品","酒水飲料"])
serializer_class = IndexCatagorySerializer
# url,配置首頁商品系列數據
from goods.views import IndexCategoryViewset router.register(r'indexgoods', IndexCategoryViewset, base_name="indexgoods")

4.商品點擊數和收藏數
(1).商品點擊數
# GoodsListViewSet其中繼承了mixins.RetrieveModelMixin(獲取商品詳情)
# RetrieveModelMixin源碼
class RetrieveModelMixin(object):
"""
Retrieve a model instance.
"""
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
# view.py,實現商品數+1
from rest_framework.response import Response class GoodsListViewSet(mixins.ListModelMixin,mixins.RetrieveModelMixin,viewsets.GenericViewSet): """ 商品列表頁,分頁,過濾器,搜索,排序 """ queryset = Goods.objects.all().order_by('goods__category_id') # queryset不可以更改 serializer_class = GoodsSerializer # 引用序列化相應的對象 pagination_class = GooodsPagination # 引用商品列表自定義分頁 # authentication_classes = (TokenAuthentication,) filter_backends = (DjangoFilterBackend,filters.SearchFilter,filters.OrderingFilter) # 過濾器,搜索,排序 # filter_fields = ('name', 'market_price') 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)
# 訪問106商品,http://127.0.0.1:8001/goods/106/

(2).收藏數
# 前面已經寫了UserFavViewset,其中繼承了mixins.CreateModelMixin,添加收藏實際就是創建數據庫,這里重寫它的perform_create方法就可以了
# user_operation/view.py,UserFavViewset新增代碼
# 實現用戶收藏的商品數量+1 def perform_create(self, serializer): instance = serializer.save() # 這里instance相當於UserFav model,通過它找到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()
# 訪問http://127.0.0.1:8001/userfavs/,收藏114商品


(3).用信號量實現收藏數+1和-1
# delete和create的時候django model都會發送一個信號量出來,用信號量的方式代碼分離性更好
# 注釋掉user_operation/view.py文件中UserFavViewset類perform_create方法
# user_operation/signal.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
# 用戶收藏的商品數量+1
@receiver(post_save, sender=UserFav)
def create_UserFav(sender, instance=None, created=False, **kwargs):
# 是否新建,因為update的時候也會進行post_save
if created:
goods = instance.goods
goods.fav_num += 1
goods.save()
# 用戶收藏的商品數量-1
@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
from django.apps import AppConfig
class UserOperationConfig(AppConfig):
name = 'user_operation'
verbose_name = '操作管理'
def ready(self):
import user_operation.signals
# 訪問剛才的114商品,刪除操作


5.商品庫存和銷量修改
(1).商品庫存數
# 影響商品庫存數量的行為:
- 新增商品到購物車
- 修改購物車數量
- 刪除購物車記錄
# trade/views.py,新增代碼
class ShoppingCartViewset(viewsets.ModelViewSet): # 庫存數-1,購物車數+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(serializer.instance.id) # 先保存之前的數據existed_nums(存在購買記錄) existed_nums = existed_record.nums saved_record = serializer.save() # 數量變化 nums = saved_record.nums - existed_nums goods = saved_record.goods goods.goods_num -= nums goods.save()
# trade/views.py,全部代碼
class ShoppingCartViewset(viewsets.ModelViewSet): """ 購物車功能 list: 獲取購物車詳情 create: 加入購物車 delete: 刪除購物車記錄 """ permission_classes = (IsAuthenticated, IsOwnerOrReadOnly) authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication) serializer_class = ShopCartSerializer # 搜索的字段 lookup_field = "goods_id" def get_serializer_class(self): if self.action == "list": return ShopCartDetailSerializer else: return ShopCartSerializer # 返回當前用戶購物車列表頁 def get_queryset(self): return ShoppingCart.objects.filter(user=self.request.user) # 庫存數-1,購物車數+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(serializer.instance.id) # 先保存之前的數據existed_nums(存在購買記錄) existed_nums = existed_record.nums saved_record = serializer.save() # 數量變化 nums = saved_record.nums - existed_nums goods = saved_record.goods goods.goods_num -= nums goods.save()
(2).銷量數
# trade/views.py,OrderViewset類,商品的銷量只有在支付成功后才會 +1(新增代碼)
# 訂單商品項,OrderInfo反向取OrderGoods,通過related_name="goods"找到OrderGoods對象 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()
# trade/views.py,全部代碼
class OrderViewset(mixins.ListModelMixin, mixins.CreateModelMixin, mixins.RetrieveModelMixin,mixins.DestroyModelMixin, viewsets.GenericViewSet): # 此處不使用ModelViewSet,因為訂單不能修改,不能使用UpdateModelMixin """ 訂單詳情 List: 獲取個人訂單 delete: 刪除訂單 create: 新增訂單 Retrieve: 訂單詳情 """ permission_classes = (IsAuthenticated, IsOwnerOrReadOnly) authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication) serializer_class = OrderSerializer # 獲取當前用戶訂單 def get_queryset(self): return OrderInfo.objects.filter(user= self.request.user) # 動態獲取 def get_serializer_class(self): if self.action == "retrieve": return OrderDetailSerializer return OrderSerializer # 在訂單提交保存之前還需要多兩步步驟,所以這里自定義perform_create方法 # 1.將購物車中的商品保存到OrderGoods中 # 2.清空購物車 def perform_create(self, serializer): # save之前生成訂單號 order = serializer.save() shop_carts = ShoppingCart.objects.filter(user=self.request.user) for shop_cart in shop_carts: order_goods = OrderGoods() order_goods.goods = shop_cart.goods order_goods.goods_num = shop_cart.nums order_goods.order = order order_goods.save() # 清空購物車 shop_cart.delete() return order from rest_framework.views import APIView from utils.alipay import AliPay from Shop.settings import private_key_path,ali_pub_key_path from datetime import datetime from rest_framework.response import Response from django.shortcuts import redirect class AlipayView(APIView): def get(self,request): """ 處理支付寶的return_url返回 :param requeat: :return: """ processed_dict = { } # 獲取GET中的數據 for key, value in request.GET.items(): processed_dict[key] = value # 取出sign和空數據 sign =processed_dict.pop("sign",None) # 測試用例 alipay = AliPay( # 沙箱里面的appid值 appid="2016092900626681", # notify_url是異步的url app_notify_url="http://47.107.36.249:8001/alipay/return/", # 我們自己商戶的密鑰 app_private_key_path=private_key_path, # 支付寶的公鑰 alipay_public_key_path=ali_pub_key_path, # 支付寶的公鑰,驗證支付寶回傳消息使用,不是你自己的公鑰, # debug為true時使用沙箱的url。如果不是用正式環境的url debug=True, # 默認False, return_url="http://47.107.36.249:8001/alipay/return/", ) # 進行驗證,是否為支付寶請求過來的數據 verify_re = alipay.verify(processed_dict, sign) # 這里可以不做操作.因為不管發不發return url, notify url都會修改訂單狀態. if verify_re == 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") # response = redirect("index") # response.set_cookie("nextPath", "pay", max_age=2) return response else: response = redirect("index") return response def post(self,request): """ 處理支付寶的notify_url :param request: :return: """ # 存放post里面所有的數據 processed_dict = { } # 取出post里面的數據,數據在request.POST里面 for key, value in request.POST.items(): processed_dict[key] = value # 把sign和空數據都需要pop掉,阿里文檔有說明 sign =processed_dict.pop("sign",None) # 測試用例 alipay = AliPay( # 沙箱里面的appid值 appid="2016092900626681", # notify_url是異步的url app_notify_url="http://47.107.36.249:8001/alipay/return/", # 我們自己商戶的密鑰 app_private_key_path=private_key_path, # 支付寶的公鑰 alipay_public_key_path=ali_pub_key_path, # 支付寶的公鑰,驗證支付寶回傳消息使用,不是你自己的公鑰, # debug為true時使用沙箱的url。如果不是用正式環境的url debug=True, # 默認False, return_url="http://47.107.36.249:8001/alipay/return/" ) # 進行驗證,是否為支付寶請求過來的數據 verify_re = alipay.verify(processed_dict, sign) if verify_re == 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: # 訂單商品項,OrderInfo反向取OrderGoods,通過related_name="goods"找到OrderGoods對象 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


# 安裝模塊
pip install drf-extensions
# goods/views.py,GoodsListViewSet添加緩存
from rest_framework_extensions.cache.mixins import CacheResponseMixin #CacheResponseMixin一定要放在第一個位置 class GoodsListViewSet(CacheResponseMixin,mixins.ListModelMixin,mixins.RetrieveModelMixin,viewsets.GenericViewSet):
# settings中設置緩存過期時間
REST_FRAMEWORK_EXTENSIONS = {
'DEFAULT_CACHE_RESPONSE_TIMEOUT': 60 * 15 #15分鍾過期,時間自己可以隨便設定
}
7.drf配置redis緩存
# 使用django-redis第三方庫,官方中文文檔:https://django-redis-chs.readthedocs.io/zh_CN/latest/
8.drf的throttle設置api的訪問速率
# 為了防止爬蟲對服務器造成的重大壓力,對數據進行訪問速率限制就顯得非常的重要了
# 官方文檔:https://www.django-rest-framework.org/api-guide/throttling/

# settings配置限速
REST_FRAMEWORK = {
# 限速設置
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle', #未登陸用戶
'rest_framework.throttling.UserRateThrottle', #登陸用戶
],
'DEFAULT_THROTTLE_RATES': {
'anon': '5/minute', #每分鍾可以請求5次
'user': '7/minute', #每分鍾可以請求7次
}
}
# 登陸用戶連續刷新8次會出現限速提示

八、第三方登錄
1.申請應用
# 進入微博開放平台,首先要經過認證,然后才可以創建應用,鏈接地址:https://open.weibo.com/
# 創建應用

# 創建好應用后可以獲取“APP Key”

# 模擬第三方登錄(測試)
(1).我的應用-->高級設置

(2).我的應用-->測試信息

# apps/utils/weibo_login.py
def get_auth_url():
weibo_auth_url = 'https://api.weibo.com/oauth2/authorize'
redirect_url = 'http://47.107.36.249:8001/complete/weibo'
auth_url = weibo_auth_url+"?client_id={client_id}&redirect_uri={re_url}".format(client_id=4039556340,re_url= redirect_url)
# 第二種拼接
# auth_url = weibo_auth_url + "?client_id={0}&redirect_uri={1}".format(4039556340, redirect_url)
print(auth_url)
def get_access_token(code = '26247442d3a297193d0be5a6e3acd1ca'):
access_token_url = 'https://api.weibo.com/oauth2/access_token'
import requests
re_dict = requests.post(access_token_url,data={
'client_id':'4039556340',
'client_secret':'0e7647596763832770fa7c0cb91e0eda',
'grant_type':'authorization_code',
'code':code,
'redirect_uri':'http://47.107.36.249:8001/complete/weibo'
})
pass
# Dubug模式下可以看見access_token和uid
# '{"access_token":"2.00eOGEbGWIZ46E75cb0d56aaduP7cC","remind_in":"157679999","expires_in":157679999,"uid":"6044498708","isRealName":"true"}'
def get_user_url(access_token='',uid=''):
user_url = 'https://api.weibo.com/2/users/show.json?access_token={token}&uid={uid}'.format(token=access_token,uid=uid)
print(user_url)
if __name__ == "__main__":
# get_auth_url()
# get_access_token(code = '26247442d3a297193d0be5a6e3acd1ca')
get_user_url(access_token='2.00eOGEbGWIZ46E75cb0d56aaduP7cC', uid='6044498708')
# 拿到access_token和uid就可以訪問微博API用戶(user/show)接口信息

2.social_app_django第三方登錄
# GitHub上social_app_django鏈接地址:https://github.com/python-social-auth/social-app-django
# 文檔使用說明鏈接:https://python-social-auth.readthedocs.io/en/latest/,此處使用的是Django Framework
# 安裝social-auth-app-django
pip install social-auth-app-django==3.1.0
# 配置setting中的注冊app
INSTALLED_APPS = (
'social_django',
)
# 數據庫生成表,只需要做migrate,因為migration的文件已經生成好了
python manage.py migrate

# 操作完成會生成5張表

# 配置setting中的身份驗證后端
AUTHENTICATION_BACKENDS = (
'users.views.CustomBackend',
# social_core自定義認證類
'social_core.backends.weibo.WeiboOAuth2',
'social_core.backends.qq.QQOAuth2',
'social_core.backends.weixin.WeixinOAuth2',
# django的ModelBackend
'django.contrib.auth.backends.ModelBackend',
)
# 配置URL,第三方登錄接口
urlpatterns = patterns(
url('', include('social_django.urls', namespace='social'))
)
# 配置setting中的TEMPLATES模板
TEMPLATES = [
{
...
'OPTIONS': {
...
'context_processors': [
# 第三方登錄
...
'social_django.context_processors.backends',
'social_django.context_processors.login_redirect',
]
}
}
]
# settings里面設置APP Secret和App key配置
SOCIAL_AUTH_WEIBO_KEY = '4039556340' SOCIAL_AUTH_WEIBO_SECRET = '0e7647596763832770fa7c0cb91e0eda' SOCIAL_AUTH_QQ_KEY = 'qq' SOCIAL_AUTH_QQ_SECRET = '123' SOCIAL_AUTH_WEIXIN_KEY = 'weixin' SOCIAL_AUTH_WEIXIN_SECRET = '456'
# 瀏覽器訪問http://127.0.0.1:8000/login/weibo,登微博錄成功后還需要設置用戶跳轉到首頁
# settings里面設置登錄成功后跳轉到首頁
SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/index/'
# 登錄成功跳到首頁,發現還處於未登錄狀態,我們需要對源碼做修改
# social_core/actions.py
原始代碼
return backend.strategy.redirect(url)
修改為
# 修改源碼適配drf
from rest_framework_jwt.serializers import jwt_encode_handler,jwt_payload_handler
response = backend.strategy.redirect(url)
payload = jwt_payload_handler(user)
response.set_cookie("name",user.name if user.name else user.username, max_age=24*3600)
response.set_cookie("token", jwt_encode_handler(payload), max_age=24*3600)
return response
