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

前言

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

1、django切换sqllite至mysql
2、 用户注册功能
3、JWT用户认证功能
4、 获取用户信息

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

1、django切换sqllite至mysql

下载依赖

pip install -i https://pypi.douban.com/simple/ pymysql

将settings中DATABASES的配置修改为mysql

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

需要在 TestPlatform/init.py中添加

import pymysql
pymysql.version_info = (1, 4, 0, "final", 0)
pymysql.install_as_MySQLdb()

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 的User
AUTH_USER_MODEL = 'users.User'

然后在models.py中定义User类

from django.contrib.auth.models import AbstractUser
from 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.name

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

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)

因为这里是直接保存,所以我们通过接口传过来的 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()

这里还有一步操作

instance.username = instance.email

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

2.5 添加urls,并调试

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

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

然后调试
在这里插入图片描述

数据库中的数据
在这里插入图片描述

3、JWT用户认证功能

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

pip install -i https://pypi.douban.com/simple/ 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}}
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 None
3.3 注册相关信息

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

REST_FRAMEWORK = {
    # rest framework的认证机制
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ),
}

import datetime
JWT_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',
)
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"))

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

class UsersPagination(PageNumberPagination):
    '''
    商品列表自定义分页
    '''
    # 默认每页显示的个数
    page_size = 10
    # 可以动态改变每页显示的个数
    page_size_query_param = 'page_size'
    # 页码参数
    page_query_param = 'page'
    # 最多能显示多少页
    max_page_size = 100


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

接口调试:

单一数据接口
在这里插入图片描述
多数据接口:
在这里插入图片描述

5、总结

可能有人会觉得上面的代码很不“django ”,明明已经有很多封装好的东西没有拿来直接使用,而重新完成了一些逻辑 。
因为个人觉得直接使用django的 “视图集” 封装的太多,不看源码的话不知道这些类,方法 到底是做什么的,而且在这个项目中,我需要自己定制化一些东西,所以我选择 APIView 进行开发。

如果你有想实现的功能欢迎提交
本项目的代码已上传git
https://github.com/627886474/TestPlatform(本章内容分支 git checkout user)

如果你觉得项目对你有帮助,可以关注一下微信公众号,持续分享干货
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值