Django实现注册登录 --- Django自带认证系统

采用Django实现的注册登录功能

项目地址:https://github.com/ylpxzx/Django_Auth

主要实现内容

在这里插入图片描述

参考

开通阿里云短信服务:https://help.aliyun.com/document_detail/59210.html

开通并审核成功后,可先进入下面链接测试短信服务是否能正常发送

https://api.aliyun.com/new?spm=a2c4g.11186623.2.13.4a7919d9RuZfPg#/

效果演示

登录

在这里插入图片描述

注册

在这里插入图片描述

在未登录的情况下访问:http://127.0.0.1:8000/index/。将自带跳转到登录页面

登录成功后

跳转到气泡页


在这里插入图片描述

项目目录结构

在这里插入图片描述

项目实现

只介绍比较核心的代码部分,其余的可以到Github上拉取

扩展Django自带的认证Users表

定义模型
  • users/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser

# Create your models here.

#继承AbstractUser,对原有的User表进行扩展,记得在setting中修改为AUTH_USER_MODEL = 'users.LoginUser'
class LoginUser(AbstractUser):
    '''
    用户表
    '''
    phone_numbers = models.CharField(verbose_name='手机号', unique=True,max_length=11, default='')

    def __str__(self):
        return self.username
配置Settings.py

扩展了Users表后,需要在Settings.py声明你之后所使用的用户权限表

AUTH_USER_MODEL = 'users.LoginUser'  # 扩展系统的用户表后记得添加此行
LOGIN_URL = '/login/'  # 想进入需要登录才能访问的页面时,如果未登录,将跳转到LOGIN_URL指定的登录界面

实现阿里云短信发送服务

采用读取配置文件的方式加载短信发送所需要的字段,解耦。

  • conf/aliyun_api.py
import configparser
import os
from aliyunsdkcore.client import AcsClient
from aliyunsdkcore.request import CommonRequest


class AliYunSms:
    def __init__(self,phone,params):
        self.phone = phone
        self.params = params
        self.sms_param = parser_config('AliYun')
        self.SignName = self.sms_param['SignName']
        self.TemplateCode = self.sms_param['TemplateCode']
        self.client = AcsClient(self.sms_param['ACCESS_KEY_ID'], self.sms_param['ACCESS_KEY_SECRET'], 'cn-hangzhou')


    def send(self):
        request = CommonRequest()
        request.set_accept_format('json')
        request.set_domain('dysmsapi.aliyuncs.com')
        request.set_method('POST')
        request.set_protocol_type('https')  # https | http
        request.set_version('2017-05-25')
        request.set_action_name('SendSms')

        request.add_query_param('RegionId', "cn-hangzhou")
        request.add_query_param('PhoneNumbers', self.phone)  # 接收方手机号
        request.add_query_param('SignName', self.SignName) # SignName为审核通过的签名名称
        request.add_query_param('TemplateCode', self.TemplateCode) # TemplateCode为审核通过的模板code
        request.add_query_param('TemplateParam', self.params) # 要发送的验证码

        response = self.client.do_action_with_exception(request)

        return response
  • 配置Django redis缓存 :settings.py
    由于短信验证码的保存期限不长,而且考虑后续数量的问题,所以我们把验证码存入redis
# 配置redis缓存,短时间存储手机验证码
redis_cache = res = parser_config('Redis')
CAHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": redis_cache['LOCATION'],
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            # "PASSWORD": "密码",
            "DECODE_RESPONSES":True
        }
    },
}
  • 调用短信发送:users/views.py
class SendSmsView(View):
    def get(self,request):
        return render(request, 'registered.html')

    def post(self,request):
        # 处理/static/js/jquery.js的ajax请求:Sendpwd(sender)
        phone_number = request.POST.get('phone','')
        print('手机号:',phone_number)
        if LoginUser.objects.filter(phone_numbers=phone_number):
            ret = {"status": 40, 'msg': '该手机号已被注册'}
            return HttpResponse(json.dumps(ret))
        else:
            code = (random.randint(1000, 100000))
            # params = "{'code':%d}" % code

            # 不采用celery方式发送短信
            sms_obj = AliYunSms(phone_number,params)
            print(sms_obj)
            response = sms_obj.send()

            # 采用celery发送短信
            # send_sms.delay(phone_number,params)

            cache.set(phone_number, code, 150) # 存入redis
            ret = {"status": 20, 'msg': '验证码发送成功','code':code}
            return HttpResponse(json.dumps(ret))

ajax请求实现过程

使用ajax请求可以不加载页面发送请求。
实现请求的js文件主要位于以下两个文件

  • static/js/jquery.js
  • static/js/jquery.step.js

处理ajax请求的路由

  • users/urls.py
from django.conf.urls import url
from .views import RegisterView,IndexView,SendSmsView,CheckSmsView,PasswordSaveView,LoginView,CheckUserView
app_name = 'users'
urlpatterns = [
    url(r'^register/',RegisterView.as_view(),name='register'),
    url(r'^send_sms/',SendSmsView.as_view()),  # 处理static/js/jquery.js的ajax请求,请求事件:Sendpwd
    url(r'^check_sms/',CheckSmsView.as_view()), # 处理static/js/jquery.step.js的ajax请求,请求事件:$("#applyBtn").click(function(event)
    url(r'^save_psd/',PasswordSaveView.as_view()), # 处理static/js/jquery.step.js的ajax请求,请求事件:$("#submitBtn").click(function(event)
    url(r'^login/',LoginView.as_view(),name='login'),
    url(r'^check_user/',CheckUserView.as_view()),  # 处理static/js/jquery.js的ajax请求,请求事件:cliLogin
    url(r'^index/',IndexView.as_view(),name='index'),
]

ajax请求实现,举例:

  • static/js/jquery.step.js
$("#submitBtn").click(function(event) {
               var txtconfirm = $.trim($("#confirmpwd").val());
               var txtPwd = $("#password").val();
               
              if ($.trim(txtPwd) == "") {
                Tips('请输入你要设置的密码!');
               $("#txtPwd").focus();
              return;
                }
              if($.trim(txtconfirm) == "") {
                Tips('请再次输入密码!');
               $("#txtconfirm").focus();
              return;
                }
              if( $.trim(txtconfirm) != $.trim(txtPwd) ) {
                Tips('你输入的密码不匹配,请从新输入!');
               $("#txtconfirm").focus();
              return;
                }

                $.ajax({
                   url: "/save_psd/",
                   type: "POST",
                   dataType: "json",
                   data: {
                    'phone':$("#phone").val(),
                    'password':$("#confirmpwd").val()
                   },
                   success: function (data) {
                       console.log('sucess',data)
                       if (data.status == 20){
                           var yes=step.nextStep();
                           
                            // 倒计时读秒效果实现
                            var second = 5;
                            var time = document.getElementById("second");
                            //定义一个方法,获取span标签,修改span标签体内容,时间--
                            function showTime(){
                                second -- ;
                                //判断时间如果<= 0 ,则跳转到首页
                                if(second <= 0){
                                    //跳转到首页
                                    location.href = "/login/";
                                }
                                time.innerHTML = second +"";
                            }
                            //设置定时器,1秒执行一次该方法
                            setInterval(showTime,1000);
                       }else {
                           console.log('密码保存失败')
                           Tip(data.msg);
                            $("#txtPwd").focus();
                            return;
                       }
                   }
                })
        });

对应视图处理

  • users/views.py
class PasswordSaveView(View):
    def get(self,request):
        return render(request, 'registered.html')

    def post(self,request):
        # 处理/static/js/jquery.step.js的ajax请求:$("#submitBtn").click
        phone_number = request.POST.get('phone','')
        password = request.POST.get('password','')
        print('手机号:',phone_number,'密码:',password)

        if LoginUser.objects.filter(phone_numbers=phone_number):
            ret = {"status": 40, 'msg': '该手机号已被注册'}
            return HttpResponse(json.dumps(ret))
        else:

            # 保存注册成功的用户数据
            user_profile = LoginUser(phone_numbers=phone_number)
            user_profile.username = phone_number
            # user_profile.is_active = False
            user_profile.password = make_password(password)
            user_profile.save()
            ret = {"status": 20, 'msg': '注册成功!'}
            return HttpResponse(json.dumps(ret))

celery实现短信异步发送

celery一般用于处理比较耗时的请求任务,而短信、邮箱发送等都属于比较耗时的任务请求,可以接入celery处理。
注意:Celery、Django和Python之间有一定的版本影响
这里采用的各个版本为:

Python 3.7
celery 4.4.2
Django 3.0.6

# 安装Celery
pip install --upgrade -U celery  # 也可自行百度安装方法

# 在windows系统下,还需要安装eventlet
pip install eventlet

Celery接入步骤

  • 在与settings.py同目录下创建celery.py文件
from __future__ import absolute_import
import os
from celery import Celery

# 只要是想在自己的脚本中访问Django的数据库等文件就必须配置Django的环境变量
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'login_djangoauth.settings') # login_djangoauth改为自己的项目名

# app名字
app = Celery('login_djangoauth')  # login_djangoauth改为自己的项目名

# 配置celery
class Config:
    BROKER_URL = 'redis://:密码@127.0.0.1:6379/2'  # 记得加上密码,不然会一直报错:RecursionError: maximum recursion depth exceeded in comparison
    CELERY_RESULT_BACKEND = 'redis://:密码@127.0.0.1:6379/3' # 格式:redis :// [: password@] host [: port] [/ database][? [timeout=timeout[d|h|m|s|ms|us|ns]] [&database=database]]

# 无密码的情况:'redis://127.0.0.1:6379/2'

    CELERY_TIMEZONE = 'Asia/Shanghai'

    CELERY_ACCEPT_CONTENT = ['json', 'pickle']
    CELERY_TASK_SERIALIZER = 'json'
    CELERY_RESULT_SERIALIZER = 'json'


app.config_from_object(Config)
# 到各个APP里自动发现tasks.py文件
app.autodiscover_tasks()


@app.task(bind=True)
def debug_task(self):
    print('Request: {0!r}'.format(self.request))
  • 在与settings.py同目录下创建init.py文件
from __future__ import absolute_import, unicode_literals

# 告诉Django在启动时别忘了检测我的celery文件
from .celery import app as celery_app
__all__ = ['celery_app']
  • 在需要实现异步处理的应用下创建 tasks.py 文件
    这里我们在 users 文件夹下创建 tasks.py 文件
from celery import shared_task
from aliyunsdkcore.client import AcsClient
from aliyunsdkcore.request import CommonRequest
from conf.aliyun_api import parser_config

# 被@shared_task装饰的为调用的异步请求任务
@shared_task
def send_sms(phone,code):
    sms_obj = AliYunSms(phone,code)
    print(sms_obj)
    sms_obj.send()

class AliYunSms:
    def __init__(self,phone,params):
        self.phone = phone
        self.params = params
        self.sms_param = parser_config('AliYun')
        self.SignName = self.sms_param['SignName']
        self.TemplateCode = self.sms_param['TemplateCode']
        self.client = AcsClient(self.sms_param['ACCESS_KEY_ID'], self.sms_param['ACCESS_KEY_SECRET'], 'cn-hangzhou')


    def send(self):
        request = CommonRequest()
        request.set_accept_format('json')
        request.set_domain('dysmsapi.aliyuncs.com')
        request.set_method('POST')
        request.set_protocol_type('https')  # https | http
        request.set_version('2017-05-25')
        request.set_action_name('SendSms')
        request.add_query_param('RegionId', "cn-hangzhou")
        request.add_query_param('PhoneNumbers', self.phone)
        request.add_query_param('SignName', self.SignName)
        request.add_query_param('TemplateCode', self.TemplateCode)
        request.add_query_param('TemplateParam', self.params)

        response = self.client.do_action_with_exception(request)
        print(response)
  • 调用异步请求任务
    users/views.py
from .tasks import send_sms
class SendSmsView(View):
    def get(self,request):
        return render(request, 'registered.html')

    def post(self,request):
        # 处理/static/js/jquery.js的ajax请求:Sendpwd(sender)
        phone_number = request.POST.get('phone','')
        print('手机号:',phone_number)
        if LoginUser.objects.filter(phone_numbers=phone_number):
            ret = {"status": 40, 'msg': '该手机号已被注册'}
            return HttpResponse(json.dumps(ret))
        else:
            code = (random.randint(1000, 100000))
            params = "{'code':%d}" % code
            
            # 采用celery异步发送短信
            send_sms.delay(phone_number,params)

            cache.set(phone_number, code, 150)
            ret = {"status": 20, 'msg': '验证码发送成功','code':code}
            return HttpResponse(json.dumps(ret))
  • 启动Celery:另开一个终端启动celery
# login_djangoauth为项目名
celery worker -A login_djangoauth --loglevel=info --pool=solo

接入Celery后的异常问题解决

这里主要介绍个人遇到的一些异常问题,其他情况可自行百度

  1. RecursionError: maximum recursion depth exceeded in comparison
    方法:redis未加上密码时的异常问题,加上密码后,基本就解决了
  2. Celery ValueError: not enough values to unpack (expected 3, got 0)
    方法:win10上运行celery4.x就会出现这个问题,安装eventlet即可解决,pip install eventlet
  3. **TypeError: wrap_socket() got an unexpected keyword argument '_context' **
    方法:启动命令的问题,改为下面的命令启动celery
# login_djangoauth为项目名
celery worker -A login_djangoauth --loglevel=info --pool=solo
  1. django.db.utils.DatabaseError: DatabaseWrapper objects created in a thread can only be used in that same thread
    方法:windows平台下出现的问题,修改源码 site-packages/celery/__init__.py 下的 _patch_eventlet(0 函数

修改前

def _patch_eventlet():
    import eventlet
    import eventlet.debug
 
    eventlet.monkey_patch()
    blockdetect = float(os.environ.get('EVENTLET_NOBLOCK', 0))
    if blockdetect:
        eventlet.debug.hub_blocking_detection(blockdetect, blockdetect)

修改后

def _patch_eventlet():
    import eventlet
    import eventlet.debug

    eventlet.monkey_patch(thread=False)  # 修改部分
    blockdetect = float(os.environ.get('EVENTLET_NOBLOCK', 0))
    if blockdetect:
        eventlet.debug.hub_blocking_detection(blockdetect, blockdetect)

具体完整项目,自行从Github上拉取

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,923评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,154评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,775评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,960评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,976评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,972评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,893评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,709评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,159评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,400评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,552评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,265评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,876评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,528评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,701评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,552评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,451评论 2 352