首页 > 技术文章 > 1209 BBS 登录

fwzzz 原文

上周内容

bbs项目
	
	
	项目开发流程
		需求分析
		
		架构设计
		
		分组开发
			我们一般情况下都只是作用于这一步
		
		各项测试

		交付上线
			
	bbs表设计
		任何一个项目 最最重要的部分都是数据库的表设计
	
	用户表
		继承AbstractUser表  来做额外的字段扩展
			1.类的继承
			2.配置文件配置
				AUTH_USER_MODEL = '应用名.类名'
		
		phone
		avatar
			upload_to='avatar/'
		register_time
		
		
		
	
	个人站点表
		站点标题
		站点名称
		站点样式
			模拟
		
	
	文章标签表
		标签名称
	
	文章分类表
		分类名称
	
	文章表
		文章标题
		文章简介
		文章内容
		文章日期
		
		# 数据库优化字段
		点赞数
		点踩数
		评论数
		
	
	点赞点踩表
		用户id         一对多字段
		文章id		   一对多字段
		点赞点踩		1/0
		
	
	评论表
		用户id			一对多字段
		文章id			一对多字段
		评论内容
		评论时间
		自关联字段		自己跟自己所在的表关联
		
	
	数据库配置
		1.配置文件
		2.__init__文件
	
	数据库迁移命令
		python3 manage.py makemigrations	
		python3 manage.py migrate
	
	注册功能
		只要是注册功能一般都有一定的校验规则
		
		我们利用forms组件 来完成注册的校验
			用户名
				label
				error_messages
					required
					invalid
				widget
				validators
			密码
			确认密码
			邮箱
			
			钩子函数
				校验用户名是否已存在
			
				校验两次密码是否一致
		
		一旦你的项目特别庞大 需要用到很多forms组件 
		那么你可以单独的将所有forms组件放到一个py文件中或者一个文件下
		然后针对不同的功能开设不同的py文件
			单独的py文件
		
			文件夹
				1.py
				2.py
				3.py
				4.py
			
	注册功能使用的是ajax做后端交互
	但是我们用的了form标签
		因为form标签有一个自动序列化普通键值对的功能
		可以方便的将普通键值对循环添加到formdata对象中
	
	label标签在跟input有绑定关系的前提下
	内部无论放什么对象 点击都能够是对应的input框聚焦
	
	img标签src属性可以放的值
		1.图片的地址
		2.图片的二进制数据
		3.后端url   自动朝该url发送get请求
	
	实时展示用户选择的头像
		利用内置对象fileReader
			文件阅读器在读取文件的时候 是一个异步io操作
			所以你在渲染img标签的时候一定要等待文件阅读器加载完毕之后再渲染
			等待...加载完毕
				onload
				window.onload = function(){}
				fileReader.onload = function(){}
	发送ajax请求
		1.ajax提交数据基本语法
		2.ajax发送文件需要借助于内置对象formdata
			ajax发送formdata对象
			需要制定两个参数都为false
				processData
				contentType
	后端正常的业务逻辑无需多说
	
	
	一旦报错之后前端如何对应的渲染相应的信息
		1.你需要研究form组件渲染的input框的id值的特点
			id_字段名
		2.你又发现返回的报错信息 键就是一个个的字段名
			自己手动拼接处input框的id值
			然后利用DOM操作 来操作span标签以及div标签
	
	最后为了功能的健壮性
		给所有的input设置获取焦点事件
			自动移除报错信息和报错的样式
		
	
	控制字体样式的文件都是以.ttf结尾
	
	
	
	
本周
	bbs结束

今日内容
	登录功能
	
	首页搭建
	
	导航条用户功能
	
	admin后台管理

下周安排(15d~22d)
	vue前端框架   3~5d
	django restframework(******)
	路飞项目
	git
	celery
	项目上线

今日内容

url.py

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # 注册功能
    url(r'^register/',views.register,name='register'),
    # 登录功能
    url(r'^login/',views.login,name='login'),
    # 验证码图片
    url(r'^get_code/',views.get_code,name='code'),
    # 首页搭建
    url(r'^home/',views.home,name='home'),
    # 退出登录
    url(r'^logout/',views.logout,name='logout'),
    # 修改密码
    url(r'^set_password/',views.set_password,name='set_pwd')
]

views.py

from django.shortcuts import render,HttpResponse,redirect,reverse
from app01 import  myform
from app01 import  models
from django.http import JsonResponse
# 验证码相关
from PIL import Image, ImageDraw, ImageFont
from io import BytesIO, StringIO
# auth模块校验密码
from django.contrib import auth
from django.contrib.auth.decorators import login_required

# Create your views here.
def register(request):
    # 生成一个空的forms对象
    form_obj = myform.MyRegForm()

    if request.method == 'POST':
        back_dic = {'code':1000,'msg':''}

        # 对用户提交的数据先进行校验 forms
            # 直接使用forms组件的校验方法,校验整个的数据字典
        form_obj = myform.MyRegForm(request.POST)
        if form_obj.is_valid():
            # 如果数据校验结果正确
            clean_data = form_obj.cleaned_data   # 获取正确的数据 4个键值对
            # 将确认密码的键值对弹出,只剩余三个便于创建用户
            clean_data.pop('confirm_password')
            # 获取用户上传的文件
            file_obj = request.FILES.get('avatar')
            # 判断文件是否存在,用户是否上传,如果没有上传才会设置default头像
            if file_obj:
                clean_data['avatar'] = file_obj     # 四个键值对
            # 自动创建,使用**直接打散关键字参数
            models.Userinfo.objects.create_user(**clean_data)
            # 添加成功界面信息,及跳转
            back_dic['msg'] = '注册成功'
            back_dic['url'] = '/login/'
        else:
            back_dic['code'] = 2000
            # 将错误信息返回给前端
            back_dic['msg'] = form_obj.errors
        # 返回给前端字典
        return JsonResponse(back_dic)
    return render(request,'register.html',locals())



# 登录功能
def login(request):
    if request.method == 'POST':
        back_dic = {'code':1000,'msg':''}
        username = request.POST.get('username')
        password = request.POST.get('password')
        code = request.POST.get('code')
        # 1.先校验验证码是否正确 忽略大小写
        if request.session.get('code').upper() == code.upper():
            # 2.校验用户名密码是否正确 利用auth模块
            user_obj = auth.authenticate(request,username=username,password=password)
            if user_obj:
                # 3.保存用户登录状态
                auth.login(request,user_obj)
# 就可以在任意位置通过request.user获取到当前登录对象 并且 request.user.is_authenticated()判断当前用户是否登录
                # 返回前端信息,并定向主页
                back_dic['msg'] = '登录成功'
                back_dic['url'] = '/home/'
            else:
                back_dic['code'] = 2000
                back_dic['msg'] = '用户名或密码错误'
        else:
            back_dic['code'] = 3000
            back_dic['msg'] = '验证错误'
        # 返回给前端字典数据
        return JsonResponse(back_dic)
    return render(request,'login.html')


import random
# 给验证码的图片添加随机颜色
def get_random():
    return random.randint(0,255),random.randint(0,255),random.randint(0,255)


# 图片验证码相关
def get_code(request):
    # 推导步骤一:直接发送后端存在的图片
        # img标签可以直接返回二进制数据进行显示
    # with open(r'static/img/02b02.jpg.jpg','rb') as f:
    #     data = f.read()
    # return HttpResponse(data)

    # 推导步骤二 利用pillow模块自动生成图片
    '''
    from PIL import Image,ImageDraw,ImageFont
    Image       生成图片
    ImageDraw   在图片上写字
    ImageFont   控制字体的样式'''
    # 生成图片对象
    # img_obj = Image.new('RGB',(360,35),'red')     直接放文件的颜色
    # img_obj = Image.new('RGB',(360,35),get_random())   # 放rgb模式(255,23,232)
    # # 利用文件操作先保存下来
    # with open('xxx.png','wb') as f:
    #     img_obj.save(f,'png')
    # # 然后利用文件操作将图片数据读取并发送
    # with open(r'xxx.png','rb') as f:
    #     data = f.read()
    # return HttpResponse(data)

    # 推导步骤三  临时存储数据并且能够随时取出的地方
    '''内存管理模块
    from io import BytesIO,StringIO
    BytesIO     保存数据(并且在获取的时候,是以二进制的方式给你)
    StringIO    保存数据(以字符串的形式给你)
    '''
    # img_obj = Image.new('RGB',(360,35),get_random())
    #     # # 先生成一个io对象
    #     # io_obj = BytesIO()    # 可以将对象当做是文件的句柄
    #     # # 存储数据
    #     # img_obj.save(io_obj,'png')
    #     # # getvalue获取二进制的数据
    #     # return HttpResponse(io_obj.getvalue())

    # 推导步骤四     在图片上写字
    img_obj = Image.new('RGB',(360,35),get_random())
    # 将生成好的图片对象交给imageDraw
    img_draw = ImageDraw.Draw(img_obj)  # 生成一个画笔对象
    # 字体样式,将ttf格式字体,及字体的大小 设置
    img_font = ImageFont.truetype('static/fonts/1.ttf',30)
    # 产生一个随机验证码  大小写英文加数字 五位,每一位都可以是大小写数据
    code= ''
    for i in range(5):
        # chr 根据数字对应字符编码的英文
        upper_str = chr(random.randint(65,90))
        lower_str = chr(random.randint(97,122))
        random_int = str(random.randint(0,9))
        # 随机选取一个字符
        tmp = random.choice([upper_str,random_int,lower_str])
        # 向图片中写入一个((x轴,y轴)坐标,文本,图片北京,字体样式)
        img_draw.text((i*60+60,0),tmp,get_random(),img_font)
        # 存储写的字
        code += tmp
    print(code)
    # 这个验证码后面的其他视图函可能用得上,找到地方保存,并且这个地方的全局视图函数都可以访问
    request.session['code'] = code
    # 图片保存
    io_obj = BytesIO()
    img_obj.save(io_obj,'png')
    return HttpResponse(io_obj.getvalue())


# 首页的搭建
def home(request):
    return render(request,'home.html',locals())


# 退出登录
@login_required
def logout(request):
    # 退出登录
    auth.logout(request)
    # 重定向到主页
    return redirect(reverse('home'))


@login_required
def set_password(request):
    if request.method == 'POST':
        old_pwd = request.POST.get('old_password')
        new_pwd = request.POST.get('new_password')
        confirm_password = request.POST.get('confirm_password')
        # 1.先判断旧密码是否正确
        is_right = request.user.check_password(old_pwd)
        if is_right:
            # 判断新密码与确认密码是否一致
            if new_pwd == confirm_password:
                # 修改密码
                request.user.set_password(new_pwd)
                request.user.save()
                # 重定向界面
                return redirect(reverse('login'))
            else:
                return HttpResponse('两次密码不一致')
        else:
            return HttpResponse('原密码不正确')

login

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    {% load static %}
    <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
    <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>

</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2 ">
            <h2 class="text-center">登录界面</h2>
            <div class="form-group">
                <label for="id_username">用户名</label>
                <input type="text" name="username" id="id_username" class="form-control">
            </div>
            <div class="form-group">
                <label for="id_password">密码</label>
                <input type="password" name="password" id="id_password" class="form-control">
            </div>
{#            验证码框#}
            <div class="form-group">
                <label for="id_code">验证码</label>
                <div class="row">
                    <div class="col-md-6">
                        <input type="text" name="code" id="id_code" class="form-control">
                    </div>
                    <div class="col-md-6">
                        <img src="/get_code/" alt="" width="360" height="35" id="id_img">
                    </div>
                </div>
            </div>
            <input type="button" class="btn btn-primary" value="登录" id="id_submit">
{#            // 错误信息的展示#}
            <span style="color: red;" id="id_error"></span>
        </div>
    </div>
</div>

<script>
    {#绑定验证码点击刷新事件#}
    $('#id_img').click(function () {
        var oldPath = $(this).attr('src');
        $(this).attr('src',oldPath+='?')
    });

    {#发送ajax请求与后端交互#}
    $('#id_submit').click(function () {
        $.ajax({
            url:'',
            type:'post',
            data:{
                'username':$('#id_username').val(),
                'password':$('#id_password').val(),
                'code':$('#id_code').val(),
                'csrfmiddlewaretoken':'{{ csrf_token }}'
            },
            {#进行处理后端数据#}
            success:function (data) {
                if(data.code==1000){
                    // 验证成功则跳转主页
                    window.location.href = data.url
                }else {  // 报错信息的展示
                    $('#id_error').text(data.msg)
                }
            }
        })
    })

</script>
</body>
</html>

home.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>主页</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    {% load static %}
    <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
    <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>

</head>
<body>

{#导航条样式#}
<nav class="navbar navbar-inverse">
  <div class="container-fluid">
    <!-- Brand and toggle get grouped for better mobile display -->
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="/home/">BBS首页系统</a>
    </div>

    <!-- Collect the nav links, forms, and other content for toggling -->
    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
      <ul class="nav navbar-nav">
        <li class="active"><a href="#">文章 <span class="sr-only">(current)</span></a></li>
        <li><a href="#">随笔</a></li>
        <li class="dropdown">
          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">更多 <span class="caret"></span></a>
          <ul class="dropdown-menu">
            <li><a href="#">Action</a></li>
            <li><a href="#">Another action</a></li>
            <li><a href="#">Something else here</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">Separated link</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">One more separated link</a></li>
          </ul>
        </li>
      </ul>
      <form class="navbar-form navbar-left">
        <div class="form-group">
          <input type="text" class="form-control" placeholder="Search">
        </div>
        <button type="submit" class="btn btn-default">Submit</button>
      </form>
      <ul class="nav navbar-nav navbar-right">
{#     判断用户是否登录  显示登录注册按钮#}
          {% if request.user.is_authenticated %}
{#              根据后端,显示登录之后的按钮#}
                <li><a href="#">{{ request.user.username }}</a></li>
                <li class="dropdown">
                  <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">更多操作 <span class="caret"></span></a>
                  <ul class="dropdown-menu">
{#               添加模态框的属性data-toggle="modal" data-target="#myModal"#}
                    <li><a data-toggle="modal" data-target="#myModal">修改密码</a></li>
                    <li><a href="#">修改头像</a></li>
                    <li><a href="#">后台管理</a></li>
                    <li role="separator" class="divider"></li>
                    <li><a href="{% url 'logout' %}">退出登录</a></li>
                  </ul>
                </li>
              {% else %}
{#              没有登录则显示登录注册按钮#}
                <li><a href="{% url 'login' %}">登录</a></li>
                <li><a href="{% url 'register' %}">注册</a></li>
          {% endif %}

      </ul>
    </div><!-- /.navbar-collapse -->
  </div><!-- /.container-fluid -->
</nav>

{#整体页面布局#}
<div class="container-fluid">
    <div class="row">
        <div class="col-md-2">
{#            左边面板#}
            <div class="panel panel-primary">
              <div class="panel-heading">
                <h3 class="panel-title">点击就送</h3>
              </div>
              <div class="panel-body">
                劳斯莱斯5元代金券
              </div>
            </div>
            <div class="panel panel-danger">
              <div class="panel-heading">
                <h3 class="panel-title">限定优惠</h3>
              </div>
              <div class="panel-body">
                割双眼皮买二送一
              </div>
            </div>
            <div class="panel panel-warning">
              <div class="panel-heading">
                <h3 class="panel-title">最后换购</h3>
              </div>
              <div class="panel-body">
                买电烤炉送蜂窝煤一斤!
              </div>
            </div>
        </div>
{# 中间面板 #}
        <div class="col-md-8"></div>
        <div class="col-md-2">
{#            右边面板#}
            <div class="panel panel-primary">
              <div class="panel-heading">
                <h3 class="panel-title">点击就送</h3>
              </div>
              <div class="panel-body">
                劳斯莱斯5元代金券
              </div>
            </div>
            <div class="panel panel-danger">
              <div class="panel-heading">
                <h3 class="panel-title">限定优惠</h3>
              </div>
              <div class="panel-body">
                割双眼皮买二送一
              </div>
            </div>
            <div class="panel panel-warning">
              <div class="panel-heading">
                <h3 class="panel-title">最后换购</h3>
              </div>
              <div class="panel-body">
                买电烤炉送蜂窝煤一斤!
              </div>
            </div>
        </div>
    </div>
</div>

{#修改密码的弹出框#}
<!-- Modal -->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
        <h4 class="modal-title" id="myModalLabel">修改密码</h4>
      </div>
      <div class="modal-body">
{#          设置密码的更改#}
          <form action="{% url 'set_pwd' %}" method="post">
              {% csrf_token %}
              <div class="form-group">
                  <label for="id_username">用户名</label>
                  <input type="text" id="id_username" name="username" disabled class="form-control" value="{{ request.user.username }}">
              </div>
              <div class="form-group">
                  <label for="id_password">初始密码</label>
                  <input type="password" id="id_password" name="old_password" class="form-control" >
              </div>
              <div class="form-group">
                  <label for="id_new_password">新密码</label>
                  <input type="password" id="id_new_password" name="new_password" class="form-control" >
              </div>
              <div class="form-group">
                  <label for="id_confirm_password">确认新密码</label>
                  <input type="password" id="id_confirm_password" name="confirm_password" class="form-control">
              </div>

             <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
                <button class="btn btn-primary" id="id_s">提交</button>
              </div>
          </form>
      </div>
    </div>
  </div>
</div>

</body>
</html>

逻辑流程

登录功能

1.定义登录界面

2.添加验证码的功能

	图片验证码的传值有三种方式,直接写地址,文件二进制数据,路径
	验证码的实现
		通过Image获取imgobj对象
		生成好的img对象交给ImageDraw.Draw 生成画笔
		定义字体样式,文字大小
		产生随机的验证码,利用chr实现,(65,96)大写,(97,122)小写,数字直接就是0-9
		随机5个数字,在每个循环里写入
		利用画笔对象.text((x轴,y轴)坐标,文本,图片北京,字体样式)
		存储写的字
	保存到session中的code
	然后保存图片io
	最终返回给前端值  return HttpResponse(io_obj.getvalue())
	
3.给验证码绑定点击事件
	每点击一次url添加一个?用于刷新
	
4.使用ajax与后端进行交互
	将用户的输入获取到手
	success回调函数拿到后端返回的数据
	根据code的信信息对相关标签进行渲染
	后端得到数据进行验证
		先判断验证码是否正确
		然后利用auth模块判断密码是否正确
		根据得到对象判断存在
			定义返回字典,返回给前端
			return JsonResponse(back_dic)

验证码相关推导

def get_code(request):
    # 推导步骤一:直接发送后端存在的图片
        # img标签可以直接返回二进制数据进行显示
    # with open(r'static/img/02b02.jpg.jpg','rb') as f:
    #     data = f.read()
    # return HttpResponse(data)




    # 推导步骤二 利用pillow模块自动生成图片
    '''
    from PIL import Image,ImageDraw,ImageFont
    Image       生成图片
    ImageDraw   在图片上写字
    ImageFont   控制字体的样式'''
    # 生成图片对象
    # img_obj = Image.new('RGB',(360,35),'red')     直接放文件的颜色
    # img_obj = Image.new('RGB',(360,35),get_random())   # 放rgb模式(255,23,232)
    # # 利用文件操作先保存下来
    # with open('xxx.png','wb') as f:
    #     img_obj.save(f,'png')
    # # 然后利用文件操作将图片数据读取并发送
    # with open(r'xxx.png','rb') as f:
    #     data = f.read()
    # return HttpResponse(data)





    # 推导步骤三  临时存储数据并且能够随时取出的地方
    '''内存管理模块
    from io import BytesIO,StringIO
    BytesIO     保存数据(并且在获取的时候,是以二进制的方式给你)
    StringIO    保存数据(以字符串的形式给你)
    '''
    # img_obj = Image.new('RGB',(360,35),get_random())
    #     # # 先生成一个io对象
    #     # io_obj = BytesIO()    # 可以将对象当做是文件的句柄
    #     # # 存储数据
    #     # img_obj.save(io_obj,'png')
    #     # # getvalue获取二进制的数据
    #     # return HttpResponse(io_obj.getvalue())






    # 推导步骤四     在图片上写字
    
    img_obj = Image.new('RGB',(360,35),get_random())
    # 将生成好的图片对象交给imageDraw
    img_draw = ImageDraw.Draw(img_obj)  # 生成一个画笔对象
    # 字体样式,将ttf格式字体,及字体的大小 设置
    img_font = ImageFont.truetype('static/fonts/1.ttf',30)
    # 产生一个随机验证码  大小写英文加数字 五位,每一位都可以是大小写数据
    code= ''
    for i in range(5):
        # chr 根据数字对应字符编码的英文
        upper_str = chr(random.randint(65,90))
        lower_str = chr(random.randint(97,122))
        random_int = str(random.randint(0,9))
        # 随机选取一个字符
        tmp = random.choice([upper_str,random_int,lower_str])
        # 向图片中写入一个((x轴,y轴)坐标,文本,图片北京,字体样式)
        img_draw.text((i*60+60,0),tmp,get_random(),img_font)
        # 存储写的字
        code += tmp
    print(code)
    # 这个验证码后面的其他视图函可能用得上,找到地方保存,并且这个地方的全局视图函数都可以访问
    request.session['code'] = code
    # 图片保存
    io_obj = BytesIO()
    img_obj.save(io_obj,'png')
    return HttpResponse(io_obj.getvalue())

推荐阅读