vue+django2.0.2-rest-framework 生鲜项目
商品类别数据和vue展示
一、商品类别数据接口
1、此次项目,关于商品类别分类需要两个接口,一个是head部的全部分类,用于header首页的全部分类,一级、二级、三级分类等;另一个是侧边栏提供的分类
首页分类:
侧边栏分页:
1.1、对于商品类需要提供分页、过滤、搜索、排序等功能
1.2、对于商品分类,不需要提供上述功能
上一篇博文中我们已经实现了商品列表页API接口,现在我们接着实现商品分类的API接口
1)goods/views.py中创建分类CategoryViewset类:
serializers.py会创建CategorySerializer,用于序列化GoodsCategory商品分类。由于商品分类共有三级,我们通过在serializers.py中实现三级GoodsCategory序列化,
再通过CategoryViewset中获取商品分类数据时传递进来的:category_type=1 ,实现三级分类顺序序列化
class CategoryViewset(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet): """ list: 商品分类列表数据 """ queryset = GoodsCategory.objects.filter(category_type=1) serializer_class = CategorySerializer
注:CategoryViewset需要继承mixins.RetrieveModelMixin,是为了商品分类接口实现后,我们可以通过url上加入ID就可以获取某个商品的所有信息,mixins.RetrieveModelMixin遵循restful api的规范 (如:GET /zoos/ID:获取某个指定动物园的信息
),已经帮我们将所有事做好了,不需要配置url等,只需要在商品分类api接口实现后,在url上加上/ID即可以直接获取某个商品的所有数据。
2)Serializers.py中添加三级分类的CategorySerializer:
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) #关联的是自身,拿到自己的下一类,不是单个,需加many=True class Meta: model = GoodsCategory fields = "__all__"
当queryset = GoodsCategory.objects.filter(category_type=1),实例化CategorySerializer时,Serializers.py中CategorySerializer拿到category_type=1(一级分类)的所有数据进行序列化,
因里面又包含sub_cat = CategorySerializer2(many=True) 二级GoodsCategory序列化,层层递进,就实现了三级分类按层次序列化。
3)配置到我们的Mxshop/url.py中:
# 配置GoodsCategory的url router.register(r'categories', CategoryViewset, base_name="categories")
说明:
- goods/views.py中创建的类,"""注释的内容,在后面生成drf文档的时候会显示出来,所有要写清楚
- 要想获取某一个商品的详情的时候,继承 mixins.RetrieveModelMixin 就可以了
此时,商品分类的API接口就实现了,访问http://127.0.0.1:8000/categories/,即可看到分类数据:
当要获取一级类目的某个类目时(因category_type=1,设置了一级限制),只需要在http://127.0.0.1:8000/categories/ 加上一级分类的ID就可以了,即:http://127.0.0.1:8000/categories/74,
页面效果:
二、前后端跨域问题
使用前后端分离的项目,不可避免的就会遇到跨域问题,一般解决方式有两种:
- 服务端解决跨域问题
- 前端解决跨域问题
本节主要讲述服务器(后端)解决跨域问题
1)终端进入项目虚拟环境,安装django-cors-headers:
pip install django-cors-headers
2)Mxshop中的setting.py配置:
①、INSTALLED_APPS加上配置:注册
INSTALLED_APPS = ( ... 'corsheaders', ... )
②、MIDDLEWARE中添加配置:添加中间件监听
MIDDLEWARE = [ # Or MIDDLEWARE_CLASSES on Django < 1.10 ... 'corsheaders.middleware.CorsMiddleware', #需要添加的 'django.middleware.common.CommonMiddleware', # 本来存在 ... ]
注:新添加的 CorsMiddleware 要放在 CommonMiddleware 前面
③、setting中直接增加 CORS_ORIGIN_ALLOW_ALL 设置:为True则表示所有域名下都能访问(忽略跨域问题),默认为False
# If True, the whitelist will not be used and all origins will be accepted. Defaults to False. CORS_ORIGIN_ALLOW_ALL = True
④、设置 CORS_ORIGIN_WHITELIST:允许访问的白名单,只有在白名单内的可以忽略跨域问题访问(第③步设为False):
# CORS CORS_ORIGIN_WHITELIST = ( '127.0.0.1:8080', 'localhost:8080', 'www.meiduo.site:8080', 'api.meiduo.site:8000' ) CORS_ALLOW_CREDENTIALS = True # 允许携带cookie
- 凡是出现在白名单中的域名,都可以访问后端接口
- CORS_ALLOW_CREDENTIALS 指明在跨域访问中,后端是否支持对cookie的操作。
也可以定义允许的匹配路径正则表达式:
CORS_ORIGIN_REGEX_WHITELIST = ('^(https?://)?(\w+.)?>google.com$', )
⑤、设置允许访问的方法:
CORS_ALLOW_METHODS = ( 'DELETE', 'GET', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'VIEW', )
⑥、设置允许的header:
CORS_ALLOW_HEADERS = ( 'XMLHttpRequest', 'X_FILENAME', 'accept-encoding', 'authorization', 'content-type', 'dnt', 'origin', 'user-agent', 'x-csrftoken', 'x-requested-with', 'Pragma', )
注:以上都是在setting中配置。
商品分类数据api接口:
跨域问题解决后,就可以来查看Django中数据,是否能跟前端vue接上了--前端使用vue语言,页面已经处理好,只要django数据接口设置好,跨域问题解决,前端就可以根据url获取django中提供的数据
首先,vue展示商品分类数据(上述已经做好后端--商品分类api接口):
1)在vue中的api.js文件中设置我们django的url及端口:
let loclahost = 'http://127.0.0.1:8000'
2)设置vue中获取接口数据的代码:
//获取商品类别信息 export const getCategory = params => { if('id' in params){ return axios.get(`${loclahost}/categorys/`+params.id+'/'); #获取8000端口下的单个商品分类数据 } else { return axios.get(`${loclahost}/categorys/`, params);#获取8000端口下的所有商品分类数据
} };
此时,访问url:127.0.0.1:8080 ,页面效果:
注意,上述操作前提需要pycharm中启动mxshop项目,终端启动vue前端项目
后端提供商品一级分类过滤数据操作:
接下来是商品一级分类的过滤,如上图所示,当我们点击导航栏上的商品类别时,比如点击生鲜食品、酒水饮料 ,这些都是属于一级类目的,我们需要提供一级类目的接口给前端调用,这样前端页面可以显示出该一级商品下的所有二级商品分类及所有三级商品:
我们需要在goods → filter.py中,自定义过滤方法:
from django.db.models import Q import django_filters from .models import Goods class GoodsFilter(django_filters.rest_framework.FilterSet): ''' 自定义过滤器,实现区间过滤 商品过滤的类 ''' #filters.NumberFilter有两个参数,name是要过滤的字段,lookup是执行的行为,‘小与等于本店价格’ pricemin = django_filters.NumberFilter(field_name="shop_price", lookup_expr='gte') pricemax = django_filters.NumberFilter(field_name="shop_price", lookup_expr='lte') # 方法:自定义过滤条件,过滤一级类目 ,field_name:字段名 , method:指定自定义方法 top_category = django_filters.NumberFilter(field_name="category", method='top_category_filter') def top_category_filter(self, queryset, name, value): # value:一级类目,如果两个值相等,说明是过滤一级类目 return queryset.filter(Q(category_id=value)|Q(category__parent_category_id=value)|Q(category__parent_category__parent_category_id=value)) class Meta: model = Goods fields = ['pricemin', 'pricemax','top_category']
三、drf的token登录和原理
前后端分离的系统,不需要做crsf的验证。app和网站服务端本来就是跨站了,
drf为我们提供了三种不同的authen验证:
BasicAuthentication、
SessionAuthentication 提供基础认证跟session认证,此时request.auth中是没有token相关的,但无论三种验证方式的任何一种,只要认证成功就会将用户信息添加到request.user中,我们可以通过
request.user获取当前用户。
如果是想获取request.auth中的token,则需要通过
TokenAuthentication 验证来获取。
客户端身份验证 token值:
Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
使用:
1)setting配置中INSTALLED_APPS添加:
INSTALLED_APPS = ( ... 'rest_framework.authtoken' # 新添加 drf自带用户验证 )
此时需要在在终端 python manage.py makemigrations 、migrate
原因在于authtoken会为我们创建一张关于token的表,所以需要做上述操作
注:凡是有表产生的都要注册到,INSTALLED_APPS 中
2)url.py配置:
url的配置,当前端或浏览器直接访问localhost下的api-token-auth时,会自动生成一个token令牌,token数据会保存到数据库的token表中,即上面migrate出来的token表中。token令牌是与用户(user_id)相关联的,
我们只需要将token令牌给到前端,在用户访问时将令牌以name:value( Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b )的格式放在header返回到后端验证就可以了。后端再通过request.auth获取token值进行验证。
from rest_framework.authtoken import views urlpatterns = [ # token path('api-token-auth/', views.obtain_auth_token) # 新添加 token验证 ]
3)客户端身份验证
对于客户端进行身份验证,令牌密钥应包含在 Authorization
HTTP header 中。关键字应以字符串文字 “Token” 为前缀,用空格分隔两个字符串。例如:
Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
注意: 如果您想在 header 中使用不同的关键字(例如 Bearer
),只需子类化 TokenAuthentication
并设置 keyword
类变量。
如果成功通过身份验证,TokenAuthentication
将提供以下凭据。
request.user
是一个 DjangoUser
实例.request.auth
是一个rest_framework.authtoken.models.Token
实例.
未经身份验证的响应被拒绝将导致 HTTP 401 Unauthorized
的响应和相应的 WWW-Authenticate header。
要想获取request.user和request.auth还要在settings中添加:
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.BasicAuthentication', 'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication'
)
}
drf使用场景:
用户注册时,在后端注册逻辑里添加下面代码,可实现token获取并保存到数据库中
from rest_framework.authtoken.models import Token token = Token.object.create(user = ...) print(token.key) # token令牌
drf的token缺点:
- 保存在数据库中,如果是一个分布式的系统,需要做多系统同步,非常麻烦
- token永久有效,没有过期时间,用户可以一直使用,如果被盗后果不堪设想
上述token验证是全局性的,即所有页面都需在验证成功之后才能访问,如需实现部分或指定验证功能,如淘宝中商品页面都是可以在未登录情况下访问,此时我们就可以配置局部即需要验证的页面进行token验证,而其他页面不做token验证
局部验证流程:
1)注释掉:
# setting中REST_FRAMEWORK中的TokenAuthenticition:
2)在需要做验证的页面加入验证:
from rest_framework.authentication import TokenAuthentication # 导入token验证相关模块 class GoodsListViewSet(mixins.ListModelMixin,viewsets.GenericViewSet): """ list: 商品列表页数据 """ … authentication_classes = (TokenAuthentication,) # 新增,部分页面token验证 …
四、Json Web Token的验证原理(用户登录验证)
使用方法:http://getblimp.github.io/django-rest-framework-jwt/
因为我们的drf 的token auth有它的缺点。所以最常用的还是JWT验证用户登录的方式。
JWT 是一个开放标准(RFC 7519),它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。JWT 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名。它具备两个特点
- 简洁(Compact)
可以通过URL, POST 参数或者在 HTTP header 发送,因为数据量小,传输速度快
- 自包含(Self-contained)
负载中包含了所有用户所需要的信息,避免了多次查询数据库
设计用户认证和授权系统(独立),以及单点登录:
单点登录,多个子域名通过一个统一的授权认证接口进行登录。
JWT 使用(全局验证):
1)虚拟环境下安装:
pip install djangorestframework-jwt
2)setting.py中的REST_FRAMEWORK 添加:
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.BasicAuthentication', 'rest_framework.authentication.SessionAuthentication', # 'rest_framework.authentication.TokenAuthentication' 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', #新添加 ,JWT验证 ) }
3)urls.py中配置:
from rest_framework_jwt.views import obtain_jwt_token # 导入jwt验证相关 # 新添加,jwt的token认证接口 path('login/', obtain_jwt_token )
JWT验证,默认使用的是用户名+密码验证,而本项目用户登录用的是手机号+密码注册登录,故需要自定义用户登录认证:
1)setting.py中配置:
AUTHENTICATION_BACKENDS = ( 'users.views.CustomBackend', # 用户认证的path路径(users/vlews下的CustomBackend) )
2)users/views.py:
# users.views.py from django.contrib.auth.backends import ModelBackend from django.contrib.auth import get_user_model from django.db.models import Q User = get_user_model() # 当前用户 class CustomBackend(ModelBackend): """ 自定义用户验证规则 """ def authenticate(self, username=None, password=None, **kwargs): try: # 不希望用户存在两个,get只能有一个。两个是get失败的一种原因 # 后期可以添加邮箱验证 user = User.objects.get( Q(username=username) | Q(mobile=username)) # django的后台中密码加密:所以不能直接password==password # UserProfile继承的AbstractUser中有def check_password(self, raw_password)方法,及加密后的密码 if user.check_password(password): return user except Exception as e: return None
注意:jwt验证调用的是django自带的authenticate验证方法,如上面所说默认使用用户名+密码验证,本次项目中使用的是手机号+密码验证登录,故需要自定义authenticate用户验证。当用户使用 手机号+password进行登录操作时,
经jwt验证(会跳到自定义authenticate方法中验证用户信息),验证通过时会生成一个token验证,前端可以拿到存到客户cookies中,客户下次登录或者访问携带该token便不需要再登录了
3)设置JWT用户验证的登录有效期:
setting.py中配置:
import datetime #有效期限 JWT_AUTH = { # JWT 全局配置 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), #有效期7天,也可以设置seconds=20(20秒)等 'JWT_AUTH_HEADER_PREFIX': 'JWT', #JWT跟前端保持一致,比如“token”,这里默认JWT }
* 测试:
访问登录url:http://127.0.0.1:8000/login/
web界面:
输入账号密码,post提交
提交成功,后端会直接给我们返回token,可以直接拿到前端中使用:
五、云片网发送短信验证码
注册需要用到验证码,本节手机短信验证码相关需要通过云片网发送(注册云片网)
云片网注册成功,可以设置多个子账号,每个子账号都有一个api key(很重要),发送短信验证码需要用到
初注册需要新建签名、新建模板,都需要经过审核 ,发送短信相关请查询云片网api文档使用说明
国内短信api文档:https://www.yunpian.com/doc/zh_CN/domestic/list.html
发送短信测试:
1)在云片网ip白名单设置中,将本地ip添加进去,原因是云片网默认ip地址不能发邮件,需手动添加到白名单才可以
2)在apps中新建包:utils包,
3)在utils包中新建py文件:yunpian.py
4)yunpian.py编写代码:
# encoding: utf-8 __author__ = 'mtianyan' __date__ = '2018/3/8 0008 09:28' import json import requests class YunPian(object): def __init__(self, api_key): self.api_key = api_key self.single_send_url = "https://sms.yunpian.com/v2/sms/single_send.json" def send_sms(self, code, mobile): parmas = { "apikey": self.api_key, "mobile": mobile, "text": "【慕学生鲜】您的验证码是{code}。如非本人操作,请忽略本短信".format(code=code) } response = requests.post(self.single_send_url, data=parmas) re_dict = json.loads(response.text) return re_dict if __name__ == "__main__": yun_pian = YunPian("apikey的值") yun_pian.send_sms("2017", "手机号码")
注意text内容必须要与云片网中后台已申请过签名并审核通过的模板保持一致
项目中使用:
用户通过手机号注册,有验证码验证的环节,来实现用户注册时,drf实现发送短信验证码接口
手机号验证:
- 是否合法
- 是否已经注册
- 发送频率,60秒可发一次
1)setting中设置手机号正则表达式、云片网api key:
# 手机号码正则表达式 REGEX_MOBILE = "^1[358]\d{9}$|^147\d{8}$|^176\d{8}$" #云片网APIKEY APIKEY = "xxxxx327d4be01608xxxxxxxxxx"
2)在users app下新建serializers.py,用于验证码合法验证及序列化:
# users/serializers.py import re from datetime import datetime, timedelta from MxShop.settings import REGEX_MOBILE from users.models import VerifyCode from rest_framework import serializers from django.contrib.auth import get_user_model User = get_user_model() class SmsSerializer(serializers.Serializer): mobile = serializers.CharField(max_length=11) #函数名必须:validate + 验证字段名 def validate_mobile(self, mobile): """ 手机号码验证 """ # 是否已经注册 if User.objects.filter(mobile=mobile).count(): raise serializers.ValidationError("用户已经存在") # 是否合法 if not re.match(REGEX_MOBILE, mobile): raise serializers.ValidationError("手机号码非法") # 验证码发送频率 #60s内只能发送一次 one_mintes_ago = datetime.now() - timedelta(hours=0, minutes=1, seconds=0) if VerifyCode.objects.filter(add_time__gt=one_mintes_ago, mobile=mobile).count(): raise serializers.ValidationError("距离上一次发送未超过60s") return mobile
3)users app中的views.py新增SmsCodeViewset,继承于CreateModelMixin、viewsets.GenericViewSet,并重新重写CreateModelMixin的create方法:
from rest_framework.mixins import CreateModelMixin from rest_framework import viewsets from .serializers import SmsSerializer from rest_framework.response import Response from rest_framework import status from utils.yunpian import YunPian from MxShop.settings import APIKEY from random import choice from .models import VerifyCode class SmsCodeViewset(CreateModelMixin,viewsets.GenericViewSet): ''' 手机验证码 ''' serializer_class = SmsSerializer def generate_code(self): """ 生成四位数字的验证码 """ seeds = "1234567890" random_str = [] for i in range(4): random_str.append(choice(seeds)) 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) #生成验证码 code = self.generate_code() sms_status = yun_pian.send_sms(code=code, mobile=mobile) if sms_status["code"] != 0: return Response({ "mobile": sms_status["msg"] }, status=status.HTTP_400_BAD_REQUEST) else: code_record = VerifyCode(code=code, mobile=mobile) code_record.save() # 保存验证码及手机号 return Response({ "mobile": mobile }, status=status.HTTP_201_CREATED)
4)url.py配置:
from users.views import SmsCodeViewset # 配置codes的url router.register(r'code', SmsCodeViewset, base_name="code")
5)前端vue中访问后端url配置:
//短信 export const getMessage = parmas => { return axios.post(`${loclahost}/code/`, parmas) } # 说明:前端页面客户注册账号,点击获取验证码时触发此行代码,vue向后端 code 请求数据,获取手机验证码
云片网单条短信发送的使用说明:
六、用户注册功能
user serializer和validator验证
编写UserRegSerializer类,用于用户注册序列化
说明:
1)write_only:views中create()方法会将所有数据返回到api接口中显示,设置此字段可被忽略,不被序列化返回到api接口显示
2)validate + 字段名:如,validate_code,是对单个字段进行验证
3)validate(self,attrs):作用于所有字段,每个字段验证成功后都会以"username":"admin"的形式添加到attrs中,attrs是个字典类型,
存有所有字段:attrs:{"username":"1256485412","password":'123456'}
4)用户从前端post过来的值都会放入initial_data里面
5)drf中提供的字段验证,如单个字段验证:UniqueValidator
username = serializers.CharField(label="用户名", help_text="用户名", required=True, allow_blank=False,
validators=[UniqueValidator(queryset=User.objects.all(), message="用户已经存在")])
查找数据库中关于该字段的记录,如果有则报错:(message="用户已经存在")
drf提供的其他验证:
注意:username字段,用户填的必须是手机号,而不是姓名或昵称,本次项目用户注册用的手机号注册
1、users/serializers.py 用户注册序列化相关:
class UserRegSerializer(serializers.ModelSerializer): """用户注册""" #model中user表没有code字段,此字段是新增,用来验证前端用户注册输入的验证码 code = serializers.CharField(label="验证码",required=True, write_only=True, max_length=4, min_length=4, error_messages={ "blank": "请输入验证码", "required": "请输入验证码", "max_length": "验证码格式错误", "min_length": "验证码格式错误" }, help_text="验证码") #用户名验证,如果存在则报错 username = serializers.CharField(label="用户名", help_text="用户名(手机号)", required=True, allow_blank=False, validators=[UniqueValidator(queryset=User.objects.all(), message="手机号已经存在")]) #后端验证成功会把数据序列化显示到api接口中,设置write_only=True,让序列化时忽略此字段 password = serializers.CharField(label="密码",style={'input_type':'password'},write_only=True) def validate_code(self, code):# 验证码验证 # 验证码在数据库中是否存在,用户从前端post过来的值都会放入initial_data里面,排序(最新一条)。 verify_records = VerifyCode.objects.filter(mobile=self.initial_data["username"]).order_by("-add_time") if verify_records: # 获取到最新一条 last_record = verify_records[0] # 有效期为五分钟。 five_mintes_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0) if five_mintes_ago > last_record.add_time: raise serializers.ValidationError("验证码过期") if last_record.code != code: raise serializers.ValidationError("验证码错误") else: raise serializers.ValidationError("验证码错误") def validate(self,attrs):#每个字段验证成功后,都会添加到attrs这个字段中,attrs是个字典类型 #用户注册用手机号,但前端属性注明是username,故需要将username中的值传给mobile print(attrs) attrs["mobile"] = attrs["username"] del attrs["code"] #User没有code这个字段,用完可以del掉 return attrs #验证成功后,需要保存密码,后端调用.save()时,触发此处create方法,密码没进行验证,只加密处理 def create(self, validated_data):# 重载父类create方法 user =super(UserRegSerializer,self).create(validated_data=validated_data) user.set_password(validated_data["password"]) user.save() return user class Meta: model = User fields = {"username","code","password","mobile"}
2、views 用户注册:
class UserViewset(CreateModelMixin,viewsets.GenericViewSet): """用户注册""" serializer_class = UserRegSerializer queryset = User.objects.all()
3、urls配置:
# 用户注册 router.register(r'users', UserViewset, base_name="users")
4、前端vue api接口调用:
//注册 export const register = parmas => { return axios.post(`${loclahost}/users/`, parmas) }
前端客户注册账号:获取验证码 → 填写注册信息post提交 → 触发urls user路由 → views UserViewset → UserRegSeralizer序列化 ,验证通过 → views UserViewset的create方法
→ 调用create方法的save保存数据时 →触发UserRegSeralizer重载的create方法,将用户输入数据保存 → 同时,将序列化数据都显示到IPA接口让vue前端调用
注册成功:
除了上述在users/serializers.py中重载父类create方法给password加密的这种方式外,还有另一种方式:使用Django自带signals
首先,简单介绍下django自带的signals信号量,本项目主要讲Model_Signals:
当操作数据库数据时,会触发Model_Signals,即会发送全局信号,我们捕捉到信号便能做我们自己的逻辑操作,如
post_save:当新增数据库数据 或 更新数据库数据时会触发此方法,我们可以捕捉到并处理我们的逻辑
post_delete:删除数据时触发
好了,现在可以开始我们另一种方式注册客户账号了,
1)首先,将users/serializers.py下 UserRegSerializer 中注释掉关于密码加密并保存部分,其余代码不变:
#验证成功后,需要保存密码,后端调用.save()时,触发此处# create方法,密码没进行验证,只加密处理 # def create(self, validated_data):# 重载父类create方法 # user =super(UserRegSerializer,self).create(validated_data=validated_data) # user.set_password(validated_data["password"]) # user.save() # return user
2)在users下新建signals.py:
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_auth_token(sender, instance=None, created=False, **kwargs): # 判断是否新建,新建才需要设置密码并保存,因为update的时候也会触发post_save if created: password = instance.password instance.set_password(password) instance.save()
3)重载配置:
users/apps.py
# users/apps.py from django.apps import AppConfig class UsersConfig(AppConfig): name = 'users' verbose_name = "用户管理" def ready(self): # 新增方法 import users.signals # importusers/signals.py
AppConfig自定义的函数,会在django启动时被运行,现在添加用户的时候,密码就会自动加密存储了
上述注册方法,还没有设置token,这类注册适合用于客户注册账号,然后跳转到登录页面,客户需要再登录,登录成功才给客户设置token身份认证(JWT登录认证,自动生成token,可给前端使用,设置存放到客户cookies中),
但本项目前端使用的注册页面是这样的:
即注册成功便跳转到某页面,此时是已经给用户生成token并给到客户cookie中,所以需要重载create方法,给用户设置token,让前端可以在api接口拿到。
生成token的两个重要步骤:一是payload,二是encode:
payload = jwt_payload_handler(user)
re_dict["token"] = jwt_encode_handler(payload)
重载views/UserViewset中的create方法:
from rest_framework_jwt.serializers import jwt_payload_handler, jwt_encode_handler
# 此时views中的UserViewset类的代码: class UserViewset(CreateModelMixin,viewsets.GenericViewSet): ''' 用户 ''' serializer_class = UserRegSerializer queryset = User.objects.all() def create(self, request, *args, **kwargs): # 重载create方法 serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) user = self.perform_create(serializer) re_dict = serializer.data payload = jwt_payload_handler(user) re_dict["token"] = jwt_encode_handler(payload) # 设置token 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): return serializer.save()
* Django内置信号量:
Model signals pre_init # django的modal执行其构造方法前,自动触发 post_init # django的modal执行其构造方法后,自动触发 pre_save # django的modal对象保存前,自动触发 post_save # django的modal对象保存后,自动触发 pre_delete # django的modal对象删除前,自动触发 post_delete # django的modal对象删除后,自动触发 m2m_changed # django的modal中使用m2m字段操作第三张表(add,remove,clear)前后,自动触发 class_prepared # 程序启动时,检测已注册的app中modal类,对于每一个类,自动触发 Management signals pre_migrate # 执行migrate命令前,自动触发 post_migrate # 执行migrate命令后,自动触发 Request/response signals request_started # 请求到来前,自动触发 request_finished # 请求结束后,自动触发 got_request_exception # 请求异常后,自动触发 Test signals setting_changed # 使用test测试修改配置文件时,自动触发 template_rendered # 使用test测试渲染模板时,自动触发 Database Wrappers connection_created # 创建数据库连接时,自动触发
注意:如果登录页面设有 记住密码 字段,记得给token设置有效期,并将token设置到客户cookies中,
JWT认证中的token是不会保存在服务端的
七、退出登录
退出功能比较好做,,因为JWT验证时,token并不是保存在服务器端的。前端会直接给设置到客户cookies中取,客户退出登录时,清除cookies中相关的token就可以了
前端vue实现代码:
loginOut(){ # loginOut函数 cookie.delCookie('token'); cookie.delCookie('name'); //重新触发store //更新store数据 this.$store.dispatch('setInfo'); //跳转到登录 this.$router.push({name: 'login'}) },