Django实现的登录注册功能

1 前言

在Web开发中,用户登录和注册是最基本且必不可少的功能。Django,作为一个高级的Python Web框架,为我们提供了强大的工具和库来快速实现这些功能。下面,我将详细介绍如何使用Django来实现用户登录和注册功能。

2 功能介绍

该项目是使用django+bootstrp开发的项目,包含以下功能

  • 注册: 手机获取验证码、ModelForm数据验证、验证码redis超时处理,
  • 登录:手机验证码登录、账号密码登录、生成随机图片验证码、用户信息seesion处理

项目示例

1 账号密码登录

2 短信验证码登录

3 用户的注册

4 用户退出

3 申请容联云短信服务

登录和注册都需要一个短信的验证码,但是阿里、腾讯的短信服务申请太麻烦了,所有就申请了容联云短信服务,新用户有8元的免费额度只要项目不正式上线本地测试的话申请还是没有什么问题的,

容联云,全球智能通讯云服务商 (yuntongxun.com)

3.1 创建应用

3.2 创建短信模板

3.3 填写测试号码

3.4 安装sdk

python 复制代码
 pip install ronglian_sms_sdk

3.5 发送短信的python代码

python 复制代码
import json
from ronglian_sms_sdk import SmsSDK

# 容联云创建的应用中获取
accId = "应用的id"
accToken = "应用的token"
appId = "appid"


def send_sms(mobile, sms_code):
    """发送短信"""
    sdk = SmsSDK(accId, accToken, appId)
    tid = "1"
    try:
        resp = sdk.sendMessage(tid, mobile, (sms_code, ))
        res_json = json.loads(resp)
        if res_json.get("statusCode") == "000000":
            logger.info(f"向mobile[{mobile}]发送短信验证码[{sms_code}]成功")
        else:
            logger.info(f"发送短信失败:{resp}")
    except Exception as e:
        logger.error(f"发送短信失败:{e}")



if __name__ == '__main__':
    send_sms("平台测试手机号码", "9991")

运行脚本后手机会受到短信验证码,如下图

4 注册

注册分为两个部分:

  • 点击获取验证码:前端通过ajax发送请求到后端,后端生成随机验证码随机保存到redis中,同时调用短信服务发送短信到用户手机。
  • 点击注册:用户输入用户信息和验证码之后,后端进行校验,没有问题时保存到数据库,同时跳转到登录页面。

    代码实现: 直截关键代码,完整代码跳转最后下载完整代码
python 复制代码
# setting.py  django 使用redis
# 安装
pip install django-redis
# setting
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://192.168.1.200:6379",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "CONNECTION_POOL_KWARGS": {"max_connections": 100, "encoding": "utf-8"}
            # "PASSWORD": "123",
        }
    }
}

from django_redis import get_redis_connection
def test_django_redis(request):
    # 可以直接从连接池中拿到连接
    redis=get_redis_connection()

    age = str(conn.get('age'), encoding='utf-8')
    # 设置过期时间
    redis.set('name','xiaoming',4)  
    redis.set('xxx',test_redis)
python 复制代码
# view.py
from django.shortcuts import render
from django.http.response import JsonResponse
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
from django import forms
from django_redis import get_redis_connection
from users import models


class RegisterModelForm(forms.ModelForm):
    mobile_phone = forms.CharField(label="手机号",
                                   validators=[RegexValidator(r'^(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ])
    password = forms.CharField(label="密码",
                               min_length=8,
                               max_length=64,
                               error_messages={
                                   'min_length': "密码长度不能小于8个字符",
                                   'max_length': "密码长度不能大于64个字符"
                               },
                               widget=forms.PasswordInput())
    confirm_password = forms.CharField(label="重复密码",
                                       min_length=8,
                                       max_length=64,
                                       error_messages={
                                           'min_length': "重复密码长度不能小于8个字符",
                                           'max_length': "重复密码长度不能大于64个字符"
                                       },
                                       widget=forms.PasswordInput())
    code = forms.CharField(label="验证码", widget=forms.TextInput())

    class Meta:
        model = models.UserInfo
        # fields = "__all__"
        fields = ["username", "email", "password", "confirm_password", "mobile_phone", "code"]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for name, field in self.fields.items():
            field.widget.attrs['class'] = 'form-control'
            field.widget.attrs['placeholder'] = '请输入%s' % (field.label,)

    def clean_username(self):
        username = self.cleaned_data['username']
        exists = models.UserInfo.objects.filter(username=username).exists()
        if exists:
            raise ValidationError('用户名已存在')
        return username

    def clean_email(self):
        email = self.cleaned_data['email']
        exists = models.UserInfo.objects.filter(email=email).exists()
        if exists:
            raise ValidationError('邮箱已存在')
        return email

    def clean_confirm_password(self):
        pwd = self.cleaned_data.get('password')
        confirm_pwd = self.cleaned_data['confirm_password']
        if pwd != confirm_pwd:
            raise ValidationError('两次密码不一致')

    def clean_mobile_phone(self):
        mobile_phone = self.cleaned_data['mobile_phone']
        exists = models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists()
        if exists:
            raise ValidationError('手机号已注册')
        return mobile_phone

    def clean_code(self):
        code = self.cleaned_data['code']

        # mobile_phone = self.cleaned_data['mobile_phone']

        mobile_phone = self.cleaned_data.get('mobile_phone')
        if not mobile_phone:
            return code

        conn = get_redis_connection()
        redis_code = conn.get(mobile_phone)
        if not redis_code:
            raise ValidationError('验证码失效或未发送,请重新发送')

        redis_str_code = redis_code.decode('utf-8')

        if code.strip() != redis_str_code:
            raise ValidationError('验证码错误,请重新输入')

        return code


def register(request):
    """用户注册视图"""
    if request.method == "GET":
        form = RegisterModelForm()
        return render(request, "users/register.html", {"form": form})
    elif request.method == "POST":
        form = RegisterModelForm(data=request.POST)
        if form.is_valid():
            form.save()
            logger.info(f"用户[{form.username}]注册成功")
            return JsonResponse({"status": True})
        logger.warning(f"用户[{form.username}]注册失败")
        return JsonResponse({"status": False, "error": form.errors})

主要是用Django的ModeForm对数据格式进行验证,以及注册时候的一些逻辑判断

5 短信验证码登录

短信验证码登录也分为两个部分

  • 点击获取验证码: 这个过程和用户的注册获取验证码类似,可以复用注册获取手机验证的代码
  • 点击登录: 输入手机号码和验证码之后,后端校验数据的有效性,将用户的信息保存在session中,跳转主页

    代码实现: 直截关键代码,完整代码跳转最后下载完整代码
python 复制代码
# view,py 中数据校验和逻辑处理
class LoginSMSForm(forms.Form):
    """短信验证码登录模板"""
    mobile_phone = forms.CharField(label='手机号',
                                   validators=[RegexValidator(r'^(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ]
                                   )
    code = forms.CharField(label='验证码',
                           widget=forms.TextInput())

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for name, field in self.fields.items():
            field.widget.attrs['class'] = 'form-control'
            field.widget.attrs['placeholder'] = '请输入%s' % (field.label,)

    def clean_mobile_phone(self):
        mobile_phone = self.cleaned_data['mobile_phone']
        exists = models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists()
        if not exists:
            raise ValidationError('用户未注册')
        return mobile_phone

    def clean_code(self):
        code = self.cleaned_data['code']
        mobile_phone = self.cleaned_data.get('mobile_phone')
        if not mobile_phone:
            return code
        redis = get_redis_connection()
        redis_code = redis.get(mobile_phone)  # 根据手机号去获取验证码
        if not redis_code:
            raise ValidationError('验证码失效或未发送,请重新发送')
        real_code = redis_code.decode('utf-8')
        if code.strip() != real_code:
            raise ValidationError('验证码错误,请重新输入')
        return code


def login_sms(request):
    """短信验证码登录"""
    if request.method == "GET":
        form = LoginSMSForm()
        return render(request, "users/login_sms.html", context={"form": form})
    elif request.method == "POST":
        form = LoginSMSForm(data=request.POST)
        if form.is_valid():
            logger.info(f"用户登录成功")
            return JsonResponse({"status": True})
        logger.warning(f"用户登录失败")
        return JsonResponse({"status": False, "error": form.errors})

主要是用Django的ModeForm对数据格式进行验证,以及登录时候的一些逻辑判断

6 用户密码登录

用户密码登录也分为两个部分

  • 生成图像验证码: 加载网页时会请求后端生成图像验证码的接口,后端会根据pillow画出一张图片返回给前端,同时将验证码保存到session中, 图像验证码具体怎么生成的看生成随机图片验证码-CSDN博客
  • 点击登录: 填好信息后点击登录,请求端口登录接口后端会对数据进行验证,成功后跳转。

代码实现: 直截关键代码,完整代码跳转最后下载完整代码

python 复制代码
class LoginForm(forms.Form):
    username = forms.CharField(label='用户名',
                               min_length=4,
                               max_length=64,
                               error_messages={
                                   'min_length': "密码长度不能小于4个字符",
                                   'max_length': "密码长度不能大于64个字符"
                               })
    password = forms.CharField(label="密码",
                               min_length=8,
                               max_length=64,
                               error_messages={
                                   'min_length': "密码长度不能小于8个字符",
                                   'max_length': "密码长度不能大于64个字符"
                               },
                               widget=forms.PasswordInput(render_value=True))
    code = forms.CharField(label='图片验证码',
                           widget=forms.TextInput())

    def __init__(self, request,  *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.request = request
        for name, field in self.fields.items():
            field.widget.attrs['class'] = 'form-control'
            field.widget.attrs['placeholder'] = '请输入%s' % (field.label,)

    def clean_username(self):
        username = self.cleaned_data["username"]
        if not models.UserInfo.objects.filter(username=username).exists():
            logger.warning("用户未注册")
            raise ValidationError("用户未注册")
        return username

    def clean_password(self):
        username = self.cleaned_data.get("username")
        if not username:
            logger.warning("用户名为空")
            return username
        password = self.cleaned_data['password']
        encrypt_password = md5(password)
        if not models.UserInfo.objects.filter(username=username, password=encrypt_password).exists():
            logger.warning("用户名或者密码错误")
            raise ValidationError("用户名或者密码错误")
        return encrypt_password

    def clean_code(self):
        code = self.cleaned_data["code"]
        session_code = self.request.session.get("image_code")
        if not session_code:
            logger.warning("验证码已过期, 请求重新获取")
            raise ValidationError("验证码已过期, 请求重新获取")
        if code.strip().upper() != session_code.strip().upper():
            logger.warning(f"验证码输入错误,{code}:{session_code}")
            raise ValidationError("验证码输入错误")
        return code


def login(request):
    """用户账号密码登录"""
    if request.method == "GET":
        form = LoginForm(request)
    else:
        form = LoginForm(request, data=request.POST)
        if form.is_valid():
            username = form.cleaned_data["username"]
            user_obj = models.UserInfo.objects.filter(username=username).first()
            request.session["user_id"] = user_obj.id
            request.session.set_expiry(settings.SESSION_EXPIRY)
            logger.info("用户登录成功")
            return redirect("home")
        logger.warning("用户登录失败")
    return render(request, "users/login.html", context={"form": form})

主要是用Django的ModeForm对数据格式进行验证,以及登录时候的一些逻辑判断

6 运行项目

完整代码一下链接下载

【免费】Django+bootstrp实现用户的注册和登录功能资源-CSDN文库

bash 复制代码
# 1 下载代码解压
# 2 安装依赖
pip install -r requirement.txt
# 3 迁移数据库
python manage.py makemigrations
python manage.py migrate
# 4 登录容联云获取accId、accToken、appId替换sms.py
# 5 在setting中修改redis中的地址
# 4 启动项目
python manage.py runserver

另外安装redis的教程也可以看::Docket常见的软件部署1-CSDN博客

相关推荐
王佑辉11 分钟前
【redis】redis缓存和数据库保证一致性的方案
redis·面试
小码的头发丝、41 分钟前
Django中ListView 和 DetailView类的区别
数据库·python·django
小兜全糖(xdqt)1 小时前
mysql数据同步到sql server
mysql·adb
Karoku0661 小时前
【企业级分布式系统】Zabbix监控系统与部署安装
运维·服务器·数据库·redis·mysql·zabbix
gorgor在码农1 小时前
Redis 热key总结
java·redis·热key
想进大厂的小王1 小时前
项目架构介绍以及Spring cloud、redis、mq 等组件的基本认识
redis·分布式·后端·spring cloud·微服务·架构
Java 第一深情1 小时前
高性能分布式缓存Redis-数据管理与性能提升之道
redis·分布式·缓存
周全全1 小时前
MySQL报错解决:The user specified as a definer (‘root‘@‘%‘) does not exist
android·数据库·mysql
白云如幻2 小时前
MySQL的分组函数
数据库·mysql
知识的宝藏2 小时前
Django中间件应该怎么使用
中间件·django