第一章、Django序列化操作

1.django的view实现商品列表页基于View类)

# 通过json来序列化,但手写字典key代码量较大,容易出错;还有遇到时间,图片序列化会报错

from goods.base_views import Goodslistview

url(r'^goods/$',Goodslistview.as_view(),name='goods_list'),

urls.py

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

models.py

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

goods/models.py

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)

trade/models.py

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

user_operation/models.py

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

users/models.py

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")

users/serializers.py

from users.serializers import UserRegSerializer
class UserViewset(CreateModelMixin, viewsets.GenericViewSet):
"""
用户
"""
serializer_class = UserRegSerializer

users/views.py

# 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()

users/views.py

# user/serializer.py中添加password字段

fields = ("username","code","mobile","password")

# 设置password不能明文显示和加密保存

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

user/serializer.py

上面的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

user/serializer.py

# 使用信号量,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()

users/signals.py

# 加载配置

from django.apps import AppConfig

class UsersConfig(AppConfig):
name = 'users'
verbose_name = '用户管理' def ready(self):
import users.signals

users/apps.py

问题点:

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()

users/views.py

四、商品详情页功能

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__"

goods/serializers.py

2.热卖商品接口实现

# fields里面添加is_hot字段

class GoodsFilter(django_filters.rest_framework.FilterSet):
class Meta:
model = Goods
fields = ['pricemin','pricemax','is_hot']

goods\filters.py

# 后台管理,是否热销打勾

 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")

user_operation\serializers.py

# 增加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

user_operation\views.py

# 说明:继承的类

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

utils/permissions.py

# 引入用户验证和权限

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_operation/views.py

# 后台数据

# 拿到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)

user_operation/views.py

# 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")

users/serializers.py

# 用户视图函数

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()

users/views.py

# 主要添加的内容:

  • 继承mixins.RetrieveModelMixin -->>获取用户信息
  • 重写get_object -->>获取登录的用户
  • get_permissions -->>动态权限分配
  • get_serializer_class -->>动态序列化分配

# 输入id获取用户个人信息

# 修改用户个人信息,添加继承mixins.UpdateModelMixin类

class UserViewset(CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin,viewsets.GenericViewSet):

users/views.py

# 查看当前用户登陆信息

出现问题:

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")

user_operation/serializers.py

# 动态选择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)

user_operation/views.py

# 会员中心-->我的收藏

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")

user_operation/serializers.py

# 留言视图函数

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)

user_operation/views.py

# 配置用户留言的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")

user_operation/serializers.py

# 收货地址函数,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)

user_operation/views.py

# 配置用户收货地址的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

trade/serializers.py

# 购物车功能视图函数

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)

trade\views.py

# 配置购物车的url

from trade.views import ShoppingCartViewset
router.register(r'shopcarts', ShoppingCartViewset, base_name="shopcarts")

 2.修改购物车商品的数量(通过goods_id搜索出来的具体商品,需要重构update方法)

# ShoppingCartViewset类中添加商品id为搜索的字段

lookup_field = "goods_id"

trade/views.py

Serializer继承BaseSerializer,但是Seriazer中并没有更新update方法,所有添加一个update方法;ModelSerializer有update方法

# ShopCartSerializer类中添加update方法

def update(self, instance, validated_data):
# 修改商品数量
instance.nums = validated_data["nums"]
instance.save()
return instance

trade/serializers.py

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__"

trade/serializers.py

# 动态选择serializer,ShoppingCartViewset类加入get_serializer_class方法

def get_serializer_class(self):
if self.action == "list":
return ShopCartDetailSerializer
else:
return ShopCartSerializer

trade/views.py

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__"

trade/serializers.py

# 上面的订单详情序列化里面嵌套了两层序列化

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

trade/views.py

# 配置订单的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下面

官方文档说明

编写代码

# 把环境改成本地的,安装加密模块

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&timestamp=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="",
# 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="",
# 订单金额
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)

utils/alipay.py

# 点击打印出的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&timestamp=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="",
# 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="",
# 订单金额
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)

apps/utils.py

# 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="",
# 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="",
# 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')

trade/views.py

# 完成支付订单的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="",
# 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="",
# 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__"

trade/serializers.py

# 测试代码改为服务器,记得上传本地修改代码,创建订单-->生成订单(订单生成时里面包含支付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

trade/views.py(添加的部分代码)

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="",
# 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="",
# 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')

trade/views.py(全部代码)

# 访问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)

goods/view.py

# 访问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()

user_operation/view.py

# 访问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()

trade/views.py

(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="",
# 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="",
# 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')

trade/views.py

 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

Django序列化&django REST framework的更多相关文章

  1. 通过ajax GET方式查询数据,Django序列化objects

    点击“查找2”按钮,通过ajax GET方式进行查询数据,这样页面不需要整体刷新,之后清空tbody数据,将查询结果重新附加到tbody 前端html: <div class="box ...

  2. django: django rest framework 分页

    django: django rest framework 分页 2018年06月22日 13:41:43 linux_player_c 阅读数:665更多 所属专栏: django 实战   版权声 ...

  3. Django序列化

    一.Django序列化    1.序列化应用场景     1.关于Django中的序列化主要应用在将数据库中检索的数据返回给客户端用户,由于httpresponse只能返回字符串或者是字节,而从数据库 ...

  4. Django序列化组件Serializers详解

    本文主要系统性的讲解django rest framwork 序列化组件的使用,基本看完可以解决工作中序列化90%的问题,写作参考官方文档https://www.django-rest-framewo ...

  5. Python 之 Django框架( Cookie和Session、Django中间件、AJAX、Django序列化)

    12.4 Cookie和Session 12.41 cookie Cookie具体指的是一段小信息,它是服务器发送出来存储在浏览器上的一组组键值对,下次访问服务器时浏览器会自动携带这些键值对,以便服务 ...

  6. [django] Deploy Django Applications Using uWSGI and Nginx on Ubuntu 14.04

    关键点1:chmod-socket=666 (mysite_uwsgi.ini) 关键点2 : 工程目录和虚拟环境目录搞清楚 几个参考: http://uwsgi-docs.readthedocs.i ...

  7. Django组件---Django请求生命周期和中间件

    Django组件---Django请求生命周期和中间件 Django请求生命周期 说明: client代表浏览器,浏览器的内部为我们封装了socket,Django的WSGI模块也为我们封装了sock ...

  8. Django settings — Django 1.6 documentation

    Django settings - Django 1.6 documentation export DJANGO_SETTINGS_MODULE=mysite.settings django-admi ...

  9. Python之路【第二十三篇】:Django 初探--Django的开发服务器及创建数据库(笔记)

    Django 初探--Django的开发服务器及创建数据库(笔记) 1.Django的开发服务器 Django框架中包含一些轻量级的web应用服务器,开发web项目时不需再对其配置服务器,Django ...

随机推荐

  1. SSH密码和秘钥认证原理

    SSH登录方式主要分为两种: 1. 用户名密码验证方式 说明: (1) 当客户端发起ssh请求,服务器会把自己的公钥发送给用户: (2) 用户会根据服务器发来的公钥对密码进行加密: (3) 加密后的信 ...

  2. 大转盘(CocosCreator)

    推荐阅读:  我的CSDN  我的博客园  QQ群:704621321 1.在场景中搭建大转盘场景,假设 奖项有n项,对应的每项旋转角度如下: 第几项 需要旋转的角度 0 360/n/2 1 360/ ...

  3. Vue 关于多个父子组件嵌套传值

    prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来.这是为了防止子组件无意修改了父组件的状态——这会让应用的数据流难以理解. props: { selectMember: { ...

  4. redis数据结构、持久化、缓存淘汰策略

    Redis 单线程高性能,它所有的数据都在内存中,所有的运算都是内存级别的运算,而且单线程避免了多线程的切换性能损耗问题.redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,依次放 ...

  5. 从零开始搭建Java开发环境第一篇:Java工程师必备软件大合集

    1.JDK https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 目前主流的JDK版 ...

  6. MySql优化相关概念的理解笔记

    MySQL架构 查询执行流程 查询执行的流程是怎样的: 连接1.1客户端发起一条Query请求,监听客户端的‘连接管理模块’接收请求1.2将请求转发到‘连接进/线程模块’1.3调用‘用户模块’来进行授 ...

  7. 生产环境中Redis的key的设计

    问题:如果我们需要将MySql表的数据存储到Redis中该如何存储? 例如:有t_user表 id username email  11 leo  leo@163.com  22  laymans   ...

  8. java基础-多线程一

    什么是线程 说到线程就不得不说下进程了, 大家都知道,许许多多的线程组合在一起就成了一个进程,进程是由操作系统进行资源操作的一个最小的单位,线程则是比进程更小的实际执行操作的单位:每个线程都有自己的堆 ...

  9. .net中DES加密算法研究

    /// <summary> /// DES加密算法 /// </summary> /// <param name="toEncrypt">要加密 ...

  10. DB2 根据id查表

    SELECT * FROM SYSCAT.TABLES WHERE TBSPACEID = 2 AND TABLEID = 50 SELECT * FROM SYSCAT.COLUMNS WHERE  ...