龙空技术网

Django测试工具平台(三)——用户注册登录

大佬喝可乐 279

前言:

眼前各位老铁们对“as登录注册”大概比较讲究,兄弟们都想要分析一些“as登录注册”的相关知识。那么小编在网络上汇集了一些对于“as登录注册””的相关文章,希望你们能喜欢,看官们一起来学习一下吧!

前言

本章内容主要完成以下几个内容:

1、django切换sqllite至mysql

2、 用户注册功能

3、JWT用户认证功能

4、 获取用户信息

因为个人并不喜欢使用django 的 “视图集” ,所以后续的内容会使用“类视图”来实现API

1、django切换sqllite至mysql

下载依赖

pip install -i pymysql

将settings中DATABASES的配置修改为mysql

DATABASES = {    'default': {        'ENGINE': 'django.db.backends.mysql',        'NAME': 'testplatform',        'USER': 'root',        'PASSWORD': 'root',        'HOST': 'localhost',        'PORT': '3306',    }}12345678910

需要在 TestPlatform/init.py中添加

import pymysqlpymysql.version_info = (1, 4, 0, "final", 0)pymysql.install_as_MySQLdb()123
2、用户注册功能

用户是一个独立的模块,所以这里我们继续采用之前的“老四样”:

1、新建APP

python manage.py startapp users

2、将APP注册在settings.py文件中

INSTALLED_APPS=[ + users.apps.UsersConfig]

3、实现业务逻辑

4、将url注册在urls.py文件中

2.1定义用户数据模型

第一步,第二步没有什么异议,只是我们需要在settings.py中增加一些内容

# 使用 新定义的user 不用django 的UserAUTH_USER_MODEL = 'users.User'12

然后在models.py中定义User类

from django.contrib.auth.models import AbstractUserfrom django.db import models# Create your models here.class User(AbstractUser):    '''    用户信息表    '''    name = models.CharField(max_length=32, null=False, blank=False, verbose_name="姓名")    mobile = models.CharField(max_length=11, unique=True, null=False, blank=False, verbose_name="电话")    email = models.EmailField(max_length=128, unique=True,null=False, blank=False, verbose_name="邮箱")    # username 不做唯一处理    username = models.CharField(max_length=30, unique=False)    USERNAME_FIELD = 'mobile'    class Meta:        # 联合约束 mobile ,email不能重复        unique_together = ["mobile", "email"]        verbose_name = '用户'        verbose_name_plural = '用户'    def __str__(self):        return self.name1234567891011121314151617181920212223242526

AbstractUser类是django自带的用户类,我们继承这个类,添加一些我们需要的字段。AbstractUser默认username是唯一的,不符合我们的设计要求,所以 我将 USERNAME_FIELD 设置为’mobile’

执行命令行,自动生成模型相关的数据库表结构:

python manage.py makemigrations

python manage.py migrate

2.2 serializer 序列化

序列化是将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来,可以轻松地存储和传输数据

class UserRegSerializer(serializers.ModelSerializer):    name = serializers.CharField(label="用户名", help_text="用户名", required=True, allow_blank=False)    mobile = serializers.CharField(label="手机号", help_text="手机号", required=True, allow_blank=False,                                   validators=[UniqueValidator(queryset=User.objects.all(), message="手机号已经存在")])    email = serializers.EmailField(label="邮箱号", help_text="邮箱号", required=True, allow_blank=False,                                   validators=[UniqueValidator(queryset=User.objects.all(), message="邮箱号已经存在")])    password = serializers.CharField(        style={'input_type': 'password'}, help_text="密码", label="密码", write_only=True,    )    class Meta:        model = User        fields = ("name", "mobile", "email", 'password')12345678910111213
2.3 定义统一的返回格式

web开发中,通常我们会将系统返回的Reponse统一格式,方便前端获取数据。

我们再utilsapp/common中定义统一的返回数据

# 自定义状态码class HttpCode(object):    # 正常登陆    ok = 200    # 参数错误    paramserror = 400    # 权限错误    unauth = 401    # 方法错误    methoderror = 405    # 服务器内部错误    servererror = 500# 定义统一的 json 字符串返回格式def result(code=HttpCode.ok, message="", data=None, kwargs=None):    json_dict = {"code": code, "message": message, "data": data}    # isinstance(object对象, 类型):判断是否数据xx类型    if kwargs and isinstance(kwargs, dict) and kwargs.keys():        json_dict.update(kwargs)    return Response(json_dict)def ok():    return result(message="success")def ok_data(data=None):    return result(data=data,message="success")# 参数错误def params_error(message="params error", data=None):    return result(code=HttpCode.paramserror, message=message, data=data)# 权限错误def unauth(message="", data=None):    return result(code=HttpCode.unauth, message=message, data=data)# 方法错误def method_error(message="methods error", data=None):    return result(code=HttpCode.methoderror, message=message, data=data)# 服务器内部错误def server_error(message="server error", data=None):    return result(code=HttpCode.servererror, message=message, data=data)12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
2.4 用户注册业务逻辑

字段的校验逻辑 在UserRegSerializ类中已经声明了,我们这里直接将接口的数据request.data 获取,进行校验,符合规则就保存

class UserRegisterView(CreateAPIView):    serializer_class = UserRegSerializer    permission_classes = [AllowAny]    def post(self, request, *args, **kwargs):        serializer = self.get_serializer(data=request.data)        if serializer.is_valid():            serializer.save()            return ok()        else:            return params_error(message=serializer.errors)1234567891011

因为这里是直接保存,所以我们通过接口传过来的 password 字段也是明文保存到数据库中,这显然是不合理的。

在django 中我们使用 signals(信号)------允许解耦的应用在框架的其它地方发生操作时会被通知到,也就是说在特定事件发生时,可以发送一个信号去通知所有注册了这个信号的回调,在回调里进行想要的操作处理。

这里我们使用 post_save 方法,在调用save方法之后,对models进行操作,也就是对密码加密

User = get_user_model()@receiver(post_save, sender=User)def create_user(sender, instance=None, created=False, **kwargs):    if created:        password = instance.password        instance.username = instance.email        instance.set_password(password)        instance.save()12345678

这里还有一步操作

instance.username = instance.email

我们希望将email 作为唯一的username进行存储,使用mobile 也可以

2.5 添加urls,并调试

我们按照之前文档的步骤,添加urls

urlpatterns = [    path('register', views.UserRegisterView.as_view(), name='register'),    ]123

然后调试

数据库中的数据

3、JWT用户认证功能

json web token 简称JWT ,建议不知道这个概念的小伙伴 搜索一下相关资料,这里不做过多讲解。

下载依赖

pip install -i djangorestframework-jwt

3.1 定义token返回

因为我们集成了django 自带的 “ModelBackend” 类,我们如果不定义格式的话,会直接返回 token,为了保持统一格式,我们这里需要定义一个新的 ‘JWT_RESPONSE_PAYLOAD_HANDLER’,来保证格式的统一。

在 users/utils.py 中定义一个方法

def jwt_response_payload_handler(token, user=None, request=None):    """    自定义jwt认证成功返回数据    """    return {"code": 200, "message": "登录成功", "data": {"token": token}}12345
3.2 用户认证接口

我们再users/views.py中定义我们的登录接口,我们先通过 User.objects.get()方法来查询是否存在用户,其中 Q 的使用是 django.db.models 中的一个方法,等同于我们的mysql 语句

where email =xxx or mobile =xxx

然后通过check_password ,校验密码是否正确。

class CustomBackend(ModelBackend):    """    自定义用户验证    """    def authenticate(self, username=None, password=None, **kwargs):        try:            user = User.objects.get(Q(email=kwargs['mobile']) | Q(mobile=username))            if user.check_password(password):                return user        except Exception as e:            return None123456789101112
3.3 注册相关信息

在settings.py文件中注册我们刚才完成的信息

REST_FRAMEWORK = {    # rest framework的认证机制    'DEFAULT_AUTHENTICATION_CLASSES': (        'rest_framework.authentication.BasicAuthentication',        'rest_framework.authentication.SessionAuthentication',    ),}import datetimeJWT_AUTH = {    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),    'JWT_AUTH_HEADER_PREFIX': 'JWT',    # 自定义JWT返回数据    'JWT_RESPONSE_PAYLOAD_HANDLER': 'users.utils.jwt_response_payload_handler',}AUTHENTICATION_BACKENDS = (    'users.views.CustomBackend',   # 用户自定义认证    'django.contrib.auth.backends.ModelBackend',)1234567891011121314151617181920
3.4 调试

将接口信息添加至urls.py ,启动服务器就可以进行调试了

4、获取用户信息

我们在view.py中新增了一个 UserInfoView 类,并让这个类继承了 RetrieveAPIView 这个视图类,表示 获取 单个数据

IsAuthenticated:必须登录用户;

permission_classes = (IsAuthenticated,)用来做用户认证的

authentication_classes = (JSONWebTokenAuthentication,SessionAuthentication)

class UserInfoView(RetrieveAPIView):    serializer_class = UserDetailSerializer    authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)    def get(self, request, *args):        user_id = request.GET.get('id')        if request.user.id != int(user_id):            return unauth(message="无法查询他人信息")        else:            user = User.objects.filter(id=user_id)            user_info_str = serializers.serialize('json', user, fields=("name", "email", "mobile"))            user_info = json.loads(user_info_str)            return ok_data(data=user_info[0].get("fields"))1234567891011121314

新增 UserInfosView 类 ,并让这个类继承了 ListAPIView 这个视图类,表示 获取多个数据。我们知道在获取多数据的时候,如果不做分页,会对 数据库造成很大的压力,所以我们这里同时实现了一个分页的功能

class UsersPagination(PageNumberPagination):    '''    商品列表自定义分页    '''    # 默认每页显示的个数    page_size = 10    # 可以动态改变每页显示的个数    page_size_query_param = 'page_size'    # 页码参数    page_query_param = 'page'    # 最多能显示多少页    max_page_size = 100class UserInfosView(ListAPIView):    serializer_class = UserDetailSerializer    queryset = User.objects.all()    # 分页    pagination_class = UsersPagination    authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)    def get(self, request, *args, **kwargs):        user_infos_str = serializers.serialize('json', self.queryset.all().order_by('-id'), fields=("name", "email", "mobile"))        user_infos = json.loads(user_infos_str)        # 实例化分页对象,获取数据库中的分页数据        paginator = UsersPagination()        page_user_list = paginator.paginate_queryset(user_infos, self.request, view=self)        json_list = []        for user in page_user_list:            user_info = user.get("fields")            json_list.append(user_info)        return ok_data(json_list)12345678910111213141516171819202122232425262728293031323334

接口调试:

单一数据接口

多数据接口:

5、总结

可能有人会觉得上面的代码很不“django ”,明明已经有很多封装好的东西没有拿来直接使用,而重新完成了一些逻辑 。

因为个人觉得直接使用django的 “视图集” 封装得太多,不看源码的话不知道这些类,方法 到底是做什么的,而且在这个项目中,我需要自己定制化一些东西,所以我选择 APIView 进行开发。

如果你有想实现的功能欢迎提交

本项目的代码已上传git

(本章内容分支 git checkout user)

标签: #as登录注册 #django获取登录的用户名 #django登录界面代码