經(jīng)過對(duì)django的初步學(xué)習(xí),我們已經(jīng)對(duì)后臺(tái)的基本流程以及django的運(yùn)作有了一定的了解,但是這還不足夠,django還有許多方法和API需要我們?cè)敿?xì)滴學(xué)習(xí),是時(shí)候開始進(jìn)階學(xué)習(xí)了。
上期文章:后臺(tái)學(xué)習(xí)——django(1)
零、上篇文章修改
- 靜態(tài)文件引用修改
- 在上篇文章就說過,要盡可能滴把前端跟后臺(tái)分開,不要前后端代碼混在一起,我們看回之前的
server/learning/templates/index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>登錄注冊(cè)系統(tǒng)</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>帳號(hào):</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"/>注冊(cè)
</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('帳號(hào)不能為空,請(qǐng)重新輸入');
username.select();
}else if(password.value == ''){
alert('密碼不能為空,請(qǐng)重新輸入');
password.select();
}else{
return true;
}
return false;
}
</script>
</body>
</html> - 可以看到,里面還是存在了兩段混入了后臺(tái)代碼了,其中一個(gè)就是靜態(tài)文件引入所用到了
{% load staticfiles %},實(shí)際上,我們可以把這句代碼刪掉,將引入鏈接的部分<link rel="stylesheet" type="text/css" href='{% static "css/index.css" %}' />修改成
<link rel="stylesheet" type="text/css" href="/static/css/index.css"/>,你們可以打開服務(wù)器再次看一下頁(yè)面,實(shí)際效果是一樣的 - 至于
{% csrf_token %}這句代碼,我也沒有什么好的辦法解決,要不就不要用django的防止CSRF模式攻擊功能,要不就換一種傳輸方式,例如ajax,但是這樣做就遠(yuǎn)離了初衷了,還是不要改吧,到時(shí)候后臺(tái)工程師自己修改好了,反正也就是添加一句話的事
一、進(jìn)階學(xué)習(xí)——驗(yàn)證碼
在一個(gè)正常的登錄系統(tǒng)中,驗(yàn)證碼是非常重要的,用于識(shí)別人機(jī),畢竟我們都知道,這個(gè)世界中存在著萬惡的爬蟲,驗(yàn)證碼有很多種方式,有圖片的,有郵件的,有短信的,有拼圖的,不管什么樣的驗(yàn)證碼,目的都是驗(yàn)證訪問用戶到底是人還是機(jī)器,要對(duì)機(jī)器say no,接下來我們要實(shí)踐一個(gè)圖片性的驗(yàn)證碼。
- URL拓展
- 還記得URL設(shè)置文件
urls.py里面,匹配路徑的是用正則表達(dá)式的么,學(xué)過正則表達(dá)式的應(yīng)該會(huì)知道分組吧,其實(shí)在路徑匹配的正則表達(dá)式那里使用分組,django還可以將之匹配出來當(dāng)作參數(shù)給接口函數(shù),不多說先試試效果 - 假設(shè)要通過URL直接傳遞兩個(gè)參數(shù),返回兩個(gè)數(shù)相加的結(jié)果
- 先改
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),
# 通過正則分組匹配兩個(gè)相加的數(shù)字
url(r'^add/(\d+)/(\d+)/$', learning.add),
]
urlpatterns+=static(settings.STATIC_URL,document_root=settings.STATIC_ROOT)
這樣我們可以通過訪問例如localhost:8000/add/3/4/的網(wǎng)址(后面兩個(gè)數(shù)字可以換),來實(shí)現(xiàn)直接傳參的效果 - 再在
server/server/views.py添加接口函數(shù)
def add(request, a, b):
return HttpResponse(str(int(a) + int(b)))
沒錯(cuò),括號(hào)里面的數(shù)字會(huì)當(dāng)作參數(shù)直接就傳到接口函數(shù)里面去了,不過這些參數(shù)都是字符串,如果是要當(dāng)作數(shù)字或者其他類型使用的時(shí)候,記得要轉(zhuǎn)換類型 - 再次懷著激動(dòng)的心情開啟服務(wù)器(為啥要說再次???),輸入網(wǎng)址
localhost:8000/add/4/5/
- 換個(gè)數(shù)字再試一遍
localhost:8000/add/200/300/
Perfect!!!
- 先改
- 下一步我們就要開始構(gòu)造驗(yàn)證碼函數(shù)了,也就是返回一張驗(yàn)證碼圖片的函數(shù),怎么建呢?我們又沒有驗(yàn)證碼圖片在?沒有就直接畫出來唄,python擁有一個(gè)庫(kù)pillow專門用于畫圖的,安裝命令
pip install pillow
- 首先還是要添加路由,在
server/server/urls.py中添加下面的代碼
url(r'^verify/(\d+)/(\d+)/', learning.verify)
大家可以看得到這里用到了上面URL拓展的知識(shí),為的就是使得這驗(yàn)證碼函數(shù)可以得到重用的機(jī)會(huì),那兩個(gè)參數(shù)就是寬度width和高度height,這樣的話以后要用到寬度或者高度不同的驗(yàn)證碼都可以使用這個(gè)函數(shù)了 - 下一步我們就要開始準(zhǔn)備我們的畫筆構(gòu)造我們的驗(yàn)證碼了
- 使用pillow畫圖,第一步就是創(chuàng)建一張畫布,有了畫布我們才可以在上面畫畫對(duì)不,創(chuàng)建畫布代碼如下
# 創(chuàng)建畫布需要導(dǎo)入Image包
from PIL import Image
# 用到了Image的new函數(shù)
# 第一個(gè)參數(shù)是顏色通道,這里使用了RGB通道,還有其他的一些通道,如CMYK之類的,但不用管
# 第二個(gè)參數(shù)是由寬高組成的元組,數(shù)字
# 第三個(gè)參數(shù)是圖片的背景色,這里用rgb的顏色顯示,例如( 255, 255, 255),注意這是元組
img = Image.new('RGB', (width, height), bgColor) - 有了畫布,但我們用什么來畫圖,(難道是。。。畫筆?),廢話,當(dāng)然是,來,給你支畫筆
# 創(chuàng)建畫筆需要導(dǎo)入ImageDraw包
from PIL import ImageDraw
# 用到了ImageDraw的Draw函數(shù)
# 有且只有一個(gè)參數(shù),就是之前創(chuàng)建的畫布
draw = ImageDraw.Draw(img) - 有了畫布和畫筆,但驗(yàn)證碼中是有字的,難道我們真的要像現(xiàn)實(shí)一樣一筆一劃滴寫字么,而且用電腦寫好難看啊,其實(shí)pillow還可以導(dǎo)入電腦的字體,你不用再想些亂七八糟的東西了
# 導(dǎo)入字體需要導(dǎo)入ImageFont包
from PIL import ImageFont
# 用到了ImageFont的truetype函數(shù),可以自動(dòng)查詢電腦中的字體
# 第一個(gè)參數(shù)是字體名字
# 第二個(gè)參數(shù)是字體大小
# 注意這個(gè)是windows系統(tǒng)下默認(rèn)的字體,其他系統(tǒng)自己找
font = ImageFont.truetype('arial.ttf', size)
- 使用pillow畫圖,第一步就是創(chuàng)建一張畫布,有了畫布我們才可以在上面畫畫對(duì)不,創(chuàng)建畫布代碼如下
- 好了,畫布畫筆字體我們都有了,那我們開畫吧,該畫些什么呢。。。。。。。
- 不想那么多,反正驗(yàn)證碼總該有字吧,我們研究怎么寫字
# 寫字需要使用draw的text方法
# 第一個(gè)參數(shù)是一個(gè)坐標(biāo)軸元組,分別是距離左邊和上邊的距離
# 第二個(gè)參數(shù)是要寫的字(字符串)
# 后面的兩個(gè)參數(shù)分別是字體和字體顏色
draw.text((x, y), text, font=font, fill=textColor) - 還有可能要一些干擾元素,例如線條
# 畫線條需要使用draw的line方法
# 第一個(gè)參數(shù)是包含了兩個(gè)坐標(biāo)的元組,分別是線條一頭一尾的坐標(biāo)
# 后面的參數(shù)是線條的顏色
draw.line((x1, y1, x2, y2), fill=lineColor) -
還想畫些什么,算了吧,畫那么復(fù)雜干嘛
- 不想那么多,反正驗(yàn)證碼總該有字吧,我們研究怎么寫字
- 一切都準(zhǔn)備就緒了,開工寫接口函數(shù)
# 導(dǎo)入繪圖對(duì)象
from PIL import Image, ImageFont, ImageDraw
# 導(dǎo)入隨機(jī)函數(shù),randint:生成固定范圍內(nèi)的隨機(jī)整數(shù)
from random import randint
def verify(request, width, height):
wordsCount = 4# 驗(yàn)證碼中的字符長(zhǎng)度
width = int(width)# 圖片寬度
height = int(height)# 圖片高度
size = int(min(width/wordsCount, height)/1.5)# 字體大小設(shè)置
bgColor = (randint(200, 255), randint(200, 255), randint(200, 255))# 隨機(jī)背景色(淺色)
img = Image.new('RGB', (width, height), bgColor)# 創(chuàng)建圖像
font = ImageFont.truetype('Arial.ttf', size)# 導(dǎo)入字體
draw = ImageDraw.Draw(img)# 創(chuàng)建畫筆 - 等等,我們要怎么返回生成的圖片????
- 我們要知道,網(wǎng)頁(yè)間的傳輸都是字符串的傳輸,并沒有其他數(shù)據(jù)結(jié)構(gòu),所以不管你要傳輸什么,都要把它轉(zhuǎn)成字符串
- 圖像的字符串形式實(shí)際上就是二進(jìn)制數(shù)字
-
pillow中并沒有直接返回圖像二進(jìn)制的功能 - 實(shí)際上有個(gè)笨方法就是利用
Image對(duì)象的save方法保存到本地,然后讀取本地文件返回 - 但是每一次請(qǐng)求都要在本地存儲(chǔ)驗(yàn)證碼圖片,會(huì)耗費(fèi)大量的內(nèi)存空間滴,而且這驗(yàn)證碼用過一次就沒用了
- 為了應(yīng)付這需求,python有個(gè)內(nèi)置模塊StringIO,可以將圖片緩存到內(nèi)存里面,讀取后就清空內(nèi)存,是不是很爽
- 廢話不多說,讓我們看看要怎么弄
# 導(dǎo)入StringIO模塊
from StringIO import StringIO
# 建立一個(gè)緩存對(duì)象
mstream = StringIO()
# 將圖片保存到內(nèi)存中
img.save(mstream, 'jpeg')
# 返回內(nèi)存中的圖片
return HttpResponse(mstream.getvalue(), 'image/jpeg')
- 還有還有,我們要畫一個(gè)什么樣驗(yàn)證碼,總該定好一些規(guī)則吧
- 規(guī)則一:均勻繪畫字符,居中
- 規(guī)則二:字符顏色要比較深
- 規(guī)則三:要有線條雪花等干擾元素
- 規(guī)則四:一切能隨機(jī)的都隨機(jī)
- 我***,那么多規(guī)則,怎么畫??!不管了不管了,先想一下文字怎么畫吧,這個(gè)是重點(diǎn)。然而,pillow里面是沒有旋轉(zhuǎn)文字這東西的,就只有圖像本身的旋轉(zhuǎn)
- 首先要確定用什么字符,當(dāng)然是數(shù)字加大小寫字母啦
text = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' - 其次要確定文字的位置,這里需要一定的數(shù)學(xué)知識(shí)(加減乘除,別說你不會(huì))
left = width * i / num + (width / 4 - size) / 2# i為第幾個(gè)文字
top = (height - size) / 2 - 還要確定文字的顏色,要隨機(jī)的顏色,顏色要比較深
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)
- 首先要確定用什么字符,當(dāng)然是數(shù)字加大小寫字母啦
- 再畫雪花,話說雪花是什么東東,呵呵,就是白色的*嘛~~~
- 顏色:白色
textColor = (255, 255, 255) - 位置:隨機(jī)
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)
- 顏色:白色
- 最后畫線條,這個(gè)簡(jiǎn)單
- 位置:頭尾都隨機(jī)
line = (randint(0, width), randint(0, height), randint(0, width), randint(0, height)) - 顏色:隨機(jī)
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)
- 位置:頭尾都隨機(jī)
- OK,把所有的步驟都合起來就是結(jié)果了
# -- 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') - 保存,打開服務(wù)器,打開網(wǎng)址
localhost:8000/verify/100/40/,你會(huì)看到這樣的一張圖片
-
刷新一下
- 換個(gè)網(wǎng)址
localhost:8000/verify/200/40/
Paste_Image.png - 非常完美不是么
- 然而的然而,你還沒完成呢??!只有驗(yàn)證碼圖片卻不能驗(yàn)證有什么用??!
要起到驗(yàn)證的效果首先就要保存相應(yīng)的文字,那么就要在寫文字那里保存相應(yīng)的文字
verifyText然后然后還要將之保存在session里面
request.session['verify'] = verifytext這個(gè)函數(shù)基本完成了,但一個(gè)完整的驗(yàn)證碼系統(tǒng)還是不夠的,我們來完善以下,接下來由于個(gè)人問題,實(shí)在不想看到之前的代碼了,會(huì)將之前沒用的清掉,我會(huì)把變動(dòng)的文件代碼全部給出(這算做潔癖么,還是強(qiáng)迫癥)
更改后的文件目錄
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 settingsurlpatterns = [ 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>
這個(gè)html文件通過verify/300/80/顯示一張300x80的驗(yàn)證碼,點(diǎn)擊提交按鈕后提交到/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# 頁(yè)面接口:返回`index.html`頁(yè)面 def index(request): return render(request, 'index.html') # 功能接口:返回驗(yàn)證碼輸入正確與否(忽略大小寫) def check(request): if request.POST['verify'].lower() == request.session['verify'].lower(): return HttpResponse('success') else: return HttpResponse('failed') # 功能接口:返回驗(yàn)證碼圖片 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')
- OK,開始驗(yàn)證成果
- 打開網(wǎng)址
localhost:8000,顯示頁(yè)面如下(驗(yàn)證碼圖片可能不同):
-
輸入正確的驗(yàn)證碼
- 點(diǎn)擊提交后,顯示
success
- 刷新頁(yè)面
localhost:8000,重復(fù)上面的步驟,輸入錯(cuò)誤的驗(yàn)證碼,顯示failed
- 到此,一個(gè)完整的驗(yàn)證碼就完成了,如果你有更好的想象力,還可以畫出更好的驗(yàn)證碼,深究請(qǐng)查閱
pillow文檔
二、進(jìn)階學(xué)習(xí)——用戶管理系統(tǒng)
- 先不說那么多,先清空兩個(gè)文件先(真的是強(qiáng)迫癥),分別是:
-
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. - 清完同步數(shù)據(jù)庫(kù):
python manage.py makemigrations
python manage.py migrate
- 再修改
server/learning/templates/idnex.html,用于應(yīng)用相關(guān)接口函數(shù):
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>登錄注冊(cè)系統(tǒng)</title>
</head>
<body>
<div>
<form action="check/" method="post">
{% csrf_token %}
<table>
<tr>
<th>帳號(hào):</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>驗(yàn)證碼</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"/>注冊(cè)</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('帳號(hào)不能為空,請(qǐng)重新輸入');
username.select();
} else if(password.value == '') {
alert('密碼不能為空,請(qǐng)重新輸入');
password.select();
} else if(verify.value.length != 4){
alert('驗(yàn)證碼輸入錯(cuò)誤,請(qǐng)重新輸入');
verify.select();
} else {
return true;
}
return false;
}
</script>
</body>
</html>
- 打開服務(wù)器,進(jìn)入網(wǎng)址
localhost:8000,結(jié)果如下:
-
很難看,不過將就一下吧
-
修改
server/learning/views.py文件中的check函數(shù),并添加兩個(gè)空函數(shù):
# check函數(shù)的作用已經(jīng)在之前涉及過了,這里就不講了
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('驗(yàn)證碼錯(cuò)誤')def login(request, username, password): pass def register(request, username, password): pass OK,終于準(zhǔn)備好所有東西了,我們開始研究django的用戶認(rèn)證系統(tǒng)吧
- 用戶的基本操作一共有四個(gè),分別是:
- 注冊(cè)
- 登錄
- 登出
- 判斷是否已經(jīng)登錄
- django封裝了一個(gè)比較完善的用戶認(rèn)證系統(tǒng),各種操作都非常方便,注冊(cè)便是如此
- 添加一個(gè)用戶只需用到下面的代碼:
from django.contrib.auth.models import User
# username為用戶名,password為密碼
User.objects.create_user(username=username, password=password).save() - 然而注冊(cè)需要考慮到一種狀況:用戶已存在,其實(shí)如果是這樣的話,在創(chuàng)建的時(shí)候就會(huì)報(bào)錯(cuò),做好錯(cuò)誤處理就好
try:
User.objects.create_user(username=username, password=password).save()
return '注冊(cè)成功'
except:
return '已存在用戶'
- 添加一個(gè)用戶只需用到下面的代碼:
- 有了注冊(cè)好的用戶,自然需要登錄:
- 登錄之前肯定要驗(yàn)證帳號(hào)密碼是否正確啦
from django.contrib.auth import authenticate
# username為用戶名,password為密碼
# 該函數(shù)會(huì)返回一個(gè)user對(duì)象,如果不存在該用戶或者密碼錯(cuò)誤則返回None
authenticate(username=username, password=password) - 驗(yàn)證正確自然要儲(chǔ)存登錄信息,通常而言都會(huì)用session來存儲(chǔ),而django對(duì)此方法進(jìn)行了包裝,只需使用以下代碼就行了:
from django.contrib.auth import login
# request為請(qǐng)求對(duì)象,user為用戶對(duì)象
login(request, user)
- 登錄之前肯定要驗(yàn)證帳號(hào)密碼是否正確啦
- 登錄后,怎么登出呢,其實(shí)這個(gè)更簡(jiǎn)單,其他后臺(tái)或許直接清掉session就可以了,django也一樣將其封裝好了:
from django.contrib.auth import logout
# request為請(qǐng)求對(duì)象
logout(request) - 最后一個(gè),判斷是否已經(jīng)登錄,這個(gè)django用
request對(duì)象中的user對(duì)象中的is_authenticated()方法(此處應(yīng)該有哭笑不得的表情)
# 用戶登錄時(shí),該函數(shù)返回True,否則返回False
request.user.is_authenticated()
-
OK,一切都準(zhǔn)備就緒后就可以寫之前那兩個(gè)函數(shù)了,代碼如下:
from django.contrib.auth.models import User
from django.contrib import authdef login(request, username, password): user = auth.authenticate(username=username, password=password) if user is None: return '登錄錯(cuò)誤' else: auth.login(request, user) return '登錄成功' def register(request, username, password): try: User.objects.create_user(username=username, password=password).save() # 注冊(cè)后默認(rèn)為登錄狀態(tài) login(request, username, password) return '注冊(cè)成功' except: return '已存在用戶'
- 大家可以打開服務(wù)器試一下,分別注冊(cè)新的用戶,注冊(cè)已經(jīng)存在相同用戶名的用戶,登錄帳號(hào)密碼正確的用戶, 登錄不存在的用戶,登錄密碼錯(cuò)誤的用戶,是不是都如想象中一般返回了想要返回的信息?是的話就恭喜你完成了登錄和注冊(cè)的操作了
- 然而還沒有完呢,登錄注冊(cè)完總該有個(gè)用戶界面吧,在用戶界面總該有登出的設(shè)置吧,訪問用戶界面時(shí)總該有確認(rèn)是否已經(jīng)登錄了吧
為了有一個(gè)更好的應(yīng)用場(chǎng)景,這里需要添加一個(gè)html文件
server/learning/templates/user.html,內(nèi)容如下:
<!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>
現(xiàn)在的目錄如下:
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文件,接上對(duì)應(yīng)的接口函數(shù):
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)
]-
最后寫上各個(gè)接口函數(shù)就搞定啦,具體細(xì)節(jié)看注釋:
# -- coding:utf-8 --
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
from django.shortcuts import render
# 這里新導(dǎo)入了一個(gè)HttpResponseRedirect函數(shù),用于頁(yè)面的重定向
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 authdef index(request): return render(request, 'index.html') def user(request): # 使用request.user.is_authenticated()來判斷是否已經(jīng)登錄 if request.user.is_authenticated(): return render(request, 'user.html') else: # 如果沒有登錄的話自動(dòng)重定向到首頁(yè) return HttpResponseRedirect('/') def logout(request): # 登出設(shè)置,并重定向到首頁(yè) 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': # 這里修改為直接返回函數(shù)返回的值 return login(request, username, password) elif request.POST['way'] == 'register': return register(request, username, password) else: return HttpResponse('驗(yàn)證碼錯(cuò)誤') def login(request, username, password): user = auth.authenticate(username=username, password=password) if user is None: return HttpResponse('登錄錯(cuò)誤') else: auth.login(request, user) # 如果登錄成功就自動(dòng)跳轉(zhuǎn)到用戶頁(yè)面 return HttpResponseRedirect('/user?username=' + username) def register(request, username, password): try: User.objects.create_user(username=username, password=password).save() # 剛注冊(cè)完肯定可以登錄成功,直接返回用戶頁(yè)面 return login(request, username, password) except: return HttpResponse('已存在用戶') # 驗(yàn)證碼函數(shù)已省略這里解析一下什么是重定向,其實(shí)也就是服務(wù)器端的網(wǎng)頁(yè)跳轉(zhuǎn),重定向后就轉(zhuǎn)到不同的路徑去了,比如你要訪問a網(wǎng)頁(yè),在地址欄輸入a網(wǎng)頁(yè)的URL,但a網(wǎng)頁(yè)會(huì)重定向到b網(wǎng)頁(yè),那么返回來的是b網(wǎng)頁(yè)的信息,而且地址欄也是顯示b網(wǎng)頁(yè)的URL(當(dāng)然這個(gè)只是給一臉懵的人看的,想了解更詳細(xì)的信息,就看百度百科的解析吧)
- 我們來測(cè)試一下成果,如果你看到的跟下面的一樣就證明你已經(jīng)搞定了
- 打開服務(wù)器,進(jìn)入網(wǎng)址
localhost:8000,進(jìn)入首頁(yè):
-
注冊(cè)一個(gè)正確的帳號(hào),自動(dòng)跳轉(zhuǎn)到用戶頁(yè)面:
-
點(diǎn)擊注銷,跳轉(zhuǎn)到首頁(yè):
-
注冊(cè)一個(gè)已經(jīng)存在的用戶,返回錯(cuò)誤信息:
-
返回首頁(yè)輸入正確的帳號(hào)密碼,登錄帳號(hào),自動(dòng)跳轉(zhuǎn)到用戶頁(yè)面:
-
注銷返回首頁(yè),登錄不存在的用戶,返回錯(cuò)誤信息:
-
返回首頁(yè),登錄密碼錯(cuò)誤的用戶,返回錯(cuò)誤信息:
- 基本功能通通都實(shí)現(xiàn)了,然而還不足夠完善,我們還需要更多的功能
- 我們可以發(fā)現(xiàn),之前的代碼在出現(xiàn)登錄錯(cuò)誤的時(shí)候是無法分辨是用戶不存在還是密碼錯(cuò)誤的,而且我們?nèi)蘸罂隙〞?huì)經(jīng)常需要查詢用戶存不存在的信息,不一定要用戶密碼都正確才可以查詢得到。但是django的用戶認(rèn)證模塊并區(qū)分這兩個(gè)錯(cuò)誤,不過我們可以發(fā)現(xiàn)用戶認(rèn)證是基于django的模型的,也就是之前說的數(shù)據(jù)庫(kù)操作那里,我們可以通過數(shù)據(jù)庫(kù)操作的方法只通過用戶名來查詢是否存在該用戶,登錄接口函數(shù)修改如下:
def login(request, username, password):
try:
# 當(dāng)不存在該用戶時(shí)會(huì)出現(xiàn)錯(cuò)誤
User.objects.get(username=username)
user = auth.authenticate(username=username, password=password)
if user is None:
return HttpResponse('密碼錯(cuò)誤')
else:
auth.login(request, user)
return HttpResponseRedirect('/user?username=' + username)
except:
return HttpResponse('不存在該用戶') - 還有,我們以后會(huì)有大量的頁(yè)面需要登錄用戶才可以訪問,但如果每次都使用
request.user.is_authenticated()來判斷會(huì)顯得很麻煩,有沒有更好的方法呢?答案是有的,django在設(shè)計(jì)的時(shí)候就已經(jīng)考慮到這個(gè)問題了,它提供了一種裝飾器,只要在需要登錄后訪問的頁(yè)面接口函數(shù)前加上這個(gè)裝飾器就可以自動(dòng)判斷是否已經(jīng)登錄了,使用方法如下:
# 導(dǎo)入裝飾器
from django.contrib.auth.decorators import login_required
# 使用裝飾器,在函數(shù)前@這個(gè)裝飾器
# 參數(shù)login_url為沒有登錄的時(shí)候需要跳轉(zhuǎn)到哪里,這里設(shè)置為跳轉(zhuǎn)到首頁(yè)
# 參數(shù)redirect_field_name為重定向時(shí)傳輸當(dāng)前的頁(yè)面路徑,這里設(shè)置為沒有
@login_required(login_url='/', redirect_field_name=None)
def user(request):
# 不再需要判斷是否已經(jīng)登錄了
return render(request, 'user.html') - 有時(shí)候我們存儲(chǔ)的用戶信息可不單單只有這些(包括username、first_name、last_name、email、password),我們還需要額外添加一些用戶信息,如登錄時(shí)間,注冊(cè)時(shí)間(其實(shí)也有滴,不過我就懶得想其他啦,反正原理都一樣)等,但是如果直接更改原本的模型又很麻煩,這個(gè)時(shí)候就需要使用模型的代理繼承功能了。
模型的代理繼承實(shí)際上借用了python類的繼承,修改
server/learning/models.py代碼如下:
from future import unicode_literals
from django.db import models
from django.contrib.auth.models import User
# 繼承與django默認(rèn)的User模型
class learningUser(User):
# 添加兩個(gè)字段,分別是登錄日期和注冊(cè)日期(自動(dòng)填入)
logindate = models.DateField(auto_now=True)
registerdate = models.DateField(auto_now_add=True)實(shí)際上,這并不是一種正規(guī)的寫法(但經(jīng)驗(yàn)證,是可以這樣做的),正規(guī)的寫法如下:
class learningUser(models.Model):
# 設(shè)置與User對(duì)象一一對(duì)應(yīng)
user = models.OneToOneField(User)
logindate = models.DateField(auto_now=True)
registerdate = models.DateField(auto_now_add=True)
但是這樣一來引用的時(shí)候會(huì)顯得很麻煩,創(chuàng)建用戶的時(shí)候還要?jiǎng)?chuàng)建多一個(gè)對(duì)象,而且到時(shí)候要獲取用戶的logindate的時(shí)候還要用user.learningUser.logindate來獲取,雖然這樣可以動(dòng)態(tài)改變相關(guān)model,但如果當(dāng)前項(xiàng)目中每一個(gè)用戶都需要有這個(gè)拓展就顯得很麻煩了,所以之后還是以第一種方法來拓展User模型接下來就是模型改動(dòng)必要的程序了,記住以后都要這樣做
修改server/learning/admin.py文件:
from django.contrib import admin
from learning.models import learningUser
admin.site.register(learningUser)
終端執(zhí)行代碼:
python manage.py makemigrations
python manage.py migrate-
最后一步就是修改接口了,將
from django.contrib.auth.models import User替換成from learning.models import learningUser
修改登錄注冊(cè)函數(shù)為如下:
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('密碼錯(cuò)誤')
else:
auth.login(request, user)
# 輸出我們之前新添加的兩個(gè)屬性(懶的在頁(yè)面上顯示了)
print guest.logindate, guest.registerdate
return HttpResponseRedirect('/user?username=' + username)
except:
return HttpResponse('不存在該用戶')def register(request, username, password): try: # 記住也要用learningUser創(chuàng)建對(duì)象 learningUser.objects.create_user(username=username, password=password).save() login(request, username, password) return HttpResponseRedirect('/user?username=' + username) except: return HttpResponse('已存在用戶')這里要提醒的是如果使用的是django用戶認(rèn)證的函數(shù),他們的操作對(duì)象都是最原本的
User對(duì)象,而不是我們新創(chuàng)建的用戶對(duì)象 -
我們重新注冊(cè)一個(gè)帳號(hào),在終端可以看到登錄和注冊(cè)的日期
- 如果我們要對(duì)用戶進(jìn)行分級(jí)又該怎么辦呢?比如要分管理員和普通用戶(注意這里不是可以進(jìn)入admin頁(yè)面的超級(jí)用戶,管理員可以管理用戶,刪除用戶等),或者更負(fù)責(zé)的分類又怎么辦呢?其實(shí)這里涉及到用戶權(quán)限還有組的問題,組用于標(biāo)識(shí)用戶的類別(一個(gè)用戶可以多個(gè)類),權(quán)限對(duì)應(yīng)具體的權(quán)限,一個(gè)組可以對(duì)應(yīng)多個(gè)權(quán)限。說這么多,不用理解,先看下面就會(huì)明白(我會(huì)告訴你我也研究了好久么):
首先我們要知道權(quán)限是什么,有什么用。權(quán)限通俗來講就是你能不能夠做某種事,就像男人不能生孩子(先天性上帝沒有給你權(quán)限,除了某個(gè)從上帝盜取的權(quán)限的人),權(quán)限的存在可以很方便滴管理用戶,一些特殊的功能就只能給有權(quán)限的用戶使用,而組就是對(duì)用戶的一種分類,組包含了多個(gè)權(quán)限,用戶屬于這個(gè)組的話就擁有了這些權(quán)限,這樣就不需要很麻煩滴對(duì)用戶添加多個(gè)權(quán)限了,而且分組后還可以更直接滴對(duì)用戶進(jìn)行管理,比如一個(gè)班有部分人當(dāng)選了組長(zhǎng)(組),他們可以管理組員,檢查作業(yè)、管理組員、組織小組活動(dòng)等(權(quán)限),如果老師需要所有人的名單,可以直接讓組長(zhǎng)提供小組名單(對(duì)組的管理),這樣一來就不需要一個(gè)一個(gè)權(quán)限的管理了,上升到組的層次。
最近廢話有點(diǎn)多,還是專心研究代碼吧(以下代碼在
server/learning/views.py文件寫的)。首先你要懂得怎樣創(chuàng)建一個(gè)新的權(quán)限:
# 在django中權(quán)限就是一個(gè)Permission模型的實(shí)例
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
# 用get_or_create防止重復(fù)創(chuàng)建
newPermission = Permission.objects.get_or_create(**{
# 創(chuàng)建Permission對(duì)象需要三個(gè)參數(shù)(都是必須的)
# codename是代碼中使用的名字
# name是顯示出來的名字
# content_type要通過創(chuàng)建ContentType對(duì)象獲取一個(gè)該模型的ContentType實(shí)例
'codename': 'new_permission',
'name': 'New Permission',
'content_type': ContentType.objects.get_for_model(learningUser)
})[0]
本質(zhì)上講ContentType實(shí)例可以從更高的層次操作模型(這里操作learningUser模型,不過你們都不需要知道啦,現(xiàn)在知道怎么寫就好,重點(diǎn)是codename和name)創(chuàng)建之后總該要添加吧,其實(shí)添加很簡(jiǎn)單,假設(shè)user是一個(gè)User的實(shí)例(實(shí)際上可以是User的繼承模型,如我們之前設(shè)置的learningUser模型):
# 注意所有的newPermission都是Permission的實(shí)例對(duì)象
# 設(shè)定user對(duì)象的權(quán)限為列表中的權(quán)限
user.permission = [newPermission1, newPermission2, ...]
# 向user對(duì)象添加權(quán)限
user.permission.add(newPermission1, newPermission2, ...)
# 移除user對(duì)象中的權(quán)限
user.permission.remove(newPermission1, newPermission2, ...)
# 清空user對(duì)象的權(quán)限
user.permission.clear()user對(duì)象有了權(quán)限,那么我們應(yīng)該怎么判斷user到底有沒有該權(quán)限呢?這時(shí)候就需要調(diào)用user對(duì)象的
has_perm()或者has_perms()方法了
# 還記得之前寫的codename么,判斷權(quán)限的時(shí)候就需要用到了
# 格式為:app_name.codename
user.has_perm('learning.new_permission')
# 這個(gè)是判斷同時(shí)擁有多個(gè)權(quán)限的(傳一個(gè)列表或者元組過去)
user.has_perms(['learning.new_permission'])像登錄一樣,權(quán)限判斷也有相對(duì)因的裝飾器在需要權(quán)限的函數(shù)前面寫上這個(gè)裝飾器就可以了
# 第一個(gè)參數(shù)是權(quán)限(多個(gè)權(quán)限時(shí),傳遞一個(gè)列表)
@permission_required('learning.new_permission', login_url='/')權(quán)限搞定了,那么組又該怎么做呢?其實(shí)組也有相對(duì)應(yīng)的模型Group,每個(gè)組都是Group的實(shí)例,我們先創(chuàng)建一個(gè):
newGroup = Group.objects.get_or_create(name='manager')[0]這個(gè)時(shí)候,這個(gè)組是不沒有任何的權(quán)限的,我們要添加權(quán)限到組里面,添加方法跟添加到User對(duì)象類似:
# 注意所有的newPermission都是Permission的實(shí)例對(duì)象
# 設(shè)定newGroup對(duì)象的權(quán)限為列表中的權(quán)限
newGroup.permission = [newPermission1, newPermission2, ...]
# 向newGroup對(duì)象添加權(quán)限
newGroup.permission.add(newPermission1, newPermission2, ...)
# 移除newGroup對(duì)象中的權(quán)限
newGroup.permission.remove(newPermission1, newPermission2, ...)
# 清空newGroup對(duì)象的權(quán)限
newGroup.permission.clear()創(chuàng)建好組就可以添加到user對(duì)象中了,添加方法還是一樣滴:
# 注意所有的newGroup都是Group的實(shí)例對(duì)象
# 設(shè)定user對(duì)象的權(quán)限為列表中的組
user.groups= [newGroup1, newGroup2, ...]
# 向user對(duì)象添加組
user.groups.add(newGroup1, newGroup2, ...)
# 移除user對(duì)象中的組
user.groups.remove(newGroup1, newGroup2, ...)
# 清空user對(duì)象的組
user.groups.clear()用戶屬于一個(gè)組,就擁有了這個(gè)組所包含的權(quán)限,判斷有沒有權(quán)限的方法跟之前用戶直接擁有某權(quán)限的方法一樣,這里就不寫了
為了更好滴應(yīng)用到權(quán)限和組的功能,這里用一個(gè)實(shí)例(只有是manager的用戶才可以查看所有的用戶名字)來演示
先添加一個(gè)html文件
server/learning/templates/manager.html,內(nèi)容如下:
<!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 = '當(dāng)前有以下用戶<br />'
for(var i = 0; i < data.length; i++){
write += data[i] + '<br />'
}
document.write(write);
}
});
</script>
</body>
</html>修改
server/server/urls.py文件,添加以下幾個(gè)路徑:
url(r'^manager$', learning.manager),
url(r'^show_all_user/$', learning.show_all_user),-
修改
server/learning/views.py文件,以下列出要新導(dǎo)入的方法和要修改或者添加的函數(shù):
from django.contrib.auth.decorators import user_passes_test
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType# 獲取組的函數(shù),傳遞組的名字和對(duì)應(yīng)的權(quán)限名(列表,自動(dòng)創(chuàng)建) 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 # 因?yàn)閐jango沒有提供判斷是否有組的裝飾器,自己就寫了一個(gè) # 這個(gè)裝飾器跟權(quán)限裝飾器一樣,是不過傳遞的組名而不是權(quán)限名 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, ) # 這個(gè)函數(shù)用于跳轉(zhuǎn),check_group判斷是否存在組,然后返回布爾值 return user_passes_test(check_group, **kwargs) # 組的裝飾器都寫了,就不介意自己寫一個(gè)權(quán)限的裝飾器吧 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') # 必須是擁有l(wèi)earning.show_all_user權(quán)限的才可以訪問 @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) # 直接默認(rèn)給新添加的用戶添加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('已存在用戶') -
現(xiàn)在你用新注冊(cè)的用戶去訪問
localhost:8000/manager頁(yè)面就會(huì)顯示如下:
如果是沒有權(quán)限的用戶就直接返回首頁(yè)
- 好了,現(xiàn)在為止用戶系統(tǒng)才基本完善,其實(shí)還有很多需要深入的,不過目前為止還是了解到這為好,因?yàn)槲覀冇忠M(jìn)入下一輪的進(jìn)階學(xué)習(xí)了。

















