神刀安全网

11- vue django restful framework 打造生鲜超市 -用户登录和手…

Vue+Django REST framework实战

搭建一个前后端分离的生鲜超市网站
Django rtf 完成 手机注册和用户登录(下)

user serializer和validator验证(注册功能)

注册页面需要我们输入手机号码 验证码 和密码

django的form 和 model form 是用来验证用户提交的字段的合法性的。

restful api 中实际是对资源的操作。我们的注册对应的资源就是用户。

因为用户注册必定是会crate model操作的,所以继承CreateModelMixin

先为viewset准备一个配套的Serializers

users/serializers.py(继承modelSerializer,因为字段都是必填项,都有的,虽然相比较用户model多了一个code字段)

通过一些小技巧,既能享受model Serializer带来的好处,又能突破它的限制

class UserRegSerializer(serializers.ModelSerializer):     code = serializers.CharField(required=True, write_only=True, max_length=4, min_length=4,error_messages={                                      "blank": "请输入验证码",                                      "required": "请输入验证码",                                      "max_length": "验证码格式错误",                                      "min_length": "验证码格式错误"                                  },                                  help_text="验证码") 

自定义我们每种验证的错误信息。

验证username是否存在:

    username = serializers.CharField(label="用户名", help_text="用户名", required=True, allow_blank=False,                                      validators=[UniqueValidator(queryset=User.objects.all(), message="用户已经存在")]) 
11- vue django restful framework 打造生鲜超市 -用户登录和手...

mark

drf为我们提供的唯一性字段验证。together是联合字段验证,也就是比如收藏这种。
关系唯一。

UniqueValidator的参数:

  • queryset required – This is the queryset against which uniqueness should be enforced.
  • message – The error message that should be used when validation fails.
  • lookup – The lookup used to find an existing instance with the value being validated. Defaults to ‘exact’.

lookup执行哪些操作。

    username = serializers.CharField(label="用户名", help_text="用户名", required=True, allow_blank=False,                                      validators=[UniqueValidator(queryset=User.objects.all(), message="用户已经存在")]) 

eclipse快捷键下,pycharm格式化快捷键为ctrl+shift+f

为mobile model添加可以为空的选项。

    mobile = models.CharField(null=True, blank=True, max_length=11, verbose_name="电话") 

因为前端只有手机号码这一个值。而这个值还存在username中。所以需要置空,保证验证成功。

在Serializer中添加code字段,这个code是多余字段不会被保存到数据库中

 def validate_code(self, code):          # get与filter的区别: get有两种异常,一个是有多个,一个是一个都没有。         # try:         #     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过来的值都会放入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("验证码错误") 

进行验证码的验证,可能出现的错误有过期,验证码错误。或记录根本不存在。

验证完之后将code这个字段删除掉

    # 不加字段名的验证器作用于所有字段之上。attrs是字段 validate之后返回的总的dict     def validate(self, attrs):         attrs["mobile"] = attrs["username"]         del attrs["code"]         return attrs 

views中的用户viewset中实例化Serializer

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

为该viewset配置路由

# 配置users的url router.register(r'users', UserViewset, base_name="users") 

drf验证默认的返回格式:

HTTP 400 Bad Request Allow: POST, OPTIONS Content-Type: application/json Vary: Accept  {     "username": [         "用户已经存在"     ],     "code": [         "验证码错误"     ] } 
  • 单个字段出错: 字段 + 数组
  • 联合字段出错: non_fields_error

哪个出错哪个高亮。

一般我们的传统开发都会喜欢自定义消息

{     "status": 0,     "msg": 验证码出错 } 

不好的地方:

前端自己要去做解析知道哪种对应着失败。
想要做到单个字段的提示,消息要做成这种格式

{     "status": 0,     "msg":     {         moblie: [""],         code: [""]       } } 

status如果只是用来判定用户的状态正确或失败,那跟httpcode的区别就几乎没有了
rest api 的设计模式。

拉勾网: 请求过多page, 已经发生异常显示的不正常的页面,但是200状态。
影响seo。对于Google

基于http code进行开发,可以让大家对于错误正确的状态判断保持一致

完善用户注册,django信号量实现用户密码修改。

用户注册逻辑编码。

后台添加一条用户短信验证码数据之后进行验证。

class UserViewset(CreateModelMixin, viewsets.GenericViewSet):     """     用户     """     serializer_class = UserRegSerializer     queryset = User.objects.all() 

http://www.django-rest-framework.org/api-guide/fields/

报错信息:

Got AttributeError when attempting to get a value for field `code` on serializer `UserRegSerializer`. The serializer field might be named incorrectly and not match any attribute or key on the `UserProfile` instance. Original exception text was: 'UserProfile' object has no attribute 'code'. 

这是因为我们配置的

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

有四个字段,而我们在Serializer的处理验证过程中已经删除了其中的code。

解决方案(重要参数):

code字段添加write only=true。就不会将此字段进行序列化返回给前端。

上面的错误查看源码中CreateModelMixin的部分代码。可以看到它在验证了是否有效之后执行了save。这些都是不会有问题的,但是当它return Response时,它会return S rializer的data(会依照我们在fields中的配置)。这时候因为data中的字段已经和model中的不再一致。

如果是一个正常的Serializer会将我们post过去的数据序列化之后返回回来。

password也被返回了回来。这是不合理的,为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 

重载Serializer的create方法。可以实现。

虽然重载代码量已经很少了,但是可能比较难理解,所以我们选择其他解决方案

附录:

label是如下图form中左侧字样,helptext是input框下面的。

django信号量机制。

django post_save()方法

我们的model对象进行操作的时候,会发出全局的信号。捕捉之后做出我们自己的操作。

https://docs.djangoproject.com/en/2.0/ref/signals/

django的信号量如request_started和scrapy中的是一致的。

删除之前做一些事情,就可以接收pre_delete的信号

post_save

http://www.django-rest-framework.org/api-guide/authentication/

Generating Tokens
By using signals
If you want every user to have an automatically generated Token, you can simply catch the User’s post_save signal.

from django.conf import settings from django.db.models.signals import post_save from django.dispatch import receiver from rest_framework.authtoken.models import Token  @receiver(post_save, sender=settings.AUTH_USER_MODEL) def create_auth_token(sender, instance=None, created=False, **kwargs):     if created:         Token.objects.create(user=instance) 

新建文件users/signals.py

# encoding: utf-8 __author__ = 'mtianyan' __date__ = '2018/3/9 0009 09:29'  from django.conf import settings from django.db.models.signals import post_save from django.dispatch import receiver from rest_framework.authtoken.models import Token  from django.contrib.auth import get_user_model User = get_user_model()   # 参数一接收哪种信号,参数二是接收哪个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() 

做完刚才这些操作,还要重载一个配置

否则会导致虽然没有任何报错信息,但是密码并没有被加密

apps.py中。

    def ready(self):         """         Override this method in subclasses to run code when Django starts.         """ 

这是AppConfig中我们可以在子类中自定义的函数,它将会在django启动时被运行。

    def ready(self):         import users.signals 

我们修改的mobile的字段可为空,只在代码中修改是没有用的,还需要我们进行migrations

这样我们才可以在后台中不需要mobile字段直接添加用户。

11- vue django restful framework 打造生鲜超市 -用户登录和手...

mark

可以看到成功的添加了密码并且为我们进行加密。

vue和注册功能联调

  • 查看vue的项目结构,找到register.vue
                    <input class="btn btn-green" id="jsMobileRegBtn" @click="isRegister" type="button" value="注册并登录"> 

点击注册并登录会调用isRegister 函数,

isRegister(){             var that = this;             register({                 password:that.password,                 username:that.mobile ,                 code:that.code,             }).then((response)=> {               cookie.setCookie('name',response.data.username,7);               cookie.setCookie('token',response.data.token,7)               //存储在store               // 更新store数据               that.$store.dispatch('setInfo');               //跳转到首页页面               this.$router.push({ name: 'index'})            }) 

调用了一个register的函数接口。

export const register = parmas => { return axios.post(`${host}/users/`, parmas) } 

实际上就是指向我们的user这个url。获取参数。将host 改为localhost进行联调

register传递的参数有password,username,code。这些都是前端页面post过来的值

常见的注册登录有注册完了你自己登录,以及注册完成帮你自动登录。

如果是让用户自己去登录,那么就将cookie.setcookie这两行注释掉。直接让它跳转到首页。

但是如果是自动登录,那么我们此时就没有给前台返回jwt 的token。

重载createmodelmixin里面的create函数。

框架原本实现代码:

    def create(self, request, *args, **kwargs):         serializer = self.get_serializer(data=request.data)         serializer.is_valid(raise_exception=True)         self.perform_create(serializer)         headers = self.get_success_headers(serializer.data)         return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)      def perform_create(self, serializer):         serializer.save() 

这里的perform中的save是save了当前的model(user)。但是并没有返回该model。我们要想获取到user model 就必须重写让它返回model

然后在执行perform_create之后插入我们自己的逻辑。

分析jwt的源码实现,找到它哪部分才是生成token的。

  • 从url入口。点进obtain_jwt_token
    # jwt的token认证     path('login/', obtain_jwt_token ) 

实现代码:

obtain_jwt_token = ObtainJSONWebToken.as_view() 
class ObtainJSONWebToken(JSONWebTokenAPIView):     """     API View that receives a POST with a user's username and password.      Returns a JSON Web Token that can be used for authenticated requests.     """     serializer_class = JSONWebTokenSerializer 

此时我们就可以去查看继承的父类: JSONWebTokenAPIView

该类中用户在post数据过来之后。

 def post(self, request, *args, **kwargs):         serializer = self.get_serializer(data=request.data)          if serializer.is_valid():             user = serializer.object.get('user') or request.user             token = serializer.object.get('token')             response_data = jwt_response_payload_handler(token, user, request)             response = Response(response_data)             if api_settings.JWT_AUTH_COOKIE:                 expiration = (datetime.utcnow() +                               api_settings.JWT_EXPIRATION_DELTA)                 response.set_cookie(api_settings.JWT_AUTH_COOKIE,                                     token,                                     expires=expiration,                                     httponly=True)             return response          return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 

关键在于token是直接从Serializer中获取的,那么token的生成应该是在Serializer中实现的。

            token = serializer.object.get('token') 

寻找我们的Serializer

serializer = self.get_serializer(data=request.data)      def get_serializer(self, *args, **kwargs):         serializer_class = self.get_serializer_class()         return self.serializer_class 

Serializer位于rest_framework_jwt/views.py的ObtainJSONWebToken类中

    serializer_class = JSONWebTokenSerializer 

该类中token的生成相关代码

                payload = jwt_payload_handler(user)                  return {                     'token': jwt_encode_handler(payload),                     'user': user                 } 

调用了jwt_encode_handler, 而该handler是api_setting中设置的

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER 

rest_framework_jwt/settings.py

DEFAULTS = {     'JWT_ENCODE_HANDLER':     'rest_framework_jwt.utils.jwt_encode_handler',       'JWT_PAYLOAD_HANDLER':     'rest_framework_jwt.utils.jwt_payload_handler',     } 

所以我们已经找到了生成token的两个重要步骤,一payload,二encode

实现代码:

from rest_framework_jwt.serializers import jwt_payload_handler, jwt_encode_handler          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) 

其中的token要和前端保持一致。注意将原本返回的Serializer.data进行加工之后返回。

退出功能

退出功能就变得更好做了,因为jwt的token并不是保存在服务器端的。

src/views/head/shophead.vue

实现代码:

                <a @click="loginOut">退出</a> 

退出按钮调用的是loginout函数

实现代码:

 loginOut(){         cookie.delCookie('token');         cookie.delCookie('name');         //重新触发store         //更新store数据         this.$store.dispatch('setInfo');         //跳转到登录         this.$router.push({name: 'login'})       }, 

清空token给axios发一个通知。跳转到登录页面。

注册页面测试,将codes与前端保持一致,以及修改localhost

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » 11- vue django restful framework 打造生鲜超市 -用户登录和手…

分享到:更多 ()

评论 抢沙发