Python入门自学进阶-Web框架——16Django登录/注册

Posted kaoa000

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python入门自学进阶-Web框架——16Django登录/注册相关的知识,希望对你有一定的参考价值。

以抽屉为原型,实现用户的注册和登录。

基本的界面:

 第一个知识点:自动发送验证码到邮箱,也就是实现自动发送邮件的功能:

 

 要自动给别人发送邮件,首先要有自己的邮箱,msg["From"]保存发送邮件的发送人名称、发件人邮箱地址,实际就是你自己的邮箱地址,发送人名称可以随意写。msg["Subject"]保存发送邮件的标题。

然后是配置邮件发送服务器,就是你自己的邮箱服务器,在你的邮箱中进行设置,启动POP3/SMTP服务,我使用的是搜狐的邮箱,启动后,会给你一个单独的登录密码,配置server.login时会用到。

批量发送邮件时,只要将邮件列表写在emailsend的第一个参数中就可以了。

第二个知识点,生成验证码图片:

主要是pillow库的使用,生成图片,图片中带有扰动过的随机字符串。

import random
from PIL import Image, ImageDraw, ImageFont, ImageFilter
# pip3 install Pillow
_letter_cases = "abcdefghjkmnpqrstuvwxy"  # 小写字母,去除可能干扰的i,l,o,z
_upper_cases = _letter_cases.upper()  # 大写字母
_numbers = ''.join(map(str, range(3, 10)))  # 数字
init_chars = ''.join((_letter_cases, _upper_cases, _numbers))

def create_validate_code(size=(120, 30),
                         chars=init_chars,
                         img_type="GIF",
                         mode="RGB",
                         bg_color=(255, 255, 255),
                         fg_color=(0, 0, 255),
                         font_size=18,
                         font_type="Monaco.ttf",
                         length=4,
                         draw_lines=True,
                         n_line=(1, 2),
                         draw_points=True,
                         point_chance = 2):
    '''
    @todo: 生成验证码图片
    @param size: 图片的大小,格式(宽,高),默认为(120, 30)
    @param chars: 允许的字符集合,格式字符串
    @param img_type: 图片保存的格式,默认为GIF,可选的为GIF,JPEG,TIFF,PNG
    @param mode: 图片模式,默认为RGB
    @param bg_color: 背景颜色,默认为白色
    @param fg_color: 前景色,验证码字符颜色,默认为蓝色#0000FF
    @param font_size: 验证码字体大小
    @param font_type: 验证码字体,默认为 ae_AlArabiya.ttf
    @param length: 验证码字符个数
    @param draw_lines: 是否划干扰线
    @param n_lines: 干扰线的条数范围,格式元组,默认为(1, 2),只有draw_lines为True时有效
    @param draw_points: 是否画干扰点
    @param point_chance: 干扰点出现的概率,大小范围[0, 100]
    @return: [0]: PIL Image实例
    @return: [1]: 验证码图片中的字符串
    '''

    width, height = size # 宽, 高
    img = Image.new(mode, size, bg_color) # 创建图形
    draw = ImageDraw.Draw(img) # 创建画笔

    def get_chars():
        '''生成给定长度的字符串,返回列表格式'''
        return random.sample(chars, length)

    def create_lines():
        '''绘制干扰线'''
        line_num = random.randint(*n_line) # 干扰线条数

        for i in range(line_num):
            # 起始点
            begin = (random.randint(0, size[0]), random.randint(0, size[1]))
            #结束点
            end = (random.randint(0, size[0]), random.randint(0, size[1]))
            draw.line([begin, end], fill=(0, 0, 0))

    def create_points():
        '''绘制干扰点'''
        chance = min(100, max(0, int(point_chance))) # 大小限制在[0, 100]

        for w in range(width):
            for h in range(height):
                tmp = random.randint(0, 100)
                if tmp > 100 - chance:
                    draw.point((w, h), fill=(0, 0, 0))

    def create_strs():
        '''绘制验证码字符'''
        c_chars = get_chars()
        strs = ' %s ' % ' '.join(c_chars) # 每个字符前后以空格隔开

        font = ImageFont.truetype(r'd:\\Monaco.ttf', font_size)

        font_width, font_height = font.getsize(strs)

        draw.text(((width - font_width) / 3, (height - font_height) / 3),
                    strs, font=font, fill=fg_color)

        return ''.join(c_chars)

    if draw_lines:
        create_lines()
    if draw_points:
        create_points()
    strs = create_strs()

    # 图形扭曲参数
    params = [1 - float(random.randint(1, 2)) / 100,
              0,
              0,
              0,
              1 - float(random.randint(1, 10)) / 100,
              float(random.randint(1, 2)) / 500,
              0.001,
              float(random.randint(1, 2)) / 500
              ]
    img = img.transform(size, Image.PERSPECTIVE, params) # 创建扭曲

    img = img.filter(ImageFilter.EDGE_ENHANCE_MORE) # 滤镜,边界加强(阈值更大)

    return img, strs

在views中函数:

from myutils import create_code
import io
def check_code(req):
    stream = io.BytesIO()
    img,code = create_code.create_validate_code()
    print(code)
    img.save(stream,'PNG')
    req.session['CheckCode'] = code
    return HttpResponse(stream.getvalue())

返回给前端的是图片的字节流,在前端,使用的是img标签:
<img class="check-img" src="check_code" alt="验证码" οnclick="ChangeCode(this);">,就显示图片了。而随机字符串保存在session中。

整个验证的前端:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/css/mycommon.css">
</head>
<body>
<div class="head-box">
    <div class="head-content">
        <a href="#" class="logo"></a>
        <div class="action-menu">
            <a href="#" class="tb active">全部</a>
            <a href="#" class="tb">42区</a>
            <a href="#" class="tb">段子</a>
            <a href="#" class="tb">图片</a>
            <a href="#" class="tb">挨踢1024</a>
            <a href="#" class="tb">你问我答</a>
        </div>
        <div class="key-search">
            <form action="/" method="post">
                <input type="text" class="search-txt" autocomplete="off">
                <a href="#" class="i" >
                    <span class="ico"></span>
                </a>
            </form>
		</div>
        % if request.session.is_login %
            <div class="action-nav">
                <a>动态</a>
                <a>通知</a>
                <a href="#" class="login-user"> request.session.user_info.username </a>
                <a id="loginout" href="loginout.html">退出</a>
            </div>
        % else %
            <div class="action-nav">
                <a href="#" class="register-login-btn">注册/登录</a>
            </div>
        % endif %
    </div>
</div>
<div class="shadow hide"></div>
<div class="accountDialog hide">
    <div id="model_login" class="login left">
        <div class="header">登陆</div>
        <div class="content">
            <div style="padding: 0 50px">
                <div class="tips">
                    <span>用户名登陆</span>
                    <span style="padding: 0 5px;">|</span>
                    <span>邮箱登陆</span>
                </div>
                <div id="login_error_summary" class="error-msg">

                </div>
                <div class="inp">
                    <input name="user" type="text" placeholder="请输入用户名或邮箱" />
                </div>
                <div class="inp">
                    <input name="pwd" type="password" placeholder="请输入密码" />
                </div>
                <div class="inp clearfix">
                    <input name="code" class="check-code" type="text" placeholder="请输入验证码" />
                    <span>
                        <img class="check-img" src="check_code" alt="验证码" onclick="ChangeCode(this);">
                    </span>
               </div>
                <div class="extra" style="margin-top: 10px;">
                    <input type="checkbox" name="autoLogin" checked="checked" /> <span>一个月内自动登录</span>
                    <a class="right" href="javascript:void(0);">忘记密码?</a>
                </div>
                <div class="inp">
                    <div class="submit" onclick="SubmitLogin(this);">
                        <span>登陆</span>
                        <span class="hide">
                            <img src="/static/images/loader.gif" style="height: 16px;width: 16px">
                            <span>正在登陆</span>
                        </span>
                    </div>
                </div>
            </div>
                <script>
                    function ChangeCode(ths) 
                        ths.src += '?';
                    
                </script>
        </div>
    </div>
    <div id="model_register" class="register right">
        <div class="header">
            <span>注册</span>
            <div class="dialog-close" onclick="CloseDialog('.accountDialog');">X</div>
        </div>
        <div class="content">
            <div style="padding: 0 50px">
                <div class="tips">
                    <span>输入注册信息</span>
                </div>
                <div id="register_error_summary" class="error-msg"></div>
                <div class="inp">
                    <input name="username" type="text" placeholder="请输入用户名" />
                </div>
                <div class="inp">
                    <input name="email" id="email" type="text" placeholder="请输入邮箱" />
                </div>
                <div class="inp">
                    <input name="email_code" class="email-code" type="text" placeholder="请输入验证码" />
                    <a id="fetch_code" class="fetch-code" href="javascript:void(0);">获取验证码</a>
                    <!-- a标签中的href属性,如果设为“#”,鼠标放在上面是一个竖杠,且点击时地址栏会增加#,如上面写法,会变成小手,且点击时不会增加地址栏内容-->
                </div>
                <div class="inp">
                    <input name="pwd" type="password" placeholder="请输入密码" />
                </div>
                <div class="inp">
                    <div class="submit" onclick="SubmitRegister(this);">
                        <span>注册</span>
                        <span class="hide">
                            <img src="/static/images/loader.gif" style="height: 16px;width: 16px">
                            <span>正在注册</span>
                        </span>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
<div class="main-content-box">
    <div class="main-content">
        <div class="content-L">
            <div class="top-area">
				<div class="child-nav">
					<a href="#"  class="hotbtn active" >最热</a>
					<a href="#"  class="newbtn"    >最新</a>
					<a href="#"  class="personbtn" >人类发布</a>
				</div>
				<div class="sort-nav">
					<a href="#"  class="sortbtn active" >即时排序</a>
					<a href="#"  class="newbtn" >24小时</a>
					<a href="#"  class="newbtn" >3天</a>
				</div>
				% if userinfo.is_login %
                <a href="javascript:void(0);" class="publish-btn">
                    <span class="n2">+发布</span>
				</a>
                % else %
                <a href="javascript:void(0);" class="publish-btn">
                    <span class="n2">+发布</span>
				</a>
                % endif %
			</div>
            <div class="content-list">
                % for item in news_list %

                    <div class="item">
                    <div class="news-pic">
                        <img src=" item.image_url " alt="抽屉新热榜">
                    </div>
                    <div class="news-content">
                        <div class="part1">
                            <a href="#" class="show-content" target="_blank">
                              item.title 
                            </a>
                            <span class="content-source">- item.url_laiyuan </span>
                            <a href="#" class="n2">
                                <span class="content-kind">42区</span>
                            </a>
                        </div>
                        <div class="part2">
                            <a href="#" class="recommend" title="推荐">
                                <span class="hand-icon icon-recommend"></span>
                                <b> item.counts_acc </b>
                            </a>
                            <a href="javascript:;" class="discuss">
                                <span class="hand-icon icon-discuss"></span>
                                <b>5</b>
                            </a>
                            <a href="javascript:;" class="collect" title="加入私藏">
                                <span class="hand-icon icon-collect"></span>
                                <b>私藏</b>
                            </a>
                            <a href="#" class="user-a">
                                <span>
                                    <img src="/static/images/13.png">
                                </span>
                                <b>乱太郎</b>
                            </a>
                            <span class="left time-into">
                                <a class="time-a" href="#" target="_blank">
                                    <b>4分钟前</b>
                                </a>
                                <i>入热榜</i>
                            </span>
                            <!-- 分享各微博的按钮 -->
                            <span class="share-site-to">
                                <i>分享到</i>
                                <span class="share-icon">
                                    <a class="icon-sina"    title="分享到新浪微博" href="#" ></a>
                                    <a class="icon-douban"  title="分享到豆瓣"    href="#" ></a>
                                    <a class="icon-qqzone"  title="分享到QQ空间"  href="#" ></a>
                                    <a class="icon-tenxun"  title="分享到腾讯微博" href="#" ></a>
                                    <a class="icon-renren"  title="分享到人人网"   href="#" ></a>
                                </span>
                            </span>
                        </div>
                    </div>
                  </div>
                % endfor %
            </div>
            <div class="page-area">
                 <ul>
                     <li><span class="ct_pagepw">1</span></li>
                     <li><a href="#" class="ct_pagepa">2</a></li>
                     <li><a href="#" class="ct_pagepa">3</a></li>
                     <li><a href="#" class="ct_pagepa">4</a></li>
                     <li><a href="#" class="ct_pagepa">5</a></li>
                     <li><a href="#" class="ct_pagepa">6</a></li>
                     <li><a href="#" class="ct_pagepa">7</a></li>
                     <li><a href="#" class="ct_pagepa">8</a></li>
                     <li><a href="#" class="ct_pagepa">9</a></li>
                     <li><a href="#" class="ct_pagepa">10</a></li>
                     <li class="next"><a href="#" class="ct_page_edge">下一页</a></li>
                 </ul>
			</div>
        </div>
        <div class="content-R">
        </div>
    </div>
</div>
<div class="footer-box">
    <div class="foot-nav">
        <a href="#" target="_blank">关于我们</a>
        <span>|</span>
        <a href="#" target="_blank">联系我们</a>
        <span>|</span>
        <a href="#" target="_blank">服务条款</a>
        <span>|</span>
        <a href="#" target="_blank">隐私政策</a>
        <span>|</span>
        <a href="#" target="_blank">抽屉新热榜工具</a>
        <span>|</span>
        <a href="#" target="_blank">下载客户端</a>
        <span>|</span>
        <a href="#" target="_blank">意见与反馈</a>
        <span>|</span>
        <a href="#" target="_blank">友情链接</a>
        <span>|</span>
        <a href="#" target="_blank">公告</a>
        <a href="#" target="_blank" style="margin-left:0;vertical-align:-2px;">
            <img src="/static/images/ct_rss.gif" width="36" height="14">
        </a>
    </div>
    <div class="foot-nav2">
        <a target="_blank" href="#">
            <img class="foot_e" src="/static/images/footer1.gif" width="36" height="14">
        </a>
        <span class="foot_d">旗下站点</span>
        <span class="foot_a">©2016chouti.com</span>
        <a target="_blank" href="#" class="foot_b">京ICP备09053974号-3 京公网安备 110102004562</a>
        <div style="margin-top:6px;">版权所有:北京格致璞科技有限公司</div>
    </div>
</div>
<script src="/static/jquery-3.6.0.js"></script>
<script>
    $(function () 
       bindLoginRegister();
       bindSendMsg();
    );
    function bindLoginRegister() 
        $('.action-nav a.register-login-btn').click(function () 
            $('.shadow,.accountDialog').removeClass('hide');
        );
    
    function bindSendMsg() 
        $('#fetch_code').click(function () 
            $('#register_error_summary').empty();
            var email = $('#email').val();
            if(email.trim().length == 0)
                $('#register_error_summary').text('请输入邮箱');
                return;
            
            if($(this).hasClass('sending'))
                return;
            
            var ths = $(this);
            var time = 60;

            $.ajax(
                url: "send_msg/",
                type: "POST",
                data: email:email,csrfmiddlewaretoken:' csrf_token ',
                dataType: "json",
                success: function (arg) 
                    if(!arg.status)
                        $('#register_error_summary').text(arg.summary);
                    else 
                        ths.addClass('sending');
                        var interval = setInterval(function () 
                            ths.text("已发送(" + time +")");
                            time -= 1;
                            if(time<=0)
                                clearInterval(interval);
                                ths.removeClass('sending');
                                ths.text("获取验证码");
                            
                        ,1000);
                    
                
            );
        );
    
    function SubmitRegister(self) 
        $('#register_error_summary').empty();
        $('#model_register .inp .error').remove();

        $(self).children(':eq(0)').addClass('hide');
        $(self).addClass('not-allow').children(':eq(1)').removeClass('hide');

        var post_dict = ;
        $('#model_register input').each(function () 
            post_dict[$(this).attr("name")] = $(this).val();
        );
        post_dict['csrfmiddlewaretoken'] = ' csrf_token ';
        $.ajax(
            url: 'register.html',
            type: 'POST',
            data: post_dict,
            dataType: 'json',
            success: function (arg) 
                if(arg.status)
                    window.location.href = 'chouti-index.html';
                else 
                    $.each(arg.message,function (k,v) 
                        var tag = document.createElement('span');
                        tag.className = 'error';
                        tag.innerText = v;
                        $('#model_register input[name="' + k + '"]').after(tag);
                    )
                
            
        );
        $(self).removeClass('not-allow').children(':eq(1)').addClass('hide');
        $(self).children(':eq(0)').removeClass('hide');
    

    function CloseDialog(dialog) 
        $(dialog).addClass('hide');
        $('.shadow').addClass('hide');
    

    function ChangeCode(self) 
        self.src += '?';
    
    function SubmitLogin(self) 
        $(self).children(':eq(0)').addClass('hide');
        $(self).addClass('not-allow').children(':eq(1)').removeClass('hide');
        $('#model_login .inp .error').remove();

        var post_dict =;
        $('#model_login input').each(function () 
            post_dict[$(this).attr('name')] = $(this).val();
        );
        post_dict['csrfmiddlewaretoken'] = ' csrf_token ';
        $.ajax(
            url: 'login.html',
            type: 'POST',
            data: post_dict,
            dataType: 'json',
            success: function (arg) 
                if(arg.status)
                    window.location.href = 'chouti-index.html'
                else 
                    $.each(arg.message,function (k,v) 
                        alert(v);
                        var tag = document.createElement('span');
                        tag.className = 'error';
                        tag.innerText = v[0]['message'];
                        $('#model_login input[name="' + k + '"]').after(tag);
                    );
                

            
        );
        $(self).removeClass('not-allow').children(':eq(1)').addClass('hide');
        $(self).children(':eq(0)').removeClass('hide');
    
</script>
</body>
</html>

样式文件:

*
    margin: 0;
    padding: 0;

a
    text-decoration: none;

body
    font-size: 14px;


.head-box
    background-color: #2459a2;
    height: 44px;
    width: 100%;
    position: fixed;
    top: 0;
    left: 0;
    /* position的fixed定位,要加上位置top,left等,使人明白是固定在哪个位置,现在是屏幕的0,0处   */
    /*  是一直在屏幕的0,0位置 ,随着鼠标向下滚动,位置一直不变 */

.head-content
    margin: 0 auto;   /* 内容左右居中,0代表上下,auto代表左右 */
    width: 1016px;
    height: 44px;
    background-color: #2459a2;
    line-height: 44px;
    position: relative;

.logo
    background : url("/static/images/logo.png") no-repeat 0 0;
    height: 23px;
    width: 121px;
    float: left;
    margin-top: 11px;

.action-menu
    float: left;
    margin-left: 20px;

.action-menu a.tb
    color: #c0cddf;
    margin-left: -6px;  /* 两个菜单项之间margin是0,使用padding来分隔,因为默认margin有个值,使用负值使之为0*/
    padding: 0 13px 0 13px;
    display: inline-block;

.action-menu a.tb:hover
    color: #fff;
    background-color: #c0cddf;

.action-menu a.active,.action-menu a.active:hover
    color: #fff;
    background-color: #204982;

.key-search
    float: right;
    margin-top: 7px;

.key-search .search-txt,.key-search a.i
    float: left;

.key-search .search-txt
    width: 91px;
    height: 25px;
    padding: 2px 2px 2px 5px;
    color: #333;

.key-search .ico  /* span标签中背景图片,进行调整,使放大镜图像显示在小窗口中 */
    background: url("/static/images/icon.png") no-repeat 0 -197px;
    height: 12px;
    width: 12px;
    display: inline-block;
    /* span本身是内联标签,需要改成inline-block*/
    margin-bottom: 5px;
    margin-left: 8px;
    /*  margin用于调整下面的span标签的位置*/

.key-search a.i   /* a标签中容纳了上面的span标签*/
    margin-top: 1px;
    height: 31px;
    width: 30px;
    background-color: #f4f4f4;
    display: inline-block;
    /*border: 1px yellow solid;*/
    border-left: none;

.action-nav
    position: absolute;
    right: 131px;

.action-nav a
    color: white;
    padding: 0 20px;
    display: inline-block;

.action-nav a:hover
    background-color: #c0cddf;

.shadow
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    background-color: rgba(0,0,0,0.4);
    z-index: 1000;

.hide
    display: none;


.main-content-box
    background-color: #ededed;
    width: 100%;
    padding-top: 44px;

.main-content
    margin: 0 auto;
    background-color: white;
    width: 960px;
    height: auto!important;
    min-height: 500px;
    padding: 6px 28px 60px 28px;
    overflow: hidden;

.content-L
    float: left;
    width: 630px;

.child-nav,.sort-nav
    float: left;
    padding: 10px;

.publish-btn
    float: right;
    padding: 10px;

.top-area
    overflow: hidden;
    border-bottom: 1px solid #99aecb;

.child-nav a
    display: inline-block;
    width: 60px;
    height: 26px;
    line-height: 26px;
    text-align: center;
    color: #369;
    font-weight: 700;
    margin-top: 3px;

.child-nav .hotbtn
    background: url("/static/images/tip.png") no-repeat 0 -299px;
    /*display: inline-block;*/
    /*width: 60px;*/
    /*height: 26px;*/
    /*line-height: 26px;*/
    /*text-align: center;*/
    color: black;

.sort-nav
    margin-left: 100px;
    margin-top: 6px;

.sort-nav a
    margin-left: 1px;
    color: green;

.sort-nav .sortbtn
    color: #b4b4b4;

.publish-btn
    display: inline-block;
    background-color: #84a42b;
    width: 120px;
    height: 18px;
    color: white;
    line-height: 18px;
    text-align: center;
    margin-top: 3px;

.content-list .item
    border-bottom: 1px solid red;
    margin-top: 10px;

.item .news-pic
    float: right;
    margin-top: 3px;
    margin-left: 8px;

.part2
    padding-top: 10px;
    margin-bottom: 12px;

.hand-icon
    background: url("/static/images/icon_18_118.png") no-repeat 0 0;
    width: 18px;
    height: 18px;
    display: inline-block;
    vertical-align: -4px;

.icon-recommend
    background-position: 0 -40px;

.icon-discuss
    background-position: 0 -100px;

.icon-collect
    background-position: 0 -140px;

.part2 .user-a span
    vertical-align: -4px;

.part2 a
    margin-left: 10px;

.part1
    line-height: 20px;

.part1 .content-source,.content-kind
    color: #b4b4b4;

.part1 .content-kind
    text-decoration: underline;

.part1 .show-content
    color: #369;
    font-size: 14px;
    font-weight: 700;

.part2 b,.time-into i
    color: #b4b4b4;

.share-icon a
    background: url("/static/images/share_icon.png") no-repeat 0 0;
    height: 14px;
    width: 17px;
    display: inline-block;
    vertical-align: -4px;
    opacity: 0.5;

.share-icon a:hover
    opacity: 1;

.icon-sina


.share-site-to .share-icon a.icon-sina
    background-position: 0 -90px;

.share-site-to .share-icon a.icon-douban
    background-position: 0 -105px;

.share-site-to .share-icon a.icon-qqzone
    background-position: 0 -120px;

.share-site-to .share-icon a.icon-tenxun
    background-position: 0 -136px;

.share-site-to .share-icon a.icon-renren
    background-position: 0 -151px;

.share-site-to
    float: right;

.page-area ul li
    display: inline-block;
    float: left;
    color: #369;
    height: 34px;
    width: 34px;
    line-height: 34px;
    text-align: center;
    border: 1px solid #a3a3a1;
    border-radius: 20%;
    margin-left: 4px;



.page-area
    margin-left: 10px;
    margin-top: 10px;

ul li.next
    width: 60px;

.page-area ul li:hover
    color: white;
    background-color: #204982;

.footer-box
    margin-top: -20px;
    background-color: #ededed;
    width: 100%;

.foot-nav,.foot-nav2
    width: 1016px;
    margin: 0 auto;
    padding-top: 10px;
    background-color: white;
    text-align: center;

.foot-nav
    border-top: 1px solid #a3a3a1;

.accountDialog
    position: fixed;
    width: 700px;
    height: 375px;
    left: 50%;
    top: 50%;
    margin-left: -350px;
    margin-top: -250px;
    z-index: 1001;
    background-color: rgb(230,236,243);

.accountDialog .login
    width: 349px;
    background-color: #fff;
    height: 375px;
    border-right: 1px solid #cbdcee;
    float: left;

.accountDialog .register
    width: 350px;
    background-color: white;
    height: 375px;
    float: right;

.accountDialog .login .header,.accountDialog .register .header
    background: #e7ecf2;
    padding: 0 10px;
    font-size: 14px;
    height: 30px;
    line-height: 30px;
    font-weight: bold;
    color: #abb6d2;
    position: relative;

.accountDialog .register .content .tips,.accountDialog .login .content .tips
    /*padding: 20px 0 0 -10px;*/
    padding-top: 10px;
    font-size: 14px;
    color: #abb6d2;
    cursor: pointer;
    text-align: left;

.accountDialog .login .content .inp,.accountDialog .register .content .inp
    padding: 10px 0;
    position: relative;

.accountDialog .login .content .inp input,.accountDialog .register .content .inp input
    width: 150px;
    padding: 6px;
    border: 1px solid #CDDDEF;

.accountDialog .register .content .inp .fetch-code
    display: inline-block;
    background-color: #336699;
    height: 29px;
    line-height: 29px;
    padding: 0 5px;
    color: #ffffff;
    text-decoration: none;

.accountDialog .login .content .error-msg,.accountDialog .register .content .error-msg
    color: red;
    height: 14px;

.accountDialog .login .content .inp .check-code
    width: 117px;
    padding: 6px;
    border: 1px solid #CDDDEF;
    display: inline-block;
    margin-right: 3px;

.accountDialog .login .content .inp .check-img
    height: 29px;
    width: 70px;
    display: inline-block;
    cursor: pointer;
    vertical-align: top;


.accountDialog .register .content .inp .email-code
    width: 122px;
    padding: 6px;
    border: 1px solid #CDDDEF;

.accountDialog .register .content .inp .fetch-code.sending
    cursor: not-allowed;
    background-color: #a3a3a1 !important

.accountDialog .register .content .inp .submit,.accountDialog .login .content .inp .submit
    width: 95px;
    height: 31px;
    line-height: 31px;
    margin-top: 5px;
    background-color: #336699;
    color: #ffffff;
    text-align: center;
    cursor: pointer;
    font-size: 14px;
    font-weight: 700;
    display: inline-block;

.accountDialog .login .content .extra
    padding-bottom: 10px;

.accountDialog .login .content .extra a
    color: #369;

.accountDialog .register .header .dialog-close
    position: absolute;
    right: 10px;
    top: 1px;
    color: #99aecb;
    cursor: pointer;


.accountDialog .register
    width: 350px;
    background-color: #ffffff;
    height: 375px;

.accountDialog .register .content .inp .error,.accountDialog .login .content .inp .error
    font-size: 10px;
    position: absolute;
    color: #e4393c;
    background: #FFEBEB;
    border: 1px solid #ffbdbe;
    z-index: 10;
    height: 15px;
    width: 208px;
    line-height: 15px;
    display: block;
    overflow: hidden;

.not-allow
    cursor: not-allowed !important;

views函数:

from django.shortcuts import render,redirect,HttpResponse
from projectct import models
from myutils.myresponse import BaseResponse
from myutils.myforms import SendMsgForm,RegisterForm,LoginForm
import json
import datetime
from myutils import commons
from django.utils import timezone
from chouti import settings
import pytz

def test(req):
    news_list = models.News.objects.all().values("title", "url_laiyuan", "counts_acc", "image_url","auth__username")


    return render(req,'test.html','obj':news_list)

def login(req):
    rep = BaseResponse()     # 返回前端信息封装类
    form = LoginForm(req.POST)   # 对输入的登录信息进行格式验证,不是数据库数据的验证
    if form.is_valid():    # 验证无误时
        _value_dict =form.clean()
        print(_value_dict)
        if _value_dict['code'].lower() != req.session["CheckCode"].lower():   # 输入的验证码与服务器session中的验证码比对
            rep.message = 'code':['message':'验证码错误']
            print('::::::::==>',rep.__dict__)
            return HttpResponse(json.dumps(rep.__dict__))   # __dict__,类中的属性以字典形式存储在此变量中
        # 验证码正确
        from django.db.models import Q
        # Q是或逻辑关系查询,进行数据库数据的验证
        con = Q()
        q1 = Q()
        q1.connector = 'AND'
        q1.children.append(('email',_value_dict['user']))
        q1.children.append(('pwd',_value_dict['pwd']))

        q2 = Q()
        q2.connector = 'AND'
        q2.children.append(('username', _value_dict['user']))
        q2.children.append(('pwd', _value_dict['pwd']))

        con.add(q1,'OR')
        con.add(q2,'OR')
        # con最后形成的是查询“email与密码” 或 “username与密码”,即以邮件登录或以用户名登录
        # 一般的filter中直接加字段名=的形式,相互之间是与的关系
        obj = models.UserInfo.objects.filter(con).first()
        if not obj:
            rep.message = 'user':['message':'用户名或邮箱、密码错误']
            return HttpResponse(json.dumps(rep.__dict__))
        req.session['is_login'] = True
        req.session['user_info'] = 'nid':obj.id,'email':obj.email,'username':obj.username
        rep.status = True
    else:
        error_msg = form.errors.as_json()
        rep.message = json.loads((error_msg))
    return HttpResponse(json.dumps(rep.__dict__))


def chouti(req):
    # news_list = models.News.objects.all().values("title","url_laiyuan","counts_acc","contents","image_url","auth__username")
    news_list = models.News.objects.all().values("title", "url_laiyuan", "counts_acc", "image_url","auth__username")
    print(req.session.session_key)
    print(req.session.keys())
    print(req.session.values())
    return render(req,'chouti-index.html',"news_list":news_list)

import pytz
def send_msg(req):
    # 注册时,发送邮箱验证码
    rep = BaseResponse()
    form = SendMsgForm(req.POST)
    if form.is_valid():
        _value_dict = form.clean()
        email = _value_dict['email']

        has_exisits_email = models.UserInfo.objects.filter(email=email).count()
        if has_exisits_email:
            rep.summary = "此邮箱已经被注册"
            return HttpResponse(json.dumps(rep.__dict__))

        # current_date = datetime.datetime.now(pytz.UTC) # 获取的是UTC时间,带时区,即是active datetime
        # print(current_date)    # 2022-06-29 00:29:16.044477+00:00
        current_date = datetime.datetime.now(pytz.timezone('Asia/Shanghai')) # 获得是是不带时区的时间,即naive datetime
        print(current_date)  # 2022-06-29 08:37:05.017300,是本地时间,没有转换为UTC
        # 当models中定义的类使用了DateTimeField字段时,要求使用active datetime日期时间对象,否则报如下警告:
        #  RuntimeWarning: DateTimeField SendMsg.ctime received a naive datetime (2022-06-29 08:37:05.017300) while time zone support is active.
        # current_date = timezone.make_aware(current_date)
        # print(current_date)
        # print(timezone.now())  # 2022-06-29 00:47:02.625482+00:00,默认UTC的active datetime对象,本地时间减去8

        code = commons.random_code()
        emailsend([email,],code)   # 给邮箱发送验证码
        count = models.SendMsg.objects.filter(email=email).count()
        if not count:
            models.SendMsg.objects.create(code=code,email=email,ctime=current_date)
            rep.status = True
        else:
            limit_day = current_date - datetime.timedelta(hours=1)
            times = models.SendMsg.objects.filter(email=email,ctime__gt=limit_day,times__gt=9).count()
            if times:
                rep.summary = "'已超最大次数(1小时后重试)'"
            else:
                unfreeze = models.SendMsg.objects.filter(email=email,ctime__lt=limit_day).count()
                if unfreeze:
                    models.SendMsg.objects.filter(email=email).update(times=0)
                else:
                    from django.db.models import F
                    models.SendMsg.objects.filter(email=email).update(code=code,ctime=current_date,times=F('times')+1)
                    rep.status=True
    else:
        rep.summary = form.errors['email'][0]
    return HttpResponse(json.dumps(rep.__dict__))

import smtplib
from email.mime.text import MIMEText
from email.utils import formataddr
def emailsend(email_list,content,subject="测试自动发送邮件"):
    msg = MIMEText(content,'plain','utf-8')
    msg['From'] = formataddr(["测试邮箱","ifxxxxxx@sohu.com"])
    msg['Subject'] = subject

    server = smtplib.SMTP("smtp.sohu.com",25)
    server.login("ifxxxxxx@sohu.com","密码")
    server.sendmail("ifxxxxxx@sohu.com",email_list,msg.as_string())
    server.quit()

def register(req):
    rep = BaseResponse()
    form = RegisterForm(req.POST)
    if form.is_valid():
        current_date = datetime.datetime.now(pytz.timezone('Asia/Shanghai'))
        limit_day = current_date -datetime.timedelta(minutes=1)
        _value_dict = form.clean()

        is_valid_code = models.SendMsg.objects.filter(email=_value_dict['email'],code=_value_dict['email_code'],ctime__gt=limit_day).count()
        if not is_valid_code:
            rep.message['email_code'] = '邮箱验证码不正确或过期'
            return HttpResponse(json.dumps(rep.__dict__))

        has_exists_email = models.UserInfo.objects.filter(email=_value_dict['email']).count()
        if has_exists_email:
            rep.message['email'] = '邮箱已经存在'
            return HttpResponse(json.dumps(rep.__dict__))

        has_exists_username = models.UserInfo.objects.filter(username=_value_dict['username']).count()
        if has_exists_username:
            rep.message['username'] = '用户名已经存在'
            return HttpResponse(json.dumps(rep.__dict__))

        _value_dict['ctime'] = current_date
        _value_dict.pop('email_code')

        obj = models.UserInfo.objects.create(**_value_dict)
        user_info_dict = 'nid':obj.id,'email':obj.email,'username':obj.username
        models.SendMsg.objects.filter(email=_value_dict['email']).delete()
        req.session['is_login'] = True
        req.session['user_info'] = user_info_dict
        rep.status = True
    else:
        error_msg = form.errors.as_json()
        rep.message = json.loads(error_msg)
    return HttpResponse(json.dumps(rep.__dict__))

def loginout(req):
    req.session.clear()
    return redirect('chouti-index.html')

from myutils import create_code
import io
def check_code(req):
    stream = io.BytesIO()
    img,code = create_code.create_validate_code()
    img.save(stream,'PNG')
    req.session['CheckCode'] = code
    return HttpResponse(stream.getvalue())

models模块:

from django.db import models

# Create your models here.
class UserInfo(models.Model):
    username = models.CharField(max_length=32,db_index=True)
    email = models.CharField(max_length=32,unique=True)
    pwd = models.CharField(max_length=64)
    ctime = models.DateTimeField(auto_now_add=True)

    """
    定义联合索引
    class Meta:
        index_together = [
            ('username','pwd'),
        ]        
        unique_together = [
            ('username','pwd'),
        ]
    """

class SendMsg(models.Model):
    email = models.EmailField(max_length=64,unique=True)
    code = models.CharField(max_length=6)
    ctime = models.DateTimeField()
    times = models.IntegerField(default=1)

# 使用SendMsg.objects.create(email="111111",code="1234",stime=datetime.now())
# 上述语句会保存数据库成功,不会进行email验证
# 可以先生成对象:obj = SendMsg(email="111111",code="1234",stime=datetime.now())
# 然后obj.clean() 执行这一句时,才会进行验证,email不符合要求,会直接异常报错
# 如果正确,使用obj.save()保存数据库
# django Admin也是可以进行验证的
class News(models.Model):
    title = models.CharField(max_length=256)
    content = models.CharField(max_length=1024)
    url_laiyuan = models.URLField()
    counts_acc = models.IntegerField()
    image_url = models.CharField(max_length=128)
    contents = models.TextField()
    auth = models.ForeignKey("UserInfo",on_delete=models.DO_NOTHING)

form验证模块:

from django import forms

class SendMsgForm(forms.Form):
    email = forms.EmailField()

class RegisterForm(forms.Form):
    username = forms.CharField()
    email = forms.EmailField()
    pwd = forms.CharField()
    email_code = forms.CharField()

class LoginForm(forms.Form):
    user = forms.CharField()
    pwd = forms.CharField()
    code = forms.CharField()

路由项:

from django.contrib import admin
from django.urls import path
from projectct import views
urlpatterns = [
    path('admin/', admin.site.urls),
    path('test.html',views.test),
    path('index.html',views.login),
    path('chouti-index.html',views.chouti),
    path('send_msg/',views.send_msg),
    path('register.html',views.register),
    path('loginout.html',views.loginout),
    path('check_code/',views.check_code),
    path('login.html',views.login),
]

第三个知识点:时间函数的使用,主要是datetime,pytz,timezone模块的使用,要注意日期时间有有带时区和不带时区的区别,使用时一定要一致。

第四个知识点:F和Q,F是对数据库自增字段操作时需要使用,Q用来组合或查询(使用connector也可以是与),构建复杂的查询语句。

通过这个小实验,感觉还是前端的编写比较麻烦,要开发B/S架构程序,需要加强js、jQuery、CSS学习。

以上是关于Python入门自学进阶-Web框架——16Django登录/注册的主要内容,如果未能解决你的问题,请参考以下文章

Python入门自学进阶-Web框架——18FormModelForm

Python入门自学进阶-Web框架——18FormModelForm

Python入门自学进阶-Web框架——20Django其他相关知识2

Python入门自学进阶-Web框架——2Django初识

Python入门自学进阶-Web框架——3Django的URL配置

Python入门自学进阶-Web框架——21DjangoAdmin项目应用