后台学习——django(2)

经过对django的初步学习,我们已经对后台的基本流程以及django的运作有了一定的了解,但是这还不足够,django还有许多方法和API需要我们详细滴学习,是时候开始进阶学习了。
上期文章:后台学习——django(1)

零、上篇文章修改

  1. 静态文件引用修改
  • 在上篇文章就说过,要尽可能滴把前端跟后台分开,不要前后端代码混在一起,我们看回之前的server/learning/templates/index.html
    <!doctype html>
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <title>登录注册系统</title>
    {% load staticfiles %} # 这里
    <link rel="stylesheet" type="text/css" href='{% static "css/index.css" %}' />
    </head>
    <body>
    <div>
    <form action="login_register" method="post">
    {% csrf_token %}# 还有这里
    <table>
    <tr>
    <th>帐号:</th>
    <td><input type="text" id="username" name="username" maxlength="20"/></td>
    </tr>
    <tr>
    <th>密码:</th>
    <td><input type="password" id="password" name="password" maxlength="20"/></td>
    </tr>
    <tr>
    <th></th>
    <td>
    <label>
    <input type="radio" name="way" value="login" checked="checked"/>登录
    </label>
    <label>
    <input type="radio" name="way" value="register"/>注册
    </label>
    </td>
    </tr>
    <tr>
    <th></th>
    <td><input type="submit" id="submit" value="提交" onclick="return check()"/></td>
    </tr>
    </table>
    </form>
    </div>
    <script type="text/javascript">
    function check(){
    var username = document.getElementById('username');
    var password = document.getElementById('password');
    if(username.value == ''){
    alert('帐号不能为空,请重新输入');
    username.select();
    }else if(password.value == ''){
    alert('密码不能为空,请重新输入');
    password.select();
    }else{
    return true;
    }
    return false;
    }
    </script>
    </body>
    </html>
  • 可以看到,里面还是存在了两段混入了后台代码了,其中一个就是静态文件引入所用到了{% load staticfiles %},实际上,我们可以把这句代码删掉,将引入链接的部分<link rel="stylesheet" type="text/css" href='{% static "css/index.css" %}' />修改成
    <link rel="stylesheet" type="text/css" href="/static/css/index.css"/>,你们可以打开服务器再次看一下页面,实际效果是一样的
  • 至于{% csrf_token %}这句代码,我也没有什么好的办法解决,要不就不要用django的防止CSRF模式攻击功能,要不就换一种传输方式,例如ajax,但是这样做就远离了初衷了,还是不要改吧,到时候后台工程师自己修改好了,反正也就是添加一句话的事

一、进阶学习——验证码

在一个正常的登录系统中,验证码是非常重要的,用于识别人机,毕竟我们都知道,这个世界中存在着万恶的爬虫,验证码有很多种方式,有图片的,有邮件的,有短信的,有拼图的,不管什么样的验证码,目的都是验证访问用户到底是人还是机器,要对机器say no,接下来我们要实践一个图片性的验证码。

  1. URL拓展
  • 还记得URL设置文件urls.py里面,匹配路径的是用正则表达式的么,学过正则表达式的应该会知道分组吧,其实在路径匹配的正则表达式那里使用分组,django还可以将之匹配出来当作参数给接口函数,不多说先试试效果
  • 假设要通过URL直接传递两个参数,返回两个数相加的结果
    • 先改server/server/urls.py文件
      # -- coding:utf-8 --
      from django.conf.urls import url
      from django.contrib import admin
      from learning import views as learning
      from django.conf.urls.static import static
      from django.conf import settings
      urlpatterns = [
      url(r'^admin/', admin.site.urls),
      url(r'^$', learning.index),
      # 通过正则分组匹配两个相加的数字
      url(r'^add/(\d+)/(\d+)/$', learning.add),
      ]
      urlpatterns+=static(settings.STATIC_URL,document_root=settings.STATIC_ROOT)
      这样我们可以通过访问例如localhost:8000/add/3/4/的网址(后面两个数字可以换),来实现直接传参的效果
    • 再在server/server/views.py添加接口函数
      def add(request, a, b):
      return HttpResponse(str(int(a) + int(b)))
      没错,括号里面的数字会当作参数直接就传到接口函数里面去了,不过这些参数都是字符串,如果是要当作数字或者其他类型使用的时候,记得要转换类型
    • 再次怀着激动的心情开启服务器(为啥要说再次???),输入网址localhost:8000/add/4/5/
    • 换个数字再试一遍localhost:8000/add/200/300/

      Perfect!!!
  1. 下一步我们就要开始构造验证码函数了,也就是返回一张验证码图片的函数,怎么建呢?我们又没有验证码图片在?没有就直接画出来呗,python拥有一个库pillow专门用于画图的,安装命令pip install pillow
  • 首先还是要添加路由,在server/server/urls.py中添加下面的代码
    url(r'^verify/(\d+)/(\d+)/', learning.verify)
    大家可以看得到这里用到了上面URL拓展的知识,为的就是使得这验证码函数可以得到重用的机会,那两个参数就是宽度width和高度height,这样的话以后要用到宽度或者高度不同的验证码都可以使用这个函数了
  • 下一步我们就要开始准备我们的画笔构造我们的验证码了
    • 使用pillow画图,第一步就是创建一张画布,有了画布我们才可以在上面画画对不,创建画布代码如下
      # 创建画布需要导入Image
      from PIL import Image
      # 用到了Imagenew函数
      # 第一个参数是颜色通道,这里使用了RGB通道,还有其他的一些通道,如CMYK之类的,但不用管
      # 第二个参数是由宽高组成的元组,数字
      # 第三个参数是图片的背景色,这里用rgb的颜色显示,例如( 255, 255, 255),注意这是元组
      img = Image.new('RGB', (width, height), bgColor)
    • 有了画布,但我们用什么来画图,(难道是。。。画笔?),废话,当然是,来,给你支画笔
      # 创建画笔需要导入ImageDraw
      from PIL import ImageDraw
      # 用到了ImageDrawDraw函数
      # 有且只有一个参数,就是之前创建的画布
      draw = ImageDraw.Draw(img)
    • 有了画布和画笔,但验证码中是有字的,难道我们真的要像现实一样一笔一划滴写字么,而且用电脑写好难看啊,其实pillow还可以导入电脑的字体,你不用再想些乱七八糟的东西了
      # 导入字体需要导入ImageFont
      from PIL import ImageFont
      # 用到了ImageFonttruetype函数,可以自动查询电脑中的字体
      # 第一个参数是字体名字
      # 第二个参数是字体大小
      # 注意这个是windows系统下默认的字体,其他系统自己找
      font = ImageFont.truetype('arial.ttf', size)
  • 好了,画布画笔字体我们都有了,那我们开画吧,该画些什么呢。。。。。。。
    • 不想那么多,反正验证码总该有字吧,我们研究怎么写字
      # 写字需要使用drawtext方法
      # 第一个参数是一个坐标轴元组,分别是距离左边和上边的距离
      # 第二个参数是要写的字(字符串)
      # 后面的两个参数分别是字体和字体颜色
      draw.text((x, y), text, font=font, fill=textColor)
    • 还有可能要一些干扰元素,例如线条
      # 画线条需要使用drawline方法
      # 第一个参数是包含了两个坐标的元组,分别是线条一头一尾的坐标
      # 后面的参数是线条的颜色
      draw.line((x1, y1, x2, y2), fill=lineColor)
    • 还想画些什么,算了吧,画那么复杂干嘛


  • 一切都准备就绪了,开工写接口函数
    # 导入绘图对象
    from PIL import Image, ImageFont, ImageDraw
    # 导入随机函数,randint:生成固定范围内的随机整数
    from random import randint
    def verify(request, width, height):
    wordsCount = 4# 验证码中的字符长度
    width = int(width)# 图片宽度
    height = int(height)# 图片高度
    size = int(min(width/wordsCount, height)/1.5)# 字体大小设置
    bgColor = (randint(200, 255), randint(200, 255), randint(200, 255))# 随机背景色(浅色)
    img = Image.new('RGB', (width, height), bgColor)# 创建图像
    font = ImageFont.truetype('Arial.ttf', size)# 导入字体
    draw = ImageDraw.Draw(img)# 创建画笔
  • 等等,我们要怎么返回生成的图片????
    • 我们要知道,网页间的传输都是字符串的传输,并没有其他数据结构,所以不管你要传输什么,都要把它转成字符串
    • 图像的字符串形式实际上就是二进制数字
    • pillow中并没有直接返回图像二进制的功能
    • 实际上有个笨方法就是利用Image对象的save方法保存到本地,然后读取本地文件返回
    • 但是每一次请求都要在本地存储验证码图片,会耗费大量的内存空间滴,而且这验证码用过一次就没用了
    • 为了应付这需求,python有个内置模块StringIO,可以将图片缓存到内存里面,读取后就清空内存,是不是很爽
    • 废话不多说,让我们看看要怎么弄
      # 导入StringIO模块
      from StringIO import StringIO
      # 建立一个缓存对象
      mstream = StringIO()
      # 将图片保存到内存中
      img.save(mstream, 'jpeg')
      # 返回内存中的图片
      return HttpResponse(mstream.getvalue(), 'image/jpeg')
  • 还有还有,我们要画一个什么样验证码,总该定好一些规则吧
    • 规则一:均匀绘画字符,居中
    • 规则二:字符颜色要比较深
    • 规则三:要有线条雪花等干扰元素
    • 规则四:一切能随机的都随机
  • 我***,那么多规则,怎么画!!不管了不管了,先想一下文字怎么画吧,这个是重点。然而,pillow里面是没有旋转文字这东西的,就只有图像本身的旋转
    • 首先要确定用什么字符,当然是数字加大小写字母啦
      text = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
    • 其次要确定文字的位置,这里需要一定的数学知识(加减乘除,别说你不会)
      left = width * i / num + (width / 4 - size) / 2# i为第几个文字
      top = (height - size) / 2
    • 还要确定文字的颜色,要随机的颜色,颜色要比较深
      textColor = (randint(0, 160), randint(0, 160), randint(0, 160))
    • 合起来就是这样滴
      text = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
      for i in range(wordsCount):
      textColor = (randint(0, 160), randint(0, 160), randint(0, 160))
      left = width * i / wordsCount + (width / 4 - size) / 2
      top = (height - size) / 2
      draw.text((left, top), text[randint(0, len(text) - 1)], font=font, fill=textColor)
  • 再画雪花,话说雪花是什么东东,呵呵,就是白色的*嘛~~~
    • 颜色:白色
      textColor = (255, 255, 255)
    • 位置:随机
      left = randint(0, width)
      top = randint(0, height)
    • 合起来:
      for i in range(30):
      textColor = (255, 255, 255)
      left = randint(0, width)
      top = randint(0, height)
      draw.text((left, top), '*', font=font, fill=textColor)
  • 最后画线条,这个简单
    • 位置:头尾都随机
      line = (randint(0, width), randint(0, height), randint(0, width), randint(0, height))
    • 颜色:随机
      linecolor = (randint(0, 160), randint(0, 160), randint(0, 160))
    • 合起来:
      for i in range(5):
      linecolor = (randint(0, 160), randint(0, 160), randint(0, 160))
      line = (randint(0, width), randint(0, height), randint(0, width), randint(0, height))
      draw.line(line, fill=linecolor)
  • OK,把所有的步骤都合起来就是结果了
    # -- coding:utf-8 --
    import sys
    reload(sys)
    sys.setdefaultencoding('utf-8')
    from django.http import HttpResponse
    from PIL import Image, ImageFont, ImageDraw
    from StringIO import StringIO
    from random import randint
    def verify(request, width, height):
    wordsCount = 4
    width = int(width)
    height = int(height)
    size = int(min(width / wordsCount, height) / 1.3)
    bgColor = (randint(200, 255), randint(200, 255), randint(200, 255))
    img = Image.new('RGB', (width, height), bgColor)
    font = ImageFont.truetype('arial.ttf', size)
    draw = ImageDraw.Draw(img)
    text = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
    for i in range(wordsCount):
    textColor = (randint(0, 160), randint(0, 160), randint(0, 160))
    left = width * i / wordsCount + (width / 4 - size) / 2
    top = (height - size) / 2
    draw.text((left, top), text[randint(0, len(text) - 1)], font=font, fill=textColor)
    for i in range(30):
    textColor = (255, 255, 255)
    left = randint(0, width)
    top = randint(0, height)
    draw.text((left, top), '*', font=font, fill=textColor)
    for i in range(5):
    linecolor = (randint(0, 160), randint(0, 160), randint(0, 160))
    line = (randint(0, width), randint(0, height), randint(0, width), randint(0, height))
    draw.line(line, fill=linecolor)
    del draw
    mstream = StringIO()
    img.save(mstream, 'jpeg')
    return HttpResponse(mstream.getvalue(), 'image/jpeg')
  • 保存,打开服务器,打开网址localhost:8000/verify/100/40/,你会看到这样的一张图片
  • 刷新一下


  • 换个网址localhost:8000/verify/200/40/
    Paste_Image.png
  • 非常完美不是么
  1. 然而的然而,你还没完成呢!!只有验证码图片却不能验证有什么用!!
  • 要起到验证的效果首先就要保存相应的文字,那么就要在写文字那里保存相应的文字verifyText

  • 然后然后还要将之保存在session里面
    request.session['verify'] = verifytext

  • 这个函数基本完成了,但一个完整的验证码系统还是不够的,我们来完善以下,接下来由于个人问题,实在不想看到之前的代码了,会将之前没用的清掉,我会把变动的文件代码全部给出(这算做洁癖么,还是强迫症)

  • 更改后的文件目录
    server
    ├────learning
    | ├────migrations
    | | └──init.py
    | ├────static
    | ├────templates
    | | └──index.html
    | ├────init.py
    | ├────admin.py
    | ├────apps.py
    | ├────models.py
    | ├────tests.py
    | └────views.py
    ├────server
    | ├────init.py
    | ├────settings.py
    | ├────urls.py
    | └────wsgi.py
    ├────db.sqlite3
    └────manage.py

  • server/server/urls.py文件:
    # -- coding:utf-8 --
    from django.conf.urls import url
    from django.contrib import admin
    from learning import views as learning
    from django.conf.urls.static import static
    from django.conf import settings

       urlpatterns = [
           url(r'^admin/', admin.site.urls),
           url(r'^$', learning.index),
           url(r'^verify/(\d+)/(\d+)/$', learning.verify),
           url(r'^check/$', learning.check),
       ]
       urlpatterns+=static(settings.STATIC_URL,document_root=settings.STATIC_ROOT)
    
  • server/learning/templates/index.html文件:
    <!doctype html>
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <title>Document</title>
    </head>
    <body>
    <form action="/check/" method="post">
    {% csrf_token %}
    <img src="verify/300/80/" /><br />
    <input type="text" name="verify" id="verify"/>
    <input type="submit"/>
    </form>
    </body>
    </html>
    这个html文件通过verify/300/80/显示一张300x80的验证码,点击提交按钮后提交到/check/

  • server/learning/views.py文件:
    # -- coding:utf-8 --
    import sys
    reload(sys)
    sys.setdefaultencoding('utf-8')
    from django.shortcuts import render
    from django.http import HttpResponse
    from PIL import Image, ImageFont, ImageDraw
    from StringIO import StringIO
    from random import randint

       # 页面接口:返回`index.html`页面
       def index(request):
           return render(request, 'index.html')
    
       # 功能接口:返回验证码输入正确与否(忽略大小写)
       def check(request):
           if request.POST['verify'].lower() == request.session['verify'].lower():
               return HttpResponse('success')
           else:
               return HttpResponse('failed')
    
       # 功能接口:返回验证码图片
       def verify(request, width, height):
           wordsCount = 4
           width = int(width)
           height = int(height)
           size = int(min(width / wordsCount, height) / 1.3)
           bgColor = (randint(200, 255), randint(200, 255), randint(200, 255))
           img = Image.new('RGB', (width, height), bgColor)
           font = ImageFont.truetype('arial.ttf', size)
           draw = ImageDraw.Draw(img)
           text = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
           verifytext = ''
           for i in range(wordsCount):
               textColor = (randint(0, 160), randint(0, 160), randint(0, 160))
               left = width * i / wordsCount + (width / 4 - size) / 2
               top = (height - size) / 2
               word = text[randint(0, len(text) - 1)]
               verifytext += word
               draw.text((left, top), word, font=font, fill=textColor)
           for i in range(30):
               textColor = (255, 255, 255)
               left = randint(0, width)
               top = randint(0, height)
               draw.text((left, top), '*', font=font, fill=textColor)
           for i in range(5):
               linecolor = (randint(0, 160), randint(0, 160), randint(0, 160))
               line = (randint(0, width), randint(0, height), randint(0, width), randint(0, height))
               draw.line(line, fill=linecolor)
           del draw
           mstream = StringIO()
           img.save(mstream, 'jpeg')
           request.session['verify'] = verifytext
           return HttpResponse(mstream.getvalue(), 'image/jpeg')
    
  1. OK,开始验证成果
  • 打开网址localhost:8000,显示页面如下(验证码图片可能不同):
  • 输入正确的验证码


  • 点击提交后,显示success
  • 刷新页面localhost:8000,重复上面的步骤,输入错误的验证码,显示failed
  • 到此,一个完整的验证码就完成了,如果你有更好的想象力,还可以画出更好的验证码,深究请查阅pillow文档

二、进阶学习——用户管理系统

  1. 先不说那么多,先清空两个文件先(真的是强迫症),分别是:
  • server/learning/admin.py文件:
    from django.contrib import admin
    # Register your models here.
  • server/learning/models.py文件:
    from future import unicode_literals
    from django.db import models
    # Create your models here.
  • 清完同步数据库:
    python manage.py makemigrations
    python manage.py migrate
  1. 再修改server/learning/templates/idnex.html,用于应用相关接口函数:
    <!doctype html>
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <title>登录注册系统</title>
    </head>
    <body>
    <div>
    <form action="check/" method="post">
    {% csrf_token %}
    <table>
    <tr>
    <th>帐号:</th>
    <td><input type="text" id="username" name="username" maxlength="20" /></td>
    </tr>
    <tr>
    <th>密码:</th>
    <td><input type="password" id="password" name="password" maxlength="20" /></td>
    </tr>
    <tr>
    <th>验证码</th>
    <td>
    <input type="text" id="verify" name="verify" maxlength="4"/>
    <img src="verify/100/30/" alt="" />
    </td>
    </tr>
    <tr>
    <th></th>
    <td>
    <label><input type="radio" name="way" value="login" checked="checked"/>登录</label>
    <label><input type="radio" name="way" value="register"/>注册</label>
    </td>
    </tr>
    <tr>
    <th></th>
    <td><input type="submit" id="submit" value="提交" onclick="return check()" /></td>
    </tr>
    </table>
    </form>
    </div>
    <script type="text/javascript">
    function check() {
    var username = document.getElementById('username');
    var password = document.getElementById('password');
    var verify = document.getElementById('verify');
    if(username.value == '') {
    alert('帐号不能为空,请重新输入');
    username.select();
    } else if(password.value == '') {
    alert('密码不能为空,请重新输入');
    password.select();
    } else if(verify.value.length != 4){
    alert('验证码输入错误,请重新输入');
    verify.select();
    } else {
    return true;
    }
    return false;
    }
    </script>
    </body>
    </html>
  • 打开服务器,进入网址localhost:8000,结果如下:
  • 很难看,不过将就一下吧


  1. 修改server/learning/views.py文件中的check函数,并添加两个空函数:
    # check函数的作用已经在之前涉及过了,这里就不讲了
    def check(request):
    if request.POST['verify'].lower() == request.session['verify'].lower():
    username = request.POST['username']
    password = request.POST['password']
    if request.POST['way'] == 'login':
    return HttpResponse(login(request, username, password))
    elif request.POST['way'] == 'register':
    return HttpResponse(register(request, username, password))
    else:
    return HttpResponse('验证码错误')

    def login(request, username, password):
        pass
    
    def register(request, username, password):
        pass
    
  2. OK,终于准备好所有东西了,我们开始研究django的用户认证系统吧

  • 用户的基本操作一共有四个,分别是:
    • 注册
    • 登录
    • 登出
    • 判断是否已经登录
  • django封装了一个比较完善的用户认证系统,各种操作都非常方便,注册便是如此
    • 添加一个用户只需用到下面的代码:
      from django.contrib.auth.models import User
      # username为用户名,password为密码
      User.objects.create_user(username=username, password=password).save()
    • 然而注册需要考虑到一种状况:用户已存在,其实如果是这样的话,在创建的时候就会报错,做好错误处理就好
      try:
      User.objects.create_user(username=username, password=password).save()
      return '注册成功'
      except:
      return '已存在用户'
  • 有了注册好的用户,自然需要登录:
    • 登录之前肯定要验证帐号密码是否正确啦
      from django.contrib.auth import authenticate
      # username为用户名,password为密码
      # 该函数会返回一个user对象,如果不存在该用户或者密码错误则返回None
      authenticate(username=username, password=password)
    • 验证正确自然要储存登录信息,通常而言都会用session来存储,而django对此方法进行了包装,只需使用以下代码就行了:
      from django.contrib.auth import login
      # request为请求对象,user为用户对象
      login(request, user)
  • 登录后,怎么登出呢,其实这个更简单,其他后台或许直接清掉session就可以了,django也一样将其封装好了:
    from django.contrib.auth import logout
    # request为请求对象
    logout(request)
  • 最后一个,判断是否已经登录,这个django用request对象中的user对象中的is_authenticated()方法(此处应该有哭笑不得的表情)
    # 用户登录时,该函数返回True,否则返回False
    request.user.is_authenticated()
  1. OK,一切都准备就绪后就可以写之前那两个函数了,代码如下:
    from django.contrib.auth.models import User
    from django.contrib import auth

     def login(request, username, password):
         user = auth.authenticate(username=username, password=password)
         if user is None:
             return '登录错误'
         else:
             auth.login(request, user)
             return '登录成功'
    
     def register(request, username, password):
         try:
             User.objects.create_user(username=username, password=password).save()
             # 注册后默认为登录状态
             login(request, username, password)
             return '注册成功'
         except:
             return '已存在用户'
    
  • 大家可以打开服务器试一下,分别注册新的用户,注册已经存在相同用户名的用户,登录帐号密码正确的用户, 登录不存在的用户,登录密码错误的用户,是不是都如想象中一般返回了想要返回的信息?是的话就恭喜你完成了登录和注册的操作了
  1. 然而还没有完呢,登录注册完总该有个用户界面吧,在用户界面总该有登出的设置吧,访问用户界面时总该有确认是否已经登录了吧
  • 为了有一个更好的应用场景,这里需要添加一个html文件server/learning/templates/user.html,内容如下:
    <!doctype html>
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <title>user</title>
    </head>
    <body>
    <script type="text/javascript">
    username = location.search.match(/username=(.*?)(&|$)/);
    if(username) {
    document.write('欢迎' + username[1] + '的到来<a href="/logout/">注销</a>');
    }
    </script>
    </body>
    </html>
    现在的目录如下:
    server
    ├────learning
    | ├────migrations
    | | └──init.py
    | ├────static
    | ├────templates
    | | ├──index.html
    | | └──user.html
    | ├────init.py
    | ├────admin.py
    | ├────apps.py
    | ├────models.py
    | ├────tests.py
    | └────views.py
    ├────server
    | ├────init.py
    | ├────settings.py
    | ├────urls.py
    | └────wsgi.py
    ├────db.sqlite3
    └────manage.py

  • 还要修改一下server/server/urls.py文件,接上对应的接口函数:
    urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^$', learning.index),
    url(r'^user$', learning.user),
    url(r'^verify/(\d+)/(\d+)/$', learning.verify),
    url(r'^check/$', learning.check),
    url(r'^logout/$', learning.logout)
    ]

  • 最后写上各个接口函数就搞定啦,具体细节看注释:
    # -- coding:utf-8 --
    import sys
    reload(sys)
    sys.setdefaultencoding('utf-8')
    from django.shortcuts import render
    # 这里新导入了一个HttpResponseRedirect函数,用于页面的重定向
    from django.http import HttpResponse,HttpResponseRedirect
    from PIL import Image, ImageFont, ImageDraw
    from StringIO import StringIO
    from random import randint
    from django.contrib.auth.models import User
    from django.contrib import auth

       def index(request):
           return render(request, 'index.html')
    
       def user(request):
           # 使用request.user.is_authenticated()来判断是否已经登录
           if request.user.is_authenticated():
               return render(request, 'user.html')
           else:
               # 如果没有登录的话自动重定向到首页
               return HttpResponseRedirect('/')
    
       def logout(request):
           # 登出设置,并重定向到首页
           auth.logout(request)
           return HttpResponseRedirect('/')
    
       def check(request):
           if request.POST['verify'].lower() == request.session['verify'].lower():
               username = request.POST['username']
               password = request.POST['password']
               if request.POST['way'] == 'login':
                   # 这里修改为直接返回函数返回的值
                   return login(request, username, password)
               elif request.POST['way'] == 'register':
                   return register(request, username, password)
           else:
               return HttpResponse('验证码错误')
    
       def login(request, username, password):
           user = auth.authenticate(username=username, password=password)
           if user is None:
               return HttpResponse('登录错误')
           else:
               auth.login(request, user)
               # 如果登录成功就自动跳转到用户页面
               return HttpResponseRedirect('/user?username=' + username)
    
       def register(request, username, password):
           try:
               User.objects.create_user(username=username, password=password).save()
               # 刚注册完肯定可以登录成功,直接返回用户页面
               return login(request, username, password)
           except:
               return HttpResponse('已存在用户')
       # 验证码函数已省略
    

    这里解析一下什么是重定向,其实也就是服务器端的网页跳转,重定向后就转到不同的路径去了,比如你要访问a网页,在地址栏输入a网页的URL,但a网页会重定向到b网页,那么返回来的是b网页的信息,而且地址栏也是显示b网页的URL(当然这个只是给一脸懵的人看的,想了解更详细的信息,就看百度百科的解析吧)

  1. 我们来测试一下成果,如果你看到的跟下面的一样就证明你已经搞定了
  • 打开服务器,进入网址localhost:8000,进入首页:
  • 注册一个正确的帐号,自动跳转到用户页面:


  • 点击注销,跳转到首页:


  • 注册一个已经存在的用户,返回错误信息:


  • 返回首页输入正确的帐号密码,登录帐号,自动跳转到用户页面:


  • 注销返回首页,登录不存在的用户,返回错误信息:


  • 返回首页,登录密码错误的用户,返回错误信息:


  1. 基本功能通通都实现了,然而还不足够完善,我们还需要更多的功能
  • 我们可以发现,之前的代码在出现登录错误的时候是无法分辨是用户不存在还是密码错误的,而且我们日后肯定会经常需要查询用户存不存在的信息,不一定要用户密码都正确才可以查询得到。但是django的用户认证模块并区分这两个错误,不过我们可以发现用户认证是基于django的模型的,也就是之前说的数据库操作那里,我们可以通过数据库操作的方法只通过用户名来查询是否存在该用户,登录接口函数修改如下:
    def login(request, username, password):
    try:
    # 当不存在该用户时会出现错误
    User.objects.get(username=username)
    user = auth.authenticate(username=username, password=password)
    if user is None:
    return HttpResponse('密码错误')
    else:
    auth.login(request, user)
    return HttpResponseRedirect('/user?username=' + username)
    except:
    return HttpResponse('不存在该用户')
  • 还有,我们以后会有大量的页面需要登录用户才可以访问,但如果每次都使用request.user.is_authenticated()来判断会显得很麻烦,有没有更好的方法呢?答案是有的,django在设计的时候就已经考虑到这个问题了,它提供了一种装饰器,只要在需要登录后访问的页面接口函数前加上这个装饰器就可以自动判断是否已经登录了,使用方法如下:
    # 导入装饰器
    from django.contrib.auth.decorators import login_required
    # 使用装饰器,在函数前@这个装饰器
    # 参数login_url为没有登录的时候需要跳转到哪里,这里设置为跳转到首页
    # 参数redirect_field_name为重定向时传输当前的页面路径,这里设置为没有
    @login_required(login_url='/', redirect_field_name=None)
    def user(request):
    # 不再需要判断是否已经登录了
    return render(request, 'user.html')
  • 有时候我们存储的用户信息可不单单只有这些(包括username、first_name、last_name、email、password),我们还需要额外添加一些用户信息,如登录时间,注册时间(其实也有滴,不过我就懒得想其他啦,反正原理都一样)等,但是如果直接更改原本的模型又很麻烦,这个时候就需要使用模型的代理继承功能了。
    • 模型的代理继承实际上借用了python类的继承,修改server/learning/models.py代码如下:
      from future import unicode_literals
      from django.db import models
      from django.contrib.auth.models import User
      # 继承与django默认的User模型
      class learningUser(User):
      # 添加两个字段,分别是登录日期和注册日期(自动填入)
      logindate = models.DateField(auto_now=True)
      registerdate = models.DateField(auto_now_add=True)

    • 实际上,这并不是一种正规的写法(但经验证,是可以这样做的),正规的写法如下:
      class learningUser(models.Model):
      # 设置与User对象一一对应
      user = models.OneToOneField(User)
      logindate = models.DateField(auto_now=True)
      registerdate = models.DateField(auto_now_add=True)
      但是这样一来引用的时候会显得很麻烦,创建用户的时候还要创建多一个对象,而且到时候要获取用户的logindate的时候还要用user.learningUser.logindate来获取,虽然这样可以动态改变相关model,但如果当前项目中每一个用户都需要有这个拓展就显得很麻烦了,所以之后还是以第一种方法来拓展User模型

    • 接下来就是模型改动必要的程序了,记住以后都要这样做
      修改server/learning/admin.py文件:
      from django.contrib import admin
      from learning.models import learningUser
      admin.site.register(learningUser)
      终端执行代码:
      python manage.py makemigrations
      python manage.py migrate

    • 最后一步就是修改接口了,将from django.contrib.auth.models import User替换成from learning.models import learningUser
      修改登录注册函数为如下:
      def login(request, username, password):
      try:
      # 使用learning_user模型获取用户
      guest = learningUser.objects.get(username=username)
      user = auth.authenticate(username=username, password=password)
      if user is None:
      return HttpResponse('密码错误')
      else:
      auth.login(request, user)
      # 输出我们之前新添加的两个属性(懒的在页面上显示了)
      print guest.logindate, guest.registerdate
      return HttpResponseRedirect('/user?username=' + username)
      except:
      return HttpResponse('不存在该用户')

       def register(request, username, password):
           try:
               # 记住也要用learningUser创建对象
               learningUser.objects.create_user(username=username, password=password).save()
               login(request, username, password)
               return HttpResponseRedirect('/user?username=' + username)
           except:
               return HttpResponse('已存在用户')
      

      这里要提醒的是如果使用的是django用户认证的函数,他们的操作对象都是最原本的User对象,而不是我们新创建的用户对象

    • 我们重新注册一个帐号,在终端可以看到登录和注册的日期


  • 如果我们要对用户进行分级又该怎么办呢?比如要分管理员和普通用户(注意这里不是可以进入admin页面的超级用户,管理员可以管理用户,删除用户等),或者更负责的分类又怎么办呢?其实这里涉及到用户权限还有组的问题,组用于标识用户的类别(一个用户可以多个类),权限对应具体的权限,一个组可以对应多个权限。说这么多,不用理解,先看下面就会明白(我会告诉你我也研究了好久么):
    • 首先我们要知道权限是什么,有什么用。权限通俗来讲就是你能不能够做某种事,就像男人不能生孩子(先天性上帝没有给你权限,除了某个从上帝盗取的权限的人),权限的存在可以很方便滴管理用户,一些特殊的功能就只能给有权限的用户使用,而组就是对用户的一种分类,组包含了多个权限,用户属于这个组的话就拥有了这些权限,这样就不需要很麻烦滴对用户添加多个权限了,而且分组后还可以更直接滴对用户进行管理,比如一个班有部分人当选了组长(组),他们可以管理组员,检查作业、管理组员、组织小组活动等(权限),如果老师需要所有人的名单,可以直接让组长提供小组名单(对组的管理),这样一来就不需要一个一个权限的管理了,上升到组的层次。

    • 最近废话有点多,还是专心研究代码吧(以下代码在server/learning/views.py文件写的)。首先你要懂得怎样创建一个新的权限:
      # 在django中权限就是一个Permission模型的实例
      from django.contrib.auth.models import Permission
      from django.contrib.contenttypes.models import ContentType
      # 用get_or_create防止重复创建
      newPermission = Permission.objects.get_or_create(**{
      # 创建Permission对象需要三个参数(都是必须的)
      # codename是代码中使用的名字
      # name是显示出来的名字
      # content_type要通过创建ContentType对象获取一个该模型的ContentType实例
      'codename': 'new_permission',
      'name': 'New Permission',
      'content_type': ContentType.objects.get_for_model(learningUser)
      })[0]
      本质上讲ContentType实例可以从更高的层次操作模型(这里操作learningUser模型,不过你们都不需要知道啦,现在知道怎么写就好,重点是codenamename

    • 创建之后总该要添加吧,其实添加很简单,假设user是一个User的实例(实际上可以是User的继承模型,如我们之前设置的learningUser模型):
      # 注意所有的newPermission都是Permission的实例对象
      # 设定user对象的权限为列表中的权限
      user.permission = [newPermission1, newPermission2, ...]
      # 向user对象添加权限
      user.permission.add(newPermission1, newPermission2, ...)
      # 移除user对象中的权限
      user.permission.remove(newPermission1, newPermission2, ...)
      # 清空user对象的权限
      user.permission.clear()

    • user对象有了权限,那么我们应该怎么判断user到底有没有该权限呢?这时候就需要调用user对象的has_perm()或者has_perms()方法了
      # 还记得之前写的codename么,判断权限的时候就需要用到了
      # 格式为:app_name.codename
      user.has_perm('learning.new_permission')
      # 这个是判断同时拥有多个权限的(传一个列表或者元组过去)
      user.has_perms(['learning.new_permission'])

    • 像登录一样,权限判断也有相对因的装饰器在需要权限的函数前面写上这个装饰器就可以了
      # 第一个参数是权限(多个权限时,传递一个列表)
      @permission_required('learning.new_permission', login_url='/')

    • 权限搞定了,那么组又该怎么做呢?其实组也有相对应的模型Group,每个组都是Group的实例,我们先创建一个:
      newGroup = Group.objects.get_or_create(name='manager')[0]

    • 这个时候,这个组是不没有任何的权限的,我们要添加权限到组里面,添加方法跟添加到User对象类似:
      # 注意所有的newPermission都是Permission的实例对象
      # 设定newGroup对象的权限为列表中的权限
      newGroup.permission = [newPermission1, newPermission2, ...]
      # 向newGroup对象添加权限
      newGroup.permission.add(newPermission1, newPermission2, ...)
      # 移除newGroup对象中的权限
      newGroup.permission.remove(newPermission1, newPermission2, ...)
      # 清空newGroup对象的权限
      newGroup.permission.clear()

    • 创建好组就可以添加到user对象中了,添加方法还是一样滴:
      # 注意所有的newGroup都是Group的实例对象
      # 设定user对象的权限为列表中的组
      user.groups= [newGroup1, newGroup2, ...]
      # 向user对象添加组
      user.groups.add(newGroup1, newGroup2, ...)
      # 移除user对象中的组
      user.groups.remove(newGroup1, newGroup2, ...)
      # 清空user对象的组
      user.groups.clear()

    • 用户属于一个组,就拥有了这个组所包含的权限,判断有没有权限的方法跟之前用户直接拥有某权限的方法一样,这里就不写了

    • 为了更好滴应用到权限和组的功能,这里用一个实例(只有是manager的用户才可以查看所有的用户名字)来演示

    • 先添加一个html文件server/learning/templates/manager.html,内容如下:
      <!doctype html>
      <html lang="en">
      <head>
      <meta charset="UTF-8" />
      <title>manager</title>
      <script src="http://code.jquery.com/jquery-latest.js" type="text/javascript" charset="utf-8"></script>
      </head>
      <body>
      <script type="text/javascript">
      $.ajax({
      type:"get",
      url:"/show_all_user/",
      dataType:"json",
      success:function(data){
      write = '当前有以下用户<br />'
      for(var i = 0; i < data.length; i++){
      write += data[i] + '<br />'
      }
      document.write(write);
      }
      });
      </script>
      </body>
      </html>

    • 修改server/server/urls.py文件,添加以下几个路径:
      url(r'^manager$', learning.manager),
      url(r'^show_all_user/$', learning.show_all_user),

    • 修改server/learning/views.py文件,以下列出要新导入的方法和要修改或者添加的函数:
      from django.contrib.auth.decorators import user_passes_test
      from django.contrib.auth.models import Group, Permission
      from django.contrib.contenttypes.models import ContentType

       # 获取组的函数,传递组的名字和对应的权限名(列表,自动创建)
       def get_or_create_group(name, permissions=None):
           group = Group.objects.get_or_create(name=name)[0]
           if permissions:
               permissions_codename = [i.codename for i in group.permissions.all()]
               for i in permissions:
                   if i not in permissions_codename:
                       group.permissions.add(Permission.objects.get_or_create(**{
                           'codename': i,
                           'name': ' '.join(i.split('_')).title(),
                           'content_type': ContentType.objects.get_for_model(learningUser)
                       })[0])
           return group
      
       # 因为django没有提供判断是否有组的装饰器,自己就写了一个
       # 这个装饰器跟权限装饰器一样,是不过传递的组名而不是权限名
       def group_required(group, **kwargs):
           def check_group(user):
               return set(groups).issubset(set([i.name for i in user.groups.all()]))
           groups = group if isinstance(group, (list, tuple)) else (group, )
           # 这个函数用于跳转,check_group判断是否存在组,然后返回布尔值
           return user_passes_test(check_group, **kwargs)
      
       # 组的装饰器都写了,就不介意自己写一个权限的装饰器吧
       def permission_required(perm, **kwargs):
           perms = perm if isinstance(perm, (list, tuple)) else (perm, )
           return user_passes_test(lambda user:user.has_perms(perms), **kwargs)
      
       # 必须是拥有组manager的用户才可以访问
       @group_required('manager', login_url='/', redirect_field_name=None)
       def manager(request):
           return render(request, 'manager.html')
      
       # 必须是拥有learning.show_all_user权限的才可以访问
       @permission_required('learning.show_all_user', login_url='/', redirect_field_name=None)
       def show_all_user(request):
           return HttpResponse(json.dumps([i.username for i in User.objects.all()]))
       
       def register(request, username, password):
           try:
               user = learningUser.objects.create_user(username=username, password=password)
               # 直接默认给新添加的用户添加manager组(主要是懒)
               user.groups = [get_or_create_group('manager', permissions=['show_all_user'])]
               user.save()
               login(request, username, password)
               return HttpResponseRedirect('/user?username=' + username)
           except: 
               return HttpResponse('已存在用户')
      
    • 现在你用新注册的用户去访问localhost:8000/manager页面就会显示如下:

    • 如果是没有权限的用户就直接返回首页

  1. 好了,现在为止用户系统才基本完善,其实还有很多需要深入的,不过目前为止还是了解到这为好,因为我们又要进入下一轮的进阶学习了。

下期文章:后台学习——django(3)(还无有)

推荐阅读更多精彩内容