以抽屉为原型,实现用户的注册和登录。
基本的界面:

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


要自动给别人发送邮件,首先要有自己的邮箱,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学习。